@deksden-com/dd-flow-cli 0.9.0-beta.1 → 0.9.0-beta.100
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 +561 -0
- package/README.md +68 -0
- package/dist/build-info.json +8 -8
- package/dist/cli/command-inputs.js +305 -0
- package/dist/cli/help.js +91 -15
- package/dist/cli/hook-ingress.js +82 -0
- package/dist/cli/input-preparation.js +116 -0
- package/dist/cli/run-cli.js +1506 -334
- package/dist/cli.js +6 -2
- package/dist/harness-runtime/DD-ZCODE.md +99 -0
- package/dist/harness-runtime/bin/dd-agy.mjs +39 -0
- package/dist/harness-runtime/bin/dd-codex.mjs +25 -0
- package/dist/harness-runtime/bin/dd-droid.mjs +33 -0
- package/dist/harness-runtime/bin/dd-grok.mjs +30 -0
- package/dist/harness-runtime/bin/dd-opencode.mjs +21 -0
- package/dist/harness-runtime/bin/dd-zcode.mjs +83 -0
- package/dist/harness-runtime/lib/daemon-operations.mjs +215 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +449 -0
- package/dist/harness-runtime/lib/dd-agy.mjs +59 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +146 -0
- package/dist/harness-runtime/lib/dd-codex.mjs +562 -0
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +125 -0
- package/dist/harness-runtime/lib/dd-droid.mjs +468 -0
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +300 -0
- package/dist/harness-runtime/lib/dd-grok.mjs +174 -0
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +217 -0
- package/dist/harness-runtime/lib/dd-opencode.mjs +98 -0
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +644 -0
- package/dist/harness-runtime/lib/dd-zcode.mjs +941 -0
- package/dist/harness-runtime/lib/delegation-instructions.d.mts +10 -0
- package/dist/harness-runtime/lib/delegation-instructions.mjs +130 -0
- package/dist/harness-runtime/lib/dispatch-fence.mjs +14 -0
- package/dist/harness-runtime/lib/driver-recovery.mjs +129 -0
- package/dist/harness-runtime/lib/droid-observation.mjs +66 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +267 -0
- package/dist/harness-runtime/lib/model-observations.mjs +77 -0
- package/dist/harness-runtime/lib/native-hook-command.d.mts +1 -0
- package/dist/harness-runtime/lib/native-hook-command.mjs +34 -0
- package/dist/harness-runtime/lib/observation-clock.mjs +36 -0
- package/dist/harness-runtime/lib/operation-context.mjs +5 -0
- package/dist/harness-runtime/lib/operation-errors.mjs +19 -0
- package/dist/harness-runtime/lib/process-json.mjs +70 -0
- package/dist/harness-runtime/lib/process-snapshot.mjs +10 -0
- package/dist/harness-runtime/lib/runner-events.mjs +200 -0
- package/dist/harness-runtime/lib/runner-lock.mjs +42 -0
- package/dist/harness-runtime/lib/session-settlement.mjs +30 -0
- package/dist/harness-runtime/lib/tool-observations.d.mts +17 -0
- package/dist/harness-runtime/lib/tool-observations.mjs +153 -0
- package/dist/harness-runtime/lib/zcode-usage.mjs +54 -0
- package/dist/runtime/context.js +3 -1
- package/dist/schemas/agent-profile.schema.json +1 -1
- package/dist/schemas/code-review-result.schema.json +2 -2
- package/dist/schemas/code-work-batch.schema.json +4 -3
- package/dist/schemas/code-work-result.schema.json +4 -4
- package/dist/schemas/harness-config.schema.json +23 -0
- package/dist/schemas/plan-review-decision.schema.json +1 -1
- package/dist/schemas/plan-review-result.schema.json +2 -2
- package/dist/schemas/run-control-receipt.schema.json +99 -0
- package/dist/schemas/run-control-request.schema.json +36 -0
- package/dist/schemas/vnext-protocol-plan.schema.json +2 -2
- package/dist/services/canon.js +11 -8
- package/dist/services/cleanup.js +75 -21
- package/dist/services/cli-operation-classifier.js +22 -24
- package/dist/services/code-checks.js +676 -79
- package/dist/services/codex-hook-delivery.js +28 -0
- package/dist/services/command-context.js +80 -0
- package/dist/services/config.js +11 -8
- package/dist/services/continuation-outcome.js +20 -0
- package/dist/services/controller-fanout.js +134 -0
- package/dist/services/dashboard.js +103 -31
- package/dist/services/engines.js +125 -80
- package/dist/services/eval-snapshots.js +838 -74
- package/dist/services/execution-policy.js +170 -0
- package/dist/services/external-work-launch.js +110 -0
- package/dist/services/harness-adapter.js +147 -24
- package/dist/services/harness-config.js +68 -0
- package/dist/services/hooks.js +609 -310
- package/dist/services/lanes.js +62 -53
- package/dist/services/lifecycle-command.js +103 -17
- package/dist/services/lifecycle-contract.js +30 -0
- package/dist/services/lifecycle-invocations.js +1128 -0
- package/dist/services/managed-daemon-binding.js +38 -0
- package/dist/services/managed-processes.js +425 -0
- package/dist/services/merge-queue.js +179 -104
- package/dist/services/merge-server.js +78 -27
- package/dist/services/migrations.js +14 -9
- package/dist/services/native-daemon-history.js +49 -0
- package/dist/services/native-session-control.js +39 -0
- package/dist/services/plan-runtime.js +19 -8
- package/dist/services/plans.js +28 -25
- package/dist/services/portable-refs.js +57 -0
- package/dist/services/projects.js +18 -5
- package/dist/services/prompts.js +27 -18
- package/dist/services/protocols.js +64 -20
- package/dist/services/recovery-observation-budget.js +61 -0
- package/dist/services/recovery-snapshot-database.js +107 -0
- package/dist/services/repair-intents.js +40 -0
- package/dist/services/run-control-receipt.js +56 -0
- package/dist/services/run-control-worker.js +363 -0
- package/dist/services/run-control.js +876 -0
- package/dist/services/run-controller-adapter.js +222 -0
- package/dist/services/run-controller-capture.js +136 -0
- package/dist/services/run-controller-process.js +189 -0
- package/dist/services/run-controller-recovery.js +222 -0
- package/dist/services/run-controller-state.js +36 -0
- package/dist/services/run-controller.js +803 -0
- package/dist/services/run-engine-bindings.js +20 -62
- package/dist/services/run-fork.js +138 -0
- package/dist/services/run-observations.js +112 -0
- package/dist/services/run-projection.js +10 -8
- package/dist/services/run-recovery-runtime.js +69 -0
- package/dist/services/run-recovery.js +343 -0
- package/dist/services/runs.js +437 -81
- package/dist/services/runtime-budget.js +439 -0
- package/dist/services/runtime-command.js +43 -0
- package/dist/services/runtime-scope-capture.js +50 -0
- package/dist/services/runtime-scope-control.js +450 -0
- package/dist/services/runtime-scope-resume.js +565 -0
- package/dist/services/runtime-scope-stop.js +99 -0
- package/dist/services/runtime-scope-worker.js +243 -0
- package/dist/services/runtime-service.js +97 -0
- package/dist/services/schema-validation.js +21 -19
- package/dist/services/session-identity.js +19 -0
- package/dist/services/sessions.js +61 -80
- package/dist/services/stage-blocker.js +17 -7
- package/dist/services/stage-context.js +44 -18
- package/dist/services/stage-lifecycle.js +112 -108
- package/dist/services/stage-pause.js +117 -73
- package/dist/services/stage-work-graph.js +35 -0
- package/dist/services/usage.js +108 -132
- package/dist/services/vnext-code-review.js +245 -98
- package/dist/services/vnext-code.js +355 -257
- package/dist/services/vnext-execution-profile.js +5 -3
- package/dist/services/vnext-fanout.js +166 -26
- package/dist/services/vnext-merge.js +413 -147
- package/dist/services/vnext-plan-review.js +182 -109
- package/dist/services/vnext-plan.js +199 -82
- package/dist/services/vnext-protocolize.js +133 -60
- package/dist/services/vnext-specify.js +94 -71
- package/dist/services/work-registry.js +813 -160
- package/dist/services/workspace-bootstrap.js +76 -0
- package/dist/services/worktrees.js +85 -35
- package/dist/shared/entity-references.js +8 -0
- package/dist/shared/errors.js +12 -0
- package/dist/storage/database.js +569 -16
- package/dist/storage/paths.js +17 -4
- package/dist/storage/work-references.js +23 -0
- package/dist/storage/writer-contract.js +77 -0
- package/dist/storage/writer-migration.js +102 -0
- package/package.json +24 -13
- package/tools/audit-runtime-fix-boundaries.mjs +96 -0
- package/tools/repair-paused-run-status.mjs +59 -0
|
@@ -1,19 +1,24 @@
|
|
|
1
|
+
import { managedLifecycleCommand } from "./lifecycle-invocations.js";
|
|
1
2
|
import crypto from "node:crypto";
|
|
2
3
|
import fs from "node:fs";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { spawnSync } from "node:child_process";
|
|
5
6
|
import { AppError } from "../shared/errors.js";
|
|
6
|
-
import { applyExternalStageContext } from "./stage-context.js";
|
|
7
|
+
import { applyExternalStageContext, prepareExternalStageContext } from "./stage-context.js";
|
|
7
8
|
import { ensureVnextWorkStorage } from "../storage/database.js";
|
|
8
|
-
import { resolveProjectRoot, writeJsonAtomic } from "../storage/paths.js";
|
|
9
|
+
import { projectHome, resolveProjectRoot, writeJsonAtomic } from "../storage/paths.js";
|
|
9
10
|
import { preflightMemoryPermissions } from "./memory-permissions.js";
|
|
10
11
|
import { registerProject, requireProjectByRoot } from "./projects.js";
|
|
11
|
-
import { advanceFlowRun, appendFlowRunTimelineEvent, attachFlowRunStage, completeFlowRun, completeFlowRunStage, getFlowRunStatus, startFlowRun } from "./runs.js";
|
|
12
|
+
import { advanceFlowRun, appendFlowRunTimelineEvent, attachFlowRunStage, completeFlowRun, completeFlowRunStage, getFlowRunStatus, prepareFlowRunStart, startFlowRun } from "./runs.js";
|
|
13
|
+
import { resolveStageTransition } from "./execution-policy.js";
|
|
12
14
|
import { validateSchema } from "./schema-validation.js";
|
|
13
15
|
import { bindRunningWorkSession, refreshRunWorkProjection } from "./work-registry.js";
|
|
14
16
|
import { nextWorkId } from "./ids.js";
|
|
15
17
|
import { flowCommand, stagePauseCommand, stagePauseCommandTemplate } from "./stage-pause.js";
|
|
16
18
|
import { writeStageReport } from "./stage-report-renderer.js";
|
|
19
|
+
import { prepareError, prepareJsonSchema, prepareJsonText, prepareOutputDirectory, prepareOutputFile, prepareTextFile } from "../cli/input-preparation.js";
|
|
20
|
+
import { hookSessionIdentity } from "./hooks.js";
|
|
21
|
+
import { assertRecoveryHookCurrent, assertRunMutationAllowed } from "./run-recovery.js";
|
|
17
22
|
const flowId = "mb-sdlc-vnext-specify";
|
|
18
23
|
const protocolizeFlowId = "mb-sdlc-vnext-protocolize";
|
|
19
24
|
const flowVersion = 4;
|
|
@@ -27,28 +32,57 @@ export function isVnextSpecifyRun(context, input) {
|
|
|
27
32
|
const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
|
|
28
33
|
return Boolean(context.db.get("SELECT id FROM runs WHERE project_id = ? AND id = ? AND flow_kind IN ('vnext_specify', 'vnext_protocolize')", [project.id, input.runId]));
|
|
29
34
|
}
|
|
30
|
-
export function
|
|
31
|
-
ensureVnextWorkStorage(context.db);
|
|
35
|
+
export function prepareVnextSpecifyStart(context, input) {
|
|
32
36
|
const projectRoot = resolveProjectRoot(input.projectRoot);
|
|
33
|
-
registerProject(context, { root: projectRoot });
|
|
34
37
|
const intake = readIntake(input);
|
|
35
38
|
const discussion = intake.markdown.trim();
|
|
36
39
|
if (!discussion)
|
|
37
40
|
throw new AppError("validation", "SPECIFY intake must not be empty", 2);
|
|
38
41
|
const flow = assertFlowDefinition(projectRoot);
|
|
39
42
|
const template = readRequiredFile(path.join(projectRoot, ".memory-bank", "dd-flow", "vnext", "specify.md"), "vNext SPECIFY prompt template");
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
const startInput = { projectRoot, flowKind: flow.id === protocolizeFlowId ? "vnext_protocolize" : "vnext_specify", subjectType: "discussion", subjectId: "SPECIFY", slug: input.slug ?? "specify", nextAction: "await_work_session" };
|
|
44
|
+
const start = input.runId ? undefined : prepareFlowRunStart(context, startInput);
|
|
45
|
+
if (input.runId)
|
|
46
|
+
assertSpecifyNotStarted(context, projectRoot, input.runId);
|
|
47
|
+
const run = input.runId ? requireRun(context, projectRoot, input.runId) : undefined;
|
|
48
|
+
if (!run) {
|
|
49
|
+
const project = context.db.get("SELECT id FROM projects WHERE root = ?", [projectRoot]);
|
|
50
|
+
prepareOutputDirectory(project ? path.join(projectHome(context.ddFlowHome, project.id), "runs") : path.join(context.ddFlowHome, "projects"));
|
|
51
|
+
}
|
|
52
|
+
if (run) {
|
|
53
|
+
assertRunMutationAllowed(context, run.project_id, run.id);
|
|
54
|
+
const home = requiredRunHome(run), stageRoot = path.join(home, "01-specify");
|
|
55
|
+
for (const relative of ["intake/discussion.md", "01-specify/prompt.md", "01-specify/work-context.json"])
|
|
56
|
+
prepareOutputFile(path.join(home, relative));
|
|
57
|
+
if (input.externalContext)
|
|
58
|
+
prepareExternalStageContext({ stageRoot, loaded: input.externalContext });
|
|
59
|
+
}
|
|
60
|
+
const preflight = preflightMemoryPermissions({ root: projectRoot, memoryBank: ".memory-bank", flow: "custom", mode: "read", targets: [{ path: ".memory-bank", mode: "read" }] });
|
|
61
|
+
if (preflight.ok !== true)
|
|
62
|
+
throw new AppError("permission_preflight_failed", "SPECIFY project context is not readable", 1, { preflight });
|
|
63
|
+
const grounding = projectGrounding(projectRoot);
|
|
64
|
+
const handoff = run ? executionSnapshot(context, run.project_id, run.id) : executionSnapshotFromProfile(context, start?.executionProfile);
|
|
65
|
+
return { projectRoot, intake, discussion, template, startInput, start, preflight, grounding, handoff };
|
|
66
|
+
}
|
|
67
|
+
function assertSpecifyNotStarted(context, projectRoot, runId) {
|
|
68
|
+
const existing = getFlowRunStatus(context, { projectRoot, runId });
|
|
69
|
+
if (existing.index?.stage_runs?.some(entry => entry.stage === stageId))
|
|
70
|
+
throw new AppError("stage_already_started", "SPECIFY is already started for this RUN", 1, { run_id: runId });
|
|
71
|
+
}
|
|
72
|
+
export function launchVnextSpecify(context, input, prepared = prepareVnextSpecifyStart(context, input)) {
|
|
73
|
+
const { projectRoot, intake, discussion, template, startInput, start, preflight, grounding, handoff } = prepared;
|
|
74
|
+
if (input.runId)
|
|
75
|
+
assertSpecifyNotStarted(context, projectRoot, input.runId);
|
|
76
|
+
const project = requireProjectByRoot(context, projectRoot);
|
|
77
|
+
const identity = hookSessionIdentity(context, project.id, input.hookEventId);
|
|
78
|
+
if (input.runId) {
|
|
79
|
+
assertRunMutationAllowed(context, project.id, input.runId);
|
|
80
|
+
assertRecoveryHookCurrent(context, project.id, input.runId, identity);
|
|
81
|
+
}
|
|
82
|
+
ensureVnextWorkStorage(context.db);
|
|
83
|
+
registerProject(context, { root: projectRoot });
|
|
84
|
+
const started = input.runId ? null : startFlowRun(context, startInput, start);
|
|
48
85
|
const run = requireRun(context, projectRoot, input.runId ?? started.run.id);
|
|
49
|
-
const existingStages = getFlowRunStatus(context, { projectRoot, runId: run.id });
|
|
50
|
-
if (existingStages.index?.stage_runs?.some((entry) => entry.stage === stageId))
|
|
51
|
-
throw new AppError("stage_already_started", "SPECIFY is already started for this RUN", 1, { run_id: run.id });
|
|
52
86
|
const runHome = requiredRunHome(run);
|
|
53
87
|
const stageRoot = path.join(runHome, "01-specify");
|
|
54
88
|
const intakeDir = path.join(runHome, "intake");
|
|
@@ -61,11 +95,6 @@ export function launchVnextSpecify(context, input) {
|
|
|
61
95
|
const contextPath = path.join(stageRoot, "work-context.json");
|
|
62
96
|
const workId = nextWorkId(context, run.project_id, "root");
|
|
63
97
|
const now = context.now();
|
|
64
|
-
const preflight = writablePreflight(projectRoot, stageRoot);
|
|
65
|
-
if (preflight.ok !== true)
|
|
66
|
-
throw new AppError("permission_preflight_failed", "SPECIFY workspace is not writable", 1, { preflight });
|
|
67
|
-
const grounding = projectGrounding(projectRoot);
|
|
68
|
-
const handoff = executionSnapshot(context, run.id);
|
|
69
98
|
const workContext = {
|
|
70
99
|
schema_id: "dd-flow/work-context@1",
|
|
71
100
|
system: { run_id: run.id, work_id: workId, stage: stageId },
|
|
@@ -82,7 +111,7 @@ export function launchVnextSpecify(context, input) {
|
|
|
82
111
|
};
|
|
83
112
|
writeJson(contextPath, workContext);
|
|
84
113
|
const pauseCommand = stagePauseCommand(context, { runId: run.id, stage: stageId, workId, projectRoot });
|
|
85
|
-
const prompt = renderPrompt({ workId, runId: run.id, projectRoot, stageRoot, resultPath, discussion, template, workContext, grounding, pauseCommand, pauseCommandTemplate: stagePauseCommandTemplate(pauseCommand),
|
|
114
|
+
const prompt = renderPrompt({ workId, runId: run.id, projectRoot, stageRoot, resultPath, discussion, template, workContext, grounding, pauseCommand, pauseCommandTemplate: stagePauseCommandTemplate(pauseCommand), finishCommand: managedLifecycleCommand(context, `${flowCommand(context)} stage finish ${run.id} --project-root ${JSON.stringify(projectRoot)} --stage specify --result-file ${JSON.stringify(path.join(stageRoot, "specify-result.json"))} --outcome specified --json`) });
|
|
86
115
|
fs.writeFileSync(promptPath, prompt);
|
|
87
116
|
const externalContext = applyExternalStageContext({ stageRoot, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
|
|
88
117
|
context.db.run(`INSERT INTO works (work_id, project_id, run_id, parent_work_id, task, launch_policy, result_schema, depends_on_json, status, result, started_at, created_at, updated_at, completed_at)
|
|
@@ -126,7 +155,9 @@ export function submitVnextSpecify(context, input) {
|
|
|
126
155
|
if (work.project_id !== project.id)
|
|
127
156
|
throw new AppError("project_mismatch", "Work does not belong to --project-root", 1, { work_id: work.work_id });
|
|
128
157
|
if (work.status !== "running") {
|
|
129
|
-
|
|
158
|
+
// This check precedes all result reads and mutations. A later legal state
|
|
159
|
+
// transition may make a new invocation appropriate, but never replays this one.
|
|
160
|
+
throw new AppError("invalid_work_state", "Work is not waiting for a SPECIFY result", 1, { work_id: work.work_id, status: work.status, retryable_no_effect: true });
|
|
130
161
|
}
|
|
131
162
|
const run = requireRun(context, projectRoot, work.run_id);
|
|
132
163
|
if (runStatus(context, project.id, run.id) !== "running") {
|
|
@@ -141,7 +172,7 @@ export function submitVnextSpecify(context, input) {
|
|
|
141
172
|
throw new AppError("not_found", "--result-file must point to an existing file inside the SPECIFY workspace", 1, { result_file: input.resultFile });
|
|
142
173
|
}
|
|
143
174
|
try {
|
|
144
|
-
validateSchema({ schemaName: "vnext-specify", file: candidateFile, projectRoot, ddFlowHome: context.ddFlowHome, runId: run.id });
|
|
175
|
+
validateSchema({ schemaName: "vnext-specify", file: candidateFile, projectRoot, ddFlowHome: context.ddFlowHome, runId: run.id, runRoot: runHome });
|
|
145
176
|
const result = readVnextSpecifyResult(candidateFile);
|
|
146
177
|
validateObligations(result, candidateFile);
|
|
147
178
|
const normalizedResult = `${JSON.stringify(result, null, 2)}\n`;
|
|
@@ -169,7 +200,7 @@ export function submitVnextSpecify(context, input) {
|
|
|
169
200
|
const htmlPath = path.join(stageRoot, "stage-report.html");
|
|
170
201
|
const report = buildStageReport({ run, work, workSession, outcome, result, resultMarkdown: renderedMarkdown, now, stageRoot, resultFile });
|
|
171
202
|
writeStageReport(stageRoot, report);
|
|
172
|
-
validateSchema({ schemaName: "stage-report", file: reportPath, projectRoot, ddFlowHome: context.ddFlowHome, runId: run.id });
|
|
203
|
+
validateSchema({ schemaName: "stage-report", file: reportPath, projectRoot, ddFlowHome: context.ddFlowHome, runId: run.id, runRoot: runHome });
|
|
173
204
|
completeFlowRunStage(context, {
|
|
174
205
|
projectRoot,
|
|
175
206
|
runId: run.id,
|
|
@@ -187,9 +218,9 @@ export function submitVnextSpecify(context, input) {
|
|
|
187
218
|
appendFlowRunTimelineEvent(context, run.project_id, run.id, { type: "work_completed", work_id: work.work_id, outcome, stage: stageId });
|
|
188
219
|
if (continuesToProtocolize) {
|
|
189
220
|
const nextWork = requireWork(context, work.work_id);
|
|
190
|
-
advanceFlowRun(context, { projectRoot, runId: run.id, status: "running", verdict: "specified", nextAction: "start_protocolize" });
|
|
221
|
+
advanceFlowRun(context, { settlementStage: "specify", projectRoot, runId: run.id, status: "running", verdict: "specified", nextAction: "start_protocolize" });
|
|
191
222
|
refreshRunWorkProjection(context, run.project_id, run.id);
|
|
192
|
-
const nextCommand = `${flowCommand(context)} stage start ${run.id} --stage protocolize --project-root ${JSON.stringify(projectRoot)} --json
|
|
223
|
+
const nextCommand = managedLifecycleCommand(context, `${flowCommand(context)} stage start ${run.id} --stage protocolize --project-root ${JSON.stringify(projectRoot)} --json`);
|
|
193
224
|
return {
|
|
194
225
|
ok: true,
|
|
195
226
|
schema_id: "dd-flow/vnext-work-submit@1",
|
|
@@ -241,13 +272,23 @@ export function finishVnextSpecifyStage(context, input) {
|
|
|
241
272
|
const run = requireRun(context, projectRoot, input.runId);
|
|
242
273
|
const stageRoot = path.join(requiredRunHome(run), "01-specify");
|
|
243
274
|
const acceptedResult = path.join(stageRoot, "specify.json");
|
|
244
|
-
const
|
|
275
|
+
const source = input.resultFile ? path.resolve(input.resultFile) : acceptedResult;
|
|
276
|
+
const prepared = input.result
|
|
277
|
+
? { file: source, value: input.result }
|
|
278
|
+
: input.resultJson !== undefined
|
|
279
|
+
? prepareJsonText(input.resultJson, { file: "<stdin>", label: "SPECIFY result" })
|
|
280
|
+
: prepareJsonSchema(source, "SPECIFY result", { schemaName: "vnext-specify", projectRoot, ddFlowHome: context.ddFlowHome, runId: run.id, runRoot: requiredRunHome(run) });
|
|
281
|
+
try {
|
|
282
|
+
validateSchema({ schemaName: "vnext-specify", file: prepared.file, data: prepared.value, projectRoot, ddFlowHome: context.ddFlowHome, runId: run.id, runRoot: requiredRunHome(run) });
|
|
283
|
+
validateObligations(prepared.value, prepared.file);
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
return prepareError(error, { file: prepared.file, label: "SPECIFY result" });
|
|
287
|
+
}
|
|
245
288
|
const candidate = path.join(stageRoot, `.specify-candidate-${crypto.randomUUID()}.json`);
|
|
246
|
-
|
|
247
|
-
//
|
|
248
|
-
|
|
249
|
-
if (input.resultFile && path.resolve(input.resultFile) === acceptedResult)
|
|
250
|
-
fs.rmSync(acceptedResult, { force: true });
|
|
289
|
+
// Candidate storage starts only after the exact input has passed schema and
|
|
290
|
+
// semantic checks; it is never the caller's source file.
|
|
291
|
+
writeText(candidate, `${JSON.stringify(prepared.value, null, 2)}\n`);
|
|
251
292
|
try {
|
|
252
293
|
return submitVnextSpecify(context, { projectRoot, workId: work.work_id, resultFile: candidate, outcome: input.outcome });
|
|
253
294
|
}
|
|
@@ -260,15 +301,11 @@ function readIntake(input) {
|
|
|
260
301
|
return { markdown: input.intakeMarkdown };
|
|
261
302
|
if (!input.intakeFile)
|
|
262
303
|
throw new AppError("usage", "SPECIFY intake is required", 2);
|
|
263
|
-
|
|
264
|
-
if (!fs.existsSync(intakeFile) || !fs.statSync(intakeFile).isFile()) {
|
|
265
|
-
throw new AppError("not_found", "--intake-file must point to an existing file", 1, { intake_file: input.intakeFile });
|
|
266
|
-
}
|
|
267
|
-
return { markdown: fs.readFileSync(intakeFile, "utf8") };
|
|
304
|
+
return { markdown: prepareTextFile(input.intakeFile, "SPECIFY intake").text };
|
|
268
305
|
}
|
|
269
306
|
function requireRun(context, projectRoot, runId) {
|
|
270
307
|
const project = requireProjectByRoot(context, projectRoot);
|
|
271
|
-
const run = context.db.get("SELECT id, project_id, project_root, workspace_root,
|
|
308
|
+
const run = context.db.get("SELECT id, project_id, project_root, workspace_root, run_root FROM runs WHERE project_id = ? AND id = ?", [project.id, runId]);
|
|
272
309
|
if (!run)
|
|
273
310
|
throw new AppError("not_found", "RUN is not registered", 1, { run_id: runId });
|
|
274
311
|
return run;
|
|
@@ -280,9 +317,9 @@ function runStatus(context, projectId, runId) {
|
|
|
280
317
|
return run.status;
|
|
281
318
|
}
|
|
282
319
|
function requiredRunHome(run) {
|
|
283
|
-
if (!run.
|
|
284
|
-
throw new AppError("runtime_missing", "RUN has no portable
|
|
285
|
-
return run.
|
|
320
|
+
if (!run.run_root)
|
|
321
|
+
throw new AppError("runtime_missing", "RUN has no portable artifact root", 1, { run_id: run.id });
|
|
322
|
+
return run.run_root;
|
|
286
323
|
}
|
|
287
324
|
function requireWork(context, workId) {
|
|
288
325
|
const work = context.db.get("SELECT * FROM works WHERE work_id = ?", [workId]);
|
|
@@ -299,28 +336,6 @@ function requireWorkSession(context, workSessionId) {
|
|
|
299
336
|
function latestWorkSession(context, workId) {
|
|
300
337
|
return context.db.get("SELECT * FROM work_sessions WHERE work_id = ? ORDER BY created_at DESC, id DESC LIMIT 1", [workId]) ?? null;
|
|
301
338
|
}
|
|
302
|
-
function writablePreflight(projectRoot, stageRoot) {
|
|
303
|
-
const memoryBank = preflightMemoryPermissions({
|
|
304
|
-
root: projectRoot,
|
|
305
|
-
memoryBank: ".memory-bank",
|
|
306
|
-
flow: "custom",
|
|
307
|
-
mode: "write",
|
|
308
|
-
targets: [{ path: ".memory-bank", mode: "read" }]
|
|
309
|
-
});
|
|
310
|
-
const probe = path.join(stageRoot, `.dd-flow-probe-${crypto.randomUUID()}`);
|
|
311
|
-
try {
|
|
312
|
-
fs.writeFileSync(probe, "");
|
|
313
|
-
fs.rmSync(probe);
|
|
314
|
-
return { ...memoryBank, ok: memoryBank.ok === true, stage_workspace: { path: stageRoot, status: "passed" } };
|
|
315
|
-
}
|
|
316
|
-
catch (error) {
|
|
317
|
-
try {
|
|
318
|
-
fs.rmSync(probe, { force: true });
|
|
319
|
-
}
|
|
320
|
-
catch { /* best effort */ }
|
|
321
|
-
return { ...memoryBank, ok: false, stage_workspace: { path: stageRoot, status: "failed", message: String(error) } };
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
339
|
function projectGrounding(projectRoot) {
|
|
325
340
|
return [
|
|
326
341
|
".memory-bank/index.md",
|
|
@@ -392,13 +407,13 @@ function renderPrompt(input) {
|
|
|
392
407
|
"```",
|
|
393
408
|
"The JSON must preserve the problem-space contract for a fresh PROTOCOLIZE worker: user intent, scope, acceptance and verification, settled defaults, relevant project facts, gap-method outcomes, task assessment, delivery shape and handoff. Use stable R-001... identifiers in requirements and AC-001... identifiers in acceptance_criteria. Do not describe implementation design.",
|
|
394
409
|
"Do not finish while a material user question remains. Use stage pause instead of adding a question to specify.json.",
|
|
395
|
-
"If a user answer is required,
|
|
410
|
+
"If a user answer is required, use the file and standalone lifecycle command below instead of finish. Write only the concise user-facing question packet to the named text file; do not combine file creation and dd-flow in one shell command:",
|
|
396
411
|
"```sh",
|
|
397
412
|
input.pauseCommandTemplate,
|
|
398
413
|
"```",
|
|
399
414
|
"The response tells you exactly what to ask and how to resume this same stage. Ask its user_message and stop the Turn.",
|
|
400
|
-
`Finish exactly once only after all questions are resolved. Run this as one standalone Bash command:\n\`${input.
|
|
401
|
-
"Do not combine the lifecycle command with cat, skill reads, Git commands, pipes or shell operators. If finish reports a validation error, correct the same result file and
|
|
415
|
+
`Finish exactly once only after all questions are resolved. Run this as one standalone Bash command:\n\`${input.finishCommand}\`.`,
|
|
416
|
+
"Do not combine the lifecycle command with cat, skill reads, Git commands, pipes or shell operators. If finish reports a no-effect validation error, correct the same result file and use its returned retry_command in this Session. Internal invocation IDs are bound by the runtime and must not be copied or invented. After success, follow only its returned next directive; a focused eval controller may explicitly stop at this stage boundary.",
|
|
402
417
|
"</output_contract>",
|
|
403
418
|
""
|
|
404
419
|
].join("\n");
|
|
@@ -562,12 +577,20 @@ export function readVnextFlowDefinition(projectRoot) {
|
|
|
562
577
|
}
|
|
563
578
|
return null;
|
|
564
579
|
}
|
|
565
|
-
function executionSnapshot(context, runId) {
|
|
566
|
-
const row = context.db.get("SELECT index_json FROM runs WHERE id = ?", [runId]);
|
|
580
|
+
function executionSnapshot(context, projectId, runId) {
|
|
581
|
+
const row = context.db.get("SELECT index_json FROM runs WHERE project_id = ? AND id = ?", [projectId, runId]);
|
|
567
582
|
const snapshot = row ? JSON.parse(row.index_json) : null;
|
|
568
|
-
|
|
583
|
+
return executionSnapshotFromProfile(context, snapshot?.execution_profile, runId);
|
|
584
|
+
}
|
|
585
|
+
function executionSnapshotFromProfile(context, profile, runId) {
|
|
586
|
+
let handoff = profile?.settings.stage_session_mode;
|
|
569
587
|
if (handoff !== "same_session" && handoff !== "new_session")
|
|
570
588
|
throw new AppError("execution_profile_invalid", "RUN has no frozen stage session mode", 1, { run_id: runId });
|
|
589
|
+
if (profile?.settings.execution) {
|
|
590
|
+
if (!profile.agent_profiles)
|
|
591
|
+
throw new AppError("execution_profile_not_frozen", "Stage handoff requires frozen RUN profiles", 1);
|
|
592
|
+
handoff = resolveStageTransition({ ddFlowHome: context.ddFlowHome, policy: profile.settings.execution, profiles: profile.agent_profiles, fromStage: "specify", stage: "protocolize", stageSessionMode: handoff, mergeMode: profile.settings.merge_mode }).session_mode;
|
|
593
|
+
}
|
|
571
594
|
return { stage_handoff: { effective: handoff, source: "run_override" } };
|
|
572
595
|
}
|
|
573
596
|
function executionSnapshotFromPath(contextPath, workId) {
|