@deksden-com/dd-flow-cli 0.9.0-beta.9 → 0.9.0-beta.91

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.
Files changed (144) hide show
  1. package/CHANGELOG.md +467 -0
  2. package/README.md +65 -0
  3. package/dist/build-info.json +8 -8
  4. package/dist/cli/command-inputs.js +305 -0
  5. package/dist/cli/help.js +89 -13
  6. package/dist/cli/hook-ingress.js +82 -0
  7. package/dist/cli/input-preparation.js +116 -0
  8. package/dist/cli/run-cli.js +1421 -384
  9. package/dist/cli.js +6 -2
  10. package/dist/harness-runtime/bin/dd-agy.mjs +37 -0
  11. package/dist/harness-runtime/bin/dd-codex.mjs +25 -0
  12. package/dist/harness-runtime/bin/dd-droid.mjs +33 -0
  13. package/dist/harness-runtime/bin/dd-grok.mjs +30 -0
  14. package/dist/harness-runtime/bin/dd-opencode.mjs +21 -0
  15. package/dist/harness-runtime/bin/dd-zcode.mjs +83 -0
  16. package/dist/harness-runtime/lib/daemon-operations.mjs +215 -0
  17. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +423 -0
  18. package/dist/harness-runtime/lib/dd-agy.mjs +59 -0
  19. package/dist/harness-runtime/lib/dd-codex-daemon.d.mts +1 -0
  20. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +146 -0
  21. package/dist/harness-runtime/lib/dd-codex.mjs +562 -0
  22. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +125 -0
  23. package/dist/harness-runtime/lib/dd-droid.mjs +471 -0
  24. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +274 -0
  25. package/dist/harness-runtime/lib/dd-grok.mjs +174 -0
  26. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +198 -0
  27. package/dist/harness-runtime/lib/dd-opencode.mjs +98 -0
  28. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +644 -0
  29. package/dist/harness-runtime/lib/dd-zcode.mjs +910 -0
  30. package/dist/harness-runtime/lib/delegation-instructions.d.mts +10 -0
  31. package/dist/harness-runtime/lib/delegation-instructions.mjs +130 -0
  32. package/dist/harness-runtime/lib/dispatch-fence.mjs +14 -0
  33. package/dist/harness-runtime/lib/driver-recovery.mjs +129 -0
  34. package/dist/harness-runtime/lib/droid-observation.mjs +66 -0
  35. package/dist/harness-runtime/lib/managed-daemon.mjs +260 -0
  36. package/dist/harness-runtime/lib/model-observations.mjs +77 -0
  37. package/dist/harness-runtime/lib/native-hook-command.d.mts +1 -0
  38. package/dist/harness-runtime/lib/native-hook-command.mjs +34 -0
  39. package/dist/harness-runtime/lib/observation-clock.mjs +36 -0
  40. package/dist/harness-runtime/lib/operation-context.mjs +5 -0
  41. package/dist/harness-runtime/lib/operation-errors.mjs +19 -0
  42. package/dist/harness-runtime/lib/process-json.mjs +70 -0
  43. package/dist/harness-runtime/lib/process-snapshot.mjs +10 -0
  44. package/dist/harness-runtime/lib/runner-events.mjs +200 -0
  45. package/dist/harness-runtime/lib/runner-lock.mjs +42 -0
  46. package/dist/harness-runtime/lib/session-settlement.mjs +30 -0
  47. package/dist/harness-runtime/lib/tool-observations.d.mts +17 -0
  48. package/dist/harness-runtime/lib/tool-observations.mjs +153 -0
  49. package/dist/schemas/agent-profile.schema.json +1 -1
  50. package/dist/schemas/code-review-result.schema.json +2 -2
  51. package/dist/schemas/code-work-batch.schema.json +4 -3
  52. package/dist/schemas/code-work-result.schema.json +4 -4
  53. package/dist/schemas/plan-review-result.schema.json +2 -2
  54. package/dist/schemas/run-control-receipt.schema.json +99 -0
  55. package/dist/schemas/run-control-request.schema.json +36 -0
  56. package/dist/schemas/vnext-protocol-plan.schema.json +2 -2
  57. package/dist/services/canon.js +11 -8
  58. package/dist/services/cleanup.js +57 -13
  59. package/dist/services/cli-operation-classifier.js +22 -24
  60. package/dist/services/code-checks.js +543 -96
  61. package/dist/services/codex-hook-delivery.js +28 -0
  62. package/dist/services/command-context.js +80 -0
  63. package/dist/services/config.js +11 -8
  64. package/dist/services/continuation-outcome.js +20 -0
  65. package/dist/services/controller-fanout.js +132 -0
  66. package/dist/services/dashboard.js +103 -31
  67. package/dist/services/engines.js +121 -76
  68. package/dist/services/eval-snapshots.js +750 -73
  69. package/dist/services/execution-policy.js +170 -0
  70. package/dist/services/external-work-launch.js +110 -0
  71. package/dist/services/harness-adapter.js +147 -24
  72. package/dist/services/harness-config.js +2 -0
  73. package/dist/services/hooks.js +587 -299
  74. package/dist/services/lanes.js +61 -53
  75. package/dist/services/lifecycle-command.js +103 -17
  76. package/dist/services/lifecycle-contract.js +30 -0
  77. package/dist/services/lifecycle-invocations.js +1122 -0
  78. package/dist/services/managed-daemon-binding.js +38 -0
  79. package/dist/services/managed-processes.js +283 -27
  80. package/dist/services/merge-queue.js +179 -104
  81. package/dist/services/merge-server.js +73 -28
  82. package/dist/services/migrations.js +14 -9
  83. package/dist/services/native-daemon-history.js +49 -0
  84. package/dist/services/native-session-control.js +39 -0
  85. package/dist/services/plan-runtime.js +19 -8
  86. package/dist/services/plans.js +28 -25
  87. package/dist/services/projects.js +18 -5
  88. package/dist/services/prompts.js +23 -16
  89. package/dist/services/protocols.js +64 -20
  90. package/dist/services/recovery-observation-budget.js +61 -0
  91. package/dist/services/recovery-snapshot-database.js +107 -0
  92. package/dist/services/repair-intents.js +40 -0
  93. package/dist/services/run-control-receipt.js +56 -0
  94. package/dist/services/run-control-worker.js +363 -0
  95. package/dist/services/run-control.js +876 -0
  96. package/dist/services/run-controller-adapter.js +222 -0
  97. package/dist/services/run-controller-capture.js +134 -0
  98. package/dist/services/run-controller-process.js +189 -0
  99. package/dist/services/run-controller-recovery.js +222 -0
  100. package/dist/services/run-controller-state.js +36 -0
  101. package/dist/services/run-controller.js +793 -0
  102. package/dist/services/run-engine-bindings.js +1 -1
  103. package/dist/services/run-fork.js +138 -0
  104. package/dist/services/run-observations.js +112 -0
  105. package/dist/services/run-recovery-runtime.js +69 -0
  106. package/dist/services/run-recovery.js +343 -0
  107. package/dist/services/runs.js +368 -74
  108. package/dist/services/runtime-budget.js +434 -0
  109. package/dist/services/runtime-command.js +43 -0
  110. package/dist/services/runtime-scope-capture.js +50 -0
  111. package/dist/services/runtime-scope-control.js +450 -0
  112. package/dist/services/runtime-scope-resume.js +565 -0
  113. package/dist/services/runtime-scope-stop.js +99 -0
  114. package/dist/services/runtime-scope-worker.js +243 -0
  115. package/dist/services/runtime-service.js +97 -0
  116. package/dist/services/schema-validation.js +10 -8
  117. package/dist/services/sessions.js +35 -69
  118. package/dist/services/stage-blocker.js +17 -7
  119. package/dist/services/stage-context.js +44 -18
  120. package/dist/services/stage-lifecycle.js +99 -102
  121. package/dist/services/stage-pause.js +84 -55
  122. package/dist/services/stage-work-graph.js +35 -0
  123. package/dist/services/usage.js +37 -93
  124. package/dist/services/vnext-code-review.js +195 -88
  125. package/dist/services/vnext-code.js +285 -252
  126. package/dist/services/vnext-execution-profile.js +5 -3
  127. package/dist/services/vnext-fanout.js +160 -17
  128. package/dist/services/vnext-merge.js +291 -104
  129. package/dist/services/vnext-plan-review.js +139 -81
  130. package/dist/services/vnext-plan.js +138 -69
  131. package/dist/services/vnext-protocolize.js +129 -56
  132. package/dist/services/vnext-specify.js +88 -65
  133. package/dist/services/work-registry.js +716 -168
  134. package/dist/services/workspace-bootstrap.js +76 -0
  135. package/dist/services/worktrees.js +85 -35
  136. package/dist/shared/entity-references.js +8 -0
  137. package/dist/shared/errors.js +12 -0
  138. package/dist/storage/database.js +459 -32
  139. package/dist/storage/paths.js +17 -4
  140. package/dist/storage/work-references.js +23 -0
  141. package/dist/storage/writer-contract.js +77 -0
  142. package/dist/storage/writer-migration.js +102 -0
  143. package/package.json +24 -13
  144. package/tools/repair-paused-run-status.mjs +59 -0
@@ -12,6 +12,7 @@ import { requireProtocol } from "./protocols.js";
12
12
  import { resolveCanonRootBeforeContext } from "./canon.js";
13
13
  import { appendAudit } from "./audit.js";
14
14
  import { findProjectByRoot } from "./projects.js";
15
+ import { parseCommandArgs } from "./lifecycle-command.js";
15
16
  import { allRunEngineBindings, locateRunRoot, readRunEngineBinding, runEngineBindingSchemaId, writeRunEngineBinding } from "./run-engine-bindings.js";
16
17
  export const engineManifestSchemaId = "dd-flow/engine-manifest@1";
17
18
  const routerNativeFamilies = new Set(["engine", "version", "schema"]);
@@ -117,7 +118,7 @@ export function installCurrentEngine(context, input = {}) {
117
118
  }
118
119
  export function listEngines(context) {
119
120
  const root = engineStoreRoot(context.ddFlowHome);
120
- const engines = readInstalledManifests(root).map(manifestSummary);
121
+ const engines = readInstalledManifests(root).map(manifest => manifestSummary(manifest));
121
122
  return { ok: true, schema_id: "dd-flow/engine-list@1", engine_store: root, engines };
122
123
  }
123
124
  export function engineInfo(context, input) {
@@ -138,11 +139,10 @@ export function resolveEngine(context, input = {}) {
138
139
  export function doctorEngines(context, input = {}) {
139
140
  const root = engineStoreRoot(context.ddFlowHome);
140
141
  const installed = readInstalledManifests(root);
141
- const checks = installed.map((manifest) => ({
142
- engine: manifestSummary(manifest),
143
- status: manifestHealthy(manifest) ? "ok" : "corrupt",
144
- diagnostics: manifestDiagnostics(manifest)
145
- }));
142
+ const checks = installed.map(manifest => {
143
+ const diagnostics = manifestDiagnostics(manifest);
144
+ return { engine: manifestSummary(manifest, diagnostics), status: diagnostics.length ? "corrupt" : "ok", diagnostics };
145
+ });
146
146
  const classification = input.classification ?? classifyCliOperation(["engine", "doctor"], input.env ?? context.env);
147
147
  const selection = selectEngine(context, input, {}, classification);
148
148
  return {
@@ -174,13 +174,16 @@ export function routeArgsThroughEngine(context, args, io, stdin, env, resolvedPr
174
174
  if (!selection.selected)
175
175
  return Promise.resolve(null);
176
176
  const current = getCliBuildInfo();
177
- if (selection.selected.package_name === current.package_name && selection.selected.package_version === current.version) {
177
+ const currentRoot = findPackageRoot(path.dirname(sourceFile));
178
+ if (selection.selected.package_name === current.package_name && selection.selected.package_version === current.version
179
+ && currentRoot !== null && fs.realpathSync(selection.selected.snapshot_root) === fs.realpathSync(currentRoot)) {
178
180
  return Promise.resolve({ routed: true, exitCode: 0, inProcess: true });
179
181
  }
180
182
  const entrypoint = resolveManifestEntrypoint(selection.selected);
181
183
  return spawnEngine(selection.selected, entrypoint, args, io, stdin, env, current.version);
182
184
  }
183
- export function bindRunEngine(context, input) {
185
+ export function prepareRunEngineBind(context, input) {
186
+ const reason = requiredText(input.reason, "reason");
184
187
  const projectRoot = resolveProjectRoot(input.projectRoot);
185
188
  const located = locateRunRoot(context.db, projectRoot, input.runId);
186
189
  if (!located)
@@ -190,19 +193,31 @@ export function bindRunEngine(context, input) {
190
193
  packageName: input.packageName ?? getCliBuildInfo().package_name,
191
194
  packageVersion: input.engineVersion,
192
195
  ...(input.integrityChecksum ? { integrityChecksum: input.integrityChecksum } : {})
193
- });
196
+ }, { checkEntrypoint: false });
194
197
  const requested = engineIdentity(manifest);
195
198
  if (existing) {
196
199
  if (sameEngineIdentity(existing.engine, requested))
197
- return { ok: true, action: "run_engine_bind", changed: false, binding: existing };
200
+ return { kind: "replay", binding: existing };
198
201
  throw new AppError("run_engine_binding_immutable", "RUN engine binding cannot be changed", 1, {
199
202
  run_id: located.run_id,
200
203
  current: existing.engine,
201
204
  requested
202
205
  });
203
206
  }
207
+ return { kind: "create", projectRoot, located, manifest, requested, reason, entrypoint: resolveManifestEntrypoint(manifest) };
208
+ }
209
+ export function bindRunEngine(context, input, prepared = prepareRunEngineBind(context, input)) {
210
+ if (prepared.kind === "replay")
211
+ return { ok: true, action: "run_engine_bind", changed: false, binding: prepared.binding };
212
+ const { projectRoot, located, manifest, requested, reason, entrypoint } = prepared;
213
+ const existing = readRunEngineBinding(located.binding_path, { allowMissing: true });
214
+ if (existing) {
215
+ if (sameEngineIdentity(existing.engine, requested))
216
+ return { ok: true, action: "run_engine_bind", changed: false, binding: existing };
217
+ throw new AppError("run_engine_binding_immutable", "RUN engine binding cannot be changed", 1, { run_id: located.run_id, current: existing.engine, requested });
218
+ }
204
219
  const probeArgs = ["run", "status", located.run_id, "--project-root", projectRoot, "--json"];
205
- const probe = spawnSync(process.execPath, [resolveManifestEntrypoint(manifest), ...probeArgs], {
220
+ const probe = spawnSync(process.execPath, [entrypoint, ...probeArgs], {
206
221
  cwd: manifest.snapshot_root,
207
222
  env: { ...process.env, ...context.env, DD_FLOW_HOME: context.ddFlowHome, DD_FLOW_ENGINE_MODE: "1", DD_FLOW_ENGINE_HOME: manifest.snapshot_root },
208
223
  encoding: "utf8",
@@ -223,7 +238,7 @@ export function bindRunEngine(context, input) {
223
238
  project_root: projectRoot,
224
239
  bound_at: context.now(),
225
240
  source: "legacy_recovery",
226
- reason: requiredText(input.reason, "reason"),
241
+ reason,
227
242
  engine: requested,
228
243
  probe: { status: "passed", command: `dd-flow ${probeArgs.join(" ")}` }
229
244
  };
@@ -252,6 +267,38 @@ export function bindCurrentEngineToRun(context, input) {
252
267
  };
253
268
  return writeRunEngineBinding(path.join(input.runRoot, "engine-binding.json"), binding).binding;
254
269
  }
270
+ /** Forks are a new execution authority. The source binding stays immutable;
271
+ * this replaces only the copied binding after proving the selected engine can
272
+ * read the copied RUN. */
273
+ export function upgradeForkedRunEngine(context, input) {
274
+ const projectRoot = resolveProjectRoot(input.projectRoot);
275
+ const located = locateRunRoot(context.db, projectRoot, input.runId);
276
+ if (!located)
277
+ throw new AppError("not_found", `Forked RUN is not registered: ${input.runId}`, 1);
278
+ const manifest = requireBoundManifest(context.ddFlowHome, {
279
+ packageName: getCliBuildInfo().package_name,
280
+ packageVersion: input.engineVersion,
281
+ ...(input.integrityChecksum ? { integrityChecksum: input.integrityChecksum } : {})
282
+ });
283
+ const requested = engineIdentity(manifest);
284
+ readRunEngineBinding(located.binding_path);
285
+ const lineage = path.join(located.run_root, "fork-lineage.json");
286
+ if (!fs.existsSync(lineage))
287
+ throw new AppError("fork_lineage_missing", "Engine upgrade is permitted only for a derived RUN", 1, { run_id: located.run_id });
288
+ const probeArgs = ["run", "status", located.run_id, "--project-root", projectRoot, "--json"];
289
+ const probe = spawnSync(process.execPath, [resolveManifestEntrypoint(manifest), ...probeArgs], {
290
+ cwd: manifest.snapshot_root,
291
+ env: { ...process.env, ...context.env, DD_FLOW_HOME: context.ddFlowHome, DD_FLOW_ENGINE_MODE: "1", DD_FLOW_ENGINE_HOME: manifest.snapshot_root },
292
+ encoding: "utf8", timeout: 30_000
293
+ });
294
+ if (probe.status !== 0)
295
+ throw new AppError("fork_engine_probe_failed", "Selected engine cannot read the forked RUN", 1, { run_id: located.run_id, engine: manifestSummary(manifest), stderr: probe.stderr.slice(0, 2000) });
296
+ const binding = { schema_id: runEngineBindingSchemaId, run_id: located.run_id, project_root: projectRoot, bound_at: context.now(), source: "fork_upgrade", reason: requiredText(input.reason, "reason"), engine: requested, probe: { status: "passed", command: `dd-flow ${probeArgs.join(" ")}` } };
297
+ const temporary = `${located.binding_path}.fork-${process.pid}-${Date.now()}.tmp`;
298
+ fs.writeFileSync(temporary, `${JSON.stringify(binding, null, 2)}\n`);
299
+ fs.renameSync(temporary, located.binding_path);
300
+ return binding;
301
+ }
255
302
  function boundRunEngineForArgs(context, args, projectRoot) {
256
303
  const runId = runIdForArgs(args);
257
304
  if (!runId || !projectRoot)
@@ -274,6 +321,8 @@ function runIdForArgs(args) {
274
321
  const [family, command, ...rest] = args;
275
322
  const parsed = parseLightArgs(rest);
276
323
  if (family === "run" && command !== "start" && command !== "list") {
324
+ if (["drive", "control", "recovery", "capacity"].includes(command ?? ""))
325
+ return option(parsed, "run");
277
326
  if (command === "flags")
278
327
  return positional(parsed, 1);
279
328
  return positional(parsed, 0);
@@ -290,7 +339,7 @@ function runIdForArgs(args) {
290
339
  return option(parsed, "run");
291
340
  return null;
292
341
  }
293
- function requireBoundManifest(ddFlowHome, input) {
342
+ function requireBoundManifest(ddFlowHome, input, options = {}) {
294
343
  const manifest = input.snapshotRoot
295
344
  ? requireManifest(path.join(input.snapshotRoot, "engine.json"))
296
345
  : findInstalledManifest(ddFlowHome, {
@@ -303,7 +352,7 @@ function requireBoundManifest(ddFlowHome, input) {
303
352
  || manifest.package_version !== input.packageVersion
304
353
  || input.integrityChecksum !== undefined && manifest.integrity.checksum !== input.integrityChecksum
305
354
  || input.snapshotRoot !== undefined && path.resolve(manifest.snapshot_root) !== path.resolve(input.snapshotRoot)
306
- || !manifestHealthy(manifest)) {
355
+ || manifestDiagnostics(manifest, options).length !== 0) {
307
356
  throw new AppError("run_engine_binding_invalid", "RUN engine binding points to a missing or unhealthy engine snapshot", 1, {
308
357
  package_name: input.packageName,
309
358
  package_version: input.packageVersion,
@@ -344,7 +393,7 @@ function executionManifest(context) {
344
393
  return requireManifest(path.join(String(installed.path), "engine.json"));
345
394
  }
346
395
  function requiredText(value, label) {
347
- const normalized = value.trim();
396
+ const normalized = typeof value === "string" ? value.trim() : "";
348
397
  if (!normalized)
349
398
  throw new AppError("usage", `Missing required value: ${label}`, 2);
350
399
  return normalized;
@@ -383,11 +432,7 @@ export function selectEngine(context, input, options = {}, classification = inpu
383
432
  const manifests = readInstalledManifests(engineStoreRoot(context.ddFlowHome))
384
433
  .filter((manifest) => manifest.package_name === packageName)
385
434
  .filter((manifest) => requiredRange !== null && satisfiesRange(manifest.package_version, requiredRange))
386
- .filter((manifest) => options.allowCurrentInProcess
387
- && manifest.package_name === build.package_name
388
- && manifest.package_version === build.version
389
- ? true
390
- : manifestHealthy(manifest))
435
+ .filter((manifest) => manifestHealthy(manifest))
391
436
  .sort((a, b) => {
392
437
  const versionOrder = compareSemverStrings(b.package_version, a.package_version);
393
438
  if (versionOrder !== 0)
@@ -560,35 +605,11 @@ function canonCompatibilitySha(memorybankRoot) {
560
605
  return null;
561
606
  }
562
607
  }
563
- function parseLightArgs(args) {
564
- const positional = [];
565
- const options = new Map();
566
- for (let index = 0; index < args.length; index += 1) {
567
- const value = args[index];
568
- if (value?.startsWith("--")) {
569
- const equal = value.indexOf("=");
570
- if (equal > 2) {
571
- const key = value.slice(2, equal);
572
- options.set(key, [...(options.get(key) ?? []), value.slice(equal + 1)]);
573
- continue;
574
- }
575
- const key = value.slice(2);
576
- const next = args[index + 1];
577
- if (!next || next.startsWith("--"))
578
- options.set(key, [...(options.get(key) ?? []), ""]);
579
- else {
580
- options.set(key, [...(options.get(key) ?? []), next]);
581
- index += 1;
582
- }
583
- }
584
- else if (value)
585
- positional.push(value);
586
- }
587
- return { positional, options };
588
- }
608
+ const parseLightArgs = parseCommandArgs;
589
609
  function option(parsed, key) {
590
610
  const values = parsed.options.get(key);
591
- return values?.[values.length - 1] || null;
611
+ const value = values?.[values.length - 1];
612
+ return value && value !== "__dd_flow_flag__" ? value : null;
592
613
  }
593
614
  function positional(parsed, index) {
594
615
  return parsed.positional[index] ?? null;
@@ -620,23 +641,37 @@ function buildManifest(context, packageName, version, packageRoot, snapshotRoot,
620
641
  };
621
642
  }
622
643
  function copyPackageSnapshot(packageRoot, target) {
644
+ for (const [relative, source] of packageSnapshotFiles(packageRoot)) {
645
+ const destination = path.join(target, relative);
646
+ ensureDir(path.dirname(destination));
647
+ fs.copyFileSync(source, destination);
648
+ }
649
+ }
650
+ /** The same source inventory drives validation and copying; no staging writes. */
651
+ function packageSnapshotFiles(packageRoot) {
652
+ const files = new Map();
653
+ const collect = (source, relative, skipNodeModules = false) => {
654
+ if (!fs.statSync(source).isDirectory()) {
655
+ files.set(relative, source);
656
+ return;
657
+ }
658
+ for (const entry of fs.readdirSync(source, { withFileTypes: true })) {
659
+ if (skipNodeModules && entry.name === "node_modules")
660
+ continue;
661
+ const child = path.join(source, entry.name), name = path.join(relative, entry.name);
662
+ if (entry.isDirectory())
663
+ collect(child, name, skipNodeModules);
664
+ else if (entry.isFile())
665
+ files.set(name, child);
666
+ }
667
+ };
623
668
  for (const item of ["dist", "package.json", "README.md", "CHANGELOG.md", "tools"]) {
624
669
  const from = path.join(packageRoot, item);
625
670
  if (!fs.existsSync(from))
626
671
  continue;
627
- const to = path.join(target, item);
628
- const stat = fs.statSync(from);
629
- if (stat.isDirectory())
630
- copyDir(from, to);
631
- else {
632
- ensureDir(path.dirname(to));
633
- fs.copyFileSync(from, to);
634
- }
672
+ collect(from, item);
635
673
  }
636
- copyProductionDependencies(packageRoot, target);
637
- }
638
- function copyProductionDependencies(packageRoot, target) {
639
- const queue = [{ sourceRoot: packageRoot, targetRoot: target, ancestors: new Set() }];
674
+ const queue = [{ sourceRoot: packageRoot, targetRoot: "", ancestors: new Set() }];
640
675
  const copied = new Set();
641
676
  while (queue.length > 0) {
642
677
  const current = queue.shift();
@@ -651,12 +686,35 @@ function copyProductionDependencies(packageRoot, target) {
651
686
  if (copied.has(copyKey))
652
687
  continue;
653
688
  copied.add(copyKey);
654
- copyDir(sourceRoot, targetRoot, true);
689
+ collect(sourceRoot, targetRoot, true);
655
690
  if (current.ancestors.has(sourceRoot))
656
691
  continue;
657
692
  queue.push({ sourceRoot, targetRoot, ancestors: new Set([...current.ancestors, sourceRoot]) });
658
693
  }
659
694
  }
695
+ return files;
696
+ }
697
+ export function prepareForkEngine(engineVersion, integrityChecksum) {
698
+ const build = getCliBuildInfo();
699
+ if (engineVersion !== build.version)
700
+ throw new AppError("fork_engine_unavailable", "Fork installs the invoking CLI engine; invoke the requested version to upgrade the fork", 2, { requested: engineVersion, available: build.version });
701
+ const packageRoot = findPackageRoot(path.dirname(sourceFile));
702
+ if (!packageRoot || !fs.existsSync(path.join(packageRoot, "dist", "cli.js")))
703
+ throw new AppError("harness_runtime_unbuilt", "Build the CLI before preparing a fork", 1);
704
+ const files = packageSnapshotFiles(packageRoot);
705
+ if (integrityChecksum !== undefined) {
706
+ const checksum = crypto.createHash("sha256");
707
+ for (const [relative, source] of [...files].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
708
+ if (path.basename(relative) === "engine.json")
709
+ continue;
710
+ checksum.update(relative);
711
+ checksum.update("\0");
712
+ checksum.update(fs.readFileSync(source));
713
+ checksum.update("\0");
714
+ }
715
+ if (checksum.digest("hex") !== integrityChecksum)
716
+ throw new AppError("fork_engine_unavailable", "Requested engine checksum differs from the invoking CLI artifact", 2, { requested: integrityChecksum });
717
+ }
660
718
  }
661
719
  function readProductionDependencies(packageRoot) {
662
720
  try {
@@ -713,19 +771,6 @@ function hasAncestorFile(start, name) {
713
771
  current = parent;
714
772
  }
715
773
  }
716
- function copyDir(from, to, skipNodeModules = false) {
717
- ensureDir(to);
718
- for (const entry of fs.readdirSync(from, { withFileTypes: true })) {
719
- if (skipNodeModules && entry.name === "node_modules")
720
- continue;
721
- const source = path.join(from, entry.name);
722
- const target = path.join(to, entry.name);
723
- if (entry.isDirectory())
724
- copyDir(source, target, skipNodeModules);
725
- else if (entry.isFile())
726
- fs.copyFileSync(source, target);
727
- }
728
- }
729
774
  function acquireInstallLock(lockDir) {
730
775
  ensureDir(path.dirname(lockDir));
731
776
  try {
@@ -847,7 +892,7 @@ function manifestDiagnostics(manifest, options = {}) {
847
892
  }
848
893
  return diagnostics;
849
894
  }
850
- function manifestSummary(manifest) {
895
+ function manifestSummary(manifest, diagnostics = manifestDiagnostics(manifest, { checkEntrypoint: false })) {
851
896
  return {
852
897
  schema_id: manifest.schema_id,
853
898
  package_name: manifest.package_name,
@@ -859,8 +904,8 @@ function manifestSummary(manifest) {
859
904
  snapshot_root: manifest.snapshot_root,
860
905
  resolved_entrypoint: safeResolveManifestEntrypoint(manifest),
861
906
  installed_at: manifest.installed_at,
862
- healthy: manifestHealthy(manifest),
863
- diagnostics: manifestDiagnostics(manifest)
907
+ healthy: diagnostics.length === 0,
908
+ diagnostics
864
909
  };
865
910
  }
866
911
  function resolveManifestEntrypoint(manifest) {