diff --git a/network-poc/static/index.html b/network-poc/static/index.html
index 9e50e81..be2d1fb 100644
--- a/network-poc/static/index.html
+++ b/network-poc/static/index.html
@@ -1157,7 +1157,7 @@
coder: { name: 'Koodari — System Prompt', model: 'qwen-coder', default: 'Olet kokenut ohjelmistokehittäjä. Kirjoita selkeää, testattavaa koodia ja vastaa aina koodilla.' },
data: { name: 'Data-Agentti — System Prompt', model: 'qwen-coder', default: 'Olet tietokanta-asiantuntija. Vastaat skeemojen suunnittelusta, SQL-kyselyiden optimoinnista ja datamalleista.' },
qa: { name: 'QA — System Prompt', model: 'qwen-coder', default: 'Olet laadunvarmistaja (QA). Kirjoitat testejä, etsit virheitä ja varmistat, että kaikki reunatapaukset on huomioitu.' },
- tester: { name: 'DevOps — System Prompt', model: 'qwen-coder', default: 'Olet DevOps-insinööri. Vastaat koodin julkaisuputkista, serveri-infrastruktuurista ja ympäristön suorituskyvystä.' },
+ tester: { name: 'DevOps — System Prompt', model: 'qwen-coder', default: 'Olet DevOps-insinööri. Kirjoitat Dockerfile- ja docker-compose.yml-tiedostot, README:t ja käynnistysohjeet. Käytä aina multi-stage Docker buildia ja docker compose -orkestrointia.' },
};
const selectedAgents = new Set();
let sharedPrompt = localStorage.getItem('kpn-shared-prompt') || '';
@@ -2208,21 +2208,53 @@ ${Object.entries(generatedFiles).map(([n, c]) => `--- ${n} ---\n${c}`).join('\n\
if (tests) generatedFiles['test_app.py'] = tests;
pipelineStep('qa', 'Testit', 'done', 'test_app.py', tests);
- // Vaihe 6: DevOps — käynnistysohjeet
+ // Vaihe 6: DevOps — Dockerfile
const step6 = step5 + 1;
- termLog(`\n[${step6}] DevOps — käynnistys`);
- pipelineStep('tester', 'DevOps', 'active', 'Käynnistysohjeet');
- const devopsPrompt = `Write a minimal README.md for this project. Include ONLY:
-1. One-line description
-2. How to install (pip/uv)
-3. How to run
-4. How to test
-Max 15 lines. No badges, no license, no contributing section.
+ termLog(`\n[${step6}] DevOps — Dockerfile`);
+ pipelineStep('tester', 'Dockerfile', 'active', 'Dockerfile');
+ const dockerPrompt = `Write a Dockerfile for this project. Use multi-stage build:
+- Stage 1 (builder): install dependencies
+- Stage 2 (runtime): copy only what's needed, minimal image
+Use python:3.12-slim as base. EXPOSE the correct port. CMD to start the app.
+Only output the Dockerfile content, nothing else.
+
+Files: ${Object.keys(generatedFiles).join(', ')}
+Main app entry: ${Object.keys(generatedFiles).find(f => f.includes('main') || f.includes('app')) || Object.keys(generatedFiles)[0]}`;
+ const dockerfile = await kpnRun(agentPrompts.tester.model, dockerPrompt, false, 256);
+ if (dockerfile) generatedFiles['Dockerfile'] = dockerfile;
+ pipelineStep('tester', 'Dockerfile', 'done', 'Dockerfile', dockerfile);
+
+ // Vaihe 7: DevOps — docker-compose.yml
+ const step7 = step6 + 1;
+ termLog(`\n[${step7}] DevOps — docker-compose.yml`);
+ pipelineStep('tester', 'Compose', 'active', 'docker-compose.yml');
+ const composePrompt = `Write a docker-compose.yml for this project. Include:
+- app service (build from Dockerfile, port mapping, restart: unless-stopped)
+- db service if SQLite/PostgreSQL is used (volume for data persistence)
+- Named volumes for persistent data
+Only output the YAML content, nothing else.
Files: ${Object.keys(generatedFiles).join(', ')}`;
- const readme = await kpnRun(agentPrompts.tester.model, devopsPrompt, false, 256);
+ const compose = await kpnRun(agentPrompts.tester.model, composePrompt, false, 256);
+ if (compose) generatedFiles['docker-compose.yml'] = compose;
+ pipelineStep('tester', 'Compose', 'done', 'docker-compose.yml', compose);
+
+ // Vaihe 8: DevOps — README
+ const step8 = step7 + 1;
+ termLog(`\n[${step8}] DevOps — README`);
+ pipelineStep('tester', 'README', 'active', 'README.md');
+ const readmePrompt = `Write a minimal README.md. Include ONLY:
+1. One-line description
+2. Quick start: docker compose up
+3. Development: how to run without Docker
+4. API endpoints (if applicable)
+5. How to test
+Max 20 lines.
+
+Files: ${Object.keys(generatedFiles).join(', ')}`;
+ const readme = await kpnRun(agentPrompts.tester.model, readmePrompt, false, 256);
if (readme) generatedFiles['README.md'] = readme;
- pipelineStep('tester', 'DevOps', 'done', 'README.md', readme);
+ pipelineStep('tester', 'README', 'done', 'README.md', readme);
termLog(`\n━━━ Pipeline valmis (${Object.keys(generatedFiles).length} tiedostoa) ━━━`);
renderProjectCard(generatedFiles, task);