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
@@ -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
+ }