karajan-code 1.7.0 → 1.8.0

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.
package/README.md CHANGED
@@ -42,6 +42,7 @@ Instead of running one AI agent and manually reviewing its output, `kj` chains a
42
42
  - **Interactive checkpoints** — instead of killing long-running tasks, pauses every 5 minutes with a progress report and lets you decide: continue, stop, or adjust the time
43
43
  - **Task decomposition** — triage detects when tasks should be split and recommends subtasks; with Planning Game integration, creates linked cards with sequential blocking
44
44
  - **Retry with backoff** — automatic recovery from transient API errors (429, 5xx) with exponential backoff and jitter
45
+ - **Pipeline stage tracker** — cumulative progress view during `kj_run` showing which stages are done, running, or pending — both in CLI and via MCP events for real-time host rendering
45
46
  - **Planning Game integration** — optionally pair with [Planning Game](https://github.com/AgenteIA-Geniova/planning-game) for agile project management (tasks, sprints, estimation) — like Jira, but open-source and XP-native
46
47
 
47
48
  > **Best with MCP** — Karajan Code is designed to be used as an MCP server inside your AI agent (Claude, Codex, etc.). The agent sends tasks to `kj_run`, gets real-time progress notifications, and receives structured results — no copy-pasting needed.
@@ -447,7 +448,7 @@ Use `kj roles show <role>` to inspect any template. Create a project override to
447
448
  git clone https://github.com/manufosela/karajan-code.git
448
449
  cd karajan-code
449
450
  npm install
450
- npm test # Run 1025+ tests with Vitest
451
+ npm test # Run 1040+ tests with Vitest
451
452
  npm run test:watch # Watch mode
452
453
  npm run validate # Lint + test
453
454
  ```
package/docs/README.es.md CHANGED
@@ -41,6 +41,7 @@ En lugar de ejecutar un agente de IA y revisar manualmente su output, `kj` encad
41
41
  - **Checkpoints interactivos** — en lugar de matar tareas largas, pausa cada 5 minutos con un informe de progreso y te deja decidir: continuar, parar o ajustar el tiempo
42
42
  - **Descomposicion de tareas** — triage detecta cuando una tarea debe dividirse y recomienda subtareas; con integracion Planning Game, crea cards vinculadas con bloqueo secuencial
43
43
  - **Retry con backoff** — recuperacion automatica ante errores transitorios de API (429, 5xx) con backoff exponencial y jitter
44
+ - **Pipeline stage tracker** — vista de progreso acumulativo durante `kj_run` mostrando que stages estan completadas, en ejecucion o pendientes — tanto en CLI como via eventos MCP para renderizado en tiempo real en el host
44
45
  - **Integracion con Planning Game** — combina opcionalmente con [Planning Game](https://github.com/AgenteIA-Geniova/planning-game) para gestion agil de proyectos (tareas, sprints, estimacion) — como Jira, pero open-source y nativo XP
45
46
 
46
47
  > **Mejor con MCP** — Karajan Code esta disenado para usarse como servidor MCP dentro de tu agente de IA (Claude, Codex, etc.). El agente envia tareas a `kj_run`, recibe notificaciones de progreso en tiempo real, y obtiene resultados estructurados — sin copiar y pegar.
@@ -231,7 +232,7 @@ Usa `kj roles show <rol>` para inspeccionar cualquier template. Crea un override
231
232
  git clone https://github.com/manufosela/karajan-code.git
232
233
  cd karajan-code
233
234
  npm install
234
- npm test # Ejecutar 899+ tests con Vitest
235
+ npm test # Ejecutar 1040+ tests con Vitest
235
236
  npm run test:watch # Modo watch
236
237
  npm run validate # Lint + test
237
238
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "karajan-code",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "Local multi-agent coding orchestrator with TDD, SonarQube, and code review pipeline",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0",
@@ -21,9 +21,74 @@ export const PROGRESS_STAGES = [
21
21
  "solomon:escalate",
22
22
  "question",
23
23
  "session:end",
24
- "dry-run:summary"
24
+ "dry-run:summary",
25
+ "pipeline:tracker"
25
26
  ];
26
27
 
28
+ const PIPELINE_ORDER = [
29
+ "triage", "researcher", "planner", "coder", "refactorer", "sonar", "reviewer", "tester", "security", "commiter"
30
+ ];
31
+
32
+ export function buildPipelineTracker(config, emitter) {
33
+ const pipeline = config.pipeline || {};
34
+
35
+ const stages = PIPELINE_ORDER
36
+ .filter(name => {
37
+ if (name === "coder") return true;
38
+ if (name === "reviewer") return pipeline.reviewer?.enabled !== false;
39
+ if (name === "sonar") return pipeline.sonar?.enabled || config.sonarqube?.enabled;
40
+ return pipeline[name]?.enabled;
41
+ })
42
+ .map(name => ({ name, status: "pending", summary: undefined }));
43
+
44
+ const findStage = (name) => stages.find(s => s.name === name);
45
+
46
+ const emitTracker = () => {
47
+ emitter.emit("progress", {
48
+ type: "pipeline:tracker",
49
+ detail: { stages: stages.map(s => ({ ...s })) }
50
+ });
51
+ };
52
+
53
+ emitter.on("progress", (event) => {
54
+ const match = event.type?.match(/^(\w+):(start|end)$/);
55
+ if (!match) return;
56
+
57
+ const [, name, phase] = match;
58
+ const stage = findStage(name);
59
+ if (!stage) return;
60
+
61
+ if (phase === "start") {
62
+ stage.status = "running";
63
+ stage.summary = event.detail?.[name] || stage.summary;
64
+ } else {
65
+ stage.status = event.status === "fail" ? "failed" : "done";
66
+ stage.summary = event.detail?.summary || event.detail?.gateStatus || stage.summary;
67
+ }
68
+
69
+ emitTracker();
70
+ });
71
+
72
+ return { stages };
73
+ }
74
+
75
+ export function sendTrackerLog(server, stageName, status, summary) {
76
+ try {
77
+ server.sendLoggingMessage({
78
+ level: "info",
79
+ logger: "karajan",
80
+ data: {
81
+ type: "pipeline:tracker",
82
+ detail: {
83
+ stages: [{ name: stageName, status, summary: summary || undefined }]
84
+ }
85
+ }
86
+ });
87
+ } catch {
88
+ // best-effort
89
+ }
90
+ }
91
+
27
92
  export function buildProgressHandler(server) {
28
93
  return (event) => {
29
94
  try {
@@ -7,7 +7,7 @@ import { EventEmitter } from "node:events";
7
7
  import fs from "node:fs/promises";
8
8
  import { runKjCommand } from "./run-kj.js";
9
9
  import { normalizePlanArgs } from "./tool-arg-normalizers.js";
10
- import { buildProgressHandler, buildProgressNotifier } from "./progress.js";
10
+ import { buildProgressHandler, buildProgressNotifier, buildPipelineTracker, sendTrackerLog } from "./progress.js";
11
11
  import { runFlow, resumeFlow } from "../orchestrator.js";
12
12
  import { loadConfig, applyRunOverrides, validateConfig, resolveRole } from "../config.js";
13
13
  import { createLogger } from "../utils/logger.js";
@@ -151,6 +151,7 @@ export async function handleRunDirect(a, server, extra) {
151
151
  emitter.on("progress", buildProgressHandler(server));
152
152
  const progressNotifier = buildProgressNotifier(extra);
153
153
  if (progressNotifier) emitter.on("progress", progressNotifier);
154
+ buildPipelineTracker(config, emitter);
154
155
 
155
156
  const askQuestion = buildAskQuestion(server);
156
157
  const pgTaskId = a.pgTask || null;
@@ -191,12 +192,15 @@ export async function handlePlanDirect(a, server, extra) {
191
192
 
192
193
  const planner = createAgent(plannerRole.provider, config, logger);
193
194
  const prompt = buildPlannerPrompt({ task: a.task, context: a.context });
195
+ sendTrackerLog(server, "planner", "running", plannerRole.provider);
194
196
  const result = await planner.runTask({ prompt, role: "planner" });
195
197
 
196
198
  if (!result.ok) {
199
+ sendTrackerLog(server, "planner", "failed");
197
200
  throw new Error(result.error || result.output || "Planner failed");
198
201
  }
199
202
 
203
+ sendTrackerLog(server, "planner", "done");
200
204
  const parsed = parseMaybeJsonString(result.output);
201
205
  return { ok: true, plan: parsed || result.output, raw: result.output };
202
206
  }
@@ -216,12 +220,15 @@ export async function handleCodeDirect(a, server, extra) {
216
220
  } catch { /* no coder rules file */ }
217
221
  }
218
222
  const prompt = buildCoderPrompt({ task: a.task, coderRules, methodology: config.development?.methodology || "tdd" });
223
+ sendTrackerLog(server, "coder", "running", coderRole.provider);
219
224
  const result = await coder.runTask({ prompt, role: "coder" });
220
225
 
221
226
  if (!result.ok) {
227
+ sendTrackerLog(server, "coder", "failed");
222
228
  throw new Error(result.error || result.output || `Coder failed (exit ${result.exitCode})`);
223
229
  }
224
230
 
231
+ sendTrackerLog(server, "coder", "done");
225
232
  return { ok: true, output: result.output, exitCode: result.exitCode };
226
233
  }
227
234
 
@@ -238,12 +245,15 @@ export async function handleReviewDirect(a, server, extra) {
238
245
  const { rules } = await resolveReviewProfile({ mode: config.review_mode, projectDir: process.cwd() });
239
246
 
240
247
  const prompt = buildReviewerPrompt({ task: a.task, diff, reviewRules: rules, mode: config.review_mode });
248
+ sendTrackerLog(server, "reviewer", "running", reviewerRole.provider);
241
249
  const result = await reviewer.reviewTask({ prompt, role: "reviewer" });
242
250
 
243
251
  if (!result.ok) {
252
+ sendTrackerLog(server, "reviewer", "failed");
244
253
  throw new Error(result.error || result.output || `Reviewer failed (exit ${result.exitCode})`);
245
254
  }
246
255
 
256
+ sendTrackerLog(server, "reviewer", "done");
247
257
  const parsed = parseMaybeJsonString(result.output);
248
258
  return { ok: true, review: parsed || result.output, raw: result.output };
249
259
  }
@@ -343,6 +343,26 @@ export function printEvent(event) {
343
343
  console.log(`${ANSI.dim}Resume with: kj resume ${event.sessionId} --answer "<response>"${ANSI.reset}`);
344
344
  break;
345
345
 
346
+ case "pipeline:tracker": {
347
+ const trackerStages = event.detail?.stages || [];
348
+ console.log(` ${ANSI.dim}\u250c Pipeline${ANSI.reset}`);
349
+ for (const stage of trackerStages) {
350
+ let stIcon, stColor;
351
+ switch (stage.status) {
352
+ case "done": stIcon = "\u2713"; stColor = ANSI.green; break;
353
+ case "running": stIcon = "\u25b6"; stColor = ANSI.cyan; break;
354
+ case "failed": stIcon = "\u2717"; stColor = ANSI.red; break;
355
+ default: stIcon = "\u00b7"; stColor = ANSI.dim; break;
356
+ }
357
+ const suffix = stage.summary
358
+ ? stage.status === "running" ? ` (${stage.summary})` : ` \u2192 ${stage.summary}`
359
+ : "";
360
+ console.log(` ${ANSI.dim}\u2502${ANSI.reset} ${stColor}${stIcon} ${stage.name}${suffix}${ANSI.reset}`);
361
+ }
362
+ console.log(` ${ANSI.dim}\u2514${ANSI.reset}`);
363
+ break;
364
+ }
365
+
346
366
  case "agent:output":
347
367
  console.log(` \u2502 ${ANSI.dim}${event.message}${ANSI.reset}`);
348
368
  break;