karajan-code 2.7.4 → 2.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.
Files changed (165) hide show
  1. package/README.md +1 -1
  2. package/package.json +24 -4
  3. package/scripts/diet-baseline.mjs +170 -0
  4. package/scripts/regen-arch-stats.sh +71 -0
  5. package/src/agents/availability.js +15 -9
  6. package/src/agents/claude-agent.js +143 -45
  7. package/src/agents/model-registry.js +1 -1
  8. package/src/audit/agent-readiness.js +240 -0
  9. package/src/bootstrap.js +26 -1
  10. package/src/brain/decision-tracker.js +3 -1
  11. package/src/brain/decisor.js +1 -1
  12. package/src/canvas/parse-md.js +238 -0
  13. package/src/canvas/schema.js +101 -0
  14. package/src/canvas/to-flat.js +85 -0
  15. package/src/canvas/validate.js +163 -0
  16. package/src/checks/binaries.js +1 -1
  17. package/src/checks/ports.js +85 -31
  18. package/src/checks/sonar.js +1 -1
  19. package/src/checks/system.js +1 -1
  20. package/src/cli/_shared.js +30 -0
  21. package/src/cli/register-meta.js +204 -0
  22. package/src/cli/register-pipeline.js +212 -0
  23. package/src/cli/register-plan.js +152 -0
  24. package/src/cli/register-roles-skills.js +38 -0
  25. package/src/cli/register-sonar.js +28 -0
  26. package/src/cli.js +83 -468
  27. package/src/commands/agents.js +18 -6
  28. package/src/commands/architect.js +29 -2
  29. package/src/commands/audit.js +27 -3
  30. package/src/commands/board.js +160 -30
  31. package/src/commands/clean.js +83 -0
  32. package/src/commands/code.js +7 -2
  33. package/src/commands/discover.js +7 -2
  34. package/src/commands/init.js +2 -2
  35. package/src/commands/plan/_shared.js +40 -0
  36. package/src/commands/plan/add-hu.js +21 -0
  37. package/src/commands/plan/delete.js +9 -0
  38. package/src/commands/plan/generate.js +284 -0
  39. package/src/commands/plan/index.js +14 -0
  40. package/src/commands/plan/list.js +21 -0
  41. package/src/commands/plan/ready.js +46 -0
  42. package/src/commands/plan/remove-hu.js +17 -0
  43. package/src/commands/plan/show.js +81 -0
  44. package/src/commands/plan/validate.js +20 -0
  45. package/src/commands/plan.js +14 -291
  46. package/src/commands/researcher.js +7 -2
  47. package/src/commands/resume.js +24 -1
  48. package/src/commands/review.js +7 -2
  49. package/src/commands/run.js +52 -1
  50. package/src/commands/status.js +1 -1
  51. package/src/commands/sync.js +75 -0
  52. package/src/commands/triage.js +7 -2
  53. package/src/commands/undo.js +1 -1
  54. package/src/config/defaults.js +213 -0
  55. package/src/config/loader.js +137 -0
  56. package/src/config/overrides.js +199 -0
  57. package/src/config/role-resolver.js +159 -0
  58. package/src/config/test-harness.js +79 -0
  59. package/src/config.js +30 -632
  60. package/src/git/automation.js +2 -2
  61. package/src/guards/output-guard.js +0 -2
  62. package/src/hu/acceptance-runner.js +52 -10
  63. package/src/hu/store.js +11 -0
  64. package/src/mcp/handlers/direct-handlers.js +2 -5
  65. package/src/mcp/handlers/management-handlers.js +48 -4
  66. package/src/mcp/handlers/run-handler.js +9 -8
  67. package/src/mcp/preflight.js +32 -13
  68. package/src/mcp/server-handlers.js +4 -3
  69. package/src/mcp/suggest-handler.js +3 -2
  70. package/src/mcp/tools.js +15 -0
  71. package/src/orchestrator/agent-fallback.js +1 -1
  72. package/src/orchestrator/ci-integration.js +10 -10
  73. package/src/orchestrator/config-init.js +25 -7
  74. package/src/orchestrator/direct-actions.js +6 -2
  75. package/src/orchestrator/drivers/error-recovery.js +287 -0
  76. package/src/orchestrator/drivers/init-context.js +222 -0
  77. package/src/orchestrator/drivers/iteration-loop.js +311 -0
  78. package/src/orchestrator/drivers/iteration-phases/coder-and-refactorer.js +48 -0
  79. package/src/orchestrator/drivers/iteration-phases/guards.js +84 -0
  80. package/src/orchestrator/drivers/iteration-phases/handle-approved.js +29 -0
  81. package/src/orchestrator/drivers/iteration-phases/quality-gates.js +76 -0
  82. package/src/orchestrator/drivers/iteration-phases/reviewer-gate.js +60 -0
  83. package/src/orchestrator/drivers/post-loop.js +370 -0
  84. package/src/orchestrator/drivers/pre-loop-phases/auto-hu-batch.js +116 -0
  85. package/src/orchestrator/drivers/pre-loop-phases/config-deprecations.js +47 -0
  86. package/src/orchestrator/drivers/pre-loop-phases/ensure-addyosmani-skills.js +84 -0
  87. package/src/orchestrator/drivers/pre-loop-phases/inject-loaded-plan.js +268 -0
  88. package/src/orchestrator/drivers/pre-loop.js +434 -0
  89. package/src/orchestrator/drivers/run-hu-batch.js +367 -0
  90. package/src/orchestrator/flow-control.js +4 -3
  91. package/src/orchestrator/flow-runner.js +121 -2077
  92. package/src/orchestrator/fs-leak-detector.js +156 -0
  93. package/src/orchestrator/hu-sub-pipeline.js +197 -9
  94. package/src/orchestrator/integrations.js +88 -0
  95. package/src/orchestrator/post-loop-stages.js +35 -8
  96. package/src/orchestrator/preflight-checks.js +20 -7
  97. package/src/orchestrator/repair/repair-detector.js +117 -0
  98. package/src/orchestrator/repair/repair-runner.js +142 -0
  99. package/src/orchestrator/reviewer-fallback.js +1 -1
  100. package/src/orchestrator/solomon-escalation.js +3 -2
  101. package/src/orchestrator/solomon-rules.js +7 -3
  102. package/src/orchestrator/stages/architect-stage.js +12 -11
  103. package/src/orchestrator/stages/coder-stage.js +56 -7
  104. package/src/orchestrator/stages/hu-reviewer-phases/detect-and-apply-splits.js +127 -0
  105. package/src/orchestrator/stages/hu-reviewer-stage.js +6 -79
  106. package/src/orchestrator/stages/planner-stage.js +1 -1
  107. package/src/orchestrator/stages/research-stage.js +1 -1
  108. package/src/orchestrator/stages/reviewer-stage.js +7 -7
  109. package/src/orchestrator/stages/sonar-stage.js +94 -10
  110. package/src/orchestrator/stages/stage-classes.js +86 -0
  111. package/src/orchestrator/stages/stage-executor.js +1 -1
  112. package/src/orchestrator/stages/triage-stage.js +1 -1
  113. package/src/orchestrator/standby.js +3 -2
  114. package/src/orchestrator/verification-gate.js +20 -12
  115. package/src/plan/adr-generator.js +170 -0
  116. package/src/plan/adr-loader.js +60 -0
  117. package/src/plan/plan-executor.js +19 -1
  118. package/src/plan/plan-hu-ops.js +170 -1
  119. package/src/plan/plan-reviewer.js +199 -0
  120. package/src/plan/plan-schema.js +69 -1
  121. package/src/plan/plan-store.js +40 -3
  122. package/src/plan/spec-detector.js +61 -0
  123. package/src/plan/tests-synthesizer.js +116 -0
  124. package/src/planning-game/client.js +3 -14
  125. package/src/planning-game/decomposition.js +1 -1
  126. package/src/planning-game/pipeline-adapter.js +36 -5
  127. package/src/prompts/coder.js +172 -1
  128. package/src/prompts/hu-reviewer.js +0 -1
  129. package/src/prompts/planner.js +81 -4
  130. package/src/review/diff-generator.js +35 -22
  131. package/src/review/snapshot-diff.js +1 -1
  132. package/src/roles/coder-role.js +25 -3
  133. package/src/roles/planner-role.js +42 -1
  134. package/src/roles/repairer-role.js +181 -0
  135. package/src/roles/tester-role.js +70 -4
  136. package/src/session/mutators.js +304 -0
  137. package/src/session/runtime-overrides.js +59 -0
  138. package/src/{session-store.js → session/store.js} +2 -2
  139. package/src/skills/semantic-detector.js +10 -5
  140. package/src/skills/skill-detector.js +1 -1
  141. package/src/sonar/cloud-scanner.js +0 -1
  142. package/src/sonar/credentials.js +14 -6
  143. package/src/sonar/discovery.js +123 -0
  144. package/src/sonar/manager.js +18 -0
  145. package/src/sync/detect-drift.js +113 -0
  146. package/src/tracker-bootstrap.js +44 -0
  147. package/src/utils/agent-detect.js +15 -11
  148. package/src/utils/board-prompt-bridge.js +109 -0
  149. package/src/utils/cli-progress.js +161 -0
  150. package/src/utils/cwd-confirm.js +39 -0
  151. package/src/utils/derive-project-name-from-cwd.js +119 -0
  152. package/src/utils/garbage-collector.js +396 -0
  153. package/src/utils/git.js +16 -4
  154. package/src/utils/locale.js +1 -1
  155. package/src/utils/paths.js +21 -1
  156. package/src/utils/process.js +1 -1
  157. package/src/utils/prompt-project-name.js +35 -0
  158. package/src/utils/run-context.js +75 -0
  159. package/src/utils/run-log.js +12 -0
  160. package/src/utils/task-file.js +1 -1
  161. package/src/utils/telemetry.js +13 -1
  162. package/src/webperf/devtools-detect.js +1 -1
  163. package/templates/coder-rules.md +13 -0
  164. package/templates/roles/coder.md +34 -0
  165. package/templates/roles/repairer.md +94 -0
package/src/cli.js CHANGED
@@ -3,30 +3,12 @@ import path from "node:path";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { Command } from "commander";
6
- import { applyRunOverrides, loadConfig, validateConfig } from "./config.js";
7
- import { createLogger } from "./utils/logger.js";
8
- import { initCommand } from "./commands/init.js";
9
- import { configCommand } from "./commands/config.js";
10
- import { codeCommand } from "./commands/code.js";
11
- import { reviewCommand } from "./commands/review.js";
12
- import { scanCommand } from "./commands/scan.js";
13
- import { doctorCommand } from "./commands/doctor.js";
14
- import { reportCommand } from "./commands/report.js";
15
- import { planCommand } from "./commands/plan.js";
16
- import { runCommandHandler } from "./commands/run.js";
17
- import { resumeCommand } from "./commands/resume.js";
18
- import { sonarCommand, sonarOpenCommand } from "./commands/sonar.js";
19
- import { rolesCommand } from "./commands/roles.js";
20
- import { agentsCommand } from "./commands/agents.js";
21
- import { discoverCommand } from "./commands/discover.js";
22
- import { triageCommand } from "./commands/triage.js";
23
- import { researcherCommand } from "./commands/researcher.js";
24
- import { architectCommand } from "./commands/architect.js";
25
- import { auditCommand } from "./commands/audit.js";
26
- import { boardCommand } from "./commands/board.js";
27
- import { undoCommand } from "./commands/undo.js";
28
- import { statusCommand } from "./commands/status.js";
29
-
6
+ import { loadConfig } from "./config.js";
7
+ import { registerPipeline } from "./cli/register-pipeline.js";
8
+ import { registerPlan } from "./cli/register-plan.js";
9
+ import { registerRolesSkills } from "./cli/register-roles-skills.js";
10
+ import { registerMeta } from "./cli/register-meta.js";
11
+ import { registerSonar } from "./cli/register-sonar.js";
30
12
  import { printUpdateNotice } from "./utils/update-check.js";
31
13
  import { printWelcomeScreen } from "./utils/welcome.js";
32
14
 
@@ -36,456 +18,89 @@ const PKG_VERSION = JSON.parse(readFileSync(PKG_PATH, "utf8")).version;
36
18
  // Non-blocking update check (runs in background, prints after command output)
37
19
  printUpdateNotice(PKG_VERSION).catch(() => {});
38
20
 
39
- async function withConfig(commandName, flags, fn) {
40
- const { config } = await loadConfig();
41
- const merged = applyRunOverrides(config, flags || {});
42
- validateConfig(merged, commandName);
43
- const logger = createLogger(merged.output.log_level);
44
-
45
- // Telemetry: anonymous cli_command event (non-blocking)
46
- import("./utils/telemetry.js")
47
- .then(({ sendTelemetryEvent }) => sendTelemetryEvent("cli_command", { command: commandName, version: PKG_VERSION }, merged))
48
- .catch(() => {});
49
-
50
- await fn({ config: merged, logger, flags });
51
- }
52
-
53
21
  const program = new Command();
54
- program.name("kj").description("Karajan Code CLI").version(PKG_VERSION);
55
-
56
- program
57
- .command("init")
58
- .description("Initialize config, review rules and SonarQube")
59
- .option("--no-interactive", "Skip wizard, use defaults (for CI/scripts)")
60
- .option("--scaffold-ci", "Scaffold Karajan CI Gateway workflow files")
61
- .action(async (flags) => {
62
- await withConfig("init", flags, async ({ config, logger }) => {
63
- await initCommand({ logger, flags });
64
- });
65
- });
66
-
67
- program
68
- .command("config")
69
- .description("Show current config")
70
- .option("--json", "Show as JSON")
71
- .option("--edit", "Open config in $EDITOR for editing")
72
- .action(async (flags) => {
73
- await configCommand(flags);
74
- });
75
-
76
- program
77
- .command("run")
78
- .description("Run coder+sonar+reviewer loop")
79
- .argument("[task]", "Task description (or use --task-file to read from a .md file)")
80
- .option("--task-file <path>", "Read the task from a file (e.g. .md). Alternative to the positional task argument.")
81
- .option("--planner <name>")
82
- .option("--coder <name>")
83
- .option("--reviewer <name>")
84
- .option("--refactorer <name>")
85
- .option("--planner-model <name>")
86
- .option("--coder-model <name>")
87
- .option("--reviewer-model <name>")
88
- .option("--refactorer-model <name>")
89
- .option("--enable-planner")
90
- .option("--enable-reviewer")
91
- .option("--enable-refactorer")
92
- .option("--enable-researcher")
93
- .option("--enable-tester")
94
- .option("--enable-security")
95
- .option("--enable-impeccable")
96
- .option("--enable-triage")
97
- .option("--enable-discover")
98
- .option("--enable-architect")
99
- .option("--plan <planId>", "Plan ID from kj plan. Loads persisted plan context and skips researcher/architect/planner.")
100
- .option("--enable-hu-reviewer")
101
- .option("--hu-file <path>", "YAML file with HU stories to certify before coding")
102
- .option("--enable-serena")
103
- .option("--mode <name>")
104
- .option("--max-iterations <n>")
105
- .option("--max-iteration-minutes <n>")
106
- .option("--max-total-minutes <n>")
107
- .option("--base-branch <name>")
108
- .option("--base-ref <ref>")
109
- .option("--coder-fallback <name>")
110
- .option("--reviewer-fallback <name>")
111
- .option("--reviewer-retries <n>")
112
- .option("--auto-commit")
113
- .option("--auto-push")
114
- .option("--auto-pr")
115
- .option("--enable-ci", "Enable Karajan CI (early PR + dispatch comments/reviews)")
116
- .option("--branch-prefix <prefix>")
117
- .option("--task-type <type>", "Explicit task type: sw, infra, doc, add-tests, refactor")
118
- .option("--methodology <name>")
119
- .option("--no-auto-rebase")
120
- .option("--no-sonar")
121
- .option("--enable-sonarcloud", "Enable SonarCloud scan (complementary to SonarQube)")
122
- .option("--no-sonarcloud")
123
- .option("--checkpoint-interval <n>", "Minutes between interactive checkpoints (default: 5)")
124
- .option("--pg-task <cardId>", "Planning Game card ID (e.g., KJC-TSK-0042)")
125
- .option("--pg-project <projectId>", "Planning Game project ID")
126
- .option("--auto-simplify", "Auto-simplify pipeline for simple tasks (disable reviewer/tester)")
127
- .option("--no-auto-simplify", "Force full pipeline regardless of triage level")
128
- .option("--smart-models", "Enable smart model selection based on triage complexity")
129
- .option("--no-smart-models", "Disable smart model selection")
130
- .option("--design", "Activate design refactoring mode (impeccable role applies design changes)")
131
- .option("--domain <text-or-path>", "Domain knowledge: inline text or path to .md file")
132
- .option("--skills-mode <mode>", "Skill detection mode: auto|regex|semantic|none (default: auto)")
133
- .option("--brain <mode>", "Brain decisor: on|off (default: on). When off, only legacy flags drive routing.")
134
- .option("--skip-role <role...>", "Force one or more roles OFF regardless of triage (e.g. --skip-role tester security)")
135
- .option("--force-role <role...>", "Force one or more roles ON regardless of triage")
136
- .option("--dry-run", "Show what would be executed without running anything")
137
- .option("--json", "Output JSON only (no styled display)")
138
- .option("-q, --quiet", "Show only stage status lines, suppress raw agent output (default)")
139
- .option("-v, --verbose", "Show full agent output (stream-json, raw lines)")
140
- .action(async (task, flags) => {
141
- await withConfig("run", flags, async ({ config, logger }) => {
142
- const { resolveTaskInput } = await import("./utils/task-file.js");
143
- const resolvedTask = await resolveTaskInput({ task, taskFile: flags.taskFile, projectDir: config.projectDir, logger });
144
- await runCommandHandler({ task: resolvedTask, config, logger, flags });
145
- });
146
- });
147
-
148
- program
149
- .command("code")
150
- .description("Run only coder")
151
- .argument("[task]", "Task description (or use --task-file)")
152
- .option("--task-file <path>", "Read the task from a file (e.g. .md)")
153
- .option("--coder <name>")
154
- .option("--coder-model <name>")
155
- .action(async (task, flags) => {
156
- await withConfig("code", flags, async ({ config, logger }) => {
157
- const { resolveTaskInput } = await import("./utils/task-file.js");
158
- const resolvedTask = await resolveTaskInput({ task, taskFile: flags.taskFile, projectDir: config.projectDir, logger });
159
- await codeCommand({ task: resolvedTask, config, logger });
160
- });
161
- });
162
-
163
- program
164
- .command("review")
165
- .description("Run only reviewer")
166
- .argument("[task]", "Task description (or use --task-file)")
167
- .option("--task-file <path>", "Read the task from a file (e.g. .md)")
168
- .option("--reviewer <name>")
169
- .option("--reviewer-model <name>")
170
- .option("--base-ref <ref>")
171
- .action(async (task, flags) => {
172
- await withConfig("review", flags, async ({ config, logger }) => {
173
- const { resolveTaskInput } = await import("./utils/task-file.js");
174
- const resolvedTask = await resolveTaskInput({ task, taskFile: flags.taskFile, projectDir: config.projectDir, logger });
175
- await reviewCommand({ task: resolvedTask, config, logger, baseRef: flags.baseRef });
176
- });
177
- });
178
-
179
22
  program
180
- .command("scan")
181
- .description("Run SonarQube scan")
182
- .action(async () => {
183
- await withConfig("scan", {}, scanCommand);
184
- });
185
-
186
- program
187
- .command("doctor")
188
- .description("Check environment requirements and auto-remediate when possible")
189
- .option("--check-only", "Detect issues without applying fixes")
190
- .option("-y, --yes", "Auto-confirm prompts for invasive remediations (CI mode)")
191
- .option("--json", "Emit machine-readable JSON instead of human output")
192
- .option("--verbose", "Include fix hints and timing for every check")
193
- .action(async (flags) => {
194
- const exitCode = await withConfig("doctor", {}, (ctx) =>
195
- doctorCommand({
196
- ...ctx,
197
- checkOnly: !!flags.checkOnly,
198
- yes: !!flags.yes,
199
- json: !!flags.json,
200
- verbose: !!flags.verbose,
201
- })
202
- );
203
- if (Number.isInteger(exitCode)) process.exit(exitCode);
204
- });
205
-
206
- program
207
- .command("status")
208
- .description("Show pipeline dashboard with HU progress")
209
- .option("-n, --lines <n>", "Max log lines to read", "50")
210
- .action(async (flags) => {
211
- await statusCommand({ lines: Number(flags.lines) });
212
- });
213
-
214
- program
215
- .command("report")
216
- .description("Show latest session report")
217
- .option("--list", "List session ids")
218
- .option("--session-id <id>", "Show report for a specific session ID")
219
- .option("--format <type>", "Output format: text|json", "text")
220
- .option("--trace", "Show chronological trace of all pipeline stages")
221
- .option("--currency <code>", "Display costs in currency: usd|eur", "usd")
222
- .option("--pg-task <cardId>", "Filter reports by Planning Game card ID")
223
- .action(async (flags) => {
224
- await reportCommand(flags);
225
- });
226
-
227
- program
228
- .command("skills [action]")
229
- .description("Manage the skill cache (actions: list | clear-cache | sync-addyosmani | list-addyosmani)")
230
- .action(async (action) => {
231
- const { skillsCommand } = await import("./commands/skills.js");
232
- const exitCode = await skillsCommand({ action: action || "list" });
233
- if (Number.isInteger(exitCode)) process.exit(exitCode);
234
- });
235
-
236
- program
237
- .command("roles [subcommand] [role]")
238
- .description("List pipeline roles or show role template instructions")
239
- .action(async (subcommand, role) => {
240
- await withConfig("roles", {}, async ({ config }) => {
241
- await rolesCommand({ config, subcommand: subcommand || "list", roleName: role });
242
- });
243
- });
244
-
245
- program
246
- .command("agents [subcommand] [role] [provider]")
247
- .description("List or change AI agent assignments per role (e.g. kj agents set coder gemini)")
248
- .option("--global", "Persist change to kj.config.yml (default for CLI)")
249
- .action(async (subcommand, role, provider, flags) => {
250
- await withConfig("agents", {}, async ({ config }) => {
251
- await agentsCommand({ config, subcommand: subcommand || "list", role, provider, global: flags.global });
252
- });
253
- });
254
-
255
- const plan = program.command("plan").description("Plan management (generate, review, approve, execute)");
256
-
257
- plan
258
- .command("generate", { isDefault: true })
259
- .description("Generate implementation plan with HUs")
260
- .argument("[task]", "Task description (or use --task-file)")
261
- .option("--task-file <path>", "Read the task from a file (e.g. .md)")
262
- .option("--planner <name>")
263
- .option("--planner-model <name>")
264
- .option("--context <text>", "Additional context for the planner")
265
- .option("--json", "Output raw JSON plan")
266
- .action(async (task, flags) => {
267
- await withConfig("plan", flags, async ({ config, logger }) => {
268
- const { resolveTaskInput } = await import("./utils/task-file.js");
269
- const resolvedTask = await resolveTaskInput({ task, taskFile: flags.taskFile, projectDir: config.projectDir, logger });
270
- const { planGenerateCommand } = await import("./commands/plan.js");
271
- await planGenerateCommand({ task: resolvedTask, config, logger, json: flags.json, context: flags.context });
272
- });
273
- });
274
-
275
- plan.command("list").description("List all plans").action(async (flags) => {
276
- await withConfig("plan", flags, async ({ config }) => {
277
- const { planListCommand } = await import("./commands/plan.js");
278
- await planListCommand({ config });
279
- });
280
- });
281
-
282
- plan.command("show").description("Show plan details + HUs").argument("<planId>").action(async (planId, flags) => {
283
- await withConfig("plan", flags, async ({ config }) => {
284
- const { planShowCommand } = await import("./commands/plan.js");
285
- await planShowCommand({ config, planId });
286
- });
287
- });
288
-
289
- plan.command("ready").description("Approve plan — certify all HUs").argument("<planId>").action(async (planId, flags) => {
290
- await withConfig("plan", flags, async ({ config }) => {
291
- const { planReadyCommand } = await import("./commands/plan.js");
292
- await planReadyCommand({ config, planId });
293
- });
294
- });
295
-
296
- plan.command("validate").description("Validate plan structure").argument("<planId>").action(async (planId, flags) => {
297
- await withConfig("plan", flags, async ({ config }) => {
298
- const { planValidateCommand } = await import("./commands/plan.js");
299
- await planValidateCommand({ config, planId });
300
- });
301
- });
302
-
303
- plan.command("delete").description("Delete a plan").argument("<planId>").action(async (planId, flags) => {
304
- await withConfig("plan", flags, async ({ config }) => {
305
- const { planDeleteCommand } = await import("./commands/plan.js");
306
- await planDeleteCommand({ config, planId });
307
- });
308
- });
309
-
310
- plan.command("add-hu").description("Add HU to plan").argument("<planId>")
311
- .option("--title <text>", "HU title")
312
- .option("--type <type>", "Task type: sw|infra|doc|add-tests|refactor|nocode", "sw")
313
- .option("--deps <ids>", "Comma-separated HU IDs this depends on")
314
- .option("--scope <text>", "Scope description")
315
- .action(async (planId, flags) => {
316
- await withConfig("plan", flags, async ({ config }) => {
317
- const { planAddHuCommand } = await import("./commands/plan.js");
318
- await planAddHuCommand({ config, planId, title: flags.title, type: flags.type, deps: flags.deps, scope: flags.scope });
319
- });
320
- });
321
-
322
- plan.command("remove-hu").description("Remove HU from plan").argument("<planId>").argument("<huId>").action(async (planId, huId, flags) => {
323
- await withConfig("plan", flags, async ({ config }) => {
324
- const { planRemoveHuCommand } = await import("./commands/plan.js");
325
- await planRemoveHuCommand({ config, planId, huId });
326
- });
327
- });
328
-
329
- program
330
- .command("discover")
331
- .description("Analyze task for gaps, ambiguities and missing info")
332
- .argument("[task]", "Task description (or use --task-file)")
333
- .option("--task-file <path>", "Read the task from a file (e.g. .md)")
334
- .option("--mode <name>", "Discovery mode: gaps|momtest|wendel|classify|jtbd", "gaps")
335
- .option("--discover <name>", "Override discover agent")
336
- .option("--discover-model <name>", "Override discover model")
337
- .option("--json", "Output raw JSON")
338
- .action(async (task, flags) => {
339
- await withConfig("discover", flags, async ({ config, logger }) => {
340
- const { resolveTaskInput } = await import("./utils/task-file.js");
341
- const resolvedTask = await resolveTaskInput({ task, taskFile: flags.taskFile, projectDir: config.projectDir, logger });
342
- await discoverCommand({ task: resolvedTask, config, logger, mode: flags.mode, json: flags.json });
343
- });
344
- });
345
-
346
- program
347
- .command("triage")
348
- .description("Classify task complexity and recommend pipeline roles")
349
- .argument("[task]", "Task description (or use --task-file)")
350
- .option("--task-file <path>", "Read the task from a file (e.g. .md)")
351
- .option("--triage <name>", "Override triage agent")
352
- .option("--triage-model <name>", "Override triage model")
353
- .option("--json", "Output raw JSON")
354
- .action(async (task, flags) => {
355
- await withConfig("triage", flags, async ({ config, logger }) => {
356
- const { resolveTaskInput } = await import("./utils/task-file.js");
357
- const resolvedTask = await resolveTaskInput({ task, taskFile: flags.taskFile, projectDir: config.projectDir, logger });
358
- await triageCommand({ task: resolvedTask, config, logger, json: flags.json });
359
- });
360
- });
361
-
362
- program
363
- .command("researcher")
364
- .description("Research codebase for a task (files, patterns, constraints)")
365
- .argument("[task]", "Task description (or use --task-file)")
366
- .option("--task-file <path>", "Read the task from a file (e.g. .md)")
367
- .option("--researcher <name>", "Override researcher agent")
368
- .option("--researcher-model <name>", "Override researcher model")
369
- .action(async (task, flags) => {
370
- await withConfig("researcher", flags, async ({ config, logger }) => {
371
- const { resolveTaskInput } = await import("./utils/task-file.js");
372
- const resolvedTask = await resolveTaskInput({ task, taskFile: flags.taskFile, projectDir: config.projectDir, logger });
373
- await researcherCommand({ task: resolvedTask, config, logger });
374
- });
375
- });
376
-
377
- program
378
- .command("architect")
379
- .description("Design solution architecture (layers, patterns, contracts)")
380
- .argument("[task]", "Task description (or use --task-file)")
381
- .option("--task-file <path>", "Read the task from a file (e.g. .md)")
382
- .option("--architect <name>", "Override architect agent")
383
- .option("--architect-model <name>", "Override architect model")
384
- .option("--context <text>", "Additional context (e.g. researcher output)")
385
- .option("--json", "Output raw JSON")
386
- .action(async (task, flags) => {
387
- await withConfig("architect", flags, async ({ config, logger }) => {
388
- const { resolveTaskInput } = await import("./utils/task-file.js");
389
- const resolvedTask = await resolveTaskInput({ task, taskFile: flags.taskFile, projectDir: config.projectDir, logger });
390
- await architectCommand({ task: resolvedTask, config, logger, context: flags.context, json: flags.json });
391
- });
392
- });
393
-
394
- program
395
- .command("audit")
396
- .description("Analyze codebase health (read-only)")
397
- .argument("[task]", "Task description. If absent, defaults to a full-codebase analysis. Use --task-file to point at a .md.")
398
- .option("--task-file <path>", "Read the task from a file (e.g. .md)")
399
- .option("--dimensions <list>", "Comma-separated: security,quality,performance,architecture,testing", "all")
400
- .option("--json", "Output raw JSON")
401
- .action(async (task, flags) => {
402
- await withConfig("audit", flags, async ({ config, logger }) => {
403
- let resolvedTask = task;
404
- if (flags.taskFile) {
405
- const { readTaskFile } = await import("./utils/task-file.js");
406
- resolvedTask = await readTaskFile(flags.taskFile, { projectDir: config.projectDir });
407
- }
408
- await auditCommand({ task: resolvedTask || "Analyze the full codebase", config, logger, dimensions: flags.dimensions, json: flags.json });
409
- });
410
- });
411
-
412
- program
413
- .command("resume")
414
- .description("Resume a paused session")
415
- .argument("<sessionId>")
416
- .option("--answer <text>", "Answer to the question that caused the pause")
417
- .option("--json", "Output JSON only")
418
- .action(async (sessionId, flags) => {
419
- await withConfig("resume", flags, async ({ config, logger }) => {
420
- await resumeCommand({ sessionId, answer: flags.answer, config, logger, flags });
421
- });
422
- });
23
+ .name("kj")
24
+ .description("Karajan Code CLI")
25
+ .version(PKG_VERSION)
26
+ // Tell Commander to surface "did you mean…?" + a help pointer on every
27
+ // CLI error (typo'd subcommand, missing required arg, etc.). Propagates
28
+ // automatically to subcommands.
29
+ .showSuggestionAfterError(true)
30
+ .showHelpAfterError("(usa 'kj --help' para ver los comandos disponibles)")
31
+ // Accept unknown options + extra positional args at the ROOT only.
32
+ // Without these two, typing `kj generate plan --task-file SPEC.md`
33
+ // errors with "unknown option --task-file" or "too many arguments"
34
+ // before the welcome action ever sees that 'generate' wasn't a valid
35
+ // subcommand. With them, both are captured and the welcome action
36
+ // gets a chance to emit a useful "no such command" message. Subcommands
37
+ // keep their own strict checking because both flags are per-command,
38
+ // not inherited.
39
+ .allowUnknownOption(true)
40
+ .allowExcessArguments(true);
41
+
42
+ registerPipeline(program, { pkgVersion: PKG_VERSION });
43
+ registerPlan(program, { pkgVersion: PKG_VERSION });
44
+ registerRolesSkills(program, { pkgVersion: PKG_VERSION });
45
+ registerMeta(program, { pkgVersion: PKG_VERSION });
46
+ registerSonar(program);
47
+
48
+ /**
49
+ * Find subcommands whose name is "close enough" to a typo. Used by the
50
+ * fallback action below so `kj generate plan` suggests `kj plan`. Cheap
51
+ * heuristic — same first letter OR substring match OR Levenshtein ≤ 2 —
52
+ * which is all we need for the small set of commands kj exposes.
53
+ */
54
+ function suggestSimilarCommands(bad, known) {
55
+ const lower = bad.toLowerCase();
56
+ const out = new Set();
57
+ for (const name of known) {
58
+ const n = name.toLowerCase();
59
+ if (n.includes(lower) || lower.includes(n)) out.add(name);
60
+ else if (n[0] === lower[0] && Math.abs(n.length - lower.length) <= 2) out.add(name);
61
+ else if (levenshtein(n, lower) <= 2) out.add(name);
62
+ }
63
+ return [...out].slice(0, 3);
64
+ }
423
65
 
424
- program
425
- .command("update")
426
- .description("Update karajan-code to the latest version from npm")
427
- .action(async () => {
428
- const { execaCommand } = await import("execa");
429
- console.log(`Current version: ${PKG_VERSION}`);
430
- console.log("Checking for updates...");
431
- try {
432
- const { stdout } = await execaCommand("npm view karajan-code version");
433
- const latest = stdout.trim();
434
- if (latest === PKG_VERSION) {
435
- console.log(`Already on the latest version (${PKG_VERSION}).`);
436
- return;
437
- }
438
- console.log(`Updating ${PKG_VERSION} → ${latest}...`);
439
- await execaCommand("npm install -g karajan-code@latest", { stdio: "inherit" });
440
- console.log(`Updated to ${latest}. Restart Claude to pick up the new MCP server.`);
441
- } catch (err) {
442
- console.error(`Update failed: ${err.message}`);
443
- process.exit(1);
66
+ function levenshtein(a, b) {
67
+ if (a === b) return 0;
68
+ if (!a.length) return b.length;
69
+ if (!b.length) return a.length;
70
+ const m = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
71
+ for (let i = 0; i <= a.length; i += 1) m[i][0] = i;
72
+ for (let j = 0; j <= b.length; j += 1) m[0][j] = j;
73
+ for (let i = 1; i <= a.length; i += 1) {
74
+ for (let j = 1; j <= b.length; j += 1) {
75
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
76
+ m[i][j] = Math.min(m[i - 1][j] + 1, m[i][j - 1] + 1, m[i - 1][j - 1] + cost);
444
77
  }
445
- });
446
-
447
- program
448
- .command("board [action]")
449
- .description("Manage HU Board (start|stop|status|open)")
450
- .option("--port <number>", "Port (default: 4000)", "4000")
451
- .action(async (action = "start", opts) => {
452
- await withConfig("board", opts, async ({ config, logger }) => {
453
- const port = Number(opts.port) || config.hu_board?.port || 4000;
454
- await boardCommand({ action, port, logger });
455
- });
456
- });
457
-
458
- program
459
- .command("undo")
460
- .description("Revert last pipeline run")
461
- .option("--hard", "Discard all changes (default: soft reset, keeps changes staged)")
462
- .action(async (flags) => {
463
- await withConfig("undo", flags, async ({ logger }) => {
464
- const result = await undoCommand({ hard: !!flags.hard, logger });
465
- if (!result.ok) process.exit(1);
466
- });
467
- });
78
+ }
79
+ return m[a.length][b.length];
80
+ }
468
81
 
469
- const sonar = program.command("sonar").description("Manage SonarQube container");
470
- sonar.command("status").action(async () => sonarCommand({ action: "status" }));
471
- sonar.command("start").action(async () => sonarCommand({ action: "start" }));
472
- sonar.command("stop").action(async () => sonarCommand({ action: "stop" }));
473
- sonar.command("logs").action(async () => sonarCommand({ action: "logs" }));
474
- sonar
475
- .command("open")
476
- .description("Open SonarQube dashboard in the browser")
477
- .action(async () => {
478
- const { config } = await loadConfig();
479
- const result = await sonarOpenCommand({ config });
480
- if (!result.ok) {
481
- console.error(result.error);
482
- process.exit(1);
82
+ // Default action — fires when no subcommand matched. Two cases:
83
+ // 1. No args at all → friendly welcome screen (preserved behaviour).
84
+ // 2. Args were given → user typed an unknown command. Print a clear
85
+ // error pointing at WHICH word was wrong, suggest a close match,
86
+ // list every valid command, and exit non-zero. Commander's own
87
+ // "unknown option" error never fires for these because we set
88
+ // allowUnknownOption(true) on the root above.
89
+ program.action(async (_opts, command) => {
90
+ if (command.args.length > 0) {
91
+ const bad = command.args[0];
92
+ const known = program.commands.map((c) => c.name()).sort();
93
+ process.stderr.write(`error: '${bad}' no es un comando válido de kj\n`);
94
+ const suggestions = suggestSimilarCommands(bad, known);
95
+ if (suggestions.length > 0) {
96
+ const list = suggestions.map((s) => `'${s}'`).join(suggestions.length === 2 ? " o " : ", ");
97
+ process.stderr.write(`\n¿Quisiste decir ${list}?\n`);
483
98
  }
484
- console.log(`Opened ${result.url}`);
485
- });
486
-
487
- // Show welcome screen when invoked with no subcommand
488
- program.action(async () => {
99
+ process.stderr.write("\nComandos disponibles:\n");
100
+ for (const c of known) process.stderr.write(` ${c}\n`);
101
+ process.stderr.write("\nUsa 'kj --help' para más información, o 'kj <comando> --help' para detalles de un subcomando.\n");
102
+ process.exit(1);
103
+ }
489
104
  const { config } = await loadConfig().catch(() => ({ config: null }));
490
105
  printWelcomeScreen({ version: PKG_VERSION, config });
491
106
  });
@@ -46,13 +46,21 @@ export async function setAgent(role, provider, { global: isGlobal = false } = {}
46
46
  return { role, provider, scope: "global", configPath };
47
47
  }
48
48
 
49
- // Session scope — try MCP session override first
49
+ // Session scope — write to the process-lifetime runtime-overrides store.
50
+ // Post-v2.7.5 this store lives at the session layer
51
+ // (src/session/runtime-overrides.js) instead of under src/mcp/, so the
52
+ // CLI no longer reaches into the MCP layer. The store is in-memory for
53
+ // the current process: MCP server long-lived invocations see the
54
+ // override across tool calls, CLI short-lived invocations naturally
55
+ // lose it at exit (that's fine — CLI users who want persistence pass
56
+ // --global or let it fall through to the project config below).
57
+ const { setRuntimeOverride } = await import("../session/runtime-overrides.js");
58
+ setRuntimeOverride(role, provider);
59
+
60
+ // Also mirror to the project config file so future CLI invocations pick
61
+ // it up. (The in-memory store alone is useless for CLI because the
62
+ // process exits immediately after.)
50
63
  try {
51
- const { setSessionOverride } = await import("../mcp/preflight.js");
52
- setSessionOverride(role, provider);
53
- return { role, provider, scope: "session" };
54
- } catch { /* preflight module not available in CLI mode */
55
- // preflight module not available (CLI mode) — write to project config
56
64
  const projectConfigPath = getProjectConfigPath();
57
65
  const projectConfig = (await loadProjectConfig()) || {};
58
66
  projectConfig.roles = projectConfig.roles || {};
@@ -60,6 +68,10 @@ export async function setAgent(role, provider, { global: isGlobal = false } = {}
60
68
  projectConfig.roles[role].provider = provider;
61
69
  await writeConfig(projectConfigPath, projectConfig);
62
70
  return { role, provider, scope: "project", configPath: projectConfigPath };
71
+ } catch {
72
+ // Project config not writable — still counts as a session-scope set
73
+ // because the in-memory store got updated above.
74
+ return { role, provider, scope: "session" };
63
75
  }
64
76
  }
65
77