@deksden-com/dd-flow-cli 0.9.0-beta.159 → 0.9.0-beta.160

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,11 @@
1
1
  # @deksden-com/dd-flow-cli
2
2
 
3
+ ## 0.9.0-beta.160
4
+
5
+ ### Patch Changes
6
+
7
+ - Prepare pinned Worktrunk through a provider-free CLI and reuse its verified cache across isolated runs through the explicit resource home. Use owned installation locks, bounded native download retries and actual write progress across retries. Preserve acquisition context and refuse replay when child cleanup is unknown (plan 114).
8
+
3
9
  ## 0.9.0-beta.159
4
10
 
5
11
  ### Patch Changes
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "cli_package": "@deksden-com/dd-flow-cli",
3
- "cli_version": "0.9.0-beta.159",
4
- "cli_commit": "138ff716eb8e3f8e4b5c32a7556284c30e50804b",
5
- "built_at": "2026-10-29T12:37:35.400Z",
3
+ "cli_version": "0.9.0-beta.160",
4
+ "cli_commit": "3d065fa9b570fdb25570e4c47d7c99a5d50d5574",
5
+ "built_at": "2026-10-29T15:12:42.101Z",
6
6
  "built_with_canon": {
7
7
  "version": "4.1.4",
8
8
  "commit": "7cc415b3ef3ce37c9624c7cee51023e66f67cd57",
@@ -31,6 +31,7 @@ physicalPathOptions.add("native-config-home");
31
31
  physicalPathOptions.add("operational-decision-file");
32
32
  physicalPathOptions.add("workspace");
33
33
  export const commandInputs = {
34
+ "tool ensure wt": route(0),
34
35
  "delivery inputs": route(1, "project-root stage"),
35
36
  "delivery checks": route(1, "project-root stage result-file", lifecycle, "", "", { stage: ["prepare", "verify"] }),
36
37
  "delivery execute": route(1, "project-root stage result-file", lifecycle, "", "", { stage: ["publish", "deploy"] }),
@@ -265,7 +265,7 @@ export function optionsForCommandTree(command) {
265
265
  }
266
266
  /** One exact native routing classification shared by router and compatibility admission. */
267
267
  export function routerNativeRoute(key) {
268
- return key === "version" || key.startsWith("engine ") || key === "schema validate"
268
+ return key === "version" || key === "tool ensure wt" || key.startsWith("engine ") || key === "schema validate"
269
269
  || key === "flow list" || key === "flow show" || key === "flow mb-init" || key === "init" || key === "flow mb-upgrade" || key === "upgrade"
270
270
  || key === "flow mb-audit" || key === "flow project-review" || key === "flow mb-distill" || key === "flow mb-fix" || key === "flow review-fix" || key === "flow interactive" || key === "review"
271
271
  || key === "flow release" || key === "flow deploy" || key === "flow publish"
package/dist/cli/help.js CHANGED
@@ -2,6 +2,15 @@ import { AppError } from "../shared/errors.js";
2
2
  import { commandInputs, commandAliases } from "./command-inputs.js";
3
3
  import { commandKeyForPositions, globalOptionDocumentation, optionsForCommandTree, routerNativeRoute } from "./command-metadata.js";
4
4
  const helpPages = new Map([
5
+ ["tool", `dd-flow tool ensure wt - prepare the pinned Worktrunk capability
6
+
7
+ Usage:
8
+ dd-flow tool ensure wt --json
9
+
10
+ Verifies or installs the pinned tool in explicit DD_FLOW_RESOURCE_HOME (legacy
11
+ fallback: DD_FLOW_HOME). Uses no RUN database and starts no provider Session.
12
+ Returns version, platform, binary/archive checksums and cache_root. Availability
13
+ does not approve any worktree or productive action.`],
5
14
  ...["release", "deploy", "publish"].map((flow) => [`flow ${flow}`, `dd-flow flow ${flow} - start or replay one request-bound delivery RUN
6
15
 
7
16
  Usage:
@@ -1,4 +1,5 @@
1
1
  import fs from "node:fs";
2
+ import { ensureManagedWorktrunk } from "../services/tooling.js";
2
3
  import { prepareResponseDestination, writeResponseFile } from "./response-file.js";
3
4
  import { preparePromptDelivery, publishPromptDelivery } from "../services/prompt-delivery.js";
4
5
  import { requiredOption, optionalOption, optionalNumber, optionalPositiveNumber } from "./argument-values.js";
@@ -221,6 +222,18 @@ async function runCliRequest(args, io, env) {
221
222
  return runModelsCli(args, io);
222
223
  if (output.args[0] === "profiles")
223
224
  return runOperatorProfilesCli(args, env, io);
225
+ if (output.args[0] === "tool") {
226
+ prepareCommandRoute(output.args); // Reject unknown tools before filesystem/store effects.
227
+ if (output.responseFile)
228
+ prepareResponseDestination(output.responseFile);
229
+ const tool = await ensureManagedWorktrunk({ ddFlowHome: resolveDdFlowHome(env), env });
230
+ const payload = writeResponseFile({ ok: true, tool: "wt", ...tool, cache_root: path.dirname(tool.bin) }, output);
231
+ if (output.json)
232
+ writeJson(io.stdout, payload);
233
+ else
234
+ io.stdout.write(`${renderHumanResult(output.args, payload)}${tool.bin}\n`);
235
+ return 0;
236
+ }
224
237
  const suppliedInvocationId = optionalOption(parseArgs(output.args.slice(2)), "invocation-id");
225
238
  if (isNativeHookIngress(output.args)) {
226
239
  writeJson(io.stdout, await dispatchNativeHook(output.args, await readStdin(io.stdin), env));
@@ -4,8 +4,9 @@ import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { commandResult, commandText } from '../harness-runtime/lib/process-json.mjs';
6
6
  import { publishSetup, SETUP_STARTUP_MS } from '../harness-runtime/lib/setup-progress.mjs';
7
- import { AppError, processEvidence } from "../shared/errors.js";
7
+ import { AppError, errorRecord, processEvidence } from "../shared/errors.js";
8
8
  import { ensureDir } from "../storage/paths.js";
9
+ import { acquireInstallationLock } from "./installation-lock.js";
9
10
  const manifestRelativePath = path.join("tools", "worktrunk-manifest.json");
10
11
  const managedToolRootName = "wt";
11
12
  const manifestSchemaId = "dd-flow/managed-tool-manifest@1";
@@ -45,7 +46,7 @@ export function worktrunkPlatform() {
45
46
  });
46
47
  }
47
48
  export function worktrunkCacheRoot(context, platform = worktrunkPlatform()) {
48
- return path.join(context.ddFlowHome, "tools", managedToolRootName, worktrunkManifest().version, platform);
49
+ return path.join(path.resolve(context.env.DD_FLOW_RESOURCE_HOME || context.ddFlowHome), "tools", managedToolRootName, worktrunkManifest().version, platform);
49
50
  }
50
51
  export function worktrunkCacheBinary(context, platform = worktrunkPlatform()) {
51
52
  return path.join(worktrunkCacheRoot(context, platform), process.platform === "win32" ? "wt.exe" : "wt");
@@ -78,8 +79,34 @@ export async function ensureManagedWorktrunk(context) {
78
79
  const cachedRoot = worktrunkCacheRoot(context, platform);
79
80
  if (fs.existsSync(cachedRoot))
80
81
  return verifyCachedWorktrunk(context, manifest, platform);
81
- const lockDir = `${cachedRoot}.lock`;
82
- acquireLock(lockDir);
82
+ ensureDir(path.dirname(cachedRoot));
83
+ const lockFile = `${cachedRoot}.lock`;
84
+ const identity = crypto.createHash("sha256").update(JSON.stringify([manifest.tool, manifest.version, platform, artifactFor(manifest, platform).sha256])).digest("hex");
85
+ let lock;
86
+ try {
87
+ lock = acquireInstallationLock(lockFile, identity);
88
+ }
89
+ catch (cause) {
90
+ if (fs.existsSync(cachedRoot))
91
+ return verifyCachedWorktrunk(context, manifest, platform);
92
+ if (!(cause instanceof AppError) || cause.code !== "installation_locked")
93
+ throw cause;
94
+ throw new AppError("tool_install_pending", "Pinned Worktrunk installation requires its current owner", 1, { tool: manifest.tool, version: manifest.version, platform, cache_root: cachedRoot, lock_file: lockFile,
95
+ phase: "prepare", effect: "no_effect", recoverable: false, cause: errorRecord(cause) });
96
+ }
97
+ let primary;
98
+ const release = () => {
99
+ try {
100
+ lock.release();
101
+ }
102
+ catch (cause) {
103
+ if (!primary || typeof primary !== "object")
104
+ throw cause;
105
+ Object.assign(primary, { cleanup_error: errorRecord(cause) });
106
+ if (primary instanceof AppError)
107
+ Object.assign(primary.details, { recoverable: false, lock_cleanup_error: errorRecord(cause) });
108
+ }
109
+ };
83
110
  try {
84
111
  if (fs.existsSync(cachedRoot))
85
112
  return verifyCachedWorktrunk(context, manifest, platform);
@@ -90,44 +117,56 @@ export async function ensureManagedWorktrunk(context) {
90
117
  platform,
91
118
  cache_root: cachedRoot,
92
119
  artifact_url: artifactFor(manifest, platform).url,
120
+ phase: "prepare", effect: "no_effect", recoverable: true,
93
121
  next_action: "Place the verified official artifact in the managed tool cache and retry."
94
122
  });
95
123
  }
96
- await installWorktrunkArtifact(manifest, platform, cachedRoot);
124
+ await installWorktrunkArtifact(manifest, platform, cachedRoot, lock.assertOwnership);
97
125
  return verifyCachedWorktrunk(context, manifest, platform);
98
126
  }
127
+ catch (cause) {
128
+ primary = cause;
129
+ throw cause;
130
+ }
99
131
  finally {
100
- fs.rmSync(lockDir, { recursive: true, force: true });
132
+ release();
101
133
  }
102
134
  }
103
- async function installWorktrunkArtifact(manifest, platform, targetRoot) {
135
+ async function installWorktrunkArtifact(manifest, platform, targetRoot, assertOwnership) {
104
136
  const artifact = artifactFor(manifest, platform);
105
137
  const parent = path.dirname(targetRoot);
106
138
  ensureDir(parent);
107
139
  const tempRoot = fs.mkdtempSync(path.join(parent, `.wt-${process.pid}-`));
108
140
  const archivePath = path.join(tempRoot, `worktrunk.${artifact.archive}`);
109
141
  const extractedRoot = path.join(tempRoot, "extracted");
142
+ let acquisitionPhase = "tool-download", cleanupSafe = true;
110
143
  try {
111
- let observedBytes = 0;
144
+ let marker = null, progress = 0;
112
145
  publishSetup('probe_started', 'worktrunk-download');
113
- const download = await commandResult("curl", ["--fail", "--location", "--silent", "--show-error", "-o", archivePath, artifact.url], { startupMs: SETUP_STARTUP_MS, progress: () => {
114
- const bytes = fs.existsSync(archivePath) ? fs.statSync(archivePath).size : 0;
115
- if (bytes > observedBytes) {
116
- observedBytes = bytes;
117
- publishSetup('transfer_progress', `worktrunk-transfer:${bytes}`);
146
+ const download = await commandResult("curl", ["--fail", "--location", "--silent", "--show-error", "--retry", "2", "--retry-delay", "1", "--retry-connrefused", "--connect-timeout", "30", "-o", archivePath, artifact.url], { startupMs: SETUP_STARTUP_MS, phase: acquisitionPhase, progress: () => {
147
+ const stat = fs.statSync(archivePath, { bigint: true, throwIfNoEntry: false });
148
+ const next = stat ? `${stat.size}:${stat.mtimeNs}` : null;
149
+ if (next !== marker && stat && stat.size > 0n) {
150
+ progress++;
151
+ publishSetup('transfer_progress', `worktrunk-transfer:${progress}`);
118
152
  }
119
- return observedBytes;
153
+ marker = next;
154
+ return progress;
120
155
  } });
121
- if (download.exit_code !== 0) {
156
+ if (download.exit_code !== 0 || download.signal) {
122
157
  throw new AppError("tool_unavailable", "Unable to download the pinned Worktrunk artifact", 1, {
123
158
  tool: manifest.tool,
124
159
  version: manifest.version,
125
160
  platform,
126
161
  artifact_url: artifact.url,
162
+ cache_root: targetRoot, acquisition_phase: acquisitionPhase,
163
+ phase: "prepare", effect: "no_effect", recoverable: true,
164
+ process: processEvidence(download),
127
165
  stdout: sanitize(download.stdout),
128
166
  stderr: sanitize(download.stderr)
129
167
  });
130
168
  }
169
+ acquisitionPhase = "tool-archive-verification";
131
170
  publishSetup('probe_completed', 'worktrunk-download');
132
171
  const archiveChecksum = sha256File(archivePath);
133
172
  if (archiveChecksum !== artifact.sha256) {
@@ -140,11 +179,13 @@ async function installWorktrunkArtifact(manifest, platform, targetRoot) {
140
179
  });
141
180
  }
142
181
  ensureDir(extractedRoot);
182
+ acquisitionPhase = "tool-extraction";
143
183
  await extractArchive(archivePath, extractedRoot, artifact.archive);
144
184
  const extractedBinary = findBinary(extractedRoot);
145
185
  if (!extractedBinary)
146
186
  throw new AppError("tool_integrity_failed", "Pinned Worktrunk archive does not contain wt", 1, { platform });
147
187
  fs.chmodSync(extractedBinary, 0o755);
188
+ acquisitionPhase = "tool-version";
148
189
  await verifyWorktrunkVersion(extractedBinary, manifest.version);
149
190
  const binaryChecksum = sha256File(extractedBinary);
150
191
  const stagedRoot = path.join(tempRoot, "managed");
@@ -160,12 +201,36 @@ async function installWorktrunkArtifact(manifest, platform, targetRoot) {
160
201
  archive_sha256: artifact.sha256,
161
202
  binary_sha256: binaryChecksum
162
203
  }, null, 2)}\n`);
204
+ assertOwnership();
163
205
  if (fs.existsSync(targetRoot))
164
206
  throw new AppError("tool_integrity_failed", "Managed Worktrunk cache appeared during installation", 1, { cache_root: targetRoot });
165
207
  fs.renameSync(stagedRoot, targetRoot);
166
208
  }
209
+ catch (cause) {
210
+ const cleanupError = cause?.cleanup_error;
211
+ cleanupSafe = !cleanupError && (cause instanceof AppError || cause?.code === "command_observation_lost");
212
+ if (cause?.code === "command_observation_lost") {
213
+ throw new AppError("tool_unavailable", "Pinned Worktrunk acquisition lost command observation", 1, { tool: manifest.tool, version: manifest.version, platform, artifact_url: artifact.url, cache_root: targetRoot,
214
+ acquisition_phase: acquisitionPhase, phase: "prepare", effect: cleanupError ? "unknown" : "no_effect",
215
+ recoverable: !cleanupError, cause: errorRecord(cause), ...(cleanupError ? { cleanup_error: errorRecord(cleanupError), staging_root: tempRoot } : {}) });
216
+ }
217
+ if (cause instanceof AppError) {
218
+ throw new AppError(cause.code, cause.message, cause.exitCode, {
219
+ tool: manifest.tool, version: manifest.version, platform, artifact_url: artifact.url, cache_root: targetRoot,
220
+ acquisition_phase: acquisitionPhase, phase: "prepare", effect: "no_effect", recoverable: false,
221
+ ...cause.details,
222
+ ...(cleanupError ? { effect: "unknown", recoverable: false, cleanup_error: errorRecord(cleanupError), staging_root: tempRoot } : {})
223
+ });
224
+ }
225
+ throw new AppError("tool_unavailable", "Pinned Worktrunk acquisition did not complete", 1, {
226
+ tool: manifest.tool, version: manifest.version, platform, artifact_url: artifact.url, cache_root: targetRoot,
227
+ acquisition_phase: acquisitionPhase, phase: "prepare", effect: "unknown", recoverable: false,
228
+ staging_root: tempRoot, cause: errorRecord(cause), ...(cleanupError ? { cleanup_error: errorRecord(cleanupError) } : {})
229
+ });
230
+ }
167
231
  finally {
168
- fs.rmSync(tempRoot, { recursive: true, force: true });
232
+ if (cleanupSafe)
233
+ fs.rmSync(tempRoot, { recursive: true, force: true });
169
234
  }
170
235
  }
171
236
  function verifyCachedWorktrunk(context, manifest, platform) {
@@ -208,22 +273,22 @@ function verifyCachedWorktrunk(context, manifest, platform) {
208
273
  }
209
274
  export async function extractArchive(archive, destination, format) {
210
275
  if (format === "tar.xz") {
211
- const listing = await commandResult("tar", ["-tJf", archive], { startupMs: SETUP_STARTUP_MS });
276
+ const listing = await commandResult("tar", ["-tJf", archive], { startupMs: SETUP_STARTUP_MS, phase: "tool-archive-list" });
212
277
  if (listing.exit_code !== 0 || listing.signal)
213
278
  throw new AppError("tool_integrity_failed", "Pinned Worktrunk archive listing failed", 1, { process: processEvidence(listing) });
214
279
  if (listing.stdout.split("\n").some(unsafeArchivePath))
215
280
  throw new AppError("tool_integrity_failed", "Pinned Worktrunk archive has an unsafe file list", 1, { phase: "archive_path_validation" });
216
- const extracted = await commandResult("tar", ["-xJvf", archive, "-C", destination], { startupMs: SETUP_STARTUP_MS });
281
+ const extracted = await commandResult("tar", ["-xJvf", archive, "-C", destination], { startupMs: SETUP_STARTUP_MS, phase: "tool-extraction" });
217
282
  if (extracted.exit_code !== 0 || extracted.signal)
218
283
  throw new AppError("tool_integrity_failed", "Pinned Worktrunk archive extraction failed", 1, { process: processEvidence(extracted) });
219
284
  return;
220
285
  }
221
- const listing = await commandResult("unzip", ["-Z1", archive], { startupMs: SETUP_STARTUP_MS });
286
+ const listing = await commandResult("unzip", ["-Z1", archive], { startupMs: SETUP_STARTUP_MS, phase: "tool-archive-list" });
222
287
  if (listing.exit_code !== 0 || listing.signal)
223
288
  throw new AppError("tool_integrity_failed", "Pinned Worktrunk archive listing failed", 1, { process: processEvidence(listing) });
224
289
  if (listing.stdout.split("\n").some(unsafeArchivePath))
225
290
  throw new AppError("tool_integrity_failed", "Pinned Worktrunk archive has an unsafe file list", 1, { phase: "archive_path_validation" });
226
- const extracted = await commandResult("unzip", [archive, "-d", destination], { startupMs: SETUP_STARTUP_MS });
291
+ const extracted = await commandResult("unzip", [archive, "-d", destination], { startupMs: SETUP_STARTUP_MS, phase: "tool-extraction" });
227
292
  if (extracted.exit_code !== 0 || extracted.signal)
228
293
  throw new AppError("tool_integrity_failed", "Pinned Worktrunk archive extraction failed", 1, { process: processEvidence(extracted) });
229
294
  }
@@ -243,7 +308,7 @@ function findBinary(root) {
243
308
  return null;
244
309
  }
245
310
  async function verifyWorktrunkVersion(bin, expected) {
246
- const output = await commandText(bin, ['--version'], { startupMs: SETUP_STARTUP_MS, phase: 'native-version' });
311
+ const output = await commandText(bin, ['--version'], { startupMs: SETUP_STARTUP_MS, phase: 'tool-version' });
247
312
  if (!output.includes(expected)) {
248
313
  throw new AppError("tool_integrity_failed", "Managed Worktrunk version verification failed", 1, {
249
314
  expected_version: expected,
@@ -262,15 +327,6 @@ function validateManifest(value, file) {
262
327
  throw new AppError("tool_manifest_invalid", `Managed Worktrunk manifest is invalid: ${file}`, 1, { manifest: file });
263
328
  }
264
329
  }
265
- function acquireLock(lockDir) {
266
- ensureDir(path.dirname(lockDir));
267
- try {
268
- fs.mkdirSync(lockDir);
269
- }
270
- catch {
271
- throw new AppError("tool_install_locked", "Another Worktrunk installation is in progress", 1, { lock_dir: lockDir });
272
- }
273
- }
274
330
  function sha256File(file) {
275
331
  return crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex");
276
332
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deksden-com/dd-flow-cli",
3
- "version": "0.9.0-beta.159",
3
+ "version": "0.9.0-beta.160",
4
4
  "description": "Mechanical runtime CLI for dd-flow workflows.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.23.0",