karajan-code 1.6.2 → 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/cli.js +7 -1
- package/src/mcp/orphan-guard.js +27 -0
- package/src/mcp/progress.js +66 -1
- package/src/mcp/server-handlers.js +91 -7
- package/src/mcp/server.js +19 -3
- package/src/mcp/tools.js +3 -6
- package/src/utils/display.js +28 -1
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/cli.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
2
5
|
import { Command } from "commander";
|
|
3
6
|
import { applyRunOverrides, loadConfig, validateConfig } from "./config.js";
|
|
4
7
|
import { createLogger } from "./utils/logger.js";
|
|
@@ -15,6 +18,9 @@ import { resumeCommand } from "./commands/resume.js";
|
|
|
15
18
|
import { sonarCommand, sonarOpenCommand } from "./commands/sonar.js";
|
|
16
19
|
import { rolesCommand } from "./commands/roles.js";
|
|
17
20
|
|
|
21
|
+
const PKG_PATH = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../package.json");
|
|
22
|
+
const PKG_VERSION = JSON.parse(readFileSync(PKG_PATH, "utf8")).version;
|
|
23
|
+
|
|
18
24
|
async function withConfig(commandName, flags, fn) {
|
|
19
25
|
const { config } = await loadConfig();
|
|
20
26
|
const merged = applyRunOverrides(config, flags || {});
|
|
@@ -24,7 +30,7 @@ async function withConfig(commandName, flags, fn) {
|
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
const program = new Command();
|
|
27
|
-
program.name("kj").description("Karajan Code CLI").version(
|
|
33
|
+
program.name("kj").description("Karajan Code CLI").version(PKG_VERSION);
|
|
28
34
|
|
|
29
35
|
program
|
|
30
36
|
.command("init")
|
package/src/mcp/orphan-guard.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { watch } from "node:fs";
|
|
3
|
+
|
|
1
4
|
const DEFAULT_INTERVAL_MS = 5000;
|
|
2
5
|
|
|
3
6
|
export function setupOrphanGuard({ intervalMs = DEFAULT_INTERVAL_MS, exitFn = () => process.exit(0) } = {}) {
|
|
@@ -19,3 +22,27 @@ export function setupOrphanGuard({ intervalMs = DEFAULT_INTERVAL_MS, exitFn = ()
|
|
|
19
22
|
|
|
20
23
|
return { timer, parentPid };
|
|
21
24
|
}
|
|
25
|
+
|
|
26
|
+
export function setupVersionWatcher({ pkgPath, currentVersion, exitFn = () => process.exit(0) } = {}) {
|
|
27
|
+
if (!pkgPath) return null;
|
|
28
|
+
|
|
29
|
+
function checkVersion() {
|
|
30
|
+
try {
|
|
31
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
32
|
+
if (pkg.version !== currentVersion) {
|
|
33
|
+
exitFn();
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
} catch { /* ignore read errors */ }
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let watcher = null;
|
|
41
|
+
try {
|
|
42
|
+
watcher = watch(pkgPath, { persistent: false }, () => {
|
|
43
|
+
checkVersion();
|
|
44
|
+
});
|
|
45
|
+
} catch { /* ignore watch errors */ }
|
|
46
|
+
|
|
47
|
+
return { watcher, checkVersion };
|
|
48
|
+
}
|
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 {
|
|
@@ -4,13 +4,21 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { EventEmitter } from "node:events";
|
|
7
|
+
import fs from "node:fs/promises";
|
|
7
8
|
import { runKjCommand } from "./run-kj.js";
|
|
8
9
|
import { normalizePlanArgs } from "./tool-arg-normalizers.js";
|
|
9
|
-
import { buildProgressHandler, buildProgressNotifier } from "./progress.js";
|
|
10
|
+
import { buildProgressHandler, buildProgressNotifier, buildPipelineTracker, sendTrackerLog } from "./progress.js";
|
|
10
11
|
import { runFlow, resumeFlow } from "../orchestrator.js";
|
|
11
12
|
import { loadConfig, applyRunOverrides, validateConfig, resolveRole } from "../config.js";
|
|
12
13
|
import { createLogger } from "../utils/logger.js";
|
|
13
14
|
import { assertAgentsAvailable } from "../agents/availability.js";
|
|
15
|
+
import { createAgent } from "../agents/index.js";
|
|
16
|
+
import { buildPlannerPrompt } from "../prompts/planner.js";
|
|
17
|
+
import { buildCoderPrompt } from "../prompts/coder.js";
|
|
18
|
+
import { buildReviewerPrompt } from "../prompts/reviewer.js";
|
|
19
|
+
import { parseMaybeJsonString } from "../review/parser.js";
|
|
20
|
+
import { computeBaseRef, generateDiff } from "../review/diff-generator.js";
|
|
21
|
+
import { resolveReviewProfile } from "../review/profiles.js";
|
|
14
22
|
|
|
15
23
|
export function asObject(value) {
|
|
16
24
|
if (value && typeof value === "object") return value;
|
|
@@ -93,10 +101,10 @@ export function enrichedFailPayload(error, toolName) {
|
|
|
93
101
|
return payload;
|
|
94
102
|
}
|
|
95
103
|
|
|
96
|
-
export async function buildConfig(options) {
|
|
104
|
+
export async function buildConfig(options, commandName = "run") {
|
|
97
105
|
const { config } = await loadConfig();
|
|
98
106
|
const merged = applyRunOverrides(config, options || {});
|
|
99
|
-
validateConfig(merged,
|
|
107
|
+
validateConfig(merged, commandName);
|
|
100
108
|
return merged;
|
|
101
109
|
}
|
|
102
110
|
|
|
@@ -143,6 +151,7 @@ export async function handleRunDirect(a, server, extra) {
|
|
|
143
151
|
emitter.on("progress", buildProgressHandler(server));
|
|
144
152
|
const progressNotifier = buildProgressNotifier(extra);
|
|
145
153
|
if (progressNotifier) emitter.on("progress", progressNotifier);
|
|
154
|
+
buildPipelineTracker(config, emitter);
|
|
146
155
|
|
|
147
156
|
const askQuestion = buildAskQuestion(server);
|
|
148
157
|
const pgTaskId = a.pgTask || null;
|
|
@@ -173,6 +182,82 @@ export async function handleResumeDirect(a, server, extra) {
|
|
|
173
182
|
return { ok: true, ...result };
|
|
174
183
|
}
|
|
175
184
|
|
|
185
|
+
export async function handlePlanDirect(a, server, extra) {
|
|
186
|
+
const options = normalizePlanArgs(a);
|
|
187
|
+
const config = await buildConfig(options, "plan");
|
|
188
|
+
const logger = createLogger(config.output.log_level, "mcp");
|
|
189
|
+
|
|
190
|
+
const plannerRole = resolveRole(config, "planner");
|
|
191
|
+
await assertAgentsAvailable([plannerRole.provider]);
|
|
192
|
+
|
|
193
|
+
const planner = createAgent(plannerRole.provider, config, logger);
|
|
194
|
+
const prompt = buildPlannerPrompt({ task: a.task, context: a.context });
|
|
195
|
+
sendTrackerLog(server, "planner", "running", plannerRole.provider);
|
|
196
|
+
const result = await planner.runTask({ prompt, role: "planner" });
|
|
197
|
+
|
|
198
|
+
if (!result.ok) {
|
|
199
|
+
sendTrackerLog(server, "planner", "failed");
|
|
200
|
+
throw new Error(result.error || result.output || "Planner failed");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
sendTrackerLog(server, "planner", "done");
|
|
204
|
+
const parsed = parseMaybeJsonString(result.output);
|
|
205
|
+
return { ok: true, plan: parsed || result.output, raw: result.output };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export async function handleCodeDirect(a, server, extra) {
|
|
209
|
+
const config = await buildConfig(a, "code");
|
|
210
|
+
const logger = createLogger(config.output.log_level, "mcp");
|
|
211
|
+
|
|
212
|
+
const coderRole = resolveRole(config, "coder");
|
|
213
|
+
await assertAgentsAvailable([coderRole.provider]);
|
|
214
|
+
|
|
215
|
+
const coder = createAgent(coderRole.provider, config, logger);
|
|
216
|
+
let coderRules = null;
|
|
217
|
+
if (config.coder_rules) {
|
|
218
|
+
try {
|
|
219
|
+
coderRules = await fs.readFile(config.coder_rules, "utf8");
|
|
220
|
+
} catch { /* no coder rules file */ }
|
|
221
|
+
}
|
|
222
|
+
const prompt = buildCoderPrompt({ task: a.task, coderRules, methodology: config.development?.methodology || "tdd" });
|
|
223
|
+
sendTrackerLog(server, "coder", "running", coderRole.provider);
|
|
224
|
+
const result = await coder.runTask({ prompt, role: "coder" });
|
|
225
|
+
|
|
226
|
+
if (!result.ok) {
|
|
227
|
+
sendTrackerLog(server, "coder", "failed");
|
|
228
|
+
throw new Error(result.error || result.output || `Coder failed (exit ${result.exitCode})`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
sendTrackerLog(server, "coder", "done");
|
|
232
|
+
return { ok: true, output: result.output, exitCode: result.exitCode };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export async function handleReviewDirect(a, server, extra) {
|
|
236
|
+
const config = await buildConfig(a, "review");
|
|
237
|
+
const logger = createLogger(config.output.log_level, "mcp");
|
|
238
|
+
|
|
239
|
+
const reviewerRole = resolveRole(config, "reviewer");
|
|
240
|
+
await assertAgentsAvailable([reviewerRole.provider, config.reviewer_options?.fallback_reviewer]);
|
|
241
|
+
|
|
242
|
+
const reviewer = createAgent(reviewerRole.provider, config, logger);
|
|
243
|
+
const resolvedBase = await computeBaseRef({ baseBranch: config.base_branch, baseRef: a.baseRef });
|
|
244
|
+
const diff = await generateDiff({ baseRef: resolvedBase });
|
|
245
|
+
const { rules } = await resolveReviewProfile({ mode: config.review_mode, projectDir: process.cwd() });
|
|
246
|
+
|
|
247
|
+
const prompt = buildReviewerPrompt({ task: a.task, diff, reviewRules: rules, mode: config.review_mode });
|
|
248
|
+
sendTrackerLog(server, "reviewer", "running", reviewerRole.provider);
|
|
249
|
+
const result = await reviewer.reviewTask({ prompt, role: "reviewer" });
|
|
250
|
+
|
|
251
|
+
if (!result.ok) {
|
|
252
|
+
sendTrackerLog(server, "reviewer", "failed");
|
|
253
|
+
throw new Error(result.error || result.output || `Reviewer failed (exit ${result.exitCode})`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
sendTrackerLog(server, "reviewer", "done");
|
|
257
|
+
const parsed = parseMaybeJsonString(result.output);
|
|
258
|
+
return { ok: true, review: parsed || result.output, raw: result.output };
|
|
259
|
+
}
|
|
260
|
+
|
|
176
261
|
export async function handleToolCall(name, args, server, extra) {
|
|
177
262
|
const a = asObject(args);
|
|
178
263
|
|
|
@@ -240,22 +325,21 @@ export async function handleToolCall(name, args, server, extra) {
|
|
|
240
325
|
if (!a.task) {
|
|
241
326
|
return failPayload("Missing required field: task");
|
|
242
327
|
}
|
|
243
|
-
return
|
|
328
|
+
return handleCodeDirect(a, server, extra);
|
|
244
329
|
}
|
|
245
330
|
|
|
246
331
|
if (name === "kj_review") {
|
|
247
332
|
if (!a.task) {
|
|
248
333
|
return failPayload("Missing required field: task");
|
|
249
334
|
}
|
|
250
|
-
return
|
|
335
|
+
return handleReviewDirect(a, server, extra);
|
|
251
336
|
}
|
|
252
337
|
|
|
253
338
|
if (name === "kj_plan") {
|
|
254
339
|
if (!a.task) {
|
|
255
340
|
return failPayload("Missing required field: task");
|
|
256
341
|
}
|
|
257
|
-
|
|
258
|
-
return runKjCommand({ command: "plan", commandArgs: [a.task], options });
|
|
342
|
+
return handlePlanDirect(a, server, extra);
|
|
259
343
|
}
|
|
260
344
|
|
|
261
345
|
return failPayload(`Unknown tool: ${name}`);
|
package/src/mcp/server.js
CHANGED
|
@@ -1,14 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
2
5
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
6
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
7
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
5
8
|
import { tools } from "./tools.js";
|
|
6
9
|
import { handleToolCall, responseText, enrichedFailPayload } from "./server-handlers.js";
|
|
7
10
|
|
|
11
|
+
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const PKG_PATH = path.resolve(MODULE_DIR, "../../package.json");
|
|
13
|
+
|
|
14
|
+
function readVersion() {
|
|
15
|
+
return JSON.parse(readFileSync(PKG_PATH, "utf8")).version;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const LOADED_VERSION = readVersion();
|
|
19
|
+
|
|
8
20
|
const server = new Server(
|
|
9
21
|
{
|
|
10
22
|
name: "karajan-mcp",
|
|
11
|
-
version:
|
|
23
|
+
version: LOADED_VERSION
|
|
12
24
|
},
|
|
13
25
|
{
|
|
14
26
|
capabilities: {
|
|
@@ -22,6 +34,9 @@ const server = new Server(
|
|
|
22
34
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
|
|
23
35
|
|
|
24
36
|
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
37
|
+
// Auto-exit if package version changed (stale process)
|
|
38
|
+
if (readVersion() !== LOADED_VERSION) process.exit(0);
|
|
39
|
+
|
|
25
40
|
const name = request.params?.name;
|
|
26
41
|
const args = request.params?.arguments || {};
|
|
27
42
|
|
|
@@ -33,9 +48,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
|
33
48
|
}
|
|
34
49
|
});
|
|
35
50
|
|
|
36
|
-
// --- Orphan process protection ---
|
|
37
|
-
import { setupOrphanGuard } from "./orphan-guard.js";
|
|
51
|
+
// --- Orphan process protection + version watcher ---
|
|
52
|
+
import { setupOrphanGuard, setupVersionWatcher } from "./orphan-guard.js";
|
|
38
53
|
setupOrphanGuard();
|
|
54
|
+
setupVersionWatcher({ pkgPath: PKG_PATH, currentVersion: LOADED_VERSION });
|
|
39
55
|
|
|
40
56
|
const transport = new StdioServerTransport();
|
|
41
57
|
await server.connect(transport);
|
package/src/mcp/tools.js
CHANGED
|
@@ -146,8 +146,7 @@ export const tools = [
|
|
|
146
146
|
task: { type: "string" },
|
|
147
147
|
coder: { type: "string" },
|
|
148
148
|
coderModel: { type: "string" },
|
|
149
|
-
kjHome: { type: "string" }
|
|
150
|
-
timeoutMs: { type: "number" }
|
|
149
|
+
kjHome: { type: "string" }
|
|
151
150
|
}
|
|
152
151
|
}
|
|
153
152
|
},
|
|
@@ -162,8 +161,7 @@ export const tools = [
|
|
|
162
161
|
reviewer: { type: "string" },
|
|
163
162
|
reviewerModel: { type: "string" },
|
|
164
163
|
baseRef: { type: "string" },
|
|
165
|
-
kjHome: { type: "string" }
|
|
166
|
-
timeoutMs: { type: "number" }
|
|
164
|
+
kjHome: { type: "string" }
|
|
167
165
|
}
|
|
168
166
|
}
|
|
169
167
|
},
|
|
@@ -179,8 +177,7 @@ export const tools = [
|
|
|
179
177
|
plannerModel: { type: "string" },
|
|
180
178
|
coder: { type: "string", description: "Legacy alias for planner" },
|
|
181
179
|
coderModel: { type: "string", description: "Legacy alias for plannerModel" },
|
|
182
|
-
kjHome: { type: "string" }
|
|
183
|
-
timeoutMs: { type: "number" }
|
|
180
|
+
kjHome: { type: "string" }
|
|
184
181
|
}
|
|
185
182
|
}
|
|
186
183
|
}
|
package/src/utils/display.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const DISPLAY_PKG_PATH = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../package.json");
|
|
6
|
+
const DISPLAY_VERSION = JSON.parse(readFileSync(DISPLAY_PKG_PATH, "utf8")).version;
|
|
7
|
+
|
|
1
8
|
const ANSI = {
|
|
2
9
|
reset: "\x1b[0m",
|
|
3
10
|
bold: "\x1b[1m",
|
|
@@ -57,7 +64,7 @@ export function formatElapsed(ms) {
|
|
|
57
64
|
const BAR = `${ANSI.dim}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${ANSI.reset}`;
|
|
58
65
|
|
|
59
66
|
export function printHeader({ task, config }) {
|
|
60
|
-
const version =
|
|
67
|
+
const version = DISPLAY_VERSION;
|
|
61
68
|
console.log(BAR);
|
|
62
69
|
console.log(`${ANSI.bold}${ANSI.cyan}\u25b6 Karajan Code v${version}${ANSI.reset}`);
|
|
63
70
|
console.log(BAR);
|
|
@@ -336,6 +343,26 @@ export function printEvent(event) {
|
|
|
336
343
|
console.log(`${ANSI.dim}Resume with: kj resume ${event.sessionId} --answer "<response>"${ANSI.reset}`);
|
|
337
344
|
break;
|
|
338
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
|
+
|
|
339
366
|
case "agent:output":
|
|
340
367
|
console.log(` \u2502 ${ANSI.dim}${event.message}${ANSI.reset}`);
|
|
341
368
|
break;
|