karajan-code 2.7.3 → 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 (167) hide show
  1. package/README.md +4 -4
  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 +97 -27
  18. package/src/checks/runner.js +26 -10
  19. package/src/checks/sonar.js +1 -1
  20. package/src/checks/system.js +1 -1
  21. package/src/checks/tokens.js +77 -24
  22. package/src/cli/_shared.js +30 -0
  23. package/src/cli/register-meta.js +204 -0
  24. package/src/cli/register-pipeline.js +212 -0
  25. package/src/cli/register-plan.js +152 -0
  26. package/src/cli/register-roles-skills.js +38 -0
  27. package/src/cli/register-sonar.js +28 -0
  28. package/src/cli.js +83 -468
  29. package/src/commands/agents.js +18 -6
  30. package/src/commands/architect.js +29 -2
  31. package/src/commands/audit.js +27 -3
  32. package/src/commands/board.js +160 -30
  33. package/src/commands/clean.js +83 -0
  34. package/src/commands/code.js +7 -2
  35. package/src/commands/discover.js +7 -2
  36. package/src/commands/init.js +2 -2
  37. package/src/commands/plan/_shared.js +40 -0
  38. package/src/commands/plan/add-hu.js +21 -0
  39. package/src/commands/plan/delete.js +9 -0
  40. package/src/commands/plan/generate.js +284 -0
  41. package/src/commands/plan/index.js +14 -0
  42. package/src/commands/plan/list.js +21 -0
  43. package/src/commands/plan/ready.js +46 -0
  44. package/src/commands/plan/remove-hu.js +17 -0
  45. package/src/commands/plan/show.js +81 -0
  46. package/src/commands/plan/validate.js +20 -0
  47. package/src/commands/plan.js +14 -291
  48. package/src/commands/researcher.js +7 -2
  49. package/src/commands/resume.js +24 -1
  50. package/src/commands/review.js +7 -2
  51. package/src/commands/run.js +52 -1
  52. package/src/commands/status.js +1 -1
  53. package/src/commands/sync.js +75 -0
  54. package/src/commands/triage.js +7 -2
  55. package/src/commands/undo.js +1 -1
  56. package/src/config/defaults.js +213 -0
  57. package/src/config/loader.js +137 -0
  58. package/src/config/overrides.js +199 -0
  59. package/src/config/role-resolver.js +159 -0
  60. package/src/config/test-harness.js +79 -0
  61. package/src/config.js +30 -609
  62. package/src/git/automation.js +2 -2
  63. package/src/guards/output-guard.js +0 -2
  64. package/src/hu/acceptance-runner.js +52 -10
  65. package/src/hu/store.js +11 -0
  66. package/src/mcp/handlers/direct-handlers.js +2 -5
  67. package/src/mcp/handlers/management-handlers.js +48 -4
  68. package/src/mcp/handlers/run-handler.js +9 -8
  69. package/src/mcp/preflight.js +32 -13
  70. package/src/mcp/server-handlers.js +4 -3
  71. package/src/mcp/suggest-handler.js +3 -2
  72. package/src/mcp/tools.js +15 -0
  73. package/src/orchestrator/agent-fallback.js +1 -1
  74. package/src/orchestrator/ci-integration.js +10 -10
  75. package/src/orchestrator/config-init.js +25 -7
  76. package/src/orchestrator/direct-actions.js +6 -2
  77. package/src/orchestrator/drivers/error-recovery.js +287 -0
  78. package/src/orchestrator/drivers/init-context.js +222 -0
  79. package/src/orchestrator/drivers/iteration-loop.js +311 -0
  80. package/src/orchestrator/drivers/iteration-phases/coder-and-refactorer.js +48 -0
  81. package/src/orchestrator/drivers/iteration-phases/guards.js +84 -0
  82. package/src/orchestrator/drivers/iteration-phases/handle-approved.js +29 -0
  83. package/src/orchestrator/drivers/iteration-phases/quality-gates.js +76 -0
  84. package/src/orchestrator/drivers/iteration-phases/reviewer-gate.js +60 -0
  85. package/src/orchestrator/drivers/post-loop.js +370 -0
  86. package/src/orchestrator/drivers/pre-loop-phases/auto-hu-batch.js +116 -0
  87. package/src/orchestrator/drivers/pre-loop-phases/config-deprecations.js +47 -0
  88. package/src/orchestrator/drivers/pre-loop-phases/ensure-addyosmani-skills.js +84 -0
  89. package/src/orchestrator/drivers/pre-loop-phases/inject-loaded-plan.js +268 -0
  90. package/src/orchestrator/drivers/pre-loop.js +434 -0
  91. package/src/orchestrator/drivers/run-hu-batch.js +367 -0
  92. package/src/orchestrator/flow-control.js +4 -3
  93. package/src/orchestrator/flow-runner.js +121 -2019
  94. package/src/orchestrator/fs-leak-detector.js +156 -0
  95. package/src/orchestrator/hu-sub-pipeline.js +197 -9
  96. package/src/orchestrator/integrations.js +88 -0
  97. package/src/orchestrator/post-loop-stages.js +35 -8
  98. package/src/orchestrator/preflight-checks.js +30 -3
  99. package/src/orchestrator/repair/repair-detector.js +117 -0
  100. package/src/orchestrator/repair/repair-runner.js +142 -0
  101. package/src/orchestrator/reviewer-fallback.js +1 -1
  102. package/src/orchestrator/solomon-escalation.js +3 -2
  103. package/src/orchestrator/solomon-rules.js +7 -3
  104. package/src/orchestrator/stages/architect-stage.js +12 -11
  105. package/src/orchestrator/stages/coder-stage.js +56 -7
  106. package/src/orchestrator/stages/hu-reviewer-phases/detect-and-apply-splits.js +127 -0
  107. package/src/orchestrator/stages/hu-reviewer-stage.js +6 -79
  108. package/src/orchestrator/stages/planner-stage.js +1 -1
  109. package/src/orchestrator/stages/research-stage.js +1 -1
  110. package/src/orchestrator/stages/reviewer-stage.js +7 -7
  111. package/src/orchestrator/stages/sonar-stage.js +94 -10
  112. package/src/orchestrator/stages/stage-classes.js +86 -0
  113. package/src/orchestrator/stages/stage-executor.js +1 -1
  114. package/src/orchestrator/stages/triage-stage.js +1 -1
  115. package/src/orchestrator/standby.js +3 -2
  116. package/src/orchestrator/verification-gate.js +20 -12
  117. package/src/plan/adr-generator.js +170 -0
  118. package/src/plan/adr-loader.js +60 -0
  119. package/src/plan/plan-executor.js +19 -1
  120. package/src/plan/plan-hu-ops.js +170 -1
  121. package/src/plan/plan-reviewer.js +199 -0
  122. package/src/plan/plan-schema.js +69 -1
  123. package/src/plan/plan-store.js +40 -3
  124. package/src/plan/spec-detector.js +61 -0
  125. package/src/plan/tests-synthesizer.js +116 -0
  126. package/src/planning-game/client.js +3 -14
  127. package/src/planning-game/decomposition.js +1 -1
  128. package/src/planning-game/pipeline-adapter.js +36 -5
  129. package/src/prompts/coder.js +172 -1
  130. package/src/prompts/hu-reviewer.js +0 -1
  131. package/src/prompts/planner.js +81 -4
  132. package/src/review/diff-generator.js +35 -22
  133. package/src/review/snapshot-diff.js +1 -1
  134. package/src/roles/coder-role.js +25 -3
  135. package/src/roles/planner-role.js +42 -1
  136. package/src/roles/repairer-role.js +181 -0
  137. package/src/roles/tester-role.js +70 -4
  138. package/src/session/mutators.js +304 -0
  139. package/src/session/runtime-overrides.js +59 -0
  140. package/src/{session-store.js → session/store.js} +8 -2
  141. package/src/skills/semantic-detector.js +10 -5
  142. package/src/skills/skill-detector.js +1 -1
  143. package/src/sonar/cloud-scanner.js +0 -1
  144. package/src/sonar/credentials.js +14 -6
  145. package/src/sonar/discovery.js +123 -0
  146. package/src/sonar/manager.js +18 -0
  147. package/src/sync/detect-drift.js +113 -0
  148. package/src/tracker-bootstrap.js +44 -0
  149. package/src/utils/agent-detect.js +15 -11
  150. package/src/utils/board-prompt-bridge.js +109 -0
  151. package/src/utils/cli-progress.js +161 -0
  152. package/src/utils/cwd-confirm.js +39 -0
  153. package/src/utils/derive-project-name-from-cwd.js +119 -0
  154. package/src/utils/garbage-collector.js +396 -0
  155. package/src/utils/git.js +16 -4
  156. package/src/utils/locale.js +1 -1
  157. package/src/utils/paths.js +21 -1
  158. package/src/utils/process.js +1 -1
  159. package/src/utils/prompt-project-name.js +35 -0
  160. package/src/utils/run-context.js +75 -0
  161. package/src/utils/run-log.js +12 -0
  162. package/src/utils/task-file.js +1 -1
  163. package/src/utils/telemetry.js +13 -1
  164. package/src/webperf/devtools-detect.js +1 -1
  165. package/templates/coder-rules.md +13 -0
  166. package/templates/roles/coder.md +34 -0
  167. package/templates/roles/repairer.md +94 -0
@@ -0,0 +1,204 @@
1
+ import { discoverCommand } from "../commands/discover.js";
2
+ import { triageCommand } from "../commands/triage.js";
3
+ import { researcherCommand } from "../commands/researcher.js";
4
+ import { architectCommand } from "../commands/architect.js";
5
+ import { auditCommand } from "../commands/audit.js";
6
+ import { resumeCommand } from "../commands/resume.js";
7
+ import { boardCommand } from "../commands/board.js";
8
+ import { undoCommand } from "../commands/undo.js";
9
+ import { syncCommand } from "../commands/sync.js";
10
+ import { cleanCommand } from "../commands/clean.js";
11
+ import { withConfig } from "./_shared.js";
12
+
13
+ /**
14
+ * Register the "meta" / single-role / housekeeping commands: pre-pipeline
15
+ * analysis (discover, triage, researcher, architect, audit), session
16
+ * management (resume, board, undo) and maintenance (update, sync, clean).
17
+ *
18
+ * Note: `triage` lives here (not in pipeline) because it's a single-role
19
+ * analysis command in the same family as discover / researcher /
20
+ * architect / audit, none of which run the full agent loop.
21
+ */
22
+ export function registerMeta(program, { pkgVersion }) {
23
+ program
24
+ .command("discover")
25
+ .description("Analyze task for gaps, ambiguities and missing info")
26
+ .argument("[task]", "Task description (or use --task-file)")
27
+ .option("--task-file <path>", "Read the task from a file (e.g. .md)")
28
+ .option("--mode <name>", "Discovery mode: gaps|momtest|wendel|classify|jtbd", "gaps")
29
+ .option("--discover <name>", "Override discover agent")
30
+ .option("--discover-model <name>", "Override discover model")
31
+ .option("--json", "Output raw JSON")
32
+ .action(async (task, flags) => {
33
+ await withConfig(pkgVersion, "discover", flags, async ({ config, logger }) => {
34
+ const { resolveTaskInput } = await import("../utils/task-file.js");
35
+ const resolvedTask = await resolveTaskInput({ task, taskFile: flags.taskFile, projectDir: config.projectDir, logger });
36
+ await discoverCommand({ task: resolvedTask, config, logger, mode: flags.mode, json: flags.json });
37
+ });
38
+ });
39
+
40
+ program
41
+ .command("triage")
42
+ .description("Classify task complexity and recommend pipeline roles")
43
+ .argument("[task]", "Task description (or use --task-file)")
44
+ .option("--task-file <path>", "Read the task from a file (e.g. .md)")
45
+ .option("--triage <name>", "Override triage agent")
46
+ .option("--triage-model <name>", "Override triage model")
47
+ .option("--json", "Output raw JSON")
48
+ .action(async (task, flags) => {
49
+ await withConfig(pkgVersion, "triage", flags, async ({ config, logger }) => {
50
+ const { resolveTaskInput } = await import("../utils/task-file.js");
51
+ const resolvedTask = await resolveTaskInput({ task, taskFile: flags.taskFile, projectDir: config.projectDir, logger });
52
+ await triageCommand({ task: resolvedTask, config, logger, json: flags.json });
53
+ });
54
+ });
55
+
56
+ program
57
+ .command("researcher")
58
+ .description("Research codebase for a task (files, patterns, constraints)")
59
+ .argument("[task]", "Task description (or use --task-file)")
60
+ .option("--task-file <path>", "Read the task from a file (e.g. .md)")
61
+ .option("--researcher <name>", "Override researcher agent")
62
+ .option("--researcher-model <name>", "Override researcher model")
63
+ .action(async (task, flags) => {
64
+ await withConfig(pkgVersion, "researcher", flags, async ({ config, logger }) => {
65
+ const { resolveTaskInput } = await import("../utils/task-file.js");
66
+ const resolvedTask = await resolveTaskInput({ task, taskFile: flags.taskFile, projectDir: config.projectDir, logger });
67
+ await researcherCommand({ task: resolvedTask, config, logger });
68
+ });
69
+ });
70
+
71
+ program
72
+ .command("architect")
73
+ .description("Design solution architecture (layers, patterns, contracts)")
74
+ .argument("[task]", "Task description (or use --task-file)")
75
+ .option("--task-file <path>", "Read the task from a file (e.g. .md)")
76
+ .option("--architect <name>", "Override architect agent")
77
+ .option("--architect-model <name>", "Override architect model")
78
+ .option("--context <text>", "Additional context (e.g. researcher output)")
79
+ .option("--json", "Output raw JSON")
80
+ .action(async (task, flags) => {
81
+ await withConfig(pkgVersion, "architect", flags, async ({ config, logger }) => {
82
+ const { resolveTaskInput } = await import("../utils/task-file.js");
83
+ const resolvedTask = await resolveTaskInput({ task, taskFile: flags.taskFile, projectDir: config.projectDir, logger });
84
+ await architectCommand({ task: resolvedTask, config, logger, context: flags.context, json: flags.json });
85
+ });
86
+ });
87
+
88
+ program
89
+ .command("audit")
90
+ .description("Analyze codebase health (read-only)")
91
+ .argument("[task]", "Task description. If absent, defaults to a full-codebase analysis. Use --task-file to point at a .md.")
92
+ .option("--task-file <path>", "Read the task from a file (e.g. .md)")
93
+ .option("--dimensions <list>", "Comma-separated: security,quality,performance,architecture,testing", "all")
94
+ .option("--json", "Output raw JSON")
95
+ .option("--agent-readiness", "Score the repo for AI-agent readability (llms.txt, SKILL.md coverage, page token budgets, robots allowlist, heading hierarchy). LLM-free; uses [path] or cwd as the audit target. See issue #542.")
96
+ .option("--path <dir>", "Path to audit (used with --agent-readiness; defaults to cwd)")
97
+ .action(async (task, flags) => {
98
+ await withConfig(pkgVersion, "audit", flags, async ({ config, logger }) => {
99
+ let resolvedTask = task;
100
+ if (flags.taskFile) {
101
+ const { readTaskFile } = await import("../utils/task-file.js");
102
+ resolvedTask = await readTaskFile(flags.taskFile, { projectDir: config.projectDir });
103
+ }
104
+ await auditCommand({
105
+ task: resolvedTask || "Analyze the full codebase",
106
+ config, logger,
107
+ dimensions: flags.dimensions, json: flags.json,
108
+ agentReadiness: Boolean(flags.agentReadiness),
109
+ path: flags.path,
110
+ });
111
+ });
112
+ });
113
+
114
+ program
115
+ .command("resume")
116
+ .description("Resume a paused session")
117
+ .argument("<sessionId>")
118
+ .option("--answer <text>", "Answer to the question that caused the pause")
119
+ .option("--json", "Output JSON only")
120
+ .action(async (sessionId, flags) => {
121
+ await withConfig(pkgVersion, "resume", flags, async ({ config, logger }) => {
122
+ await resumeCommand({ sessionId, answer: flags.answer, config, logger, flags });
123
+ });
124
+ });
125
+
126
+ program
127
+ .command("update")
128
+ .description("Update karajan-code to the latest version from npm")
129
+ .action(async () => {
130
+ // Audit follow-up: was execaCommand (shell parsing). Inputs are
131
+ // constants today, but the project standard since #555 is execa
132
+ // with arg arrays (no shell). Migrated for consistency.
133
+ const { execa } = await import("execa");
134
+ console.log(`Current version: ${pkgVersion}`);
135
+ console.log("Checking for updates...");
136
+ try {
137
+ const { stdout } = await execa("npm", ["view", "karajan-code", "version"]);
138
+ const latest = stdout.trim();
139
+ if (latest === pkgVersion) {
140
+ console.log(`Already on the latest version (${pkgVersion}).`);
141
+ return;
142
+ }
143
+ console.log(`Updating ${pkgVersion} → ${latest}...`);
144
+ await execa("npm", ["install", "-g", "karajan-code@latest"], { stdio: "inherit" });
145
+ console.log(`Updated to ${latest}. Restart Claude to pick up the new MCP server.`);
146
+ } catch (err) {
147
+ console.error(`Update failed: ${err.message}`);
148
+ process.exit(1);
149
+ }
150
+ });
151
+
152
+ program
153
+ .command("board [action]")
154
+ .description("Manage HU Board (start|stop|status|open)")
155
+ .option("--port <number>", "Port (default: 4000)", "4000")
156
+ .action(async (action = "start", opts) => {
157
+ await withConfig(pkgVersion, "board", opts, async ({ config, logger }) => {
158
+ const port = Number(opts.port) || config.hu_board?.port || 4000;
159
+ await boardCommand({ action, port, logger });
160
+ });
161
+ });
162
+
163
+ program
164
+ .command("undo")
165
+ .description("Revert last pipeline run")
166
+ .option("--hard", "Discard all changes (default: soft reset, keeps changes staged)")
167
+ .action(async (flags) => {
168
+ await withConfig(pkgVersion, "undo", flags, async ({ logger }) => {
169
+ const result = await undoCommand({ hard: !!flags.hard, logger });
170
+ if (!result.ok) process.exit(1);
171
+ });
172
+ });
173
+
174
+ program
175
+ .command("sync")
176
+ .description("Detect drift between code and the latest plan. Read-only report (issue #540 MVP).")
177
+ .option("--plan <planId>", "Sync against a specific plan instead of the latest")
178
+ .option("--json", "Emit machine-readable JSON instead of human output")
179
+ .action(async (flags) => {
180
+ await withConfig(pkgVersion, "sync", flags, async ({ config, logger }) => {
181
+ await syncCommand({ config, logger, planId: flags.plan, json: flags.json });
182
+ });
183
+ });
184
+
185
+ program
186
+ .command("clean")
187
+ .description("Garbage-collect stale plans, sessions and HU batches (dry-run by default)")
188
+ .option("--yes", "Actually delete — without this flag, only prints what would be removed")
189
+ .option("--nuke", "Retention=0 everywhere + wipe HU board DB. \"I want it all gone\"")
190
+ .option("--plan-days <n>", "Keep finalised plans (approved/rejected/executed) for N days (default: 30)")
191
+ .option("--draft-days <n>", "Keep draft plans for N days (default: 60)")
192
+ .option("--session-days <n>", "Keep finalised sessions for N days (default: 7)")
193
+ .option("--hu-days <n>", "Keep HU story batches for N days (default: 14)")
194
+ .action(async (flags) => {
195
+ await cleanCommand({
196
+ yes: Boolean(flags.yes),
197
+ nuke: Boolean(flags.nuke),
198
+ planDays: flags.planDays ? Number(flags.planDays) : undefined,
199
+ draftDays: flags.draftDays ? Number(flags.draftDays) : undefined,
200
+ sessionDays: flags.sessionDays ? Number(flags.sessionDays) : undefined,
201
+ huDays: flags.huDays ? Number(flags.huDays) : undefined,
202
+ });
203
+ });
204
+ }
@@ -0,0 +1,212 @@
1
+ import { initCommand } from "../commands/init.js";
2
+ import { configCommand } from "../commands/config.js";
3
+ import { codeCommand } from "../commands/code.js";
4
+ import { reviewCommand } from "../commands/review.js";
5
+ import { scanCommand } from "../commands/scan.js";
6
+ import { doctorCommand } from "../commands/doctor.js";
7
+ import { reportCommand } from "../commands/report.js";
8
+ import { runCommandHandler } from "../commands/run.js";
9
+ import { statusCommand } from "../commands/status.js";
10
+ import { loadPlan } from "../plan/plan-store.js";
11
+ import { withConfig } from "./_shared.js";
12
+
13
+ /**
14
+ * Register the "core pipeline" commands: everything the user runs day-to-day
15
+ * to drive the agent loop or inspect its output. Kept together so the
16
+ * shape of the run/code/review trio (and the scan/report/doctor helpers
17
+ * that frame them) lives in one file.
18
+ */
19
+ export function registerPipeline(program, { pkgVersion }) {
20
+ program
21
+ .command("init")
22
+ .description("Initialize config, review rules and SonarQube")
23
+ .option("--no-interactive", "Skip wizard, use defaults (for CI/scripts)")
24
+ .option("--scaffold-ci", "Scaffold Karajan CI Gateway workflow files")
25
+ .action(async (flags) => {
26
+ await withConfig(pkgVersion, "init", flags, async ({ config: _config, logger }) => {
27
+ await initCommand({ logger, flags });
28
+ });
29
+ });
30
+
31
+ program
32
+ .command("config")
33
+ .description("Show current config")
34
+ .option("--json", "Show as JSON")
35
+ .option("--edit", "Open config in $EDITOR for editing")
36
+ .action(async (flags) => {
37
+ await configCommand(flags);
38
+ });
39
+
40
+ program
41
+ .command("run")
42
+ .description("Run coder+sonar+reviewer loop")
43
+ .argument("[task]", "Task description (or use --task-file to read from a .md file)")
44
+ .option("--task-file <path>", "Read the task from a file (e.g. .md). Alternative to the positional task argument.")
45
+ .option("-y, --yes", "Skip the cwd confirmation prompt (CI / scripted runs).")
46
+ .option("--planner <name>")
47
+ .option("--coder <name>")
48
+ .option("--reviewer <name>")
49
+ .option("--refactorer <name>")
50
+ .option("--planner-model <name>")
51
+ .option("--coder-model <name>")
52
+ .option("--reviewer-model <name>")
53
+ .option("--refactorer-model <name>")
54
+ .option("--enable-planner")
55
+ .option("--enable-reviewer")
56
+ .option("--enable-refactorer")
57
+ .option("--enable-researcher")
58
+ .option("--enable-tester")
59
+ .option("--enable-security")
60
+ .option("--enable-impeccable")
61
+ .option("--enable-triage")
62
+ .option("--enable-discover")
63
+ .option("--enable-architect")
64
+ .option("--plan <planId>", "Plan ID from kj plan. Loads persisted plan context and skips researcher/architect/planner.")
65
+ // PR4: launch a single HU (or comma-separated list) instead of the
66
+ // entire plan. The board uses this for the per-HU ▶ button so the
67
+ // user can re-run only a failed HU without restarting the whole
68
+ // plan. Requires --plan to also be set.
69
+ .option("--hu <huIds>", "Comma-separated HU IDs to run. When set, the plan's other HUs are skipped. Requires --plan.")
70
+ .option("--enable-hu-reviewer")
71
+ .option("--hu-file <path>", "YAML file with HU stories to certify before coding")
72
+ .option("--enable-serena")
73
+ .option("--mode <name>")
74
+ .option("--max-iterations <n>")
75
+ .option("--max-iteration-minutes <n>")
76
+ .option("--max-total-minutes <n>")
77
+ .option("--base-branch <name>")
78
+ .option("--base-ref <ref>")
79
+ .option("--coder-fallback <name>")
80
+ .option("--reviewer-fallback <name>")
81
+ .option("--reviewer-retries <n>")
82
+ .option("--auto-commit")
83
+ .option("--auto-push")
84
+ .option("--auto-pr")
85
+ .option("--enable-ci", "Enable Karajan CI (early PR + dispatch comments/reviews)")
86
+ .option("--branch-prefix <prefix>")
87
+ .option("--task-type <type>", "Explicit task type: sw, infra, doc, add-tests, refactor")
88
+ .option("--methodology <name>")
89
+ .option("--no-auto-rebase")
90
+ .option("--no-sonar")
91
+ .option("--enable-sonarcloud", "Enable SonarCloud scan (complementary to SonarQube)")
92
+ .option("--no-sonarcloud")
93
+ .option("--checkpoint-interval <n>", "Minutes between interactive checkpoints (default: 5)")
94
+ .option("--pg-task <cardId>", "Planning Game card ID (e.g., KJC-TSK-0042)")
95
+ .option("--pg-project <projectId>", "Planning Game project ID")
96
+ .option("--auto-simplify", "Auto-simplify pipeline for simple tasks (disable reviewer/tester)")
97
+ .option("--no-auto-simplify", "Force full pipeline regardless of triage level")
98
+ .option("--smart-models", "Enable smart model selection based on triage complexity")
99
+ .option("--no-smart-models", "Disable smart model selection")
100
+ .option("--design", "Activate design refactoring mode (impeccable role applies design changes)")
101
+ .option("--domain <text-or-path>", "Domain knowledge: inline text or path to .md file")
102
+ .option("--skills-mode <mode>", "Skill detection mode: auto|regex|semantic|none (default: auto)")
103
+ .option("--brain <mode>", "Brain decisor: on|off (default: on). When off, only legacy flags drive routing.")
104
+ .option("--skip-role <role...>", "Force one or more roles OFF regardless of triage (e.g. --skip-role tester security)")
105
+ .option("--force-role <role...>", "Force one or more roles ON regardless of triage")
106
+ .option("--dry-run", "Show what would be executed without running anything")
107
+ .option("--json", "Output JSON only (no styled display)")
108
+ .option("-q, --quiet", "Show only stage status lines, suppress raw agent output (default)")
109
+ .option("-v, --verbose", "Show full agent output (stream-json, raw lines)")
110
+ .action(async (task, flags) => {
111
+ await withConfig(pkgVersion, "run", flags, async ({ config, logger }) => {
112
+ // If `--plan <id>` was passed and the user didn't provide a task
113
+ // argument or --task-file, fall back to the task embedded in the
114
+ // plan JSON. The whole point of `kj plan generate` is that the
115
+ // task is captured once and re-used; forcing the user to repeat
116
+ // --task-file on every run is friction with no upside.
117
+ let effectiveTask = task;
118
+ if (!task && !flags.taskFile && flags.plan) {
119
+ // config.projectDir is only populated post-init; before runFlow
120
+ // it can still be undefined. Plans are stored under cwd, so
121
+ // that's the correct fallback for the lookup.
122
+ const projectDir = config.projectDir || process.cwd();
123
+ const plan = await loadPlan(projectDir, flags.plan);
124
+ if (plan?.task && String(plan.task).trim()) {
125
+ effectiveTask = plan.task;
126
+ }
127
+ }
128
+ const { resolveTaskInput } = await import("../utils/task-file.js");
129
+ const resolvedTask = await resolveTaskInput({ task: effectiveTask, taskFile: flags.taskFile, projectDir: config.projectDir, logger });
130
+ await runCommandHandler({ task: resolvedTask, config, logger, flags });
131
+ });
132
+ });
133
+
134
+ program
135
+ .command("code")
136
+ .description("Run only coder")
137
+ .argument("[task]", "Task description (or use --task-file)")
138
+ .option("--task-file <path>", "Read the task from a file (e.g. .md)")
139
+ .option("--coder <name>")
140
+ .option("--coder-model <name>")
141
+ .action(async (task, flags) => {
142
+ await withConfig(pkgVersion, "code", flags, async ({ config, logger }) => {
143
+ const { resolveTaskInput } = await import("../utils/task-file.js");
144
+ const resolvedTask = await resolveTaskInput({ task, taskFile: flags.taskFile, projectDir: config.projectDir, logger });
145
+ await codeCommand({ task: resolvedTask, config, logger });
146
+ });
147
+ });
148
+
149
+ program
150
+ .command("review")
151
+ .description("Run only reviewer")
152
+ .argument("[task]", "Task description (or use --task-file)")
153
+ .option("--task-file <path>", "Read the task from a file (e.g. .md)")
154
+ .option("--reviewer <name>")
155
+ .option("--reviewer-model <name>")
156
+ .option("--base-ref <ref>")
157
+ .action(async (task, flags) => {
158
+ await withConfig(pkgVersion, "review", flags, async ({ config, logger }) => {
159
+ const { resolveTaskInput } = await import("../utils/task-file.js");
160
+ const resolvedTask = await resolveTaskInput({ task, taskFile: flags.taskFile, projectDir: config.projectDir, logger });
161
+ await reviewCommand({ task: resolvedTask, config, logger, baseRef: flags.baseRef });
162
+ });
163
+ });
164
+
165
+ program
166
+ .command("scan")
167
+ .description("Run SonarQube scan")
168
+ .action(async () => {
169
+ await withConfig(pkgVersion, "scan", {}, scanCommand);
170
+ });
171
+
172
+ program
173
+ .command("doctor")
174
+ .description("Check environment requirements and auto-remediate when possible")
175
+ .option("--check-only", "Detect issues without applying fixes")
176
+ .option("-y, --yes", "Auto-confirm prompts for invasive remediations (CI mode)")
177
+ .option("--json", "Emit machine-readable JSON instead of human output")
178
+ .option("--verbose", "Include fix hints and timing for every check")
179
+ .action(async (flags) => {
180
+ const exitCode = await withConfig(pkgVersion, "doctor", {}, (ctx) =>
181
+ doctorCommand({
182
+ ...ctx,
183
+ checkOnly: !!flags.checkOnly,
184
+ yes: !!flags.yes,
185
+ json: !!flags.json,
186
+ verbose: !!flags.verbose,
187
+ })
188
+ );
189
+ if (Number.isInteger(exitCode)) process.exit(exitCode);
190
+ });
191
+
192
+ program
193
+ .command("status")
194
+ .description("Show pipeline dashboard with HU progress")
195
+ .option("-n, --lines <n>", "Max log lines to read", "50")
196
+ .action(async (flags) => {
197
+ await statusCommand({ lines: Number(flags.lines) });
198
+ });
199
+
200
+ program
201
+ .command("report")
202
+ .description("Show latest session report")
203
+ .option("--list", "List session ids")
204
+ .option("--session-id <id>", "Show report for a specific session ID")
205
+ .option("--format <type>", "Output format: text|json", "text")
206
+ .option("--trace", "Show chronological trace of all pipeline stages")
207
+ .option("--currency <code>", "Display costs in currency: usd|eur", "usd")
208
+ .option("--pg-task <cardId>", "Filter reports by Planning Game card ID")
209
+ .action(async (flags) => {
210
+ await reportCommand(flags);
211
+ });
212
+ }
@@ -0,0 +1,152 @@
1
+ import {
2
+ planGenerateCommand,
3
+ planListCommand,
4
+ planShowCommand,
5
+ planReadyCommand,
6
+ planValidateCommand,
7
+ planDeleteCommand,
8
+ planAddHuCommand,
9
+ planRemoveHuCommand,
10
+ } from "../commands/plan.js";
11
+ import { parseCanvasMarkdown } from "../canvas/parse-md.js";
12
+ import { validateCanvas } from "../canvas/validate.js";
13
+ import { canvasToTask } from "../canvas/to-flat.js";
14
+ import { withConfig } from "./_shared.js";
15
+
16
+ /**
17
+ * Register the `plan` parent command and every sub-command that lives
18
+ * under it (generate / list / show / ready / validate / delete /
19
+ * add-hu / remove-hu). Centralising plan management here keeps the
20
+ * REASONS Canvas auto-detection logic in one file alongside the
21
+ * planner entrypoint.
22
+ */
23
+ export function registerPlan(program, { pkgVersion }) {
24
+ const plan = program.command("plan").description("Plan management (generate, review, approve, execute)");
25
+
26
+ plan
27
+ .command("generate", { isDefault: true })
28
+ .description("Generate implementation plan with HUs")
29
+ .argument("[task]", "Task description (or use --task-file)")
30
+ .option("--task-file <path>", "Read the task from a file (e.g. .md)")
31
+ .option("--planner <name>")
32
+ .option("--planner-model <name>")
33
+ .option("--context <text>", "Additional context for the planner")
34
+ .option("--json", "Output raw JSON plan")
35
+ // Tests-first quality knobs (v2.7.5). Default is "thorough":
36
+ // when the planner skips acceptance_tests on some HUs, run a
37
+ // focused synthesizer pass to fill them in. Disable with these
38
+ // flags only if you explicitly want a fast / sketch plan.
39
+ .option("--no-tests-synth", "Skip the tests-synthesizer pass that fills in missing acceptance_tests")
40
+ .option("--no-plan-review", "Skip the high-level plan reviewer pass (gaps / deps / overlap / order)")
41
+ .option("--quick", "Sketch mode — skip every quality pass after the initial planner call")
42
+ .option("-y, --yes", "Skip the project-name prompt (use the auto-derived default).")
43
+ .option("--no-interactive", "Force non-interactive mode (no prompts, use defaults).")
44
+ .action(async (task, flags) => {
45
+ await withConfig(pkgVersion, "plan", flags, async ({ config, logger }) => {
46
+ const { resolveTaskInput } = await import("../utils/task-file.js");
47
+ const resolvedTask = await resolveTaskInput({ task, taskFile: flags.taskFile, projectDir: config.projectDir, logger });
48
+
49
+ // REASONS Canvas auto-detection: a SPEC that contains
50
+ // `## REASONS:Section` headings is treated as a structured
51
+ // Canvas — parsed + validated BEFORE the planner runs. This
52
+ // catches broken acceptance tests (jq syntax, bash parse,
53
+ // gherkin missing Given/Then, the 2026-04-29 as-binding
54
+ // misuse class) at plan-time, without spending LLM dollars.
55
+ //
56
+ // Detection by file CONTENTS, not by CLI flag — the Canvas
57
+ // format is the signal. Flat SPECs without REASONS sections
58
+ // continue through the legacy path unchanged (back-compat).
59
+ if (typeof resolvedTask === "string" && /^##\s+REASONS:/m.test(resolvedTask)) {
60
+ let parsed;
61
+ try {
62
+ parsed = parseCanvasMarkdown(resolvedTask);
63
+ } catch (err) {
64
+ throw new Error(`Canvas parse error: ${err.message}`, { cause: err });
65
+ }
66
+ const validation = validateCanvas(parsed);
67
+ if (!validation.valid) {
68
+ const lines = ["Canvas validation failed."];
69
+ if (validation.schemaIssues) {
70
+ lines.push("Schema issues:");
71
+ for (const issue of validation.schemaIssues) {
72
+ lines.push(` - ${issue.path?.map((p) => p.key).join(".") || "(root)"}: ${issue.message}`);
73
+ }
74
+ }
75
+ if (validation.testIssues) {
76
+ lines.push("Acceptance test issues:");
77
+ for (const issue of validation.testIssues) {
78
+ lines.push(` - Operation #${issue.operationIndex + 1} "${issue.operationTitle}", test #${issue.testIndex + 1} (${issue.type}, ${issue.kind}):`);
79
+ lines.push(` ${issue.content}`);
80
+ lines.push(` → ${issue.error}`);
81
+ }
82
+ }
83
+ throw new Error(lines.join("\n"));
84
+ }
85
+ // Pass the Canvas-derived flat task to the existing planner.
86
+ // Norms + Safeguards ride along under explicit headings the
87
+ // coder prompt can grep for.
88
+ const taskFromCanvas = canvasToTask(validation.canvas);
89
+ await planGenerateCommand({ task: taskFromCanvas, config, logger, flags });
90
+ return;
91
+ }
92
+ await planGenerateCommand({
93
+ task: resolvedTask, config, logger, json: flags.json, context: flags.context,
94
+ flags: {
95
+ noTestsSynth: flags.testsSynth === false,
96
+ noPlanReview: flags.planReview === false,
97
+ quick: Boolean(flags.quick),
98
+ yes: Boolean(flags.yes),
99
+ interactive: flags.interactive,
100
+ },
101
+ });
102
+ });
103
+ });
104
+
105
+ plan.command("list").description("List all plans").action(async (flags) => {
106
+ await withConfig(pkgVersion, "plan", flags, async ({ config }) => {
107
+ // TSK-0340: was `await import(...)` — now served by the top-level static import.
108
+ await planListCommand({ config });
109
+ });
110
+ });
111
+
112
+ plan.command("show").description("Show plan details + HUs").argument("<planId>").action(async (planId, flags) => {
113
+ await withConfig(pkgVersion, "plan", flags, async ({ config }) => {
114
+ await planShowCommand({ config, planId });
115
+ });
116
+ });
117
+
118
+ plan.command("ready").description("Approve plan — certify all HUs").argument("<planId>").action(async (planId, flags) => {
119
+ await withConfig(pkgVersion, "plan", flags, async ({ config }) => {
120
+ await planReadyCommand({ config, planId });
121
+ });
122
+ });
123
+
124
+ plan.command("validate").description("Validate plan structure").argument("<planId>").action(async (planId, flags) => {
125
+ await withConfig(pkgVersion, "plan", flags, async ({ config }) => {
126
+ await planValidateCommand({ config, planId });
127
+ });
128
+ });
129
+
130
+ plan.command("delete").description("Delete a plan").argument("<planId>").action(async (planId, flags) => {
131
+ await withConfig(pkgVersion, "plan", flags, async ({ config }) => {
132
+ await planDeleteCommand({ config, planId });
133
+ });
134
+ });
135
+
136
+ plan.command("add-hu").description("Add HU to plan").argument("<planId>")
137
+ .option("--title <text>", "HU title")
138
+ .option("--type <type>", "Task type: sw|infra|doc|add-tests|refactor|nocode", "sw")
139
+ .option("--deps <ids>", "Comma-separated HU IDs this depends on")
140
+ .option("--scope <text>", "Scope description")
141
+ .action(async (planId, flags) => {
142
+ await withConfig(pkgVersion, "plan", flags, async ({ config }) => {
143
+ await planAddHuCommand({ config, planId, title: flags.title, type: flags.type, deps: flags.deps, scope: flags.scope });
144
+ });
145
+ });
146
+
147
+ plan.command("remove-hu").description("Remove HU from plan").argument("<planId>").argument("<huId>").action(async (planId, huId, flags) => {
148
+ await withConfig(pkgVersion, "plan", flags, async ({ config }) => {
149
+ await planRemoveHuCommand({ config, planId, huId });
150
+ });
151
+ });
152
+ }
@@ -0,0 +1,38 @@
1
+ import { rolesCommand } from "../commands/roles.js";
2
+ import { agentsCommand } from "../commands/agents.js";
3
+ import { withConfig } from "./_shared.js";
4
+
5
+ /**
6
+ * Register the meta-configuration commands the user reaches for when
7
+ * setting up the pipeline itself: which skills to load, which roles
8
+ * are wired in, which provider is mapped to each agent slot.
9
+ */
10
+ export function registerRolesSkills(program, { pkgVersion }) {
11
+ program
12
+ .command("skills [action]")
13
+ .description("Manage the skill cache (actions: list | clear-cache | sync-addyosmani | list-addyosmani)")
14
+ .action(async (action) => {
15
+ const { skillsCommand } = await import("../commands/skills.js");
16
+ const exitCode = await skillsCommand({ action: action || "list" });
17
+ if (Number.isInteger(exitCode)) process.exit(exitCode);
18
+ });
19
+
20
+ program
21
+ .command("roles [subcommand] [role]")
22
+ .description("List pipeline roles or show role template instructions")
23
+ .action(async (subcommand, role) => {
24
+ await withConfig(pkgVersion, "roles", {}, async ({ config }) => {
25
+ await rolesCommand({ config, subcommand: subcommand || "list", roleName: role });
26
+ });
27
+ });
28
+
29
+ program
30
+ .command("agents [subcommand] [role] [provider]")
31
+ .description("List or change AI agent assignments per role (e.g. kj agents set coder gemini)")
32
+ .option("--global", "Persist change to kj.config.yml (default for CLI)")
33
+ .action(async (subcommand, role, provider, flags) => {
34
+ await withConfig(pkgVersion, "agents", {}, async ({ config }) => {
35
+ await agentsCommand({ config, subcommand: subcommand || "list", role, provider, global: flags.global });
36
+ });
37
+ });
38
+ }
@@ -0,0 +1,28 @@
1
+ import { sonarCommand, sonarOpenCommand } from "../commands/sonar.js";
2
+ import { loadConfig } from "../config.js";
3
+
4
+ /**
5
+ * Register the `sonar` parent command and its sub-actions
6
+ * (status / start / stop / logs / open). Kept in its own module
7
+ * because every sub-command shares the same one-liner shape and
8
+ * the only one that needs config is `open`.
9
+ */
10
+ export function registerSonar(program) {
11
+ const sonar = program.command("sonar").description("Manage SonarQube container");
12
+ sonar.command("status").action(async () => sonarCommand({ action: "status" }));
13
+ sonar.command("start").action(async () => sonarCommand({ action: "start" }));
14
+ sonar.command("stop").action(async () => sonarCommand({ action: "stop" }));
15
+ sonar.command("logs").action(async () => sonarCommand({ action: "logs" }));
16
+ sonar
17
+ .command("open")
18
+ .description("Open SonarQube dashboard in the browser")
19
+ .action(async () => {
20
+ const { config } = await loadConfig();
21
+ const result = await sonarOpenCommand({ config });
22
+ if (!result.ok) {
23
+ console.error(result.error);
24
+ process.exit(1);
25
+ }
26
+ console.log(`Opened ${result.url}`);
27
+ });
28
+ }