diff --git a/network-poc/frontend/src/pages/index.astro b/network-poc/frontend/src/pages/index.astro
index 32f595d..d08308e 100644
--- a/network-poc/frontend/src/pages/index.astro
+++ b/network-poc/frontend/src/pages/index.astro
@@ -286,26 +286,9 @@ ALWAYS INCLUDE:
- DATABASE_URL, engine, SessionLocal, Base` },
qa: { name: 'QA', avatar: '/avatars/susi_notext.webp', model: 'qwen-coder', order: 4,
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:
-- 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:
+CODE REVIEW CHECKLIST:
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)
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()
9. COMPLETENESS: No placeholder comments, no "TODO", no "pass" in handlers
-RESPOND:
-- If all checks pass: "LGTM"
+WHEN REVIEWING:
+- If all checks pass: respond "LGTM"
- 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,
temperature: 0.6, topK: 40, repeatPenalty: 1.15, maxTokens: 1024,
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"` },
};
// Versio: kasvata kun oletuspromptit muuttuvat
- const AGENTS_VERSION = 4;
+ const AGENTS_VERSION = 5;
let agents;
const savedVersion = parseInt(localStorage.getItem('kpn-agents-version') || '0');
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');
let stepN = fileOrder.length + 1;
- // Review-korjausluuppi: max 2 kierrosta
- const tst = agents.tester || Object.values(agents)[5];
+ // QA-katselmointi → Coder-korjaus -luuppi (max N kierrosta)
+ const qaAgent = agents.qa || Object.values(agents)[4];
const MAX_REVIEW_ROUNDS = pipelineConfig.maxReviewRounds;
for (let round = 0; round < MAX_REVIEW_ROUNDS; round++) {
const currentCode = Object.entries(files).map(([n,c]) => `--- ${n} ---\n${c}`).join('\n\n');
- // DevOps review
- termLog(`\n[${stepN}] ${esc(tst.name)} — code review${round > 0 ? ' (round '+(round+1)+')' : ''}`);
- highlightAgent('tester');
- if (round === 0) explainStep('Code Review', `${tst.name} reviews code line by line: imports, naming, security.`);
- else explainStep('Re-evaluation', `${tst.name} checks the applied fixes.`);
+ // QA katselmoi
+ termLog(`\n[${stepN}] ${esc(qaAgent.name)} — katselmointi${round > 0 ? ' (kierros '+(round+1)+')' : ''}`);
+ highlightAgent('qa');
+ if (round === 0) explainStep('Katselmointi', `${qaAgent.name} analysoi koodin: importit, nimeämiset, virheenkäsittely, tietoturva.`);
+ else explainStep('Uudelleentarkistus', `${qaAgent.name} tarkistaa korjaukset.`);
- const reviewPrompt = (tst.prompt ? tst.prompt+'\n\n' : '') + `Review this project:\n\n${currentCode}`;
- const review = await kpnRun(tst.model, reviewPrompt, false, tst);
- 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 || '' });
+ 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(qaAgent.model, reviewPrompt, false, qaAgent);
+ 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++;
// LGTM → ei korjauksia tarvita
if (!review || review.toLowerCase().includes('lgtm')) {
- termLog(` ✓ ${esc(tst.name)}: LGTM`);
+ termLog(` ✓ ${esc(qaAgent.name)}: LGTM`);
break;
}
- // Korjaukset
- termLog(`\n[${stepN}] ${esc(cdr.name)} — fixes${round > 0 ? ' (round '+(round+1)+')' : ''}`);
+ // Korjaukset → Coder
+ termLog(`\n[${stepN}] ${esc(cdr.name)} — korjaukset${round > 0 ? ' (kierros '+(round+1)+')' : ''}`);
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 fixedCode = await kpnRun(cdr.model, fixPrompt, false, cdr);
// Parsitaan korjatut tiedostot takaisin files-objektiin
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);
for (let j = 1; j < fixedParts.length; j += 2) {
const fname = fixedParts[j].trim();
@@ -1179,22 +1187,22 @@ OUTPUT FORMAT:
// Päivitetään allCode korjausten jälkeen
const updatedCode = Object.entries(files).map(([n,c]) => `--- ${n} ---\n${c}`).join('\n\n');
- // QA: testit (saa korjatut tiedostot)
- const qaAgent = agents.qa || Object.values(agents)[4];
+ // QA: testit (saa katselmoidut ja korjatut tiedostot)
if (qaAgent) {
termLog(`\n[${stepN}] ${esc(qaAgent.name)} — testit`);
highlightAgent('qa');
- explainStep('Testit', `${qaAgent.name} kirjoittaa pytest-testit korjatulle 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 tests = await kpnRun(qaAgent.model, qaPrompt, false, qaAgent);
+ explainStep('Testit', `${qaAgent.name} kirjoittaa pytest-testit katselmoidulle koodille.`);
+ 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, qaTestPrompt, false, qaAgent);
if (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++;
}
- // 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(', ');
termLog(`\n[${stepN}] ${esc(tst.name)} — Dockerfile`);
highlightAgent('tester');