@deksden-com/dd-flow-cli 0.4.0 → 0.4.2
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 +22 -0
- package/README.md +25 -9
- package/dist/build-info.json +5 -5
- package/dist/cli/help.js +45 -29
- package/dist/cli/run-cli.js +229 -49
- 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 +17 -5
- 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 +3 -75
- package/dist/services/dashboard.js +48 -11
- package/dist/services/engines.js +124 -13
- package/dist/services/hooks.js +6 -6
- package/dist/services/ids.js +40 -49
- package/dist/services/merge-queue.js +33 -26
- package/dist/services/merge-worker.js +2 -1
- 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 +77 -46
- 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";
|
|
@@ -17,18 +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 { getFlowSessionStatus, registerFlowSession, stopFlowSession, stopMergeWorker, syncFlowSessionUsage } from "../services/sessions.js";
|
|
21
22
|
import { getProtocolBranchContext } from "../services/branch-context.js";
|
|
22
|
-
import { attachFlowRunStage, completeFlowRun, completeFlowRunStage, getFlowRunStatus, listFlowRuns, startFlowRun } from "../services/runs.js";
|
|
23
|
+
import { attachFlowRunStage, completeFlowRun, completeFlowRunStage, getFlowRunTimeline, getFlowRunUsage, getFlowRunFlagsStatus, getFlowRunStatus, listFlowRuns, reviseFlowRunFlags, startFlowRun } from "../services/runs.js";
|
|
23
24
|
import { previewNextEntityId } from "../services/ids.js";
|
|
24
25
|
import { validateSchema } from "../services/schema-validation.js";
|
|
25
26
|
import { preflightMemoryPermissions } from "../services/memory-permissions.js";
|
|
26
|
-
import { planMigration, verifyMigrationReport } from "../services/migrations.js";
|
|
27
|
+
import { assessMigrationImpact, planMigration, verifyMigrationReport } from "../services/migrations.js";
|
|
27
28
|
import { publishProjectSummary } from "../services/project-summary.js";
|
|
28
29
|
import { requireProjectByRoot } from "../services/projects.js";
|
|
29
30
|
import { resolveProjectRoot } from "../storage/paths.js";
|
|
30
|
-
import { doctorEngines, engineInfo, installCurrentEngine, listEngines, resolveEngine, routeArgsThroughEngine } from "../services/engines.js";
|
|
31
|
+
import { doctorEngines, engineInfo, installCurrentEngine, listEngines, resolveEngine, resolveOperationProjectRoot, routeArgsThroughEngine } from "../services/engines.js";
|
|
31
32
|
import { preflightCliCompatibility } from "../services/compatibility-preflight.js";
|
|
33
|
+
import { renderWorkerPrompt } from "../services/prompts.js";
|
|
32
34
|
const defaultIo = {
|
|
33
35
|
stdout: process.stdout,
|
|
34
36
|
stderr: process.stderr,
|
|
@@ -57,7 +59,8 @@ export async function runCli(args, io = defaultIo, env = process.env) {
|
|
|
57
59
|
}
|
|
58
60
|
const context = createContext(env);
|
|
59
61
|
emitHumanProgress(io, output, progress, "start");
|
|
60
|
-
const
|
|
62
|
+
const scopeProjectRoot = resolveOperationProjectRoot(context, output.args);
|
|
63
|
+
const routed = await routeArgsThroughEngine(context, output.args, io, io.stdin, env, scopeProjectRoot);
|
|
61
64
|
if (routed?.routed && !routed.inProcess) {
|
|
62
65
|
emitHumanProgress(io, output, progress, "done");
|
|
63
66
|
return routed.exitCode;
|
|
@@ -65,11 +68,12 @@ export async function runCli(args, io = defaultIo, env = process.env) {
|
|
|
65
68
|
const dispatchContext = routed?.inProcess
|
|
66
69
|
? createContext({ ...env, DD_FLOW_ENGINE_MODE: "1", DD_FLOW_ENGINE_HOME: "in-process", DD_FLOW_ROUTED_FROM: getCliVersionReport().cli.version })
|
|
67
70
|
: context;
|
|
68
|
-
preflightCliCompatibility(dispatchContext, output.args, env);
|
|
69
|
-
const result = await dispatch(output.args, dispatchContext, io);
|
|
70
|
-
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);
|
|
71
75
|
emitHumanProgress(io, output, progress, "done");
|
|
72
|
-
const payload = attachProgress(
|
|
76
|
+
const payload = attachProgress(resultWithWarnings, progress, output);
|
|
73
77
|
if (output.json)
|
|
74
78
|
writeJson(io.stdout, payload);
|
|
75
79
|
else
|
|
@@ -374,7 +378,7 @@ function stringValue(value) {
|
|
|
374
378
|
function shortSha(value) {
|
|
375
379
|
return value ? value.slice(0, 12) : "unknown";
|
|
376
380
|
}
|
|
377
|
-
async function dispatch(args, context, io) {
|
|
381
|
+
async function dispatch(args, context, io, scopeProjectRoot = null) {
|
|
378
382
|
const [family, command, ...rest] = args;
|
|
379
383
|
const parsed = parseArgs(rest);
|
|
380
384
|
if (family === "engine") {
|
|
@@ -433,13 +437,14 @@ async function dispatch(args, context, io) {
|
|
|
433
437
|
});
|
|
434
438
|
}
|
|
435
439
|
if (family === "protocol" && command === "status") {
|
|
436
|
-
|
|
440
|
+
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
441
|
+
return getProtocolStatus(context, { protocolId, projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "protocol status") });
|
|
437
442
|
}
|
|
438
443
|
if (family === "protocol" && command === "branch-status") {
|
|
439
444
|
const protocolId = parsed.positional[0];
|
|
440
445
|
return getProtocolBranchContext(context, {
|
|
441
446
|
...(protocolId ? { protocolId } : {}),
|
|
442
|
-
projectRoot: optionalOption(parsed, "project-root"),
|
|
447
|
+
projectRoot: scopeProjectRoot ?? optionalOption(parsed, "project-root"),
|
|
443
448
|
workspacePath: optionalOption(parsed, "path")
|
|
444
449
|
});
|
|
445
450
|
}
|
|
@@ -453,21 +458,25 @@ async function dispatch(args, context, io) {
|
|
|
453
458
|
});
|
|
454
459
|
}
|
|
455
460
|
if (family === "protocol" && command === "implement") {
|
|
461
|
+
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
456
462
|
const reason = optionalOption(parsed, "reason");
|
|
457
463
|
return implementProtocol(context, {
|
|
458
|
-
protocolId
|
|
459
|
-
projectRoot:
|
|
464
|
+
protocolId,
|
|
465
|
+
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "protocol implement"),
|
|
460
466
|
force: hasOption(parsed, "force"),
|
|
461
467
|
...(reason ? { reason } : {})
|
|
462
468
|
});
|
|
463
469
|
}
|
|
464
470
|
if (family === "protocol" && command === "ready-for-merge") {
|
|
465
|
-
|
|
471
|
+
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
472
|
+
return readyForMerge(context, { protocolId, projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "protocol ready-for-merge") });
|
|
466
473
|
}
|
|
467
474
|
if (family === "protocol" && command === "transition") {
|
|
475
|
+
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
468
476
|
const reason = optionalOption(parsed, "reason");
|
|
469
477
|
return transitionProtocol(context, {
|
|
470
|
-
protocolId
|
|
478
|
+
protocolId,
|
|
479
|
+
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "protocol transition"),
|
|
471
480
|
to: requiredOption(parsed, "to"),
|
|
472
481
|
jsonFile: requiredOptionAlias(parsed, ["payload-file", "json-file"]),
|
|
473
482
|
force: hasOption(parsed, "force"),
|
|
@@ -475,15 +484,19 @@ async function dispatch(args, context, io) {
|
|
|
475
484
|
});
|
|
476
485
|
}
|
|
477
486
|
if (family === "protocol" && command === "sync-from-run") {
|
|
487
|
+
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
478
488
|
return syncProtocolFromRun(context, {
|
|
479
|
-
protocolId
|
|
489
|
+
protocolId,
|
|
490
|
+
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "protocol sync-from-run"),
|
|
480
491
|
runId: requiredOption(parsed, "run"),
|
|
481
492
|
target: optionalOption(parsed, "target") ?? "auto"
|
|
482
493
|
});
|
|
483
494
|
}
|
|
484
495
|
if (family === "protocol" && command === "cancel") {
|
|
496
|
+
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
485
497
|
return cancelProtocol(context, {
|
|
486
|
-
protocolId
|
|
498
|
+
protocolId,
|
|
499
|
+
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "protocol cancel"),
|
|
487
500
|
reason: requiredOption(parsed, "reason"),
|
|
488
501
|
closeSessions: parseOptionalBoolean(optionalOption(parsed, "close-sessions"), "close-sessions") ?? false,
|
|
489
502
|
cancelQueue: parseOptionalBoolean(optionalOption(parsed, "cancel-queue"), "cancel-queue") ?? false,
|
|
@@ -494,9 +507,11 @@ async function dispatch(args, context, io) {
|
|
|
494
507
|
}
|
|
495
508
|
if (family === "transition") {
|
|
496
509
|
const transitionParsed = parseArgs([command ?? "", ...rest]);
|
|
510
|
+
const protocolId = requiredPosition(transitionParsed, 0, "protocol-id");
|
|
497
511
|
const reason = optionalOption(transitionParsed, "reason");
|
|
498
512
|
return transitionProtocol(context, {
|
|
499
|
-
protocolId
|
|
513
|
+
protocolId,
|
|
514
|
+
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "transition"),
|
|
500
515
|
to: requiredOption(transitionParsed, "to"),
|
|
501
516
|
jsonFile: requiredOptionAlias(transitionParsed, ["payload-file", "json-file"]),
|
|
502
517
|
force: hasOption(transitionParsed, "force"),
|
|
@@ -504,22 +519,37 @@ async function dispatch(args, context, io) {
|
|
|
504
519
|
});
|
|
505
520
|
}
|
|
506
521
|
if (family === "plan" && command === "set") {
|
|
522
|
+
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
507
523
|
return setProtocolPlan(context, {
|
|
508
|
-
protocolId
|
|
524
|
+
protocolId,
|
|
525
|
+
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "plan set"),
|
|
509
526
|
file: requiredOption(parsed, "file")
|
|
510
527
|
});
|
|
511
528
|
}
|
|
512
529
|
if (family === "plan" && command === "status") {
|
|
513
|
-
|
|
530
|
+
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
531
|
+
return getPlanStatus(context, { protocolId, projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "plan status") });
|
|
514
532
|
}
|
|
515
533
|
if (family === "plan" && command === "item") {
|
|
516
|
-
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
|
+
});
|
|
517
547
|
}
|
|
518
548
|
if (family === "lane") {
|
|
519
549
|
return dispatchLane(context, command, parsed);
|
|
520
550
|
}
|
|
521
551
|
if (family === "merge-queue") {
|
|
522
|
-
return dispatchMergeQueue(context, command, parsed);
|
|
552
|
+
return dispatchMergeQueue(context, command, parsed, requiredScopeProjectRootForOptional(scopeProjectRoot, command, parsed));
|
|
523
553
|
}
|
|
524
554
|
if (family === "merge") {
|
|
525
555
|
return dispatchMerge(context, command, parsed);
|
|
@@ -564,7 +594,7 @@ async function dispatch(args, context, io) {
|
|
|
564
594
|
return dispatchCodexHook(context, parsed, await readStdin(io.stdin));
|
|
565
595
|
}
|
|
566
596
|
if (family === "worktree") {
|
|
567
|
-
return dispatchWorktree(context, command, parsed);
|
|
597
|
+
return dispatchWorktree(context, command, parsed, requiredScopeProjectRoot(scopeProjectRoot, `worktree ${command ?? ""}`));
|
|
568
598
|
}
|
|
569
599
|
throw new AppError("usage", `Unknown command: ${args.join(" ") || "<empty>"}`, 2);
|
|
570
600
|
}
|
|
@@ -587,6 +617,8 @@ function dispatchEngine(context, command, parsed) {
|
|
|
587
617
|
throw new AppError("usage", `Unknown engine command: ${command ?? "<empty>"}`, 2);
|
|
588
618
|
}
|
|
589
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") } : {}) });
|
|
590
622
|
if (command === "plan" || command === "report") {
|
|
591
623
|
return planMigration(context, {
|
|
592
624
|
projectRoot: requiredOption(parsed, "project-root"),
|
|
@@ -615,23 +647,23 @@ function dispatchCanon(context, command, parsed) {
|
|
|
615
647
|
}
|
|
616
648
|
throw new AppError("usage", `Unknown canon command: ${command ?? "<empty>"}`, 2);
|
|
617
649
|
}
|
|
618
|
-
function dispatchMergeQueue(context, command, parsed) {
|
|
650
|
+
function dispatchMergeQueue(context, command, parsed, scopeProjectRoot) {
|
|
619
651
|
if (hasOption(parsed, "session-id")) {
|
|
620
652
|
throw new AppError("usage", "--session-id is no longer accepted on merge-queue commands; use --worker-id", 2);
|
|
621
653
|
}
|
|
622
654
|
if (command === "status") {
|
|
623
|
-
return getMergeQueueStatus(context, { projectRoot:
|
|
655
|
+
return getMergeQueueStatus(context, { projectRoot: requiredOptionOrScope(parsed, scopeProjectRoot, "project-root") });
|
|
624
656
|
}
|
|
625
657
|
if (command === "next") {
|
|
626
658
|
return claimNextMergeJob(context, {
|
|
627
|
-
projectRoot:
|
|
659
|
+
projectRoot: requiredOptionOrScope(parsed, scopeProjectRoot, "project-root"),
|
|
628
660
|
workerId: requiredWorkerId(parsed),
|
|
629
661
|
workspacePath: workspacePathOption(parsed)
|
|
630
662
|
});
|
|
631
663
|
}
|
|
632
664
|
if (command === "wait-next") {
|
|
633
665
|
return waitNextMergeJob(context, {
|
|
634
|
-
projectRoot:
|
|
666
|
+
projectRoot: requiredOptionOrScope(parsed, scopeProjectRoot, "project-root"),
|
|
635
667
|
workerId: requiredWorkerId(parsed),
|
|
636
668
|
timeoutSeconds: optionalNumber(parsed, "timeout"),
|
|
637
669
|
pollIntervalSeconds: optionalNumber(parsed, "poll-interval"),
|
|
@@ -640,16 +672,20 @@ function dispatchMergeQueue(context, command, parsed) {
|
|
|
640
672
|
});
|
|
641
673
|
}
|
|
642
674
|
if (command === "complete") {
|
|
675
|
+
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
643
676
|
return completeMergeJob(context, {
|
|
644
|
-
protocolId
|
|
677
|
+
protocolId,
|
|
678
|
+
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue complete"),
|
|
645
679
|
workerId: requiredWorkerId(parsed),
|
|
646
680
|
workspacePath: workspacePathOption(parsed),
|
|
647
681
|
summary: requiredOption(parsed, "summary")
|
|
648
682
|
});
|
|
649
683
|
}
|
|
650
684
|
if (command === "fail") {
|
|
685
|
+
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
651
686
|
return failMergeJob(context, {
|
|
652
|
-
protocolId
|
|
687
|
+
protocolId,
|
|
688
|
+
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue fail"),
|
|
653
689
|
workerId: requiredWorkerId(parsed),
|
|
654
690
|
workspacePath: workspacePathOption(parsed),
|
|
655
691
|
reason: requiredOption(parsed, "reason"),
|
|
@@ -657,15 +693,19 @@ function dispatchMergeQueue(context, command, parsed) {
|
|
|
657
693
|
});
|
|
658
694
|
}
|
|
659
695
|
if (command === "note") {
|
|
696
|
+
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
660
697
|
return noteMergeJob(context, {
|
|
661
|
-
protocolId
|
|
698
|
+
protocolId,
|
|
699
|
+
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue note"),
|
|
662
700
|
workerId: requiredWorkerId(parsed),
|
|
663
701
|
summary: requiredOption(parsed, "summary")
|
|
664
702
|
});
|
|
665
703
|
}
|
|
666
704
|
if (command === "cancel") {
|
|
705
|
+
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
667
706
|
return cancelMergeQueueJob(context, {
|
|
668
|
-
protocolId
|
|
707
|
+
protocolId,
|
|
708
|
+
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue cancel"),
|
|
669
709
|
reason: requiredOption(parsed, "reason"),
|
|
670
710
|
workerId: optionalOption(parsed, "worker-id"),
|
|
671
711
|
workspacePath: optionalOption(parsed, "path"),
|
|
@@ -816,10 +856,17 @@ function dispatchSession(context, command, parsed) {
|
|
|
816
856
|
reason: requiredOption(parsed, "reason")
|
|
817
857
|
});
|
|
818
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
|
+
}
|
|
819
865
|
throw new AppError("usage", `Unknown session command: ${command ?? "<empty>"}`, 2);
|
|
820
866
|
}
|
|
821
867
|
function dispatchRun(context, command, parsed) {
|
|
822
868
|
if (command === "start") {
|
|
869
|
+
const protocolOverrides = readOptionalJsonFile(optionalOption(parsed, "protocol-override-file"));
|
|
823
870
|
return startFlowRun(context, {
|
|
824
871
|
projectRoot: requiredOption(parsed, "project-root"),
|
|
825
872
|
workspaceRoot: optionalOption(parsed, "workspace-root"),
|
|
@@ -827,7 +874,11 @@ function dispatchRun(context, command, parsed) {
|
|
|
827
874
|
subjectType: requiredOption(parsed, "subject-type"),
|
|
828
875
|
subjectId: requiredOption(parsed, "subject-id"),
|
|
829
876
|
slug: requiredOption(parsed, "slug"),
|
|
830
|
-
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"))
|
|
831
882
|
});
|
|
832
883
|
}
|
|
833
884
|
if (command === "status") {
|
|
@@ -839,6 +890,43 @@ function dispatchRun(context, command, parsed) {
|
|
|
839
890
|
if (command === "list") {
|
|
840
891
|
return listFlowRuns(context, { projectRoot: requiredOption(parsed, "project-root") });
|
|
841
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
|
+
}
|
|
842
930
|
if (command === "attach-stage") {
|
|
843
931
|
return attachFlowRunStage(context, {
|
|
844
932
|
projectRoot: requiredOption(parsed, "project-root"),
|
|
@@ -1231,42 +1319,45 @@ function dispatchCodexHook(context, parsed, stdin) {
|
|
|
1231
1319
|
}
|
|
1232
1320
|
throw new AppError("usage", `Unknown codex hook action: ${action}`, 2);
|
|
1233
1321
|
}
|
|
1234
|
-
function dispatchWorktree(context, command, parsed) {
|
|
1322
|
+
function dispatchWorktree(context, command, parsed, scopeProjectRoot) {
|
|
1235
1323
|
if (command === "plan") {
|
|
1236
|
-
return planWorktree(context, { protocolId: requiredOption(parsed, "protocol-id") });
|
|
1324
|
+
return planWorktree(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: scopeProjectRoot });
|
|
1237
1325
|
}
|
|
1238
1326
|
if (command === "create") {
|
|
1239
1327
|
return createWorktreeRecord(context, {
|
|
1240
1328
|
protocolId: requiredOption(parsed, "protocol-id"),
|
|
1329
|
+
projectRoot: scopeProjectRoot,
|
|
1241
1330
|
branch: requiredOption(parsed, "branch"),
|
|
1242
1331
|
base: requiredOption(parsed, "base"),
|
|
1243
1332
|
path: requiredOption(parsed, "path")
|
|
1244
1333
|
});
|
|
1245
1334
|
}
|
|
1246
1335
|
if (command === "status") {
|
|
1247
|
-
return getWorktreeStatus(context, { protocolId: requiredOption(parsed, "protocol-id") });
|
|
1336
|
+
return getWorktreeStatus(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: scopeProjectRoot });
|
|
1248
1337
|
}
|
|
1249
1338
|
if (command === "bootstrap") {
|
|
1250
|
-
return bootstrapWorktree(context, { protocolId: requiredOption(parsed, "protocol-id") });
|
|
1339
|
+
return bootstrapWorktree(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: scopeProjectRoot });
|
|
1251
1340
|
}
|
|
1252
1341
|
if (command === "close") {
|
|
1253
1342
|
return closeWorktree(context, {
|
|
1254
1343
|
protocolId: requiredOption(parsed, "protocol-id"),
|
|
1344
|
+
projectRoot: scopeProjectRoot,
|
|
1255
1345
|
mode: requiredOption(parsed, "mode")
|
|
1256
1346
|
});
|
|
1257
1347
|
}
|
|
1258
1348
|
throw new AppError("usage", `Unknown worktree command: ${command ?? "<empty>"}`, 2);
|
|
1259
1349
|
}
|
|
1260
|
-
function dispatchPlanItem(context, parsed) {
|
|
1350
|
+
function dispatchPlanItem(context, parsed, projectRoot) {
|
|
1261
1351
|
const action = requiredPosition(parsed, 0, "plan item action");
|
|
1262
1352
|
const protocolId = requiredPosition(parsed, 1, "protocol-id");
|
|
1263
1353
|
const itemId = requiredPosition(parsed, 2, "item-id");
|
|
1264
1354
|
if (action === "start") {
|
|
1265
|
-
return startPlanItem(context, { protocolId, itemId });
|
|
1355
|
+
return startPlanItem(context, { protocolId, projectRoot, itemId });
|
|
1266
1356
|
}
|
|
1267
1357
|
if (action === "done") {
|
|
1268
1358
|
return completePlanItem(context, {
|
|
1269
1359
|
protocolId,
|
|
1360
|
+
projectRoot,
|
|
1270
1361
|
itemId,
|
|
1271
1362
|
summary: requiredOption(parsed, "summary"),
|
|
1272
1363
|
evidence: parsed.options.get("evidence") ?? []
|
|
@@ -1275,6 +1366,7 @@ function dispatchPlanItem(context, parsed) {
|
|
|
1275
1366
|
if (action === "block") {
|
|
1276
1367
|
return blockPlanItem(context, {
|
|
1277
1368
|
protocolId,
|
|
1369
|
+
projectRoot,
|
|
1278
1370
|
itemId,
|
|
1279
1371
|
reason: requiredOption(parsed, "reason"),
|
|
1280
1372
|
userRequired: parseBoolean(requiredOption(parsed, "user-required"), "user-required")
|
|
@@ -1283,12 +1375,46 @@ function dispatchPlanItem(context, parsed) {
|
|
|
1283
1375
|
if (action === "skip") {
|
|
1284
1376
|
return skipPlanItem(context, {
|
|
1285
1377
|
protocolId,
|
|
1378
|
+
projectRoot,
|
|
1286
1379
|
itemId,
|
|
1287
1380
|
reason: requiredOption(parsed, "reason")
|
|
1288
1381
|
});
|
|
1289
1382
|
}
|
|
1290
1383
|
throw new AppError("usage", `Unknown plan item action: ${action}`, 2);
|
|
1291
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
|
+
}
|
|
1292
1418
|
function parseArgs(args) {
|
|
1293
1419
|
const positional = [];
|
|
1294
1420
|
const options = new Map();
|
|
@@ -1333,6 +1459,22 @@ function requiredOption(parsed, key) {
|
|
|
1333
1459
|
}
|
|
1334
1460
|
return value;
|
|
1335
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
|
+
}
|
|
1336
1478
|
function requiredOptionAlias(parsed, keys) {
|
|
1337
1479
|
for (const key of keys) {
|
|
1338
1480
|
const value = optionalOption(parsed, key);
|
|
@@ -1391,20 +1533,42 @@ async function readStdin(stream) {
|
|
|
1391
1533
|
}
|
|
1392
1534
|
return data;
|
|
1393
1535
|
}
|
|
1394
|
-
function refreshDashboardsAfterMutation(context, args, result) {
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
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 [];
|
|
1398
1547
|
}
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1548
|
+
catch (error) {
|
|
1549
|
+
return [{
|
|
1550
|
+
code: "dashboard_refresh_failed",
|
|
1551
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1552
|
+
project_root: resolvedScope
|
|
1553
|
+
}];
|
|
1402
1554
|
}
|
|
1403
|
-
autoRefreshDashboards(context, { projectRoot });
|
|
1404
1555
|
}
|
|
1405
|
-
function
|
|
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] };
|
|
1563
|
+
}
|
|
1564
|
+
return { ok: true, result, warnings };
|
|
1565
|
+
}
|
|
1566
|
+
function projectRootForMutation(context, args, result, resolvedScope) {
|
|
1406
1567
|
const [family, command, ...rest] = args;
|
|
1407
1568
|
const parsed = parseArgs(rest);
|
|
1569
|
+
if (isScopedProtocolMutation(family, command)) {
|
|
1570
|
+
return resolvedScope ?? resolveOperationProjectRoot(context, args) ?? undefined;
|
|
1571
|
+
}
|
|
1408
1572
|
if (family === "dashboard" || family === "integration") {
|
|
1409
1573
|
return undefined;
|
|
1410
1574
|
}
|
|
@@ -1433,6 +1597,9 @@ function projectRootForMutation(context, args, result) {
|
|
|
1433
1597
|
if (family === "plan" && command === "item") {
|
|
1434
1598
|
return protocolProjectRoot(context, requiredPosition(parsed, 1, "protocol-id"));
|
|
1435
1599
|
}
|
|
1600
|
+
if (family === "prompt" && command === "render") {
|
|
1601
|
+
return requiredOption(parsed, "project-root");
|
|
1602
|
+
}
|
|
1436
1603
|
if (family === "lane") {
|
|
1437
1604
|
return projectRootForLaneMutation(command, parsed);
|
|
1438
1605
|
}
|
|
@@ -1454,7 +1621,7 @@ function projectRootForMutation(context, args, result) {
|
|
|
1454
1621
|
}
|
|
1455
1622
|
return ["stop", "stop-worker"].includes(command ?? "") ? requiredOption(parsed, "project-root") : undefined;
|
|
1456
1623
|
}
|
|
1457
|
-
if (family === "run" && ["start", "attach-stage", "complete-stage", "complete"].includes(command ?? "")) {
|
|
1624
|
+
if (family === "run" && ["start", "attach-stage", "complete-stage", "complete", "flags"].includes(command ?? "")) {
|
|
1458
1625
|
return requiredOption(parsed, "project-root");
|
|
1459
1626
|
}
|
|
1460
1627
|
if (family === "worktree" && ["create", "bootstrap", "close"].includes(command ?? "")) {
|
|
@@ -1498,6 +1665,19 @@ function projectRootForMergeQueueMutation(context, command, parsed) {
|
|
|
1498
1665
|
function protocolProjectRoot(context, protocolId) {
|
|
1499
1666
|
return requireProtocol(context, protocolId).project_root;
|
|
1500
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
|
+
}
|
|
1501
1681
|
function projectRootFromResult(result) {
|
|
1502
1682
|
if (!result || typeof result !== "object") {
|
|
1503
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}`;
|