@deksden-com/dd-flow-cli 0.9.0-beta.81 → 0.9.0-beta.83

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @deksden-com/dd-flow-cli
2
2
 
3
+ ## 0.9.0-beta.83
4
+
5
+ ### Patch Changes
6
+
7
+ - bf69c56: Accept shell-escaped lifecycle path aliases deterministically and render model-facing aliases with stable quoting.
8
+
9
+ ## 0.9.0-beta.82
10
+
11
+ ### Patch Changes
12
+
13
+ - Keep release registry readback running through delayed npm propagation while still failing authorization errors immediately.
14
+
3
15
  ## 0.9.0-beta.81
4
16
 
5
17
  ### Patch Changes
@@ -1224,6 +1236,7 @@ record` command with the bounded 15-probe contract.
1224
1236
  - 16f68a3: Add router-native engine snapshot commands and routed project-command dispatch.
1225
1237
 
1226
1238
  Also include the runtime/dashboard compatibility wave needed by the current Memory Bank canon:
1239
+
1227
1240
  - target-based dashboard `open`/`refresh` commands and related-command help;
1228
1241
  - persisted FIFO lane waiters and merge wait-acquire specialization;
1229
1242
  - `migration plan/report/verify` evidence helpers for controlled `mb-upgrade` runtime migrations;
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "cli_package": "@deksden-com/dd-flow-cli",
3
- "cli_version": "0.9.0-beta.81",
4
- "cli_commit": "0e1bd85b82868a501bc9e368e103fe288445370a",
5
- "built_at": "2026-09-20T10:24:22.045Z",
3
+ "cli_version": "0.9.0-beta.83",
4
+ "cli_commit": "7ede03ea5da48b77ab59b46bed8028b2a885da61",
5
+ "built_at": "2026-09-20T14:09:35.175Z",
6
6
  "built_with_canon": {
7
7
  "version": "4.1.1",
8
8
  "commit": "97f811d33c212ae3497020178b1ed825c7c3ebac",
@@ -4,7 +4,7 @@ import { randomUUID } from "node:crypto";
4
4
  import { quote } from "shell-quote";
5
5
  import { observeLifecycleCommand } from "../services/lifecycle-invocations.js";
6
6
  import { verifyCodexHookDelivery } from "../services/codex-hook-delivery.js";
7
- import { assertLifecycleInvocationCurrent, awaitLifecycleInvocation, expandLifecycleInvocationArgs, issueLifecycleInvocation, prepareLifecycleIssuance, lifecycleInvocationScope, observedLifecycleInvocation, settleLifecycleInvocation, settleLifecycleRejection } from "../services/lifecycle-invocations.js";
7
+ import { assertLifecycleInvocationCurrent, awaitLifecycleInvocation, expandLifecycleInvocationArgs, issueLifecycleInvocation, prepareLifecycleIssuance, lifecycleInvocationScope, observedLifecycleInvocation, settleLifecycleInvocation, settleLifecycleRejection, validateLifecyclePathAliases } from "../services/lifecycle-invocations.js";
8
8
  import { createContext, createRouterContext } from "../runtime/context.js";
9
9
  import { migrateStoreWriter } from "../storage/writer-migration.js";
10
10
  import { helpForArgs } from "./help.js";
@@ -130,6 +130,8 @@ export async function runCli(args, io = defaultIo, env = process.env) {
130
130
  const routerContext = createRouterContext(env);
131
131
  const publicCommand = quote(["dd-flow", ...output.args]);
132
132
  const lifecycleCommand = parseLifecycleCommand(publicCommand);
133
+ if (lifecycleCommand.kind === "standalone")
134
+ validateLifecyclePathAliases(output.args, lifecycleCommand.invocation.operation);
133
135
  const observedInvocation = observedLifecycleInvocation(routerContext, publicCommand);
134
136
  if (!observedInvocation && !suppliedInvocationId && env.DD_FLOW_DAEMON_ID && lifecycleCommand.kind === "standalone") {
135
137
  throw new AppError("invocation_receipt_missing", "Managed lifecycle command has no committed native receipt", 1, { effect: "no_effect", recoverable: false });
@@ -64,6 +64,7 @@ function fingerprint(parsed) {
64
64
  return crypto.createHash("sha256").update(JSON.stringify([parsed.operation, parsed.args.positional.map(value => normalizedInvocationValue("positional", value)), options])).digest("hex");
65
65
  }
66
66
  function normalizedInvocationValue(key, value) {
67
+ value = normalizedPathAlias(value);
67
68
  if (key === "project-root" && (path.isAbsolute(value) || value === "@project"))
68
69
  return "@project";
69
70
  if (["result-file", "decision-file", "verification-file"].includes(key))
@@ -79,6 +80,16 @@ function normalizedInvocationValue(key, value) {
79
80
  return shortEntityReference(value);
80
81
  return key === "retry-check" ? /(?:^|\/)(RCP-\d{3,})$/.exec(value)?.[1] ?? value : value;
81
82
  }
83
+ /** Shell/JSON builders sometimes preserve the shell escape before an alias.
84
+ * Accept it only for the small declared alias vocabulary. */
85
+ function normalizedPathAlias(value) {
86
+ return value.replace(/^\\+(?=@(?:project|workspace|run)(?:\/|$))/, "");
87
+ }
88
+ function quoteModelFacingArgs(argv) {
89
+ return argv.map(value => /^@(project|workspace|run)(?:\/.*)?$/.test(value)
90
+ ? `'${value.replaceAll("'", `'\\''`)}'`
91
+ : quote([value])).join(" ");
92
+ }
82
93
  function withoutInvocationId(command, aliases) {
83
94
  const parsed = invocation(command);
84
95
  const argv = [...parsed.argv];
@@ -109,7 +120,7 @@ function withoutInvocationId(command, aliases) {
109
120
  const executable = parsed.env.DD_FLOW_BIN || parsed.executable === "$DD_FLOW_BIN" ? '"$DD_FLOW_BIN"' : quote([parsed.executable]);
110
121
  const prefix = Object.entries(env).map(([key, value]) => `${key}=${quote([value])}`).join(" ");
111
122
  const suffix = command.slice(shellSuffixOffset(command)).trimStart();
112
- return `${prefix ? `${prefix} ` : ""}${executable}${argv.length ? ` ${quote(argv)}` : ""}${suffix ? ` ${suffix}` : ""}`.trim();
123
+ return `${prefix ? `${prefix} ` : ""}${executable}${argv.length ? ` ${quoteModelFacingArgs(argv)}` : ""}${suffix ? ` ${suffix}` : ""}`.trim();
113
124
  }
114
125
  function publicInvocationCommand(context, row, command) {
115
126
  const rendered = renderInvocationCommand(row, command);
@@ -128,7 +139,10 @@ export function expandLifecycleInvocationArgs(context, args, scope, operation) {
128
139
  if (run && !args[index - 1]?.startsWith("--") && [run.short_id, normalizedInvocationValue("positional", run.id)].includes(value) && !operation.startsWith("work_"))
129
140
  return run.id;
130
141
  const option = args[index - 1]?.replace(/^--/, "");
131
- if (!option || !pathOptions.has(option) || !value.startsWith("@"))
142
+ if (!option || !pathOptions.has(option))
143
+ return value;
144
+ value = normalizedPathAlias(value);
145
+ if (!value.startsWith("@"))
132
146
  return value;
133
147
  const match = /^@(project|workspace|run)(?:\/(.*))?$/.exec(value);
134
148
  if (!match)
@@ -139,6 +153,22 @@ export function expandLifecycleInvocationArgs(context, args, scope, operation) {
139
153
  return assertPathWithin(root, path.join(root, match[2] ?? ""), `${match[1]}_alias`);
140
154
  });
141
155
  }
156
+ /** Validate alias spelling before native-receipt lookup, so a correctable
157
+ * command typo is never reported as missing infrastructure evidence. */
158
+ export function validateLifecyclePathAliases(args, operation) {
159
+ const pathOptions = contextualPathOptionsForLifecycle(operation);
160
+ for (let index = 0; index < args.length; index += 1) {
161
+ const option = args[index - 1]?.replace(/^--/, "");
162
+ if (!option || !pathOptions.has(option))
163
+ continue;
164
+ const original = args[index];
165
+ const value = normalizedPathAlias(original);
166
+ if ((original.startsWith("\\") && original.replace(/^\\+/, "").startsWith("@") && value === original)
167
+ || (value.startsWith("@") && !/^@(project|workspace|run)(?:\/.*)?$/.test(value))) {
168
+ throw new AppError("usage", `Unknown path alias: ${original}`, 2, { phase: "prepare", effect: "no_effect", recoverable: true, parameter: option });
169
+ }
170
+ }
171
+ }
142
172
  function canonicalManagedCommand(context, command, scope) {
143
173
  const parsed = invocation(command);
144
174
  const argv = expandLifecycleInvocationArgs(context, parsed.argv, scope, parsed.operation);
@@ -988,7 +988,7 @@ function renderWorkerPrompt(context, work, run, dependencies) {
988
988
  codeContext.push("<document_updates>", JSON.stringify(packet.document_updates, null, 2), "Materialize every listed update. dd-flow verifies the resulting file against its PLAN-time baseline.", "</document_updates>", "", "<completion_contract>", "Successful completion requires empty deviations and blockers and every assigned document update in changed_paths. A necessary path outside planned_write_areas is normal coordination drift, not a blocker; include it in changed_paths and continue.", "</completion_contract>", "");
989
989
  if (packet)
990
990
  codeContext.push("<temporary_services>", "Prefer the declared check launcher: it already owns check resources. If the planned scenario genuinely requires an interactive HTTP service, use the managed supervisor below. This is a template: replace the project service command, port names and readiness path from the plan/project instructions; do not invent a fixed port.", `${command} runtime process start --run ${run.id} --project-root ${JSON.stringify(run.project_root)} --command '<project-service-command>' --ports api --ready-port api --ready-path /health --json --progress-jsonl`, "The service receives DD_FLOW_PORT_API (and equivalent variables for all declared names). The command stays running as its supervisor. Retain its tool handle; wait for the service ready event and read its service.json receipt. Pass those exact ports and the same project environment to reset/seed, API and browser operations.", "A ready receipt proves service readiness only. Record the scenario outcome and real evidence separately. After the scenario, execute the exact stop_command from that receipt, then wait for the supervisor to exit. Never use pkill/killall or stop a sibling's process. If cleanup fails, retain the process id and report the failure; do not claim the resource is free.", "</temporary_services>", "");
991
- return ["<work>", `- work_id: ${work.work_id}`, `- run_id: ${work.run_id}`, `- project_root: ${run.project_root}`, `- workspace_root: ${run.workspace_root}`, `- run_home: ${requireRunHome(run)}`, "</work>", "", "<cli_context>", `- Work commands use the short ID ${shortWorkId(work.work_id)}; dd-flow binds it to this RUN.`, `- @project = ${run.project_root}`, `- @workspace = ${run.workspace_root}`, `- @run = ${requireRunHome(run)}`, "- @ aliases are accepted only by declared dd-flow path parameters. Shell tools such as cat and rg require ordinary paths relative to cwd or absolute paths.", "- Lifecycle invocation IDs are internal runtime authority and are intentionally omitted from commands.", "</cli_context>", "", "<hard_write_boundary>", ...writeBoundary, "</hard_write_boundary>", "", ...codeContext, "<dependency_results>", JSON.stringify(dependencies.filter(Boolean), null, 2), "</dependency_results>", "", "<task>", resolveRunReferences(work.task, work.run_id, requireRunHome(run)), "</task>", "", ...(work.result_schema ? ["<result_contract>", `Return JSON matching \`${work.result_schema}\`.`, ...resultSchemaGuidance(work, run.id), "Do not create result.json yourself. Send the JSON to dd-flow on stdin; it atomically validates and stores the canonical receipt.", "</result_contract>", ""] : []), "<completion>", packet?.repair?.verification_check_refs?.length ? "Work finish runs its normal work-scoped checks plus the listed causal repair checks. Their original run_at remains an aggregate obligation; this is the additional proof required before accepting this repair." : "Work finish runs only declared run_at=work checks. Stage finish owns readiness/code/merge gates; successful Work completion does not mean those gates have passed.", "A failed receipt means only that the check failed; it is not proof of an engine, harness, dependency, or environment blocker.", ...completionRepair, "Use Fail only for a concrete external blocker after deterministic bootstrap or a contradiction with an accepted requirement/non-goal. Never fail merely because a necessary project path was absent from planned_write_areas.", "Finish may run for several minutes. Preserve the shell tool's process/session handle and poll that same invocation until it exits; progress arrives as JSONL on stderr. Never reissue Finish merely because final stdout has not arrived.", `Finish as one standalone command with a quoted heredoc (replace the example JSON with your result):\n${finishWorkCommand} <<'DD_FLOW_RESULT'\n{}\nDD_FLOW_RESULT`, `Fail only for an evidenced external or semantic-contract blocker: ${failWorkCommand}`, "</completion>", ""].join("\n");
991
+ return ["<work>", `- work_id: ${work.work_id}`, `- run_id: ${work.run_id}`, `- project_root: ${run.project_root}`, `- workspace_root: ${run.workspace_root}`, `- run_home: ${requireRunHome(run)}`, "</work>", "", "<cli_context>", `- Work commands use the short ID ${shortWorkId(work.work_id)}; dd-flow binds it to this RUN.`, `- @project = ${run.project_root}`, `- @workspace = ${run.workspace_root}`, `- @run = ${requireRunHome(run)}`, "- @ aliases are accepted only by declared dd-flow path parameters. Copy the quoted alias literally; do not add backslashes. Shell tools such as cat and rg require ordinary paths relative to cwd or absolute paths.", "- Lifecycle invocation IDs are internal runtime authority and are intentionally omitted from commands.", "</cli_context>", "", "<hard_write_boundary>", ...writeBoundary, "</hard_write_boundary>", "", ...codeContext, "<dependency_results>", JSON.stringify(dependencies.filter(Boolean), null, 2), "</dependency_results>", "", "<task>", resolveRunReferences(work.task, work.run_id, requireRunHome(run)), "</task>", "", ...(work.result_schema ? ["<result_contract>", `Return JSON matching \`${work.result_schema}\`.`, ...resultSchemaGuidance(work, run.id), "Do not create result.json yourself. Send the JSON to dd-flow on stdin; it atomically validates and stores the canonical receipt.", "</result_contract>", ""] : []), "<completion>", packet?.repair?.verification_check_refs?.length ? "Work finish runs its normal work-scoped checks plus the listed causal repair checks. Their original run_at remains an aggregate obligation; this is the additional proof required before accepting this repair." : "Work finish runs only declared run_at=work checks. Stage finish owns readiness/code/merge gates; successful Work completion does not mean those gates have passed.", "A failed receipt means only that the check failed; it is not proof of an engine, harness, dependency, or environment blocker.", ...completionRepair, "Use Fail only for a concrete external blocker after deterministic bootstrap or a contradiction with an accepted requirement/non-goal. Never fail merely because a necessary project path was absent from planned_write_areas.", "Finish may run for several minutes. Preserve the shell tool's process/session handle and poll that same invocation until it exits; progress arrives as JSONL on stderr. Never reissue Finish merely because final stdout has not arrived.", `Finish as one standalone command with a quoted heredoc (replace the example JSON with your result):\n${finishWorkCommand} <<'DD_FLOW_RESULT'\n{}\nDD_FLOW_RESULT`, `Fail only for an evidenced external or semantic-contract blocker: ${failWorkCommand}`, "</completion>", ""].join("\n");
992
992
  }
993
993
  export function resultSchemaGuidance(work, runId) {
994
994
  const schema = work.result_schema;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deksden-com/dd-flow-cli",
3
- "version": "0.9.0-beta.81",
3
+ "version": "0.9.0-beta.83",
4
4
  "description": "Mechanical runtime CLI for dd-flow workflows.",
5
5
  "type": "module",
6
6
  "bin": {