@deksden-com/dd-flow-cli 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +666 -0
- package/README.md +7 -2
- package/dist/build-info.json +5 -5
- package/dist/cli/help.js +88 -10
- package/dist/cli/run-cli.js +523 -28
- package/dist/domain/stage-catalog.js +22 -0
- package/dist/domain/validation.js +1 -1
- package/dist/schemas/code-review-decision.schema.json +26 -0
- package/dist/schemas/code-review-result.schema.json +14 -0
- package/dist/schemas/code-verification.schema.json +14 -0
- package/dist/schemas/code-work-batch.schema.json +24 -0
- package/dist/schemas/code-work-result.schema.json +16 -0
- package/dist/schemas/compatibility.schema.json +32 -0
- package/dist/schemas/flow-contract.schema.json +9 -5
- package/dist/schemas/flow-run.schema.json +16 -123
- package/dist/schemas/plan-aspect-map.schema.json +22 -0
- package/dist/schemas/plan-review-decision.schema.json +14 -0
- package/dist/schemas/plan-review-result.schema.json +42 -0
- package/dist/schemas/protocol-plan.schema.json +15 -182
- package/dist/schemas/stage-finish-input.schema.json +16 -2
- package/dist/schemas/stage-report.schema.json +8 -7
- package/dist/schemas/stage-start-response.schema.json +4 -2
- package/dist/schemas/status-report.schema.json +76 -0
- package/dist/schemas/vnext-protocol-plan.schema.json +37 -0
- package/dist/schemas/vnext-protocolize-result.schema.json +29 -0
- package/dist/schemas/vnext-specify.schema.json +45 -0
- package/dist/services/branch-context.js +1 -1
- package/dist/services/cleanup.js +8 -8
- package/dist/services/cli-operation-classifier.js +10 -2
- package/dist/services/code-checks.js +244 -0
- package/dist/services/config.js +7 -1
- package/dist/services/dashboard.js +12 -12
- package/dist/services/engines.js +1 -1
- package/dist/services/eval-snapshots.js +404 -0
- package/dist/services/hooks.js +774 -18
- package/dist/services/ids.js +16 -6
- package/dist/services/lanes.js +1 -1
- package/dist/services/merge-queue.js +5 -5
- package/dist/services/merge-worker.js +2 -2
- package/dist/services/migrations.js +2 -2
- package/dist/services/plan-runtime.js +1 -1
- package/dist/services/projects.js +4 -4
- package/dist/services/prompts.js +17 -11
- package/dist/services/protocols.js +8 -8
- package/dist/services/run-projection.js +49 -13
- package/dist/services/runs.js +504 -51
- package/dist/services/schema-validation.js +21 -3
- package/dist/services/sessions.js +51 -12
- package/dist/services/stage-blocker.js +57 -0
- package/dist/services/stage-context.js +90 -0
- package/dist/services/stage-lifecycle.js +198 -75
- package/dist/services/stage-pause.js +175 -0
- package/dist/services/stage-report-renderer.js +65 -0
- package/dist/services/usage.js +526 -18
- package/dist/services/vnext-code-review.js +305 -0
- package/dist/services/vnext-code.js +686 -0
- package/dist/services/vnext-contracts.js +1 -0
- package/dist/services/vnext-execution-profile.js +27 -0
- package/dist/services/vnext-fanout.js +79 -0
- package/dist/services/vnext-plan-review.js +499 -0
- package/dist/services/vnext-plan.js +552 -0
- package/dist/services/vnext-protocolize.js +542 -0
- package/dist/services/vnext-specify.js +595 -0
- package/dist/services/vnext-workspace-policy.js +87 -0
- package/dist/services/work-registry.js +522 -0
- package/dist/services/worktrees.js +58 -37
- package/dist/storage/database.js +263 -34
- package/dist/storage/paths.js +47 -1
- package/package.json +12 -12
package/dist/cli/run-cli.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
import { createContext, createRouterContext } from "../runtime/context.js";
|
|
3
4
|
import { helpForArgs } from "./help.js";
|
|
4
5
|
import { AppError, isAppError } from "../shared/errors.js";
|
|
@@ -9,7 +10,7 @@ import { blockPlanItem, completePlanItem, getPlanStatus, skipPlanItem, startPlan
|
|
|
9
10
|
import { cancelMergeQueueJob, claimNextMergeJob, claimMergeJob, claimMergeBundle, completeMergeBundle, completeMergeJob, failMergeBundle, failMergeJob, getMergeBundleStatus, getMergeQueueStatus, noteMergeJob, waitNextMergeJob } from "../services/merge-queue.js";
|
|
10
11
|
import { getMergeWorkerStatus, oneShotMergeClaim, startMergeWorker, stopProjectMergeWorker } from "../services/merge-worker.js";
|
|
11
12
|
import { cleanupApply, cleanupScan } from "../services/cleanup.js";
|
|
12
|
-
import { getCodexHomeStatus, getCodexHooksStatus, handleCodexHook, initCodexHome, installCodexHooks, planCodexHome, printCodexHomeEnv, printCodexHooks, removeCodexHome, removeCodexHooks } from "../services/hooks.js";
|
|
13
|
+
import { getCodexHomeStatus, getCodexHooksStatus, handleCodexHook, handleAgyEvent, handleGrokEvent, handleOpenCodeEvent, handleZcodeEvent, isolatedDdFlowHomeFromHookStdin, claimBootstrapHookEvent, findRecentMatchingHookEvent, stageStartMatchKey, initCodexHome, installCodexHooks, planCodexHome, printCodexHomeEnv, printCodexHooks, removeCodexHome, removeCodexHooks } from "../services/hooks.js";
|
|
13
14
|
import { bootstrapWorktree, closeWorktree, createWorktreeRecord, getWorktreeStatus, planWorktree } from "../services/worktrees.js";
|
|
14
15
|
import { acquireLaneLock, cancelLaneWaiter, checkLaneWorkspace, getLaneWaiters, getLaneStatus, heartbeatLaneLock, releaseLaneLock, setLaneWorkspace, waitAcquireLaneLock, waitForLaneLock } from "../services/lanes.js";
|
|
15
16
|
import { getProjectConfigStatus, setProjectConfigValue } from "../services/config.js";
|
|
@@ -20,19 +21,32 @@ import { autoRefreshDashboards, dashboardData, getCmuxStatus, openDashboard, ope
|
|
|
20
21
|
import { dashboardTargetSummary, preferredDashboardCommand, relatedDashboardCommands, resolveDashboardTarget } from "../services/dashboard-targets.js";
|
|
21
22
|
import { getFlowSessionStatus, registerFlowSession, stopFlowSession, stopMergeWorker, syncFlowSessionUsage } from "../services/sessions.js";
|
|
22
23
|
import { getProtocolBranchContext } from "../services/branch-context.js";
|
|
23
|
-
import { attachFlowRunStage, completeFlowRun, completeFlowRunStage, getFlowRunTimeline,
|
|
24
|
+
import { attachFlowRunStage, completeFlowRun, completeFlowRunStage, getFlowRunSessions, getFlowRunTimeline, getStoredFlowRunUsage, getFlowRunFlagsStatus, getFlowRunStatus, getFlowRunConfig, getFlowRunVariables, listFlowRuns, reviseFlowRunFlags, setFlowRunConfig, setFlowRunVariable, startFlowRun } from "../services/runs.js";
|
|
24
25
|
import { previewNextEntityId } from "../services/ids.js";
|
|
25
26
|
import { validateSchema } from "../services/schema-validation.js";
|
|
26
27
|
import { preflightMemoryPermissions } from "../services/memory-permissions.js";
|
|
28
|
+
import { ingestAgyUsage, ingestGrokUsage, ingestOpenCodeUsage, ingestZcodeUsage } from "../services/usage.js";
|
|
27
29
|
import { applyMigration, assessMigrationImpact, assertUpgradeRunStorageReady, planMigration, verifyMigrationReport } from "../services/migrations.js";
|
|
28
30
|
import { publishProjectSummary } from "../services/project-summary.js";
|
|
29
31
|
import { requireProjectByRoot } from "../services/projects.js";
|
|
30
32
|
import { resolveProjectRoot } from "../storage/paths.js";
|
|
31
33
|
import { doctorEngines, bindRunEngine, engineInfo, installCurrentEngine, listEngines, resolveEngine, resolveExplicitProjectRoot, resolveOperationProjectRoot, routeArgsThroughEngine } from "../services/engines.js";
|
|
32
34
|
import { preflightCliCompatibility } from "../services/compatibility-preflight.js";
|
|
35
|
+
import { finishVnextSpecifyStage, getVnextWorkStatus, isVnextSpecifyFlow, isVnextSpecifyRun, launchVnextSpecify } from "../services/vnext-specify.js";
|
|
36
|
+
import { finishVnextProtocolize, isVnextProtocolizeRun, startVnextProtocolize } from "../services/vnext-protocolize.js";
|
|
37
|
+
import { finishVnextPlan, isVnextPlanRun, startVnextPlan } from "../services/vnext-plan.js";
|
|
38
|
+
import { dispatchVnextPlanReview, finishVnextPlanReview, isVnextPlanReviewRun, recordVnextPlanReviewCapacity, startVnextPlanReview } from "../services/vnext-plan-review.js";
|
|
39
|
+
import { addVnextCodeRepair, finishVnextCode, startVnextCode } from "../services/vnext-code.js";
|
|
40
|
+
import { addVnextCodeReviewRepair, finishVnextCodeReview, isVnextCodeReviewRun, startVnextCodeReview } from "../services/vnext-code-review.js";
|
|
41
|
+
import { getVnextFanoutStatus } from "../services/vnext-fanout.js";
|
|
42
|
+
import { pauseStageForUser, resumeStageAfterUser } from "../services/stage-pause.js";
|
|
43
|
+
import { blockStageForRuntime, unblockStageAfterRuntimeRepair } from "../services/stage-blocker.js";
|
|
33
44
|
import { classifyCliOperation, requiresExplicitUpgradeAuthorization } from "../services/cli-operation-classifier.js";
|
|
34
45
|
import { renderWorkerPrompt } from "../services/prompts.js";
|
|
35
46
|
import { finishStage, startStage } from "../services/stage-lifecycle.js";
|
|
47
|
+
import { addWorkBatch, cancelWork, deleteWork, failWork, finishWork, listWorks, mutateWorkDeps, retryWork, showWork, startWork } from "../services/work-registry.js";
|
|
48
|
+
import { createEvalBootstrapSnapshot, createEvalRunSnapshot, prepareVnextSpecifyRun, restoreEvalBootstrapSnapshot, restoreEvalRunSnapshot } from "../services/eval-snapshots.js";
|
|
49
|
+
import { loadExternalStageContext } from "../services/stage-context.js";
|
|
36
50
|
const defaultIo = {
|
|
37
51
|
stdout: process.stdout,
|
|
38
52
|
stderr: process.stderr,
|
|
@@ -42,10 +56,8 @@ const flagValue = "__dd_flow_flag__";
|
|
|
42
56
|
export async function runCli(args, io = defaultIo, env = process.env) {
|
|
43
57
|
const output = parseOutputOptions(args);
|
|
44
58
|
const progress = progressForCommand(output.args);
|
|
59
|
+
let heartbeat;
|
|
45
60
|
try {
|
|
46
|
-
if (args.includes("--progress-jsonl")) {
|
|
47
|
-
throw new AppError("usage", "--progress-jsonl is not implemented yet; use --json for final structured progress", 2);
|
|
48
|
-
}
|
|
49
61
|
if (output.args.length === 1 && output.args[0] === "--version") {
|
|
50
62
|
const payload = getCliVersionReport();
|
|
51
63
|
if (output.json)
|
|
@@ -68,9 +80,15 @@ export async function runCli(args, io = defaultIo, env = process.env) {
|
|
|
68
80
|
}
|
|
69
81
|
const routerContext = createRouterContext(env);
|
|
70
82
|
emitHumanProgress(io, output, progress, "start");
|
|
83
|
+
if (output.progressJsonl) {
|
|
84
|
+
writeProgressJsonl(io, "start", `Starting ${output.args.slice(0, 3).join(" ")}`);
|
|
85
|
+
heartbeat = setInterval(() => writeProgressJsonl(io, "running", "Command is still running"), 15_000);
|
|
86
|
+
}
|
|
71
87
|
const scopeProjectRoot = resolveProjectRootBeforeRouting(output.args, env);
|
|
72
|
-
const routed = await routeArgsThroughEngine(routerContext, output.json ? [...output.args, "--json"] : output.args, io, io.stdin, env, scopeProjectRoot);
|
|
88
|
+
const routed = await routeArgsThroughEngine(routerContext, output.json ? [...output.args, "--json", ...(output.progressJsonl ? ["--progress-jsonl"] : [])] : output.args, io, io.stdin, env, scopeProjectRoot);
|
|
73
89
|
if (routed?.routed && !routed.inProcess) {
|
|
90
|
+
if (heartbeat)
|
|
91
|
+
clearInterval(heartbeat);
|
|
74
92
|
emitHumanProgress(io, output, progress, "done");
|
|
75
93
|
return routed.exitCode;
|
|
76
94
|
}
|
|
@@ -78,9 +96,13 @@ export async function runCli(args, io = defaultIo, env = process.env) {
|
|
|
78
96
|
? createContext({ ...env, DD_FLOW_ENGINE_MODE: "1", DD_FLOW_ENGINE_HOME: "in-process", DD_FLOW_ROUTED_FROM: getCliVersionReport().cli.version }, databaseModeForCommand(output.args, classification))
|
|
79
97
|
: createContext(env, databaseModeForCommand(output.args, classification));
|
|
80
98
|
preflightCliCompatibility(dispatchContext, output.args, env, scopeProjectRoot);
|
|
81
|
-
const result = await dispatch(output.args, dispatchContext, io, scopeProjectRoot, classification);
|
|
99
|
+
const result = await dispatch(output.args, dispatchContext, io, scopeProjectRoot, classification, output);
|
|
82
100
|
const refreshWarnings = refreshDashboardsAfterMutation(dispatchContext, output.args, result, scopeProjectRoot);
|
|
83
101
|
const resultWithWarnings = addWarnings(result, refreshWarnings);
|
|
102
|
+
if (heartbeat)
|
|
103
|
+
clearInterval(heartbeat);
|
|
104
|
+
if (output.progressJsonl)
|
|
105
|
+
writeProgressJsonl(io, "done", "Command finished");
|
|
84
106
|
emitHumanProgress(io, output, progress, "done");
|
|
85
107
|
const payload = attachProgress(resultWithWarnings, progress, output);
|
|
86
108
|
if (output.json)
|
|
@@ -90,12 +112,18 @@ export async function runCli(args, io = defaultIo, env = process.env) {
|
|
|
90
112
|
return completedCommandExitCode(payload);
|
|
91
113
|
}
|
|
92
114
|
catch (error) {
|
|
115
|
+
if (heartbeat)
|
|
116
|
+
clearInterval(heartbeat);
|
|
93
117
|
const payload = isAppError(error)
|
|
94
118
|
? { ok: false, error: { code: error.code, message: error.message, details: error.details } }
|
|
95
119
|
: { ok: false, error: { code: "unexpected", message: String(error) } };
|
|
96
120
|
emitHumanProgress(io, output, progress, "failed");
|
|
121
|
+
if (output.progressJsonl)
|
|
122
|
+
writeProgressJsonl(io, "failed", payload.error.message, { code: payload.error.code });
|
|
97
123
|
const errorPayload = attachProgress(payload, progress, output);
|
|
98
|
-
if (output.json)
|
|
124
|
+
if (output.json && output.progressJsonl)
|
|
125
|
+
writeJson(io.stdout, errorPayload);
|
|
126
|
+
else if (output.json)
|
|
99
127
|
writeJson(io.stderr, errorPayload);
|
|
100
128
|
else
|
|
101
129
|
io.stderr.write(renderHumanError(payload));
|
|
@@ -157,15 +185,20 @@ function renderHumanError(payload) {
|
|
|
157
185
|
function parseOutputOptions(args) {
|
|
158
186
|
const stripped = [];
|
|
159
187
|
let json = false;
|
|
188
|
+
let progressJsonl = false;
|
|
160
189
|
for (const arg of args) {
|
|
161
190
|
if (arg === "--json") {
|
|
162
191
|
json = true;
|
|
163
192
|
continue;
|
|
164
193
|
}
|
|
194
|
+
if (arg === "--progress-jsonl") {
|
|
195
|
+
progressJsonl = true;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
165
198
|
stripped.push(arg);
|
|
166
199
|
}
|
|
167
200
|
const hookJsonOnly = stripped[0] === "codex" && stripped[1] === "hook" && stripped[2] === "handle";
|
|
168
|
-
return { json: json || hookJsonOnly, args: stripped, hookJsonOnly };
|
|
201
|
+
return { json: json || hookJsonOnly, progressJsonl, args: stripped, hookJsonOnly };
|
|
169
202
|
}
|
|
170
203
|
function progressForCommand(args) {
|
|
171
204
|
const [family, command, subcommand] = args;
|
|
@@ -211,6 +244,12 @@ function emitHumanProgress(io, output, progress, phase) {
|
|
|
211
244
|
}
|
|
212
245
|
}
|
|
213
246
|
}
|
|
247
|
+
function writeProgressJsonl(io, phase, message, detail) {
|
|
248
|
+
io.stderr.write(`${JSON.stringify({ schema_id: "dd-flow/progress@1", phase, message, at: new Date().toISOString(), ...(detail ? { detail } : {}) })}\n`);
|
|
249
|
+
}
|
|
250
|
+
function commandProgress(io, output) {
|
|
251
|
+
return (message) => output.progressJsonl ? writeProgressJsonl(io, "running", message) : io.stderr.write(`[running] ${message}\n`);
|
|
252
|
+
}
|
|
214
253
|
function attachProgress(result, progress, output) {
|
|
215
254
|
if (progress.length === 0 || output.hookJsonOnly)
|
|
216
255
|
return result;
|
|
@@ -416,7 +455,7 @@ function stringValue(value) {
|
|
|
416
455
|
function shortSha(value) {
|
|
417
456
|
return value ? value.slice(0, 12) : "unknown";
|
|
418
457
|
}
|
|
419
|
-
async function dispatch(args, context, io, scopeProjectRoot = null, classification = classifyCliOperation(args, context.env)) {
|
|
458
|
+
async function dispatch(args, context, io, scopeProjectRoot = null, classification = classifyCliOperation(args, context.env), output = parseOutputOptions(args)) {
|
|
420
459
|
const [family, command, ...rest] = args;
|
|
421
460
|
const parsed = parseArgs(rest);
|
|
422
461
|
if (family === "engine") {
|
|
@@ -560,6 +599,32 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
560
599
|
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
561
600
|
return getPlanStatus(context, { protocolId, projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "plan status") });
|
|
562
601
|
}
|
|
602
|
+
if (family === "plan" && command === "reviews") {
|
|
603
|
+
throw new AppError("usage", "PLAN review dispatch moved to: dd-flow plan-review dispatch <RUN>", 2);
|
|
604
|
+
}
|
|
605
|
+
if (family === "plan-review" && command === "dispatch") {
|
|
606
|
+
return dispatchVnextPlanReview(context, { projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "plan-review dispatch"), runId: requiredPosition(parsed, 0, "run-id") });
|
|
607
|
+
}
|
|
608
|
+
if (family === "stage" && command === "fanout") {
|
|
609
|
+
const action = requiredPosition(parsed, 0, "fanout action");
|
|
610
|
+
const runId = requiredPosition(parsed, 1, "run-id");
|
|
611
|
+
const stage = requiredOption(parsed, "stage");
|
|
612
|
+
const projectRoot = requiredScopeProjectRoot(scopeProjectRoot, "stage fanout");
|
|
613
|
+
if (action === "status")
|
|
614
|
+
return getVnextFanoutStatus(context, { projectRoot, runId, stage });
|
|
615
|
+
if (action === "dispatch") {
|
|
616
|
+
if (stage === "plan-review")
|
|
617
|
+
return dispatchVnextPlanReview(context, { projectRoot, runId });
|
|
618
|
+
return getVnextFanoutStatus(context, { projectRoot, runId, stage });
|
|
619
|
+
}
|
|
620
|
+
throw new AppError("usage", "Use: dd-flow stage fanout <status|dispatch> <RUN> --stage <stage> --project-root <path>", 2);
|
|
621
|
+
}
|
|
622
|
+
if (family === "run" && command === "capacity") {
|
|
623
|
+
if (requiredPosition(parsed, 0, "capacity action") !== "record")
|
|
624
|
+
throw new AppError("usage", "Use: dd-flow run capacity record <RUN> --available-slots <0..15> --project-root <path>", 2);
|
|
625
|
+
const availableSlots = Number(requiredOption(parsed, "available-slots"));
|
|
626
|
+
return recordVnextPlanReviewCapacity(context, { projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "run capacity record"), runId: requiredPosition(parsed, 1, "run-id"), availableSlots });
|
|
627
|
+
}
|
|
563
628
|
if (family === "plan" && command === "item") {
|
|
564
629
|
return dispatchPlanItem(context, parsed, requiredScopeProjectRoot(scopeProjectRoot, "plan item"));
|
|
565
630
|
}
|
|
@@ -575,31 +640,277 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
575
640
|
...(workspaceRoot ? { workspaceRoot } : {})
|
|
576
641
|
});
|
|
577
642
|
}
|
|
643
|
+
if (family === "work" && command === "status") {
|
|
644
|
+
return getVnextWorkStatus(context, {
|
|
645
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
646
|
+
workId: requiredPosition(parsed, 0, "work-id")
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
if (family === "work" && command === "add-batch") {
|
|
650
|
+
return addWorkBatch(context, { parentWorkId: requiredOption(parsed, "parent"), file: requiredOption(parsed, "file") });
|
|
651
|
+
}
|
|
652
|
+
if (family === "work" && command === "ls") {
|
|
653
|
+
return listWorks(context, { ...(optionalOption(parsed, "run") ? { runId: optionalOption(parsed, "run") } : {}), ...(optionalOption(parsed, "parent") ? { parentWorkId: optionalOption(parsed, "parent") } : {}), ...(optionalOption(parsed, "status") ? { status: optionalOption(parsed, "status") } : {}), ...(hasOption(parsed, "ready") ? { ready: true } : {}), ...(hasOption(parsed, "include-results") ? { includeResults: true } : {}), ...(optionalOption(parsed, "limit") ? { limit: Number(optionalOption(parsed, "limit")) } : {}) });
|
|
654
|
+
}
|
|
655
|
+
if (family === "work" && command === "show")
|
|
656
|
+
return showWork(context, requiredPosition(parsed, 0, "work-id"));
|
|
657
|
+
if (family === "work" && command === "repair" && requiredPosition(parsed, 0, "action") === "add") {
|
|
658
|
+
if (!hasOption(parsed, "task-stdin"))
|
|
659
|
+
throw new AppError("usage", "work repair add requires --task-stdin", 2);
|
|
660
|
+
const origins = parsed.options.get("origin-work") ?? [];
|
|
661
|
+
const findings = parsed.options.get("from-finding") ?? [];
|
|
662
|
+
if (!origins.length && !findings.length)
|
|
663
|
+
throw new AppError("usage", "work repair add requires --from-check with --origin-work or --from-finding", 2);
|
|
664
|
+
if (findings.length)
|
|
665
|
+
return addVnextCodeReviewRepair(context, {
|
|
666
|
+
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "work repair add"),
|
|
667
|
+
runId: requiredOption(parsed, "run"),
|
|
668
|
+
findingIds: findings,
|
|
669
|
+
objective: await readStdin(io.stdin)
|
|
670
|
+
});
|
|
671
|
+
if (!hasOption(parsed, "from-check"))
|
|
672
|
+
throw new AppError("usage", "check repair requires --from-check", 2);
|
|
673
|
+
if (!origins.length)
|
|
674
|
+
throw new AppError("usage", "check repair requires at least one --origin-work", 2);
|
|
675
|
+
return addVnextCodeRepair(context, {
|
|
676
|
+
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "work repair add"),
|
|
677
|
+
runId: requiredOption(parsed, "run"),
|
|
678
|
+
checkReceiptId: requiredOption(parsed, "from-check"),
|
|
679
|
+
originWorkIds: origins,
|
|
680
|
+
objective: await readStdin(io.stdin)
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
if (family === "work" && command === "deps") {
|
|
684
|
+
const on = parsed.options.get("on") ?? [];
|
|
685
|
+
return mutateWorkDeps(context, { workId: requiredPosition(parsed, 1, "work-id"), action: requiredPosition(parsed, 0, "action"), ...(on.length ? { on } : {}) });
|
|
686
|
+
}
|
|
687
|
+
if (family === "work" && command === "delete")
|
|
688
|
+
return deleteWork(context, requiredPosition(parsed, 0, "work-id"));
|
|
689
|
+
if (family === "work" && command === "start") {
|
|
690
|
+
const hookEventId = optionalOption(parsed, "hook-event-id");
|
|
691
|
+
const projectRoot = requiredScopeProjectRoot(scopeProjectRoot, "work start");
|
|
692
|
+
return startWork(context, requiredPosition(parsed, 0, "work-id"), { ...(hookEventId ? { hookEventId } : {}), projectRoot });
|
|
693
|
+
}
|
|
694
|
+
if (family === "work" && command === "finish") {
|
|
695
|
+
const resultFile = optionalOption(parsed, "result-file");
|
|
696
|
+
const resultStdin = hasOption(parsed, "result-stdin");
|
|
697
|
+
if (Boolean(resultFile) === resultStdin)
|
|
698
|
+
throw new AppError("usage", "work finish requires exactly one of --result-file or --result-stdin", 2);
|
|
699
|
+
const result = resultFile ? fs.readFileSync(path.resolve(resultFile), "utf8") : await readStdin(io.stdin);
|
|
700
|
+
return await finishWork(context, requiredPosition(parsed, 0, "work-id"), result, commandProgress(io, output));
|
|
701
|
+
}
|
|
702
|
+
if (family === "work" && command === "fail")
|
|
703
|
+
return failWork(context, requiredPosition(parsed, 0, "work-id"), requiredOption(parsed, "reason"));
|
|
704
|
+
if (family === "work" && command === "cancel")
|
|
705
|
+
return cancelWork(context, requiredPosition(parsed, 0, "work-id"), requiredOption(parsed, "reason"));
|
|
706
|
+
if (family === "work" && command === "retry")
|
|
707
|
+
return retryWork(context, requiredPosition(parsed, 0, "work-id"), requiredOption(parsed, "reason"));
|
|
578
708
|
if (family === "stage" && command === "start") {
|
|
579
709
|
const dir = optionalOption(parsed, "dir");
|
|
580
710
|
const bootstrap = hasOption(parsed, "bootstrap");
|
|
581
711
|
const subject = optionalOption(parsed, "subject");
|
|
582
712
|
const intakeFile = optionalOption(parsed, "intake-file");
|
|
583
|
-
const
|
|
713
|
+
const intakeStdin = hasOption(parsed, "intake-stdin");
|
|
714
|
+
const contextFile = optionalOption(parsed, "context-file");
|
|
715
|
+
const contextSha256 = optionalOption(parsed, "context-sha256");
|
|
716
|
+
if (Boolean(contextFile) !== Boolean(contextSha256))
|
|
717
|
+
throw new AppError("usage", "stage start requires --context-file together with --context-sha256", 2);
|
|
718
|
+
if (hasOption(parsed, "session-id")) {
|
|
719
|
+
throw new AppError("usage", "--session-id is managed by the harness lifecycle adapter and is not accepted", 2);
|
|
720
|
+
}
|
|
721
|
+
let hookEventId = optionalOption(parsed, "hook-event-id");
|
|
722
|
+
const requireSessionBinding = hasOption(parsed, "require-session-binding");
|
|
723
|
+
const projectRoot = requiredOption(parsed, "project-root");
|
|
724
|
+
const stage = requiredOption(parsed, "stage");
|
|
725
|
+
const externalContext = contextFile && contextSha256 ? loadExternalStageContext({ file: contextFile, expectedSha256: contextSha256, stage }) : undefined;
|
|
726
|
+
const claimVnextStageStart = (runId) => {
|
|
727
|
+
registerProject(context, { root: projectRoot });
|
|
728
|
+
return findRecentMatchingHookEvent(context, {
|
|
729
|
+
projectId: requireProjectByRoot(context, resolveProjectRoot(projectRoot)).id,
|
|
730
|
+
matchKey: stageStartMatchKey(runId, stage, projectRoot, contextSha256),
|
|
731
|
+
errorCode: "trusted_session_binding_required",
|
|
732
|
+
operation: `vNext ${stage.toUpperCase()} start`
|
|
733
|
+
}).eventKey;
|
|
734
|
+
};
|
|
735
|
+
if ((bootstrap || (!bootstrap && stage === "specify" && isVnextSpecifyRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") }))) && stage === "specify" && isVnextSpecifyFlow(projectRoot)) {
|
|
736
|
+
if ((intakeFile && intakeStdin) || (externalContext && (intakeFile || intakeStdin))) {
|
|
737
|
+
throw new AppError("usage", "Use exactly one of --intake-file or --intake-stdin", 2);
|
|
738
|
+
}
|
|
739
|
+
if (!intakeFile && !intakeStdin && !externalContext) {
|
|
740
|
+
throw new AppError("usage", "vNext SPECIFY start requires --intake-file, --intake-stdin or a runner context", 2);
|
|
741
|
+
}
|
|
742
|
+
if (!hookEventId) {
|
|
743
|
+
registerProject(context, { root: projectRoot });
|
|
744
|
+
hookEventId = bootstrap
|
|
745
|
+
? claimBootstrapHookEvent(context, {
|
|
746
|
+
projectId: requireProjectByRoot(context, resolveProjectRoot(projectRoot)).id,
|
|
747
|
+
projectRoot,
|
|
748
|
+
subject: subject ?? "specify",
|
|
749
|
+
intakeMode: externalContext ? "context" : intakeStdin ? "stdin" : "file",
|
|
750
|
+
...(contextSha256 ? { contextSha256 } : {})
|
|
751
|
+
})
|
|
752
|
+
: claimVnextStageStart(requiredPosition(parsed, 0, "run-id"));
|
|
753
|
+
}
|
|
754
|
+
const result = launchVnextSpecify(context, {
|
|
755
|
+
projectRoot,
|
|
756
|
+
...(externalContext ? { intakeMarkdown: runnerIntake(externalContext) } : intakeFile ? { intakeFile } : { intakeMarkdown: await readStdin(io.stdin) }),
|
|
757
|
+
hookEventId,
|
|
758
|
+
...(subject ? { slug: subject } : {}),
|
|
759
|
+
...(externalContext ? { externalContext } : {}),
|
|
760
|
+
...(!bootstrap ? { runId: requiredPosition(parsed, 0, "run-id") } : {})
|
|
761
|
+
});
|
|
762
|
+
return attachExternalStageContext(result, externalContext);
|
|
763
|
+
}
|
|
764
|
+
if (!bootstrap && stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
765
|
+
hookEventId ??= claimVnextStageStart(requiredPosition(parsed, 0, "run-id"));
|
|
766
|
+
return attachExternalStageContext(startVnextProtocolize(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }), externalContext);
|
|
767
|
+
}
|
|
768
|
+
if (!bootstrap && stage === "plan" && isVnextPlanRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
769
|
+
hookEventId ??= claimVnextStageStart(requiredPosition(parsed, 0, "run-id"));
|
|
770
|
+
return attachExternalStageContext(startVnextPlan(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }), externalContext);
|
|
771
|
+
}
|
|
772
|
+
if (!bootstrap && stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
773
|
+
hookEventId ??= claimVnextStageStart(requiredPosition(parsed, 0, "run-id"));
|
|
774
|
+
return attachExternalStageContext(startVnextPlanReview(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }), externalContext);
|
|
775
|
+
}
|
|
776
|
+
if (!bootstrap && stage === "code" && isVnextPlanRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
777
|
+
hookEventId ??= claimVnextStageStart(requiredPosition(parsed, 0, "run-id"));
|
|
778
|
+
return attachExternalStageContext(await startVnextCode(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, progress: commandProgress(io, output), ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }), externalContext);
|
|
779
|
+
}
|
|
780
|
+
if (!bootstrap && stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
781
|
+
hookEventId ??= claimVnextStageStart(requiredPosition(parsed, 0, "run-id"));
|
|
782
|
+
return attachExternalStageContext(startVnextCodeReview(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }), externalContext);
|
|
783
|
+
}
|
|
784
|
+
if (intakeStdin) {
|
|
785
|
+
throw new AppError("usage", "--intake-stdin is only available for vNext SPECIFY", 2);
|
|
786
|
+
}
|
|
787
|
+
if (externalContext) {
|
|
788
|
+
throw new AppError("usage", "--context-file is currently supported only by vNext flow stages", 2);
|
|
789
|
+
}
|
|
584
790
|
return startStage(context, {
|
|
585
|
-
projectRoot
|
|
791
|
+
projectRoot,
|
|
586
792
|
...(bootstrap ? { bootstrap: { subject: subject ?? requiredOption(parsed, "subject"), ...(intakeFile ? { intakeFile } : {}) } } : { runId: requiredPosition(parsed, 0, "run-id") }),
|
|
587
|
-
stage
|
|
588
|
-
...(
|
|
793
|
+
stage,
|
|
794
|
+
...(hookEventId ? { hookEventId } : {}),
|
|
795
|
+
...(requireSessionBinding ? { requireSessionBinding: true } : {}),
|
|
589
796
|
...(dir ? { dir } : {})
|
|
590
797
|
});
|
|
591
798
|
}
|
|
592
799
|
if (family === "stage" && command === "finish") {
|
|
593
800
|
const dir = optionalOption(parsed, "dir");
|
|
594
|
-
|
|
801
|
+
if (hasOption(parsed, "status")) {
|
|
802
|
+
throw new AppError("usage", "Stage status belongs in the semantic input file", 2);
|
|
803
|
+
}
|
|
804
|
+
const projectRoot = requiredOption(parsed, "project-root");
|
|
805
|
+
const runId = requiredPosition(parsed, 0, "run-id");
|
|
806
|
+
const stage = requiredOption(parsed, "stage");
|
|
595
807
|
const semanticFile = optionalOption(parsed, "semantic-file") ?? optionalOption(parsed, "data");
|
|
808
|
+
const resultFile = optionalOption(parsed, "result-file") ?? semanticFile;
|
|
809
|
+
const resultStdin = hasOption(parsed, "result-stdin");
|
|
810
|
+
if (stage === "specify" && isVnextSpecifyRun(context, { projectRoot, runId })) {
|
|
811
|
+
const outcome = optionalOption(parsed, "outcome");
|
|
812
|
+
if (!outcome || !["specified", "failed", "cancelled"].includes(outcome)) {
|
|
813
|
+
throw new AppError("usage", "vNext SPECIFY requires --outcome specified|failed|cancelled; use stage pause for a user question", 2);
|
|
814
|
+
}
|
|
815
|
+
if (resultFile && resultStdin) {
|
|
816
|
+
throw new AppError("usage", "Use exactly one of --result-file or --result-stdin", 2);
|
|
817
|
+
}
|
|
818
|
+
if (!resultFile && !resultStdin) {
|
|
819
|
+
throw new AppError("usage", "vNext SPECIFY finish requires --result-file or --result-stdin", 2);
|
|
820
|
+
}
|
|
821
|
+
return finishVnextSpecifyStage(context, {
|
|
822
|
+
projectRoot,
|
|
823
|
+
runId,
|
|
824
|
+
...(resultFile ? { resultFile } : { resultJson: await readStdin(io.stdin) }),
|
|
825
|
+
outcome: outcome
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
if (stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot, runId })) {
|
|
829
|
+
if (!resultFile || resultStdin || hasOption(parsed, "outcome")) {
|
|
830
|
+
throw new AppError("usage", "vNext PROTOCOLIZE requires exactly --result-file", 2);
|
|
831
|
+
}
|
|
832
|
+
return finishVnextProtocolize(context, { projectRoot, runId, resultFile });
|
|
833
|
+
}
|
|
834
|
+
if (stage === "plan" && isVnextPlanRun(context, { projectRoot, runId })) {
|
|
835
|
+
if (optionalOption(parsed, "code-work-batch-file"))
|
|
836
|
+
throw new AppError("usage", "vNext PLAN derives its CODE batch from accepted semantic plans; do not supply a batch file", 2);
|
|
837
|
+
return finishVnextPlan(context, { projectRoot, runId });
|
|
838
|
+
}
|
|
839
|
+
if (stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot, runId })) {
|
|
840
|
+
if (resultStdin || hasOption(parsed, "outcome"))
|
|
841
|
+
throw new AppError("usage", "vNext PLAN-REVIEW uses --decision-file", 2);
|
|
842
|
+
const decisionFile = optionalOption(parsed, "decision-file");
|
|
843
|
+
return finishVnextPlanReview(context, { projectRoot, runId, ...(decisionFile ? { decisionFile } : {}) });
|
|
844
|
+
}
|
|
845
|
+
if (stage === "code" && isVnextPlanRun(context, { projectRoot, runId })) {
|
|
846
|
+
if (semanticFile || resultFile || resultStdin || hasOption(parsed, "outcome")) {
|
|
847
|
+
throw new AppError("usage", "vNext CODE finish accepts only --verification-file", 2);
|
|
848
|
+
}
|
|
849
|
+
return await finishVnextCode(context, { projectRoot, runId, verificationFile: optionalOption(parsed, "verification-file") ?? path.join("<missing>", "code-verification.json"), progress: commandProgress(io, output) });
|
|
850
|
+
}
|
|
851
|
+
if (stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot, runId })) {
|
|
852
|
+
if (resultStdin || semanticFile || resultFile || hasOption(parsed, "outcome"))
|
|
853
|
+
throw new AppError("usage", "vNext CODE-REVIEW uses --decision-file", 2);
|
|
854
|
+
return await finishVnextCodeReview(context, { projectRoot, runId, ...(optionalOption(parsed, "decision-file") ? { decisionFile: optionalOption(parsed, "decision-file") } : {}), progress: commandProgress(io, output) });
|
|
855
|
+
}
|
|
856
|
+
if (hasOption(parsed, "outcome") || hasOption(parsed, "result-file") || resultStdin) {
|
|
857
|
+
throw new AppError("usage", "--outcome, --result-file and --result-stdin are only available for vNext SPECIFY", 2);
|
|
858
|
+
}
|
|
596
859
|
return finishStage(context, {
|
|
860
|
+
projectRoot,
|
|
861
|
+
runId,
|
|
862
|
+
stage,
|
|
863
|
+
...(dir ? { dir } : {}),
|
|
864
|
+
...(semanticFile ? { semanticFile } : {})
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
if (family === "stage" && command === "pause") {
|
|
868
|
+
if (!hasOption(parsed, "question-stdin"))
|
|
869
|
+
throw new AppError("usage", "stage pause requires --question-stdin", 2);
|
|
870
|
+
return pauseStageForUser(context, {
|
|
597
871
|
projectRoot: requiredOption(parsed, "project-root"),
|
|
598
872
|
runId: requiredPosition(parsed, 0, "run-id"),
|
|
599
873
|
stage: requiredOption(parsed, "stage"),
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
874
|
+
workId: requiredOption(parsed, "work"),
|
|
875
|
+
question: await readStdin(io.stdin)
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
if (family === "stage" && command === "resume") {
|
|
879
|
+
if (!hasOption(parsed, "answer-stdin"))
|
|
880
|
+
throw new AppError("usage", "stage resume requires --answer-stdin", 2);
|
|
881
|
+
const hookEventId = optionalOption(parsed, "hook-event-id");
|
|
882
|
+
return resumeStageAfterUser(context, {
|
|
883
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
884
|
+
runId: requiredPosition(parsed, 0, "run-id"),
|
|
885
|
+
stage: requiredOption(parsed, "stage"),
|
|
886
|
+
workId: requiredOption(parsed, "work"),
|
|
887
|
+
answer: await readStdin(io.stdin),
|
|
888
|
+
...(hookEventId ? { hookEventId } : {})
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
if (family === "stage" && command === "block") {
|
|
892
|
+
if (!hasOption(parsed, "summary-stdin"))
|
|
893
|
+
throw new AppError("usage", "stage block requires --summary-stdin", 2);
|
|
894
|
+
const kind = requiredOption(parsed, "kind");
|
|
895
|
+
if (kind !== "engine" && kind !== "harness" && kind !== "environment")
|
|
896
|
+
throw new AppError("usage", "--kind must be engine, harness or environment", 2);
|
|
897
|
+
return blockStageForRuntime(context, {
|
|
898
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
899
|
+
runId: requiredPosition(parsed, 0, "run-id"),
|
|
900
|
+
stage: requiredOption(parsed, "stage"),
|
|
901
|
+
workId: requiredOption(parsed, "work"),
|
|
902
|
+
kind,
|
|
903
|
+
code: requiredOption(parsed, "code"),
|
|
904
|
+
summary: await readStdin(io.stdin),
|
|
905
|
+
retryable: hasOption(parsed, "retryable")
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
if (family === "stage" && command === "unblock") {
|
|
909
|
+
return unblockStageAfterRuntimeRepair(context, {
|
|
910
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
911
|
+
runId: requiredPosition(parsed, 0, "run-id"),
|
|
912
|
+
stage: requiredOption(parsed, "stage"),
|
|
913
|
+
workId: requiredOption(parsed, "work")
|
|
603
914
|
});
|
|
604
915
|
}
|
|
605
916
|
if (family === "lane") {
|
|
@@ -623,6 +934,9 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
623
934
|
if (family === "run") {
|
|
624
935
|
return dispatchRun(context, command, parsed);
|
|
625
936
|
}
|
|
937
|
+
if (family === "stat") {
|
|
938
|
+
return dispatchStat(context, command, parsed);
|
|
939
|
+
}
|
|
626
940
|
if (family === "id") {
|
|
627
941
|
return dispatchId(context, command, parsed);
|
|
628
942
|
}
|
|
@@ -650,6 +964,37 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
650
964
|
if (family === "codex" && command === "hook") {
|
|
651
965
|
return dispatchCodexHook(context, parsed, await readStdin(io.stdin));
|
|
652
966
|
}
|
|
967
|
+
if (family === "zcode" && command === "event") {
|
|
968
|
+
return dispatchZcodeEvent(context, parsed, await readStdin(io.stdin));
|
|
969
|
+
}
|
|
970
|
+
if (family === "zcode" && command === "usage") {
|
|
971
|
+
if (requiredPosition(parsed, 0, "zcode usage action") !== "ingest")
|
|
972
|
+
throw new AppError("usage", "Unknown zcode usage action", 2);
|
|
973
|
+
return ingestZcodeUsage(context, { projectRoot: requiredOption(parsed, "project-root"), stdin: await readStdin(io.stdin) });
|
|
974
|
+
}
|
|
975
|
+
if (family === "grok" && command === "event") {
|
|
976
|
+
return dispatchGrokEvent(context, parsed, await readStdin(io.stdin));
|
|
977
|
+
}
|
|
978
|
+
if (family === "grok" && command === "usage") {
|
|
979
|
+
if (requiredPosition(parsed, 0, "grok usage action") !== "ingest")
|
|
980
|
+
throw new AppError("usage", "Unknown grok usage action", 2);
|
|
981
|
+
return ingestGrokUsage(context, { projectRoot: requiredOption(parsed, "project-root"), stdin: await readStdin(io.stdin) });
|
|
982
|
+
}
|
|
983
|
+
if (family === "opencode" && command === "event") {
|
|
984
|
+
return dispatchOpenCodeEvent(context, parsed, await readStdin(io.stdin));
|
|
985
|
+
}
|
|
986
|
+
if (family === "opencode" && command === "usage") {
|
|
987
|
+
if (requiredPosition(parsed, 0, "opencode usage action") !== "ingest")
|
|
988
|
+
throw new AppError("usage", "Unknown opencode usage action", 2);
|
|
989
|
+
return ingestOpenCodeUsage(context, { projectRoot: requiredOption(parsed, "project-root"), stdin: await readStdin(io.stdin) });
|
|
990
|
+
}
|
|
991
|
+
if (family === "agy" && command === "event")
|
|
992
|
+
return dispatchAgyEvent(context, parsed, await readStdin(io.stdin));
|
|
993
|
+
if (family === "agy" && command === "usage") {
|
|
994
|
+
if (requiredPosition(parsed, 0, "agy usage action") !== "ingest")
|
|
995
|
+
throw new AppError("usage", "Unknown agy usage action", 2);
|
|
996
|
+
return ingestAgyUsage(context, { projectRoot: requiredOption(parsed, "project-root"), stdin: await readStdin(io.stdin) });
|
|
997
|
+
}
|
|
653
998
|
if (family === "worktree") {
|
|
654
999
|
return dispatchWorktree(context, command, parsed, requiredScopeProjectRoot(scopeProjectRoot, `worktree ${command ?? ""}`));
|
|
655
1000
|
}
|
|
@@ -962,6 +1307,48 @@ function dispatchSession(context, command, parsed) {
|
|
|
962
1307
|
throw new AppError("usage", `Unknown session command: ${command ?? "<empty>"}`, 2);
|
|
963
1308
|
}
|
|
964
1309
|
function dispatchRun(context, command, parsed) {
|
|
1310
|
+
if (command === "snapshot") {
|
|
1311
|
+
const action = requiredPosition(parsed, 0, "snapshot action");
|
|
1312
|
+
if (action === "bootstrap") {
|
|
1313
|
+
const bootstrapAction = requiredPosition(parsed, 1, "bootstrap snapshot action");
|
|
1314
|
+
if (bootstrapAction === "create")
|
|
1315
|
+
return createEvalBootstrapSnapshot(context, {
|
|
1316
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
1317
|
+
output: requiredOption(parsed, "output")
|
|
1318
|
+
});
|
|
1319
|
+
if (bootstrapAction === "restore")
|
|
1320
|
+
return restoreEvalBootstrapSnapshot(context, {
|
|
1321
|
+
snapshot: requiredOption(parsed, "snapshot"),
|
|
1322
|
+
projectRoot: requiredOption(parsed, "project-root")
|
|
1323
|
+
});
|
|
1324
|
+
throw new AppError("usage", "Use: dd-flow run snapshot bootstrap create|restore", 2);
|
|
1325
|
+
}
|
|
1326
|
+
if (action === "create") {
|
|
1327
|
+
const candidate = parsed.options.has("candidate");
|
|
1328
|
+
const stageEntry = optionalOption(parsed, "stage-entry");
|
|
1329
|
+
if (candidate && stageEntry !== undefined)
|
|
1330
|
+
throw new AppError("usage", "Use either --candidate or --stage-entry, not both", 2);
|
|
1331
|
+
if (!candidate && stageEntry === undefined)
|
|
1332
|
+
throw new AppError("usage", "Snapshot create requires --candidate or --stage-entry", 2);
|
|
1333
|
+
const input = {
|
|
1334
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
1335
|
+
runId: requiredPosition(parsed, 1, "run-id"),
|
|
1336
|
+
output: requiredOption(parsed, "output")
|
|
1337
|
+
};
|
|
1338
|
+
return candidate
|
|
1339
|
+
? createEvalRunSnapshot(context, { ...input, candidate: true })
|
|
1340
|
+
: createEvalRunSnapshot(context, { ...input, stageEntry: stageEntry });
|
|
1341
|
+
}
|
|
1342
|
+
if (action === "restore")
|
|
1343
|
+
return restoreEvalRunSnapshot(context, {
|
|
1344
|
+
snapshot: requiredOption(parsed, "snapshot"),
|
|
1345
|
+
projectRoot: requiredOption(parsed, "project-root")
|
|
1346
|
+
});
|
|
1347
|
+
throw new AppError("usage", "Use: dd-flow run snapshot bootstrap create|restore | dd-flow run snapshot create <RUN> (--stage-entry <stage>|--candidate) --project-root <path> --output <directory> | dd-flow run snapshot restore --snapshot <directory> --project-root <path>", 2);
|
|
1348
|
+
}
|
|
1349
|
+
if (command === "prepare-vnext-specify") {
|
|
1350
|
+
return prepareVnextSpecifyRun(context, { projectRoot: requiredOption(parsed, "project-root"), slug: requiredOption(parsed, "slug") });
|
|
1351
|
+
}
|
|
965
1352
|
if (command === "start") {
|
|
966
1353
|
if (optionalOption(parsed, "flow-kind") === "mb-upgrade") {
|
|
967
1354
|
assertUpgradeRunStorageReady(context, requiredOption(parsed, "project-root"));
|
|
@@ -997,12 +1384,33 @@ function dispatchRun(context, command, parsed) {
|
|
|
997
1384
|
hiddenSections: allOptions(parsed, "hide")
|
|
998
1385
|
});
|
|
999
1386
|
}
|
|
1000
|
-
if (command === "
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
}
|
|
1387
|
+
if (command === "config") {
|
|
1388
|
+
const action = requiredPosition(parsed, 0, "run config action");
|
|
1389
|
+
const runId = requiredPosition(parsed, 1, "run-id");
|
|
1390
|
+
if (action === "status") {
|
|
1391
|
+
return getFlowRunConfig(context, { projectRoot: requiredOption(parsed, "project-root"), runId });
|
|
1392
|
+
}
|
|
1393
|
+
if (action === "set") {
|
|
1394
|
+
return setFlowRunConfig(context, {
|
|
1395
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
1396
|
+
runId,
|
|
1397
|
+
key: requiredOption(parsed, "key"),
|
|
1398
|
+
value: requiredOption(parsed, "value"),
|
|
1399
|
+
reason: requiredOption(parsed, "reason")
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
throw new AppError("usage", `Unknown run config action: ${action}`, 2);
|
|
1403
|
+
}
|
|
1404
|
+
if (command === "vars") {
|
|
1405
|
+
const action = requiredPosition(parsed, 0, "run vars action");
|
|
1406
|
+
const runId = requiredPosition(parsed, 1, "run-id");
|
|
1407
|
+
if (action === "ls")
|
|
1408
|
+
return getFlowRunVariables(context, { projectRoot: requiredOption(parsed, "project-root"), runId });
|
|
1409
|
+
if (action === "get")
|
|
1410
|
+
return getFlowRunVariables(context, { projectRoot: requiredOption(parsed, "project-root"), runId, key: requiredOption(parsed, "key") });
|
|
1411
|
+
if (action === "set")
|
|
1412
|
+
return setFlowRunVariable(context, { projectRoot: requiredOption(parsed, "project-root"), runId, key: requiredOption(parsed, "key"), value: requiredOption(parsed, "value") });
|
|
1413
|
+
throw new AppError("usage", `Unknown run vars command: ${action}`, 2);
|
|
1006
1414
|
}
|
|
1007
1415
|
if (command === "flags") {
|
|
1008
1416
|
const action = requiredPosition(parsed, 0, "flow flags action");
|
|
@@ -1052,16 +1460,39 @@ function dispatchRun(context, command, parsed) {
|
|
|
1052
1460
|
});
|
|
1053
1461
|
}
|
|
1054
1462
|
if (command === "complete") {
|
|
1463
|
+
throw new AppError("usage", "run complete is retired; only an operator may use run override", 2);
|
|
1464
|
+
}
|
|
1465
|
+
if (command === "override") {
|
|
1466
|
+
const status = requiredOption(parsed, "status");
|
|
1467
|
+
if (!['cancelled', 'failed'].includes(status)) {
|
|
1468
|
+
throw new AppError("usage", "run override status must be cancelled or failed", 2, { status });
|
|
1469
|
+
}
|
|
1055
1470
|
return completeFlowRun(context, {
|
|
1056
1471
|
projectRoot: requiredOption(parsed, "project-root"),
|
|
1057
1472
|
runId: requiredPosition(parsed, 0, "run-id"),
|
|
1058
|
-
status
|
|
1473
|
+
status,
|
|
1059
1474
|
verdict: optionalOption(parsed, "verdict"),
|
|
1060
|
-
nextAction: optionalOption(parsed, "next-action")
|
|
1475
|
+
nextAction: optionalOption(parsed, "next-action"),
|
|
1476
|
+
manualOverrideReason: requiredOption(parsed, "reason")
|
|
1061
1477
|
});
|
|
1062
1478
|
}
|
|
1063
1479
|
throw new AppError("usage", `Unknown run command: ${command ?? "<empty>"}`, 2);
|
|
1064
1480
|
}
|
|
1481
|
+
function dispatchStat(context, command, parsed) {
|
|
1482
|
+
if (command === "usage") {
|
|
1483
|
+
return getStoredFlowRunUsage(context, {
|
|
1484
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
1485
|
+
runId: requiredOption(parsed, "run"),
|
|
1486
|
+
groupBy: optionalOption(parsed, "group-by"),
|
|
1487
|
+
sessionId: optionalOption(parsed, "session"),
|
|
1488
|
+
stage: optionalOption(parsed, "stage")
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
if (command === "run" && parsed.positional[0] === "sessions" && parsed.positional[1] === "ls") {
|
|
1492
|
+
return getFlowRunSessions(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredOption(parsed, "run") });
|
|
1493
|
+
}
|
|
1494
|
+
throw new AppError("usage", "Usage: dd-flow stat usage --run <RUN-ID> --project-root <root> | dd-flow stat run sessions ls --run <RUN-ID> --project-root <root>", 2);
|
|
1495
|
+
}
|
|
1065
1496
|
function dispatchId(context, command, parsed) {
|
|
1066
1497
|
if (command === "next") {
|
|
1067
1498
|
return previewNextEntityId(context, {
|
|
@@ -1412,7 +1843,9 @@ function dispatchCodexHome(context, parsed) {
|
|
|
1412
1843
|
function dispatchCodexHook(context, parsed, stdin) {
|
|
1413
1844
|
const action = requiredPosition(parsed, 0, "codex hook action");
|
|
1414
1845
|
if (action === "handle") {
|
|
1415
|
-
const
|
|
1846
|
+
const isolatedHome = isolatedDdFlowHomeFromHookStdin(stdin);
|
|
1847
|
+
const hookContext = isolatedHome ? createContext({ ...context.env, DD_FLOW_HOME: isolatedHome }) : context;
|
|
1848
|
+
const result = handleCodexHook(hookContext, {
|
|
1416
1849
|
event: requiredOption(parsed, "event"),
|
|
1417
1850
|
projectRoot: optionalOption(parsed, "project-root"),
|
|
1418
1851
|
stdin
|
|
@@ -1424,6 +1857,39 @@ function dispatchCodexHook(context, parsed, stdin) {
|
|
|
1424
1857
|
}
|
|
1425
1858
|
throw new AppError("usage", `Unknown codex hook action: ${action}`, 2);
|
|
1426
1859
|
}
|
|
1860
|
+
function dispatchZcodeEvent(context, parsed, stdin) {
|
|
1861
|
+
const action = requiredPosition(parsed, 0, "zcode event action");
|
|
1862
|
+
if (action === "handle") {
|
|
1863
|
+
return handleZcodeEvent(context, {
|
|
1864
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
1865
|
+
stdin
|
|
1866
|
+
});
|
|
1867
|
+
}
|
|
1868
|
+
throw new AppError("usage", `Unknown zcode event action: ${action}`, 2);
|
|
1869
|
+
}
|
|
1870
|
+
function dispatchGrokEvent(context, parsed, stdin) {
|
|
1871
|
+
const action = requiredPosition(parsed, 0, "grok event action");
|
|
1872
|
+
if (action === "handle") {
|
|
1873
|
+
const result = handleGrokEvent(context, { projectRoot: requiredOption(parsed, "project-root"), stdin });
|
|
1874
|
+
if (result && typeof result === "object" && !Array.isArray(result) && "hookSpecificOutput" in result) {
|
|
1875
|
+
return { hookSpecificOutput: result.hookSpecificOutput };
|
|
1876
|
+
}
|
|
1877
|
+
return {};
|
|
1878
|
+
}
|
|
1879
|
+
throw new AppError("usage", `Unknown grok event action: ${action}`, 2);
|
|
1880
|
+
}
|
|
1881
|
+
function dispatchOpenCodeEvent(context, parsed, stdin) {
|
|
1882
|
+
const action = requiredPosition(parsed, 0, "opencode event action");
|
|
1883
|
+
if (action === "handle")
|
|
1884
|
+
return handleOpenCodeEvent(context, { projectRoot: requiredOption(parsed, "project-root"), stdin });
|
|
1885
|
+
throw new AppError("usage", `Unknown opencode event action: ${action}`, 2);
|
|
1886
|
+
}
|
|
1887
|
+
function dispatchAgyEvent(context, parsed, stdin) {
|
|
1888
|
+
const action = requiredPosition(parsed, 0, "agy event action");
|
|
1889
|
+
if (action === "handle")
|
|
1890
|
+
return handleAgyEvent(context, { projectRoot: requiredOption(parsed, "project-root"), stdin });
|
|
1891
|
+
throw new AppError("usage", `Unknown agy event action: ${action}`, 2);
|
|
1892
|
+
}
|
|
1427
1893
|
function dispatchWorktree(context, command, parsed, scopeProjectRoot) {
|
|
1428
1894
|
if (command === "plan") {
|
|
1429
1895
|
return planWorktree(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: scopeProjectRoot });
|
|
@@ -1726,7 +2192,7 @@ function projectRootForMutation(context, args, result, resolvedScope) {
|
|
|
1726
2192
|
if (family === "run" && ["start", "attach-stage", "complete-stage", "complete", "flags"].includes(command ?? "")) {
|
|
1727
2193
|
return requiredOption(parsed, "project-root");
|
|
1728
2194
|
}
|
|
1729
|
-
if (family === "stage" && ["start", "finish"].includes(command ?? "")) {
|
|
2195
|
+
if (family === "stage" && ["start", "pause", "resume", "finish"].includes(command ?? "")) {
|
|
1730
2196
|
return requiredOption(parsed, "project-root");
|
|
1731
2197
|
}
|
|
1732
2198
|
if (family === "worktree" && ["create", "bootstrap", "close"].includes(command ?? "")) {
|
|
@@ -1735,6 +2201,14 @@ function projectRootForMutation(context, args, result, resolvedScope) {
|
|
|
1735
2201
|
if (family === "codex" && command === "hook" && requiredPosition(parsed, 0, "codex hook action") === "handle") {
|
|
1736
2202
|
return optionalOption(parsed, "project-root");
|
|
1737
2203
|
}
|
|
2204
|
+
if (family === "zcode" && ((command === "event" && requiredPosition(parsed, 0, "zcode event action") === "handle") || command === "usage")) {
|
|
2205
|
+
return requiredOption(parsed, "project-root");
|
|
2206
|
+
}
|
|
2207
|
+
if (family === "opencode" && ((command === "event" && requiredPosition(parsed, 0, "opencode event action") === "handle") || command === "usage")) {
|
|
2208
|
+
return requiredOption(parsed, "project-root");
|
|
2209
|
+
}
|
|
2210
|
+
if (family === "agy" && ((command === "event" && requiredPosition(parsed, 0, "agy event action") === "handle") || command === "usage"))
|
|
2211
|
+
return requiredOption(parsed, "project-root");
|
|
1738
2212
|
if (family === "codex" && command === "hooks") {
|
|
1739
2213
|
const action = requiredPosition(parsed, 0, "codex hooks action");
|
|
1740
2214
|
return ["install", "remove"].includes(action) ? requiredOption(parsed, "project-root") : undefined;
|
|
@@ -1770,6 +2244,27 @@ function projectRootForMergeQueueMutation(context, command, parsed) {
|
|
|
1770
2244
|
function protocolProjectRoot(context, protocolId) {
|
|
1771
2245
|
return requireProtocol(context, protocolId).project_root;
|
|
1772
2246
|
}
|
|
2247
|
+
function runnerIntake(context) {
|
|
2248
|
+
const input = context.value.task_input?.[0];
|
|
2249
|
+
if (!input?.path)
|
|
2250
|
+
throw new AppError("validation", "Runner SPECIFY context requires one ordered task_input file", 2);
|
|
2251
|
+
const file = path.resolve(input.path);
|
|
2252
|
+
if (!fs.existsSync(file) || !fs.statSync(file).isFile())
|
|
2253
|
+
throw new AppError("not_found", "Runner SPECIFY task input is missing", 2, { task_input: file });
|
|
2254
|
+
return fs.readFileSync(file, "utf8");
|
|
2255
|
+
}
|
|
2256
|
+
/** Stage services install context before their lifecycle transition. Keeping
|
|
2257
|
+
* this check here makes an incomplete service migration fail closed instead of
|
|
2258
|
+
* silently materialising evidence after a Stage has become running. */
|
|
2259
|
+
function attachExternalStageContext(result, loaded) {
|
|
2260
|
+
if (!loaded)
|
|
2261
|
+
return result;
|
|
2262
|
+
const promptPath = typeof result.prompt_path === "string" ? result.prompt_path : null;
|
|
2263
|
+
if (!promptPath || !result.external_context || typeof result.external_context !== "object") {
|
|
2264
|
+
throw new AppError("stage_context_not_installed", "vNext stage service did not atomically install the supplied runner context", 1);
|
|
2265
|
+
}
|
|
2266
|
+
return { ...result, worker_prompt_markdown: fs.readFileSync(promptPath, "utf8") };
|
|
2267
|
+
}
|
|
1773
2268
|
function isScopedProtocolMutation(family, command) {
|
|
1774
2269
|
if (family === "transition")
|
|
1775
2270
|
return true;
|