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/commands/plan.js
CHANGED
|
@@ -1,291 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const step = plan.steps[i];
|
|
16
|
-
const commit = step.commit ? ` → \`${step.commit}\`` : "";
|
|
17
|
-
lines.push(`${i + 1}. ${step.description}${commit}`);
|
|
18
|
-
}
|
|
19
|
-
lines.push("");
|
|
20
|
-
}
|
|
21
|
-
if (plan.risks?.length) {
|
|
22
|
-
lines.push("## Risks");
|
|
23
|
-
for (const risk of plan.risks) lines.push(`- ${risk}`);
|
|
24
|
-
lines.push("");
|
|
25
|
-
}
|
|
26
|
-
if (plan.outOfScope?.length) {
|
|
27
|
-
lines.push("## Out of scope");
|
|
28
|
-
for (const item of plan.outOfScope) lines.push(`- ${item}`);
|
|
29
|
-
lines.push("");
|
|
30
|
-
}
|
|
31
|
-
return lines.join("\n");
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function formatHuTable(hus) {
|
|
35
|
-
if (!hus?.length) return " (no HUs)";
|
|
36
|
-
const lines = [];
|
|
37
|
-
const maxTitle = Math.min(50, Math.max(...hus.map(h => h.title?.length || 0)));
|
|
38
|
-
lines.push(` ${"ID".padEnd(30)} ${"Title".padEnd(maxTitle)} ${"Type".padEnd(10)} ${"Status".padEnd(10)} Deps`);
|
|
39
|
-
lines.push(` ${"─".repeat(30)} ${"─".repeat(maxTitle)} ${"─".repeat(10)} ${"─".repeat(10)} ${"─".repeat(15)}`);
|
|
40
|
-
for (const hu of hus) {
|
|
41
|
-
const title = (hu.title || "").slice(0, maxTitle).padEnd(maxTitle);
|
|
42
|
-
const deps = (hu.blocked_by || []).join(", ") || "—";
|
|
43
|
-
lines.push(` ${hu.id.padEnd(30)} ${title} ${(hu.task_type || "sw").padEnd(10)} ${(hu.status || "?").padEnd(10)} ${deps}`);
|
|
44
|
-
}
|
|
45
|
-
return lines.join("\n");
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// ---- Subcommand handlers ----
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* kj plan "task" — generate plan + HUs
|
|
52
|
-
*/
|
|
53
|
-
export async function planGenerateCommand({ task, config, logger, json, context }) {
|
|
54
|
-
const { withCliRunLog } = await import("../utils/cli-run-log.js");
|
|
55
|
-
return withCliRunLog("plan", { projectDir: config?.projectDir, logger }, async ({ runLog }) => {
|
|
56
|
-
return planGenerateImpl({ task, config, logger, json, context, runLog });
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function planGenerateImpl({ task, config, logger, json, context, runLog }) {
|
|
61
|
-
const plannerRole = resolveRole(config, "planner");
|
|
62
|
-
await assertAgentsAvailable([plannerRole.provider]);
|
|
63
|
-
runLog.logText(`[planner] provider=${plannerRole.provider}`);
|
|
64
|
-
|
|
65
|
-
const planner = createAgent(plannerRole.provider, config, logger);
|
|
66
|
-
const prompt = buildPlannerPrompt({ task, context });
|
|
67
|
-
const silenceTimeoutMs = Number(config?.session?.max_agent_silence_minutes) > 0
|
|
68
|
-
? Math.round(Number(config.session.max_agent_silence_minutes) * 60 * 1000)
|
|
69
|
-
: undefined;
|
|
70
|
-
const timeoutMs = Number(config?.session?.max_planner_minutes) > 0
|
|
71
|
-
? Math.round(Number(config.session.max_planner_minutes) * 60 * 1000)
|
|
72
|
-
: undefined;
|
|
73
|
-
const result = await planner.runTask({ prompt, role: "planner", silenceTimeoutMs, timeoutMs });
|
|
74
|
-
|
|
75
|
-
if (!result.ok) throw new Error(result.error || result.output || "Planner failed");
|
|
76
|
-
|
|
77
|
-
const parsed = parseMaybeJsonString(result.output);
|
|
78
|
-
|
|
79
|
-
// Create v2 plan with HUs from planner steps
|
|
80
|
-
const { createPlanV2 } = await import("../plan/plan-schema.js");
|
|
81
|
-
const { addHu } = await import("../plan/plan-hu-ops.js");
|
|
82
|
-
const { savePlan } = await import("../plan/plan-store.js");
|
|
83
|
-
const { deriveProjectName, classifyTaskType } = await import("../hu/auto-generator.js");
|
|
84
|
-
|
|
85
|
-
const projectDir = config.projectDir || process.cwd();
|
|
86
|
-
const plan = createPlanV2(task);
|
|
87
|
-
plan.name = deriveProjectName(task);
|
|
88
|
-
plan.approach = parsed?.approach || (typeof parsed === "string" ? parsed : (typeof result.output === "string" ? result.output : null));
|
|
89
|
-
plan.risks = parsed?.risks || [];
|
|
90
|
-
plan.outOfScope = parsed?.outOfScope || [];
|
|
91
|
-
|
|
92
|
-
// Convert planner steps → HUs
|
|
93
|
-
const steps = parsed?.steps || [];
|
|
94
|
-
let prevId = null;
|
|
95
|
-
for (const step of steps) {
|
|
96
|
-
const desc = typeof step === "string" ? step : step.description || step.title || JSON.stringify(step);
|
|
97
|
-
const hu = addHu(plan, {
|
|
98
|
-
title: desc.slice(0, 80),
|
|
99
|
-
task_type: classifyTaskType(desc),
|
|
100
|
-
scope: desc,
|
|
101
|
-
blocked_by: prevId ? [prevId] : [],
|
|
102
|
-
acceptance_tests: [
|
|
103
|
-
"npx vitest run 2>&1; test $? -eq 0 && echo PASS || echo FAIL"
|
|
104
|
-
]
|
|
105
|
-
});
|
|
106
|
-
prevId = hu.id;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const planId = await savePlan(projectDir, plan);
|
|
110
|
-
|
|
111
|
-
runLog.logText(`[planner] finished — hus=${plan.hus.length} plan=${planId}`);
|
|
112
|
-
|
|
113
|
-
if (json) {
|
|
114
|
-
console.log(JSON.stringify(plan, null, 2));
|
|
115
|
-
return { ok: true };
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (parsed?.approach) {
|
|
119
|
-
console.log(formatPlan(parsed));
|
|
120
|
-
} else if (plan.approach) {
|
|
121
|
-
console.log(plan.approach);
|
|
122
|
-
}
|
|
123
|
-
console.log(`\n## HUs (${plan.hus.length})`);
|
|
124
|
-
console.log(formatHuTable(plan.hus));
|
|
125
|
-
console.log(`\nPlan saved: ${planId} (${plan.hus.length} HUs, status: ${plan.status})`);
|
|
126
|
-
console.log(`Review: kj plan show ${planId}`);
|
|
127
|
-
console.log(`Approve: kj plan ready ${planId}`);
|
|
128
|
-
console.log(`Execute: kj run --plan ${planId} "${task.slice(0, 40)}..."`);
|
|
129
|
-
return { ok: true };
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* kj plan list
|
|
134
|
-
*/
|
|
135
|
-
export async function planListCommand({ config }) {
|
|
136
|
-
const { listPlans } = await import("../plan/plan-store.js");
|
|
137
|
-
const projectDir = config.projectDir || process.cwd();
|
|
138
|
-
const plans = await listPlans(projectDir);
|
|
139
|
-
|
|
140
|
-
if (plans.length === 0) {
|
|
141
|
-
console.log("No plans found. Create one with: kj plan \"your task\"");
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
console.log(`Plans for ${projectDir}:\n`);
|
|
146
|
-
for (const p of plans) {
|
|
147
|
-
const husInfo = p.huCount > 0 ? ` (${p.huCount} HUs)` : "";
|
|
148
|
-
const status = p.status !== "draft" ? ` [${p.status}]` : "";
|
|
149
|
-
console.log(` ${p.planId} ${(p.name || p.task || "").slice(0, 50)}${husInfo}${status}`);
|
|
150
|
-
console.log(` Created: ${p.createdAt}`);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* kj plan show <planId>
|
|
156
|
-
*/
|
|
157
|
-
export async function planShowCommand({ config, planId }) {
|
|
158
|
-
const { loadPlan } = await import("../plan/plan-store.js");
|
|
159
|
-
const projectDir = config.projectDir || process.cwd();
|
|
160
|
-
const plan = await loadPlan(projectDir, planId);
|
|
161
|
-
|
|
162
|
-
if (!plan) {
|
|
163
|
-
console.error(`Plan not found: ${planId}`);
|
|
164
|
-
process.exitCode = 1;
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
console.log(`Plan: ${plan.planId}`);
|
|
169
|
-
console.log(`Task: ${plan.task}`);
|
|
170
|
-
console.log(`Name: ${plan.name || "—"}`);
|
|
171
|
-
console.log(`Status: ${plan.status}`);
|
|
172
|
-
console.log(`Version: ${plan.version}`);
|
|
173
|
-
console.log(`Created: ${plan.createdAt}`);
|
|
174
|
-
|
|
175
|
-
if (plan.approach) {
|
|
176
|
-
console.log(`\n## Approach\n${plan.approach}`);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (plan.hus?.length > 0) {
|
|
180
|
-
console.log(`\n## HUs (${plan.hus.length})`);
|
|
181
|
-
console.log(formatHuTable(plan.hus));
|
|
182
|
-
} else {
|
|
183
|
-
console.log("\nNo HUs defined. Add with: kj plan add-hu " + planId);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* kj plan ready <planId>
|
|
189
|
-
*/
|
|
190
|
-
export async function planReadyCommand({ config, planId }) {
|
|
191
|
-
const { loadPlan, savePlan } = await import("../plan/plan-store.js");
|
|
192
|
-
const { certifyAllHus } = await import("../plan/plan-hu-ops.js");
|
|
193
|
-
const { validatePlan } = await import("../plan/plan-schema.js");
|
|
194
|
-
const projectDir = config.projectDir || process.cwd();
|
|
195
|
-
|
|
196
|
-
const plan = await loadPlan(projectDir, planId);
|
|
197
|
-
if (!plan) { console.error(`Plan not found: ${planId}`); process.exitCode = 1; return; }
|
|
198
|
-
|
|
199
|
-
const validation = validatePlan(plan);
|
|
200
|
-
if (!validation.valid) {
|
|
201
|
-
console.error("Plan validation failed:");
|
|
202
|
-
for (const err of validation.errors) console.error(` - ${err}`);
|
|
203
|
-
process.exitCode = 1;
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (plan.hus.length === 0) {
|
|
208
|
-
console.error("Cannot approve a plan with 0 HUs.");
|
|
209
|
-
process.exitCode = 1;
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const count = certifyAllHus(plan);
|
|
214
|
-
await savePlan(projectDir, plan);
|
|
215
|
-
console.log(`✅ ${count} HUs certified. Plan status: ready`);
|
|
216
|
-
console.log(`Execute: kj run --plan ${planId} "${(plan.task || "").slice(0, 40)}..."`);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* kj plan validate <planId>
|
|
221
|
-
*/
|
|
222
|
-
export async function planValidateCommand({ config, planId }) {
|
|
223
|
-
const { loadPlan } = await import("../plan/plan-store.js");
|
|
224
|
-
const { validatePlan } = await import("../plan/plan-schema.js");
|
|
225
|
-
const projectDir = config.projectDir || process.cwd();
|
|
226
|
-
|
|
227
|
-
const plan = await loadPlan(projectDir, planId);
|
|
228
|
-
if (!plan) { console.error(`Plan not found: ${planId}`); process.exitCode = 1; return; }
|
|
229
|
-
|
|
230
|
-
const result = validatePlan(plan);
|
|
231
|
-
if (result.valid) {
|
|
232
|
-
console.log(`✅ Plan ${planId} is valid (${plan.hus.length} HUs, status: ${plan.status})`);
|
|
233
|
-
} else {
|
|
234
|
-
console.error(`❌ Plan ${planId} has ${result.errors.length} error(s):`);
|
|
235
|
-
for (const err of result.errors) console.error(` - ${err}`);
|
|
236
|
-
process.exitCode = 1;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* kj plan delete <planId>
|
|
242
|
-
*/
|
|
243
|
-
export async function planDeleteCommand({ config, planId }) {
|
|
244
|
-
const { deletePlan } = await import("../plan/plan-store.js");
|
|
245
|
-
const projectDir = config.projectDir || process.cwd();
|
|
246
|
-
const ok = await deletePlan(projectDir, planId);
|
|
247
|
-
console.log(ok ? `Deleted: ${planId}` : `Not found: ${planId}`);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* kj plan add-hu <planId> --title "..." --type sw --deps hu_xxx
|
|
252
|
-
*/
|
|
253
|
-
export async function planAddHuCommand({ config, planId, title, type, deps, scope }) {
|
|
254
|
-
const { loadPlan, savePlan } = await import("../plan/plan-store.js");
|
|
255
|
-
const { addHu } = await import("../plan/plan-hu-ops.js");
|
|
256
|
-
const projectDir = config.projectDir || process.cwd();
|
|
257
|
-
|
|
258
|
-
const plan = await loadPlan(projectDir, planId);
|
|
259
|
-
if (!plan) { console.error(`Plan not found: ${planId}`); process.exitCode = 1; return; }
|
|
260
|
-
|
|
261
|
-
const hu = addHu(plan, {
|
|
262
|
-
title: title || "New HU",
|
|
263
|
-
task_type: type || "sw",
|
|
264
|
-
scope: scope || null,
|
|
265
|
-
blocked_by: deps ? deps.split(",").map(d => d.trim()) : []
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
await savePlan(projectDir, plan);
|
|
269
|
-
console.log(`Added: ${hu.id} — ${hu.title}`);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* kj plan remove-hu <planId> <huId>
|
|
274
|
-
*/
|
|
275
|
-
export async function planRemoveHuCommand({ config, planId, huId }) {
|
|
276
|
-
const { loadPlan, savePlan } = await import("../plan/plan-store.js");
|
|
277
|
-
const { removeHu } = await import("../plan/plan-hu-ops.js");
|
|
278
|
-
const projectDir = config.projectDir || process.cwd();
|
|
279
|
-
|
|
280
|
-
const plan = await loadPlan(projectDir, planId);
|
|
281
|
-
if (!plan) { console.error(`Plan not found: ${planId}`); process.exitCode = 1; return; }
|
|
282
|
-
|
|
283
|
-
const ok = removeHu(plan, huId);
|
|
284
|
-
if (!ok) { console.error(`HU not found: ${huId}`); process.exitCode = 1; return; }
|
|
285
|
-
|
|
286
|
-
await savePlan(projectDir, plan);
|
|
287
|
-
console.log(`Removed: ${huId}`);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// Keep backward compat — old callers import planCommand
|
|
291
|
-
export const planCommand = planGenerateCommand;
|
|
1
|
+
// Thin shim. The plan command was split into one file per sub-command
|
|
2
|
+
// under `./plan/`. This file remains so existing callers that import
|
|
3
|
+
// `./commands/plan.js` (src/cli.js, tests) keep working without churn.
|
|
4
|
+
export {
|
|
5
|
+
planGenerateCommand,
|
|
6
|
+
planListCommand,
|
|
7
|
+
planShowCommand,
|
|
8
|
+
planReadyCommand,
|
|
9
|
+
planValidateCommand,
|
|
10
|
+
planDeleteCommand,
|
|
11
|
+
planAddHuCommand,
|
|
12
|
+
planRemoveHuCommand,
|
|
13
|
+
planCommand,
|
|
14
|
+
} from "./plan/index.js";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createAgent } from "../agents/index.js";
|
|
2
2
|
import { assertAgentsAvailable } from "../agents/availability.js";
|
|
3
3
|
import { resolveRole } from "../config.js";
|
|
4
|
+
import { createCliProgressReporter } from "../utils/cli-progress.js";
|
|
4
5
|
|
|
5
6
|
const SUBAGENT_PREAMBLE = [
|
|
6
7
|
"IMPORTANT: You are running as a Karajan sub-agent.",
|
|
@@ -29,8 +30,12 @@ export async function researcherCommand({ task, config, logger }) {
|
|
|
29
30
|
|
|
30
31
|
const agent = createAgent(researcherRole.provider, config, logger);
|
|
31
32
|
const prompt = buildResearchPrompt(task);
|
|
32
|
-
const
|
|
33
|
-
|
|
33
|
+
const progress = createCliProgressReporter({ role: "researcher" });
|
|
34
|
+
let result;
|
|
35
|
+
try {
|
|
36
|
+
result = await agent.runTask({ prompt, onOutput: progress.onOutput, role: "researcher" });
|
|
37
|
+
progress.finish(result.ok ? "done" : "failed");
|
|
38
|
+
} catch (err) { progress.finish("failed"); throw err; }
|
|
34
39
|
|
|
35
40
|
if (!result.ok) {
|
|
36
41
|
throw new Error(result.error || result.output || "Researcher failed");
|
package/src/commands/resume.js
CHANGED
|
@@ -4,8 +4,31 @@ import { resumeFlow } from "../orchestrator.js";
|
|
|
4
4
|
import { createActivityLog } from "../activity-log.js";
|
|
5
5
|
import { printEvent } from "../utils/display/event-handlers.js";
|
|
6
6
|
|
|
7
|
-
function createCliAskQuestion() {
|
|
7
|
+
function createCliAskQuestion(opts = {}) {
|
|
8
|
+
const { sessionId = null } = opts;
|
|
8
9
|
return async (question, context) => {
|
|
10
|
+
// Mirror the run.js logic: route to the board via the file-based
|
|
11
|
+
// bridge when there's no TTY; otherwise prompt the local terminal.
|
|
12
|
+
const stdinReadable = process.stdin && process.stdin.readable !== false;
|
|
13
|
+
const isInteractive = Boolean(process.stdin?.isTTY) && stdinReadable;
|
|
14
|
+
if (!isInteractive) {
|
|
15
|
+
const { askThroughBoard } = await import("../utils/board-prompt-bridge.js");
|
|
16
|
+
console.log(`\n\u2753 ${question}`);
|
|
17
|
+
if (context?.detail) {
|
|
18
|
+
console.log(` Context: ${JSON.stringify(context.detail, null, 2)}`);
|
|
19
|
+
}
|
|
20
|
+
console.log(
|
|
21
|
+
"\n[non-interactive] Routing the prompt to the HU Board.\n"
|
|
22
|
+
+ " Open http://localhost:4000 \u2014 a modal will appear asking for your answer."
|
|
23
|
+
);
|
|
24
|
+
try {
|
|
25
|
+
return await askThroughBoard({ sessionId, question, context });
|
|
26
|
+
} catch (err) {
|
|
27
|
+
console.log(`\n[prompt-bridge] ${err.message} \u2014 stopping the session.`);
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
9
32
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
10
33
|
return new Promise((resolve) => {
|
|
11
34
|
console.log(`\n\u2753 ${question}`);
|
package/src/commands/review.js
CHANGED
|
@@ -5,6 +5,7 @@ import { buildReviewerPrompt } from "../prompts/reviewer.js";
|
|
|
5
5
|
import { resolveRole } from "../config.js";
|
|
6
6
|
import { resolveReviewProfile } from "../review/profiles.js";
|
|
7
7
|
import { withCliRunLog } from "../utils/cli-run-log.js";
|
|
8
|
+
import { createCliProgressReporter } from "../utils/cli-progress.js";
|
|
8
9
|
|
|
9
10
|
export async function reviewCommand({ task, config, logger, baseRef }) {
|
|
10
11
|
return withCliRunLog("review", { projectDir: config?.projectDir, logger }, async ({ runLog }) => {
|
|
@@ -36,8 +37,12 @@ export async function reviewCommand({ task, config, logger, baseRef }) {
|
|
|
36
37
|
const { rules } = await resolveReviewProfile({ mode: config.review_mode, projectDir: process.cwd() });
|
|
37
38
|
|
|
38
39
|
const prompt = await buildReviewerPrompt({ task, diff, reviewRules: rules, mode: config.review_mode });
|
|
39
|
-
const
|
|
40
|
-
|
|
40
|
+
const progress = createCliProgressReporter({ role: "reviewer" });
|
|
41
|
+
let result;
|
|
42
|
+
try {
|
|
43
|
+
result = await reviewer.reviewTask({ prompt, onOutput: progress.onOutput, role: "reviewer" });
|
|
44
|
+
progress.finish(result.ok ? "done" : "failed");
|
|
45
|
+
} catch (err) { progress.finish("failed"); throw err; }
|
|
41
46
|
if (!result.ok) {
|
|
42
47
|
if (result.error) logger.error(result.error);
|
|
43
48
|
throw new Error(result.error || result.output || `Reviewer failed (exit ${result.exitCode})`);
|
package/src/commands/run.js
CHANGED
|
@@ -8,9 +8,39 @@ import { printHeader } from "../utils/display/header.js";
|
|
|
8
8
|
import { printEvent } from "../utils/display/event-handlers.js";
|
|
9
9
|
import { resolveRole } from "../config.js";
|
|
10
10
|
import { parseCardId } from "../planning-game/adapter.js";
|
|
11
|
+
import { confirmCwd } from "../utils/cwd-confirm.js";
|
|
11
12
|
|
|
12
|
-
function createCliAskQuestion() {
|
|
13
|
+
function createCliAskQuestion(opts = {}) {
|
|
14
|
+
const { sessionId = null } = opts;
|
|
13
15
|
return async (question, context) => {
|
|
16
|
+
// Two paths:
|
|
17
|
+
// - Interactive TTY: prompt via readline (the developer's terminal).
|
|
18
|
+
// - No TTY (board's \u25b6 Run plan with stdio=ignore, CI, etc.): publish
|
|
19
|
+
// the prompt through the file-based bridge so the HU Board can
|
|
20
|
+
// surface it as a modal. The runner blocks on the bridge until
|
|
21
|
+
// the user answers (or times out / kills the run). Pre-v2.7.5
|
|
22
|
+
// this path just bailed; now the question actually gets answered.
|
|
23
|
+
const stdinReadable = process.stdin && process.stdin.readable !== false;
|
|
24
|
+
const isInteractive = Boolean(process.stdin?.isTTY) && stdinReadable;
|
|
25
|
+
if (!isInteractive) {
|
|
26
|
+
const { askThroughBoard } = await import("../utils/board-prompt-bridge.js");
|
|
27
|
+
console.log(`\n\u2753 ${question}`);
|
|
28
|
+
if (context?.detail) {
|
|
29
|
+
console.log(` Context: ${JSON.stringify(context.detail, null, 2)}`);
|
|
30
|
+
}
|
|
31
|
+
console.log(
|
|
32
|
+
"\n[non-interactive] Routing the prompt to the HU Board.\n"
|
|
33
|
+
+ " Open http://localhost:4000 \u2014 a modal will appear asking for your answer.\n"
|
|
34
|
+
+ " This process is now waiting; closing the board does NOT cancel the run."
|
|
35
|
+
);
|
|
36
|
+
try {
|
|
37
|
+
return await askThroughBoard({ sessionId, question, context });
|
|
38
|
+
} catch (err) {
|
|
39
|
+
console.log(`\n[prompt-bridge] ${err.message} \u2014 stopping the session.`);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
14
44
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
15
45
|
return new Promise((resolve) => {
|
|
16
46
|
console.log(`\n\u2753 ${question}`);
|
|
@@ -30,6 +60,27 @@ function createCliAskQuestion() {
|
|
|
30
60
|
}
|
|
31
61
|
|
|
32
62
|
export async function runCommandHandler({ task, config, logger, flags }) {
|
|
63
|
+
// PR-F (cwd confirmation): when launching from an interactive
|
|
64
|
+
// terminal, surface the working directory so the user can abort if
|
|
65
|
+
// they typed `kj run` from the wrong place. The user's words:
|
|
66
|
+
// "todas las que lo lancé desde terminal te garantizo que estaba
|
|
67
|
+
// en la carpeta del proyecto … aún así preguntar por si acaso".
|
|
68
|
+
//
|
|
69
|
+
// Skip the prompt when:
|
|
70
|
+
// - --yes / -y is set (CI / scripted runs),
|
|
71
|
+
// - --json mode is on (machine-readable output, no TTY assumed),
|
|
72
|
+
// - stdin is not a TTY (piped, board-spawned subprocess, MCP).
|
|
73
|
+
const projectDir = config?.projectDir || process.cwd();
|
|
74
|
+
const canPrompt = process.stdin.isTTY && process.stdout.isTTY
|
|
75
|
+
&& !flags?.yes && !flags?.json;
|
|
76
|
+
if (canPrompt) {
|
|
77
|
+
const ok = await confirmCwd(projectDir);
|
|
78
|
+
if (!ok) {
|
|
79
|
+
console.log("Aborted by user.");
|
|
80
|
+
return { ok: false, aborted: true };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
33
84
|
// Best-effort session cleanup before starting
|
|
34
85
|
try {
|
|
35
86
|
const { cleanupExpiredSessions } = await import("../session-cleanup.js");
|
package/src/commands/status.js
CHANGED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `kj sync` — closed-loop drift detection between code and Canvas/plan.
|
|
3
|
+
* Issue #540 MVP: detection + report only. No --apply yet.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { listPlans, loadPlan } from "../plan/plan-store.js";
|
|
9
|
+
import { classifyDrift, formatDriftReport } from "../sync/detect-drift.js";
|
|
10
|
+
|
|
11
|
+
const SKIP_DIRS = new Set(["node_modules", ".git", "dist", "build", "coverage", ".karajan", ".kj", "tmp", ".cache", ".astro", ".next"]);
|
|
12
|
+
|
|
13
|
+
function listSourceFiles(rootDir, out = []) {
|
|
14
|
+
for (const e of fs.readdirSync(rootDir, { withFileTypes: true })) {
|
|
15
|
+
if (e.name.startsWith(".") && !["src", "tests"].includes(e.name)) continue;
|
|
16
|
+
if (SKIP_DIRS.has(e.name)) continue;
|
|
17
|
+
const p = path.join(rootDir, e.name);
|
|
18
|
+
if (e.isDirectory()) {
|
|
19
|
+
listSourceFiles(p, out);
|
|
20
|
+
} else if (e.isFile()) {
|
|
21
|
+
try {
|
|
22
|
+
const s = fs.statSync(p);
|
|
23
|
+
out.push({
|
|
24
|
+
path: path.relative(rootDir, p),
|
|
25
|
+
mtime: s.mtime,
|
|
26
|
+
});
|
|
27
|
+
} catch { /* ignore stat errors */ }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {object} args
|
|
35
|
+
* @param {object} args.config
|
|
36
|
+
* @param {object} args.logger
|
|
37
|
+
* @param {string} [args.planId] — when set, sync against this plan; else use the latest
|
|
38
|
+
* @param {boolean} [args.json]
|
|
39
|
+
*/
|
|
40
|
+
export async function syncCommand({ config, logger, planId, json }) {
|
|
41
|
+
const projectDir = config?.projectDir || process.cwd();
|
|
42
|
+
|
|
43
|
+
// Resolve which plan to sync against.
|
|
44
|
+
let plan;
|
|
45
|
+
if (planId) {
|
|
46
|
+
plan = await loadPlan(projectDir, planId);
|
|
47
|
+
if (!plan) throw new Error(`kj sync: plan not found: ${planId}`);
|
|
48
|
+
} else {
|
|
49
|
+
const all = await listPlans(projectDir);
|
|
50
|
+
if (!all || all.length === 0) {
|
|
51
|
+
throw new Error("kj sync: no plans found for this project. Generate one with `kj plan generate --task-file SPEC.md` (or `--canvas SPEC.canvas.md` for the structured form).");
|
|
52
|
+
}
|
|
53
|
+
// Pick the most recent plan by createdAt.
|
|
54
|
+
plan = await loadPlan(projectDir, all.sort((a, b) =>
|
|
55
|
+
new Date(b.createdAt || 0) - new Date(a.createdAt || 0))[0].planId);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
logger.info(`kj sync: comparing ${projectDir} against plan ${plan.planId}`);
|
|
59
|
+
|
|
60
|
+
const files = listSourceFiles(projectDir);
|
|
61
|
+
const report = classifyDrift(plan, files);
|
|
62
|
+
|
|
63
|
+
if (json) {
|
|
64
|
+
console.log(JSON.stringify({
|
|
65
|
+
planId: plan.planId,
|
|
66
|
+
projectDir,
|
|
67
|
+
...report,
|
|
68
|
+
}, null, 2));
|
|
69
|
+
} else {
|
|
70
|
+
console.log(formatDriftReport(report, plan));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Exit 0 always — drift is informational, not an error. CI users
|
|
74
|
+
// can opt-in to "exit non-zero on drift" later via a flag.
|
|
75
|
+
}
|
package/src/commands/triage.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createAgent } from "../agents/index.js";
|
|
2
2
|
import { assertAgentsAvailable } from "../agents/availability.js";
|
|
3
3
|
import { resolveRole } from "../config.js";
|
|
4
|
+
import { createCliProgressReporter } from "../utils/cli-progress.js";
|
|
4
5
|
import { buildTriagePrompt } from "../prompts/triage.js";
|
|
5
6
|
import { parseMaybeJsonString } from "../review/parser.js";
|
|
6
7
|
|
|
@@ -43,8 +44,12 @@ export async function triageCommand({ task, config, logger, json }) {
|
|
|
43
44
|
|
|
44
45
|
const agent = createAgent(triageRole.provider, config, logger);
|
|
45
46
|
const prompt = buildTriagePrompt({ task });
|
|
46
|
-
const
|
|
47
|
-
|
|
47
|
+
const progress = createCliProgressReporter({ role: "triage" });
|
|
48
|
+
let result;
|
|
49
|
+
try {
|
|
50
|
+
result = await agent.runTask({ prompt, onOutput: progress.onOutput, role: "triage" });
|
|
51
|
+
progress.finish(result.ok ? "done" : "failed");
|
|
52
|
+
} catch (err) { progress.finish("failed"); throw err; }
|
|
48
53
|
|
|
49
54
|
if (!result.ok) {
|
|
50
55
|
throw new Error(result.error || result.output || "Triage failed");
|
package/src/commands/undo.js
CHANGED