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