QA katselmoi, DevOps keskittyy deploymenttiin

- Review-luuppi siirretty DevOps→QA: QA katselmoi koodin ja
  lähettää korjausvaatimukset Coderille (max 3 kierrosta)
- QA:n prompt laajennettu: review-checklist + testien kirjoitus
- DevOps:n prompt uusittu: Dockerfile + deployment -fokus
- Pipeline: Client→Manager→Coder→QA review↔Coder fix→QA testit→DevOps Dockerfile→Observer
- AGENTS_VERSION 4→5
This commit is contained in:
Jaakko Vanhala
2026-04-12 15:55:45 +03:00
parent 1f85c03624
commit cd67562a67

View File

@@ -286,26 +286,9 @@ ALWAYS INCLUDE:
- DATABASE_URL, engine, SessionLocal, Base` }, - DATABASE_URL, engine, SessionLocal, Base` },
qa: { name: 'QA', avatar: '/avatars/susi_notext.webp', model: 'qwen-coder', order: 4, qa: { name: 'QA', avatar: '/avatars/susi_notext.webp', model: 'qwen-coder', order: 4,
temperature: 0.4, topK: 40, repeatPenalty: 1.15, maxTokens: 1024, temperature: 0.4, topK: 40, repeatPenalty: 1.15, maxTokens: 1024,
prompt: `You are a QA engineer writing automated tests. prompt: `You are a QA engineer responsible for code review and automated testing.
WRITE TESTS USING: CODE REVIEW CHECKLIST:
- pytest as the test framework
- FastAPI TestClient for API endpoint testing
- SQLAlchemy in-memory SQLite for test database isolation
TEST STRUCTURE:
1. test_create: POST valid data → 201, verify response matches input
2. test_list: GET collection → 200, verify array response
3. test_get_by_id: GET with valid/invalid ID → 200/404
4. test_update: PUT with valid data → 200, verify changes persisted
5. test_delete: DELETE → 204, verify GET returns 404 after
ALWAYS: from fastapi.testclient import TestClient` },
tester: { name: 'DevOps', avatar: '/avatars/laiskiainen_notext.webp', model: 'qwen-coder', order: 5,
temperature: 0.3, topK: 40, repeatPenalty: 1.1, maxTokens: 1024,
prompt: `You are a strict code reviewer and static analysis expert. Analyze the code line by line.
STATIC ANALYSIS CHECKLIST:
1. IMPORTS: Every "from X import Y" must match an actual export in file X 1. IMPORTS: Every "from X import Y" must match an actual export in file X
2. NAMES: Pydantic schemas (UserCreate) must not shadow SQLAlchemy models (User) 2. NAMES: Pydantic schemas (UserCreate) must not shadow SQLAlchemy models (User)
3. TYPES: All function parameters have type hints, return types specified 3. TYPES: All function parameters have type hints, return types specified
@@ -316,10 +299,35 @@ STATIC ANALYSIS CHECKLIST:
8. MODELS: Pydantic Config has from_attributes=True, uses model_dump() not dict() 8. MODELS: Pydantic Config has from_attributes=True, uses model_dump() not dict()
9. COMPLETENESS: No placeholder comments, no "TODO", no "pass" in handlers 9. COMPLETENESS: No placeholder comments, no "TODO", no "pass" in handlers
RESPOND: WHEN REVIEWING:
- If all checks pass: "LGTM" - If all checks pass: respond "LGTM"
- If issues found: list each as "ISSUE: filename.py: description" - If issues found: list each as "ISSUE: filename.py: description"
- Be specific and actionable, not vague` }, - Be specific and actionable, not vague
WHEN WRITING TESTS:
- pytest as the test framework
- FastAPI TestClient for API endpoint testing
- SQLAlchemy in-memory SQLite for test database isolation
- Test all CRUD: create (201), list (200), get by id (200/404), update (200), delete (204)
- ALWAYS: from fastapi.testclient import TestClient` },
tester: { name: 'DevOps', avatar: '/avatars/laiskiainen_notext.webp', model: 'qwen-coder', order: 5,
temperature: 0.3, topK: 40, repeatPenalty: 1.1, maxTokens: 1024,
prompt: `You are a DevOps engineer specializing in containerization and deployment.
YOUR RESPONSIBILITIES:
1. Write production-ready Dockerfiles
2. Use multi-stage builds when appropriate
3. Follow security best practices (non-root user, minimal base image)
4. Configure health checks and proper signal handling
DOCKERFILE RULES:
- Use python:3.12-slim as base
- Install uv: COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
- Copy pyproject.toml first, then uv sync, then copy source
- Expose appropriate ports
- Use uv run for CMD
Write ONLY the requested files, no explanations.` },
observer: { name: 'Observer', avatar: '/avatars/aikuinen_susi.webp', model: 'qwen-coder', order: 6, observer: { name: 'Observer', avatar: '/avatars/aikuinen_susi.webp', model: 'qwen-coder', order: 6,
temperature: 0.6, topK: 40, repeatPenalty: 1.15, maxTokens: 1024, temperature: 0.6, topK: 40, repeatPenalty: 1.15, maxTokens: 1024,
prompt: `You are an independent technical observer and risk analyst. prompt: `You are an independent technical observer and risk analyst.
@@ -336,7 +344,7 @@ OUTPUT FORMAT:
- End with overall assessment: "SHIP IT" or "NEEDS WORK: reason"` }, - End with overall assessment: "SHIP IT" or "NEEDS WORK: reason"` },
}; };
// Versio: kasvata kun oletuspromptit muuttuvat // Versio: kasvata kun oletuspromptit muuttuvat
const AGENTS_VERSION = 4; const AGENTS_VERSION = 5;
let agents; let agents;
const savedVersion = parseInt(localStorage.getItem('kpn-agents-version') || '0'); const savedVersion = parseInt(localStorage.getItem('kpn-agents-version') || '0');
if (savedVersion < AGENTS_VERSION && localStorage.getItem('kpn-agents')) { if (savedVersion < AGENTS_VERSION && localStorage.getItem('kpn-agents')) {
@@ -1129,41 +1137,41 @@ OUTPUT FORMAT:
const allCode = Object.entries(files).map(([n,c]) => `--- ${n} ---\n${c}`).join('\n\n'); const allCode = Object.entries(files).map(([n,c]) => `--- ${n} ---\n${c}`).join('\n\n');
let stepN = fileOrder.length + 1; let stepN = fileOrder.length + 1;
// Review-korjausluuppi: max 2 kierrosta // QA-katselmointi → Coder-korjaus -luuppi (max N kierrosta)
const tst = agents.tester || Object.values(agents)[5]; const qaAgent = agents.qa || Object.values(agents)[4];
const MAX_REVIEW_ROUNDS = pipelineConfig.maxReviewRounds; const MAX_REVIEW_ROUNDS = pipelineConfig.maxReviewRounds;
for (let round = 0; round < MAX_REVIEW_ROUNDS; round++) { for (let round = 0; round < MAX_REVIEW_ROUNDS; round++) {
const currentCode = Object.entries(files).map(([n,c]) => `--- ${n} ---\n${c}`).join('\n\n'); const currentCode = Object.entries(files).map(([n,c]) => `--- ${n} ---\n${c}`).join('\n\n');
// DevOps review // QA katselmoi
termLog(`\n<span style="color:var(--accent);font-weight:bold">[${stepN}] ${esc(tst.name)}</span> — code review${round > 0 ? ' (round '+(round+1)+')' : ''}`); termLog(`\n<span style="color:#d2a8ff;font-weight:bold">[${stepN}] ${esc(qaAgent.name)}</span> — katselmointi${round > 0 ? ' (kierros '+(round+1)+')' : ''}`);
highlightAgent('tester'); highlightAgent('qa');
if (round === 0) explainStep('Code Review', `${tst.name} reviews code line by line: imports, naming, security.`); if (round === 0) explainStep('Katselmointi', `${qaAgent.name} analysoi koodin: importit, nimeämiset, virheenkäsittely, tietoturva.`);
else explainStep('Re-evaluation', `${tst.name} checks the applied fixes.`); else explainStep('Uudelleentarkistus', `${qaAgent.name} tarkistaa korjaukset.`);
const reviewPrompt = (tst.prompt ? tst.prompt+'\n\n' : '') + `Review this project:\n\n${currentCode}`; const reviewPrompt = (qaAgent.prompt ? qaAgent.prompt+'\n\n' : '') + `Review this project code for issues. If everything is correct, respond with "LGTM". Otherwise list issues as "ISSUE: filename.py: description".\n\n${currentCode}`;
const review = await kpnRun(tst.model, reviewPrompt, false, tst); const review = await kpnRun(qaAgent.model, reviewPrompt, false, qaAgent);
promptLog.push({ step: promptLog.length, agentKey: 'tester', agentName: tst.name, model: tst.model, label: 'review' + (round > 0 ? '_r'+(round+1) : ''), systemPrompt: tst.prompt || '', userPrompt: reviewPrompt, response: review || '' }); promptLog.push({ step: promptLog.length, agentKey: 'qa', agentName: qaAgent.name, model: qaAgent.model, label: 'review' + (round > 0 ? '_r'+(round+1) : ''), systemPrompt: qaAgent.prompt || '', userPrompt: reviewPrompt, response: review || '' });
stepN++; stepN++;
// LGTM → ei korjauksia tarvita // LGTM → ei korjauksia tarvita
if (!review || review.toLowerCase().includes('lgtm')) { if (!review || review.toLowerCase().includes('lgtm')) {
termLog(` <span style="color:#3fb950">✓ ${esc(tst.name)}: LGTM</span>`); termLog(` <span style="color:#3fb950">✓ ${esc(qaAgent.name)}: LGTM</span>`);
break; break;
} }
// Korjaukset // Korjaukset → Coder
termLog(`\n<span style="color:#d29922;font-weight:bold">[${stepN}] ${esc(cdr.name)}</span> — fixes${round > 0 ? ' (round '+(round+1)+')' : ''}`); termLog(`\n<span style="color:#d29922;font-weight:bold">[${stepN}] ${esc(cdr.name)}</span> — korjaukset${round > 0 ? ' (kierros '+(round+1)+')' : ''}`);
highlightAgent('coder'); highlightAgent('coder');
explainStep('Fixing', `${tst.name} found issues. ${cdr.name} will attempt to fix them.`); explainStep('Korjaus', `${qaAgent.name} löysi ongelmia. ${cdr.name} saa palautteen ja korjaa.`);
const fixPrompt = `${cdr.prompt ? cdr.prompt+'\n\n' : ''}Fix these issues:\n${review}\n\nCurrent code:\n${currentCode}\n\nWrite ALL corrected files. Start each file with: --- filename.py ---`; const fixPrompt = `${cdr.prompt ? cdr.prompt+'\n\n' : ''}Fix these issues:\n${review}\n\nCurrent code:\n${currentCode}\n\nWrite ALL corrected files. Start each file with: --- filename.py ---`;
const fixedCode = await kpnRun(cdr.model, fixPrompt, false, cdr); const fixedCode = await kpnRun(cdr.model, fixPrompt, false, cdr);
// Parsitaan korjatut tiedostot takaisin files-objektiin // Parsitaan korjatut tiedostot takaisin files-objektiin
if (fixedCode) { if (fixedCode) {
promptLog.push({ step: promptLog.length, agentKey: 'coder', agentName: cdr.name, model: cdr.model, label: 'fix' + (round > 0 ? '_r'+(round+1) : ''), systemPrompt: cdr.prompt || '', userPrompt: fixPrompt, response: fixedCode }); promptLog.push({ step: promptLog.length, agentKey: 'coder', agentName: cdr.name, model: cdr.model, label: 'korjaus' + (round > 0 ? '_r'+(round+1) : ''), systemPrompt: cdr.prompt || '', userPrompt: fixPrompt, response: fixedCode });
const fixedParts = fixedCode.split(/^---\s*(\S+)\s*---$/m); const fixedParts = fixedCode.split(/^---\s*(\S+)\s*---$/m);
for (let j = 1; j < fixedParts.length; j += 2) { for (let j = 1; j < fixedParts.length; j += 2) {
const fname = fixedParts[j].trim(); const fname = fixedParts[j].trim();
@@ -1179,22 +1187,22 @@ OUTPUT FORMAT:
// Päivitetään allCode korjausten jälkeen // Päivitetään allCode korjausten jälkeen
const updatedCode = Object.entries(files).map(([n,c]) => `--- ${n} ---\n${c}`).join('\n\n'); const updatedCode = Object.entries(files).map(([n,c]) => `--- ${n} ---\n${c}`).join('\n\n');
// QA: testit (saa korjatut tiedostot) // QA: testit (saa katselmoidut ja korjatut tiedostot)
const qaAgent = agents.qa || Object.values(agents)[4];
if (qaAgent) { if (qaAgent) {
termLog(`\n<span style="color:#d2a8ff;font-weight:bold">[${stepN}] ${esc(qaAgent.name)}</span> — testit`); termLog(`\n<span style="color:#d2a8ff;font-weight:bold">[${stepN}] ${esc(qaAgent.name)}</span> — testit`);
highlightAgent('qa'); highlightAgent('qa');
explainStep('Testit', `${qaAgent.name} kirjoittaa pytest-testit korjatulle koodille.`); explainStep('Testit', `${qaAgent.name} kirjoittaa pytest-testit katselmoidulle koodille.`);
const qaPrompt = (qaAgent.prompt ? qaAgent.prompt+'\n\n' : '') + `Write pytest tests for this project:\n\n${updatedCode}\n\nWrite a complete test_main.py file with TestClient.`; const qaTestPrompt = (qaAgent.prompt ? qaAgent.prompt+'\n\n' : '') + `Write pytest tests for this project:\n\n${updatedCode}\n\nWrite a complete test_main.py file with TestClient.`;
const tests = await kpnRun(qaAgent.model, qaPrompt, false, qaAgent); const tests = await kpnRun(qaAgent.model, qaTestPrompt, false, qaAgent);
if (tests) { if (tests) {
files['test_main.py'] = tests; files['test_main.py'] = tests;
promptLog.push({ step: promptLog.length, agentKey: 'qa', agentName: qaAgent.name, model: qaAgent.model, label: 'test_main.py', systemPrompt: qaAgent.prompt || '', userPrompt: qaPrompt, response: tests }); promptLog.push({ step: promptLog.length, agentKey: 'qa', agentName: qaAgent.name, model: qaAgent.model, label: 'test_main.py', systemPrompt: qaAgent.prompt || '', userPrompt: qaTestPrompt, response: tests });
} }
stepN++; stepN++;
} }
// DevOps: Dockerfile (saa kaikki tiedostot mukaan lukien testit) // DevOps: Dockerfile + deployment (saa kaikki tiedostot mukaan lukien testit)
const tst = agents.tester || Object.values(agents)[5];
const allFilesNow = Object.keys(files).join(', '); const allFilesNow = Object.keys(files).join(', ');
termLog(`\n<span style="color:var(--accent);font-weight:bold">[${stepN}] ${esc(tst.name)}</span> — Dockerfile`); termLog(`\n<span style="color:var(--accent);font-weight:bold">[${stepN}] ${esc(tst.name)}</span> — Dockerfile`);
highlightAgent('tester'); highlightAgent('tester');