@deksden-com/dd-flow-cli 0.9.0-beta.110 → 0.9.0-beta.112
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 +12 -0
- package/dist/build-info.json +3 -3
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +6 -0
- package/dist/harness-runtime/lib/dd-zcode.mjs +3 -1
- package/dist/services/code-checks.js +45 -9
- package/dist/services/external-work-launch.js +1 -1
- package/dist/services/merge-server.js +1 -1
- package/dist/services/run-controller-adapter.js +3 -1
- package/dist/services/vnext-plan.js +4 -2
- package/dist/services/workspace-files.js +3 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @deksden-com/dd-flow-cli
|
|
2
2
|
|
|
3
|
+
## 0.9.0-beta.112
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Give ZCode native Session creation a bounded end-to-end budget and admit the matching tested bridge commit, reject bare long-running service scripts as checks, run managed checks with the engine's Node architecture, and serialize concurrent Work checks within one RUN. Classify Git's expected EPIPE on a gitless check-ignore probe without hiding other Git failures.
|
|
8
|
+
|
|
9
|
+
## 0.9.0-beta.111
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Settle retired Grok child Sessions from their retained terminal native TaskOutput when session/info omits status; keep explicit activity, new attempts and outcome conflicts blocking settlement.
|
|
14
|
+
|
|
3
15
|
## 0.9.0-beta.110
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/dist/build-info.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"cli_package": "@deksden-com/dd-flow-cli",
|
|
3
|
-
"cli_version": "0.9.0-beta.
|
|
4
|
-
"cli_commit": "
|
|
5
|
-
"built_at": "2026-09-
|
|
3
|
+
"cli_version": "0.9.0-beta.112",
|
|
4
|
+
"cli_commit": "eeaf77a6f9dad6d16b993b0867b6098618556469",
|
|
5
|
+
"built_at": "2026-09-27T13:52:13.633Z",
|
|
6
6
|
"built_with_canon": {
|
|
7
7
|
"version": "4.1.1",
|
|
8
8
|
"commit": "678daa038287c948ada5b2d785a6dcc925c7b891",
|
|
@@ -291,6 +291,12 @@ export class Runtime {
|
|
|
291
291
|
}
|
|
292
292
|
rootRunning(result) {
|
|
293
293
|
const status = String(result?.info?.status ?? result?.info?.state ?? result?.info?.data?.status ?? "unknown").toLowerCase();
|
|
294
|
+
const child = this.descendants.get(result?.provider_session_id);
|
|
295
|
+
// Grok may retire a completed child Session before session/info can be
|
|
296
|
+
// inspected. A retained TaskOutput for that exact attempt is native
|
|
297
|
+
// terminal proof; missing session/info alone is not.
|
|
298
|
+
if (status === "unknown" && child?.source === "x.ai/tool/TaskOutput" && !child.outcome_error && !child.activity_status
|
|
299
|
+
&& ["completed", "failed", "cancelled"].includes(child.status)) return false;
|
|
294
300
|
// Grok 1.0.17 omits status from session/info. Only a durable native
|
|
295
301
|
// creation/end_turn receipt can then establish an idle root. Dispatch
|
|
296
302
|
// clears that receipt before sending any subsequent productive request.
|
|
@@ -804,6 +804,7 @@ export function zcodeLifecycleQualification(sha256, bridgeCommit, harnessContrac
|
|
|
804
804
|
["f4d47b517073f9bdb8d4eba3d0d59ce66c6a2833", "dd-zcode-harness@2"],
|
|
805
805
|
["5a8c89ab716a4b2ccc50df81cb4d204ff5c06368", "dd-zcode-harness@2"],
|
|
806
806
|
["91a1470650f5fd6105f9863fd31d1cad7b90a109", "dd-zcode-harness@2"],
|
|
807
|
+
["b9e0f5106dddb439b76186278bd9be5f45dbf3ef", "dd-zcode-harness@2"],
|
|
807
808
|
]);
|
|
808
809
|
const qualified = qualifiedBridgeCommits.get(bridgeCommit) === harnessContract;
|
|
809
810
|
return { status: qualified ? "qualified" : "unqualified", mechanism: "cli-invocation@1", sha256,
|
|
@@ -845,7 +846,8 @@ export async function createSessionWithBridge(bridge, options, initialized) {
|
|
|
845
846
|
if (identity.cwd !== undefined && identity.cwd !== cwd) fail("ZCode allocation workspace mismatch", "session_identity_mismatch");
|
|
846
847
|
await options.onSessionAllocated?.({ ...identity, cwd });
|
|
847
848
|
});
|
|
848
|
-
|
|
849
|
+
// Two provider syncs (10s each) and native create (90s) run inside resolve.
|
|
850
|
+
const resolved = await bridge.request("zcode/session/resolve", { sessionId: created.sessionId }, 150_000);
|
|
849
851
|
const providerSessionId = resolved.providerSessionId;
|
|
850
852
|
// Expose both identities before profile/usage bookkeeping. The owning
|
|
851
853
|
// daemon can persist this allocation even when a later step fails.
|
|
@@ -15,9 +15,9 @@ const profileRelativePath = path.join(".memory-bank", "spec", "engineering", "co
|
|
|
15
15
|
const receiptStartingGraceMs = 30_000;
|
|
16
16
|
export function codeExecutionEnvironment(workspaceRoot) {
|
|
17
17
|
const suffix = crypto.createHash("sha256").update(path.resolve(workspaceRoot)).digest("hex").slice(0, 12);
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
const environment = { ...process.env, DD_FLOW_LOCAL_DATABASE_SUFFIX: suffix };
|
|
18
|
+
// The managed CLI's Node must win over a provider app's PATH: a Rosetta
|
|
19
|
+
// provider can otherwise run x64 checks against arm64-installed modules.
|
|
20
|
+
const environment = { ...process.env, PATH: `${path.dirname(process.execPath)}${path.delimiter}${process.env.PATH ?? ""}`, DD_FLOW_LOCAL_DATABASE_SUFFIX: suffix };
|
|
21
21
|
delete environment.NODE_PATH;
|
|
22
22
|
return environment;
|
|
23
23
|
}
|
|
@@ -102,6 +102,8 @@ export function validateCheckDeclaration(workspaceRoot, check) {
|
|
|
102
102
|
if (!["work", "code", "readiness", "merge", "release"].includes(check.run_at))
|
|
103
103
|
throw new AppError("unsupported_check_gate", "Check requires an autonomous executable gate; replan the incompatible declaration", 2, { check_id: check.id, run_at: check.run_at });
|
|
104
104
|
const { profile } = readCodeCheckProfile(workspaceRoot);
|
|
105
|
+
const resolvedCommand = profile?.aliases[check.command] ?? check.command;
|
|
106
|
+
validateTerminatingCheckCommand(resolvedCommand, check.id);
|
|
105
107
|
if (check.availability === "planned" && check.command.startsWith("@check/") && !profile?.aliases[check.command])
|
|
106
108
|
throw new AppError("planned_check_not_materialized", "A planned check alias was not materialized by its provider Work", 2, { check_id: check.id, command: check.command, provider: check.provided_by ?? null });
|
|
107
109
|
validateCodeCheckCommands(workspaceRoot, [check.command]);
|
|
@@ -119,6 +121,26 @@ export function validateCheckDeclaration(workspaceRoot, check) {
|
|
|
119
121
|
if (!profile?.aliases[check.command])
|
|
120
122
|
throw new AppError("planned_check_not_materialized", "A planned check alias was not materialized by its provider Work", 2, { check_id: check.id, command: check.command, provider: check.provided_by ?? null });
|
|
121
123
|
}
|
|
124
|
+
export function validateTerminatingCheckCommand(command, checkId) {
|
|
125
|
+
if (isBareServiceScript(command))
|
|
126
|
+
throw new AppError("non_terminating_check", "A check must terminate; run a finite test instead of a bare dev/start/serve/watch service script", 2, { check_id: checkId, command });
|
|
127
|
+
}
|
|
128
|
+
function isBareServiceScript(command) {
|
|
129
|
+
const words = parse(command.trim());
|
|
130
|
+
if (words.some((word) => typeof word !== "string"))
|
|
131
|
+
return false;
|
|
132
|
+
let args = words;
|
|
133
|
+
while (/^[A-Za-z_][A-Za-z0-9_]*=/.test(args[0] ?? ""))
|
|
134
|
+
args = args.slice(1);
|
|
135
|
+
const manager = path.basename(args.shift() ?? "");
|
|
136
|
+
if (!["pnpm", "npm", "yarn", "bun"].includes(manager))
|
|
137
|
+
return false;
|
|
138
|
+
while (args.length && ["--filter", "-F", "--dir", "-C", "--cwd"].includes(args[0]))
|
|
139
|
+
args.splice(0, 2);
|
|
140
|
+
if (args[0] === "run")
|
|
141
|
+
args.shift();
|
|
142
|
+
return ["dev", "start", "serve", "watch"].includes(args[0] ?? "");
|
|
143
|
+
}
|
|
122
144
|
/** Reject a plan-time typo or a fictitious "manual" command before it becomes
|
|
123
145
|
* a late shell exit. Compound shell programs remain valid: only a simple,
|
|
124
146
|
* directly named executable is preflighted here. */
|
|
@@ -148,18 +170,32 @@ function assertDirectCheckExecutable(workspaceRoot, command, checkId) {
|
|
|
148
170
|
throw new AppError("check_command_unavailable", "Available CODE check names an executable that is unavailable in this environment", 2, { check_id: checkId, command, executable: program });
|
|
149
171
|
}
|
|
150
172
|
export async function runCodeChecks(context, input) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
173
|
+
const generation = assertRunMutationAllowed(context, input.projectId, input.runId);
|
|
174
|
+
let gate;
|
|
175
|
+
for (;;) {
|
|
176
|
+
assertRunMutationAllowed(context, input.projectId, input.runId, generation);
|
|
177
|
+
reconcileUnfinishedChecks(context, input);
|
|
178
|
+
reconcileWorkspaceOwnership(context, input.workspaceRoot);
|
|
179
|
+
try {
|
|
180
|
+
gate = acquireWorkspaceGate(context, input);
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
const sameRunGate = managedProcessStatus(context).find(item => item.kind === "workspace-gate" && item.operation_id === workspaceOperationId(input.workspaceRoot) && ["starting", "running", "stopping"].includes(item.state) && item.project_id === input.projectId && item.run_id === input.runId);
|
|
185
|
+
if (input.scope !== "work" || !input.workId || !(error instanceof AppError) || !["check_in_progress", "managed_operation_in_progress"].includes(error.code) || !sameRunGate)
|
|
186
|
+
throw error;
|
|
187
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
154
190
|
try {
|
|
155
|
-
return await runCodeChecksOwned(context, input, gate.id);
|
|
191
|
+
return await runCodeChecksOwned(context, input, gate.id, generation);
|
|
156
192
|
}
|
|
157
193
|
finally {
|
|
158
194
|
finishManagedProcess(context, { id: gate.id, leaseToken: gate.lease_token, state: "stopped", reason: "workspace_gate_settled" });
|
|
159
195
|
}
|
|
160
196
|
}
|
|
161
|
-
async function runCodeChecksOwned(context, input, workspaceGateId) {
|
|
162
|
-
|
|
197
|
+
async function runCodeChecksOwned(context, input, workspaceGateId, generation) {
|
|
198
|
+
assertRunMutationAllowed(context, input.projectId, input.runId, generation);
|
|
163
199
|
for (const check of input.checks)
|
|
164
200
|
validateCheckDeclaration(input.workspaceRoot, check);
|
|
165
201
|
const { profile } = readCodeCheckProfile(input.workspaceRoot);
|
|
@@ -94,7 +94,7 @@ export async function launchExternalWork(context, input) {
|
|
|
94
94
|
const call = (args, timeoutMs) => runHarnessAdapter({ executable, args, env: runtimeContext.env, operationId: `${operationId}:${args[0]}.${args[1]}`, runtimeOwner, ...(timeoutMs ? { timeoutMs } : {}), ...(input.progress ? { progress: input.progress } : {}), onEvidence: evidence });
|
|
95
95
|
await call(["daemon", "start", ...common]);
|
|
96
96
|
assertRunMutationAllowed(context, work.project_id, work.run_id, generation);
|
|
97
|
-
const created = await call(["session", "create", ...common]);
|
|
97
|
+
const created = await call(["session", "create", ...common], runtimeHarness(resolved.worker) === "zcode-acp" ? 180_000 : undefined);
|
|
98
98
|
const sessionId = adapterSessionId(created);
|
|
99
99
|
if (!sessionId)
|
|
100
100
|
throw new AppError("harness_adapter_invalid", "Harness adapter did not return a typed Session id", 1, { work_id: work.work_id, harness: runtimeHarness(resolved.worker) });
|
|
@@ -112,7 +112,7 @@ async function dispatch(context, input) {
|
|
|
112
112
|
try {
|
|
113
113
|
const adapterCall = (args, timeoutMs, progressMessage) => { assertRunMutationAllowed(context, input.request.project_id, input.request.run_id, generation); return runHarnessAdapter({ executable, args, env: runtimeContext.env, runtimeOwner, operationId: `${input.request.merge_request_id}:${token}:${args[0]}.${args[1]}`, ...(timeoutMs ? { timeoutMs } : {}), ...(input.progress ? { progress: input.progress } : {}), ...(progressMessage ? { progressMessage } : {}), onEvidence: (value) => event(input.journal, { type: "adapter_call", ...value }) }); };
|
|
114
114
|
await adapterCall(["daemon", "start", ...common]);
|
|
115
|
-
const created = await adapterCall(["session", "create", ...common]);
|
|
115
|
+
const created = await adapterCall(["session", "create", ...common], runtimeHarness(input.profile) === "zcode-acp" ? 180_000 : undefined);
|
|
116
116
|
const sessionId = adapterSessionId(created);
|
|
117
117
|
if (!sessionId)
|
|
118
118
|
throw new AppError("merge_adapter_invalid", "Harness adapter did not return a Session id", 1, { harness: input.profile.harness, response: created });
|
|
@@ -197,7 +197,9 @@ export async function controllerAdapter(context, row, manifest, session, args, a
|
|
|
197
197
|
let receipt;
|
|
198
198
|
let recoveredOperation = false;
|
|
199
199
|
try {
|
|
200
|
-
|
|
200
|
+
const timeoutMs = args[0] === "session" && args[1] === "prompt" ? null
|
|
201
|
+
: args[0] === "session" && args[1] === "create" && runtimeHarness(session.profile) === "zcode-acp" ? 180_000 : 120_000;
|
|
202
|
+
receipt = await runHarnessAdapter({ executable, args, env: controllerEnvironment(context, manifest, session), operationId, runtimeOwner: session.owner, timeoutMs, ...(signal ? { signal } : {}) });
|
|
201
203
|
}
|
|
202
204
|
catch (error) {
|
|
203
205
|
const retained = await recoverCompletedOperation(context, manifest, session, executable, args, operationId, signal);
|
|
@@ -5,7 +5,7 @@ import { withStageSettlement, prepareFlowRunStageAttachment, prepareFlowRunStage
|
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { AppError } from "../shared/errors.js";
|
|
8
|
-
import { effectiveCheckDeclarations, readCodeCheckProfile, validateCheckDeclaration, validateCheckPlacement, validateCodeCheckCommands } from "./code-checks.js";
|
|
8
|
+
import { effectiveCheckDeclarations, readCodeCheckProfile, validateCheckDeclaration, validateCheckPlacement, validateCodeCheckCommands, validateTerminatingCheckCommand } from "./code-checks.js";
|
|
9
9
|
import { requireProjectByRoot } from "./projects.js";
|
|
10
10
|
import { canonicalPath, resolveProjectRoot } from "../storage/paths.js";
|
|
11
11
|
import { advanceFlowRun, appendFlowRunTimelineEvent, attachFlowRunStage, completeFlowRunStage, getFlowRunVariables, gitFacts } from "./runs.js";
|
|
@@ -102,7 +102,7 @@ export function startVnextPlan(context, input, prepared = prepareVnextPlanStart(
|
|
|
102
102
|
? [`This RUN must reach MERGE. Project policy already supplies the mandatory merge gate${policyMergeAliases.length === 1 ? "" : "s"}: ${policyMergeAliases.join(", ")}. Do not duplicate them in semantic checks[]. Add another merge check only when the task genuinely needs additional evidence.`]
|
|
103
103
|
: ["This RUN must reach MERGE and project policy supplies no merge gate. Select at least one real top-level checks[] entry with run_at: merge. It may use an existing project alias or a planned alias materialised by a named P* provider Work. This is a planning obligation: do not defer it to CODE-REVIEW or MERGE."]), "The CLI validates the effective merge gate but never invents one or migrates an incompatible project policy.", "</merge_gate_contract>", ""]
|
|
104
104
|
: [];
|
|
105
|
-
const prompt = ["<stage_identity>", `- RUN: ${run.id}`, `- Work: ${planWorkId}`, "- stage: plan", "</stage_identity>", "", "<trusted_runtime_context>", "These facts were collected by dd-flow. Trust them; do not repeat CLI, Git, compatibility or permission discovery.", `- Project root: ${projectRoot}`, `- Workspace: ${run.workspace_root}`, `- Stage workspace: ${root}`, `- Git: ${JSON.stringify(git)}`, capacityContext, "</trusted_runtime_context>", "", "<workspace_contract>", `- route: ${workspaceRoute.route}`, `- feature branch: ${workspaceRoute.feature_branch ?? "not applicable"}`, `- base commit: ${workspaceRoute.base_ref ?? "not applicable"}`, `- write workspace: ${run.workspace_root}`, "The CLI has verified this frozen route. All project reads and writes for PLAN and later CODE happen in the write workspace; project root is only the stable runtime identity for lifecycle commands. Do not create, switch, merge or delete branches/worktrees.", "Keep the task runner's current cwd. Use the absolute paths in this packet instead of trying to set the provisioned workspace as a tool workdir.", "</workspace_contract>", "", "<accepted_inputs>", `- ${path.join(home, "01-specify", "specify.json")}`, `- ${path.join(home, "02-protocolize", "protocolize-result.json")}`, ...protocols.map((id) => `- ${path.join(run.workspace_root, ".memory-bank", "protocol", id, "summary.md")}`), "</accepted_inputs>", "", ...(codeCheckProfile ? ["<code_check_policy>", "You, not the CLI, select evidence for every accepted requirement and acceptance criterion. The profile only lists reusable aliases, mandatory project policy gates and guarded raw command prefixes. Inspect relevant package/test manifests before choosing a check. Do not classify checks by weight and do not omit a needed check because it looks expensive.", JSON.stringify(codeCheckProfile, null, 2), "</code_check_policy>", ""] : []), ...mergeContract, "<artifacts>", "The CLI has already materialized every artifact below as a partially filled draft. Edit these files in place; do not create replacements elsewhere.", "Prefilled and CLI-owned plan fields: schema_id, plan_id, protocol_id, initial revision and source_refs.", "Prefilled and CLI-owned aspect-map fields: schema_id, protocol_id, plan_id, plan revision, catalog_ref and every catalog aspect_id.", "You own the remaining semantic fields. Empty or missing semantic values are intentional draft markers and must be completed before validation.", ...planPaths.map((value) => `- partially filled plan: ${value}`), ...mapPaths.map((value) => `- partially filled aspect map: ${value}`), "</artifacts>", "", "<output_contract>", "Complete every named plan and aspect map in place. Do not create or edit code-work-batch.json: dd-flow derives it after validation.", "The CLI owns schema_id, plan_id, protocol_id, revision and source_refs. Preserve them exactly.", "Use protocol-plan@6. Its top-level checks[] is the single check catalog. Every check has id, command, purpose, run_at and availability. available means executable now. planned means one named P* Work first creates a NEW @check/... alias: planned therefore always needs provided_by and the exact alias definition. Every semantic @check alias, including an existing one, repeats its exact accepted profile command in definition so later stages can detect drift. Items and acceptance entries use check_refs only; never duplicate command declarations.", "For each R-* and AC-*, choose an actually relevant proof: an existing focused test, a new planned alias plus its provider Work, a project policy gate, or an autonomous executable check. Every plan item needs at least one check_ref. The CLI validates ids, provider ordering, materialization and guarded command policy; it never chooses a check for you. A provider Work may verify itself with the alias it has just created. A consumer must depend on that provider.", "Each plan item must name concrete existing source/test paths in required_read. planned_write_areas is optional: use stable component directories or files only when they help coordinate parallel Work; it is never a write allowlist. Reference every owned R-* and AC-* in one or more items; every AC-* needs an observable acceptance proof.", "For every selected check, inspect its command's launch path and the runtime entrypoints it starts. The fixture/reset process, service process and client process must observe one intended environment and data world. If a required runtime entrypoint needs a code change, make that change explicit in the Work task and its verification. Use planned_write_areas only to advertise likely concurrent overlap; do not treat it as ownership or assume another Work will repair an omitted change. If an independent infrastructure Work is clearer, plan that Work explicitly and order consumers after it.", reviewGroupingRule, "Complete compact contract and schema paths:", `- protocol plan schema: ${path.join(run.workspace_root, ".memory-bank", "dd-flow", "schemas", "vnext-protocol-plan.schema.json")}`, `- aspect map schema: ${path.join(run.workspace_root, ".memory-bank", "dd-flow", "schemas", "plan-aspect-map.schema.json")}`, "Minimal valid protocol-plan shape:", "```json", JSON.stringify(planExample(protocols[0]), null, 2), "```", "Minimal valid aspect-map shape:", "```json", JSON.stringify(aspectMapExample(protocols[0]), null, 2), "```", "</output_contract>", "", "<execution_commands>", "PLAN never launches independent reviewers or registers CODE Work.", "If PLAN needs a material user decision with no reasonable default, write the question packet to the named text file, then run the separate standalone lifecycle command below. Do not combine file creation and dd-flow in one shell command:", "```sh", pauseCommandTemplate, "```", "Ask the returned user_message, stop, and resume this same PLAN Work with the exact returned command.", "Validate both partially filled drafts after completing their semantic fields:", ...validationCommands.map((command) => `- ${command}`), "Finish PLAN only after all questions are resolved and both validation commands pass:", finishCommand, "The response returns the only PLAN-REVIEW start command. Follow it; do not start CODE directly.", "</execution_commands>", "", "<stage_instructions>", template, "</stage_instructions>", ""].join("\n");
|
|
105
|
+
const prompt = ["<stage_identity>", `- RUN: ${run.id}`, `- Work: ${planWorkId}`, "- stage: plan", "</stage_identity>", "", "<trusted_runtime_context>", "These facts were collected by dd-flow. Trust them; do not repeat CLI, Git, compatibility or permission discovery.", `- Project root: ${projectRoot}`, `- Workspace: ${run.workspace_root}`, `- Stage workspace: ${root}`, `- Git: ${JSON.stringify(git)}`, capacityContext, "</trusted_runtime_context>", "", "<workspace_contract>", `- route: ${workspaceRoute.route}`, `- feature branch: ${workspaceRoute.feature_branch ?? "not applicable"}`, `- base commit: ${workspaceRoute.base_ref ?? "not applicable"}`, `- write workspace: ${run.workspace_root}`, "The CLI has verified this frozen route. All project reads and writes for PLAN and later CODE happen in the write workspace; project root is only the stable runtime identity for lifecycle commands. Do not create, switch, merge or delete branches/worktrees.", "Keep the task runner's current cwd. Use the absolute paths in this packet instead of trying to set the provisioned workspace as a tool workdir.", "</workspace_contract>", "", "<accepted_inputs>", `- ${path.join(home, "01-specify", "specify.json")}`, `- ${path.join(home, "02-protocolize", "protocolize-result.json")}`, ...protocols.map((id) => `- ${path.join(run.workspace_root, ".memory-bank", "protocol", id, "summary.md")}`), "</accepted_inputs>", "", ...(codeCheckProfile ? ["<code_check_policy>", "You, not the CLI, select evidence for every accepted requirement and acceptance criterion. The profile only lists reusable aliases, mandatory project policy gates and guarded raw command prefixes. Inspect relevant package/test manifests before choosing a check. Do not classify checks by weight and do not omit a needed check because it looks expensive.", JSON.stringify(codeCheckProfile, null, 2), "</code_check_policy>", ""] : []), ...mergeContract, "<artifacts>", "The CLI has already materialized every artifact below as a partially filled draft. Edit these files in place; do not create replacements elsewhere.", "Prefilled and CLI-owned plan fields: schema_id, plan_id, protocol_id, initial revision and source_refs.", "Prefilled and CLI-owned aspect-map fields: schema_id, protocol_id, plan_id, plan revision, catalog_ref and every catalog aspect_id.", "You own the remaining semantic fields. Empty or missing semantic values are intentional draft markers and must be completed before validation.", ...planPaths.map((value) => `- partially filled plan: ${value}`), ...mapPaths.map((value) => `- partially filled aspect map: ${value}`), "</artifacts>", "", "<output_contract>", "Complete every named plan and aspect map in place. Do not create or edit code-work-batch.json: dd-flow derives it after validation.", "The CLI owns schema_id, plan_id, protocol_id, revision and source_refs. Preserve them exactly.", "Use protocol-plan@6. Its top-level checks[] is the single check catalog. Every check has id, command, purpose, run_at and availability. available means executable now. planned means one named P* Work first creates a NEW @check/... alias: planned therefore always needs provided_by and the exact alias definition. Every semantic @check alias, including an existing one, repeats its exact accepted profile command in definition so later stages can detect drift. Items and acceptance entries use check_refs only; never duplicate command declarations.", "For each R-* and AC-*, choose an actually relevant proof: an existing focused test, a new planned alias plus its provider Work, a project policy gate, or an autonomous executable check. Every plan item needs at least one check_ref. The CLI validates ids, provider ordering, materialization and guarded command policy; it never chooses a check for you. A provider Work may verify itself with the alias it has just created. A consumer must depend on that provider.", "Each check must terminate and return an exit status. Never declare a bare dev/start/serve/watch service command as a check; use a finite test that manages its own service lifecycle (for example, a browser test with a webServer fixture).", "Each plan item must name concrete existing source/test paths in required_read. planned_write_areas is optional: use stable component directories or files only when they help coordinate parallel Work; it is never a write allowlist. Reference every owned R-* and AC-* in one or more items; every AC-* needs an observable acceptance proof.", "For every selected check, inspect its command's launch path and the runtime entrypoints it starts. The fixture/reset process, service process and client process must observe one intended environment and data world. If a required runtime entrypoint needs a code change, make that change explicit in the Work task and its verification. Use planned_write_areas only to advertise likely concurrent overlap; do not treat it as ownership or assume another Work will repair an omitted change. If an independent infrastructure Work is clearer, plan that Work explicitly and order consumers after it.", reviewGroupingRule, "Complete compact contract and schema paths:", `- protocol plan schema: ${path.join(run.workspace_root, ".memory-bank", "dd-flow", "schemas", "vnext-protocol-plan.schema.json")}`, `- aspect map schema: ${path.join(run.workspace_root, ".memory-bank", "dd-flow", "schemas", "plan-aspect-map.schema.json")}`, "Minimal valid protocol-plan shape:", "```json", JSON.stringify(planExample(protocols[0]), null, 2), "```", "Minimal valid aspect-map shape:", "```json", JSON.stringify(aspectMapExample(protocols[0]), null, 2), "```", "</output_contract>", "", "<execution_commands>", "PLAN never launches independent reviewers or registers CODE Work.", "If PLAN needs a material user decision with no reasonable default, write the question packet to the named text file, then run the separate standalone lifecycle command below. Do not combine file creation and dd-flow in one shell command:", "```sh", pauseCommandTemplate, "```", "Ask the returned user_message, stop, and resume this same PLAN Work with the exact returned command.", "Validate both partially filled drafts after completing their semantic fields:", ...validationCommands.map((command) => `- ${command}`), "Finish PLAN only after all questions are resolved and both validation commands pass:", finishCommand, "The response returns the only PLAN-REVIEW start command. Follow it; do not start CODE directly.", "</execution_commands>", "", "<stage_instructions>", template, "</stage_instructions>", ""].join("\n");
|
|
106
106
|
const artifactMaterialization = { status: "materialized", completeness: "partially_filled", plan_paths: planPaths, aspect_map_paths: mapPaths, cli_owned_plan_fields: ["schema_id", "plan_id", "protocol_id", "revision", "source_refs"], cli_owned_aspect_map_fields: ["schema_id", "protocol_id", "plan_id", "plan_revision", "catalog_ref", "aspects[].aspect_id"], validation_commands: validationCommands };
|
|
107
107
|
const promptPath = path.join(root, "stage-prompt.md");
|
|
108
108
|
// This explicit final rule supersedes historical pack wording: a flow gate
|
|
@@ -218,6 +218,8 @@ export function prepareVnextPlanArtifacts(context, input) {
|
|
|
218
218
|
validateCodeCheckCommands(workspaceRoot, value.checks.filter((check) => check.availability === "available").map((check) => check.command));
|
|
219
219
|
for (const check of value.checks.filter((item) => item.availability === "available"))
|
|
220
220
|
validateCheckDeclaration(workspaceRoot, check);
|
|
221
|
+
for (const check of value.checks.filter((item) => item.availability === "planned" && item.definition))
|
|
222
|
+
validateTerminatingCheckCommand(check.definition, check.id);
|
|
221
223
|
// Placement is a PLAN-time rule. A Work may legitimately materialize a
|
|
222
224
|
// new aggregate alias later, so rechecking against the changed profile
|
|
223
225
|
// at CODE entry would retroactively invalidate an accepted plan.
|
|
@@ -11,7 +11,9 @@ export function localEnvironmentAllowed(source) {
|
|
|
11
11
|
}
|
|
12
12
|
/** A damaged/inaccessible repository is not an intentional gitless workspace. */
|
|
13
13
|
export function isGitlessWorkspace(root, result) {
|
|
14
|
-
|
|
14
|
+
// git check-ignore can exit 128 before consuming --stdin, yielding EPIPE
|
|
15
|
+
// alongside the authoritative "not a git repository" diagnostic.
|
|
16
|
+
if ((result.error && result.error.code !== "EPIPE") || result.status !== 128 || !/not a git repository|outside a repository/i.test(result.stderr ?? ""))
|
|
15
17
|
return false;
|
|
16
18
|
for (let directory = path.resolve(root);; directory = path.dirname(directory)) {
|
|
17
19
|
try {
|