10 Commits

Author SHA1 Message Date
Jaakko Vanhala
b8e8a83e49 Dockerfile.prod päivitetty Astro-frontendille
Muutokset:
- Vaihe 1: Node.js buildaa Astro-frontendin (frontend/dist/)
- Vaihe 2: wasm-pack buildaa Wasm-moduulin erikseen
- Vaihe 3: Hub rakennetaan Rustilla
- Vaihe 4: Yhdistetään dist/ + pkg/ + avatars/ + templates/ + GUIDE.md

STATIC_DIR=/app/frontend/dist (ei enää /app/static)
Avatarit, templates ja GUIDE.md kopioidaan erikseen koska
Astro ei kopioi public/-tiedostoja dist/:iin buildin yhteydessä.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 08:04:49 +03:00
Jaakko Vanhala
3d6914974d Prompti-textarea kasvaa automaattisesti sisällön mukaan
Koko prompti näkyy kerralla kun avatarin klikkaa — ei scrollausta.
Textarea saa overflow:hidden + auto-height sekä avatessa että kirjoittaessa.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 08:03:18 +03:00
Jaakko Vanhala
9aff2ec154 uv kauttaaltaan + Tarkkailijan raportti rakenteelliseksi markdowniksi
uv-päivitykset:
- Koodarin NEVER-lista: ei requirements.txt, ei pip, käytä uv
- Template pyproject.toml: PEP 621, uv-yhteensopiva
- Raportin Quick Start: uv sync + uv run uvicorn

Tarkkailijan raportti uudessa formaatissa:
- Overview (yksi kappale)
- Files (taulukko: tiedosto + tarkoitus)
- Quick Start (uv-komennot koodiblokissa)
- Docker (build + run koodiblokissa)
- API Endpoints (taulukko: method, path, description)
- Architecture (rakenne ja päätökset)
- Risk Assessment (taulukko: severity, issue)

Malli saa taulukkopohjat valmiina → täyttää ne oikealla datalla.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 07:59:10 +03:00
Jaakko Vanhala
ecd4525a7f Review-korjauskierrokset nostettu 2→3
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 07:51:41 +03:00
Jaakko Vanhala
7a3e5278b9 Review-korjausluuppi: DevOps tarkistaa korjaukset, max 2 kierrosta
Pipeline:
1. DevOps review → löytää virheitä
2. Koodari korjaa → päivittää files-objektin
3. DevOps review (kierros 2) → tarkistaa korjaukset
4. Jos yhä virheitä → Koodari korjaa uudelleen
5. LGTM tai max 2 kierrosta → eteenpäin

Terminaalissa näkyy kierrosnumero: "koodikatselmointi (kierros 2)"
LGTM merkitään vihreällä ✓-merkillä.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 07:50:43 +03:00
Jaakko Vanhala
8dcf269b42 strip_code_fences: poistetaan kaikki backtick-rivit aggressiivisesti
Ollama tuottaa \`\`\`python ... \`\`\` -blokkeja vaikka system prompt
kieltää ne. Nyt kaikki rivit jotka alkavat \`\`\` suodatetaan pois,
myös keskeltä vastausta (useita koodiblokkeja per vastaus).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 07:47:03 +03:00
Jaakko Vanhala
cb16f35265 Tiedostot kulkevat agentilta toiselle: korjaukset päivittyvät, QA saa korjatun koodin
Kontekstiketju nyt:
1. Data → models.py
2. Koodari → schemas.py (saa models.py kontekstina)
3. Koodari → main.py (saa models.py + schemas.py)
4. Koodari → pyproject.toml
5. DevOps → review (saa kaikki tiedostot)
6. Koodari → korjaukset → parsitaan takaisin files-objektiin (--- filename ---)
7. QA → testit (saa KORJATUT tiedostot, ei alkuperäisiä)
8. DevOps → Dockerfile (saa kaikki tiedostot + testit)
9. Tarkkailija → README (saa kaiken)

Aiemmin: korjaukset menivät terminaaliin mutta eivät päivittyneet files-objektiin.
Nyt: korjattu koodi parsitaan --- filename --- -erottimilla takaisin.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 07:44:01 +03:00
Jaakko Vanhala
b9d340b4b4 install.sh: Debian/Ubuntu-asennusskripti
Asentaa automaattisesti:
1. Build-työkalut (build-essential, pkg-config, libssl-dev)
2. Rust (rustup)
3. Node.js 22 (nodesource)
4. Ollama
5. qwen2.5-coder:3b -malli

Käyttö: ./network-poc/install.sh && ./network-poc/local.sh

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 07:42:27 +03:00
Jaakko Vanhala
dd07e536f0 Korjattu duplikaatti const tst -määrittely kpnProject:ssa
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 07:39:51 +03:00
Jaakko Vanhala
9af481a022 DevOps-agentin prompti laajennettu staattiseksi koodianalyysiksi
9-kohdan checklist: importit, nimeämiset, tyypit, virheenkäsittely,
resurssivuodot, tietoturva, endpointit, Pydantic v2, täydellisyys.

Aiemmin 7 kohtaa, nyt 9 — lisätty: type hints, tietoturva (raw SQL,
hardcoded secrets), Pydantic v2 (model_dump, from_attributes).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 07:36:57 +03:00
6 changed files with 200 additions and 85 deletions

View File

@@ -1,47 +1,57 @@
# syntax=docker/dockerfile:1
FROM rust:slim AS builder
RUN apt-get update && apt-get install -y \
curl pkg-config libssl-dev g++ \
&& rm -rf /var/lib/apt/lists/*
# --- Vaihe 1: Frontend (Astro) ---
FROM node:22-slim AS frontend
WORKDIR /app/frontend
COPY frontend/package.json frontend/package-lock.json* ./
RUN npm install --silent
COPY frontend/ .
COPY frontend/public/pkg public/pkg
RUN npm run build
# --- Vaihe 2: Wasm (wasm-pack) ---
FROM rust:slim AS wasm-builder
RUN apt-get update && apt-get install -y curl pkg-config libssl-dev g++ && rm -rf /var/lib/apt/lists/*
RUN curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
WORKDIR /app
COPY Cargo.toml Cargo.lock* ./
COPY node/Cargo.toml node/Cargo.toml
COPY node/src node/src
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/app/target \
cd node && wasm-pack build --target web --out-dir /app/wasm-pkg
# Kopioi kaikki Cargo-tiedostot
COPY Cargo.toml ./
COPY Cargo.lock* ./
# --- Vaihe 3: Hub (Rust) ---
FROM rust:slim AS hub-builder
RUN apt-get update && apt-get install -y pkg-config libssl-dev && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY Cargo.toml Cargo.lock* ./
COPY hub/Cargo.toml hub/Cargo.toml
COPY hub/src hub/src
# Tarvitaan dummy-cratet jotta workspace kompiloi
COPY node/Cargo.toml node/Cargo.toml
COPY native-node/Cargo.toml native-node/Cargo.toml
COPY cli/Cargo.toml cli/Cargo.toml
# Kopioi lähdekoodi
COPY hub/src hub/src
COPY node/src node/src
COPY native-node/src native-node/src
COPY cli/src cli/src
COPY static static
# Rakenna Wasm — cache mount pitää Cargo-rekisterin ja target-kansion buildien välillä
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/app/target \
cd node && wasm-pack build --target web --out-dir ../static/pkg
# Rakenna Hub
RUN mkdir -p node/src native-node/src cli/src && touch node/src/lib.rs native-node/src/main.rs cli/src/main.rs
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/app/target \
cargo build --release -p hub \
&& cp /app/target/release/hub /usr/local/bin/hub
# --- Vaihe 4: Tuotantoimage ---
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /usr/local/bin/hub /usr/local/bin/hub
COPY --from=builder /app/static /app/static
COPY --from=hub-builder /usr/local/bin/hub /usr/local/bin/hub
COPY --from=frontend /app/frontend/dist /app/frontend/dist
COPY --from=wasm-builder /app/wasm-pkg /app/frontend/dist/pkg
# Kopioidaan GUIDE.md ja templates
COPY frontend/public/GUIDE.md /app/frontend/dist/GUIDE.md
COPY frontend/public/templates /app/frontend/dist/templates
COPY frontend/public/avatars /app/frontend/dist/avatars
WORKDIR /app
ENV STATIC_DIR=/app/static
ENV STATIC_DIR=/app/frontend/dist
EXPOSE 3000
CMD ["hub"]

View File

@@ -20,7 +20,7 @@
"pyproject.toml": {
"description": "Project dependencies",
"example": "[project]\nname = \"myapp\"\nversion = \"0.1.0\"\nrequires-python = \">=3.11\"\ndependencies = [\n \"fastapi\",\n \"uvicorn[standard]\",\n \"sqlalchemy\",\n]\n\n[project.scripts]\ndev = \"uvicorn main:app --reload\"",
"instructions": "List the exact dependencies needed. Use [project.scripts] for run commands."
"instructions": "Use [project] format (PEP 621, compatible with uv). List dependencies under [project.dependencies]. Add [project.scripts] with dev command. Never use requirements.txt or Poetry format. Run with: uv run uvicorn main:app --reload"
}
},
"order": ["models.py", "schemas.py", "main.py", "pyproject.toml"]

View File

@@ -41,7 +41,7 @@
<!-- System prompt -->
<div style="margin-bottom:10px" title="Agentin perusohje joka lähetetään kielimallille jokaisessa pyynnössä.&#10;&#10;Hyvän promptin rakenne:&#10;1. Rooli: 'You are an expert...'&#10;2. Säännöt: RULES/CRITICAL RULES listana&#10;3. Esimerkit: EXAMPLE OUTPUT&#10;4. Kiellot: NEVER-lista&#10;&#10;Vinkki: käytä englantia — malli ymmärtää sen paremmin ja se kuluttaa vähemmän tokeneita.">
<label style="font-size:12px;color:#8b949e;display:block;margin-bottom:4px;cursor:help">System prompt 💡</label>
<textarea id="config-prompt" rows="8" style="width:100%;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:4px;padding:8px;font-size:13px;font-family:'Courier New',monospace;resize:vertical" placeholder="Kuvaa agentin rooli ja käyttäytyminen..."></textarea>
<textarea id="config-prompt" style="width:100%;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:4px;padding:8px;font-size:13px;font-family:'Courier New',monospace;resize:vertical;overflow:hidden;min-height:60px" placeholder="Kuvaa agentin rooli ja käyttäytyminen..."></textarea>
</div>
<!-- Sampling-parametrit -->

View File

@@ -101,7 +101,9 @@ NEVER:
- Add explanations or comments like "# Add routes here"
- Leave placeholder code or TODO comments
- Use Flask syntax (app.run) in FastAPI projects
- Forget to import from other project files` },
- Forget to import from other project files
- Use requirements.txt or Poetry — always use pyproject.toml with [project] format (PEP 621)
- Use pip install — use uv (e.g. uv run uvicorn main:app --reload)` },
data: { name: 'Data', avatar: '/avatars/pesukarhu_notext.png', model: 'qwen-coder', order: 2,
temperature: 0.5, topK: 40, repeatPenalty: 1.15, maxTokens: 1024,
prompt: `You are a database architect specializing in SQLAlchemy and relational databases.
@@ -138,16 +140,18 @@ TEST STRUCTURE:
ALWAYS: from fastapi.testclient import TestClient` },
tester: { name: 'DevOps', avatar: '/avatars/laiskiainen_notext.png', model: 'qwen-coder', order: 4,
temperature: 0.3, topK: 40, repeatPenalty: 1.1, maxTokens: 512,
prompt: `You are a strict code reviewer. Review the provided code and check for these issues:
prompt: `You are a strict code reviewer and static analysis expert. Analyze the code line by line.
CHECKLIST:
1. ✓ All imports exist (no missing "from X import Y")
2. ✓ Import names match: if models.py exports "User", main.py imports "User" (not "UserModel")
3. ✓ Pydantic schema names don't conflict with SQLAlchemy model names
4. ✓ All CRUD endpoints have error handling (404 for not found)
5. Database session is properly closed (get_db with yield + finally)
6. ✓ Response models are specified for type safety
7. ✓ No placeholder comments like "# Add routes here"
STATIC ANALYSIS 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
4. ERRORS: Every db query that can return None has a 404 check
5. RESOURCES: Database session uses yield+finally pattern (no leaks)
6. SECURITY: No raw SQL, no hardcoded secrets, inputs validated via Pydantic
7. ENDPOINTS: All CRUD operations exist (POST/GET/GET-by-id/PUT/DELETE)
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"
@@ -254,7 +258,11 @@ OUTPUT FORMAT:
document.getElementById('config-name').value = a.name;
document.getElementById('config-role').textContent = key;
document.getElementById('config-model').value = a.model;
document.getElementById('config-prompt').value = a.prompt || '';
const promptEl = document.getElementById('config-prompt');
promptEl.value = a.prompt || '';
// Auto-resize: textarea kasvaa sisällön mukaan
promptEl.style.height = 'auto';
promptEl.style.height = promptEl.scrollHeight + 'px';
// Sampling-parametrit
const tempEl = document.getElementById('config-temperature');
@@ -281,7 +289,7 @@ OUTPUT FORMAT:
// Muutosten tallennus
document.getElementById('config-name').oninput = () => { agents[key].name = document.getElementById('config-name').value; saveAgents(); renderAgentBar(); };
document.getElementById('config-model').onchange = () => { agents[key].model = document.getElementById('config-model').value; saveAgents(); };
document.getElementById('config-prompt').oninput = () => { agents[key].prompt = document.getElementById('config-prompt').value; saveAgents(); };
promptEl.oninput = () => { agents[key].prompt = promptEl.value; saveAgents(); promptEl.style.height = 'auto'; promptEl.style.height = promptEl.scrollHeight + 'px'; };
tempEl.oninput = () => { agents[key].temperature = +tempEl.value; tempValEl.textContent = tempEl.value; saveAgents(); };
maxtokEl.oninput = () => { agents[key].maxTokens = +maxtokEl.value; maxtokValEl.textContent = maxtokEl.value; saveAgents(); };
topkEl.oninput = () => { agents[key].topK = +topkEl.value; topkValEl.textContent = topkEl.value; saveAgents(); };
@@ -724,7 +732,6 @@ OUTPUT FORMAT:
async function kpnProject(task) {
const cdr = agents.coder || Object.values(agents)[1];
const tst = agents.tester || Object.values(agents)[2];
// Etsitään sopivin mallipohja
const template = Object.values(templates)[0]; // Toistaiseksi vain FastAPI CRUD
@@ -791,43 +798,74 @@ OUTPUT FORMAT:
const allCode = Object.entries(files).map(([n,c]) => `--- ${n} ---\n${c}`).join('\n\n');
let stepN = template.order.length + 1;
// DevOps/Testaaja: koodikatselmointi
// Review-korjausluuppi: max 2 kierrosta
const tst = agents.tester || Object.values(agents)[4];
termLog(`\n<span style="color:var(--accent);font-weight:bold">[${stepN}] ${esc(tst.name)}</span> — koodikatselmointi`);
highlightAgent('tester');
explainStep('Koodikatselmointi', `${tst.name} tarkistaa importit, nimeämiset, virheenkäsittelyn ja tiedostojen yhteensopivuuden.`);
const tstPrompt = (tst.prompt ? tst.prompt+'\n\n' : '') + `Review this project:\n\n${allCode}`;
const review = await kpnRun(tst.model, tstPrompt);
stepN++;
const MAX_REVIEW_ROUNDS = 3;
// Korjausluuppi (jos tarpeen)
if (review && !review.toLowerCase().includes('lgtm')) {
termLog(`\n<span style="color:#d29922;font-weight:bold">[${stepN}] ${esc(cdr.name)}</span> — korjaukset`);
highlightAgent('coder');
explainStep('Korjausluuppi', `${tst.name} löysi ongelmia. ${cdr.name} saa palautteen ja korjaa koodin.`);
await kpnRun(cdr.model, `${cdr.prompt ? cdr.prompt+'\n\n' : ''}Fix these issues:\n${review}\n\nCurrent code:\n${allCode}\n\nWrite the corrected files.`);
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> — koodikatselmointi${round > 0 ? ' (kierros '+(round+1)+')' : ''}`);
highlightAgent('tester');
if (round === 0) explainStep('Koodikatselmointi', `${tst.name} analysoi koodin rivi riviltä: importit, nimeämiset, virheenkäsittely, tietoturva.`);
else explainStep('Uudelleentarkistus', `${tst.name} tarkistaa korjaukset.`);
const reviewPrompt = (tst.prompt ? tst.prompt+'\n\n' : '') + `Review this project:\n\n${currentCode}`;
const review = await kpnRun(tst.model, reviewPrompt);
stepN++;
}
// QA: testit
// LGTM → ei korjauksia tarvita
if (!review || review.toLowerCase().includes('lgtm')) {
termLog(` <span style="color:#3fb950">✓ ${esc(tst.name)}: LGTM</span>`);
break;
}
// Korjaukset
termLog(`\n<span style="color:#d29922;font-weight:bold">[${stepN}] ${esc(cdr.name)}</span> — korjaukset${round > 0 ? ' (kierros '+(round+1)+')' : ''}`);
highlightAgent('coder');
explainStep('Korjaus', `${tst.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);
// Parsitaan korjatut tiedostot takaisin files-objektiin
if (fixedCode) {
const fixedParts = fixedCode.split(/^---\s*(\S+)\s*---$/m);
for (let j = 1; j < fixedParts.length; j += 2) {
const fname = fixedParts[j].trim();
const fcode = (fixedParts[j+1] || '').trim();
if (fname && fcode && files[fname] !== undefined) {
files[fname] = fcode;
}
}
}
stepN++;
} // for review round
// 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)[3];
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 kaikille endpointeille.`);
const qaPrompt = (qaAgent.prompt ? qaAgent.prompt+'\n\n' : '') + `Write pytest tests for this project:\n\n${allCode}\n\nWrite a complete test_main.py file with TestClient.`;
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);
if (tests) files['test_main.py'] = tests;
stepN++;
}
// DevOps: Dockerfile
// DevOps: Dockerfile (saa kaikki tiedostot mukaan lukien testit)
const allFilesNow = Object.keys(files).join(', ');
termLog(`\n<span style="color:var(--accent);font-weight:bold">[${stepN}] ${esc(tst.name)}</span> — Dockerfile`);
highlightAgent('tester');
explainStep('Dockerfile', `${tst.name} generoi Docker-kontin joka pakkaa projektin ajettavaksi.`);
explainStep('Dockerfile', `${tst.name} generoi Docker-kontin kaikista ${Object.keys(files).length} tiedostosta: ${allFilesNow}`);
const dockerPrompt = (tst.prompt ? tst.prompt+'\n\n' : '') +
`Write a Dockerfile for this Python FastAPI project.\n\n` +
`Project files: ${Object.keys(files).join(', ')}\n\n` +
`Project files: ${allFilesNow}\n\n` +
`Requirements:\n` +
`- Use python:3.12-slim as base\n` +
`- Install uv: COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv\n` +
@@ -846,15 +884,29 @@ OUTPUT FORMAT:
highlightAgent('observer');
explainStep('Raportti', `${obs.name} kokoaa yhteenvedon ja antaa arvosanan.`);
const finalCode = Object.entries(files).map(([n,c]) => `--- ${n} ---\n${c}`).join('\n\n');
const fileList = Object.keys(files).join(', ');
const obsPrompt = (obs.prompt ? obs.prompt+'\n\n' : '') +
`Write a project README.md report in markdown for: ${task}\n\n` +
`IMPORTANT: Start the FIRST LINE with exactly one of these verdicts:\n` +
`VERDICT: GREEN — project is production-ready, no issues\n` +
`VERDICT: ORANGE — project works but has warnings or improvements needed\n` +
`VERDICT: RED — project has critical issues that must be fixed\n\n` +
`Then include:\n` +
`# Project: ${task}\n` +
`## Files\n## How to run\n## API Endpoints\n## Architecture\n## Risk assessment\n\n` +
`Write a project report in clean markdown for: ${task}\n\n` +
`FIRST LINE must be exactly one of:\n` +
`VERDICT: GREEN\nVERDICT: ORANGE\nVERDICT: RED\n\n` +
`Then write this report:\n\n` +
`# ${task}\n\n` +
`## Overview\nOne paragraph describing what this project does.\n\n` +
`## Files\n| File | Purpose |\n|------|---------|` +
Object.entries(files).map(([n]) => `\n| ${n} | ... |`).join('') + `\n\n` +
`## Quick Start\n` +
'```bash\n' +
`git clone <repo>\ncd project\nuv sync\nuv run uvicorn main:app --reload\n` +
'```\n\n' +
`## Docker\n` +
'```bash\n' +
`docker build -t ${task.toLowerCase().replace(/[^a-z0-9]/g, '-')} .\ndocker run -p 8000:8000 ${task.toLowerCase().replace(/[^a-z0-9]/g, '-')}\n` +
'```\n\n' +
`## API Endpoints\n| Method | Path | Description |\n|--------|------|-------------|` +
`\n| POST | /items/ | Create |\n| GET | /items/ | List all |\n| GET | /items/{id} | Get by ID |\n| PUT | /items/{id} | Update |\n| DELETE | /items/{id} | Delete |\n` +
`(Adapt paths and descriptions to match the actual code)\n\n` +
`## Architecture\nDescribe the project structure and design decisions.\n\n` +
`## Risk Assessment\n| Severity | Issue |\n|----------|-------|\n| ... | ... |\n\n` +
`Project code:\n${finalCode}`;
const readme = await kpnRun(obs.model, obsPrompt);
if (readme) {

59
network-poc/install.sh Executable file
View File

@@ -0,0 +1,59 @@
#!/bin/bash
# Kipinä Agentic Studio — asennusskripti (Debian/Ubuntu)
set -e
echo "=== Kipinä Agentic Studio — Asennus ==="
echo ""
# Tarkistetaan käyttöjärjestelmä
if [ ! -f /etc/debian_version ]; then
echo "⚠ Tämä skripti on suunniteltu Debian/Ubuntu-järjestelmille."
echo " Muilla jakeluilla voit asentaa riippuvuudet manuaalisesti."
read -p " Jatketaanko? (k/e) " -n 1 -r; echo
[[ $REPLY =~ ^[Kk]$ ]] || exit 1
fi
echo "[1/6] Päivitetään pakettilistaus..."
sudo apt-get update -qq
echo "[2/6] Asennetaan peruspaketteja..."
sudo apt-get install -y -qq curl git build-essential pkg-config libssl-dev
# Rust
if command -v rustc &>/dev/null; then
echo "[3/6] Rust löytyi: $(rustc --version)"
else
echo "[3/6] Asennetaan Rust..."
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source "$HOME/.cargo/env"
fi
# Node.js (Astro-frontend vaatii)
if command -v node &>/dev/null; then
echo "[4/6] Node.js löytyi: $(node --version)"
else
echo "[4/6] Asennetaan Node.js 22..."
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt-get install -y -qq nodejs
fi
# Ollama
if command -v ollama &>/dev/null; then
echo "[5/6] Ollama löytyi"
else
echo "[5/6] Asennetaan Ollama..."
curl -fsSL https://ollama.ai/install.sh | sh
fi
# Malli
echo "[6/6] Ladataan kielimalli (qwen2.5-coder:3b)..."
ollama pull qwen2.5-coder:3b
echo ""
echo "=== Asennus valmis! ==="
echo ""
echo "Käynnistä:"
echo " cd $(pwd)"
echo " ./network-poc/local.sh"
echo ""
echo "Avaa selaimessa: http://localhost:3000"

View File

@@ -109,7 +109,7 @@ impl LlmEngine {
.map_err(|e| format!("Ollama JSON: {}", e))?;
let text = body["response"].as_str().unwrap_or("").to_string();
let total_duration_ns = body["total_duration"].as_u64().unwrap_or(0);
let _total_duration_ns = body["total_duration"].as_u64().unwrap_or(0);
let eval_count = body["eval_count"].as_u64().unwrap_or(0) as usize;
let eval_duration_ns = body["eval_duration"].as_u64().unwrap_or(1);
@@ -129,23 +129,17 @@ impl LlmEngine {
/// Siivoa markdown-koodiblokki-merkit ja selitystekstit
fn strip_code_fences(text: &str) -> String {
let mut result = text.trim().to_string();
// Poista aloittava ```lang
if result.starts_with("```") {
if let Some(nl) = result.find('\n') {
result = result[nl + 1..].to_string();
// Poistetaan kaikki ```-rivit ja kielitunnisteet (```python, ```rust jne.)
let lines: Vec<&str> = text.lines().collect();
let filtered: Vec<&str> = lines.into_iter().filter(|line| {
let trimmed = line.trim();
// Poista rivit jotka ovat pelkkiä ``` tai ```kielitunniste
if trimmed.starts_with("```") {
return false;
}
}
// Poista sulkeva ```
let trimmed = result.trim_end();
if trimmed.ends_with("```") {
let before = &trimmed[..trimmed.len() - 3];
if before.is_empty() || before.ends_with('\n') {
result = before.trim_end().to_string();
}
}
true
}).collect();
let mut result = filtered.join("\n").trim().to_string();
// Poista selitysteksti lopusta (kaikki rivin "\nPlease note" jälkeen jne.)
let lower = result.to_lowercase();