@deksden-com/dd-flow-cli 0.3.0 → 0.4.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/CHANGELOG.md +55 -0
- package/README.md +74 -5
- package/dist/build-info.json +5 -5
- package/dist/cli/help.js +116 -18
- package/dist/cli/run-cli.js +361 -29
- package/dist/schemas/compatibility.schema.json +81 -2
- package/dist/schemas/engine-manifest.schema.json +61 -0
- package/dist/schemas/flow-guidance.schema.json +17 -0
- package/dist/schemas/global-dashboard-data.schema.json +60 -2
- package/dist/schemas/mb-upgrade-migration-report.schema.json +93 -0
- package/dist/schemas/project-dashboard-data.schema.json +26 -2
- package/dist/schemas/project-summary.schema.json +73 -0
- package/dist/schemas/protocol-dashboard-data.schema.json +25 -2
- package/dist/schemas/status-report.schema.json +4 -2
- package/dist/services/branch-context.js +254 -0
- package/dist/services/cleanup.js +31 -0
- package/dist/services/cli-operation-classifier.js +104 -0
- package/dist/services/compatibility-preflight.js +127 -0
- package/dist/services/config.js +6 -0
- package/dist/services/dashboard-targets.js +95 -0
- package/dist/services/dashboard.js +376 -59
- package/dist/services/engines.js +532 -0
- package/dist/services/flow-guidance.js +8 -1
- package/dist/services/hooks.js +1 -1
- package/dist/services/lanes.js +333 -1
- package/dist/services/merge-queue.js +310 -15
- package/dist/services/merge-worker.js +44 -3
- package/dist/services/migrations.js +231 -0
- package/dist/services/project-summary.js +122 -0
- package/dist/services/projects.js +41 -6
- package/dist/services/protocol-lifecycle.js +144 -0
- package/dist/services/protocols.js +34 -7
- package/dist/services/sessions.js +21 -4
- package/dist/services/status.js +10 -0
- package/dist/services/version-status.js +39 -15
- package/dist/storage/database.js +25 -0
- package/dist/storage/paths.js +12 -0
- package/package.json +3 -2
package/dist/cli/run-cli.js
CHANGED
|
@@ -5,24 +5,30 @@ import { writeJson } from "../shared/json.js";
|
|
|
5
5
|
import { archiveProject, getProjectStatus, migrateProjectIds, registerProject, resolveProject } from "../services/projects.js";
|
|
6
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
|
-
import { cancelMergeQueueJob, claimNextMergeJob, completeMergeJob, failMergeJob, getMergeQueueStatus, noteMergeJob, waitNextMergeJob } from "../services/merge-queue.js";
|
|
8
|
+
import { cancelMergeQueueJob, claimNextMergeJob, claimMergeBundle, completeMergeBundle, completeMergeJob, failMergeBundle, failMergeJob, getMergeBundleStatus, 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
17
|
import { getCliVersionReport } from "../services/build-info.js";
|
|
18
|
-
import { autoRefreshDashboards, dashboardData, getCmuxStatus, openDashboard, refreshDashboard, refreshGlobalDashboard, renderDashboard, renderGlobalDashboard } from "../services/dashboard.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";
|
|
19
20
|
import { getFlowSessionStatus, registerFlowSession, stopFlowSession, stopMergeWorker } from "../services/sessions.js";
|
|
21
|
+
import { getProtocolBranchContext } from "../services/branch-context.js";
|
|
20
22
|
import { attachFlowRunStage, completeFlowRun, completeFlowRunStage, getFlowRunStatus, listFlowRuns, startFlowRun } from "../services/runs.js";
|
|
21
23
|
import { previewNextEntityId } from "../services/ids.js";
|
|
22
24
|
import { validateSchema } from "../services/schema-validation.js";
|
|
23
25
|
import { preflightMemoryPermissions } from "../services/memory-permissions.js";
|
|
26
|
+
import { planMigration, verifyMigrationReport } from "../services/migrations.js";
|
|
27
|
+
import { publishProjectSummary } from "../services/project-summary.js";
|
|
24
28
|
import { requireProjectByRoot } from "../services/projects.js";
|
|
25
29
|
import { resolveProjectRoot } from "../storage/paths.js";
|
|
30
|
+
import { doctorEngines, engineInfo, installCurrentEngine, listEngines, resolveEngine, routeArgsThroughEngine } from "../services/engines.js";
|
|
31
|
+
import { preflightCliCompatibility } from "../services/compatibility-preflight.js";
|
|
26
32
|
const defaultIo = {
|
|
27
33
|
stdout: process.stdout,
|
|
28
34
|
stderr: process.stderr,
|
|
@@ -51,7 +57,16 @@ export async function runCli(args, io = defaultIo, env = process.env) {
|
|
|
51
57
|
}
|
|
52
58
|
const context = createContext(env);
|
|
53
59
|
emitHumanProgress(io, output, progress, "start");
|
|
54
|
-
const
|
|
60
|
+
const routed = await routeArgsThroughEngine(context, output.args, io, io.stdin, env);
|
|
61
|
+
if (routed?.routed && !routed.inProcess) {
|
|
62
|
+
emitHumanProgress(io, output, progress, "done");
|
|
63
|
+
return routed.exitCode;
|
|
64
|
+
}
|
|
65
|
+
const dispatchContext = routed?.inProcess
|
|
66
|
+
? createContext({ ...env, DD_FLOW_ENGINE_MODE: "1", DD_FLOW_ENGINE_HOME: "in-process", DD_FLOW_ROUTED_FROM: getCliVersionReport().cli.version })
|
|
67
|
+
: context;
|
|
68
|
+
preflightCliCompatibility(dispatchContext, output.args, env);
|
|
69
|
+
const result = await dispatch(output.args, dispatchContext, io);
|
|
55
70
|
refreshDashboardsAfterMutation(context, output.args, result);
|
|
56
71
|
emitHumanProgress(io, output, progress, "done");
|
|
57
72
|
const payload = attachProgress(result, progress, output);
|
|
@@ -70,10 +85,34 @@ export async function runCli(args, io = defaultIo, env = process.env) {
|
|
|
70
85
|
if (output.json)
|
|
71
86
|
writeJson(io.stderr, errorPayload);
|
|
72
87
|
else
|
|
73
|
-
io.stderr.write(
|
|
88
|
+
io.stderr.write(renderHumanError(payload));
|
|
74
89
|
return isAppError(error) ? error.exitCode : 1;
|
|
75
90
|
}
|
|
76
91
|
}
|
|
92
|
+
function renderHumanError(payload) {
|
|
93
|
+
const lines = [`dd-flow: ${payload.error.message}`];
|
|
94
|
+
const details = recordObject(payload.error.details);
|
|
95
|
+
const compatibility = recordObject(details?.compatibility);
|
|
96
|
+
const remediation = recordObject(details?.remediation);
|
|
97
|
+
if (compatibility) {
|
|
98
|
+
lines.push(`Compatibility: ${String(compatibility.verdict ?? "unknown")} (${String(compatibility.engine_resolution ?? "unknown")})`);
|
|
99
|
+
if (compatibility.blocked_operation)
|
|
100
|
+
lines.push(`Blocked operation: ${String(compatibility.blocked_operation)}`);
|
|
101
|
+
if (compatibility.install_hint)
|
|
102
|
+
lines.push(`Install compatible engine: ${String(compatibility.install_hint)}`);
|
|
103
|
+
}
|
|
104
|
+
if (remediation?.mb_upgrade_mode)
|
|
105
|
+
lines.push(`Upgrade mode: ${String(remediation.mb_upgrade_mode)}`);
|
|
106
|
+
if (details?.preferred_command)
|
|
107
|
+
lines.push(`Preferred command: ${String(details.preferred_command)}`);
|
|
108
|
+
const related = Array.isArray(details?.related_commands) ? details.related_commands.map(String) : [];
|
|
109
|
+
if (related.length > 0) {
|
|
110
|
+
lines.push("", "Related commands:");
|
|
111
|
+
for (const command of related)
|
|
112
|
+
lines.push(` ${command}`);
|
|
113
|
+
}
|
|
114
|
+
return `${lines.join("\n")}\n`;
|
|
115
|
+
}
|
|
77
116
|
function parseOutputOptions(args) {
|
|
78
117
|
const stripped = [];
|
|
79
118
|
let json = false;
|
|
@@ -96,7 +135,7 @@ function progressForCommand(args) {
|
|
|
96
135
|
{ phase: "done", message: "Merge queue wait finished", at: "" }
|
|
97
136
|
];
|
|
98
137
|
}
|
|
99
|
-
if (family === "lane" && command === "lock" && subcommand
|
|
138
|
+
if (family === "lane" && command === "lock" && ["wait", "wait-acquire"].includes(subcommand ?? "")) {
|
|
100
139
|
return [
|
|
101
140
|
{ phase: "start", message: "Starting lane lock wait", at: "" },
|
|
102
141
|
{ phase: "running", message: "Waiting for lane lock", at: "" },
|
|
@@ -164,7 +203,15 @@ function renderHumanResult(args, result) {
|
|
|
164
203
|
}
|
|
165
204
|
if (family === "version") {
|
|
166
205
|
const cli = recordObject(record.cli);
|
|
167
|
-
|
|
206
|
+
const engine = recordObject(record.engine);
|
|
207
|
+
const engineSuffix = engine ? ` (engine: ${String(engine.mode ?? "unknown")})` : "";
|
|
208
|
+
return `dd-flow ${stringValue(cli?.version) ?? "unknown"}${engineSuffix}\n`;
|
|
209
|
+
}
|
|
210
|
+
if (family === "engine") {
|
|
211
|
+
return renderEngineHuman(args, record);
|
|
212
|
+
}
|
|
213
|
+
if (family === "dashboard") {
|
|
214
|
+
return renderDashboardHuman(args, record);
|
|
168
215
|
}
|
|
169
216
|
const lines = [`dd-flow ${family ?? ""}${command ? ` ${command}` : ""}: ${ok}`.trim()];
|
|
170
217
|
const project = record.project && typeof record.project === "object" ? record.project : null;
|
|
@@ -177,6 +224,18 @@ function renderHumanResult(args, result) {
|
|
|
177
224
|
const job = record.job;
|
|
178
225
|
lines.push(`job: ${String(job.protocol_id ?? "none")} ${String(job.status ?? "")}`.trim());
|
|
179
226
|
}
|
|
227
|
+
const branchContext = recordObject(record.branch_context);
|
|
228
|
+
const mergeBundle = recordObject(branchContext?.merge_bundle);
|
|
229
|
+
if (branchContext) {
|
|
230
|
+
lines.push(`branch: ${String(branchContext.feature_branch ?? branchContext.grouping_key ?? "unknown")}`);
|
|
231
|
+
const protocols = Array.isArray(branchContext.protocols) ? branchContext.protocols.length : 0;
|
|
232
|
+
lines.push(`branch_protocols: ${protocols}`);
|
|
233
|
+
if (mergeBundle) {
|
|
234
|
+
const eligible = Array.isArray(mergeBundle.eligible_protocols) ? mergeBundle.eligible_protocols.length : 0;
|
|
235
|
+
const notReady = Array.isArray(mergeBundle.not_ready_protocols) ? mergeBundle.not_ready_protocols.length : 0;
|
|
236
|
+
lines.push(`bundle: ${String(mergeBundle.reason ?? "unknown")} ready=${eligible} not_ready=${notReady}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
180
239
|
if (record.stopped)
|
|
181
240
|
lines.push(`stopped: ${String(record.reason ?? "true")}`);
|
|
182
241
|
if (record.timed_out)
|
|
@@ -230,6 +289,63 @@ function renderStatusHuman(record) {
|
|
|
230
289
|
appendFlowGuidanceHuman(lines, record);
|
|
231
290
|
return `${lines.join("\n")}\n`;
|
|
232
291
|
}
|
|
292
|
+
function renderDashboardHuman(args, record) {
|
|
293
|
+
const [, command] = args;
|
|
294
|
+
const ok = record.ok === false ? "failed" : "ok";
|
|
295
|
+
const target = recordObject(record.target);
|
|
296
|
+
const targetLabel = stringValue(target?.kind) ?? "unknown";
|
|
297
|
+
const lines = [`dd-flow dashboard ${command ?? ""}: ${ok}`.trim()];
|
|
298
|
+
lines.push(`target: ${targetLabel}`);
|
|
299
|
+
if (target?.project_id)
|
|
300
|
+
lines.push(`project: ${String(target.project_id)}`);
|
|
301
|
+
if (target?.protocol_id)
|
|
302
|
+
lines.push(`protocol: ${String(target.protocol_id)}`);
|
|
303
|
+
const dashboard = recordObject(record.dashboard);
|
|
304
|
+
if (dashboard?.path)
|
|
305
|
+
lines.push(`dashboard: ${String(dashboard.path)}`);
|
|
306
|
+
if (record.compatibility_alias)
|
|
307
|
+
lines.push(`compatibility_alias: true`);
|
|
308
|
+
if (record.preferred_command)
|
|
309
|
+
lines.push(`Preferred command: ${String(record.preferred_command)}`);
|
|
310
|
+
const related = Array.isArray(record.related_commands) ? record.related_commands.map(String) : [];
|
|
311
|
+
if (related.length > 0) {
|
|
312
|
+
lines.push("", "Related dashboard commands:");
|
|
313
|
+
for (const item of related)
|
|
314
|
+
lines.push(` ${item}`);
|
|
315
|
+
}
|
|
316
|
+
return `${lines.join("\n")}\n`;
|
|
317
|
+
}
|
|
318
|
+
function renderEngineHuman(args, record) {
|
|
319
|
+
const [, command] = args;
|
|
320
|
+
const lines = [`dd-flow engine ${command ?? ""}: ${record.ok === false ? "failed" : "ok"}`.trim()];
|
|
321
|
+
if (record.engine_store)
|
|
322
|
+
lines.push(`engine_store: ${String(record.engine_store)}`);
|
|
323
|
+
const engine = recordObject(record.engine);
|
|
324
|
+
if (engine) {
|
|
325
|
+
lines.push(`engine: ${String(engine.package_name ?? "unknown")}@${String(engine.package_version ?? "unknown")}`);
|
|
326
|
+
if (engine.entrypoint)
|
|
327
|
+
lines.push(`entrypoint: ${String(engine.entrypoint)}`);
|
|
328
|
+
if (engine.healthy !== undefined)
|
|
329
|
+
lines.push(`healthy: ${String(engine.healthy)}`);
|
|
330
|
+
}
|
|
331
|
+
const engines = Array.isArray(record.engines) ? record.engines : [];
|
|
332
|
+
if (engines.length > 0)
|
|
333
|
+
lines.push(`engines: ${engines.length}`);
|
|
334
|
+
const selection = recordObject(record.selection);
|
|
335
|
+
if (selection) {
|
|
336
|
+
lines.push(`selection: ${String(selection.status ?? "unknown")}`);
|
|
337
|
+
if (selection.selected && typeof selection.selected === "object") {
|
|
338
|
+
const selected = selection.selected;
|
|
339
|
+
lines.push(`selected: ${String(selected.package_name ?? "unknown")}@${String(selected.package_version ?? "unknown")}`);
|
|
340
|
+
}
|
|
341
|
+
if (selection.install_hint)
|
|
342
|
+
lines.push(`install_hint: ${String(selection.install_hint)}`);
|
|
343
|
+
}
|
|
344
|
+
const checks = Array.isArray(record.checks) ? record.checks : [];
|
|
345
|
+
if (checks.length > 0)
|
|
346
|
+
lines.push(`checks: ${checks.length}`);
|
|
347
|
+
return `${lines.join("\n")}\n`;
|
|
348
|
+
}
|
|
233
349
|
function appendFlowGuidanceHuman(lines, record) {
|
|
234
350
|
const guidance = recordObject(record.flow_guidance);
|
|
235
351
|
if (!guidance)
|
|
@@ -261,6 +377,9 @@ function shortSha(value) {
|
|
|
261
377
|
async function dispatch(args, context, io) {
|
|
262
378
|
const [family, command, ...rest] = args;
|
|
263
379
|
const parsed = parseArgs(rest);
|
|
380
|
+
if (family === "engine") {
|
|
381
|
+
return dispatchEngine(context, command, parsed);
|
|
382
|
+
}
|
|
264
383
|
if (family === "status") {
|
|
265
384
|
const rootParsed = parseArgs([command ?? "", ...rest]);
|
|
266
385
|
return getRuntimeStatus(context, {
|
|
@@ -280,6 +399,12 @@ async function dispatch(args, context, io) {
|
|
|
280
399
|
if (family === "project" && command === "status") {
|
|
281
400
|
return getProjectStatus(context, { root: requiredOption(parsed, "root") });
|
|
282
401
|
}
|
|
402
|
+
if (family === "project" && command === "summary") {
|
|
403
|
+
return publishProjectSummary(context, {
|
|
404
|
+
project: requireProjectByRoot(context, resolveProjectRoot(requiredOption(parsed, "project-root"))),
|
|
405
|
+
write: parseOptionalBoolean(optionalOption(parsed, "write"), "write") ?? true
|
|
406
|
+
});
|
|
407
|
+
}
|
|
283
408
|
if (family === "project" && command === "resolve") {
|
|
284
409
|
return resolveProject(context, { idOrAlias: requiredPosition(parsed, 0, "id-or-alias") });
|
|
285
410
|
}
|
|
@@ -310,6 +435,14 @@ async function dispatch(args, context, io) {
|
|
|
310
435
|
if (family === "protocol" && command === "status") {
|
|
311
436
|
return getProtocolStatus(context, { protocolId: requiredPosition(parsed, 0, "protocol-id") });
|
|
312
437
|
}
|
|
438
|
+
if (family === "protocol" && command === "branch-status") {
|
|
439
|
+
const protocolId = parsed.positional[0];
|
|
440
|
+
return getProtocolBranchContext(context, {
|
|
441
|
+
...(protocolId ? { protocolId } : {}),
|
|
442
|
+
projectRoot: optionalOption(parsed, "project-root"),
|
|
443
|
+
workspacePath: optionalOption(parsed, "path")
|
|
444
|
+
});
|
|
445
|
+
}
|
|
313
446
|
if (family === "protocol" && command === "ready") {
|
|
314
447
|
return getReadyProtocols(context, { projectRoot: requiredOption(parsed, "project-root") });
|
|
315
448
|
}
|
|
@@ -418,6 +551,9 @@ async function dispatch(args, context, io) {
|
|
|
418
551
|
if (family === "memory") {
|
|
419
552
|
return dispatchMemory(command, parsed);
|
|
420
553
|
}
|
|
554
|
+
if (family === "migration") {
|
|
555
|
+
return dispatchMigration(context, command, parsed);
|
|
556
|
+
}
|
|
421
557
|
if (family === "codex" && command === "hooks") {
|
|
422
558
|
return dispatchCodexHooks(context, parsed);
|
|
423
559
|
}
|
|
@@ -432,6 +568,41 @@ async function dispatch(args, context, io) {
|
|
|
432
568
|
}
|
|
433
569
|
throw new AppError("usage", `Unknown command: ${args.join(" ") || "<empty>"}`, 2);
|
|
434
570
|
}
|
|
571
|
+
function dispatchEngine(context, command, parsed) {
|
|
572
|
+
if (command === "install") {
|
|
573
|
+
return installCurrentEngine(context, { force: hasOption(parsed, "force") });
|
|
574
|
+
}
|
|
575
|
+
if (command === "list") {
|
|
576
|
+
return listEngines(context);
|
|
577
|
+
}
|
|
578
|
+
if (command === "info") {
|
|
579
|
+
return engineInfo(context, { packageName: optionalOption(parsed, "package"), version: optionalOption(parsed, "version") });
|
|
580
|
+
}
|
|
581
|
+
if (command === "resolve") {
|
|
582
|
+
return resolveEngine(context, { projectRoot: optionalOption(parsed, "project-root") ?? optionalOption(parsed, "root") });
|
|
583
|
+
}
|
|
584
|
+
if (command === "doctor") {
|
|
585
|
+
return doctorEngines(context, { projectRoot: optionalOption(parsed, "project-root") ?? optionalOption(parsed, "root") });
|
|
586
|
+
}
|
|
587
|
+
throw new AppError("usage", `Unknown engine command: ${command ?? "<empty>"}`, 2);
|
|
588
|
+
}
|
|
589
|
+
function dispatchMigration(context, command, parsed) {
|
|
590
|
+
if (command === "plan" || command === "report") {
|
|
591
|
+
return planMigration(context, {
|
|
592
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
593
|
+
sourceVersion: optionalOption(parsed, "source-version"),
|
|
594
|
+
targetVersion: optionalOption(parsed, "target-version"),
|
|
595
|
+
runId: optionalOption(parsed, "run"),
|
|
596
|
+
backupPath: optionalOption(parsed, "backup-path"),
|
|
597
|
+
backupCreatedAt: optionalOption(parsed, "backup-created-at"),
|
|
598
|
+
allowActive: parseOptionalBoolean(optionalOption(parsed, "allow-active"), "allow-active") ?? false
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
if (command === "verify") {
|
|
602
|
+
return verifyMigrationReport(context, { file: requiredOption(parsed, "file") });
|
|
603
|
+
}
|
|
604
|
+
throw new AppError("usage", `Unknown migration command: ${command ?? "<empty>"}`, 2);
|
|
605
|
+
}
|
|
435
606
|
function dispatchCanon(context, command, parsed) {
|
|
436
607
|
if (command === "register") {
|
|
437
608
|
return registerCanonRoot(context, { root: requiredOption(parsed, "root") });
|
|
@@ -505,7 +676,38 @@ function dispatchMergeQueue(context, command, parsed) {
|
|
|
505
676
|
}
|
|
506
677
|
function dispatchMerge(context, command, parsed) {
|
|
507
678
|
if (command === "status") {
|
|
508
|
-
return getMergeWorkerStatus(context, { projectRoot: requiredOption(parsed, "project-root") });
|
|
679
|
+
return getMergeWorkerStatus(context, { projectRoot: requiredOption(parsed, "project-root"), workspacePath: optionalOption(parsed, "path") });
|
|
680
|
+
}
|
|
681
|
+
if (command === "bundle") {
|
|
682
|
+
const action = requiredPosition(parsed, 0, "bundle action");
|
|
683
|
+
if (action === "status") {
|
|
684
|
+
return getMergeBundleStatus(context, { projectRoot: requiredOption(parsed, "project-root"), workspacePath: workspacePathOption(parsed) });
|
|
685
|
+
}
|
|
686
|
+
if (action === "claim") {
|
|
687
|
+
return claimMergeBundle(context, {
|
|
688
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
689
|
+
workerId: requiredWorkerId(parsed),
|
|
690
|
+
workspacePath: workspacePathOption(parsed)
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
if (action === "complete") {
|
|
694
|
+
return completeMergeBundle(context, {
|
|
695
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
696
|
+
workerId: requiredWorkerId(parsed),
|
|
697
|
+
workspacePath: workspacePathOption(parsed),
|
|
698
|
+
summary: requiredOption(parsed, "summary")
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
if (action === "fail") {
|
|
702
|
+
return failMergeBundle(context, {
|
|
703
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
704
|
+
workerId: requiredWorkerId(parsed),
|
|
705
|
+
workspacePath: workspacePathOption(parsed),
|
|
706
|
+
reason: requiredOption(parsed, "reason"),
|
|
707
|
+
requeue: parseBoolean(requiredOption(parsed, "requeue"), "requeue")
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
throw new AppError("usage", `Unknown merge bundle action: ${action}`, 2);
|
|
509
711
|
}
|
|
510
712
|
if (command === "one-shot") {
|
|
511
713
|
return oneShotMergeClaim(context, {
|
|
@@ -690,49 +892,149 @@ function dispatchCmux(context, parsed) {
|
|
|
690
892
|
throw new AppError("usage", `Unknown integration cmux action: ${action}`, 2);
|
|
691
893
|
}
|
|
692
894
|
function dispatchDashboard(context, command, parsed) {
|
|
895
|
+
const action = dashboardAction(command);
|
|
693
896
|
if (command === "data") {
|
|
694
|
-
const
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
projectRoot:
|
|
698
|
-
protocol: optionalOption(parsed, "protocol")
|
|
897
|
+
const target = resolveDashboardTarget(context, {
|
|
898
|
+
action,
|
|
899
|
+
project: optionalOption(parsed, "project"),
|
|
900
|
+
projectRoot: optionalOption(parsed, "project-root"),
|
|
901
|
+
protocol: optionalOption(parsed, "protocol"),
|
|
902
|
+
global: hasOption(parsed, "global")
|
|
699
903
|
});
|
|
904
|
+
return withDashboardGuidance(action, target, dashboardDataForResolvedTarget(context, target));
|
|
700
905
|
}
|
|
701
906
|
if (command === "render") {
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
907
|
+
const target = resolveDashboardTarget(context, {
|
|
908
|
+
action,
|
|
909
|
+
project: optionalOption(parsed, "project"),
|
|
910
|
+
projectRoot: optionalOption(parsed, "project-root"),
|
|
706
911
|
protocol: optionalOption(parsed, "protocol")
|
|
707
912
|
});
|
|
913
|
+
return withDashboardGuidance(action, target, renderDashboardForResolvedTarget(context, target, parsed));
|
|
708
914
|
}
|
|
709
915
|
if (command === "render-global") {
|
|
710
|
-
|
|
916
|
+
const target = resolveDashboardTarget(context, { action: "render", global: true });
|
|
917
|
+
return withDashboardGuidance("render", target, renderGlobalDashboard(context, { output: optionalOption(parsed, "output"), format: optionalOption(parsed, "format") }), {
|
|
918
|
+
compatibilityAlias: true,
|
|
919
|
+
preferredCommand: "dd-flow dashboard render"
|
|
920
|
+
});
|
|
711
921
|
}
|
|
712
922
|
if (command === "open") {
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
923
|
+
const target = resolveDashboardTarget(context, {
|
|
924
|
+
action,
|
|
925
|
+
project: optionalOption(parsed, "project"),
|
|
926
|
+
projectRoot: optionalOption(parsed, "project-root"),
|
|
927
|
+
protocol: optionalOption(parsed, "protocol")
|
|
717
928
|
});
|
|
929
|
+
return withDashboardGuidance(action, target, openDashboardForResolvedTarget(context, target, parsed));
|
|
718
930
|
}
|
|
719
931
|
if (command === "refresh") {
|
|
720
932
|
const open = optionalOption(parsed, "open");
|
|
721
933
|
if (typeof open !== "undefined" && !["auto", "true", "false"].includes(open)) {
|
|
722
934
|
throw new AppError("validation", "--open must be auto, true, or false", 2);
|
|
723
935
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
protocol: optionalOption(parsed, "protocol")
|
|
936
|
+
const target = resolveDashboardTarget(context, {
|
|
937
|
+
action,
|
|
938
|
+
project: optionalOption(parsed, "project"),
|
|
939
|
+
projectRoot: optionalOption(parsed, "project-root"),
|
|
940
|
+
protocol: optionalOption(parsed, "protocol"),
|
|
941
|
+
all: hasOption(parsed, "all")
|
|
729
942
|
});
|
|
943
|
+
return withDashboardGuidance(action, target, refreshDashboardForResolvedTarget(context, target, parsed, open));
|
|
730
944
|
}
|
|
731
945
|
if (command === "refresh-global") {
|
|
732
|
-
|
|
946
|
+
const target = resolveDashboardTarget(context, { action: "refresh", global: true });
|
|
947
|
+
return withDashboardGuidance("refresh", target, refreshGlobalDashboard(context, { output: optionalOption(parsed, "output"), format: optionalOption(parsed, "format") }), {
|
|
948
|
+
compatibilityAlias: true,
|
|
949
|
+
preferredCommand: "dd-flow dashboard refresh"
|
|
950
|
+
});
|
|
733
951
|
}
|
|
734
952
|
throw new AppError("usage", `Unknown dashboard command: ${command ?? "<empty>"}`, 2);
|
|
735
953
|
}
|
|
954
|
+
function dashboardAction(command) {
|
|
955
|
+
if (command === "data" || command === "render" || command === "open" || command === "refresh") {
|
|
956
|
+
return command;
|
|
957
|
+
}
|
|
958
|
+
if (command === "render-global")
|
|
959
|
+
return "render";
|
|
960
|
+
if (command === "refresh-global")
|
|
961
|
+
return "refresh";
|
|
962
|
+
throw new AppError("usage", `Unknown dashboard command: ${command ?? "<empty>"}`, 2);
|
|
963
|
+
}
|
|
964
|
+
function dashboardDataForResolvedTarget(context, target) {
|
|
965
|
+
if (target.kind === "all_projects") {
|
|
966
|
+
throw new AppError("validation", "dashboard data does not support --all", 2, { related_commands: relatedDashboardCommands("data") });
|
|
967
|
+
}
|
|
968
|
+
if (target.kind === "global") {
|
|
969
|
+
return dashboardData(context, { global: true });
|
|
970
|
+
}
|
|
971
|
+
if (target.kind === "protocol") {
|
|
972
|
+
return dashboardData(context, { projectRoot: target.project.root, protocol: target.protocolId });
|
|
973
|
+
}
|
|
974
|
+
return dashboardData(context, { projectRoot: target.project.root });
|
|
975
|
+
}
|
|
976
|
+
function renderDashboardForResolvedTarget(context, target, parsed) {
|
|
977
|
+
if (target.kind === "all_projects") {
|
|
978
|
+
throw new AppError("validation", "dashboard render does not support --all", 2, { related_commands: relatedDashboardCommands("render") });
|
|
979
|
+
}
|
|
980
|
+
if (target.kind === "global") {
|
|
981
|
+
return renderGlobalDashboard(context, { output: optionalOption(parsed, "output"), format: optionalOption(parsed, "format") ?? "html" });
|
|
982
|
+
}
|
|
983
|
+
return renderDashboard(context, {
|
|
984
|
+
projectRoot: target.project.root,
|
|
985
|
+
output: optionalOption(parsed, "output"),
|
|
986
|
+
format: optionalOption(parsed, "format") ?? (hasOption(parsed, "project") ? "html" : undefined),
|
|
987
|
+
...(target.kind === "protocol" ? { protocol: target.protocolId } : {})
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
function openDashboardForResolvedTarget(context, target, parsed) {
|
|
991
|
+
if (target.kind === "all_projects") {
|
|
992
|
+
throw new AppError("validation", "dashboard open --all is not supported", 2, {
|
|
993
|
+
preferred_command: "dd-flow dashboard refresh --all",
|
|
994
|
+
related_commands: relatedDashboardCommands("open", "all_projects")
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
if (target.kind === "global") {
|
|
998
|
+
return openGlobalDashboard(context, { viewer: optionalOption(parsed, "viewer"), format: optionalOption(parsed, "format") ?? "html" });
|
|
999
|
+
}
|
|
1000
|
+
if (target.kind === "protocol") {
|
|
1001
|
+
return openProtocolDashboard(context, {
|
|
1002
|
+
projectRoot: target.project.root,
|
|
1003
|
+
protocol: target.protocolId,
|
|
1004
|
+
viewer: optionalOption(parsed, "viewer"),
|
|
1005
|
+
format: optionalOption(parsed, "format") ?? "html"
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
return openDashboard(context, {
|
|
1009
|
+
projectRoot: target.project.root,
|
|
1010
|
+
viewer: optionalOption(parsed, "viewer"),
|
|
1011
|
+
format: optionalOption(parsed, "format") ?? (hasOption(parsed, "project") ? "html" : undefined)
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
function refreshDashboardForResolvedTarget(context, target, parsed, open) {
|
|
1015
|
+
if (target.kind === "all_projects") {
|
|
1016
|
+
return refreshAllDashboards(context, { format: optionalOption(parsed, "format") ?? "html" });
|
|
1017
|
+
}
|
|
1018
|
+
if (target.kind === "global") {
|
|
1019
|
+
return refreshGlobalDashboard(context, { output: optionalOption(parsed, "output"), format: optionalOption(parsed, "format") ?? "html" });
|
|
1020
|
+
}
|
|
1021
|
+
return refreshDashboard(context, {
|
|
1022
|
+
projectRoot: target.project.root,
|
|
1023
|
+
open,
|
|
1024
|
+
format: optionalOption(parsed, "format") ?? (hasOption(parsed, "project") ? "html" : undefined),
|
|
1025
|
+
...(target.kind === "protocol" ? { protocol: target.protocolId } : {})
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
function withDashboardGuidance(action, target, result, options = {}) {
|
|
1029
|
+
const record = result && typeof result === "object" && !Array.isArray(result) ? result : { ok: true, result };
|
|
1030
|
+
return {
|
|
1031
|
+
...record,
|
|
1032
|
+
target: dashboardTargetSummary(target),
|
|
1033
|
+
...(options.compatibilityAlias ? { compatibility_alias: true } : {}),
|
|
1034
|
+
preferred_command: options.preferredCommand ?? preferredDashboardCommand(action, target),
|
|
1035
|
+
related_commands: relatedDashboardCommands(action, target.kind)
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
736
1038
|
function dispatchSchema(command, parsed) {
|
|
737
1039
|
if (command === "validate") {
|
|
738
1040
|
const schemaDir = optionalOption(parsed, "schema-dir");
|
|
@@ -750,6 +1052,21 @@ async function dispatchLane(context, command, parsed) {
|
|
|
750
1052
|
if (command === "status") {
|
|
751
1053
|
return getLaneStatus(context, { projectRoot: requiredOption(parsed, "project-root"), lane: optionalOption(parsed, "lane") });
|
|
752
1054
|
}
|
|
1055
|
+
if (command === "waiters") {
|
|
1056
|
+
return getLaneWaiters(context, { projectRoot: requiredOption(parsed, "project-root"), lane: optionalOption(parsed, "lane") });
|
|
1057
|
+
}
|
|
1058
|
+
if (command === "waiter") {
|
|
1059
|
+
const action = requiredPosition(parsed, 0, "lane waiter action");
|
|
1060
|
+
if (action === "cancel") {
|
|
1061
|
+
return cancelLaneWaiter(context, {
|
|
1062
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
1063
|
+
lane: requiredOption(parsed, "lane"),
|
|
1064
|
+
workerId: requiredOption(parsed, "worker-id"),
|
|
1065
|
+
reason: requiredOption(parsed, "reason")
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
throw new AppError("usage", `Unknown lane waiter action: ${action}`, 2);
|
|
1069
|
+
}
|
|
753
1070
|
if (command === "workspace") {
|
|
754
1071
|
const action = requiredPosition(parsed, 0, "lane workspace action");
|
|
755
1072
|
if (action === "set") {
|
|
@@ -814,6 +1131,18 @@ async function dispatchLane(context, command, parsed) {
|
|
|
814
1131
|
pollIntervalSeconds: optionalNumber(parsed, "poll-interval")
|
|
815
1132
|
});
|
|
816
1133
|
}
|
|
1134
|
+
if (action === "wait-acquire") {
|
|
1135
|
+
return waitAcquireLaneLock(context, {
|
|
1136
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
1137
|
+
lane: requiredOption(parsed, "lane"),
|
|
1138
|
+
workerId: requiredOption(parsed, "worker-id"),
|
|
1139
|
+
workspacePath: workspacePathOption(parsed),
|
|
1140
|
+
timeoutSeconds: optionalNumber(parsed, "timeout"),
|
|
1141
|
+
pollIntervalSeconds: optionalNumber(parsed, "poll-interval"),
|
|
1142
|
+
ttlSeconds: optionalNumber(parsed, "ttl"),
|
|
1143
|
+
reason: requiredOption(parsed, "reason")
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
817
1146
|
throw new AppError("usage", `Unknown lane lock action: ${action}`, 2);
|
|
818
1147
|
}
|
|
819
1148
|
throw new AppError("usage", `Unknown lane command: ${command ?? "<empty>"}`, 2);
|
|
@@ -1148,11 +1477,14 @@ function projectRootForLaneMutation(command, parsed) {
|
|
|
1148
1477
|
if (command === "workspace" && requiredPosition(parsed, 0, "lane workspace action") === "set") {
|
|
1149
1478
|
return requiredOption(parsed, "project-root");
|
|
1150
1479
|
}
|
|
1480
|
+
if (command === "waiter" && requiredPosition(parsed, 0, "lane waiter action") === "cancel") {
|
|
1481
|
+
return requiredOption(parsed, "project-root");
|
|
1482
|
+
}
|
|
1151
1483
|
if (command !== "lock") {
|
|
1152
1484
|
return undefined;
|
|
1153
1485
|
}
|
|
1154
1486
|
const action = requiredPosition(parsed, 0, "lane lock action");
|
|
1155
|
-
return ["acquire", "heartbeat", "release"].includes(action) ? requiredOption(parsed, "project-root") : undefined;
|
|
1487
|
+
return ["acquire", "heartbeat", "release", "wait-acquire"].includes(action) ? requiredOption(parsed, "project-root") : undefined;
|
|
1156
1488
|
}
|
|
1157
1489
|
function projectRootForMergeQueueMutation(context, command, parsed) {
|
|
1158
1490
|
if (["next", "wait-next"].includes(command ?? "")) {
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"$id": "dd-flow/compatibility@1",
|
|
4
4
|
"title": "dd-flow compatibility manifest",
|
|
5
|
-
"description": "Canonical compatibility map between a Memory Bank release
|
|
5
|
+
"description": "Canonical compatibility map between a Memory Bank release, tool packages, routed CLI engine expectations and machine data contracts required or recommended by that release.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"additionalProperties": false,
|
|
8
|
-
"required": ["schema_id", "memory_bank_version", "dd_flow_cli"],
|
|
8
|
+
"required": ["schema_id", "memory_bank_version", "dd_flow_cli", "router", "engine", "contracts", "migrations", "mb_lint"],
|
|
9
9
|
"properties": {
|
|
10
10
|
"schema_id": { "const": "dd-flow/compatibility@1" },
|
|
11
11
|
"memory_bank_version": { "type": "string", "minLength": 1 },
|
|
@@ -21,6 +21,85 @@
|
|
|
21
21
|
"version_contract": { "type": "string", "minLength": 1 },
|
|
22
22
|
"flow_contract": { "type": "string", "minLength": 1 }
|
|
23
23
|
}
|
|
24
|
+
},
|
|
25
|
+
"router": {
|
|
26
|
+
"type": "object",
|
|
27
|
+
"additionalProperties": false,
|
|
28
|
+
"required": ["package_name", "min_version", "recommended_version", "routing_model", "engine_store"],
|
|
29
|
+
"properties": {
|
|
30
|
+
"package_name": { "type": "string", "minLength": 1 },
|
|
31
|
+
"min_version": { "type": "string", "minLength": 1 },
|
|
32
|
+
"recommended_version": { "type": "string", "minLength": 1 },
|
|
33
|
+
"routing_model": { "type": "string", "minLength": 1 },
|
|
34
|
+
"engine_store": { "type": "string", "minLength": 1 },
|
|
35
|
+
"notes": { "type": "string" }
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"engine": {
|
|
39
|
+
"type": "object",
|
|
40
|
+
"additionalProperties": false,
|
|
41
|
+
"required": ["package_name", "version_range", "recommended_version", "install_hint", "old_engine_install_hint", "first_engine_installable_version", "legacy_direct_cli_before", "manifest_contract", "status"],
|
|
42
|
+
"properties": {
|
|
43
|
+
"package_name": { "type": "string", "minLength": 1 },
|
|
44
|
+
"version_range": { "type": "string", "minLength": 1 },
|
|
45
|
+
"recommended_version": { "type": "string", "minLength": 1 },
|
|
46
|
+
"install_hint": { "type": "string", "minLength": 1 },
|
|
47
|
+
"old_engine_install_hint": { "type": "string", "minLength": 1 },
|
|
48
|
+
"first_engine_installable_version": { "type": "string", "minLength": 1 },
|
|
49
|
+
"legacy_direct_cli_before": { "type": "string", "minLength": 1 },
|
|
50
|
+
"manifest_contract": { "type": "string", "minLength": 1 },
|
|
51
|
+
"status": { "type": "string", "minLength": 1 }
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"contracts": {
|
|
55
|
+
"type": "object",
|
|
56
|
+
"additionalProperties": false,
|
|
57
|
+
"required": ["storage_contract_set", "project_summary_contract", "project_dashboard_contract", "global_dashboard_contract", "status_contract", "version_contract"],
|
|
58
|
+
"properties": {
|
|
59
|
+
"storage_contract_set": { "type": "string", "minLength": 1 },
|
|
60
|
+
"project_summary_contract": { "type": "string", "minLength": 1 },
|
|
61
|
+
"project_dashboard_contract": { "type": "string", "minLength": 1 },
|
|
62
|
+
"global_dashboard_contract": { "type": "string", "minLength": 1 },
|
|
63
|
+
"status_contract": { "type": "string", "minLength": 1 },
|
|
64
|
+
"version_contract": { "type": "string", "minLength": 1 }
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"global_dashboard": {
|
|
68
|
+
"type": "object",
|
|
69
|
+
"additionalProperties": false,
|
|
70
|
+
"required": ["supported_project_summary_contracts", "unsupported_project_visibility", "summary_version_groups"],
|
|
71
|
+
"properties": {
|
|
72
|
+
"supported_project_summary_contracts": {
|
|
73
|
+
"type": "array",
|
|
74
|
+
"minItems": 1,
|
|
75
|
+
"items": { "type": "string", "minLength": 1 }
|
|
76
|
+
},
|
|
77
|
+
"unsupported_project_visibility": { "type": "boolean" },
|
|
78
|
+
"summary_version_groups": { "type": "boolean" }
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"migrations": {
|
|
82
|
+
"type": "object",
|
|
83
|
+
"additionalProperties": false,
|
|
84
|
+
"required": ["mode", "from_previous", "to_this", "adjacent_only", "normal_commands_may_migrate"],
|
|
85
|
+
"properties": {
|
|
86
|
+
"mode": { "const": "mb-upgrade-only" },
|
|
87
|
+
"from_previous": { "type": ["string", "null"] },
|
|
88
|
+
"to_this": { "type": "string", "minLength": 1 },
|
|
89
|
+
"adjacent_only": { "type": "boolean" },
|
|
90
|
+
"normal_commands_may_migrate": { "const": false }
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
"mb_lint": {
|
|
94
|
+
"type": "object",
|
|
95
|
+
"additionalProperties": false,
|
|
96
|
+
"required": ["package_name", "min_version", "recommended_version", "command_contract"],
|
|
97
|
+
"properties": {
|
|
98
|
+
"package_name": { "type": "string", "minLength": 1 },
|
|
99
|
+
"min_version": { "type": "string", "minLength": 1 },
|
|
100
|
+
"recommended_version": { "type": "string", "minLength": 1 },
|
|
101
|
+
"command_contract": { "type": "string", "minLength": 1 }
|
|
102
|
+
}
|
|
24
103
|
}
|
|
25
104
|
}
|
|
26
105
|
}
|