karajan-code 2.19.0 → 2.19.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +4 -0
  2. package/package.json +9 -1
  3. package/packages/hu-board/README.md +69 -0
  4. package/packages/hu-board/package.json +23 -0
  5. package/packages/hu-board/public/app.js +4038 -0
  6. package/packages/hu-board/public/index.html +55 -0
  7. package/packages/hu-board/public/pipeline.css +132 -0
  8. package/packages/hu-board/public/pipeline.html +48 -0
  9. package/packages/hu-board/public/pipeline.js +200 -0
  10. package/packages/hu-board/public/styles.css +918 -0
  11. package/packages/hu-board/src/auth.js +92 -0
  12. package/packages/hu-board/src/boot-info.js +16 -0
  13. package/packages/hu-board/src/cleanup-zombies.js +142 -0
  14. package/packages/hu-board/src/command-runner.js +134 -0
  15. package/packages/hu-board/src/config-yaml.js +285 -0
  16. package/packages/hu-board/src/db.js +739 -0
  17. package/packages/hu-board/src/ephemeral-cleaner.js +198 -0
  18. package/packages/hu-board/src/event-bus.js +58 -0
  19. package/packages/hu-board/src/format.js +83 -0
  20. package/packages/hu-board/src/hu-zombie-reaper.js +103 -0
  21. package/packages/hu-board/src/journal-parser.js +180 -0
  22. package/packages/hu-board/src/plan-mutations.js +422 -0
  23. package/packages/hu-board/src/preflight.js +285 -0
  24. package/packages/hu-board/src/routes/api.js +1271 -0
  25. package/packages/hu-board/src/routes/pipeline.js +45 -0
  26. package/packages/hu-board/src/run-tracker.js +121 -0
  27. package/packages/hu-board/src/server.js +435 -0
  28. package/packages/hu-board/src/sync.js +626 -0
  29. package/packages/hu-board/src/token-store.js +57 -0
  30. package/packages/hu-board/src/zombie-reaper.js +207 -0
  31. package/src/audit/webperf-input.js +4 -7
  32. package/src/checks/dir-setup.js +5 -3
  33. package/src/cli/register-pipeline.js +1 -0
  34. package/src/cli/register-plan.js +1 -0
  35. package/src/commands/plan/generate.js +15 -0
  36. package/src/commands/run.js +19 -1
  37. package/src/commands/webperf.js +4 -6
  38. package/src/config/role-resolver.js +3 -1
  39. package/src/mcp/handlers/direct-handlers.js +3 -0
  40. package/src/mcp/handlers/run-handler.js +5 -0
  41. package/src/mcp/tools.js +4 -2
  42. package/src/prompts/spec-reviewer.js +66 -0
  43. package/src/roles/perf-role.js +4 -4
  44. package/src/roles/spec-reviewer-role.js +106 -0
  45. package/src/sonar/api.js +17 -3
  46. package/src/sonar/token-recovery.js +67 -0
  47. package/src/spec-review/refine-loop.js +58 -0
  48. package/src/spec-review/run-spec-review.js +70 -0
  49. package/src/utils/board-prompt-bridge.js +4 -5
  50. package/src/utils/display/spec-findings.js +44 -0
  51. package/src/utils/paths.js +15 -0
  52. package/src/utils/run-registry.js +4 -4
  53. package/templates/roles/spec-reviewer.md +38 -0
@@ -0,0 +1,55 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Karajan HU Board</title>
7
+ <link rel="stylesheet" href="/styles.css">
8
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><rect width='32' height='32' rx='6' fill='%237c3aed'/><text x='16' y='22' text-anchor='middle' fill='white' font-size='16' font-family='sans-serif' font-weight='bold'>K</text></svg>">
9
+ </head>
10
+ <body>
11
+ <header class="header">
12
+ <div class="header__logo">
13
+ <svg viewBox="0 0 32 32" width="32" height="32">
14
+ <rect width="32" height="32" rx="6" fill="#7c3aed"/>
15
+ <text x="16" y="22" text-anchor="middle" fill="white" font-size="16" font-family="sans-serif" font-weight="bold">K</text>
16
+ </svg>
17
+ <div>
18
+ <div class="header__title">Karajan HU Board</div>
19
+ <div class="header__subtitle">story management dashboard</div>
20
+ </div>
21
+ </div>
22
+ <nav class="header__nav">
23
+ <button class="nav-btn active" data-view="board" title="Kanban board: every HU of the selected project, grouped by status (Pending → Certified → Done). Click an HU card to expand the detail panel.">Board</button>
24
+ <button class="nav-btn" data-view="graph" title="Dependency graph of the selected project's HUs — who blocks whom. Useful before scheduling work.">Graph</button>
25
+ <button class="nav-btn" data-view="dashboard" title="Per-project landing: stats grid + project list. Click a project card to focus the kanban / graph on that project.">Dashboard</button>
26
+ <button class="nav-btn" data-view="sessions" title="Every kj run that has touched the board, newest first. Filter by project; click a session to inspect its iterations, commits and checkpoints.">Sessions</button>
27
+ <a class="nav-btn" href="/pipeline.html" title="Live observability of pipeline runs (Karajan v2.7+): stage timings, agent calls, decision logs.">Pipeline</a>
28
+ <select class="project-select" id="project-select">
29
+ <option value="">All Projects</option>
30
+ </select>
31
+ <div class="header__controls">
32
+ <button class="control-btn" id="help-btn" title="What does each view do? Quick reference for Board / Graph / Dashboard / Sessions / Pipeline.">?</button>
33
+ <button class="control-btn" id="config-btn" title="Configurar Karajan (modelos, agentes, tope de iteraciones, …)">⚙</button>
34
+ <button class="control-btn" id="commands-btn" title="Launch a Karajan CLI command (kj plan, kj architect, kj clean…)">⚡</button>
35
+ <button class="control-btn" id="restart-btn" title="Restart the HU Board server (preserves URL, the page reconnects automatically)">🔁</button>
36
+ <button class="control-btn" id="sync-btn" title="Re-scan disk for new batches">🔄</button>
37
+ <button class="control-btn" id="force-refresh-btn" title="Force a clean reload — wipes browser caches and reloads. Use when the UI looks stale and a normal refresh isn't fixing it." onclick="window.forceRefresh()">🧹</button>
38
+ </div>
39
+ </nav>
40
+ </header>
41
+
42
+ <main class="main" id="app">
43
+ <div class="loading">
44
+ <div class="loading__spinner"></div>
45
+ <p>Loading...</p>
46
+ </div>
47
+ </main>
48
+
49
+ <div class="modal-backdrop hidden" id="modal-backdrop">
50
+ <div class="modal" id="modal-content"></div>
51
+ </div>
52
+
53
+ <script src="/app.js"></script>
54
+ </body>
55
+ </html>
@@ -0,0 +1,132 @@
1
+ /* Pipeline observability view (TSK-0325). */
2
+
3
+ .topbar {
4
+ display: flex;
5
+ align-items: center;
6
+ gap: 1.5rem;
7
+ padding: 0.75rem 1.5rem;
8
+ border-bottom: 1px solid #2a3a4e;
9
+ background: #0f1420;
10
+ }
11
+ .topbar h1 { font-size: 1.125rem; margin: 0; color: #e1e8f2; }
12
+ .topbar .brand {
13
+ color: #7d9ebc;
14
+ text-decoration: none;
15
+ font-weight: 500;
16
+ }
17
+ .topbar .brand:hover { color: #e1e8f2; }
18
+
19
+ .session-picker {
20
+ margin-left: auto;
21
+ display: flex;
22
+ align-items: center;
23
+ gap: 0.5rem;
24
+ color: #94a4ac;
25
+ }
26
+ .session-picker select {
27
+ background: #1a2332;
28
+ color: #e1e8f2;
29
+ border: 1px solid #2a3a4e;
30
+ padding: 0.25rem 0.5rem;
31
+ border-radius: 4px;
32
+ min-width: 220px;
33
+ }
34
+ .session-picker button {
35
+ background: #1a2332;
36
+ color: #e1e8f2;
37
+ border: 1px solid #2a3a4e;
38
+ padding: 0.25rem 0.5rem;
39
+ border-radius: 4px;
40
+ cursor: pointer;
41
+ }
42
+ .session-picker button:hover { background: #2a3a4e; }
43
+
44
+ main {
45
+ padding: 1.5rem;
46
+ display: grid;
47
+ gap: 1.5rem;
48
+ max-width: 1200px;
49
+ margin: 0 auto;
50
+ }
51
+
52
+ .role-grid {
53
+ display: grid;
54
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
55
+ gap: 0.75rem;
56
+ }
57
+
58
+ .role-card {
59
+ border: 1px solid #2a3a4e;
60
+ border-radius: 6px;
61
+ padding: 0.75rem;
62
+ background: #0f1420;
63
+ display: flex;
64
+ flex-direction: column;
65
+ gap: 0.25rem;
66
+ }
67
+ .role-card .name {
68
+ font-weight: 600;
69
+ font-size: 0.875rem;
70
+ color: #e1e8f2;
71
+ }
72
+ .role-card .meta {
73
+ font-size: 0.75rem;
74
+ color: #7d9ebc;
75
+ }
76
+ .role-card[data-status="done"] { border-color: #4caf50; }
77
+ .role-card[data-status="running"] { border-color: #f4b63f; }
78
+ .role-card[data-status="error"] { border-color: #e74c3c; }
79
+ .role-card[data-status="idle"] { opacity: 0.65; }
80
+
81
+ .panel {
82
+ border: 1px solid #2a3a4e;
83
+ border-radius: 6px;
84
+ padding: 1rem 1.25rem;
85
+ background: #0f1420;
86
+ }
87
+ .panel h2 {
88
+ margin: 0 0 0.75rem 0;
89
+ font-size: 1rem;
90
+ color: #e1e8f2;
91
+ }
92
+ .event-list {
93
+ list-style: none;
94
+ padding: 0;
95
+ margin: 0;
96
+ max-height: 420px;
97
+ overflow-y: auto;
98
+ display: grid;
99
+ gap: 0.5rem;
100
+ }
101
+ .event-list li {
102
+ border-left: 2px solid #2a3a4e;
103
+ padding: 0.5rem 0.75rem;
104
+ background: #0a0e18;
105
+ border-radius: 3px;
106
+ }
107
+ .event-list li strong { color: #e1e8f2; display: block; margin-bottom: 0.25rem; }
108
+ .event-list li pre {
109
+ margin: 0;
110
+ white-space: pre-wrap;
111
+ font-size: 0.8125rem;
112
+ color: #94a4ac;
113
+ }
114
+
115
+ .summary {
116
+ white-space: pre-wrap;
117
+ font-family: ui-monospace, SFMono-Regular, monospace;
118
+ font-size: 0.8125rem;
119
+ color: #94a4ac;
120
+ margin: 0;
121
+ max-height: 520px;
122
+ overflow-y: auto;
123
+ }
124
+
125
+ footer {
126
+ text-align: center;
127
+ font-size: 0.75rem;
128
+ color: #7d9ebc;
129
+ padding: 0.75rem 1.5rem;
130
+ border-top: 1px solid #2a3a4e;
131
+ }
132
+ footer .sep { margin: 0 0.5rem; opacity: 0.5; }
@@ -0,0 +1,48 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Karajan — Pipeline Observability</title>
7
+ <link rel="stylesheet" href="/styles.css" />
8
+ <link rel="stylesheet" href="/pipeline.css" />
9
+ </head>
10
+ <body>
11
+ <header class="topbar">
12
+ <a class="brand" href="/">← HU Board</a>
13
+ <h1>Pipeline Observability</h1>
14
+ <div class="session-picker">
15
+ <label for="sessionSelect">Session:</label>
16
+ <select id="sessionSelect" aria-label="Active session"></select>
17
+ <button id="refreshBtn" type="button" title="Refresh now">⟳</button>
18
+ </div>
19
+ </header>
20
+
21
+ <main>
22
+ <section id="roleGrid" class="role-grid" aria-label="Pipeline roles"></section>
23
+
24
+ <section class="panel">
25
+ <h2>Solomon arbitrations</h2>
26
+ <ol id="solomonList" class="event-list"></ol>
27
+ </section>
28
+
29
+ <section class="panel">
30
+ <h2>Iterations</h2>
31
+ <ol id="iterationList" class="event-list"></ol>
32
+ </section>
33
+
34
+ <section class="panel">
35
+ <h2>Summary</h2>
36
+ <pre id="summaryBlock" class="summary"></pre>
37
+ </section>
38
+ </main>
39
+
40
+ <footer>
41
+ <span id="lastRefresh">—</span>
42
+ <span class="sep">·</span>
43
+ <span>Auto-refresh every 2 s (click ⟳ to force)</span>
44
+ </footer>
45
+
46
+ <script src="/pipeline.js" type="module"></script>
47
+ </body>
48
+ </html>
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Pipeline observability client (TSK-0325).
3
+ *
4
+ * - Polls /api/pipeline/sessions for the session list (on refresh).
5
+ * - Polls /api/pipeline/sessions/:id every 2 s for the selected session.
6
+ * - Renders role cards, Solomon arbitrations, iteration log, summary.
7
+ *
8
+ * Zero dependencies, native fetch + DOM. Runs as ES module.
9
+ */
10
+
11
+ const POLL_INTERVAL_MS = 2000;
12
+
13
+ const els = {
14
+ sessionSelect: document.getElementById("sessionSelect"),
15
+ refreshBtn: document.getElementById("refreshBtn"),
16
+ roleGrid: document.getElementById("roleGrid"),
17
+ solomonList: document.getElementById("solomonList"),
18
+ iterationList: document.getElementById("iterationList"),
19
+ summaryBlock: document.getElementById("summaryBlock"),
20
+ lastRefresh: document.getElementById("lastRefresh"),
21
+ };
22
+
23
+ // Fixed set of pipeline roles we always render, even if the journal has not
24
+ // mentioned them yet. Mirrors src/orchestrator/stages/ + src/roles/.
25
+ const KNOWN_ROLES = [
26
+ "triage",
27
+ "discover",
28
+ "domain-curator",
29
+ "researcher",
30
+ "architect",
31
+ "planner",
32
+ "coder",
33
+ "refactorer",
34
+ "sonar",
35
+ "reviewer",
36
+ "tester",
37
+ "security",
38
+ "impeccable",
39
+ "audit",
40
+ ];
41
+
42
+ let pollTimer = null;
43
+ let currentSessionId = null;
44
+
45
+ function escapeHtml(s) {
46
+ return String(s ?? "").replace(/[&<>"']/g, (c) => ({
47
+ "&": "&amp;",
48
+ "<": "&lt;",
49
+ ">": "&gt;",
50
+ '"': "&quot;",
51
+ "'": "&#39;",
52
+ }[c]));
53
+ }
54
+
55
+ async function api(path) {
56
+ const res = await fetch(path, { headers: { accept: "application/json" } });
57
+ if (!res.ok) throw new Error(`${path} → ${res.status}`);
58
+ return res.json();
59
+ }
60
+
61
+ /**
62
+ * Walk the iteration list and derive {status, lastIteration, retries} per
63
+ * role. A role is "done" if it appears in any iteration; "running" if it
64
+ * appears in the latest iteration and has no "error" marker; "error" if the
65
+ * body of its latest stage contains /error|failed/i.
66
+ */
67
+ function deriveRoleStatus(iterations) {
68
+ const summary = new Map();
69
+ iterations.forEach((it) => {
70
+ it.stages.forEach((stage) => {
71
+ const name = String(stage.name || "").toLowerCase();
72
+ const prev = summary.get(name) || { count: 0, lastIteration: 0, lastBody: "" };
73
+ prev.count += 1;
74
+ prev.lastIteration = Math.max(prev.lastIteration, it.number);
75
+ prev.lastBody = stage.body;
76
+ summary.set(name, prev);
77
+ });
78
+ });
79
+ return summary;
80
+ }
81
+
82
+ function renderRoleGrid(iterations) {
83
+ const status = deriveRoleStatus(iterations);
84
+ const latest = iterations.reduce((m, it) => Math.max(m, it.number), 0);
85
+ const rendered = new Set();
86
+ const cards = [];
87
+ const makeCard = (roleName) => {
88
+ const info = status.get(roleName.toLowerCase());
89
+ let state = "idle";
90
+ let meta = "not run yet";
91
+ if (info) {
92
+ const errored = /error|failed|✗|blocked/i.test(info.lastBody);
93
+ const isLatest = info.lastIteration === latest && latest > 0;
94
+ state = errored ? "error" : (isLatest ? "running" : "done");
95
+ meta = `iter #${info.lastIteration} · ${info.count} run${info.count > 1 ? "s" : ""}`;
96
+ }
97
+ rendered.add(roleName.toLowerCase());
98
+ return `<article class="role-card" data-status="${state}"><span class="name">${escapeHtml(roleName)}</span><span class="meta">${escapeHtml(meta)}</span></article>`;
99
+ };
100
+ for (const r of KNOWN_ROLES) cards.push(makeCard(r));
101
+ // Render any journal-observed role we did not know about (future roles).
102
+ for (const [name] of status) {
103
+ if (!rendered.has(name)) cards.push(makeCard(name));
104
+ }
105
+ els.roleGrid.innerHTML = cards.join("");
106
+ }
107
+
108
+ function renderSolomon(decisions) {
109
+ const solomon = decisions.filter((d) => /solomon/i.test(d.heading));
110
+ if (solomon.length === 0) {
111
+ els.solomonList.innerHTML = `<li><em>No Solomon arbitrations in this session yet.</em></li>`;
112
+ return;
113
+ }
114
+ els.solomonList.innerHTML = solomon
115
+ .map((s) => `<li><strong>${escapeHtml(s.heading)}</strong><pre>${escapeHtml(s.body)}</pre></li>`)
116
+ .join("");
117
+ }
118
+
119
+ function renderIterations(iterations) {
120
+ if (iterations.length === 0) {
121
+ els.iterationList.innerHTML = `<li><em>Session has not produced iterations yet.</em></li>`;
122
+ return;
123
+ }
124
+ els.iterationList.innerHTML = iterations
125
+ .slice()
126
+ .reverse() // latest on top
127
+ .map((it) => {
128
+ const stages = it.stages
129
+ .map((st) => `<div><strong>${escapeHtml(st.name)}</strong><pre>${escapeHtml(st.body)}</pre></div>`)
130
+ .join("");
131
+ const dur = it.duration ? ` — ${escapeHtml(it.duration)}` : "";
132
+ return `<li><strong>${escapeHtml(it.title)}${dur}</strong>${stages}</li>`;
133
+ })
134
+ .join("");
135
+ }
136
+
137
+ function renderSummary(summary) {
138
+ if (!summary) { els.summaryBlock.textContent = ""; return; }
139
+ const body = [summary.preamble, ...summary.sections.map((s) => `## ${s.heading}\n${s.body}`)]
140
+ .filter(Boolean).join("\n\n");
141
+ els.summaryBlock.textContent = body.trim() || "(empty summary.md)";
142
+ }
143
+
144
+ async function refreshOnce() {
145
+ if (!currentSessionId) return;
146
+ try {
147
+ const journal = await api(`/api/pipeline/sessions/${encodeURIComponent(currentSessionId)}`);
148
+ renderRoleGrid(journal.iterations);
149
+ renderSolomon(journal.decisions);
150
+ renderIterations(journal.iterations);
151
+ renderSummary(journal.summary);
152
+ els.lastRefresh.textContent = `Last refresh: ${new Date().toLocaleTimeString()}`;
153
+ } catch (err) {
154
+ els.lastRefresh.textContent = `Error: ${err.message}`;
155
+ }
156
+ }
157
+
158
+ async function loadSessions() {
159
+ const { sessions } = await api("/api/pipeline/sessions");
160
+ els.sessionSelect.innerHTML = sessions
161
+ .map((s) => `<option value="${escapeHtml(s.id)}">${escapeHtml(s.id)} (${new Date(s.mtime).toLocaleString()})</option>`)
162
+ .join("");
163
+ if (sessions.length > 0 && !currentSessionId) {
164
+ currentSessionId = sessions[0].id;
165
+ }
166
+ if (currentSessionId) {
167
+ els.sessionSelect.value = currentSessionId;
168
+ }
169
+ }
170
+
171
+ function startPolling() {
172
+ if (pollTimer) clearInterval(pollTimer);
173
+ pollTimer = setInterval(refreshOnce, POLL_INTERVAL_MS);
174
+ }
175
+
176
+ function stopPolling() {
177
+ if (pollTimer) { clearInterval(pollTimer); pollTimer = null; }
178
+ }
179
+
180
+ els.sessionSelect.addEventListener("change", (e) => {
181
+ currentSessionId = e.target.value || null;
182
+ refreshOnce();
183
+ });
184
+
185
+ els.refreshBtn.addEventListener("click", async () => {
186
+ await loadSessions();
187
+ await refreshOnce();
188
+ });
189
+
190
+ document.addEventListener("visibilitychange", () => {
191
+ if (document.hidden) stopPolling();
192
+ else startPolling();
193
+ });
194
+
195
+ // Boot
196
+ (async () => {
197
+ await loadSessions();
198
+ await refreshOnce();
199
+ startPolling();
200
+ })();