@deksden-com/dd-flow-cli 0.2.0 → 0.3.1
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/CHANGELOG.md +43 -0
- package/README.md +76 -5
- package/dist/build-info.json +10 -6
- package/dist/cli/help.js +165 -20
- package/dist/cli/run-cli.js +427 -17
- package/dist/schemas/compatibility.schema.json +105 -0
- package/dist/schemas/engine-manifest.schema.json +61 -0
- package/dist/schemas/flow-guidance.schema.json +90 -0
- package/dist/schemas/flow-run-index.schema.json +30 -4
- package/dist/schemas/global-dashboard-data.schema.json +126 -0
- package/dist/schemas/mb-sdlc-review-report.schema.json +242 -0
- package/dist/schemas/mb-upgrade-migration-report.schema.json +93 -0
- package/dist/schemas/plan-stage-report.schema.json +83 -0
- package/dist/schemas/project-dashboard-data.schema.json +122 -0
- package/dist/schemas/project-flow-pack-manifest.schema.json +5 -1
- package/dist/schemas/project-summary.schema.json +73 -0
- package/dist/schemas/protocol-dashboard-data.schema.json +112 -0
- package/dist/schemas/status-report.schema.json +38 -2
- package/dist/schemas/version-report.schema.json +22 -0
- package/dist/services/build-info.js +26 -3
- package/dist/services/canon.js +93 -22
- package/dist/services/cleanup.js +45 -1
- package/dist/services/cli-operation-classifier.js +104 -0
- package/dist/services/compatibility-preflight.js +124 -0
- package/dist/services/config.js +31 -0
- package/dist/services/dashboard-targets.js +95 -0
- package/dist/services/dashboard.js +972 -10
- package/dist/services/engines.js +532 -0
- package/dist/services/flow-guidance.js +221 -0
- package/dist/services/hooks.js +1 -1
- package/dist/services/ids.js +106 -0
- package/dist/services/lanes.js +333 -1
- package/dist/services/merge-queue.js +106 -16
- package/dist/services/merge-worker.js +67 -4
- package/dist/services/migrations.js +231 -0
- package/dist/services/project-summary.js +122 -0
- package/dist/services/projects.js +44 -3
- package/dist/services/protocol-lifecycle.js +144 -0
- package/dist/services/protocols.js +660 -7
- package/dist/services/runs.js +98 -21
- package/dist/services/schema-validation.js +84 -4
- package/dist/services/sessions.js +21 -4
- package/dist/services/status.js +199 -1
- package/dist/services/version-status.js +59 -9
- package/dist/storage/database.js +31 -0
- package/dist/storage/paths.js +33 -0
- package/package.json +3 -2
package/dist/cli/run-cli.js
CHANGED
|
@@ -3,24 +3,31 @@ import { helpForArgs } from "./help.js";
|
|
|
3
3
|
import { AppError, isAppError } from "../shared/errors.js";
|
|
4
4
|
import { writeJson } from "../shared/json.js";
|
|
5
5
|
import { archiveProject, getProjectStatus, migrateProjectIds, registerProject, resolveProject } from "../services/projects.js";
|
|
6
|
-
import { cancelProtocol, getProtocolStatus, readyForMerge, registerProtocol, requireProtocol, transitionProtocol } from "../services/protocols.js";
|
|
6
|
+
import { cancelProtocol, getProtocolBlockers, getProtocolStatus, getReadyProtocols, implementProtocol, readyForMerge, registerProtocol, requireProtocol, syncProtocolFromRun, transitionProtocol } from "../services/protocols.js";
|
|
7
7
|
import { blockPlanItem, completePlanItem, getPlanStatus, setProtocolPlan, skipPlanItem, startPlanItem } from "../services/plans.js";
|
|
8
8
|
import { cancelMergeQueueJob, claimNextMergeJob, completeMergeJob, failMergeJob, getMergeQueueStatus, noteMergeJob, waitNextMergeJob } from "../services/merge-queue.js";
|
|
9
9
|
import { getMergeWorkerStatus, oneShotMergeClaim, startMergeWorker, stopProjectMergeWorker } from "../services/merge-worker.js";
|
|
10
10
|
import { cleanupApply, cleanupScan } from "../services/cleanup.js";
|
|
11
11
|
import { getCodexHomeStatus, getCodexHooksStatus, handleCodexHook, initCodexHome, installCodexHooks, planCodexHome, printCodexHomeEnv, printCodexHooks, removeCodexHome, removeCodexHooks } from "../services/hooks.js";
|
|
12
12
|
import { bootstrapWorktree, closeWorktree, createWorktreeRecord, getWorktreeStatus, planWorktree } from "../services/worktrees.js";
|
|
13
|
-
import { acquireLaneLock, checkLaneWorkspace, getLaneStatus, heartbeatLaneLock, releaseLaneLock, setLaneWorkspace, waitForLaneLock } from "../services/lanes.js";
|
|
13
|
+
import { acquireLaneLock, cancelLaneWaiter, checkLaneWorkspace, getLaneWaiters, getLaneStatus, heartbeatLaneLock, releaseLaneLock, setLaneWorkspace, waitAcquireLaneLock, waitForLaneLock } from "../services/lanes.js";
|
|
14
14
|
import { getProjectConfigStatus, setProjectConfigValue } from "../services/config.js";
|
|
15
15
|
import { getCanonStatus, registerCanonRoot, resolveCanonRoot } from "../services/canon.js";
|
|
16
16
|
import { getRuntimeStatus } from "../services/status.js";
|
|
17
|
-
import {
|
|
17
|
+
import { getCliVersionReport } from "../services/build-info.js";
|
|
18
|
+
import { autoRefreshDashboards, dashboardData, getCmuxStatus, openDashboard, openGlobalDashboard, openProtocolDashboard, refreshAllDashboards, refreshDashboard, refreshGlobalDashboard, renderDashboard, renderGlobalDashboard } from "../services/dashboard.js";
|
|
19
|
+
import { dashboardTargetSummary, preferredDashboardCommand, relatedDashboardCommands, resolveDashboardTarget } from "../services/dashboard-targets.js";
|
|
18
20
|
import { getFlowSessionStatus, registerFlowSession, stopFlowSession, stopMergeWorker } from "../services/sessions.js";
|
|
19
21
|
import { attachFlowRunStage, completeFlowRun, completeFlowRunStage, getFlowRunStatus, listFlowRuns, startFlowRun } from "../services/runs.js";
|
|
22
|
+
import { previewNextEntityId } from "../services/ids.js";
|
|
20
23
|
import { validateSchema } from "../services/schema-validation.js";
|
|
21
24
|
import { preflightMemoryPermissions } from "../services/memory-permissions.js";
|
|
25
|
+
import { planMigration, verifyMigrationReport } from "../services/migrations.js";
|
|
26
|
+
import { publishProjectSummary } from "../services/project-summary.js";
|
|
22
27
|
import { requireProjectByRoot } from "../services/projects.js";
|
|
23
28
|
import { resolveProjectRoot } from "../storage/paths.js";
|
|
29
|
+
import { doctorEngines, engineInfo, installCurrentEngine, listEngines, resolveEngine, routeArgsThroughEngine } from "../services/engines.js";
|
|
30
|
+
import { preflightCliCompatibility } from "../services/compatibility-preflight.js";
|
|
24
31
|
const defaultIo = {
|
|
25
32
|
stdout: process.stdout,
|
|
26
33
|
stderr: process.stderr,
|
|
@@ -34,6 +41,14 @@ export async function runCli(args, io = defaultIo, env = process.env) {
|
|
|
34
41
|
if (args.includes("--progress-jsonl")) {
|
|
35
42
|
throw new AppError("usage", "--progress-jsonl is not implemented yet; use --json for final structured progress", 2);
|
|
36
43
|
}
|
|
44
|
+
if (output.args.length === 1 && output.args[0] === "--version") {
|
|
45
|
+
const payload = getCliVersionReport();
|
|
46
|
+
if (output.json)
|
|
47
|
+
writeJson(io.stdout, payload);
|
|
48
|
+
else
|
|
49
|
+
io.stdout.write(`dd-flow ${payload.cli.version}\n`);
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
37
52
|
const help = helpForArgs(output.args);
|
|
38
53
|
if (help) {
|
|
39
54
|
io.stdout.write(`${help}\n`);
|
|
@@ -41,7 +56,16 @@ export async function runCli(args, io = defaultIo, env = process.env) {
|
|
|
41
56
|
}
|
|
42
57
|
const context = createContext(env);
|
|
43
58
|
emitHumanProgress(io, output, progress, "start");
|
|
44
|
-
const
|
|
59
|
+
const routed = await routeArgsThroughEngine(context, output.args, io, io.stdin, env);
|
|
60
|
+
if (routed?.routed && !routed.inProcess) {
|
|
61
|
+
emitHumanProgress(io, output, progress, "done");
|
|
62
|
+
return routed.exitCode;
|
|
63
|
+
}
|
|
64
|
+
const dispatchContext = routed?.inProcess
|
|
65
|
+
? createContext({ ...env, DD_FLOW_ENGINE_MODE: "1", DD_FLOW_ENGINE_HOME: "in-process", DD_FLOW_ROUTED_FROM: getCliVersionReport().cli.version })
|
|
66
|
+
: context;
|
|
67
|
+
preflightCliCompatibility(dispatchContext, output.args, env);
|
|
68
|
+
const result = await dispatch(output.args, dispatchContext, io);
|
|
45
69
|
refreshDashboardsAfterMutation(context, output.args, result);
|
|
46
70
|
emitHumanProgress(io, output, progress, "done");
|
|
47
71
|
const payload = attachProgress(result, progress, output);
|
|
@@ -60,10 +84,34 @@ export async function runCli(args, io = defaultIo, env = process.env) {
|
|
|
60
84
|
if (output.json)
|
|
61
85
|
writeJson(io.stderr, errorPayload);
|
|
62
86
|
else
|
|
63
|
-
io.stderr.write(
|
|
87
|
+
io.stderr.write(renderHumanError(payload));
|
|
64
88
|
return isAppError(error) ? error.exitCode : 1;
|
|
65
89
|
}
|
|
66
90
|
}
|
|
91
|
+
function renderHumanError(payload) {
|
|
92
|
+
const lines = [`dd-flow: ${payload.error.message}`];
|
|
93
|
+
const details = recordObject(payload.error.details);
|
|
94
|
+
const compatibility = recordObject(details?.compatibility);
|
|
95
|
+
const remediation = recordObject(details?.remediation);
|
|
96
|
+
if (compatibility) {
|
|
97
|
+
lines.push(`Compatibility: ${String(compatibility.verdict ?? "unknown")} (${String(compatibility.engine_resolution ?? "unknown")})`);
|
|
98
|
+
if (compatibility.blocked_operation)
|
|
99
|
+
lines.push(`Blocked operation: ${String(compatibility.blocked_operation)}`);
|
|
100
|
+
if (compatibility.install_hint)
|
|
101
|
+
lines.push(`Install compatible engine: ${String(compatibility.install_hint)}`);
|
|
102
|
+
}
|
|
103
|
+
if (remediation?.mb_upgrade_mode)
|
|
104
|
+
lines.push(`Upgrade mode: ${String(remediation.mb_upgrade_mode)}`);
|
|
105
|
+
if (details?.preferred_command)
|
|
106
|
+
lines.push(`Preferred command: ${String(details.preferred_command)}`);
|
|
107
|
+
const related = Array.isArray(details?.related_commands) ? details.related_commands.map(String) : [];
|
|
108
|
+
if (related.length > 0) {
|
|
109
|
+
lines.push("", "Related commands:");
|
|
110
|
+
for (const command of related)
|
|
111
|
+
lines.push(` ${command}`);
|
|
112
|
+
}
|
|
113
|
+
return `${lines.join("\n")}\n`;
|
|
114
|
+
}
|
|
67
115
|
function parseOutputOptions(args) {
|
|
68
116
|
const stripped = [];
|
|
69
117
|
let json = false;
|
|
@@ -86,7 +134,7 @@ function progressForCommand(args) {
|
|
|
86
134
|
{ phase: "done", message: "Merge queue wait finished", at: "" }
|
|
87
135
|
];
|
|
88
136
|
}
|
|
89
|
-
if (family === "lane" && command === "lock" && subcommand
|
|
137
|
+
if (family === "lane" && command === "lock" && ["wait", "wait-acquire"].includes(subcommand ?? "")) {
|
|
90
138
|
return [
|
|
91
139
|
{ phase: "start", message: "Starting lane lock wait", at: "" },
|
|
92
140
|
{ phase: "running", message: "Waiting for lane lock", at: "" },
|
|
@@ -152,6 +200,18 @@ function renderHumanResult(args, result) {
|
|
|
152
200
|
if (family === "status") {
|
|
153
201
|
return renderStatusHuman(record);
|
|
154
202
|
}
|
|
203
|
+
if (family === "version") {
|
|
204
|
+
const cli = recordObject(record.cli);
|
|
205
|
+
const engine = recordObject(record.engine);
|
|
206
|
+
const engineSuffix = engine ? ` (engine: ${String(engine.mode ?? "unknown")})` : "";
|
|
207
|
+
return `dd-flow ${stringValue(cli?.version) ?? "unknown"}${engineSuffix}\n`;
|
|
208
|
+
}
|
|
209
|
+
if (family === "engine") {
|
|
210
|
+
return renderEngineHuman(args, record);
|
|
211
|
+
}
|
|
212
|
+
if (family === "dashboard") {
|
|
213
|
+
return renderDashboardHuman(args, record);
|
|
214
|
+
}
|
|
155
215
|
const lines = [`dd-flow ${family ?? ""}${command ? ` ${command}` : ""}: ${ok}`.trim()];
|
|
156
216
|
const project = record.project && typeof record.project === "object" ? record.project : null;
|
|
157
217
|
const protocol = record.protocol && typeof record.protocol === "object" ? record.protocol : null;
|
|
@@ -176,6 +236,7 @@ function renderHumanResult(args, result) {
|
|
|
176
236
|
if (run.run_index_path)
|
|
177
237
|
lines.push(`run_index: ${String(run.run_index_path)}`);
|
|
178
238
|
}
|
|
239
|
+
appendFlowGuidanceHuman(lines, record);
|
|
179
240
|
return `${lines.join("\n")}\n`;
|
|
180
241
|
}
|
|
181
242
|
function renderStatusHuman(record) {
|
|
@@ -183,6 +244,8 @@ function renderStatusHuman(record) {
|
|
|
183
244
|
const cli = recordObject(record.cli);
|
|
184
245
|
const cliBuild = recordObject(cli?.build);
|
|
185
246
|
const builtWithCanon = recordObject(cliBuild?.built_with_canon);
|
|
247
|
+
const cliCompatibility = recordObject(cli?.compatibility);
|
|
248
|
+
const registry = recordObject(cli?.registry);
|
|
186
249
|
const canon = recordObject(record.canon);
|
|
187
250
|
const resolvedCanon = recordObject(canon?.resolved);
|
|
188
251
|
const project = recordObject(record.project);
|
|
@@ -190,16 +253,105 @@ function renderStatusHuman(record) {
|
|
|
190
253
|
const flowPack = recordObject(project?.flow_pack);
|
|
191
254
|
const drift = recordObject(project?.drift);
|
|
192
255
|
lines.push(`CLI package: ${stringValue(cli?.package_name) ?? "unknown"} ${stringValue(cli?.version) ?? "unknown"}`);
|
|
256
|
+
if (cliCompatibility) {
|
|
257
|
+
lines.push(`CLI compatibility: ${stringValue(cliCompatibility.verdict) ?? "unknown"} (${stringValue(cliCompatibility.reason) ?? "no_reason"})`);
|
|
258
|
+
if (cliCompatibility.update_command)
|
|
259
|
+
lines.push(`CLI update: ${String(cliCompatibility.update_command)}`);
|
|
260
|
+
}
|
|
261
|
+
if (registry) {
|
|
262
|
+
lines.push(`CLI registry: ${stringValue(registry.latest) ?? "unknown"} (${stringValue(registry.status) ?? "unknown"})`);
|
|
263
|
+
}
|
|
193
264
|
lines.push(`CLI tested with canon: ${stringValue(builtWithCanon?.version) ?? "unknown"} @ ${shortSha(stringValue(builtWithCanon?.commit))}`);
|
|
194
|
-
lines.push(`Active local canon: ${stringValue(resolvedCanon?.root) ?? "unresolved"} ${stringValue(resolvedCanon?.version) ?? "unknown"} @ ${shortSha(stringValue(resolvedCanon?.commit))}`);
|
|
265
|
+
lines.push(`Active local canon: ${stringValue(resolvedCanon?.root) ?? "unresolved"} ${stringValue(resolvedCanon?.version) ?? "unknown"} @ ${shortSha(stringValue(resolvedCanon?.commit))} (${stringValue(resolvedCanon?.layout) ?? "unknown"})`);
|
|
266
|
+
if (resolvedCanon?.memorybank_root)
|
|
267
|
+
lines.push(`Canon Memory Bank: ${String(resolvedCanon.memorybank_root)}`);
|
|
268
|
+
if (resolvedCanon?.flow_root)
|
|
269
|
+
lines.push(`Canon flow root: ${String(resolvedCanon.flow_root)}`);
|
|
195
270
|
lines.push(`Project: ${stringValue(project?.root) ?? "unknown"}${project?.registered === true ? " (registered)" : " (unregistered)"}`);
|
|
196
271
|
lines.push(`Project Memory Bank: ${stringValue(memoryBank?.version) ?? stringValue(memoryBank?.status) ?? "unknown"}`);
|
|
197
272
|
lines.push(`Project flow pack: ${stringValue(flowPack?.pack_version) ?? stringValue(flowPack?.status) ?? "unknown"} @ ${shortSha(stringValue(flowPack?.source_commit))}`);
|
|
198
273
|
lines.push(`Drift: ${stringValue(drift?.overall) ?? "unknown"}`);
|
|
199
274
|
if (drift?.next_action)
|
|
200
275
|
lines.push(`Next action: ${String(drift.next_action)}`);
|
|
276
|
+
appendFlowGuidanceHuman(lines, record);
|
|
201
277
|
return `${lines.join("\n")}\n`;
|
|
202
278
|
}
|
|
279
|
+
function renderDashboardHuman(args, record) {
|
|
280
|
+
const [, command] = args;
|
|
281
|
+
const ok = record.ok === false ? "failed" : "ok";
|
|
282
|
+
const target = recordObject(record.target);
|
|
283
|
+
const targetLabel = stringValue(target?.kind) ?? "unknown";
|
|
284
|
+
const lines = [`dd-flow dashboard ${command ?? ""}: ${ok}`.trim()];
|
|
285
|
+
lines.push(`target: ${targetLabel}`);
|
|
286
|
+
if (target?.project_id)
|
|
287
|
+
lines.push(`project: ${String(target.project_id)}`);
|
|
288
|
+
if (target?.protocol_id)
|
|
289
|
+
lines.push(`protocol: ${String(target.protocol_id)}`);
|
|
290
|
+
const dashboard = recordObject(record.dashboard);
|
|
291
|
+
if (dashboard?.path)
|
|
292
|
+
lines.push(`dashboard: ${String(dashboard.path)}`);
|
|
293
|
+
if (record.compatibility_alias)
|
|
294
|
+
lines.push(`compatibility_alias: true`);
|
|
295
|
+
if (record.preferred_command)
|
|
296
|
+
lines.push(`Preferred command: ${String(record.preferred_command)}`);
|
|
297
|
+
const related = Array.isArray(record.related_commands) ? record.related_commands.map(String) : [];
|
|
298
|
+
if (related.length > 0) {
|
|
299
|
+
lines.push("", "Related dashboard commands:");
|
|
300
|
+
for (const item of related)
|
|
301
|
+
lines.push(` ${item}`);
|
|
302
|
+
}
|
|
303
|
+
return `${lines.join("\n")}\n`;
|
|
304
|
+
}
|
|
305
|
+
function renderEngineHuman(args, record) {
|
|
306
|
+
const [, command] = args;
|
|
307
|
+
const lines = [`dd-flow engine ${command ?? ""}: ${record.ok === false ? "failed" : "ok"}`.trim()];
|
|
308
|
+
if (record.engine_store)
|
|
309
|
+
lines.push(`engine_store: ${String(record.engine_store)}`);
|
|
310
|
+
const engine = recordObject(record.engine);
|
|
311
|
+
if (engine) {
|
|
312
|
+
lines.push(`engine: ${String(engine.package_name ?? "unknown")}@${String(engine.package_version ?? "unknown")}`);
|
|
313
|
+
if (engine.entrypoint)
|
|
314
|
+
lines.push(`entrypoint: ${String(engine.entrypoint)}`);
|
|
315
|
+
if (engine.healthy !== undefined)
|
|
316
|
+
lines.push(`healthy: ${String(engine.healthy)}`);
|
|
317
|
+
}
|
|
318
|
+
const engines = Array.isArray(record.engines) ? record.engines : [];
|
|
319
|
+
if (engines.length > 0)
|
|
320
|
+
lines.push(`engines: ${engines.length}`);
|
|
321
|
+
const selection = recordObject(record.selection);
|
|
322
|
+
if (selection) {
|
|
323
|
+
lines.push(`selection: ${String(selection.status ?? "unknown")}`);
|
|
324
|
+
if (selection.selected && typeof selection.selected === "object") {
|
|
325
|
+
const selected = selection.selected;
|
|
326
|
+
lines.push(`selected: ${String(selected.package_name ?? "unknown")}@${String(selected.package_version ?? "unknown")}`);
|
|
327
|
+
}
|
|
328
|
+
if (selection.install_hint)
|
|
329
|
+
lines.push(`install_hint: ${String(selection.install_hint)}`);
|
|
330
|
+
}
|
|
331
|
+
const checks = Array.isArray(record.checks) ? record.checks : [];
|
|
332
|
+
if (checks.length > 0)
|
|
333
|
+
lines.push(`checks: ${checks.length}`);
|
|
334
|
+
return `${lines.join("\n")}\n`;
|
|
335
|
+
}
|
|
336
|
+
function appendFlowGuidanceHuman(lines, record) {
|
|
337
|
+
const guidance = recordObject(record.flow_guidance);
|
|
338
|
+
if (!guidance)
|
|
339
|
+
return;
|
|
340
|
+
const currentStage = stringValue(guidance.current_stage);
|
|
341
|
+
const nextAction = stringValue(guidance.recommended_next_action);
|
|
342
|
+
const prompt = stringValue(guidance.recommended_prompt);
|
|
343
|
+
if (currentStage)
|
|
344
|
+
lines.push(`stage: ${currentStage}`);
|
|
345
|
+
if (nextAction)
|
|
346
|
+
lines.push(`next: ${nextAction}${prompt && prompt !== "none" ? ` (${prompt})` : ""}`);
|
|
347
|
+
const guards = Array.isArray(guidance.guards) ? guidance.guards : [];
|
|
348
|
+
const firstGuard = guards.find((item) => item && typeof item === "object" && !Array.isArray(item));
|
|
349
|
+
if (firstGuard?.id)
|
|
350
|
+
lines.push(`guard: ${String(firstGuard.id)} ${String(firstGuard.status ?? "unknown")}`);
|
|
351
|
+
const missing = Array.isArray(guidance.blocked_if_missing) ? guidance.blocked_if_missing : [];
|
|
352
|
+
if (missing.length > 0)
|
|
353
|
+
lines.push(`missing: ${missing.map(String).join(", ")}`);
|
|
354
|
+
}
|
|
203
355
|
function recordObject(value) {
|
|
204
356
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
205
357
|
}
|
|
@@ -212,9 +364,18 @@ function shortSha(value) {
|
|
|
212
364
|
async function dispatch(args, context, io) {
|
|
213
365
|
const [family, command, ...rest] = args;
|
|
214
366
|
const parsed = parseArgs(rest);
|
|
367
|
+
if (family === "engine") {
|
|
368
|
+
return dispatchEngine(context, command, parsed);
|
|
369
|
+
}
|
|
215
370
|
if (family === "status") {
|
|
216
371
|
const rootParsed = parseArgs([command ?? "", ...rest]);
|
|
217
|
-
return getRuntimeStatus(context, {
|
|
372
|
+
return getRuntimeStatus(context, {
|
|
373
|
+
projectRoot: optionalOption(rootParsed, "project-root") ?? optionalOption(rootParsed, "root"),
|
|
374
|
+
checkRegistry: hasOption(rootParsed, "check-registry")
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
if (family === "version") {
|
|
378
|
+
return getCliVersionReport();
|
|
218
379
|
}
|
|
219
380
|
if (family === "canon") {
|
|
220
381
|
return dispatchCanon(context, command, parsed);
|
|
@@ -225,6 +386,12 @@ async function dispatch(args, context, io) {
|
|
|
225
386
|
if (family === "project" && command === "status") {
|
|
226
387
|
return getProjectStatus(context, { root: requiredOption(parsed, "root") });
|
|
227
388
|
}
|
|
389
|
+
if (family === "project" && command === "summary") {
|
|
390
|
+
return publishProjectSummary(context, {
|
|
391
|
+
project: requireProjectByRoot(context, resolveProjectRoot(requiredOption(parsed, "project-root"))),
|
|
392
|
+
write: parseOptionalBoolean(optionalOption(parsed, "write"), "write") ?? true
|
|
393
|
+
});
|
|
394
|
+
}
|
|
228
395
|
if (family === "project" && command === "resolve") {
|
|
229
396
|
return resolveProject(context, { idOrAlias: requiredPosition(parsed, 0, "id-or-alias") });
|
|
230
397
|
}
|
|
@@ -255,9 +422,44 @@ async function dispatch(args, context, io) {
|
|
|
255
422
|
if (family === "protocol" && command === "status") {
|
|
256
423
|
return getProtocolStatus(context, { protocolId: requiredPosition(parsed, 0, "protocol-id") });
|
|
257
424
|
}
|
|
425
|
+
if (family === "protocol" && command === "ready") {
|
|
426
|
+
return getReadyProtocols(context, { projectRoot: requiredOption(parsed, "project-root") });
|
|
427
|
+
}
|
|
428
|
+
if (family === "protocol" && command === "blockers") {
|
|
429
|
+
return getProtocolBlockers(context, {
|
|
430
|
+
protocolId: requiredPosition(parsed, 0, "protocol-id"),
|
|
431
|
+
projectRoot: requiredOption(parsed, "project-root")
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
if (family === "protocol" && command === "implement") {
|
|
435
|
+
const reason = optionalOption(parsed, "reason");
|
|
436
|
+
return implementProtocol(context, {
|
|
437
|
+
protocolId: requiredPosition(parsed, 0, "protocol-id"),
|
|
438
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
439
|
+
force: hasOption(parsed, "force"),
|
|
440
|
+
...(reason ? { reason } : {})
|
|
441
|
+
});
|
|
442
|
+
}
|
|
258
443
|
if (family === "protocol" && command === "ready-for-merge") {
|
|
259
444
|
return readyForMerge(context, { protocolId: requiredPosition(parsed, 0, "protocol-id") });
|
|
260
445
|
}
|
|
446
|
+
if (family === "protocol" && command === "transition") {
|
|
447
|
+
const reason = optionalOption(parsed, "reason");
|
|
448
|
+
return transitionProtocol(context, {
|
|
449
|
+
protocolId: requiredPosition(parsed, 0, "protocol-id"),
|
|
450
|
+
to: requiredOption(parsed, "to"),
|
|
451
|
+
jsonFile: requiredOptionAlias(parsed, ["payload-file", "json-file"]),
|
|
452
|
+
force: hasOption(parsed, "force"),
|
|
453
|
+
...(reason ? { reason } : {})
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
if (family === "protocol" && command === "sync-from-run") {
|
|
457
|
+
return syncProtocolFromRun(context, {
|
|
458
|
+
protocolId: requiredPosition(parsed, 0, "protocol-id"),
|
|
459
|
+
runId: requiredOption(parsed, "run"),
|
|
460
|
+
target: optionalOption(parsed, "target") ?? "auto"
|
|
461
|
+
});
|
|
462
|
+
}
|
|
261
463
|
if (family === "protocol" && command === "cancel") {
|
|
262
464
|
return cancelProtocol(context, {
|
|
263
465
|
protocolId: requiredPosition(parsed, 0, "protocol-id"),
|
|
@@ -275,7 +477,7 @@ async function dispatch(args, context, io) {
|
|
|
275
477
|
return transitionProtocol(context, {
|
|
276
478
|
protocolId: requiredPosition(transitionParsed, 0, "protocol-id"),
|
|
277
479
|
to: requiredOption(transitionParsed, "to"),
|
|
278
|
-
jsonFile:
|
|
480
|
+
jsonFile: requiredOptionAlias(transitionParsed, ["payload-file", "json-file"]),
|
|
279
481
|
force: hasOption(transitionParsed, "force"),
|
|
280
482
|
...(reason ? { reason } : {})
|
|
281
483
|
});
|
|
@@ -313,6 +515,9 @@ async function dispatch(args, context, io) {
|
|
|
313
515
|
if (family === "run") {
|
|
314
516
|
return dispatchRun(context, command, parsed);
|
|
315
517
|
}
|
|
518
|
+
if (family === "id") {
|
|
519
|
+
return dispatchId(context, command, parsed);
|
|
520
|
+
}
|
|
316
521
|
if (family === "integration" && command === "cmux") {
|
|
317
522
|
return dispatchCmux(context, parsed);
|
|
318
523
|
}
|
|
@@ -325,6 +530,9 @@ async function dispatch(args, context, io) {
|
|
|
325
530
|
if (family === "memory") {
|
|
326
531
|
return dispatchMemory(command, parsed);
|
|
327
532
|
}
|
|
533
|
+
if (family === "migration") {
|
|
534
|
+
return dispatchMigration(context, command, parsed);
|
|
535
|
+
}
|
|
328
536
|
if (family === "codex" && command === "hooks") {
|
|
329
537
|
return dispatchCodexHooks(context, parsed);
|
|
330
538
|
}
|
|
@@ -339,6 +547,41 @@ async function dispatch(args, context, io) {
|
|
|
339
547
|
}
|
|
340
548
|
throw new AppError("usage", `Unknown command: ${args.join(" ") || "<empty>"}`, 2);
|
|
341
549
|
}
|
|
550
|
+
function dispatchEngine(context, command, parsed) {
|
|
551
|
+
if (command === "install") {
|
|
552
|
+
return installCurrentEngine(context, { force: hasOption(parsed, "force") });
|
|
553
|
+
}
|
|
554
|
+
if (command === "list") {
|
|
555
|
+
return listEngines(context);
|
|
556
|
+
}
|
|
557
|
+
if (command === "info") {
|
|
558
|
+
return engineInfo(context, { packageName: optionalOption(parsed, "package"), version: optionalOption(parsed, "version") });
|
|
559
|
+
}
|
|
560
|
+
if (command === "resolve") {
|
|
561
|
+
return resolveEngine(context, { projectRoot: optionalOption(parsed, "project-root") ?? optionalOption(parsed, "root") });
|
|
562
|
+
}
|
|
563
|
+
if (command === "doctor") {
|
|
564
|
+
return doctorEngines(context, { projectRoot: optionalOption(parsed, "project-root") ?? optionalOption(parsed, "root") });
|
|
565
|
+
}
|
|
566
|
+
throw new AppError("usage", `Unknown engine command: ${command ?? "<empty>"}`, 2);
|
|
567
|
+
}
|
|
568
|
+
function dispatchMigration(context, command, parsed) {
|
|
569
|
+
if (command === "plan" || command === "report") {
|
|
570
|
+
return planMigration(context, {
|
|
571
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
572
|
+
sourceVersion: optionalOption(parsed, "source-version"),
|
|
573
|
+
targetVersion: optionalOption(parsed, "target-version"),
|
|
574
|
+
runId: optionalOption(parsed, "run"),
|
|
575
|
+
backupPath: optionalOption(parsed, "backup-path"),
|
|
576
|
+
backupCreatedAt: optionalOption(parsed, "backup-created-at"),
|
|
577
|
+
allowActive: parseOptionalBoolean(optionalOption(parsed, "allow-active"), "allow-active") ?? false
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
if (command === "verify") {
|
|
581
|
+
return verifyMigrationReport(context, { file: requiredOption(parsed, "file") });
|
|
582
|
+
}
|
|
583
|
+
throw new AppError("usage", `Unknown migration command: ${command ?? "<empty>"}`, 2);
|
|
584
|
+
}
|
|
342
585
|
function dispatchCanon(context, command, parsed) {
|
|
343
586
|
if (command === "register") {
|
|
344
587
|
return registerCanonRoot(context, { root: requiredOption(parsed, "root") });
|
|
@@ -579,6 +822,16 @@ function dispatchRun(context, command, parsed) {
|
|
|
579
822
|
}
|
|
580
823
|
throw new AppError("usage", `Unknown run command: ${command ?? "<empty>"}`, 2);
|
|
581
824
|
}
|
|
825
|
+
function dispatchId(context, command, parsed) {
|
|
826
|
+
if (command === "next") {
|
|
827
|
+
return previewNextEntityId(context, {
|
|
828
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
829
|
+
type: requiredOption(parsed, "type"),
|
|
830
|
+
slug: requiredOption(parsed, "slug")
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
throw new AppError("usage", `Unknown id command: ${command ?? "<empty>"}`, 2);
|
|
834
|
+
}
|
|
582
835
|
function dispatchCmux(context, parsed) {
|
|
583
836
|
const action = requiredPosition(parsed, 0, "integration cmux action");
|
|
584
837
|
if (action === "status") {
|
|
@@ -587,30 +840,149 @@ function dispatchCmux(context, parsed) {
|
|
|
587
840
|
throw new AppError("usage", `Unknown integration cmux action: ${action}`, 2);
|
|
588
841
|
}
|
|
589
842
|
function dispatchDashboard(context, command, parsed) {
|
|
843
|
+
const action = dashboardAction(command);
|
|
844
|
+
if (command === "data") {
|
|
845
|
+
const target = resolveDashboardTarget(context, {
|
|
846
|
+
action,
|
|
847
|
+
project: optionalOption(parsed, "project"),
|
|
848
|
+
projectRoot: optionalOption(parsed, "project-root"),
|
|
849
|
+
protocol: optionalOption(parsed, "protocol"),
|
|
850
|
+
global: hasOption(parsed, "global")
|
|
851
|
+
});
|
|
852
|
+
return withDashboardGuidance(action, target, dashboardDataForResolvedTarget(context, target));
|
|
853
|
+
}
|
|
590
854
|
if (command === "render") {
|
|
591
|
-
|
|
855
|
+
const target = resolveDashboardTarget(context, {
|
|
856
|
+
action,
|
|
857
|
+
project: optionalOption(parsed, "project"),
|
|
858
|
+
projectRoot: optionalOption(parsed, "project-root"),
|
|
859
|
+
protocol: optionalOption(parsed, "protocol")
|
|
860
|
+
});
|
|
861
|
+
return withDashboardGuidance(action, target, renderDashboardForResolvedTarget(context, target, parsed));
|
|
592
862
|
}
|
|
593
863
|
if (command === "render-global") {
|
|
594
|
-
|
|
864
|
+
const target = resolveDashboardTarget(context, { action: "render", global: true });
|
|
865
|
+
return withDashboardGuidance("render", target, renderGlobalDashboard(context, { output: optionalOption(parsed, "output"), format: optionalOption(parsed, "format") }), {
|
|
866
|
+
compatibilityAlias: true,
|
|
867
|
+
preferredCommand: "dd-flow dashboard render"
|
|
868
|
+
});
|
|
595
869
|
}
|
|
596
870
|
if (command === "open") {
|
|
597
|
-
|
|
871
|
+
const target = resolveDashboardTarget(context, {
|
|
872
|
+
action,
|
|
873
|
+
project: optionalOption(parsed, "project"),
|
|
874
|
+
projectRoot: optionalOption(parsed, "project-root"),
|
|
875
|
+
protocol: optionalOption(parsed, "protocol")
|
|
876
|
+
});
|
|
877
|
+
return withDashboardGuidance(action, target, openDashboardForResolvedTarget(context, target, parsed));
|
|
598
878
|
}
|
|
599
879
|
if (command === "refresh") {
|
|
600
880
|
const open = optionalOption(parsed, "open");
|
|
601
881
|
if (typeof open !== "undefined" && !["auto", "true", "false"].includes(open)) {
|
|
602
882
|
throw new AppError("validation", "--open must be auto, true, or false", 2);
|
|
603
883
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
884
|
+
const target = resolveDashboardTarget(context, {
|
|
885
|
+
action,
|
|
886
|
+
project: optionalOption(parsed, "project"),
|
|
887
|
+
projectRoot: optionalOption(parsed, "project-root"),
|
|
888
|
+
protocol: optionalOption(parsed, "protocol"),
|
|
889
|
+
all: hasOption(parsed, "all")
|
|
607
890
|
});
|
|
891
|
+
return withDashboardGuidance(action, target, refreshDashboardForResolvedTarget(context, target, parsed, open));
|
|
608
892
|
}
|
|
609
893
|
if (command === "refresh-global") {
|
|
610
|
-
|
|
894
|
+
const target = resolveDashboardTarget(context, { action: "refresh", global: true });
|
|
895
|
+
return withDashboardGuidance("refresh", target, refreshGlobalDashboard(context, { output: optionalOption(parsed, "output"), format: optionalOption(parsed, "format") }), {
|
|
896
|
+
compatibilityAlias: true,
|
|
897
|
+
preferredCommand: "dd-flow dashboard refresh"
|
|
898
|
+
});
|
|
611
899
|
}
|
|
612
900
|
throw new AppError("usage", `Unknown dashboard command: ${command ?? "<empty>"}`, 2);
|
|
613
901
|
}
|
|
902
|
+
function dashboardAction(command) {
|
|
903
|
+
if (command === "data" || command === "render" || command === "open" || command === "refresh") {
|
|
904
|
+
return command;
|
|
905
|
+
}
|
|
906
|
+
if (command === "render-global")
|
|
907
|
+
return "render";
|
|
908
|
+
if (command === "refresh-global")
|
|
909
|
+
return "refresh";
|
|
910
|
+
throw new AppError("usage", `Unknown dashboard command: ${command ?? "<empty>"}`, 2);
|
|
911
|
+
}
|
|
912
|
+
function dashboardDataForResolvedTarget(context, target) {
|
|
913
|
+
if (target.kind === "all_projects") {
|
|
914
|
+
throw new AppError("validation", "dashboard data does not support --all", 2, { related_commands: relatedDashboardCommands("data") });
|
|
915
|
+
}
|
|
916
|
+
if (target.kind === "global") {
|
|
917
|
+
return dashboardData(context, { global: true });
|
|
918
|
+
}
|
|
919
|
+
if (target.kind === "protocol") {
|
|
920
|
+
return dashboardData(context, { projectRoot: target.project.root, protocol: target.protocolId });
|
|
921
|
+
}
|
|
922
|
+
return dashboardData(context, { projectRoot: target.project.root });
|
|
923
|
+
}
|
|
924
|
+
function renderDashboardForResolvedTarget(context, target, parsed) {
|
|
925
|
+
if (target.kind === "all_projects") {
|
|
926
|
+
throw new AppError("validation", "dashboard render does not support --all", 2, { related_commands: relatedDashboardCommands("render") });
|
|
927
|
+
}
|
|
928
|
+
if (target.kind === "global") {
|
|
929
|
+
return renderGlobalDashboard(context, { output: optionalOption(parsed, "output"), format: optionalOption(parsed, "format") ?? "html" });
|
|
930
|
+
}
|
|
931
|
+
return renderDashboard(context, {
|
|
932
|
+
projectRoot: target.project.root,
|
|
933
|
+
output: optionalOption(parsed, "output"),
|
|
934
|
+
format: optionalOption(parsed, "format") ?? (hasOption(parsed, "project") ? "html" : undefined),
|
|
935
|
+
...(target.kind === "protocol" ? { protocol: target.protocolId } : {})
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
function openDashboardForResolvedTarget(context, target, parsed) {
|
|
939
|
+
if (target.kind === "all_projects") {
|
|
940
|
+
throw new AppError("validation", "dashboard open --all is not supported", 2, {
|
|
941
|
+
preferred_command: "dd-flow dashboard refresh --all",
|
|
942
|
+
related_commands: relatedDashboardCommands("open", "all_projects")
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
if (target.kind === "global") {
|
|
946
|
+
return openGlobalDashboard(context, { viewer: optionalOption(parsed, "viewer"), format: optionalOption(parsed, "format") ?? "html" });
|
|
947
|
+
}
|
|
948
|
+
if (target.kind === "protocol") {
|
|
949
|
+
return openProtocolDashboard(context, {
|
|
950
|
+
projectRoot: target.project.root,
|
|
951
|
+
protocol: target.protocolId,
|
|
952
|
+
viewer: optionalOption(parsed, "viewer"),
|
|
953
|
+
format: optionalOption(parsed, "format") ?? "html"
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
return openDashboard(context, {
|
|
957
|
+
projectRoot: target.project.root,
|
|
958
|
+
viewer: optionalOption(parsed, "viewer"),
|
|
959
|
+
format: optionalOption(parsed, "format") ?? (hasOption(parsed, "project") ? "html" : undefined)
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
function refreshDashboardForResolvedTarget(context, target, parsed, open) {
|
|
963
|
+
if (target.kind === "all_projects") {
|
|
964
|
+
return refreshAllDashboards(context, { format: optionalOption(parsed, "format") ?? "html" });
|
|
965
|
+
}
|
|
966
|
+
if (target.kind === "global") {
|
|
967
|
+
return refreshGlobalDashboard(context, { output: optionalOption(parsed, "output"), format: optionalOption(parsed, "format") ?? "html" });
|
|
968
|
+
}
|
|
969
|
+
return refreshDashboard(context, {
|
|
970
|
+
projectRoot: target.project.root,
|
|
971
|
+
open,
|
|
972
|
+
format: optionalOption(parsed, "format") ?? (hasOption(parsed, "project") ? "html" : undefined),
|
|
973
|
+
...(target.kind === "protocol" ? { protocol: target.protocolId } : {})
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
function withDashboardGuidance(action, target, result, options = {}) {
|
|
977
|
+
const record = result && typeof result === "object" && !Array.isArray(result) ? result : { ok: true, result };
|
|
978
|
+
return {
|
|
979
|
+
...record,
|
|
980
|
+
target: dashboardTargetSummary(target),
|
|
981
|
+
...(options.compatibilityAlias ? { compatibility_alias: true } : {}),
|
|
982
|
+
preferred_command: options.preferredCommand ?? preferredDashboardCommand(action, target),
|
|
983
|
+
related_commands: relatedDashboardCommands(action, target.kind)
|
|
984
|
+
};
|
|
985
|
+
}
|
|
614
986
|
function dispatchSchema(command, parsed) {
|
|
615
987
|
if (command === "validate") {
|
|
616
988
|
const schemaDir = optionalOption(parsed, "schema-dir");
|
|
@@ -628,6 +1000,21 @@ async function dispatchLane(context, command, parsed) {
|
|
|
628
1000
|
if (command === "status") {
|
|
629
1001
|
return getLaneStatus(context, { projectRoot: requiredOption(parsed, "project-root"), lane: optionalOption(parsed, "lane") });
|
|
630
1002
|
}
|
|
1003
|
+
if (command === "waiters") {
|
|
1004
|
+
return getLaneWaiters(context, { projectRoot: requiredOption(parsed, "project-root"), lane: optionalOption(parsed, "lane") });
|
|
1005
|
+
}
|
|
1006
|
+
if (command === "waiter") {
|
|
1007
|
+
const action = requiredPosition(parsed, 0, "lane waiter action");
|
|
1008
|
+
if (action === "cancel") {
|
|
1009
|
+
return cancelLaneWaiter(context, {
|
|
1010
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
1011
|
+
lane: requiredOption(parsed, "lane"),
|
|
1012
|
+
workerId: requiredOption(parsed, "worker-id"),
|
|
1013
|
+
reason: requiredOption(parsed, "reason")
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
throw new AppError("usage", `Unknown lane waiter action: ${action}`, 2);
|
|
1017
|
+
}
|
|
631
1018
|
if (command === "workspace") {
|
|
632
1019
|
const action = requiredPosition(parsed, 0, "lane workspace action");
|
|
633
1020
|
if (action === "set") {
|
|
@@ -692,6 +1079,18 @@ async function dispatchLane(context, command, parsed) {
|
|
|
692
1079
|
pollIntervalSeconds: optionalNumber(parsed, "poll-interval")
|
|
693
1080
|
});
|
|
694
1081
|
}
|
|
1082
|
+
if (action === "wait-acquire") {
|
|
1083
|
+
return waitAcquireLaneLock(context, {
|
|
1084
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
1085
|
+
lane: requiredOption(parsed, "lane"),
|
|
1086
|
+
workerId: requiredOption(parsed, "worker-id"),
|
|
1087
|
+
workspacePath: workspacePathOption(parsed),
|
|
1088
|
+
timeoutSeconds: optionalNumber(parsed, "timeout"),
|
|
1089
|
+
pollIntervalSeconds: optionalNumber(parsed, "poll-interval"),
|
|
1090
|
+
ttlSeconds: optionalNumber(parsed, "ttl"),
|
|
1091
|
+
reason: requiredOption(parsed, "reason")
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
695
1094
|
throw new AppError("usage", `Unknown lane lock action: ${action}`, 2);
|
|
696
1095
|
}
|
|
697
1096
|
throw new AppError("usage", `Unknown lane command: ${command ?? "<empty>"}`, 2);
|
|
@@ -882,6 +1281,14 @@ function requiredOption(parsed, key) {
|
|
|
882
1281
|
}
|
|
883
1282
|
return value;
|
|
884
1283
|
}
|
|
1284
|
+
function requiredOptionAlias(parsed, keys) {
|
|
1285
|
+
for (const key of keys) {
|
|
1286
|
+
const value = optionalOption(parsed, key);
|
|
1287
|
+
if (value)
|
|
1288
|
+
return value;
|
|
1289
|
+
}
|
|
1290
|
+
throw new AppError("usage", `Missing required option: ${keys.map((key) => `--${key}`).join(" or ")}`, 2);
|
|
1291
|
+
}
|
|
885
1292
|
function hasOption(parsed, key) {
|
|
886
1293
|
return parsed.options.has(key);
|
|
887
1294
|
}
|
|
@@ -1018,11 +1425,14 @@ function projectRootForLaneMutation(command, parsed) {
|
|
|
1018
1425
|
if (command === "workspace" && requiredPosition(parsed, 0, "lane workspace action") === "set") {
|
|
1019
1426
|
return requiredOption(parsed, "project-root");
|
|
1020
1427
|
}
|
|
1428
|
+
if (command === "waiter" && requiredPosition(parsed, 0, "lane waiter action") === "cancel") {
|
|
1429
|
+
return requiredOption(parsed, "project-root");
|
|
1430
|
+
}
|
|
1021
1431
|
if (command !== "lock") {
|
|
1022
1432
|
return undefined;
|
|
1023
1433
|
}
|
|
1024
1434
|
const action = requiredPosition(parsed, 0, "lane lock action");
|
|
1025
|
-
return ["acquire", "heartbeat", "release"].includes(action) ? requiredOption(parsed, "project-root") : undefined;
|
|
1435
|
+
return ["acquire", "heartbeat", "release", "wait-acquire"].includes(action) ? requiredOption(parsed, "project-root") : undefined;
|
|
1026
1436
|
}
|
|
1027
1437
|
function projectRootForMergeQueueMutation(context, command, parsed) {
|
|
1028
1438
|
if (["next", "wait-next"].includes(command ?? "")) {
|