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.
Files changed (72) hide show
  1. package/package.json +1 -1
  2. package/src/activity-log.js +13 -13
  3. package/src/agents/availability.js +2 -3
  4. package/src/agents/claude-agent.js +42 -21
  5. package/src/agents/model-registry.js +1 -1
  6. package/src/becaria/dispatch.js +1 -1
  7. package/src/becaria/repo.js +3 -3
  8. package/src/cli.js +5 -2
  9. package/src/commands/doctor.js +154 -108
  10. package/src/commands/init.js +101 -90
  11. package/src/commands/plan.js +1 -1
  12. package/src/commands/report.js +77 -71
  13. package/src/commands/roles.js +0 -1
  14. package/src/commands/run.js +2 -3
  15. package/src/config.js +174 -93
  16. package/src/git/automation.js +3 -4
  17. package/src/guards/intent-guard.js +123 -0
  18. package/src/guards/output-guard.js +158 -0
  19. package/src/guards/perf-guard.js +126 -0
  20. package/src/guards/policy-resolver.js +3 -3
  21. package/src/mcp/orphan-guard.js +1 -2
  22. package/src/mcp/progress.js +4 -3
  23. package/src/mcp/run-kj.js +1 -0
  24. package/src/mcp/server-handlers.js +242 -253
  25. package/src/mcp/server.js +4 -3
  26. package/src/mcp/tools.js +2 -0
  27. package/src/orchestrator/agent-fallback.js +1 -3
  28. package/src/orchestrator/iteration-stages.js +206 -170
  29. package/src/orchestrator/pre-loop-stages.js +200 -34
  30. package/src/orchestrator/solomon-rules.js +2 -2
  31. package/src/orchestrator.js +902 -746
  32. package/src/planning-game/adapter.js +23 -20
  33. package/src/planning-game/architect-adrs.js +45 -0
  34. package/src/planning-game/client.js +15 -1
  35. package/src/planning-game/decomposition.js +7 -5
  36. package/src/prompts/architect.js +88 -0
  37. package/src/prompts/discover.js +54 -53
  38. package/src/prompts/planner.js +53 -33
  39. package/src/prompts/triage.js +8 -16
  40. package/src/review/parser.js +18 -19
  41. package/src/review/profiles.js +2 -2
  42. package/src/review/schema.js +3 -3
  43. package/src/review/scope-filter.js +3 -4
  44. package/src/roles/architect-role.js +122 -0
  45. package/src/roles/commiter-role.js +2 -2
  46. package/src/roles/discover-role.js +59 -67
  47. package/src/roles/index.js +1 -0
  48. package/src/roles/planner-role.js +54 -38
  49. package/src/roles/refactorer-role.js +8 -7
  50. package/src/roles/researcher-role.js +6 -7
  51. package/src/roles/reviewer-role.js +4 -5
  52. package/src/roles/security-role.js +3 -4
  53. package/src/roles/solomon-role.js +6 -18
  54. package/src/roles/sonar-role.js +5 -1
  55. package/src/roles/tester-role.js +8 -5
  56. package/src/roles/triage-role.js +2 -2
  57. package/src/session-cleanup.js +29 -24
  58. package/src/session-store.js +1 -1
  59. package/src/sonar/api.js +1 -1
  60. package/src/sonar/manager.js +1 -1
  61. package/src/sonar/project-key.js +5 -5
  62. package/src/sonar/scanner.js +34 -65
  63. package/src/utils/display.js +312 -272
  64. package/src/utils/git.js +3 -3
  65. package/src/utils/logger.js +6 -1
  66. package/src/utils/model-selector.js +5 -5
  67. package/src/utils/process.js +80 -102
  68. package/src/utils/rate-limit-detector.js +13 -13
  69. package/src/utils/run-log.js +55 -52
  70. package/templates/kj.config.yml +33 -0
  71. package/templates/roles/architect.md +62 -0
  72. 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
- if (
68
- lower.includes("without output")
69
- || lower.includes("silent for")
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
- const level = event.type === "agent:stall" ? "warning"
258
- : event.type === "agent:heartbeat" ? "info"
259
- : "debug";
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=${silenceTimeoutMs ? `${Math.round(silenceTimeoutMs / 1000)}s` : "disabled"}, max_runtime=${plannerTimeoutMs ? `${Math.round(plannerTimeoutMs / 1000)}s` : "disabled"}`
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
- export async function handleToolCall(name, args, server, extra) {
485
- const a = asObject(args);
472
+ /* ── Preflight helpers ─────────────────────────────────────────────── */
486
473
 
487
- if (name === "kj_status") {
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
- if (name === "kj_init") {
494
- return runKjCommand({ command: "init", options: a });
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
- if (name === "kj_doctor") {
498
- return runKjCommand({ command: "doctor", options: a });
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
- if (name === "kj_agents") {
502
- const action = a.action || "list";
503
- if (action === "set") {
504
- if (!a.role || !a.provider) {
505
- return failPayload("Missing required fields: role and provider");
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
- if (name === "kj_preflight") {
518
- const overrides = {};
519
- const AGENT_ROLES = ["coder", "reviewer", "tester", "security", "solomon"];
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
- if (name === "kj_scan") {
581
- return runKjCommand({ command: "scan", options: a });
526
+ const resp = (a.humanResponse || "").toLowerCase();
527
+ if (resp !== "ok") {
528
+ parseHumanResponseOverrides(a.humanResponse || "", overrides);
582
529
  }
530
+ return overrides;
531
+ }
583
532
 
584
- if (name === "kj_roles") {
585
- const action = a.action || "list";
586
- const commandArgs = [action];
587
- if (action === "show" && a.roleName) commandArgs.push(a.roleName);
588
- return runKjCommand({
589
- command: "roles",
590
- commandArgs,
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
- if (name === "kj_report") {
596
- const commandArgs = [];
597
- if (a.list) commandArgs.push("--list");
598
- if (a.sessionId) commandArgs.push("--session-id", String(a.sessionId));
599
- if (a.format) commandArgs.push("--format", String(a.format));
600
- if (a.trace) commandArgs.push("--trace");
601
- if (a.currency) commandArgs.push("--currency", String(a.currency));
602
- if (a.pgTask) commandArgs.push("--pg-task", String(a.pgTask));
603
- return runKjCommand({
604
- command: "report",
605
- commandArgs,
606
- options: a
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
- if (name === "kj_resume") {
611
- if (!a.sessionId) {
612
- return failPayload("Missing required field: sessionId");
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
- if (name === "kj_run") {
618
- if (!a.task) {
619
- return failPayload("Missing required field: task");
620
- }
621
- if (a.taskType) {
622
- const validTypes = ["sw", "infra", "doc", "add-tests", "refactor"];
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
- if (name === "kj_review") {
678
- if (!a.task) {
679
- return failPayload("Missing required field: task");
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
- if (name === "kj_plan") {
685
- if (!a.task) {
686
- return failPayload("Missing required field: task");
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
- if (name === "kj_discover") {
692
- if (!a.task) {
693
- return failPayload("Missing required field: task");
694
- }
695
- const validModes = ["gaps", "momtest", "wendel", "classify", "jtbd"];
696
- if (a.mode && !validModes.includes(a.mode)) {
697
- return failPayload(`Invalid mode "${a.mode}". Valid values: ${validModes.join(", ")}`);
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 { Server } from "@modelcontextprotocol/sdk/server/index.js";
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 server = new Server(
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 server.connect(transport);
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
  }