// Visual Control Plane server — v1 // Single Bun.serve process on :3950. Serves static index.html and // /data/* endpoints that fan out to the live services + tail jsonl KB // files. No build step, no node_modules. Restart via systemd or // `bun run ui/server.ts`. const PORT = Number(process.env.LH_UI_PORT ?? 3950); const KB = "/home/profit/lakehouse/data/_kb"; const REPO = "/home/profit/lakehouse"; const GATEWAY = "http://localhost:3100"; const SIDECAR = "http://localhost:3200"; const OBSERVER = "http://localhost:3800"; const MCP = "http://localhost:3700"; const CONTEXT7 = "http://localhost:3900"; // Tail helper — read last N lines of a jsonl file without loading // the whole thing. For files up to a few MB this is fine to read fully. async function tailJsonl(path: string, n = 50): Promise { try { const text = await Bun.file(path).text(); const lines = text.trim().split("\n").filter(Boolean); const tail = lines.slice(-n); return tail.map(l => { try { return JSON.parse(l); } catch { return { _raw: l, _error: "parse" }; } }); } catch (e) { return []; } } async function tryFetch(url: string, timeout = 1500): Promise { try { const r = await fetch(url, { signal: AbortSignal.timeout(timeout) }); if (!r.ok) return null; // Fix 2026-04-24: some upstream services (observer Bun.serve) return // JSON without an application/json content-type. Don't rely on header // — try parsing the body as JSON; fall back to raw text on failure. const body = await r.text(); try { return JSON.parse(body); } catch { return body; } } catch { return null; } } // Compact the massive /vectors/indexes response into just the shape the // UI needs: [{name, source, model, dims, chunks, bucket, backend}] async function indexesSummary(): Promise { const j = await tryFetch(`${GATEWAY}/vectors/indexes`); if (!Array.isArray(j)) return { count: 0, items: [] }; const items = j.slice(0, 12).map((i: any) => ({ name: i.index_name, source: i.source, dims: i.dimensions, chunks: i.chunk_count, backend: i.vector_backend, bucket: i.bucket, })); return { count: j.length, items }; } async function servicesSnapshot() { const [gw, sc, obs, mcp, c7, jstats, ustats] = await Promise.all([ tryFetch(`${GATEWAY}/health`), tryFetch(`${SIDECAR}/health`), tryFetch(`${OBSERVER}/health`), tryFetch(`${MCP}/health`), tryFetch(`${CONTEXT7}/health`), tryFetch(`${GATEWAY}/journal/stats`), tryFetch(`${GATEWAY}/v1/usage`), ]); return { ts: new Date().toISOString(), nodes: [ { id: "gateway", label: "Gateway :3100", status: gw ? "healthy" : "down", health: gw }, { id: "sidecar", label: "Sidecar :3200", status: sc ? "healthy" : "down", health: sc }, { id: "observer", label: "Observer :3800", status: obs ? "healthy" : "down", health: obs, stats: await tryFetch(`${OBSERVER}/stats`) }, { id: "mcp", label: "MCP :3700", status: mcp ? "healthy" : "down", health: mcp }, { id: "context7", label: "Context7 :3900", status: c7 ? "healthy" : "down", health: c7 }, ], // Virtual nodes — backed by gateway subsystems rather than own ports subsystems: [ { id: "journal", label: "Journal", stats: jstats }, { id: "usage", label: "Usage /v1", stats: ustats }, { id: "vectord", label: "Vectord", stats: await indexesSummary() }, { id: "playbook", label: "Playbook", stats: await tryFetch(`${GATEWAY}/vectors/playbook_memory/status`) }, { id: "agent", label: "Autotune", stats: await tryFetch(`${GATEWAY}/vectors/agent/status`) }, ], }; } // Extract phrase-level markers that indicate "this should be removed, // simplified, or refactored" across scrum suggestions. These are the // signals that accumulate into a refactor recommendation. const REFACTOR_PHRASES = [ "should be removed", "remove this", "dead code", "unused", "unnecessary", "duplicate of", "duplicates", "redundant", "consolidate", "merge with", "extract into", "refactor", "rewrite", "replace with", "orphaned", "stale", "deprecated", "pseudocode", "placeholder", "stub", "split this file", "too large", ]; async function refactorSignals(): Promise { // Walk every accepted review across all scrum runs. For each file, // count how many times its suggestions mention a refactor phrase. // Return a sorted list — files most often flagged for refactor first. const runsDir = `${REPO}/tests/real-world/runs`; const perFile: Record; examples: string[]; iterations: number }> = {}; try { const dirs = await Array.fromAsync(new Bun.Glob("scrum_*").scan({ cwd: runsDir, onlyFiles: false })); for (const d of dirs) { const files = await Array.fromAsync(new Bun.Glob("review_*.json").scan({ cwd: `${runsDir}/${d}` })); for (const f of files) { const p = `${runsDir}/${d}/${f}`; try { const j = JSON.parse(await Bun.file(p).text()); const file = j.file?.replace("/home/profit/lakehouse/", "") ?? "?"; const sug = (j.suggestions ?? "").toLowerCase(); if (!perFile[file]) perFile[file] = { file, hits: 0, phrases: {}, examples: [], iterations: 0 }; perFile[file].iterations++; for (const phrase of REFACTOR_PHRASES) { const count = (sug.match(new RegExp(phrase, "gi")) ?? []).length; if (count > 0) { perFile[file].hits += count; perFile[file].phrases[phrase] = (perFile[file].phrases[phrase] ?? 0) + count; // Pull one example sentence around the phrase if (perFile[file].examples.length < 3) { const idx = sug.indexOf(phrase); if (idx >= 0) { const s = Math.max(0, idx - 60); const e = Math.min(sug.length, idx + phrase.length + 80); perFile[file].examples.push("…" + sug.slice(s, e).replace(/\s+/g, " ") + "…"); } } } } } catch {} } } } catch (e) { return { error: String(e), signals: [] }; } const signals = Object.values(perFile) .filter(x => x.hits > 0) .sort((a, b) => b.hits - a.hits) .slice(0, 30); return { signals, scanned: Object.keys(perFile).length }; } async function reverseIndex(query: string, limit = 20): Promise { // Grep-like substring search across every review's suggestions. // Returns file + snippet + which iter it was in + score + verdict. const runsDir = `${REPO}/tests/real-world/runs`; if (!query || query.length < 2) return { query, hits: [] }; const q = query.toLowerCase(); const hits: any[] = []; try { const dirs = await Array.fromAsync(new Bun.Glob("scrum_*").scan({ cwd: runsDir, onlyFiles: false })); for (const d of dirs) { const files = await Array.fromAsync(new Bun.Glob("review_*.json").scan({ cwd: `${runsDir}/${d}` })); for (const f of files) { const p = `${runsDir}/${d}/${f}`; try { const j = JSON.parse(await Bun.file(p).text()); const sug = j.suggestions ?? ""; const lower = sug.toLowerCase(); const idx = lower.indexOf(q); if (idx < 0) continue; const s = Math.max(0, idx - 80); const e = Math.min(sug.length, idx + q.length + 200); hits.push({ file: j.file?.replace("/home/profit/lakehouse/", ""), run_id: d, model: j.escalated_to_model, snippet: sug.slice(s, e).replace(/\s+/g, " "), }); if (hits.length >= limit) break; } catch {} } if (hits.length >= limit) break; } } catch (e) { return { query, error: String(e), hits: [] }; } return { query, hits }; } async function fileHistory(relpath: string): Promise { // Walk all scrum_/review_*.json files and gather every review // for this file path. Returns timeline rows keyed by run_id. const runsDir = `${REPO}/tests/real-world/runs`; const out: any[] = []; try { const dirs = await Array.fromAsync(new Bun.Glob("scrum_*").scan({ cwd: runsDir, onlyFiles: false })); for (const d of dirs) { const safe = relpath.replaceAll("/", "_"); const p = `${runsDir}/${d}/review_${safe}.json`; if (await Bun.file(p).exists()) { const j = JSON.parse(await Bun.file(p).text()); const sug = j.suggestions ?? ""; const scoreMatch = sug.match(/(?:score[\s*:]*)?(\d(?:\.\d)?)\s*\/\s*10\b/i); const score = scoreMatch ? parseFloat(scoreMatch[1]) : null; const confs = [...sug.matchAll(/(?:Confidence[*:\s]*\s*|\|\s*)(\d{1,3})\s*%/gi)] .map(m => parseInt(m[1], 10)).filter(x => x >= 0 && x <= 100); const jsonConfs = [...sug.matchAll(/"confidence"\s*:\s*(\d{1,3})(?!\d)/gi)] .map(m => parseInt(m[1], 10)).filter(x => x >= 0 && x <= 100); const all = [...confs, ...jsonConfs]; const mt = await Bun.file(p).stat(); out.push({ run_id: d, reviewed_at: j.reviewed_at ?? mt.mtime, model: j.escalated_to_model, score, chars: sug.length, conf_avg: all.length ? Math.round(all.reduce((a,b)=>a+b,0)/all.length) : null, conf_min: all.length ? Math.min(...all) : null, findings: all.length, output_format: sug.includes('"verdict"') ? "forensic_json" : "markdown", // first 1200 chars preview preview: sug.slice(0, 1200), }); } } } catch (e) { return { error: String(e), history: [] }; } out.sort((a, b) => String(a.reviewed_at).localeCompare(String(b.reviewed_at))); return { file: relpath, history: out }; } Bun.serve({ port: PORT, hostname: "0.0.0.0", async fetch(req) { const url = new URL(req.url); const path = url.pathname; // Static shell if (path === "/" || path === "/index.html") { return new Response(Bun.file(`${REPO}/ui/index.html`)); } if (path === "/ui.css") { return new Response(Bun.file(`${REPO}/ui/ui.css`), { headers: { "content-type": "text/css" } }); } if (path === "/ui.js") { return new Response(Bun.file(`${REPO}/ui/ui.js`), { headers: { "content-type": "application/javascript" } }); } // Data API if (path === "/data/services") return Response.json(await servicesSnapshot()); if (path === "/data/reviews") { const n = Number(url.searchParams.get("tail") ?? 50); return Response.json(await tailJsonl(`${KB}/scrum_reviews.jsonl`, n)); } if (path === "/data/findings") return Response.json(await tailJsonl(`${KB}/phase_sweep_findings.jsonl`)); if (path === "/data/metrics") return Response.json(await tailJsonl(`${KB}/scrum_loop_metrics.jsonl`)); if (path === "/data/trust") return Response.json(await tailJsonl(`${KB}/model_trust.jsonl`, 200)); if (path === "/data/overrides") return Response.json(await tailJsonl(`${KB}/human_overrides.jsonl`)); if (path === "/data/outcomes") return Response.json(await tailJsonl(`${KB}/outcomes.jsonl`, 30)); if (path === "/data/audit_facts") return Response.json(await tailJsonl(`${KB}/audit_facts.jsonl`, 30)); if (path.startsWith("/data/file/")) { const relpath = decodeURIComponent(path.slice("/data/file/".length)); return Response.json(await fileHistory(relpath)); } if (path === "/data/refactor_signals") { return Response.json(await refactorSignals()); } if (path === "/data/search") { const q = url.searchParams.get("q") ?? ""; return Response.json(await reverseIndex(q, 30)); } // Per-service systemd log tail. Allowed service list is fixed so the // :service path param can never be used to invoke arbitrary units. if (path.startsWith("/data/logs/")) { const svc = path.slice("/data/logs/".length).split("?")[0]; const UNITS: Record = { gateway: "lakehouse.service", sidecar: "lakehouse-sidecar.service", observer: "lakehouse-observer.service", mcp: "lakehouse-agent.service", context7: "lakehouse-context7-bridge.service", auditor: "lakehouse-auditor.service", langfuse: "lakehouse-langfuse-bridge.service", }; const unit = UNITS[svc]; if (!unit) return Response.json({ error: "unknown service", allowed: Object.keys(UNITS) }, { status: 400 }); const n = Number(url.searchParams.get("n") ?? 60); try { // Use execFile-style API: pass args as array, never shell-interpolate const proc = Bun.spawn(["journalctl", "-u", unit, "-n", String(n), "--no-pager", "--output=short-iso"], { stdout: "pipe", stderr: "pipe", }); const text = await new Response(proc.stdout).text(); await proc.exited; const lines = text.split("\n").filter(Boolean); return Response.json({ service: svc, unit, lines }); } catch (e) { return Response.json({ service: svc, unit, error: String(e), lines: [] }); } } // Live scrum log tail — best-effort if (path === "/data/scrum_log") { try { const bg = await Array.fromAsync(new Bun.Glob("scrum_iter*.log").scan({ cwd: "/tmp" })); if (bg.length === 0) return Response.json({ lines: [] }); bg.sort(); const latest = `/tmp/${bg[bg.length - 1]}`; const text = await Bun.file(latest).text(); const lines = text.split("\n").slice(-80); return Response.json({ file: latest, lines }); } catch (e) { return Response.json({ error: String(e) }); } } return new Response("not found", { status: 404 }); }, }); console.log(`[ui] visual control plane listening on http://0.0.0.0:${PORT}`);