@deksden-com/dd-flow-cli 0.9.0-beta.145 → 0.9.0-beta.149

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 (101) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/dist/build-info.json +7 -7
  3. package/dist/cli/command-inputs.js +6 -2
  4. package/dist/cli/command-metadata.js +18 -2
  5. package/dist/cli/diagnostic-invocation.js +46 -0
  6. package/dist/cli/diagnostic.js +120 -0
  7. package/dist/cli/help.js +35 -0
  8. package/dist/cli/memory-flow-admission.js +4 -2
  9. package/dist/cli/memory-init.js +5 -2
  10. package/dist/cli/run-cli.js +16 -15
  11. package/dist/cli.js +6 -0
  12. package/dist/domain/diagnostic-aspects.js +103 -0
  13. package/dist/domain/diagnostic-flows.js +8 -0
  14. package/dist/harness-runtime/lib/agy-native-inspection.mjs +216 -0
  15. package/dist/harness-runtime/lib/agy-native_pb.js +117 -0
  16. package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
  17. package/dist/harness-runtime/lib/daemon-operations.mjs +84 -10
  18. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +306 -34
  19. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +27 -13
  20. package/dist/harness-runtime/lib/dd-codex.mjs +4 -0
  21. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +4 -3
  22. package/dist/harness-runtime/lib/dd-droid.mjs +70 -28
  23. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +3 -3
  24. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +66 -20
  25. package/dist/harness-runtime/lib/dd-opencode.mjs +30 -3
  26. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +7 -6
  27. package/dist/harness-runtime/lib/driver-recovery.mjs +9 -8
  28. package/dist/harness-runtime/lib/managed-daemon.d.mts +1 -1
  29. package/dist/harness-runtime/lib/managed-daemon.mjs +11 -3
  30. package/dist/harness-runtime/lib/native-children.d.mts +8 -1
  31. package/dist/harness-runtime/lib/native-children.mjs +114 -8
  32. package/dist/harness-runtime/lib/native-request-proof.mjs +1 -1
  33. package/dist/harness-runtime/lib/observation-clock.d.mts +2 -2
  34. package/dist/harness-runtime/lib/observation-clock.mjs +7 -4
  35. package/dist/harness-runtime/lib/provider-overload-policy.d.mts +1 -1
  36. package/dist/harness-runtime/lib/provider-overload-policy.mjs +1 -1
  37. package/dist/harness-runtime/lib/tool-observations.mjs +1 -1
  38. package/dist/schemas/diagnostic-stage-result.schema.json +354 -0
  39. package/dist/schemas/diagnostic-work-result.schema.json +296 -0
  40. package/dist/schemas/execution-bundle.schema.json +7 -1
  41. package/dist/schemas/project-flow-pack-manifest.schema.json +1 -1
  42. package/dist/services/check-environment.js +21 -0
  43. package/dist/services/check-runner-evidence.js +267 -15
  44. package/dist/services/code-checks.js +23 -15
  45. package/dist/services/controller-execution.js +10 -4
  46. package/dist/services/controller-fanout.js +13 -6
  47. package/dist/services/controller-memory-worker.js +3 -2
  48. package/dist/services/dashboard.js +2 -2
  49. package/dist/services/descriptor-run-settlement.js +5 -3
  50. package/dist/services/descriptor-run-start.js +22 -3
  51. package/dist/services/descriptor-stage-coordinator.js +37 -4
  52. package/dist/services/descriptor-stage-settlement.js +2 -1
  53. package/dist/services/diagnostic-evidence.js +73 -0
  54. package/dist/services/diagnostic-report-publication.js +72 -0
  55. package/dist/services/diagnostic-report.js +67 -0
  56. package/dist/services/diagnostic-run-bundle.js +74 -0
  57. package/dist/services/diagnostic-snapshot.js +159 -0
  58. package/dist/services/diagnostic-stage-handlers.js +108 -0
  59. package/dist/services/diagnostic-stage-results.js +132 -0
  60. package/dist/services/diagnostic-start.js +84 -0
  61. package/dist/services/diagnostic-work-batch.js +34 -0
  62. package/dist/services/diagnostic-work-collection.js +97 -0
  63. package/dist/services/diagnostic-work-registration.js +101 -0
  64. package/dist/services/diagnostic-work-results.js +93 -0
  65. package/dist/services/eval-snapshots.js +85 -29
  66. package/dist/services/execution-bundles.js +19 -7
  67. package/dist/services/flow-catalog.js +6 -1
  68. package/dist/services/frozen-engine.js +37 -2
  69. package/dist/services/hybrid-contracts.js +2 -0
  70. package/dist/services/installed-flow-source.js +25 -3
  71. package/dist/services/lifecycle-invocations.js +147 -34
  72. package/dist/services/memory-init-contexts.js +16 -0
  73. package/dist/services/memory-init-controller-start.js +12 -4
  74. package/dist/services/memory-init-lint-capability.js +5 -5
  75. package/dist/services/memory-init-native-capacity.js +4 -4
  76. package/dist/services/memory-init-preflight.js +10 -16
  77. package/dist/services/memory-init-worktrunk-capability.js +3 -7
  78. package/dist/services/memory-layer.js +4 -3
  79. package/dist/services/memory-tool-probe.js +25 -0
  80. package/dist/services/memory-verification-checks.js +15 -4
  81. package/dist/services/native-daemon-history.js +2 -1
  82. package/dist/services/provider-capacity.js +43 -38
  83. package/dist/services/release-cache.js +8 -0
  84. package/dist/services/run-control.js +2 -1
  85. package/dist/services/run-controller-adapter.js +2 -1
  86. package/dist/services/run-controller-process.js +2 -1
  87. package/dist/services/run-controller-recovery.js +2 -1
  88. package/dist/services/run-controller.js +85 -15
  89. package/dist/services/run-fork.js +1 -1
  90. package/dist/services/runs.js +6 -1
  91. package/dist/services/runtime-scope-resume.js +12 -6
  92. package/dist/services/runtime-scope-stop.js +3 -2
  93. package/dist/services/runtime-service.js +2 -1
  94. package/dist/services/stage-handlers.js +16 -3
  95. package/dist/services/stage-pause.js +4 -1
  96. package/dist/services/version-status.js +1 -1
  97. package/dist/services/vnext-code.js +2 -2
  98. package/dist/services/vnext-fanout.js +31 -1
  99. package/dist/services/work-registry.js +37 -6
  100. package/dist/services/workspace-bootstrap.js +2 -1
  101. package/package.json +6 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,76 @@
1
1
  # @deksden-com/dd-flow-cli
2
2
 
3
+ ## 0.9.0-beta.149
4
+
5
+ ### Patch Changes
6
+
7
+ - 54899ed: Publish explicit proven Codex and OpenCode shutdown activity, reject unknown activity as clean settlement, and decode OpenCode finite log synchronization markers consistently for revision and completion.
8
+
9
+ ## 0.9.0-beta.148
10
+
11
+ ### Patch Changes
12
+
13
+ - Validate lifecycle issuance before accessing storage, and align native readiness,
14
+ shutdown and overload regression fixtures with the current execution contract.
15
+
16
+ ## 0.9.0-beta.147
17
+
18
+ ### Patch Changes
19
+
20
+ - Use the shared setup startup budget for silent prerequisite probes and preserve
21
+ typed subprocess failure diagnostics for init and upgrade without leaking output.
22
+
23
+ ## 0.9.0-beta.146
24
+
25
+ ### Patch Changes
26
+
27
+ - d031e09: Add owned, trajectory-bound read-only AGY node-state inspection for native qualification. Capture parent executor idle independently from required-tree readiness, native task inventories and pending completion messages. Preserve qualified production prompt/stop ordering and owned fixture cleanup in native acceptance tooling.
28
+ - acee374: Dispatch independent architectural criticism for Memory Bank audits when their accepted architecture aspect is applicable, matching the frozen review instructions.
29
+ - 709f281: Reject diagnostic citation hash mismatches as correctable no-effect proposal errors, returning only the assigned relative path and computed digest. Retain bounded lifecycle correction, strict evidence acceptance and fatal snapshot/custody protection.
30
+ - 18b0593: Treat unchanged-scope coverage annotations as correctable proposal errors and assign global diagnostic finding IDs from exact completed Work results during analysis settlement. Preserve strict scope, content, source and independent-review custody checks.
31
+ - 7457963: Reject rewritten diagnostic focus as a bounded, no-effect proposal correction
32
+ requiring an exact copy of the accepted predecessor. Foreign focus modes,
33
+ changed aspect applicability and ownership violations remain fatal; no changed
34
+ focus is accepted and no frozen source selection is rewritten.
35
+ - 1b315bf: Issue the exact immediate predecessor in diagnostic coordinator bindings and
36
+ align the frozen schema's list bound with the runtime contract. Transitive
37
+ history is a correctable proposal shape error; foreign direct bindings still
38
+ fail without acceptance.
39
+ - da5bbf0: Allow diagnostics to record newly discovered unavailable sources without treating
40
+ them as a scope change. Retain accepted source-gap observations independently of
41
+ coordinator paraphrasing, while keeping focus, coverage and source-custody guards.
42
+ - 6825b76: Allow diagnostic reports to disclose additional limitations while retaining all reviewed evidence, decisions and existing uncertainty unchanged.
43
+ - e779d4d: Express diagnostic analyst/critic field restrictions in the frozen Work schema.
44
+ An analyst supplying review decisions remains rejected, but uses the existing
45
+ correctable schema-output path rather than a fatal foreign-aspect error.
46
+ - 9939c87: Carry accepted predecessor and independently settled Work limitations into diagnostic analytical and review results in the CLI. Preserve coordinator annotations and raw proposals; retain schema bounds, scope, evidence and independent-review checks.
47
+ - b765022: Clarify that generated review observations are not original project evidence.
48
+ Reject missing snapshot citations as correctable no-effect result errors while
49
+ preserving fatal containment, non-file and filesystem-access failures.
50
+ - 249e5e3: Express preflight and analytical coordinator output boundaries in the frozen
51
+ stage schema, so unsupported findings or review decisions use existing format
52
+ correction while independent review remains mandatory.
53
+ - 6f4011f: Separate required native execution readiness from owned resource retirement across harnesses. Require versioned, incarnation-bound shutdown phase proofs and retain process birth identities before provider shutdown. Qualify AGY daemon readiness independently from physical cleanup; ambiguous ancestry and native service identities remain unproven.
54
+
55
+ Fence retained recovery by the current control generation and preserve late activity during shutdown publication. Add metadata-bound AGY native tree control for typed services, gated by complete native qualification; control acknowledgements never substitute for physical retirement.
56
+
57
+ - a5461c9: Preserve validated repair authority through lifecycle settlement, atomically issue
58
+ nonterminal successors, and retain accepted HITL context across same-session
59
+ overload continuation. Support versioned bounded runner and browser evidence,
60
+ and separate AGY physical shutdown from current native idle proof.
61
+ - e00cda9: Use the verified relocated engine when launching a forked hybrid RUN. Preserve
62
+ the original bundle and manifest bytes as provenance; reject changed executable
63
+ content, semantic manifest metadata, or an unbound relocation path.
64
+ - 759138b: Start restored generic-flow coordinator sessions in the current admitted RUN
65
+ workspace instead of the historical frozen execution path, without changing
66
+ retained execution bytes or profiles.
67
+ - 5bea245: Use the verified current RUN project and workspace paths when settling restored
68
+ descriptor stages and terminal RUNs. Preserve frozen provenance, ownership,
69
+ Git-drift checks and exact replay rather than querying historical project roots.
70
+ - 42cb47b: Read retained descriptor stage-input custody through trusted RUN publication
71
+ relocations after restore, preserving original packet bytes and rejecting
72
+ ambiguous or changed custody.
73
+
3
74
  ## 0.9.0-beta.145
4
75
 
5
76
  ### Patch Changes
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "cli_package": "@deksden-com/dd-flow-cli",
3
- "cli_version": "0.9.0-beta.145",
4
- "cli_commit": "52851c6a5c8419e93b543fd6a53e71f9aa359d9f",
5
- "built_at": "2026-10-21T17:32:05.034Z",
3
+ "cli_version": "0.9.0-beta.149",
4
+ "cli_commit": "7d17167f768483ffdb5d37f64cc385035c7d4658",
5
+ "built_at": "2026-10-24T10:44:58.080Z",
6
6
  "built_with_canon": {
7
7
  "version": "4.1.2",
8
- "commit": "51f7d7155f578e877e4db5c3d022772084ecea5a",
8
+ "commit": "925ef10ecc0b683f7feba6f74913e15560842500",
9
9
  "flow_contract": "dd-flow-canonical-2026-08",
10
- "repo_root": "/Users/deksden/Documents/_Projects/_worktrees/dd-canon-release-145",
11
- "memorybank_root": "/Users/deksden/Documents/_Projects/_worktrees/dd-canon-release-145/.memory-bank",
12
- "flow_root": "/Users/deksden/Documents/_Projects/_worktrees/dd-canon-release-145/.memory-bank/dd-flow",
10
+ "repo_root": "/Users/deksden/Documents/_Projects/_worktrees/dd-canon-release146",
11
+ "memorybank_root": "/Users/deksden/Documents/_Projects/_worktrees/dd-canon-release146/.memory-bank",
12
+ "flow_root": "/Users/deksden/Documents/_Projects/_worktrees/dd-canon-release146/.memory-bank/dd-flow",
13
13
  "layout": "dot_memory_bank"
14
14
  }
15
15
  }
@@ -15,7 +15,8 @@ const control = "project-root run";
15
15
  const protocolTransitionInput = route(1, "to", `${scoped} payload-file json-file reason`, "force");
16
16
  const setupInput = route(0, "request-id trust-file", "canon-source canon-version release-registry channel profile profile-file save-profile-intent", "non-interactive skip-agent-setup", "", { channel: ["stable", "preview", "development"] });
17
17
  const memoryInitInput = route(0, "project-root workspace-root request-id", "intake-file intake-sha256 profile profile-file stage-profile work-profile max-parallel delegation native-config-home git-route integration-branch", "detach non-interactive prepare-only allow-live-probe", "stage-profile work-profile", { "git-route": ["integration_branch_direct", "feature_worktree"], delegation: ["external", "native"] });
18
- export const commandAliases = { transition: "protocol transition", init: "flow mb-init", upgrade: "flow mb-upgrade" };
18
+ const diagnosticInput = route(0, "project-root request-id", "intake-file profile profile-file stage-profile work-profile max-parallel", "detach non-interactive prepare-only", "stage-profile work-profile");
19
+ export const commandAliases = { transition: "protocol transition", init: "flow mb-init", upgrade: "flow mb-upgrade", review: "flow project-review" };
19
20
  /** Path-shaped arguments default to physical paths unless the route explicitly
20
21
  * opts into contextualPaths. This inventory does not expand arbitrary strings. */
21
22
  export const physicalPathOptions = new Set(words("canon-source trust-file root project-root workspace-root workspace-path workspace-path path dir file schema-dir output result-file semantic-file data decision-file verification-file reason-file task-file intake-file context-file observations-file observation-file summary-file question-file answer-file payload-file json-file task-profile-file execution-routing-file protocol-override-file manifest-file routing-file config-file input-file report-file snapshot-file output-dir state-dir capture-root capture-path destination source archive snapshot plan-file backup-path database backup source-home target-home stdout stderr adapter-executable memory-bank flow stage-report dashboard report"));
@@ -35,10 +36,13 @@ export const commandInputs = {
35
36
  "init": memoryInitInput,
36
37
  "flow mb-upgrade": memoryInitInput,
37
38
  "upgrade": memoryInitInput,
39
+ "flow mb-audit": diagnosticInput,
40
+ "flow project-review": diagnosticInput,
41
+ "review": diagnosticInput,
38
42
  "runtime storage admit": route(0, "operation", "", "", "", { operation: ["memory-init", "memory-upgrade"] }),
39
43
  "runtime storage qualify": route(0, "operation", "", "", "", { operation: ["memory-upgrade"] }),
40
44
  "runtime storage transition": route(0, "action project-root workspace-root request-id selection-sha256", "expected-sha256 recovery-id", "offline", "", { action: ["plan", "apply", "status", "recovery-plan", "recover", "recovery-status"] }),
41
- "runtime flow admit": route(0, "operation", "", "", "", { operation: ["memory-init", "memory-upgrade"] }),
45
+ "runtime flow admit": route(0, "operation", "", "", "", { operation: ["memory-init", "memory-upgrade", "mb-audit", "project-review"] }),
42
46
  "completion bash": route(0),
43
47
  "completion zsh": route(0),
44
48
  "completion fish": route(0),
@@ -1,4 +1,4 @@
1
- import { commandInputs, physicalPathOptions } from "./command-inputs.js";
1
+ import { commandInputs, commandAliases, physicalPathOptions } from "./command-inputs.js";
2
2
  import { runtimeOptionDescriptions } from "./command-option-descriptions.js";
3
3
  const optionDescriptions = {
4
4
  "intake-sha256": "SHA-256 of the exact original intake file bytes. Fresh execution verifies the bytes; replay compares the retained hash without reopening the original file.",
@@ -61,6 +61,20 @@ const memoryInitOptions = {
61
61
  "non-interactive": "Never open the first-flow executor dialog, even on a terminal. Without an explicit profile/file or configured default, refuse before project effects; init cannot skip its executor."
62
62
  };
63
63
  const commandOptionDescriptions = {
64
+ "flow mb-audit": {
65
+ "project-root": "Canonical absolute project identity. The runtime creates an isolated read-only source snapshot; neither the project nor its Memory Bank is a write destination.",
66
+ "intake-file": "Bounded UTF-8 original diagnostic request, including audit mode/focus and constraints. Required for a fresh request; optional on exact replay, where supplied bytes must match.",
67
+ "stage-profile": "Repeatable preflight/audit/review/report=profile override. Each stage may occur once and all profiles are frozen before dispatch.",
68
+ "prepare-only": "Retain the request, source snapshot and RUN without starting its controller or provider Sessions. Runtime state is created; this is not a dry run.",
69
+ "max-parallel": "External Work concurrency from 1 to 64; default 1. Never claims native child qualification."
70
+ },
71
+ "flow project-review": {
72
+ "project-root": "Canonical absolute project identity, assessed through an isolated read-only snapshot. Findings never authorize implicit repair.",
73
+ "intake-file": "Bounded UTF-8 original project-conformance review request and focus. Required fresh; supplied replay bytes must match the retained objective.",
74
+ "stage-profile": "Repeatable preflight/analyze/review/report=profile override. This is project review, not the SDLC CODE-REVIEW stage.",
75
+ "prepare-only": "Retain the request, source snapshot and RUN without launching a controller or provider Sessions. Not a dry run.",
76
+ "max-parallel": "External Work concurrency from 1 to 64; default 1."
77
+ },
64
78
  "flow mb-init": memoryInitOptions,
65
79
  init: memoryInitOptions,
66
80
  "flow mb-upgrade": { ...memoryInitOptions, "stage-profile": "Repeatable stage=profile assignment for preflight, diff-analysis, upgrade, verify, review or finish. The original selection is frozen before dispatch.", "prepare-only": "Prepare or replay the upgrade RUN and accepted storage/bootstrap selection without launching its controller or a productive Session. This is not a dry run; it does not publish the upgraded canonical layer or accept semantic preservation." },
@@ -162,7 +176,8 @@ export function optionsForCommand(input, command) {
162
176
  }));
163
177
  }
164
178
  function optionDescription(name, command) {
165
- const description = (command ? commandOptionDescriptions[command]?.[name] : undefined) ?? optionDescriptions[name] ?? runtimeOptionDescriptions[name];
179
+ const description = (command ? commandOptionDescriptions[command]?.[name]
180
+ ?? commandOptionDescriptions[commandAliases[command] ?? command]?.[name] : undefined) ?? optionDescriptions[name] ?? runtimeOptionDescriptions[name];
166
181
  if (!description)
167
182
  throw new Error(`Missing CLI option documentation: ${command ?? "inventory"} --${name}`);
168
183
  return description;
@@ -182,6 +197,7 @@ export function optionsForCommandTree(command) {
182
197
  export function routerNativeRoute(key) {
183
198
  return key === "version" || key.startsWith("engine ") || key === "schema validate"
184
199
  || key === "flow list" || key === "flow show" || key === "flow mb-init" || key === "init" || key === "flow mb-upgrade" || key === "upgrade"
200
+ || key === "flow mb-audit" || key === "flow project-review" || key === "review"
185
201
  || key === "run reconcile" || key === "runtime storage admit" || key === "runtime storage qualify" || key === "runtime storage transition"
186
202
  || key === "runtime flow admit"
187
203
  || key.startsWith("profiles ") || key.startsWith("models ") || key.startsWith("harnesses ") || key.startsWith("completion ") || key === "setup" || key.startsWith("setup ")
@@ -0,0 +1,46 @@
1
+ import path from "node:path";
2
+ import { AppError } from "../shared/errors.js";
3
+ import { diagnosticFlowStages } from "../domain/diagnostic-flows.js";
4
+ import { prepareCommandRoute } from "./command-inputs.js";
5
+ function refuse(message) { throw new AppError("usage", message, 2, { effect: "no_effect" }); }
6
+ /** Parser only: no files, operator settings, project mutation or runtime state. */
7
+ export function prepareDiagnosticInvocation(args) {
8
+ const route = prepareCommandRoute(args), options = route.parsed.options;
9
+ const flowId = route.key === "flow mb-audit" ? "mb-audit"
10
+ : ["review", "flow project-review"].includes(route.key) ? "project-review" : refuse("Use flow mb-audit or review / flow project-review");
11
+ const value = (key) => options.get(key)?.[0];
12
+ const projectRoot = value("project-root"), requestId = value("request-id");
13
+ if (!path.isAbsolute(projectRoot) || path.resolve(projectRoot) !== projectRoot
14
+ || !/^[A-Za-z0-9][A-Za-z0-9_.-]{0,95}$/.test(requestId))
15
+ refuse("Use a canonical absolute project root and a bounded request ID");
16
+ if (value("profile") && value("profile-file"))
17
+ refuse("Use either --profile or --profile-file");
18
+ if (options.has("detach") && options.has("prepare-only"))
19
+ refuse("--detach and --prepare-only are exclusive");
20
+ const execution = {};
21
+ if (value("profile"))
22
+ execution.rootProfile = value("profile");
23
+ const stageProfiles = {};
24
+ for (const assignment of options.get("stage-profile") ?? []) {
25
+ const match = /^([^=]+)=([^=]+)$/.exec(assignment);
26
+ if (!match || !diagnosticFlowStages[flowId].includes(match[1]) || Object.hasOwn(stageProfiles, match[1]))
27
+ refuse("--stage-profile requires one known stage=profile assignment per stage");
28
+ stageProfiles[match[1]] = match[2];
29
+ }
30
+ if (Object.keys(stageProfiles).length)
31
+ execution.stageProfiles = stageProfiles;
32
+ const workProfiles = options.get("work-profile");
33
+ if (workProfiles) {
34
+ if (workProfiles.some(id => !id.trim()) || new Set(workProfiles).size !== workProfiles.length)
35
+ refuse("Work profiles must be nonempty and unique");
36
+ execution.workProfiles = [...workProfiles];
37
+ }
38
+ if (value("max-parallel") !== undefined) {
39
+ const raw = value("max-parallel"), n = Number(raw);
40
+ if (!/^[1-9][0-9]*$/.test(raw) || !Number.isSafeInteger(n) || n > 64)
41
+ refuse("--max-parallel must be an integer from 1 to 64");
42
+ execution.delegation = { mode: "external", max_parallel: n };
43
+ }
44
+ return { scope: { projectRoot, requestId, flowId }, execution, profileFile: value("profile-file"), intakeFile: value("intake-file"),
45
+ detach: options.has("detach"), prepareOnly: options.has("prepare-only"), nonInteractive: options.has("non-interactive") };
46
+ }
@@ -0,0 +1,120 @@
1
+ import { createContext } from "../runtime/context.js";
2
+ import { AppError, errorRecord } from "../shared/errors.js";
3
+ import { resolveDdFlowHome, resolveProjectRoot } from "../storage/paths.js";
4
+ import { readSetupStatus } from "../services/setup.js";
5
+ import { PreparedInstalledFlowSource } from "../services/installed-flow-source.js";
6
+ import { selectMemoryInitInstalledEngine } from "../services/memory-init-engine-release.js";
7
+ import { readOperatorProfiles, readOperatorProfileInput } from "../services/operator-profiles.js";
8
+ import { FrozenGenericExecution } from "../services/generic-execution.js";
9
+ import { openMemoryInitStores } from "../services/memory-init-stores.js";
10
+ import { registerProject, requireProjectByRoot } from "../services/projects.js";
11
+ import { diagnosticSnapshotRoot } from "../services/diagnostic-snapshot.js";
12
+ import { readDiagnosticRequest, retainDiagnosticRequest, startDiagnosticRun } from "../services/diagnostic-start.js";
13
+ import { readFlowRunExecutionBundle } from "../services/runs.js";
14
+ import { hybridDigest } from "../services/hybrid-contracts.js";
15
+ import { onboardExecutor } from "../services/executor-onboarding.js";
16
+ import { askExecutorChoice } from "./executor-onboarding.js";
17
+ import { prepareDiagnosticInvocation } from "./diagnostic-invocation.js";
18
+ import { parseOutputOptions } from "./output-options.js";
19
+ import { prepareResponseDestination } from "./response-file.js";
20
+ import { prepareFileBytes } from "./input-preparation.js";
21
+ import { writePreparationError } from "./error-output.js";
22
+ import { launchAndObserveMemoryFlow } from "./memory-init.js";
23
+ import { publishSetup } from "../harness-runtime/lib/setup-progress.mjs";
24
+ export async function runDiagnosticCli(args, env = process.env, io = process) {
25
+ let context, effects = false;
26
+ try {
27
+ const output = parseOutputOptions(args), invocation = prepareDiagnosticInvocation(output.args), home = resolveDdFlowHome(env);
28
+ if (output.responseFile)
29
+ prepareResponseDestination(output.responseFile);
30
+ const scope = invocation.scope;
31
+ if (resolveProjectRoot(scope.projectRoot) !== scope.projectRoot)
32
+ throw new AppError("usage", "Project root must be canonical", 2, { effect: "no_effect" });
33
+ const reader = createContext(env, "read_existing");
34
+ let retained = null;
35
+ try {
36
+ const project = reader.db.get("SELECT status FROM projects WHERE root=?", [scope.projectRoot]);
37
+ if (project?.status && project.status !== "active")
38
+ throw new AppError("diagnostic_project_inactive", "Activate the selected project explicitly before diagnosis", 2, { effect: "no_effect" });
39
+ if (project)
40
+ retained = readDiagnosticRequest(reader, scope);
41
+ }
42
+ finally {
43
+ reader.db.close?.();
44
+ }
45
+ if (invocation.profileFile)
46
+ invocation.execution.rootProfile = readOperatorProfileInput(invocation.profileFile);
47
+ const readIntake = () => {
48
+ if (!invocation.intakeFile)
49
+ throw new AppError("usage", "Fresh diagnosis requires --intake-file with the requested focus and constraints", 2, { effect: "no_effect" });
50
+ const bytes = prepareFileBytes(invocation.intakeFile, "diagnostic intake", 64 * 1024).bytes;
51
+ let text;
52
+ try {
53
+ text = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
54
+ }
55
+ catch {
56
+ throw new AppError("usage", "Diagnostic intake must be UTF-8", 2, { effect: "no_effect" });
57
+ }
58
+ if (!text.trim())
59
+ throw new AppError("usage", "Diagnostic intake cannot be empty", 2, { effect: "no_effect" });
60
+ return text;
61
+ };
62
+ if (retained) {
63
+ if (Object.entries(invocation.execution).some(([key, value]) => !Object.hasOwn(retained.selection, key) || hybridDigest(value) !== hybridDigest(retained.selection[key]))
64
+ || invocation.intakeFile && readIntake() !== retained.objective)
65
+ throw new AppError("diagnostic_request_conflict", "Supplied selectors differ from the original diagnostic request", 2, { effect: "no_effect" });
66
+ }
67
+ const objective = retained?.objective ?? readIntake();
68
+ let source, engine;
69
+ let rootProfile = invocation.execution.rootProfile;
70
+ if (!retained) {
71
+ const setup = readSetupStatus(home), installation = setup.installation;
72
+ if (!installation || setup.installation_readiness !== "installed")
73
+ throw new AppError("diagnostic_setup_required", "Install the desired canon and CLI with setup before diagnosis", 2, { effect: "no_effect" });
74
+ publishSetup("dependency_check_completed", "diagnostic:installation");
75
+ source = PreparedInstalledFlowSource.fromInstalledRelease(home, installation.manifest_sha256, scope.flowId);
76
+ publishSetup("dependency_check_completed", "diagnostic:canon");
77
+ engine = selectMemoryInitInstalledEngine(home, installation.manifest_sha256);
78
+ publishSetup("dependency_check_completed", "diagnostic:engine");
79
+ engine.admitDiagnosticFlow(source, scope.flowId);
80
+ publishSetup("dependency_check_completed", "diagnostic:handlers");
81
+ const stdin = io.stdin ?? process.stdin;
82
+ const selected = await onboardExecutor({ ...(typeof rootProfile === "string" ? { profileId: rootProfile } : {}),
83
+ ...(invocation.profileFile ? { profileFile: invocation.profileFile } : {}) }, () => readOperatorProfiles(home), { allowSkip: false, nonInteractive: invocation.nonInteractive || output.json || output.progressJsonl
84
+ || !stdin.isTTY || !io.stderr.isTTY }, settings => askExecutorChoice(settings, { stdin, stderr: io.stderr }, { allowSave: false, allowSkip: false }));
85
+ if (!selected.profile)
86
+ throw new AppError("diagnostic_executor_required", "Select an executor profile before diagnosis", 2, { effect: "no_effect" });
87
+ rootProfile ??= selected.profile;
88
+ }
89
+ effects = true;
90
+ context = openMemoryInitStores(env, { scope: { projectRoot: scope.projectRoot, workspaceRoot: scope.projectRoot, requestId: scope.requestId },
91
+ selectionSha256: hybridDigest({ scope, objective, selection: retained?.selection ?? invocation.execution }) }).context;
92
+ publishSetup("dependency_check_completed", "diagnostic:stores");
93
+ registerProject(context, { root: scope.projectRoot });
94
+ if (!retained) {
95
+ const project = requireProjectByRoot(context, scope.projectRoot);
96
+ const workspace = diagnosticSnapshotRoot(context, { projectId: project.id, projectRoot: scope.projectRoot, sourceRoot: scope.projectRoot,
97
+ requestId: scope.requestId, flowId: scope.flowId });
98
+ const execution = FrozenGenericExecution.prepare(source.resolveRegisteredFlow(), readOperatorProfiles(home), {
99
+ ...invocation.execution, ...(rootProfile !== undefined ? { rootProfile } : {}), execution: "managed", stageSessionMode: "new_session",
100
+ delegation: invocation.execution.delegation ?? { mode: "external", max_parallel: 1 }
101
+ }, { requestId: scope.requestId, projectRoot: scope.projectRoot, workspace });
102
+ retainDiagnosticRequest(context, { scope, source: source, execution, engine: engine, objective, selection: invocation.execution });
103
+ }
104
+ const started = startDiagnosticRun(context, scope), bundle = readFlowRunExecutionBundle(context, { projectRoot: scope.projectRoot, runId: started.runId });
105
+ if (!bundle)
106
+ throw new AppError("diagnostic_bundle_missing", "Restore the retained diagnostic bundle", 1, { effect: "committed" });
107
+ return await launchAndObserveMemoryFlow(context, { ...invocation,
108
+ scope: { projectRoot: scope.projectRoot, workspaceRoot: bundle.execution.snapshot().scope.workspace, requestId: scope.requestId } }, started, scope.flowId, output, env, io);
109
+ }
110
+ catch (error) {
111
+ const record = errorRecord(error);
112
+ if (effects)
113
+ record.details = { ...record.details, effect: ["unknown", "committed", "partial"].includes(String(record.details.effect)) ? record.details.effect : "partial", next_action: "reconcile_exact_request" };
114
+ writePreparationError(io, args, record);
115
+ return record.exitCode;
116
+ }
117
+ finally {
118
+ context?.db.close?.();
119
+ }
120
+ }
package/dist/cli/help.js CHANGED
@@ -2,6 +2,41 @@ 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
+ ["flow mb-audit", `dd-flow flow mb-audit - read-only Memory Bank audit
6
+
7
+ Usage:
8
+ dd-flow flow mb-audit --project-root /absolute/project --request-id ID --intake-file /absolute/request.txt [--profile ID] --json
9
+
10
+ Describe quick/full or focused audit scope in the original intake. Stages are
11
+ preflight → audit → review → report. Applicable aspects use assigned read-only
12
+ Work and independent critics. Missing evidence and active DEFs remain visible.
13
+ Reports and runtime state live in DD_FLOW_HOME; project files are not repaired.
14
+ The snapshot includes selected dirty/untracked source, excluding private inputs.
15
+
16
+ Profiles, engine, canon and objective are frozen before dispatch. Exact replay
17
+ may omit intake/profile selectors; supplied selectors must match the original.
18
+ --prepare-only creates the snapshot and RUN without Sessions; --detach returns
19
+ the controller receipt. Neither is a dry run. Default execution is managed,
20
+ with fresh stage Sessions and external Work (concurrency 1).
21
+ Use existing run drive status and run control commands for observation/recovery.`],
22
+ ["flow project-review", `dd-flow review / flow project-review - read-only project-conformance review
23
+
24
+ Usage:
25
+ dd-flow review --project-root /absolute/project --request-id ID --intake-file /absolute/request.txt [--profile ID] --json
26
+
27
+ Both spellings share validation and effects. Describe the review focus and
28
+ constraints in the original intake. Stages are preflight → analyze → review →
29
+ report. Compare specs, features, ADRs, scenarios and policy with actual code and
30
+ evidence. This is not SDLC CODE-REVIEW and never implicitly repairs the project.
31
+ All baseline aspects retain applicability and coverage; independent criticism,
32
+ rejected findings and uncertainty remain in runtime evidence.
33
+
34
+ Execution uses an isolated read-only snapshot and frozen global canon. No local
35
+ Memory Bank installation is required. Profiles and the original request remain
36
+ pinned on replay. --prepare-only creates runtime state without Sessions;
37
+ --detach returns the accepted controller receipt. Default execution is managed,
38
+ with fresh stage Sessions and external Work. Use run drive status / run control
39
+ to observe, answer, stop or reconcile the exact retained RUN.`],
5
40
  ["runtime storage qualify", `dd-flow runtime storage qualify - pre-probe selected-engine capability
6
41
 
7
42
  --operation memory-upgrade checks existing storage with the executing engine.
@@ -6,6 +6,7 @@ import { hybridDigest } from "../services/hybrid-contracts.js";
6
6
  import { AppError, errorRecord } from "../shared/errors.js";
7
7
  import { writePreparationError } from "./error-output.js";
8
8
  import { memoryInitStartupInputSchemas } from "../services/memory-init-startup-custody.js";
9
+ import { isDiagnosticFlow } from "../domain/diagnostic-flows.js";
9
10
  /** Executing engine's real handler admission only. The caller owns independent
10
11
  * release/source selection; this command never opens storage or creates a RUN. */
11
12
  export async function runMemoryFlowAdmissionCli(args, io = process) {
@@ -20,7 +21,8 @@ export async function runMemoryFlowAdmissionCli(args, io = process) {
20
21
  throw new AppError("usage", "Flow admission requires an exact source snapshot and independent digest", 2, { effect: "no_effect" });
21
22
  }
22
23
  const selected = input;
23
- const flowId = route.parsed.options.get("operation")?.[0] === "memory-upgrade" ? "mb-upgrade" : "mb-init";
24
+ const operation = route.parsed.options.get("operation")?.[0] ?? "";
25
+ const flowId = isDiagnosticFlow(operation) ? operation : operation === "memory-upgrade" ? "mb-upgrade" : "mb-init";
24
26
  const startupSchema = memoryInitStartupInputSchemas.find(schema => schema === selected.startup_input_schema);
25
27
  if (Object.hasOwn(selected, "startup_input_schema") && (startupSchema === undefined || flowId !== "mb-init")) {
26
28
  throw new AppError("usage", "Executing engine does not support the requested startup input schema", 2, { effect: "no_effect" });
@@ -32,7 +34,7 @@ export async function runMemoryFlowAdmissionCli(args, io = process) {
32
34
  const flow = source.resolveRegisteredFlow();
33
35
  if (flow.descriptor.id !== flowId)
34
36
  throw new AppError("usage", "Flow source does not match the requested engine admission operation", 2, { effect: "no_effect" });
35
- const facts = { schema_id: flowId === "mb-upgrade" ? "dd-flow/memory-upgrade-engine-flow@1"
37
+ const facts = { schema_id: isDiagnosticFlow(flowId) ? "dd-flow/diagnostic-engine-flow@1" : flowId === "mb-upgrade" ? "dd-flow/memory-upgrade-engine-flow@1"
36
38
  : startupSchema === undefined ? "dd-flow/memory-init-engine-flow@1" : "dd-flow/memory-init-engine-flow@2",
37
39
  ...(startupSchema === undefined ? {} : { startup_input_schema: startupSchema }),
38
40
  flow_id: flowId, source_sha256: source.sha256,
@@ -28,6 +28,8 @@ import { onboardExecutor } from "../services/executor-onboarding.js";
28
28
  import { readOperatorProfiles, readOperatorProfileInput } from "../services/operator-profiles.js";
29
29
  import { resolveDdFlowHome } from "../storage/paths.js";
30
30
  import { askExecutorChoice } from "./executor-onboarding.js";
31
+ import { isDiagnosticFlow } from "../domain/diagnostic-flows.js";
32
+ import { prepareDiagnosticControllerStart } from "../services/memory-init-controller-start.js";
31
33
  /** Backpressure bounds the observing client's output; closing it never controls the RUN. */
32
34
  export async function writeMemoryInitObservation(stdout, text) {
33
35
  const disconnected = () => Boolean(stdout.destroyed);
@@ -175,7 +177,7 @@ export async function runMemoryInitCli(args, env = process.env, io = process) {
175
177
  /** Shared foreground observer only; the existing RUN-bound controller owns
176
178
  * physical Sessions. Neither alias gains another orchestration lifecycle. */
177
179
  export async function launchAndObserveMemoryFlow(context, invocation, started, flowId, output, env, io) {
178
- const action = flowId === "mb-init" ? "flow_mb_init" : "flow_mb_upgrade";
180
+ const action = `flow_${flowId.replaceAll("-", "_")}`;
179
181
  if (invocation.prepareOnly) {
180
182
  const run = requireRun(context, requireProjectByRoot(context, invocation.scope.projectRoot).id, started.runId);
181
183
  const response = { ok: true, action: `${action}_prepared`, flow_id: flowId, ...started,
@@ -184,7 +186,8 @@ export async function launchAndObserveMemoryFlow(context, invocation, started, f
184
186
  await writeMemoryInitObservation(io.stdout, `${JSON.stringify(writeResponseFile(response, output), null, output.json || output.progressJsonl ? undefined : 2)}\n`);
185
187
  return 0;
186
188
  }
187
- const launch = (flowId === "mb-init" ? prepareMemoryInitControllerStart : prepareMemoryUpgradeControllerStart)(context, invocation.scope);
189
+ const launch = isDiagnosticFlow(flowId) ? prepareDiagnosticControllerStart(context, invocation.scope, flowId)
190
+ : (flowId === "mb-init" ? prepareMemoryInitControllerStart : prepareMemoryUpgradeControllerStart)(context, invocation.scope);
188
191
  // Use the existing RUN-bound router: a global CLI must not launch its own
189
192
  // controller code in place of the independently frozen RUN executable.
190
193
  const receiptChunks = [];
@@ -65,6 +65,8 @@ import { isVnextCodeReviewRun, prepareCodeReviewDecision, prepareVnextCodeReview
65
65
  import { applyVnextMerge, getVnextMergeRequest, isVnextMergeRun, prepareVnextMergeFinish, prepareVnextMergeStart, repairVnextMerge, routeVnextMergeRequest } from "../services/vnext-merge.js";
66
66
  import { sdlcStageHandlers } from "../services/stage-handlers.js";
67
67
  import { startMemoryStage, finishMemoryStage, startMemoryUpgradeStage, finishMemoryUpgradeStage } from "../services/memory-stage-dispatch.js";
68
+ import { startDiagnosticStage, finishDiagnosticStage } from "../services/diagnostic-stage-handlers.js";
69
+ import { isDiagnosticFlow } from "../domain/diagnostic-flows.js";
68
70
  import { bindStageCoordinatorWork } from "../services/work-registry.js";
69
71
  import { registerMemoryCompositionFromStage, publishMemoryUpgradeFromStage } from "../services/memory-compose-handler.js";
70
72
  import { runMemoryVerificationChecks } from "../services/memory-verify-handler.js";
@@ -75,7 +77,7 @@ import { registerMemoryCorrectionsFromStage, registerMemorySecondCorrectionsFrom
75
77
  import { integrateMemoryFinishFromStage, registerMemoryFinishEvidenceFromStage } from "../services/memory-finish-handler.js";
76
78
  import { registerMemoryRepairBaselineFromStage, registerMemorySecondRepairBaselineFromStage, reserveMemoryRepairWriterCopyFromStage, reserveMemorySecondRepairCopyFromStage, materializeMemoryRepairWriterCopyFromStage, materializeMemorySecondRepairCopyFromStage, prepareMemoryRepairDispatchFromStage, prepareMemorySecondRepairDispatchFromStage, registerMemoryRepairCurrentViewFromStage, registerMemorySecondRepairCurrentViewFromStage, registerMemoryRepairPublicationSourceFromStage, reserveMemoryRepairPublicationAgainstSourceFromStage, reserveMemorySecondRepairPublicationFromStage, applyMemoryRepairPublicationFromStage, applyMemorySecondRepairPublicationFromStage, finishMemoryRepairPublicationFromStage, finishMemorySecondRepairPublicationFromStage } from "../services/memory-review-handler.js";
77
79
  import { mergeServerStatus, serveMergeRequests, stopMergeServer } from "../services/merge-server.js";
78
- import { getVnextFanoutStatus, memoryStageFanoutDescriptor, observeVnextNativeChildren, prepareNativeObservations, reconcileVnextFanout } from "../services/vnext-fanout.js";
80
+ import { getVnextFanoutStatus, memoryStageFanoutDescriptor, diagnosticStageFanoutDescriptor, observeVnextNativeChildren, prepareNativeObservations, reconcileVnextFanout } from "../services/vnext-fanout.js";
79
81
  import { pauseStageForUser, resumeStageAfterUser, prepareStagePause, prepareStageResume, stagePauseCommand } from "../services/stage-pause.js";
80
82
  import { runtimeCommand } from "../services/runtime-command.js";
81
83
  import { managedLifecycleCommand } from "../services/lifecycle-invocations.js";
@@ -483,11 +485,9 @@ async function runCliRequest(args, io, env) {
483
485
  if (isAppError(error)) {
484
486
  delete error.details.retry_command;
485
487
  delete error.details.retry_instruction;
486
- const settled = errorRecord(error);
487
- settleLifecycleRejection(invocationSettlement.context, invocationSettlement.id, settled);
488
- // Serialization owns a copy. Publish the committed settlement, not
489
- // the original rejection which has no successor authority.
490
- Object.assign(error.details, settled.details);
488
+ // The common settlement classifies original validated proof before
489
+ // errorRecord clones diagnostic objects and loses capability identity.
490
+ settleLifecycleRejection(invocationSettlement.context, invocationSettlement.id, error);
491
491
  }
492
492
  else
493
493
  settleLifecycleInvocation(invocationSettlement.context, invocationSettlement.id, { error: { code: "unexpected", message: String(error), exitCode: 1, details: {} } });
@@ -1232,7 +1232,7 @@ async function prepareCliInputValue(args, context, io, scopeProjectRoot, respons
1232
1232
  const bundle = readFlowRunExecutionBundle(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") });
1233
1233
  if (bundle)
1234
1234
  prepared.stageBindingBundle = { stage, bundle, ...(contextSha256 ? { contextSha256 } : {}) };
1235
- if (bundle && ["mb-init", "mb-upgrade"].includes(bundle.descriptor.descriptor.id)) {
1235
+ if (bundle && (["mb-init", "mb-upgrade"].includes(bundle.descriptor.descriptor.id) || isDiagnosticFlow(bundle.descriptor.descriptor.id))) {
1236
1236
  bundle.descriptor.stage(stage);
1237
1237
  if (hasOption(parsed, "dir"))
1238
1238
  throw new AppError("usage", "Memory stage layout belongs to its frozen descriptor", 2);
@@ -1240,7 +1240,7 @@ async function prepareCliInputValue(args, context, io, scopeProjectRoot, respons
1240
1240
  if (loaded)
1241
1241
  prepared.stageStart = { externalContext: loaded };
1242
1242
  prepared.memoryStage = { operation: "start", bytes: loaded ? Buffer.from(loaded.bytes) : Buffer.alloc(0),
1243
- flowId: bundle.descriptor.descriptor.id === "mb-upgrade" ? "mb-upgrade" : "mb-init" };
1243
+ flowId: isDiagnosticFlow(bundle.descriptor.descriptor.id) ? bundle.descriptor.descriptor.id : bundle.descriptor.descriptor.id === "mb-upgrade" ? "mb-upgrade" : "mb-init" };
1244
1244
  return prepared;
1245
1245
  }
1246
1246
  }
@@ -1374,10 +1374,11 @@ async function prepareCliInputValue(args, context, io, scopeProjectRoot, respons
1374
1374
  const semanticFile = optionalOption(parsed, "semantic-file") ?? optionalOption(parsed, "data");
1375
1375
  const resultFile = optionalOption(parsed, "result-file") ?? semanticFile;
1376
1376
  const runtime = run(projectRoot, runId);
1377
- if (["mb-init", "mb-upgrade"].includes(JSON.parse(runtime.indexJson).execution_bundle?.flow_id)) {
1377
+ const retainedFlowId = JSON.parse(runtime.indexJson).execution_bundle?.flow_id;
1378
+ if (["mb-init", "mb-upgrade"].includes(retainedFlowId) || isDiagnosticFlow(retainedFlowId)) {
1378
1379
  const bundle = readFlowRunExecutionBundle(context, { projectRoot, runId });
1379
- if (!bundle || !["mb-init", "mb-upgrade"].includes(bundle.descriptor.descriptor.id))
1380
- throw new AppError("cli_input_unprepared", "Memory RUN bundle is unavailable", 1);
1380
+ if (!bundle || !["mb-init", "mb-upgrade"].includes(bundle.descriptor.descriptor.id) && !isDiagnosticFlow(bundle.descriptor.descriptor.id))
1381
+ throw new AppError("cli_input_unprepared", "Registered RUN bundle is unavailable", 1);
1381
1382
  bundle.descriptor.stage(stage);
1382
1383
  if (["dir", "outcome", "decision-file", "verification-file", "retry-check", "reason", "reason-file", "request", "work"].some(option => hasOption(parsed, option)))
1383
1384
  throw new AppError("usage", "Memory stage finish requires its registered JSON result, not legacy result options", 2);
@@ -1386,7 +1387,7 @@ async function prepareCliInputValue(args, context, io, scopeProjectRoot, respons
1386
1387
  const bytes = resultStdin ? Buffer.from(await readStdin(io.stdin, 1024 * 1024)) : prepareFileBytes(resultFile, "Memory stage result", 1024 * 1024).bytes;
1387
1388
  if (!bytes.length || bytes.length > 1024 * 1024)
1388
1389
  throw new AppError("usage", "Memory stage result exceeds its byte bound", 2);
1389
- prepared.memoryStage = { operation: "finish", bytes, flowId: bundle.descriptor.descriptor.id === "mb-upgrade" ? "mb-upgrade" : "mb-init" };
1390
+ prepared.memoryStage = { operation: "finish", bytes, flowId: isDiagnosticFlow(bundle.descriptor.descriptor.id) ? bundle.descriptor.descriptor.id : bundle.descriptor.descriptor.id === "mb-upgrade" ? "mb-upgrade" : "mb-init" };
1390
1391
  return prepared;
1391
1392
  }
1392
1393
  const terminalReplay = (name) => {
@@ -1780,7 +1781,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
1780
1781
  matchKey: entityReferenceVariants(runId).map(reference => stageStartMatchKey(reference, stage, projectRoot, contextSha256)),
1781
1782
  errorCode: "trusted_session_binding_required", operation: "Memory stage start"
1782
1783
  }).eventKey;
1783
- const work = (prepared.memoryStage.flowId === "mb-upgrade" ? startMemoryUpgradeStage : startMemoryStage)(context, { projectRoot, runId, stage, bytes: prepared.memoryStage.bytes });
1784
+ const work = (isDiagnosticFlow(prepared.memoryStage.flowId) ? startDiagnosticStage : prepared.memoryStage.flowId === "mb-upgrade" ? startMemoryUpgradeStage : startMemoryStage)(context, { projectRoot, runId, stage, bytes: prepared.memoryStage.bytes });
1784
1785
  try {
1785
1786
  const binding = bindStageCoordinatorWork(context, { workId: work.work_id, hookEventId: event, stage,
1786
1787
  promptPath: work.prompt_path, resultPath: work.result_path, ...(contextSha256 ? { contextSha256 } : {}) });
@@ -1790,7 +1791,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
1790
1791
  const finish = managedLifecycleCommand(context, runtimeCommand(context, ["stage", "finish", runId,
1791
1792
  "--stage", stage, "--project-root", projectRoot, "--result-file", work.result_path, "--json"]));
1792
1793
  const pause = stagePauseCommand(context, { projectRoot, runId, stage, workId: work.work_id });
1793
- const orchestration = memoryStageFanoutDescriptor(context, { projectRoot, runId, stage });
1794
+ const orchestration = memoryStageFanoutDescriptor(context, { projectRoot, runId, stage }) ?? diagnosticStageFanoutDescriptor(context, { projectRoot, runId, stage });
1794
1795
  const delivery = { ...work, binding, ...(orchestration ? { orchestration } : {}), next: { finish_command: finish, pause_command: pause },
1795
1796
  worker_prompt_markdown: `${work.worker_prompt_markdown}\n## Current lifecycle commands\n\nWrite the semantic result to the assigned result path, then execute exactly:\n${finish}\n\nFor a required user choice, write the question file named by this command, then execute exactly:\n${pause}\n` };
1796
1797
  bindLifecyclePublishedPacket(context, delivery.worker_prompt_markdown);
@@ -1899,7 +1900,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
1899
1900
  // Descriptor settlement validates exact coordinator custody and claims
1900
1901
  // the fresh hook atomically with result acceptance. Do not consume it
1901
1902
  // through the SDLC lifecycle owner before that settlement.
1902
- return controlledReply((prepared.memoryStage.flowId === "mb-upgrade" ? finishMemoryUpgradeStage : finishMemoryStage)(context, { projectRoot, runId, stage, bytes: prepared.memoryStage.bytes,
1903
+ return controlledReply((isDiagnosticFlow(prepared.memoryStage.flowId) ? finishDiagnosticStage : prepared.memoryStage.flowId === "mb-upgrade" ? finishMemoryUpgradeStage : finishMemoryStage)(context, { projectRoot, runId, stage, bytes: prepared.memoryStage.bytes,
1903
1904
  hookEventId: hookEventId ?? findRecentMatchingHookEvent(context, {
1904
1905
  projectId: requireProjectByRoot(context, resolveProjectRoot(projectRoot)).id,
1905
1906
  matchKey: entityReferenceVariants(runId).map(reference => stageLifecycleMatchKey("stage_finish", reference, stage, projectRoot)),
package/dist/cli.js CHANGED
@@ -53,6 +53,12 @@ if (args[0] === "upgrade" || args[0] === "flow" && args[1] === "mb-upgrade") {
53
53
  const { runMemoryUpgradeCli } = await import("./cli/memory-upgrade.js");
54
54
  process.exit(await runMemoryUpgradeCli(args, process.env));
55
55
  }
56
+ if (args[0] === "review" || args[0] === "flow" && ["mb-audit", "project-review"].includes(args[1] ?? "")) {
57
+ publishSetup('ingress_entered');
58
+ const { runDiagnosticCli } = await import("./cli/diagnostic.js");
59
+ publishSetup('imports_ready');
60
+ process.exit(await runDiagnosticCli(args, process.env));
61
+ }
56
62
  if (args.slice(0, 3).join(" ") === "runtime storage admit") {
57
63
  const { runMemoryStorageAdmissionCli } = await import("./cli/memory-storage-admission.js");
58
64
  process.exit(runMemoryStorageAdmissionCli(args));