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');