karajan-code 1.16.0 → 1.18.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/package.json +1 -1
- package/src/activity-log.js +13 -13
- package/src/agents/availability.js +2 -3
- package/src/agents/claude-agent.js +42 -21
- package/src/agents/model-registry.js +1 -1
- package/src/becaria/dispatch.js +1 -1
- package/src/becaria/repo.js +3 -3
- package/src/cli.js +5 -2
- package/src/commands/doctor.js +154 -108
- package/src/commands/init.js +101 -90
- package/src/commands/plan.js +1 -1
- package/src/commands/report.js +77 -71
- package/src/commands/roles.js +0 -1
- package/src/commands/run.js +2 -3
- package/src/config.js +174 -93
- package/src/git/automation.js +3 -4
- package/src/guards/intent-guard.js +123 -0
- package/src/guards/output-guard.js +158 -0
- package/src/guards/perf-guard.js +126 -0
- package/src/guards/policy-resolver.js +3 -3
- package/src/mcp/orphan-guard.js +1 -2
- package/src/mcp/progress.js +4 -3
- package/src/mcp/run-kj.js +1 -0
- package/src/mcp/server-handlers.js +242 -253
- package/src/mcp/server.js +4 -3
- package/src/mcp/tools.js +2 -0
- package/src/orchestrator/agent-fallback.js +1 -3
- package/src/orchestrator/iteration-stages.js +206 -170
- package/src/orchestrator/pre-loop-stages.js +200 -34
- package/src/orchestrator/solomon-rules.js +2 -2
- package/src/orchestrator.js +902 -746
- package/src/planning-game/adapter.js +23 -20
- package/src/planning-game/architect-adrs.js +45 -0
- package/src/planning-game/client.js +15 -1
- package/src/planning-game/decomposition.js +7 -5
- package/src/prompts/architect.js +88 -0
- package/src/prompts/discover.js +54 -53
- package/src/prompts/planner.js +53 -33
- package/src/prompts/triage.js +8 -16
- package/src/review/parser.js +18 -19
- package/src/review/profiles.js +2 -2
- package/src/review/schema.js +3 -3
- package/src/review/scope-filter.js +3 -4
- package/src/roles/architect-role.js +122 -0
- package/src/roles/commiter-role.js +2 -2
- package/src/roles/discover-role.js +59 -67
- package/src/roles/index.js +1 -0
- package/src/roles/planner-role.js +54 -38
- package/src/roles/refactorer-role.js +8 -7
- package/src/roles/researcher-role.js +6 -7
- package/src/roles/reviewer-role.js +4 -5
- package/src/roles/security-role.js +3 -4
- package/src/roles/solomon-role.js +6 -18
- package/src/roles/sonar-role.js +5 -1
- package/src/roles/tester-role.js +8 -5
- package/src/roles/triage-role.js +2 -2
- package/src/session-cleanup.js +29 -24
- package/src/session-store.js +1 -1
- package/src/sonar/api.js +1 -1
- package/src/sonar/manager.js +1 -1
- package/src/sonar/project-key.js +5 -5
- package/src/sonar/scanner.js +34 -65
- package/src/utils/display.js +312 -272
- package/src/utils/git.js +3 -3
- package/src/utils/logger.js +6 -1
- package/src/utils/model-selector.js +5 -5
- package/src/utils/process.js +80 -102
- package/src/utils/rate-limit-detector.js +13 -13
- package/src/utils/run-log.js +55 -52
- package/templates/kj.config.yml +33 -0
- package/templates/roles/architect.md +62 -0
- package/templates/roles/planner.md +1 -0
|
@@ -60,71 +60,57 @@ export function failPayload(message, details = {}) {
|
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
const ERROR_CLASSIFIERS = [
|
|
64
|
+
{
|
|
65
|
+
test: (lower) => lower.includes("without output") || lower.includes("silent for") || lower.includes("unresponsive") || lower.includes("exceeded max silence"),
|
|
66
|
+
category: "agent_stall",
|
|
67
|
+
suggestion: "Agent output stalled. Check live details with kj_status, then retry with a smaller prompt or increase session.max_agent_silence_minutes if needed."
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
test: (lower) => lower.includes("sonar") && (lower.includes("connect") || lower.includes("econnrefused") || lower.includes("not available") || lower.includes("not running")),
|
|
71
|
+
category: "sonar_unavailable",
|
|
72
|
+
suggestion: "SonarQube is not reachable. Try: kj_init to set up SonarQube, or run 'docker start sonarqube' if already installed. Use --no-sonar to skip SonarQube."
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
test: (lower) => lower.includes("401") || lower.includes("unauthorized") || lower.includes("invalid token"),
|
|
76
|
+
category: "auth_error",
|
|
77
|
+
suggestion: "Authentication failed. Regenerate the SonarQube token and update it via kj_init or in ~/.karajan/kj.config.yml under sonarqube.token."
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
test: (lower) => lower.includes("config") && (lower.includes("missing") || lower.includes("not found") || lower.includes("invalid")),
|
|
81
|
+
category: "config_error",
|
|
82
|
+
suggestion: "Configuration issue detected. Run kj_doctor to diagnose, or kj_init to create a fresh config."
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
test: (lower) => lower.includes("missing provider") || lower.includes("not found") && (lower.includes("claude") || lower.includes("codex") || lower.includes("gemini") || lower.includes("aider")),
|
|
86
|
+
category: "agent_missing",
|
|
87
|
+
suggestion: "Required agent CLI not found. Run kj_doctor to check which agents are installed and get installation instructions."
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
test: (lower) => lower.includes("timed out") || lower.includes("timeout"),
|
|
91
|
+
category: "timeout",
|
|
92
|
+
suggestion: "The agent did not complete in time. Try: (1) increase --max-iteration-minutes (default: 5), (2) split the task into smaller pieces, (3) use kj_code for single-agent tasks. If a SonarQube scan timed out, check Docker health."
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
test: (lower) => lower.includes("you are on the base branch"),
|
|
96
|
+
category: "branch_error",
|
|
97
|
+
suggestion: "Create a feature branch before running Karajan. Use 'git checkout -b feat/<task-description>' and then retry. Do NOT run kj_code directly on the base branch."
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
test: (lower) => lower.includes("not a git repository"),
|
|
101
|
+
category: "git_error",
|
|
102
|
+
suggestion: "Current directory is not a git repository. Navigate to your project root or initialize git with 'git init'."
|
|
103
|
+
}
|
|
104
|
+
];
|
|
105
|
+
|
|
63
106
|
export function classifyError(error) {
|
|
64
107
|
const msg = error?.message || String(error);
|
|
65
108
|
const lower = msg.toLowerCase();
|
|
66
109
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|| lower.includes("unresponsive")
|
|
71
|
-
|| lower.includes("exceeded max silence")
|
|
72
|
-
) {
|
|
73
|
-
return {
|
|
74
|
-
category: "agent_stall",
|
|
75
|
-
suggestion: "Agent output stalled. Check live details with kj_status, then retry with a smaller prompt or increase session.max_agent_silence_minutes if needed."
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (lower.includes("sonar") && (lower.includes("connect") || lower.includes("econnrefused") || lower.includes("not available") || lower.includes("not running"))) {
|
|
80
|
-
return {
|
|
81
|
-
category: "sonar_unavailable",
|
|
82
|
-
suggestion: "SonarQube is not reachable. Try: kj_init to set up SonarQube, or run 'docker start sonarqube' if already installed. Use --no-sonar to skip SonarQube."
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (lower.includes("401") || lower.includes("unauthorized") || lower.includes("invalid token")) {
|
|
87
|
-
return {
|
|
88
|
-
category: "auth_error",
|
|
89
|
-
suggestion: "Authentication failed. Regenerate the SonarQube token and update it via kj_init or in ~/.karajan/kj.config.yml under sonarqube.token."
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (lower.includes("config") && (lower.includes("missing") || lower.includes("not found") || lower.includes("invalid"))) {
|
|
94
|
-
return {
|
|
95
|
-
category: "config_error",
|
|
96
|
-
suggestion: "Configuration issue detected. Run kj_doctor to diagnose, or kj_init to create a fresh config."
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (lower.includes("missing provider") || lower.includes("not found") && (lower.includes("claude") || lower.includes("codex") || lower.includes("gemini") || lower.includes("aider"))) {
|
|
101
|
-
return {
|
|
102
|
-
category: "agent_missing",
|
|
103
|
-
suggestion: "Required agent CLI not found. Run kj_doctor to check which agents are installed and get installation instructions."
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (lower.includes("timed out") || lower.includes("timeout")) {
|
|
108
|
-
return {
|
|
109
|
-
category: "timeout",
|
|
110
|
-
suggestion: "The agent did not complete in time. Try: (1) increase --max-iteration-minutes (default: 5), (2) split the task into smaller pieces, (3) use kj_code for single-agent tasks. If a SonarQube scan timed out, check Docker health."
|
|
111
|
-
};
|
|
110
|
+
const match = ERROR_CLASSIFIERS.find(c => c.test(lower));
|
|
111
|
+
if (match) {
|
|
112
|
+
return { category: match.category, suggestion: match.suggestion };
|
|
112
113
|
}
|
|
113
|
-
|
|
114
|
-
if (lower.includes("you are on the base branch")) {
|
|
115
|
-
return {
|
|
116
|
-
category: "branch_error",
|
|
117
|
-
suggestion: "Create a feature branch before running Karajan. Use 'git checkout -b feat/<task-description>' and then retry. Do NOT run kj_code directly on the base branch."
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (lower.includes("not a git repository")) {
|
|
122
|
-
return {
|
|
123
|
-
category: "git_error",
|
|
124
|
-
suggestion: "Current directory is not a git repository. Navigate to your project root or initialize git with 'git init'."
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
114
|
return { category: "unknown", suggestion: null };
|
|
129
115
|
}
|
|
130
116
|
|
|
@@ -254,9 +240,9 @@ function buildDirectEmitter(server, runLog, extra) {
|
|
|
254
240
|
const emitter = new EventEmitter();
|
|
255
241
|
emitter.on("progress", (event) => {
|
|
256
242
|
try {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
243
|
+
let level = "debug";
|
|
244
|
+
if (event.type === "agent:stall") level = "warning";
|
|
245
|
+
else if (event.type === "agent:heartbeat") level = "info";
|
|
260
246
|
server.sendLoggingMessage({ level, logger: "karajan", data: event });
|
|
261
247
|
} catch { /* best-effort */ }
|
|
262
248
|
if (runLog) runLog.logEvent(event);
|
|
@@ -282,8 +268,10 @@ export async function handlePlanDirect(a, server, extra) {
|
|
|
282
268
|
const plannerTimeoutMs = Number(config?.session?.max_planner_minutes) > 0
|
|
283
269
|
? Math.round(Number(config.session.max_planner_minutes) * 60 * 1000)
|
|
284
270
|
: undefined;
|
|
271
|
+
const silenceLabel = silenceTimeoutMs ? `${Math.round(silenceTimeoutMs / 1000)}s` : "disabled";
|
|
272
|
+
const runtimeLabel = plannerTimeoutMs ? `${Math.round(plannerTimeoutMs / 1000)}s` : "disabled";
|
|
285
273
|
runLog.logText(
|
|
286
|
-
`[kj_plan] started — provider=${plannerRole.provider}, max_silence=${
|
|
274
|
+
`[kj_plan] started — provider=${plannerRole.provider}, max_silence=${silenceLabel}, max_runtime=${runtimeLabel}`
|
|
287
275
|
);
|
|
288
276
|
const emitter = buildDirectEmitter(server, runLog, extra);
|
|
289
277
|
const eventBase = { sessionId: null, iteration: 0, startedAt: Date.now() };
|
|
@@ -481,223 +469,224 @@ export async function handleDiscoverDirect(a, server, extra) {
|
|
|
481
469
|
return { ok: true, ...result.result, summary: result.summary };
|
|
482
470
|
}
|
|
483
471
|
|
|
484
|
-
|
|
485
|
-
const a = asObject(args);
|
|
472
|
+
/* ── Preflight helpers ─────────────────────────────────────────────── */
|
|
486
473
|
|
|
487
|
-
|
|
488
|
-
const maxLines = a.lines || 50;
|
|
489
|
-
const projectDir = await resolveProjectDir(server);
|
|
490
|
-
return readRunLog(maxLines, projectDir);
|
|
491
|
-
}
|
|
474
|
+
const AGENT_ROLES = new Set(["coder", "reviewer", "tester", "security", "solomon"]);
|
|
492
475
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
}
|
|
476
|
+
async function buildPreflightRequiredResponse(toolName) {
|
|
477
|
+
const { config } = await loadConfig();
|
|
478
|
+
const { listAgents } = await import("../commands/agents.js");
|
|
479
|
+
const agents = listAgents(config);
|
|
480
|
+
const agentSummary = agents
|
|
481
|
+
.filter(ag => ag.provider !== "-")
|
|
482
|
+
.map(ag => {
|
|
483
|
+
const modelSuffix = ag.model === "-" ? "" : ` (${ag.model})`;
|
|
484
|
+
return ` ${ag.role}: ${ag.provider}${modelSuffix}`;
|
|
485
|
+
})
|
|
486
|
+
.join("\n");
|
|
487
|
+
return responseText({
|
|
488
|
+
ok: false,
|
|
489
|
+
preflightRequired: true,
|
|
490
|
+
message: `PREFLIGHT REQUIRED\n\nCurrent agent configuration:\n${agentSummary}\n\nAsk the human to confirm or adjust this configuration, then call kj_preflight with their response.\n\nDo NOT pass coder/reviewer parameters to ${toolName} — use kj_preflight to set them.`
|
|
491
|
+
});
|
|
492
|
+
}
|
|
496
493
|
|
|
497
|
-
|
|
498
|
-
|
|
494
|
+
function applySessionOverrides(a, roleKeys) {
|
|
495
|
+
const sessionOvr = getSessionOverrides();
|
|
496
|
+
for (const key of roleKeys) {
|
|
497
|
+
if (sessionOvr[key] !== undefined) { a[key] = sessionOvr[key]; }
|
|
499
498
|
}
|
|
499
|
+
}
|
|
500
500
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
501
|
+
function parseHumanResponseOverrides(humanResponse, overrides) {
|
|
502
|
+
for (const role of AGENT_ROLES) {
|
|
503
|
+
const patterns = [
|
|
504
|
+
new RegExp(String.raw`use\s+(\w+)\s+(?:as|for)\s+${role}`, "i"),
|
|
505
|
+
new RegExp(String.raw`${role}\s*[:=]\s*(\w+)`, "i"),
|
|
506
|
+
new RegExp(String.raw`set\s+${role}\s+(?:to|=)\s*(\w+)`, "i")
|
|
507
|
+
];
|
|
508
|
+
for (const pat of patterns) {
|
|
509
|
+
const m = pat.exec(humanResponse);
|
|
510
|
+
if (m && !overrides[role]) {
|
|
511
|
+
overrides[role] = m[1];
|
|
512
|
+
break;
|
|
506
513
|
}
|
|
507
|
-
const { setAgent } = await import("../commands/agents.js");
|
|
508
|
-
const result = await setAgent(a.role, a.provider, { global: false });
|
|
509
|
-
return { ok: true, ...result, message: `${result.role} now uses ${result.provider} (scope: ${result.scope})` };
|
|
510
514
|
}
|
|
511
|
-
const config = await buildConfig(a);
|
|
512
|
-
const { listAgents } = await import("../commands/agents.js");
|
|
513
|
-
const sessionOvr = getSessionOverrides();
|
|
514
|
-
return { ok: true, agents: listAgents(config, sessionOvr) };
|
|
515
515
|
}
|
|
516
|
+
}
|
|
516
517
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
// Apply explicit param overrides
|
|
522
|
-
for (const role of AGENT_ROLES) {
|
|
523
|
-
if (a[role]) overrides[role] = a[role];
|
|
524
|
-
}
|
|
525
|
-
if (a.enableTester !== undefined) overrides.enableTester = a.enableTester;
|
|
526
|
-
if (a.enableSecurity !== undefined) overrides.enableSecurity = a.enableSecurity;
|
|
527
|
-
|
|
528
|
-
// Parse natural-language humanResponse for agent changes
|
|
529
|
-
const resp = (a.humanResponse || "").toLowerCase();
|
|
530
|
-
if (resp !== "ok") {
|
|
531
|
-
// Match patterns like "use gemini as coder", "coder: claude", "set reviewer to codex"
|
|
532
|
-
for (const role of AGENT_ROLES) {
|
|
533
|
-
const patterns = [
|
|
534
|
-
new RegExp(`use\\s+(\\w+)\\s+(?:as|for)\\s+${role}`, "i"),
|
|
535
|
-
new RegExp(`${role}\\s*[:=]\\s*(\\w+)`, "i"),
|
|
536
|
-
new RegExp(`set\\s+${role}\\s+(?:to|=)\\s*(\\w+)`, "i")
|
|
537
|
-
];
|
|
538
|
-
for (const pat of patterns) {
|
|
539
|
-
const m = (a.humanResponse || "").match(pat);
|
|
540
|
-
if (m && !overrides[role]) {
|
|
541
|
-
overrides[role] = m[1];
|
|
542
|
-
break;
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
ackPreflight(overrides);
|
|
549
|
-
|
|
550
|
-
const config = await buildConfig(a);
|
|
551
|
-
const { listAgents } = await import("../commands/agents.js");
|
|
552
|
-
const agents = listAgents(config);
|
|
553
|
-
const lines = agents
|
|
554
|
-
.filter(ag => ag.provider !== "-")
|
|
555
|
-
.map(ag => {
|
|
556
|
-
const ovr = overrides[ag.role] ? ` -> ${overrides[ag.role]} (session override)` : "";
|
|
557
|
-
return ` ${ag.role}: ${ag.provider}${ag.model !== "-" ? ` (${ag.model})` : ""}${ovr}`;
|
|
558
|
-
});
|
|
559
|
-
const overrideLines = Object.entries(overrides)
|
|
560
|
-
.filter(([k]) => !AGENT_ROLES.includes(k))
|
|
561
|
-
.map(([k, v]) => ` ${k}: ${v}`);
|
|
562
|
-
const allLines = [...lines, ...overrideLines];
|
|
563
|
-
|
|
564
|
-
return {
|
|
565
|
-
ok: true,
|
|
566
|
-
message: `Preflight acknowledged. Agent configuration confirmed.`,
|
|
567
|
-
config: allLines.join("\n"),
|
|
568
|
-
overrides
|
|
569
|
-
};
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
if (name === "kj_config") {
|
|
573
|
-
return runKjCommand({
|
|
574
|
-
command: "config",
|
|
575
|
-
commandArgs: a.json ? ["--json"] : [],
|
|
576
|
-
options: a
|
|
577
|
-
});
|
|
518
|
+
function buildPreflightOverrides(a) {
|
|
519
|
+
const overrides = {};
|
|
520
|
+
for (const role of AGENT_ROLES) {
|
|
521
|
+
if (a[role]) overrides[role] = a[role];
|
|
578
522
|
}
|
|
523
|
+
if (a.enableTester !== undefined) overrides.enableTester = a.enableTester;
|
|
524
|
+
if (a.enableSecurity !== undefined) overrides.enableSecurity = a.enableSecurity;
|
|
579
525
|
|
|
580
|
-
|
|
581
|
-
|
|
526
|
+
const resp = (a.humanResponse || "").toLowerCase();
|
|
527
|
+
if (resp !== "ok") {
|
|
528
|
+
parseHumanResponseOverrides(a.humanResponse || "", overrides);
|
|
582
529
|
}
|
|
530
|
+
return overrides;
|
|
531
|
+
}
|
|
583
532
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
options: a
|
|
533
|
+
function formatPreflightConfig(agents, overrides) {
|
|
534
|
+
const lines = agents
|
|
535
|
+
.filter(ag => ag.provider !== "-")
|
|
536
|
+
.map(ag => {
|
|
537
|
+
const ovr = overrides[ag.role] ? ` -> ${overrides[ag.role]} (session override)` : "";
|
|
538
|
+
const modelSuffix = ag.model === "-" ? "" : ` (${ag.model})`;
|
|
539
|
+
return ` ${ag.role}: ${ag.provider}${modelSuffix}${ovr}`;
|
|
592
540
|
});
|
|
541
|
+
const overrideLines = Object.entries(overrides)
|
|
542
|
+
.filter(([k]) => !AGENT_ROLES.has(k))
|
|
543
|
+
.map(([k, v]) => ` ${k}: ${v}`);
|
|
544
|
+
return [...lines, ...overrideLines].join("\n");
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function buildReportArgs(a) {
|
|
548
|
+
const commandArgs = [];
|
|
549
|
+
if (a.list) commandArgs.push("--list");
|
|
550
|
+
if (a.sessionId) commandArgs.push("--session-id", String(a.sessionId));
|
|
551
|
+
if (a.format) commandArgs.push("--format", String(a.format));
|
|
552
|
+
if (a.trace) commandArgs.push("--trace");
|
|
553
|
+
if (a.currency) commandArgs.push("--currency", String(a.currency));
|
|
554
|
+
if (a.pgTask) commandArgs.push("--pg-task", String(a.pgTask));
|
|
555
|
+
return commandArgs;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/* ── Individual tool handlers ─────────────────────────────────────── */
|
|
559
|
+
|
|
560
|
+
async function handleStatus(a, server) {
|
|
561
|
+
const maxLines = a.lines || 50;
|
|
562
|
+
const projectDir = await resolveProjectDir(server);
|
|
563
|
+
return readRunLog(projectDir, maxLines);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
async function handleAgents(a) {
|
|
567
|
+
const action = a.action || "list";
|
|
568
|
+
if (action === "set") {
|
|
569
|
+
if (!a.role || !a.provider) {
|
|
570
|
+
return failPayload("Missing required fields: role and provider");
|
|
571
|
+
}
|
|
572
|
+
const { setAgent } = await import("../commands/agents.js");
|
|
573
|
+
const result = await setAgent(a.role, a.provider, { global: false });
|
|
574
|
+
return { ok: true, ...result, message: `${result.role} now uses ${result.provider} (scope: ${result.scope})` };
|
|
593
575
|
}
|
|
576
|
+
const config = await buildConfig(a);
|
|
577
|
+
const { listAgents } = await import("../commands/agents.js");
|
|
578
|
+
const sessionOvr = getSessionOverrides();
|
|
579
|
+
return { ok: true, agents: listAgents(config, sessionOvr) };
|
|
580
|
+
}
|
|
594
581
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
582
|
+
async function handlePreflight(a) {
|
|
583
|
+
const overrides = buildPreflightOverrides(a);
|
|
584
|
+
ackPreflight(overrides);
|
|
585
|
+
|
|
586
|
+
const config = await buildConfig(a);
|
|
587
|
+
const { listAgents } = await import("../commands/agents.js");
|
|
588
|
+
const agents = listAgents(config);
|
|
589
|
+
|
|
590
|
+
return {
|
|
591
|
+
ok: true,
|
|
592
|
+
message: `Preflight acknowledged. Agent configuration confirmed.`,
|
|
593
|
+
config: formatPreflightConfig(agents, overrides),
|
|
594
|
+
overrides
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function handleRoles(a) {
|
|
599
|
+
const action = a.action || "list";
|
|
600
|
+
const commandArgs = [action];
|
|
601
|
+
if (action === "show" && a.roleName) commandArgs.push(a.roleName);
|
|
602
|
+
return runKjCommand({ command: "roles", commandArgs, options: a });
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
async function handleResume(a, server, extra) {
|
|
606
|
+
if (!a.sessionId) {
|
|
607
|
+
return failPayload("Missing required field: sessionId");
|
|
608
608
|
}
|
|
609
|
+
return handleResumeDirect(a, server, extra);
|
|
610
|
+
}
|
|
609
611
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
612
|
+
async function handleRun(a, server, extra) {
|
|
613
|
+
if (!a.task) {
|
|
614
|
+
return failPayload("Missing required field: task");
|
|
615
|
+
}
|
|
616
|
+
if (a.taskType) {
|
|
617
|
+
const validTypes = new Set(["sw", "infra", "doc", "add-tests", "refactor"]);
|
|
618
|
+
if (!validTypes.has(a.taskType)) {
|
|
619
|
+
return failPayload(`Invalid taskType "${a.taskType}". Valid values: ${[...validTypes].join(", ")}`);
|
|
613
620
|
}
|
|
614
|
-
return handleResumeDirect(a, server, extra);
|
|
615
621
|
}
|
|
622
|
+
if (!isPreflightAcked()) {
|
|
623
|
+
return buildPreflightRequiredResponse("kj_run");
|
|
624
|
+
}
|
|
625
|
+
applySessionOverrides(a, ["coder", "reviewer", "tester", "security", "solomon", "enableTester", "enableSecurity"]);
|
|
626
|
+
return handleRunDirect(a, server, extra);
|
|
627
|
+
}
|
|
616
628
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
if (!validTypes.includes(a.taskType)) {
|
|
624
|
-
return failPayload(`Invalid taskType "${a.taskType}". Valid values: ${validTypes.join(", ")}`);
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
if (!isPreflightAcked()) {
|
|
628
|
-
const { config } = await loadConfig();
|
|
629
|
-
const { listAgents } = await import("../commands/agents.js");
|
|
630
|
-
const agents = listAgents(config);
|
|
631
|
-
const agentSummary = agents
|
|
632
|
-
.filter(ag => ag.provider !== "-")
|
|
633
|
-
.map(ag => ` ${ag.role}: ${ag.provider}${ag.model !== "-" ? ` (${ag.model})` : ""}`)
|
|
634
|
-
.join("\n");
|
|
635
|
-
return responseText({
|
|
636
|
-
ok: false,
|
|
637
|
-
preflightRequired: true,
|
|
638
|
-
message: `PREFLIGHT REQUIRED\n\nCurrent agent configuration:\n${agentSummary}\n\nAsk the human to confirm or adjust this configuration, then call kj_preflight with their response.\n\nDo NOT pass coder/reviewer parameters to kj_run — use kj_preflight to set them.`
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
|
-
// Apply session overrides, ignoring agent params from tool call
|
|
642
|
-
const sessionOvr = getSessionOverrides();
|
|
643
|
-
if (sessionOvr.coder) { a.coder = sessionOvr.coder; }
|
|
644
|
-
if (sessionOvr.reviewer) { a.reviewer = sessionOvr.reviewer; }
|
|
645
|
-
if (sessionOvr.tester) { a.tester = sessionOvr.tester; }
|
|
646
|
-
if (sessionOvr.security) { a.security = sessionOvr.security; }
|
|
647
|
-
if (sessionOvr.solomon) { a.solomon = sessionOvr.solomon; }
|
|
648
|
-
if (sessionOvr.enableTester !== undefined) { a.enableTester = sessionOvr.enableTester; }
|
|
649
|
-
if (sessionOvr.enableSecurity !== undefined) { a.enableSecurity = sessionOvr.enableSecurity; }
|
|
650
|
-
return handleRunDirect(a, server, extra);
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
if (name === "kj_code") {
|
|
654
|
-
if (!a.task) {
|
|
655
|
-
return failPayload("Missing required field: task");
|
|
656
|
-
}
|
|
657
|
-
if (!isPreflightAcked()) {
|
|
658
|
-
const { config } = await loadConfig();
|
|
659
|
-
const { listAgents } = await import("../commands/agents.js");
|
|
660
|
-
const agents = listAgents(config);
|
|
661
|
-
const agentSummary = agents
|
|
662
|
-
.filter(ag => ag.provider !== "-")
|
|
663
|
-
.map(ag => ` ${ag.role}: ${ag.provider}${ag.model !== "-" ? ` (${ag.model})` : ""}`)
|
|
664
|
-
.join("\n");
|
|
665
|
-
return responseText({
|
|
666
|
-
ok: false,
|
|
667
|
-
preflightRequired: true,
|
|
668
|
-
message: `PREFLIGHT REQUIRED\n\nCurrent agent configuration:\n${agentSummary}\n\nAsk the human to confirm or adjust this configuration, then call kj_preflight with their response.\n\nDo NOT pass coder/reviewer parameters to kj_code — use kj_preflight to set them.`
|
|
669
|
-
});
|
|
670
|
-
}
|
|
671
|
-
// Apply session overrides, ignoring agent params from tool call
|
|
672
|
-
const sessionOvr = getSessionOverrides();
|
|
673
|
-
if (sessionOvr.coder) { a.coder = sessionOvr.coder; }
|
|
674
|
-
return handleCodeDirect(a, server, extra);
|
|
629
|
+
async function handleCode(a, server, extra) {
|
|
630
|
+
if (!a.task) {
|
|
631
|
+
return failPayload("Missing required field: task");
|
|
632
|
+
}
|
|
633
|
+
if (!isPreflightAcked()) {
|
|
634
|
+
return buildPreflightRequiredResponse("kj_code");
|
|
675
635
|
}
|
|
636
|
+
applySessionOverrides(a, ["coder"]);
|
|
637
|
+
return handleCodeDirect(a, server, extra);
|
|
638
|
+
}
|
|
676
639
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
}
|
|
681
|
-
return handleReviewDirect(a, server, extra);
|
|
640
|
+
async function handleReview(a, server, extra) {
|
|
641
|
+
if (!a.task) {
|
|
642
|
+
return failPayload("Missing required field: task");
|
|
682
643
|
}
|
|
644
|
+
return handleReviewDirect(a, server, extra);
|
|
645
|
+
}
|
|
683
646
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
}
|
|
688
|
-
return handlePlanDirect(a, server, extra);
|
|
647
|
+
async function handlePlan(a, server, extra) {
|
|
648
|
+
if (!a.task) {
|
|
649
|
+
return failPayload("Missing required field: task");
|
|
689
650
|
}
|
|
651
|
+
return handlePlanDirect(a, server, extra);
|
|
652
|
+
}
|
|
690
653
|
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
}
|
|
699
|
-
return handleDiscoverDirect(a, server, extra);
|
|
654
|
+
async function handleDiscover(a, server, extra) {
|
|
655
|
+
if (!a.task) {
|
|
656
|
+
return failPayload("Missing required field: task");
|
|
657
|
+
}
|
|
658
|
+
const validModes = new Set(["gaps", "momtest", "wendel", "classify", "jtbd"]);
|
|
659
|
+
if (a.mode && !validModes.has(a.mode)) {
|
|
660
|
+
return failPayload(`Invalid mode "${a.mode}". Valid values: ${[...validModes].join(", ")}`);
|
|
700
661
|
}
|
|
662
|
+
return handleDiscoverDirect(a, server, extra);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/* ── Handler dispatch map ─────────────────────────────────────────── */
|
|
666
|
+
|
|
667
|
+
const toolHandlers = {
|
|
668
|
+
kj_status: (a, server) => handleStatus(a, server),
|
|
669
|
+
kj_init: (a) => runKjCommand({ command: "init", options: a }),
|
|
670
|
+
kj_doctor: (a) => runKjCommand({ command: "doctor", options: a }),
|
|
671
|
+
kj_agents: (a) => handleAgents(a),
|
|
672
|
+
kj_preflight: (a) => handlePreflight(a),
|
|
673
|
+
kj_config: (a) => runKjCommand({ command: "config", commandArgs: a.json ? ["--json"] : [], options: a }),
|
|
674
|
+
kj_scan: (a) => runKjCommand({ command: "scan", options: a }),
|
|
675
|
+
kj_roles: (a) => handleRoles(a),
|
|
676
|
+
kj_report: (a) => runKjCommand({ command: "report", commandArgs: buildReportArgs(a), options: a }),
|
|
677
|
+
kj_resume: (a, server, extra) => handleResume(a, server, extra),
|
|
678
|
+
kj_run: (a, server, extra) => handleRun(a, server, extra),
|
|
679
|
+
kj_code: (a, server, extra) => handleCode(a, server, extra),
|
|
680
|
+
kj_review: (a, server, extra) => handleReview(a, server, extra),
|
|
681
|
+
kj_plan: (a, server, extra) => handlePlan(a, server, extra),
|
|
682
|
+
kj_discover: (a, server, extra) => handleDiscover(a, server, extra)
|
|
683
|
+
};
|
|
701
684
|
|
|
685
|
+
export async function handleToolCall(name, args, server, extra) {
|
|
686
|
+
const a = asObject(args);
|
|
687
|
+
const handler = toolHandlers[name];
|
|
688
|
+
if (handler) {
|
|
689
|
+
return handler(a, server, extra);
|
|
690
|
+
}
|
|
702
691
|
return failPayload(`Unknown tool: ${name}`);
|
|
703
692
|
}
|
package/src/mcp/server.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import {
|
|
5
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
6
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
7
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
8
8
|
import { tools } from "./tools.js";
|
|
@@ -17,7 +17,7 @@ function readVersion() {
|
|
|
17
17
|
|
|
18
18
|
const LOADED_VERSION = readVersion();
|
|
19
19
|
|
|
20
|
-
const
|
|
20
|
+
const mcpServer = new McpServer(
|
|
21
21
|
{
|
|
22
22
|
name: "karajan-mcp",
|
|
23
23
|
version: LOADED_VERSION
|
|
@@ -30,6 +30,7 @@ const server = new Server(
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
);
|
|
33
|
+
const server = mcpServer.server;
|
|
33
34
|
|
|
34
35
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
|
|
35
36
|
|
|
@@ -54,4 +55,4 @@ setupOrphanGuard();
|
|
|
54
55
|
setupVersionWatcher({ pkgPath: PKG_PATH, currentVersion: LOADED_VERSION });
|
|
55
56
|
|
|
56
57
|
const transport = new StdioServerTransport();
|
|
57
|
-
await
|
|
58
|
+
await mcpServer.connect(transport);
|
package/src/mcp/tools.js
CHANGED
|
@@ -71,6 +71,8 @@ export const tools = [
|
|
|
71
71
|
enableSecurity: { type: "boolean" },
|
|
72
72
|
enableTriage: { type: "boolean" },
|
|
73
73
|
enableDiscover: { type: "boolean" },
|
|
74
|
+
enableArchitect: { type: "boolean" },
|
|
75
|
+
architectModel: { type: "string" },
|
|
74
76
|
enableSerena: { type: "boolean" },
|
|
75
77
|
enableBecaria: { type: "boolean", description: "Enable BecarIA Gateway (early PR + dispatch comments/reviews)" },
|
|
76
78
|
reviewerFallback: { type: "string" },
|
|
@@ -28,7 +28,6 @@ export async function runCoderWithFallback({
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
const attempts = [];
|
|
31
|
-
let allRateLimited = true;
|
|
32
31
|
|
|
33
32
|
for (const name of candidates) {
|
|
34
33
|
const agentConfig = {
|
|
@@ -72,12 +71,11 @@ export async function runCoderWithFallback({
|
|
|
72
71
|
|
|
73
72
|
// Only fallback on rate limit errors
|
|
74
73
|
if (!rateLimited) {
|
|
75
|
-
allRateLimited = false;
|
|
76
74
|
return { execResult: null, attempts, allRateLimited: false };
|
|
77
75
|
}
|
|
78
76
|
|
|
79
77
|
logger.warn(`Agent ${name} hit rate limit, trying fallback...`);
|
|
80
78
|
}
|
|
81
79
|
|
|
82
|
-
return { execResult: null, attempts, allRateLimited };
|
|
80
|
+
return { execResult: null, attempts, allRateLimited: true };
|
|
83
81
|
}
|