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.
- package/README.md +4 -4
- package/package.json +24 -4
- package/scripts/diet-baseline.mjs +170 -0
- package/scripts/regen-arch-stats.sh +71 -0
- package/src/agents/availability.js +15 -9
- package/src/agents/claude-agent.js +143 -45
- package/src/agents/model-registry.js +1 -1
- package/src/audit/agent-readiness.js +240 -0
- package/src/bootstrap.js +26 -1
- package/src/brain/decision-tracker.js +3 -1
- package/src/brain/decisor.js +1 -1
- package/src/canvas/parse-md.js +238 -0
- package/src/canvas/schema.js +101 -0
- package/src/canvas/to-flat.js +85 -0
- package/src/canvas/validate.js +163 -0
- package/src/checks/binaries.js +1 -1
- package/src/checks/ports.js +97 -27
- package/src/checks/runner.js +26 -10
- package/src/checks/sonar.js +1 -1
- package/src/checks/system.js +1 -1
- package/src/checks/tokens.js +77 -24
- package/src/cli/_shared.js +30 -0
- package/src/cli/register-meta.js +204 -0
- package/src/cli/register-pipeline.js +212 -0
- package/src/cli/register-plan.js +152 -0
- package/src/cli/register-roles-skills.js +38 -0
- package/src/cli/register-sonar.js +28 -0
- package/src/cli.js +83 -468
- package/src/commands/agents.js +18 -6
- package/src/commands/architect.js +29 -2
- package/src/commands/audit.js +27 -3
- package/src/commands/board.js +160 -30
- package/src/commands/clean.js +83 -0
- package/src/commands/code.js +7 -2
- package/src/commands/discover.js +7 -2
- package/src/commands/init.js +2 -2
- package/src/commands/plan/_shared.js +40 -0
- package/src/commands/plan/add-hu.js +21 -0
- package/src/commands/plan/delete.js +9 -0
- package/src/commands/plan/generate.js +284 -0
- package/src/commands/plan/index.js +14 -0
- package/src/commands/plan/list.js +21 -0
- package/src/commands/plan/ready.js +46 -0
- package/src/commands/plan/remove-hu.js +17 -0
- package/src/commands/plan/show.js +81 -0
- package/src/commands/plan/validate.js +20 -0
- package/src/commands/plan.js +14 -291
- package/src/commands/researcher.js +7 -2
- package/src/commands/resume.js +24 -1
- package/src/commands/review.js +7 -2
- package/src/commands/run.js +52 -1
- package/src/commands/status.js +1 -1
- package/src/commands/sync.js +75 -0
- package/src/commands/triage.js +7 -2
- package/src/commands/undo.js +1 -1
- package/src/config/defaults.js +213 -0
- package/src/config/loader.js +137 -0
- package/src/config/overrides.js +199 -0
- package/src/config/role-resolver.js +159 -0
- package/src/config/test-harness.js +79 -0
- package/src/config.js +30 -609
- package/src/git/automation.js +2 -2
- package/src/guards/output-guard.js +0 -2
- package/src/hu/acceptance-runner.js +52 -10
- package/src/hu/store.js +11 -0
- package/src/mcp/handlers/direct-handlers.js +2 -5
- package/src/mcp/handlers/management-handlers.js +48 -4
- package/src/mcp/handlers/run-handler.js +9 -8
- package/src/mcp/preflight.js +32 -13
- package/src/mcp/server-handlers.js +4 -3
- package/src/mcp/suggest-handler.js +3 -2
- package/src/mcp/tools.js +15 -0
- package/src/orchestrator/agent-fallback.js +1 -1
- package/src/orchestrator/ci-integration.js +10 -10
- package/src/orchestrator/config-init.js +25 -7
- package/src/orchestrator/direct-actions.js +6 -2
- package/src/orchestrator/drivers/error-recovery.js +287 -0
- package/src/orchestrator/drivers/init-context.js +222 -0
- package/src/orchestrator/drivers/iteration-loop.js +311 -0
- package/src/orchestrator/drivers/iteration-phases/coder-and-refactorer.js +48 -0
- package/src/orchestrator/drivers/iteration-phases/guards.js +84 -0
- package/src/orchestrator/drivers/iteration-phases/handle-approved.js +29 -0
- package/src/orchestrator/drivers/iteration-phases/quality-gates.js +76 -0
- package/src/orchestrator/drivers/iteration-phases/reviewer-gate.js +60 -0
- package/src/orchestrator/drivers/post-loop.js +370 -0
- package/src/orchestrator/drivers/pre-loop-phases/auto-hu-batch.js +116 -0
- package/src/orchestrator/drivers/pre-loop-phases/config-deprecations.js +47 -0
- package/src/orchestrator/drivers/pre-loop-phases/ensure-addyosmani-skills.js +84 -0
- package/src/orchestrator/drivers/pre-loop-phases/inject-loaded-plan.js +268 -0
- package/src/orchestrator/drivers/pre-loop.js +434 -0
- package/src/orchestrator/drivers/run-hu-batch.js +367 -0
- package/src/orchestrator/flow-control.js +4 -3
- package/src/orchestrator/flow-runner.js +121 -2019
- package/src/orchestrator/fs-leak-detector.js +156 -0
- package/src/orchestrator/hu-sub-pipeline.js +197 -9
- package/src/orchestrator/integrations.js +88 -0
- package/src/orchestrator/post-loop-stages.js +35 -8
- package/src/orchestrator/preflight-checks.js +30 -3
- package/src/orchestrator/repair/repair-detector.js +117 -0
- package/src/orchestrator/repair/repair-runner.js +142 -0
- package/src/orchestrator/reviewer-fallback.js +1 -1
- package/src/orchestrator/solomon-escalation.js +3 -2
- package/src/orchestrator/solomon-rules.js +7 -3
- package/src/orchestrator/stages/architect-stage.js +12 -11
- package/src/orchestrator/stages/coder-stage.js +56 -7
- package/src/orchestrator/stages/hu-reviewer-phases/detect-and-apply-splits.js +127 -0
- package/src/orchestrator/stages/hu-reviewer-stage.js +6 -79
- package/src/orchestrator/stages/planner-stage.js +1 -1
- package/src/orchestrator/stages/research-stage.js +1 -1
- package/src/orchestrator/stages/reviewer-stage.js +7 -7
- package/src/orchestrator/stages/sonar-stage.js +94 -10
- package/src/orchestrator/stages/stage-classes.js +86 -0
- package/src/orchestrator/stages/stage-executor.js +1 -1
- package/src/orchestrator/stages/triage-stage.js +1 -1
- package/src/orchestrator/standby.js +3 -2
- package/src/orchestrator/verification-gate.js +20 -12
- package/src/plan/adr-generator.js +170 -0
- package/src/plan/adr-loader.js +60 -0
- package/src/plan/plan-executor.js +19 -1
- package/src/plan/plan-hu-ops.js +170 -1
- package/src/plan/plan-reviewer.js +199 -0
- package/src/plan/plan-schema.js +69 -1
- package/src/plan/plan-store.js +40 -3
- package/src/plan/spec-detector.js +61 -0
- package/src/plan/tests-synthesizer.js +116 -0
- package/src/planning-game/client.js +3 -14
- package/src/planning-game/decomposition.js +1 -1
- package/src/planning-game/pipeline-adapter.js +36 -5
- package/src/prompts/coder.js +172 -1
- package/src/prompts/hu-reviewer.js +0 -1
- package/src/prompts/planner.js +81 -4
- package/src/review/diff-generator.js +35 -22
- package/src/review/snapshot-diff.js +1 -1
- package/src/roles/coder-role.js +25 -3
- package/src/roles/planner-role.js +42 -1
- package/src/roles/repairer-role.js +181 -0
- package/src/roles/tester-role.js +70 -4
- package/src/session/mutators.js +304 -0
- package/src/session/runtime-overrides.js +59 -0
- package/src/{session-store.js → session/store.js} +8 -2
- package/src/skills/semantic-detector.js +10 -5
- package/src/skills/skill-detector.js +1 -1
- package/src/sonar/cloud-scanner.js +0 -1
- package/src/sonar/credentials.js +14 -6
- package/src/sonar/discovery.js +123 -0
- package/src/sonar/manager.js +18 -0
- package/src/sync/detect-drift.js +113 -0
- package/src/tracker-bootstrap.js +44 -0
- package/src/utils/agent-detect.js +15 -11
- package/src/utils/board-prompt-bridge.js +109 -0
- package/src/utils/cli-progress.js +161 -0
- package/src/utils/cwd-confirm.js +39 -0
- package/src/utils/derive-project-name-from-cwd.js +119 -0
- package/src/utils/garbage-collector.js +396 -0
- package/src/utils/git.js +16 -4
- package/src/utils/locale.js +1 -1
- package/src/utils/paths.js +21 -1
- package/src/utils/process.js +1 -1
- package/src/utils/prompt-project-name.js +35 -0
- package/src/utils/run-context.js +75 -0
- package/src/utils/run-log.js +12 -0
- package/src/utils/task-file.js +1 -1
- package/src/utils/telemetry.js +13 -1
- package/src/webperf/devtools-detect.js +1 -1
- package/templates/coder-rules.md +13 -0
- package/templates/roles/coder.md +34 -0
- 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
|
+
}
|