diff --git a/network-poc/static/index.html b/network-poc/static/index.html
index df19615..f092cb4 100644
--- a/network-poc/static/index.html
+++ b/network-poc/static/index.html
@@ -1819,27 +1819,111 @@
}
}
- // Pipeline: manageri → koodari → testaaja
+ // Pipeline: manageri → koodari (per tiedosto) → testaaja → korjausluuppi
async function kpnPipeline(task) {
termLog(`━━━ Pipeline käynnistyy ━━━`);
- // Vaihe 1: Manageri analysoi
- termLog(`\n[1/3] Manageri — tehtävän analyysi`);
- const managerPrompt = `Analysoi seuraava ohjelmistokehitystehtävä ja kirjoita koodarille selkeä tekninen ohje mitä koodata. Vastaa lyhyesti.\n\nTehtävä: ${task}`;
+ // Vaihe 1: Manageri pilkkoo projektin tiedostoiksi
+ termLog(`\n[1] Manageri — projektin suunnittelu`);
+ const managerPrompt = `You are a project manager. Break this task into individual source files.
+For each file, write one line: FILENAME: description
+List only the essential files (max 5). No explanations.
+
+Task: ${task}`;
const plan = await kpnRun(agentPrompts.manager.model, managerPrompt);
if (!plan) { termLog(' ✗ Pipeline keskeytyi (manageri)', '#f85149'); return; }
- // Vaihe 2: Koodari toteuttaa
- termLog(`\n[2/3] Koodari — toteutus`);
- const coderPrompt = `${plan}\n\nKirjoita koodi yllä olevan ohjeen mukaisesti.`;
- const code = await kpnRun(agentPrompts.coder.model, coderPrompt);
- if (!code) { termLog(' ✗ Pipeline keskeytyi (koodari)', '#f85149'); return; }
+ // Parsitaan tiedostolista: "FILENAME: description" -rivit
+ const fileList = plan.split('\n')
+ .map(line => line.trim())
+ .filter(line => line.includes(':') && (line.includes('.') || line.includes('/')))
+ .map(line => {
+ const [name, ...desc] = line.replace(/^[\d\.\-\*]+\s*/, '').split(':');
+ return { name: name.trim().replace(/\*+/g, ''), desc: desc.join(':').trim() };
+ })
+ .filter(f => f.name.length > 0 && f.name.length < 50);
- // Vaihe 3: Testaaja arvioi
- termLog(`\n[3/3] Testaaja — arviointi`);
- const testerPrompt = `Arvioi seuraava koodi lyhyesti. Onko siinä bugeja? Puuttuuko testejä? Anna arvosana 1-5.\n\nTehtävä: ${task}\n\nKoodi:\n${code}`;
- await kpnRun(agentPrompts.tester.model, testerPrompt);
+ if (fileList.length === 0) {
+ // Fallback: manageri ei tuottanut tiedostolistaa, käytetään koko vastausta ohjeena
+ termLog(' Ei tiedostojakoa — generoidaan yhtenä kokonaisuutena');
+ termLog(`\n[2] Koodari — toteutus`);
+ const code = await kpnRun(agentPrompts.coder.model, `${plan}\n\nWrite the code.`);
+ if (code) {
+ termLog(`\n━━━ Pipeline valmis ━━━`);
+ }
+ return;
+ }
+ termLog(` ${fileList.length} tiedostoa: ${fileList.map(f => f.name).join(', ')}`);
+
+ // Vaihe 2: Koodari generoi tiedosto kerrallaan, konteksti ketjutetaan
+ const generatedFiles = {};
+ for (let i = 0; i < fileList.length; i++) {
+ const file = fileList[i];
+ termLog(`\n[${i + 2}] Koodari — ${esc(file.name)}`);
+
+ // Rakennetaan konteksti: aiemmin generoidut tiedostot
+ let context = '';
+ const prevFiles = Object.entries(generatedFiles);
+ if (prevFiles.length > 0) {
+ context = 'Already written files:\n' + prevFiles.map(([name, code]) =>
+ `--- ${name} ---\n${code}`
+ ).join('\n\n') + '\n\n';
+ }
+
+ const coderPrompt = `${context}Write ONLY the file "${file.name}": ${file.desc}
+Project: ${task}`;
+ const code = await kpnRun(agentPrompts.coder.model, coderPrompt);
+ if (!code) {
+ termLog(` ✗ Pipeline keskeytyi (${file.name})`, '#f85149');
+ return;
+ }
+ generatedFiles[file.name] = code;
+ }
+
+ // Vaihe 3: Testaaja arvioi koko projektin
+ const allCode = Object.entries(generatedFiles)
+ .map(([name, code]) => `--- ${name} ---\n${code}`)
+ .join('\n\n');
+
+ termLog(`\n[${fileList.length + 2}] Testaaja — arviointi`);
+ const reviewPrompt = `Review this project. List bugs or issues. Be brief.
+If the code is correct, say "LGTM".
+
+${allCode}`;
+ const review = await kpnRun(agentPrompts.tester.model, reviewPrompt);
+
+ // Vaihe 4: Korjausluuppi — jos testaaja löysi ongelmia
+ if (review && !review.toLowerCase().includes('lgtm') && !review.toLowerCase().includes('looks good')) {
+ termLog(`\n[${fileList.length + 3}] Koodari — korjaukset`);
+ const fixPrompt = `Fix the issues found in the review.
+Review feedback: ${review}
+
+Current code:
+${allCode}
+
+Write the corrected code.`;
+ const fixedCode = await kpnRun(agentPrompts.coder.model, fixPrompt);
+ if (fixedCode) {
+ termLog(`\n[${fileList.length + 4}] Testaaja — uudelleenarviointi`);
+ await kpnRun(agentPrompts.tester.model, `Review the corrected code briefly:\n${fixedCode}`);
+ }
+ }
+
+ termLog(`\n━━━ Pipeline valmis (${Object.keys(generatedFiles).length} tiedostoa) ━━━`);
+ }
+
+ // Yksinkertainen pipeline (vanha: manageri → koodari → testaaja)
+ async function kpnPipelineSimple(task) {
+ termLog(`━━━ Pipeline käynnistyy ━━━`);
+ termLog(`\n[1/3] Manageri`);
+ const plan = await kpnRun(agentPrompts.manager.model, `Analyse this task briefly and write a technical spec for a coder:\n${task}`);
+ if (!plan) return;
+ termLog(`\n[2/3] Koodari`);
+ const code = await kpnRun(agentPrompts.coder.model, `${plan}\n\nWrite the code.`);
+ if (!code) return;
+ termLog(`\n[3/3] Testaaja`);
+ await kpnRun(agentPrompts.tester.model, `Review briefly:\n${code}`);
termLog(`\n━━━ Pipeline valmis ━━━`);
}
@@ -1858,7 +1942,8 @@
if (sub === 'help' || !sub) {
termLog(' kpn hello — iloinen tervehdys verkosta', '#a5d6ff');
termLog(' kpn run <malli> "<prompti>" — aja tehtävä verkossa', '#a5d6ff');
- termLog(' kpn pipeline "<tehtävä>" — manageri → koodari → testaaja', '#a5d6ff');
+ termLog(' kpn pipeline "<tehtävä>" — nopea: manageri → koodari → testaaja', '#a5d6ff');
+ termLog(' kpn project "<kuvaus>" — projekti: tiedostojako + generointi + review', '#a5d6ff');
termLog(' kpn load — lataa kielimalli omalle koneelle', '#a5d6ff');
termLog(' kpn status — verkon tila', '#a5d6ff');
termLog(' kpn models — käytettävissä olevat mallit', '#a5d6ff');
@@ -1926,13 +2011,26 @@
}
if (sub === 'pipeline') {
- const afterPipeline = cmd.replace(/^kpn\s+pipeline\s*/, '');
- const pMatch = afterPipeline.match(/^"(.+)"$|^'(.+)'$|^(.+)$/);
+ const afterCmd = cmd.replace(/^kpn\s+pipeline\s*/, '');
+ const pMatch = afterCmd.match(/^"(.+)"$|^'(.+)'$|^(.+)$/);
const pTask = (pMatch && (pMatch[1] || pMatch[2] || pMatch[3] || '')).trim();
if (!pTask) {
termLog(' Käyttö: kpn pipeline "<tehtävä>"', '#f85149');
return;
}
+ kpnPipelineSimple(pTask);
+ return;
+ }
+
+ if (sub === 'project') {
+ const afterCmd = cmd.replace(/^kpn\s+project\s*/, '');
+ const pMatch = afterCmd.match(/^"(.+)"$|^'(.+)'$|^(.+)$/);
+ const pTask = (pMatch && (pMatch[1] || pMatch[2] || pMatch[3] || '')).trim();
+ if (!pTask) {
+ termLog(' Käyttö: kpn project "<projektin kuvaus>"', '#f85149');
+ termLog(' Esim: kpn project "FastAPI + SQLite REST API for users"', '#8b949e');
+ return;
+ }
kpnPipeline(pTask);
return;
}
@@ -1969,7 +2067,7 @@
// Tab-completion: ennustava komennonsyöttö sana kerrallaan
const kpnCommands = {
- 'kpn': ['help', 'run', 'pipeline', 'load', 'status', 'models', 'hello', 'clear'],
+ 'kpn': ['help', 'run', 'project', 'pipeline', 'load', 'status', 'models', 'hello', 'clear'],
'kpn run': ['coder', 'coder-3b', 'manager', 'tester', 'qa', 'data', 'observer', 'qwen-coder', 'qwen-coder-3b', 'smollm-135m', 'qwen-05b', 'phi3-mini'],
'kpn load': ['1', '2'],
'kpn pipeline': ['"'],
@@ -1980,6 +2078,7 @@
'kpn run coder-3b': ['"binary search tree in rust"', '"REST API with Flask"', '"async web scraper in python"'],
'kpn run manager': ['"suunnittele REST API"', '"priorisoi tiimin tehtävät"'],
'kpn run tester': ['"testaa login-toiminto"'],
+ 'kpn project': ['"FastAPI + SQLite REST API for users"', '"Flask todo app with database"', '"CLI tool for CSV processing in Python"'],
'kpn pipeline': ['"rakenna todo-sovellus"', '"tee laskin pythonilla"'],
};