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 +2 -1
- package/docs/README.es.md +2 -1
- package/package.json +1 -1
- package/src/mcp/progress.js +66 -1
- package/src/mcp/server-handlers.js +11 -1
- package/src/utils/display.js +20 -0
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
|
|
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
|
|
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
package/src/mcp/progress.js
CHANGED
|
@@ -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
|
}
|
package/src/utils/display.js
CHANGED
|
@@ -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;
|