@deksden-com/dd-flow-cli 0.3.1 → 0.4.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 +28 -0
- package/README.md +25 -9
- package/dist/build-info.json +5 -5
- package/dist/cli/help.js +58 -32
- package/dist/cli/run-cli.js +282 -50
- package/dist/domain/entity-ids.js +4 -4
- package/dist/domain/flow-contract.js +502 -36
- package/dist/domain/validation.js +34 -0
- package/dist/protocol/local-files.js +8 -6
- package/dist/schemas/code-stage-report.schema.json +197 -2
- package/dist/schemas/flow-contract.schema.json +126 -0
- package/dist/schemas/flow-run-index-v3.schema.json +203 -0
- package/dist/schemas/flow-run-index.schema.json +22 -2
- package/dist/schemas/flow-run.schema.json +36 -0
- package/dist/schemas/merge-stage-report.schema.json +213 -2
- package/dist/schemas/plan-stage-report.schema.json +156 -2
- package/dist/schemas/release-impact.schema.json +16 -0
- package/dist/services/audit.js +3 -3
- package/dist/services/branch-context.js +266 -0
- package/dist/services/canon.js +0 -1
- package/dist/services/cleanup.js +6 -6
- package/dist/services/cli-operation-classifier.js +1 -1
- package/dist/services/compatibility-preflight.js +8 -77
- package/dist/services/dashboard.js +48 -11
- package/dist/services/engines.js +123 -13
- package/dist/services/hooks.js +6 -6
- package/dist/services/ids.js +40 -49
- package/dist/services/merge-queue.js +240 -20
- package/dist/services/merge-worker.js +12 -4
- package/dist/services/migrations.js +64 -0
- package/dist/services/plans.js +23 -16
- package/dist/services/projects.js +2 -2
- package/dist/services/prompts.js +322 -0
- package/dist/services/protocols.js +78 -42
- package/dist/services/run-projection.js +80 -0
- package/dist/services/runs.js +360 -22
- package/dist/services/schema-validation.js +35 -12
- package/dist/services/sessions.js +81 -3
- package/dist/services/status.js +32 -1
- package/dist/services/usage.js +233 -0
- package/dist/services/worktrees.js +24 -19
- package/dist/storage/database.js +223 -9
- package/package.json +1 -1
package/dist/cli/run-cli.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
1
2
|
import { createContext } from "../runtime/context.js";
|
|
2
3
|
import { helpForArgs } from "./help.js";
|
|
3
4
|
import { AppError, isAppError } from "../shared/errors.js";
|
|
@@ -5,7 +6,7 @@ import { writeJson } from "../shared/json.js";
|
|
|
5
6
|
import { archiveProject, getProjectStatus, migrateProjectIds, registerProject, resolveProject } from "../services/projects.js";
|
|
6
7
|
import { cancelProtocol, getProtocolBlockers, getProtocolStatus, getReadyProtocols, implementProtocol, readyForMerge, registerProtocol, requireProtocol, syncProtocolFromRun, transitionProtocol } from "../services/protocols.js";
|
|
7
8
|
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";
|
|
9
|
+
import { cancelMergeQueueJob, claimNextMergeJob, claimMergeBundle, completeMergeBundle, completeMergeJob, failMergeBundle, failMergeJob, getMergeBundleStatus, getMergeQueueStatus, noteMergeJob, waitNextMergeJob } from "../services/merge-queue.js";
|
|
9
10
|
import { getMergeWorkerStatus, oneShotMergeClaim, startMergeWorker, stopProjectMergeWorker } from "../services/merge-worker.js";
|
|
10
11
|
import { cleanupApply, cleanupScan } from "../services/cleanup.js";
|
|
11
12
|
import { getCodexHomeStatus, getCodexHooksStatus, handleCodexHook, initCodexHome, installCodexHooks, planCodexHome, printCodexHomeEnv, printCodexHooks, removeCodexHome, removeCodexHooks } from "../services/hooks.js";
|
|
@@ -17,17 +18,19 @@ import { getRuntimeStatus } from "../services/status.js";
|
|
|
17
18
|
import { getCliVersionReport } from "../services/build-info.js";
|
|
18
19
|
import { autoRefreshDashboards, dashboardData, getCmuxStatus, openDashboard, openGlobalDashboard, openProtocolDashboard, refreshAllDashboards, refreshDashboard, refreshGlobalDashboard, renderDashboard, renderGlobalDashboard } from "../services/dashboard.js";
|
|
19
20
|
import { dashboardTargetSummary, preferredDashboardCommand, relatedDashboardCommands, resolveDashboardTarget } from "../services/dashboard-targets.js";
|
|
20
|
-
import { getFlowSessionStatus, registerFlowSession, stopFlowSession, stopMergeWorker } from "../services/sessions.js";
|
|
21
|
-
import {
|
|
21
|
+
import { getFlowSessionStatus, registerFlowSession, stopFlowSession, stopMergeWorker, syncFlowSessionUsage } from "../services/sessions.js";
|
|
22
|
+
import { getProtocolBranchContext } from "../services/branch-context.js";
|
|
23
|
+
import { attachFlowRunStage, completeFlowRun, completeFlowRunStage, getFlowRunTimeline, getFlowRunUsage, getFlowRunFlagsStatus, getFlowRunStatus, listFlowRuns, reviseFlowRunFlags, startFlowRun } from "../services/runs.js";
|
|
22
24
|
import { previewNextEntityId } from "../services/ids.js";
|
|
23
25
|
import { validateSchema } from "../services/schema-validation.js";
|
|
24
26
|
import { preflightMemoryPermissions } from "../services/memory-permissions.js";
|
|
25
|
-
import { planMigration, verifyMigrationReport } from "../services/migrations.js";
|
|
27
|
+
import { assessMigrationImpact, planMigration, verifyMigrationReport } from "../services/migrations.js";
|
|
26
28
|
import { publishProjectSummary } from "../services/project-summary.js";
|
|
27
29
|
import { requireProjectByRoot } from "../services/projects.js";
|
|
28
30
|
import { resolveProjectRoot } from "../storage/paths.js";
|
|
29
|
-
import { doctorEngines, engineInfo, installCurrentEngine, listEngines, resolveEngine, routeArgsThroughEngine } from "../services/engines.js";
|
|
31
|
+
import { doctorEngines, engineInfo, installCurrentEngine, listEngines, resolveEngine, resolveOperationProjectRoot, routeArgsThroughEngine } from "../services/engines.js";
|
|
30
32
|
import { preflightCliCompatibility } from "../services/compatibility-preflight.js";
|
|
33
|
+
import { renderWorkerPrompt } from "../services/prompts.js";
|
|
31
34
|
const defaultIo = {
|
|
32
35
|
stdout: process.stdout,
|
|
33
36
|
stderr: process.stderr,
|
|
@@ -56,7 +59,8 @@ export async function runCli(args, io = defaultIo, env = process.env) {
|
|
|
56
59
|
}
|
|
57
60
|
const context = createContext(env);
|
|
58
61
|
emitHumanProgress(io, output, progress, "start");
|
|
59
|
-
const
|
|
62
|
+
const scopeProjectRoot = resolveOperationProjectRoot(context, output.args);
|
|
63
|
+
const routed = await routeArgsThroughEngine(context, output.args, io, io.stdin, env, scopeProjectRoot);
|
|
60
64
|
if (routed?.routed && !routed.inProcess) {
|
|
61
65
|
emitHumanProgress(io, output, progress, "done");
|
|
62
66
|
return routed.exitCode;
|
|
@@ -64,11 +68,12 @@ export async function runCli(args, io = defaultIo, env = process.env) {
|
|
|
64
68
|
const dispatchContext = routed?.inProcess
|
|
65
69
|
? createContext({ ...env, DD_FLOW_ENGINE_MODE: "1", DD_FLOW_ENGINE_HOME: "in-process", DD_FLOW_ROUTED_FROM: getCliVersionReport().cli.version })
|
|
66
70
|
: context;
|
|
67
|
-
preflightCliCompatibility(dispatchContext, output.args, env);
|
|
68
|
-
const result = await dispatch(output.args, dispatchContext, io);
|
|
69
|
-
refreshDashboardsAfterMutation(context, output.args, result);
|
|
71
|
+
preflightCliCompatibility(dispatchContext, output.args, env, scopeProjectRoot);
|
|
72
|
+
const result = await dispatch(output.args, dispatchContext, io, scopeProjectRoot);
|
|
73
|
+
const refreshWarnings = refreshDashboardsAfterMutation(context, output.args, result, scopeProjectRoot);
|
|
74
|
+
const resultWithWarnings = addWarnings(result, refreshWarnings);
|
|
70
75
|
emitHumanProgress(io, output, progress, "done");
|
|
71
|
-
const payload = attachProgress(
|
|
76
|
+
const payload = attachProgress(resultWithWarnings, progress, output);
|
|
72
77
|
if (output.json)
|
|
73
78
|
writeJson(io.stdout, payload);
|
|
74
79
|
else
|
|
@@ -223,6 +228,18 @@ function renderHumanResult(args, result) {
|
|
|
223
228
|
const job = record.job;
|
|
224
229
|
lines.push(`job: ${String(job.protocol_id ?? "none")} ${String(job.status ?? "")}`.trim());
|
|
225
230
|
}
|
|
231
|
+
const branchContext = recordObject(record.branch_context);
|
|
232
|
+
const mergeBundle = recordObject(branchContext?.merge_bundle);
|
|
233
|
+
if (branchContext) {
|
|
234
|
+
lines.push(`branch: ${String(branchContext.feature_branch ?? branchContext.grouping_key ?? "unknown")}`);
|
|
235
|
+
const protocols = Array.isArray(branchContext.protocols) ? branchContext.protocols.length : 0;
|
|
236
|
+
lines.push(`branch_protocols: ${protocols}`);
|
|
237
|
+
if (mergeBundle) {
|
|
238
|
+
const eligible = Array.isArray(mergeBundle.eligible_protocols) ? mergeBundle.eligible_protocols.length : 0;
|
|
239
|
+
const notReady = Array.isArray(mergeBundle.not_ready_protocols) ? mergeBundle.not_ready_protocols.length : 0;
|
|
240
|
+
lines.push(`bundle: ${String(mergeBundle.reason ?? "unknown")} ready=${eligible} not_ready=${notReady}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
226
243
|
if (record.stopped)
|
|
227
244
|
lines.push(`stopped: ${String(record.reason ?? "true")}`);
|
|
228
245
|
if (record.timed_out)
|
|
@@ -361,7 +378,7 @@ function stringValue(value) {
|
|
|
361
378
|
function shortSha(value) {
|
|
362
379
|
return value ? value.slice(0, 12) : "unknown";
|
|
363
380
|
}
|
|
364
|
-
async function dispatch(args, context, io) {
|
|
381
|
+
async function dispatch(args, context, io, scopeProjectRoot = null) {
|
|
365
382
|
const [family, command, ...rest] = args;
|
|
366
383
|
const parsed = parseArgs(rest);
|
|
367
384
|
if (family === "engine") {
|
|
@@ -420,7 +437,16 @@ async function dispatch(args, context, io) {
|
|
|
420
437
|
});
|
|
421
438
|
}
|
|
422
439
|
if (family === "protocol" && command === "status") {
|
|
423
|
-
|
|
440
|
+
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
441
|
+
return getProtocolStatus(context, { protocolId, projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "protocol status") });
|
|
442
|
+
}
|
|
443
|
+
if (family === "protocol" && command === "branch-status") {
|
|
444
|
+
const protocolId = parsed.positional[0];
|
|
445
|
+
return getProtocolBranchContext(context, {
|
|
446
|
+
...(protocolId ? { protocolId } : {}),
|
|
447
|
+
projectRoot: scopeProjectRoot ?? optionalOption(parsed, "project-root"),
|
|
448
|
+
workspacePath: optionalOption(parsed, "path")
|
|
449
|
+
});
|
|
424
450
|
}
|
|
425
451
|
if (family === "protocol" && command === "ready") {
|
|
426
452
|
return getReadyProtocols(context, { projectRoot: requiredOption(parsed, "project-root") });
|
|
@@ -432,21 +458,25 @@ async function dispatch(args, context, io) {
|
|
|
432
458
|
});
|
|
433
459
|
}
|
|
434
460
|
if (family === "protocol" && command === "implement") {
|
|
461
|
+
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
435
462
|
const reason = optionalOption(parsed, "reason");
|
|
436
463
|
return implementProtocol(context, {
|
|
437
|
-
protocolId
|
|
438
|
-
projectRoot:
|
|
464
|
+
protocolId,
|
|
465
|
+
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "protocol implement"),
|
|
439
466
|
force: hasOption(parsed, "force"),
|
|
440
467
|
...(reason ? { reason } : {})
|
|
441
468
|
});
|
|
442
469
|
}
|
|
443
470
|
if (family === "protocol" && command === "ready-for-merge") {
|
|
444
|
-
|
|
471
|
+
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
472
|
+
return readyForMerge(context, { protocolId, projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "protocol ready-for-merge") });
|
|
445
473
|
}
|
|
446
474
|
if (family === "protocol" && command === "transition") {
|
|
475
|
+
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
447
476
|
const reason = optionalOption(parsed, "reason");
|
|
448
477
|
return transitionProtocol(context, {
|
|
449
|
-
protocolId
|
|
478
|
+
protocolId,
|
|
479
|
+
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "protocol transition"),
|
|
450
480
|
to: requiredOption(parsed, "to"),
|
|
451
481
|
jsonFile: requiredOptionAlias(parsed, ["payload-file", "json-file"]),
|
|
452
482
|
force: hasOption(parsed, "force"),
|
|
@@ -454,15 +484,19 @@ async function dispatch(args, context, io) {
|
|
|
454
484
|
});
|
|
455
485
|
}
|
|
456
486
|
if (family === "protocol" && command === "sync-from-run") {
|
|
487
|
+
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
457
488
|
return syncProtocolFromRun(context, {
|
|
458
|
-
protocolId
|
|
489
|
+
protocolId,
|
|
490
|
+
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "protocol sync-from-run"),
|
|
459
491
|
runId: requiredOption(parsed, "run"),
|
|
460
492
|
target: optionalOption(parsed, "target") ?? "auto"
|
|
461
493
|
});
|
|
462
494
|
}
|
|
463
495
|
if (family === "protocol" && command === "cancel") {
|
|
496
|
+
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
464
497
|
return cancelProtocol(context, {
|
|
465
|
-
protocolId
|
|
498
|
+
protocolId,
|
|
499
|
+
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "protocol cancel"),
|
|
466
500
|
reason: requiredOption(parsed, "reason"),
|
|
467
501
|
closeSessions: parseOptionalBoolean(optionalOption(parsed, "close-sessions"), "close-sessions") ?? false,
|
|
468
502
|
cancelQueue: parseOptionalBoolean(optionalOption(parsed, "cancel-queue"), "cancel-queue") ?? false,
|
|
@@ -473,9 +507,11 @@ async function dispatch(args, context, io) {
|
|
|
473
507
|
}
|
|
474
508
|
if (family === "transition") {
|
|
475
509
|
const transitionParsed = parseArgs([command ?? "", ...rest]);
|
|
510
|
+
const protocolId = requiredPosition(transitionParsed, 0, "protocol-id");
|
|
476
511
|
const reason = optionalOption(transitionParsed, "reason");
|
|
477
512
|
return transitionProtocol(context, {
|
|
478
|
-
protocolId
|
|
513
|
+
protocolId,
|
|
514
|
+
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "transition"),
|
|
479
515
|
to: requiredOption(transitionParsed, "to"),
|
|
480
516
|
jsonFile: requiredOptionAlias(transitionParsed, ["payload-file", "json-file"]),
|
|
481
517
|
force: hasOption(transitionParsed, "force"),
|
|
@@ -483,22 +519,37 @@ async function dispatch(args, context, io) {
|
|
|
483
519
|
});
|
|
484
520
|
}
|
|
485
521
|
if (family === "plan" && command === "set") {
|
|
522
|
+
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
486
523
|
return setProtocolPlan(context, {
|
|
487
|
-
protocolId
|
|
524
|
+
protocolId,
|
|
525
|
+
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "plan set"),
|
|
488
526
|
file: requiredOption(parsed, "file")
|
|
489
527
|
});
|
|
490
528
|
}
|
|
491
529
|
if (family === "plan" && command === "status") {
|
|
492
|
-
|
|
530
|
+
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
531
|
+
return getPlanStatus(context, { protocolId, projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "plan status") });
|
|
493
532
|
}
|
|
494
533
|
if (family === "plan" && command === "item") {
|
|
495
|
-
return dispatchPlanItem(context, parsed);
|
|
534
|
+
return dispatchPlanItem(context, parsed, requiredScopeProjectRoot(scopeProjectRoot, "plan item"));
|
|
535
|
+
}
|
|
536
|
+
if (family === "prompt" && command === "render") {
|
|
537
|
+
const workspaceRoot = optionalOption(parsed, "workspace-root");
|
|
538
|
+
const taskFile = optionalOption(parsed, "task-file");
|
|
539
|
+
return renderWorkerPrompt(context, {
|
|
540
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
541
|
+
runId: requiredOption(parsed, "run"),
|
|
542
|
+
stage: requiredOption(parsed, "stage"),
|
|
543
|
+
profile: requiredOption(parsed, "profile"),
|
|
544
|
+
...(taskFile ? { taskFile } : { planItemId: requiredOption(parsed, "plan-item") }),
|
|
545
|
+
...(workspaceRoot ? { workspaceRoot } : {})
|
|
546
|
+
});
|
|
496
547
|
}
|
|
497
548
|
if (family === "lane") {
|
|
498
549
|
return dispatchLane(context, command, parsed);
|
|
499
550
|
}
|
|
500
551
|
if (family === "merge-queue") {
|
|
501
|
-
return dispatchMergeQueue(context, command, parsed);
|
|
552
|
+
return dispatchMergeQueue(context, command, parsed, requiredScopeProjectRootForOptional(scopeProjectRoot, command, parsed));
|
|
502
553
|
}
|
|
503
554
|
if (family === "merge") {
|
|
504
555
|
return dispatchMerge(context, command, parsed);
|
|
@@ -543,7 +594,7 @@ async function dispatch(args, context, io) {
|
|
|
543
594
|
return dispatchCodexHook(context, parsed, await readStdin(io.stdin));
|
|
544
595
|
}
|
|
545
596
|
if (family === "worktree") {
|
|
546
|
-
return dispatchWorktree(context, command, parsed);
|
|
597
|
+
return dispatchWorktree(context, command, parsed, requiredScopeProjectRoot(scopeProjectRoot, `worktree ${command ?? ""}`));
|
|
547
598
|
}
|
|
548
599
|
throw new AppError("usage", `Unknown command: ${args.join(" ") || "<empty>"}`, 2);
|
|
549
600
|
}
|
|
@@ -566,6 +617,8 @@ function dispatchEngine(context, command, parsed) {
|
|
|
566
617
|
throw new AppError("usage", `Unknown engine command: ${command ?? "<empty>"}`, 2);
|
|
567
618
|
}
|
|
568
619
|
function dispatchMigration(context, command, parsed) {
|
|
620
|
+
if (command === "impact")
|
|
621
|
+
return assessMigrationImpact(context, { projectRoot: requiredOption(parsed, "project-root"), ...(optionalOption(parsed, "target-version") ? { targetVersion: optionalOption(parsed, "target-version") } : {}), ...(optionalOption(parsed, "mode") ? { mode: optionalOption(parsed, "mode") } : {}) });
|
|
569
622
|
if (command === "plan" || command === "report") {
|
|
570
623
|
return planMigration(context, {
|
|
571
624
|
projectRoot: requiredOption(parsed, "project-root"),
|
|
@@ -594,23 +647,23 @@ function dispatchCanon(context, command, parsed) {
|
|
|
594
647
|
}
|
|
595
648
|
throw new AppError("usage", `Unknown canon command: ${command ?? "<empty>"}`, 2);
|
|
596
649
|
}
|
|
597
|
-
function dispatchMergeQueue(context, command, parsed) {
|
|
650
|
+
function dispatchMergeQueue(context, command, parsed, scopeProjectRoot) {
|
|
598
651
|
if (hasOption(parsed, "session-id")) {
|
|
599
652
|
throw new AppError("usage", "--session-id is no longer accepted on merge-queue commands; use --worker-id", 2);
|
|
600
653
|
}
|
|
601
654
|
if (command === "status") {
|
|
602
|
-
return getMergeQueueStatus(context, { projectRoot:
|
|
655
|
+
return getMergeQueueStatus(context, { projectRoot: requiredOptionOrScope(parsed, scopeProjectRoot, "project-root") });
|
|
603
656
|
}
|
|
604
657
|
if (command === "next") {
|
|
605
658
|
return claimNextMergeJob(context, {
|
|
606
|
-
projectRoot:
|
|
659
|
+
projectRoot: requiredOptionOrScope(parsed, scopeProjectRoot, "project-root"),
|
|
607
660
|
workerId: requiredWorkerId(parsed),
|
|
608
661
|
workspacePath: workspacePathOption(parsed)
|
|
609
662
|
});
|
|
610
663
|
}
|
|
611
664
|
if (command === "wait-next") {
|
|
612
665
|
return waitNextMergeJob(context, {
|
|
613
|
-
projectRoot:
|
|
666
|
+
projectRoot: requiredOptionOrScope(parsed, scopeProjectRoot, "project-root"),
|
|
614
667
|
workerId: requiredWorkerId(parsed),
|
|
615
668
|
timeoutSeconds: optionalNumber(parsed, "timeout"),
|
|
616
669
|
pollIntervalSeconds: optionalNumber(parsed, "poll-interval"),
|
|
@@ -619,16 +672,20 @@ function dispatchMergeQueue(context, command, parsed) {
|
|
|
619
672
|
});
|
|
620
673
|
}
|
|
621
674
|
if (command === "complete") {
|
|
675
|
+
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
622
676
|
return completeMergeJob(context, {
|
|
623
|
-
protocolId
|
|
677
|
+
protocolId,
|
|
678
|
+
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue complete"),
|
|
624
679
|
workerId: requiredWorkerId(parsed),
|
|
625
680
|
workspacePath: workspacePathOption(parsed),
|
|
626
681
|
summary: requiredOption(parsed, "summary")
|
|
627
682
|
});
|
|
628
683
|
}
|
|
629
684
|
if (command === "fail") {
|
|
685
|
+
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
630
686
|
return failMergeJob(context, {
|
|
631
|
-
protocolId
|
|
687
|
+
protocolId,
|
|
688
|
+
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue fail"),
|
|
632
689
|
workerId: requiredWorkerId(parsed),
|
|
633
690
|
workspacePath: workspacePathOption(parsed),
|
|
634
691
|
reason: requiredOption(parsed, "reason"),
|
|
@@ -636,15 +693,19 @@ function dispatchMergeQueue(context, command, parsed) {
|
|
|
636
693
|
});
|
|
637
694
|
}
|
|
638
695
|
if (command === "note") {
|
|
696
|
+
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
639
697
|
return noteMergeJob(context, {
|
|
640
|
-
protocolId
|
|
698
|
+
protocolId,
|
|
699
|
+
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue note"),
|
|
641
700
|
workerId: requiredWorkerId(parsed),
|
|
642
701
|
summary: requiredOption(parsed, "summary")
|
|
643
702
|
});
|
|
644
703
|
}
|
|
645
704
|
if (command === "cancel") {
|
|
705
|
+
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
646
706
|
return cancelMergeQueueJob(context, {
|
|
647
|
-
protocolId
|
|
707
|
+
protocolId,
|
|
708
|
+
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue cancel"),
|
|
648
709
|
reason: requiredOption(parsed, "reason"),
|
|
649
710
|
workerId: optionalOption(parsed, "worker-id"),
|
|
650
711
|
workspacePath: optionalOption(parsed, "path"),
|
|
@@ -655,7 +716,38 @@ function dispatchMergeQueue(context, command, parsed) {
|
|
|
655
716
|
}
|
|
656
717
|
function dispatchMerge(context, command, parsed) {
|
|
657
718
|
if (command === "status") {
|
|
658
|
-
return getMergeWorkerStatus(context, { projectRoot: requiredOption(parsed, "project-root") });
|
|
719
|
+
return getMergeWorkerStatus(context, { projectRoot: requiredOption(parsed, "project-root"), workspacePath: optionalOption(parsed, "path") });
|
|
720
|
+
}
|
|
721
|
+
if (command === "bundle") {
|
|
722
|
+
const action = requiredPosition(parsed, 0, "bundle action");
|
|
723
|
+
if (action === "status") {
|
|
724
|
+
return getMergeBundleStatus(context, { projectRoot: requiredOption(parsed, "project-root"), workspacePath: workspacePathOption(parsed) });
|
|
725
|
+
}
|
|
726
|
+
if (action === "claim") {
|
|
727
|
+
return claimMergeBundle(context, {
|
|
728
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
729
|
+
workerId: requiredWorkerId(parsed),
|
|
730
|
+
workspacePath: workspacePathOption(parsed)
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
if (action === "complete") {
|
|
734
|
+
return completeMergeBundle(context, {
|
|
735
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
736
|
+
workerId: requiredWorkerId(parsed),
|
|
737
|
+
workspacePath: workspacePathOption(parsed),
|
|
738
|
+
summary: requiredOption(parsed, "summary")
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
if (action === "fail") {
|
|
742
|
+
return failMergeBundle(context, {
|
|
743
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
744
|
+
workerId: requiredWorkerId(parsed),
|
|
745
|
+
workspacePath: workspacePathOption(parsed),
|
|
746
|
+
reason: requiredOption(parsed, "reason"),
|
|
747
|
+
requeue: parseBoolean(requiredOption(parsed, "requeue"), "requeue")
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
throw new AppError("usage", `Unknown merge bundle action: ${action}`, 2);
|
|
659
751
|
}
|
|
660
752
|
if (command === "one-shot") {
|
|
661
753
|
return oneShotMergeClaim(context, {
|
|
@@ -764,10 +856,17 @@ function dispatchSession(context, command, parsed) {
|
|
|
764
856
|
reason: requiredOption(parsed, "reason")
|
|
765
857
|
});
|
|
766
858
|
}
|
|
859
|
+
if (command === "usage" && requiredPosition(parsed, 0, "session usage action") === "sync") {
|
|
860
|
+
return syncFlowSessionUsage(context, {
|
|
861
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
862
|
+
sessionId: requiredOption(parsed, "session-id")
|
|
863
|
+
});
|
|
864
|
+
}
|
|
767
865
|
throw new AppError("usage", `Unknown session command: ${command ?? "<empty>"}`, 2);
|
|
768
866
|
}
|
|
769
867
|
function dispatchRun(context, command, parsed) {
|
|
770
868
|
if (command === "start") {
|
|
869
|
+
const protocolOverrides = readOptionalJsonFile(optionalOption(parsed, "protocol-override-file"));
|
|
771
870
|
return startFlowRun(context, {
|
|
772
871
|
projectRoot: requiredOption(parsed, "project-root"),
|
|
773
872
|
workspaceRoot: optionalOption(parsed, "workspace-root"),
|
|
@@ -775,7 +874,11 @@ function dispatchRun(context, command, parsed) {
|
|
|
775
874
|
subjectType: requiredOption(parsed, "subject-type"),
|
|
776
875
|
subjectId: requiredOption(parsed, "subject-id"),
|
|
777
876
|
slug: requiredOption(parsed, "slug"),
|
|
778
|
-
nextAction: optionalOption(parsed, "next-action")
|
|
877
|
+
nextAction: optionalOption(parsed, "next-action"),
|
|
878
|
+
preset: optionalOption(parsed, "preset"),
|
|
879
|
+
taskProfile: readOptionalJsonFile(optionalOption(parsed, "task-profile-file")),
|
|
880
|
+
...(protocolOverrides ? { protocolOverrides } : {}),
|
|
881
|
+
runOverrides: parseFlowFlagOverrides(allOptions(parsed, "flag"))
|
|
779
882
|
});
|
|
780
883
|
}
|
|
781
884
|
if (command === "status") {
|
|
@@ -787,6 +890,43 @@ function dispatchRun(context, command, parsed) {
|
|
|
787
890
|
if (command === "list") {
|
|
788
891
|
return listFlowRuns(context, { projectRoot: requiredOption(parsed, "project-root") });
|
|
789
892
|
}
|
|
893
|
+
if (command === "timeline") {
|
|
894
|
+
return getFlowRunTimeline(context, {
|
|
895
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
896
|
+
runId: requiredPosition(parsed, 0, "run-id"),
|
|
897
|
+
hiddenSections: allOptions(parsed, "hide")
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
if (command === "usage") {
|
|
901
|
+
return getFlowRunUsage(context, {
|
|
902
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
903
|
+
runId: requiredPosition(parsed, 0, "run-id"),
|
|
904
|
+
groupBy: optionalOption(parsed, "group-by")
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
if (command === "flags") {
|
|
908
|
+
const action = requiredPosition(parsed, 0, "flow flags action");
|
|
909
|
+
const runId = requiredPosition(parsed, 1, "run-id");
|
|
910
|
+
if (action === "status") {
|
|
911
|
+
return getFlowRunFlagsStatus(context, {
|
|
912
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
913
|
+
runId
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
if (action === "revise") {
|
|
917
|
+
return reviseFlowRunFlags(context, {
|
|
918
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
919
|
+
runId,
|
|
920
|
+
expectedRevision: Number.parseInt(requiredOption(parsed, "expected-revision"), 10),
|
|
921
|
+
idempotencyKey: requiredOption(parsed, "idempotency-key"),
|
|
922
|
+
flags: parseFlowFlagOverrides(allOptions(parsed, "flag")),
|
|
923
|
+
preset: optionalOption(parsed, "preset"),
|
|
924
|
+
reason: optionalOption(parsed, "reason"),
|
|
925
|
+
allowDowngrade: hasOption(parsed, "allow-downgrade")
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
throw new AppError("usage", `Unknown run flags command: ${action}`, 2);
|
|
929
|
+
}
|
|
790
930
|
if (command === "attach-stage") {
|
|
791
931
|
return attachFlowRunStage(context, {
|
|
792
932
|
projectRoot: requiredOption(parsed, "project-root"),
|
|
@@ -1179,42 +1319,45 @@ function dispatchCodexHook(context, parsed, stdin) {
|
|
|
1179
1319
|
}
|
|
1180
1320
|
throw new AppError("usage", `Unknown codex hook action: ${action}`, 2);
|
|
1181
1321
|
}
|
|
1182
|
-
function dispatchWorktree(context, command, parsed) {
|
|
1322
|
+
function dispatchWorktree(context, command, parsed, scopeProjectRoot) {
|
|
1183
1323
|
if (command === "plan") {
|
|
1184
|
-
return planWorktree(context, { protocolId: requiredOption(parsed, "protocol-id") });
|
|
1324
|
+
return planWorktree(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: scopeProjectRoot });
|
|
1185
1325
|
}
|
|
1186
1326
|
if (command === "create") {
|
|
1187
1327
|
return createWorktreeRecord(context, {
|
|
1188
1328
|
protocolId: requiredOption(parsed, "protocol-id"),
|
|
1329
|
+
projectRoot: scopeProjectRoot,
|
|
1189
1330
|
branch: requiredOption(parsed, "branch"),
|
|
1190
1331
|
base: requiredOption(parsed, "base"),
|
|
1191
1332
|
path: requiredOption(parsed, "path")
|
|
1192
1333
|
});
|
|
1193
1334
|
}
|
|
1194
1335
|
if (command === "status") {
|
|
1195
|
-
return getWorktreeStatus(context, { protocolId: requiredOption(parsed, "protocol-id") });
|
|
1336
|
+
return getWorktreeStatus(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: scopeProjectRoot });
|
|
1196
1337
|
}
|
|
1197
1338
|
if (command === "bootstrap") {
|
|
1198
|
-
return bootstrapWorktree(context, { protocolId: requiredOption(parsed, "protocol-id") });
|
|
1339
|
+
return bootstrapWorktree(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: scopeProjectRoot });
|
|
1199
1340
|
}
|
|
1200
1341
|
if (command === "close") {
|
|
1201
1342
|
return closeWorktree(context, {
|
|
1202
1343
|
protocolId: requiredOption(parsed, "protocol-id"),
|
|
1344
|
+
projectRoot: scopeProjectRoot,
|
|
1203
1345
|
mode: requiredOption(parsed, "mode")
|
|
1204
1346
|
});
|
|
1205
1347
|
}
|
|
1206
1348
|
throw new AppError("usage", `Unknown worktree command: ${command ?? "<empty>"}`, 2);
|
|
1207
1349
|
}
|
|
1208
|
-
function dispatchPlanItem(context, parsed) {
|
|
1350
|
+
function dispatchPlanItem(context, parsed, projectRoot) {
|
|
1209
1351
|
const action = requiredPosition(parsed, 0, "plan item action");
|
|
1210
1352
|
const protocolId = requiredPosition(parsed, 1, "protocol-id");
|
|
1211
1353
|
const itemId = requiredPosition(parsed, 2, "item-id");
|
|
1212
1354
|
if (action === "start") {
|
|
1213
|
-
return startPlanItem(context, { protocolId, itemId });
|
|
1355
|
+
return startPlanItem(context, { protocolId, projectRoot, itemId });
|
|
1214
1356
|
}
|
|
1215
1357
|
if (action === "done") {
|
|
1216
1358
|
return completePlanItem(context, {
|
|
1217
1359
|
protocolId,
|
|
1360
|
+
projectRoot,
|
|
1218
1361
|
itemId,
|
|
1219
1362
|
summary: requiredOption(parsed, "summary"),
|
|
1220
1363
|
evidence: parsed.options.get("evidence") ?? []
|
|
@@ -1223,6 +1366,7 @@ function dispatchPlanItem(context, parsed) {
|
|
|
1223
1366
|
if (action === "block") {
|
|
1224
1367
|
return blockPlanItem(context, {
|
|
1225
1368
|
protocolId,
|
|
1369
|
+
projectRoot,
|
|
1226
1370
|
itemId,
|
|
1227
1371
|
reason: requiredOption(parsed, "reason"),
|
|
1228
1372
|
userRequired: parseBoolean(requiredOption(parsed, "user-required"), "user-required")
|
|
@@ -1231,12 +1375,46 @@ function dispatchPlanItem(context, parsed) {
|
|
|
1231
1375
|
if (action === "skip") {
|
|
1232
1376
|
return skipPlanItem(context, {
|
|
1233
1377
|
protocolId,
|
|
1378
|
+
projectRoot,
|
|
1234
1379
|
itemId,
|
|
1235
1380
|
reason: requiredOption(parsed, "reason")
|
|
1236
1381
|
});
|
|
1237
1382
|
}
|
|
1238
1383
|
throw new AppError("usage", `Unknown plan item action: ${action}`, 2);
|
|
1239
1384
|
}
|
|
1385
|
+
function readOptionalJsonFile(file) {
|
|
1386
|
+
if (!file)
|
|
1387
|
+
return undefined;
|
|
1388
|
+
if (!fs.existsSync(file)) {
|
|
1389
|
+
throw new AppError("not_found", `JSON file does not exist: ${file}`, 1);
|
|
1390
|
+
}
|
|
1391
|
+
try {
|
|
1392
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
1393
|
+
}
|
|
1394
|
+
catch {
|
|
1395
|
+
throw new AppError("validation", `JSON file is malformed: ${file}`, 2);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
function parseFlowFlagOverrides(values) {
|
|
1399
|
+
const result = {};
|
|
1400
|
+
for (const item of values) {
|
|
1401
|
+
const separator = item.indexOf("=");
|
|
1402
|
+
if (separator <= 0) {
|
|
1403
|
+
throw new AppError("usage", "--flag must use key=value", 2, { value: item });
|
|
1404
|
+
}
|
|
1405
|
+
const key = item.slice(0, separator).trim();
|
|
1406
|
+
const raw = item.slice(separator + 1).trim();
|
|
1407
|
+
if (!key || !raw)
|
|
1408
|
+
throw new AppError("usage", "--flag must use non-empty key and value", 2, { value: item });
|
|
1409
|
+
try {
|
|
1410
|
+
result[key] = JSON.parse(raw);
|
|
1411
|
+
}
|
|
1412
|
+
catch {
|
|
1413
|
+
result[key] = raw;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
return result;
|
|
1417
|
+
}
|
|
1240
1418
|
function parseArgs(args) {
|
|
1241
1419
|
const positional = [];
|
|
1242
1420
|
const options = new Map();
|
|
@@ -1281,6 +1459,22 @@ function requiredOption(parsed, key) {
|
|
|
1281
1459
|
}
|
|
1282
1460
|
return value;
|
|
1283
1461
|
}
|
|
1462
|
+
function requiredScopeProjectRoot(root, operation) {
|
|
1463
|
+
if (root)
|
|
1464
|
+
return root;
|
|
1465
|
+
throw new AppError("usage", `${operation} requires --project-root or a unique protocol context`, 2);
|
|
1466
|
+
}
|
|
1467
|
+
function requiredOptionOrScope(parsed, root, key) {
|
|
1468
|
+
return root ?? requiredOption(parsed, key);
|
|
1469
|
+
}
|
|
1470
|
+
function requiredScopeProjectRootForOptional(root, command, parsed) {
|
|
1471
|
+
if (root)
|
|
1472
|
+
return root;
|
|
1473
|
+
if (["complete", "note", "fail", "cancel"].includes(command ?? "")) {
|
|
1474
|
+
return requiredScopeProjectRoot(root, `merge-queue ${command ?? ""}`);
|
|
1475
|
+
}
|
|
1476
|
+
return optionalOption(parsed, "project-root") ?? optionalOption(parsed, "root") ?? null;
|
|
1477
|
+
}
|
|
1284
1478
|
function requiredOptionAlias(parsed, keys) {
|
|
1285
1479
|
for (const key of keys) {
|
|
1286
1480
|
const value = optionalOption(parsed, key);
|
|
@@ -1339,20 +1533,42 @@ async function readStdin(stream) {
|
|
|
1339
1533
|
}
|
|
1340
1534
|
return data;
|
|
1341
1535
|
}
|
|
1342
|
-
function refreshDashboardsAfterMutation(context, args, result) {
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1536
|
+
function refreshDashboardsAfterMutation(context, args, result, resolvedScope) {
|
|
1537
|
+
try {
|
|
1538
|
+
if (args[0] === "project" && args[1] === "archive") {
|
|
1539
|
+
refreshGlobalDashboard(context, {});
|
|
1540
|
+
return [];
|
|
1541
|
+
}
|
|
1542
|
+
const projectRoot = projectRootForMutation(context, args, result, resolvedScope);
|
|
1543
|
+
if (!projectRoot)
|
|
1544
|
+
return [];
|
|
1545
|
+
autoRefreshDashboards(context, { projectRoot });
|
|
1546
|
+
return [];
|
|
1346
1547
|
}
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1548
|
+
catch (error) {
|
|
1549
|
+
return [{
|
|
1550
|
+
code: "dashboard_refresh_failed",
|
|
1551
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1552
|
+
project_root: resolvedScope
|
|
1553
|
+
}];
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
function addWarnings(result, warnings) {
|
|
1557
|
+
if (warnings.length === 0)
|
|
1558
|
+
return result;
|
|
1559
|
+
if (result && typeof result === "object" && !Array.isArray(result)) {
|
|
1560
|
+
const record = result;
|
|
1561
|
+
const existing = Array.isArray(record.warnings) ? record.warnings : [];
|
|
1562
|
+
return { ...record, ok: record.ok === false ? record.ok : true, warnings: [...existing, ...warnings] };
|
|
1350
1563
|
}
|
|
1351
|
-
|
|
1564
|
+
return { ok: true, result, warnings };
|
|
1352
1565
|
}
|
|
1353
|
-
function projectRootForMutation(context, args, result) {
|
|
1566
|
+
function projectRootForMutation(context, args, result, resolvedScope) {
|
|
1354
1567
|
const [family, command, ...rest] = args;
|
|
1355
1568
|
const parsed = parseArgs(rest);
|
|
1569
|
+
if (isScopedProtocolMutation(family, command)) {
|
|
1570
|
+
return resolvedScope ?? resolveOperationProjectRoot(context, args) ?? undefined;
|
|
1571
|
+
}
|
|
1356
1572
|
if (family === "dashboard" || family === "integration") {
|
|
1357
1573
|
return undefined;
|
|
1358
1574
|
}
|
|
@@ -1381,6 +1597,9 @@ function projectRootForMutation(context, args, result) {
|
|
|
1381
1597
|
if (family === "plan" && command === "item") {
|
|
1382
1598
|
return protocolProjectRoot(context, requiredPosition(parsed, 1, "protocol-id"));
|
|
1383
1599
|
}
|
|
1600
|
+
if (family === "prompt" && command === "render") {
|
|
1601
|
+
return requiredOption(parsed, "project-root");
|
|
1602
|
+
}
|
|
1384
1603
|
if (family === "lane") {
|
|
1385
1604
|
return projectRootForLaneMutation(command, parsed);
|
|
1386
1605
|
}
|
|
@@ -1402,7 +1621,7 @@ function projectRootForMutation(context, args, result) {
|
|
|
1402
1621
|
}
|
|
1403
1622
|
return ["stop", "stop-worker"].includes(command ?? "") ? requiredOption(parsed, "project-root") : undefined;
|
|
1404
1623
|
}
|
|
1405
|
-
if (family === "run" && ["start", "attach-stage", "complete-stage", "complete"].includes(command ?? "")) {
|
|
1624
|
+
if (family === "run" && ["start", "attach-stage", "complete-stage", "complete", "flags"].includes(command ?? "")) {
|
|
1406
1625
|
return requiredOption(parsed, "project-root");
|
|
1407
1626
|
}
|
|
1408
1627
|
if (family === "worktree" && ["create", "bootstrap", "close"].includes(command ?? "")) {
|
|
@@ -1446,6 +1665,19 @@ function projectRootForMergeQueueMutation(context, command, parsed) {
|
|
|
1446
1665
|
function protocolProjectRoot(context, protocolId) {
|
|
1447
1666
|
return requireProtocol(context, protocolId).project_root;
|
|
1448
1667
|
}
|
|
1668
|
+
function isScopedProtocolMutation(family, command) {
|
|
1669
|
+
if (family === "transition")
|
|
1670
|
+
return true;
|
|
1671
|
+
if (family === "protocol")
|
|
1672
|
+
return ["transition", "sync-from-run", "ready-for-merge", "cancel"].includes(command ?? "");
|
|
1673
|
+
if (family === "plan")
|
|
1674
|
+
return command === "set" || command === "item";
|
|
1675
|
+
if (family === "worktree")
|
|
1676
|
+
return ["create", "bootstrap", "close"].includes(command ?? "");
|
|
1677
|
+
if (family === "merge-queue")
|
|
1678
|
+
return ["complete", "note", "fail", "cancel"].includes(command ?? "");
|
|
1679
|
+
return false;
|
|
1680
|
+
}
|
|
1449
1681
|
function projectRootFromResult(result) {
|
|
1450
1682
|
if (!result || typeof result !== "object") {
|
|
1451
1683
|
return undefined;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { AppError } from "../shared/errors.js";
|
|
3
|
-
const fullIdPattern = /^([A-Z]{2,4})-(\d{3})-([a-z0-9]+(?:-[a-z0-9]+)*)$/;
|
|
4
|
-
const shortIdPattern = /^([A-Z]{2,4})-\d{3}$/;
|
|
3
|
+
const fullIdPattern = /^([A-Z]{2,4})-(\d{3,})-([a-z0-9]+(?:-[a-z0-9]+)*)$/;
|
|
4
|
+
const shortIdPattern = /^([A-Z]{2,4})-\d{3,}$/;
|
|
5
5
|
export function parseFullEntityId(id) {
|
|
6
6
|
const match = fullIdPattern.exec(id);
|
|
7
7
|
if (!match?.[1] || !match[2] || !match[3]) {
|
|
8
8
|
throw new AppError("validation", `Invalid typed entity id: ${id}`, 2, {
|
|
9
|
-
expected: "TYPE-
|
|
9
|
+
expected: "TYPE-<decimal-sequence>-slug (at least three digits)"
|
|
10
10
|
});
|
|
11
11
|
}
|
|
12
12
|
return {
|
|
@@ -37,7 +37,7 @@ export function formatFullId(type, sequence, slug) {
|
|
|
37
37
|
if (!/^[A-Z]{2,4}$/.test(type)) {
|
|
38
38
|
throw new AppError("validation", `Invalid entity type: ${type}`, 2);
|
|
39
39
|
}
|
|
40
|
-
if (sequence
|
|
40
|
+
if (!Number.isSafeInteger(sequence) || sequence < 1) {
|
|
41
41
|
throw new AppError("validation", `Entity sequence is out of range: ${sequence}`, 2);
|
|
42
42
|
}
|
|
43
43
|
return `${type}-${String(sequence).padStart(3, "0")}-${slug}`;
|