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:
@@ -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<span style="color:var(--accent);font-weight:bold">[${stepN}] ${esc(tst.name)}</span> — 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<span style="color:#d2a8ff;font-weight:bold">[${stepN}] ${esc(qaAgent.name)}</span> — 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(` <span style="color:#3fb950">✓ ${esc(tst.name)}: LGTM</span>`);
|
||||
termLog(` <span style="color:#3fb950">✓ ${esc(qaAgent.name)}: LGTM</span>`);
|
||||
break;
|
||||
}
|
||||
|
||||
// Korjaukset
|
||||
termLog(`\n<span style="color:#d29922;font-weight:bold">[${stepN}] ${esc(cdr.name)}</span> — fixes${round > 0 ? ' (round '+(round+1)+')' : ''}`);
|
||||
// Korjaukset → Coder
|
||||
termLog(`\n<span style="color:#d29922;font-weight:bold">[${stepN}] ${esc(cdr.name)}</span> — 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<span style="color:#d2a8ff;font-weight:bold">[${stepN}] ${esc(qaAgent.name)}</span> — 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<span style="color:var(--accent);font-weight:bold">[${stepN}] ${esc(tst.name)}</span> — Dockerfile`);
|
||||
highlightAgent('tester');
|
||||
|
||||
Reference in New Issue
Block a user