@deksden-com/dd-flow-cli 0.9.0-beta.14 → 0.9.0-beta.140

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 (493) hide show
  1. package/CHANGELOG.md +733 -0
  2. package/README.md +143 -1
  3. package/dist/build-info.json +8 -8
  4. package/dist/cli/argument-values.js +38 -0
  5. package/dist/cli/command-inputs.js +376 -0
  6. package/dist/cli/command-metadata.js +197 -0
  7. package/dist/cli/command-option-descriptions.js +191 -0
  8. package/dist/cli/completion-cli.js +70 -0
  9. package/dist/cli/completion.js +57 -0
  10. package/dist/cli/error-output.js +52 -0
  11. package/dist/cli/executor-onboarding.js +76 -0
  12. package/dist/cli/flows.js +29 -0
  13. package/dist/cli/harnesses.js +84 -0
  14. package/dist/cli/help.js +328 -18
  15. package/dist/cli/hook-ingress.js +78 -0
  16. package/dist/cli/input-preparation.js +165 -0
  17. package/dist/cli/memory-flow-admission.js +44 -0
  18. package/dist/cli/memory-init-invocation.js +78 -0
  19. package/dist/cli/memory-init.js +240 -0
  20. package/dist/cli/memory-storage-admission.js +53 -0
  21. package/dist/cli/models.js +39 -0
  22. package/dist/cli/native-hook-ingress.js +80 -0
  23. package/dist/cli/operator-profiles.js +61 -0
  24. package/dist/cli/output-options.js +35 -0
  25. package/dist/cli/response-file.js +71 -0
  26. package/dist/cli/run-cli.js +1689 -545
  27. package/dist/cli/runtime-maintenance-diagnostics.js +46 -0
  28. package/dist/cli/runtime-maintenance-ingress.js +52 -0
  29. package/dist/cli/runtime-process.js +153 -0
  30. package/dist/cli/setup.js +149 -0
  31. package/dist/cli.js +71 -2
  32. package/dist/domain/stage-catalog.js +1 -1
  33. package/dist/domain/stage-handler-identities.js +20 -0
  34. package/dist/harness-runtime/DD-ZCODE.md +112 -0
  35. package/dist/harness-runtime/OPENCODE-LICENSE.txt +24 -0
  36. package/dist/harness-runtime/bin/dd-agy.mjs +56 -0
  37. package/dist/harness-runtime/bin/dd-codex.mjs +27 -0
  38. package/dist/harness-runtime/bin/dd-droid.mjs +34 -0
  39. package/dist/harness-runtime/bin/dd-grok.mjs +40 -0
  40. package/dist/harness-runtime/bin/dd-opencode.mjs +22 -0
  41. package/dist/harness-runtime/bin/dd-zcode.mjs +87 -0
  42. package/dist/harness-runtime/lib/adapter-timeouts.d.mts +5 -0
  43. package/dist/harness-runtime/lib/adapter-timeouts.mjs +34 -0
  44. package/dist/harness-runtime/lib/agy-hooks.d.mts +1 -0
  45. package/dist/harness-runtime/lib/agy-hooks.mjs +9 -0
  46. package/dist/harness-runtime/lib/daemon-observation.d.mts +2 -0
  47. package/dist/harness-runtime/lib/daemon-observation.mjs +163 -0
  48. package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
  49. package/dist/harness-runtime/lib/daemon-operations.mjs +371 -0
  50. package/dist/harness-runtime/lib/dd-agy-daemon.d.mts +6 -0
  51. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +724 -0
  52. package/dist/harness-runtime/lib/dd-agy.mjs +70 -0
  53. package/dist/harness-runtime/lib/dd-codex-daemon.d.mts +1 -0
  54. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +196 -0
  55. package/dist/harness-runtime/lib/dd-codex.d.mts +1 -0
  56. package/dist/harness-runtime/lib/dd-codex.mjs +817 -0
  57. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +129 -0
  58. package/dist/harness-runtime/lib/dd-droid.mjs +584 -0
  59. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +498 -0
  60. package/dist/harness-runtime/lib/dd-grok.mjs +177 -0
  61. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +507 -0
  62. package/dist/harness-runtime/lib/dd-opencode.mjs +354 -0
  63. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +750 -0
  64. package/dist/harness-runtime/lib/dd-zcode.mjs +1145 -0
  65. package/dist/harness-runtime/lib/delegation-instructions.d.mts +14 -0
  66. package/dist/harness-runtime/lib/delegation-instructions.mjs +157 -0
  67. package/dist/harness-runtime/lib/dispatch-fence.mjs +14 -0
  68. package/dist/harness-runtime/lib/driver-recovery.mjs +139 -0
  69. package/dist/harness-runtime/lib/droid-observation.mjs +66 -0
  70. package/dist/harness-runtime/lib/lease-renewal.d.mts +8 -0
  71. package/dist/harness-runtime/lib/lease-renewal.mjs +37 -0
  72. package/dist/harness-runtime/lib/lifecycle-command.d.mts +20 -0
  73. package/dist/harness-runtime/lib/lifecycle-command.mjs +273 -0
  74. package/dist/harness-runtime/lib/lifecycle-contract.d.mts +10 -0
  75. package/dist/harness-runtime/lib/lifecycle-contract.mjs +19 -0
  76. package/dist/harness-runtime/lib/managed-daemon.d.mts +13 -0
  77. package/dist/harness-runtime/lib/managed-daemon.mjs +654 -0
  78. package/dist/harness-runtime/lib/managed-flow-env.mjs +15 -0
  79. package/dist/harness-runtime/lib/model-observations.mjs +101 -0
  80. package/dist/harness-runtime/lib/native-children.d.mts +16 -0
  81. package/dist/harness-runtime/lib/native-children.mjs +113 -0
  82. package/dist/harness-runtime/lib/native-hook-command.d.mts +7 -0
  83. package/dist/harness-runtime/lib/native-hook-command.mjs +251 -0
  84. package/dist/harness-runtime/lib/native-request-proof.d.mts +2 -0
  85. package/dist/harness-runtime/lib/native-request-proof.mjs +65 -0
  86. package/dist/harness-runtime/lib/native-streams.mjs +45 -0
  87. package/dist/harness-runtime/lib/observation-clock.d.mts +18 -0
  88. package/dist/harness-runtime/lib/observation-clock.mjs +92 -0
  89. package/dist/harness-runtime/lib/opencode-plugin.mjs +85 -0
  90. package/dist/harness-runtime/lib/operation-context.mjs +5 -0
  91. package/dist/harness-runtime/lib/operation-errors.d.mts +14 -0
  92. package/dist/harness-runtime/lib/operation-errors.mjs +116 -0
  93. package/dist/harness-runtime/lib/operation-progress.d.mts +7 -0
  94. package/dist/harness-runtime/lib/operation-progress.mjs +114 -0
  95. package/dist/harness-runtime/lib/process-json.d.mts +5 -0
  96. package/dist/harness-runtime/lib/process-json.mjs +201 -0
  97. package/dist/harness-runtime/lib/process-snapshot.mjs +10 -0
  98. package/dist/harness-runtime/lib/provider-overload-policy.d.mts +13 -0
  99. package/dist/harness-runtime/lib/provider-overload-policy.mjs +138 -0
  100. package/dist/harness-runtime/lib/runner-events.mjs +200 -0
  101. package/dist/harness-runtime/lib/runner-lock.mjs +42 -0
  102. package/dist/harness-runtime/lib/session-settlement.mjs +30 -0
  103. package/dist/harness-runtime/lib/setup-progress.d.mts +9 -0
  104. package/dist/harness-runtime/lib/setup-progress.mjs +51 -0
  105. package/dist/harness-runtime/lib/tool-observations.d.mts +21 -0
  106. package/dist/harness-runtime/lib/tool-observations.mjs +228 -0
  107. package/dist/harness-runtime/lib/zcode-usage.mjs +55 -0
  108. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-1y4a697q.js +81 -0
  109. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-2ny4vxct.js +92 -0
  110. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-6mptkzbc.js +0 -0
  111. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-7x67cf1q.js +52 -0
  112. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-9rqn6x4v.js +16 -0
  113. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-9vdz0zwn.js +1661 -0
  114. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ecs5ftka.js +1466 -0
  115. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-frbwqjmf.js +145 -0
  116. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-gne7h7mq.js +50 -0
  117. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-gx9wnkzr.js +105 -0
  118. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-m2eywc8w.js +0 -0
  119. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-nwcpakzx.js +12 -0
  120. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-q4g3afnw.js +623 -0
  121. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-qav6mfdz.js +0 -0
  122. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-sxt88q69.js +12 -0
  123. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-t1xa31vm.js +39 -0
  124. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ve4aws36.js +103 -0
  125. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vmytr4q8.js +4 -0
  126. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vt9jbch8.js +36 -0
  127. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vyas3vgt.js +0 -0
  128. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-wjyeq25y.js +30 -0
  129. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-xgnj2w4v.js +20 -0
  130. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-y2etg5bs.js +9 -0
  131. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ydhz1zrh.js +238 -0
  132. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-z6qdwcsf.js +62 -0
  133. package/dist/harness-runtime/vendor/opencode-client/contract.d.ts +1 -0
  134. package/dist/harness-runtime/vendor/opencode-client/contract.js +12 -0
  135. package/dist/harness-runtime/vendor/opencode-client/effect/api/api.d.ts +2806 -0
  136. package/dist/harness-runtime/vendor/opencode-client/effect/api/api.js +0 -0
  137. package/dist/harness-runtime/vendor/opencode-client/effect/api.d.ts +4 -0
  138. package/dist/harness-runtime/vendor/opencode-client/effect/api.js +0 -0
  139. package/dist/harness-runtime/vendor/opencode-client/effect/client.d.ts +2530 -0
  140. package/dist/harness-runtime/vendor/opencode-client/effect/client.js +15 -0
  141. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client-error.d.ts +7 -0
  142. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client-error.js +6 -0
  143. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client.d.ts +2529 -0
  144. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client.js +8 -0
  145. package/dist/harness-runtime/vendor/opencode-client/effect/generated/index.d.ts +2 -0
  146. package/dist/harness-runtime/vendor/opencode-client/effect/generated/index.js +12 -0
  147. package/dist/harness-runtime/vendor/opencode-client/effect/index.d.ts +35 -0
  148. package/dist/harness-runtime/vendor/opencode-client/effect/index.js +75 -0
  149. package/dist/harness-runtime/vendor/opencode-client/effect/rpc.d.ts +20 -0
  150. package/dist/harness-runtime/vendor/opencode-client/effect/rpc.js +11 -0
  151. package/dist/harness-runtime/vendor/opencode-client/effect/service.d.ts +90 -0
  152. package/dist/harness-runtime/vendor/opencode-client/effect/service.js +187 -0
  153. package/dist/harness-runtime/vendor/opencode-client/package.json +84 -0
  154. package/dist/harness-runtime/vendor/opencode-client/promise/api.d.ts +21 -0
  155. package/dist/harness-runtime/vendor/opencode-client/promise/api.js +0 -0
  156. package/dist/harness-runtime/vendor/opencode-client/promise/client.d.ts +252 -0
  157. package/dist/harness-runtime/vendor/opencode-client/promise/client.js +14 -0
  158. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client-error.d.ts +8 -0
  159. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client-error.js +6 -0
  160. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client.d.ts +259 -0
  161. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client.js +7 -0
  162. package/dist/harness-runtime/vendor/opencode-client/promise/generated/index.d.ts +3 -0
  163. package/dist/harness-runtime/vendor/opencode-client/promise/generated/index.js +79 -0
  164. package/dist/harness-runtime/vendor/opencode-client/promise/generated/types.d.ts +8932 -0
  165. package/dist/harness-runtime/vendor/opencode-client/promise/generated/types.js +70 -0
  166. package/dist/harness-runtime/vendor/opencode-client/promise/index.d.ts +6 -0
  167. package/dist/harness-runtime/vendor/opencode-client/promise/index.js +83 -0
  168. package/dist/harness-runtime/vendor/opencode-client/promise/rpc.d.ts +32 -0
  169. package/dist/harness-runtime/vendor/opencode-client/promise/rpc.js +7 -0
  170. package/dist/harness-runtime/vendor/opencode-client/promise/service.d.ts +24 -0
  171. package/dist/harness-runtime/vendor/opencode-client/promise/service.js +187 -0
  172. package/dist/harness-runtime/vendor/opencode-client/pty-handoff.d.ts +12 -0
  173. package/dist/harness-runtime/vendor/opencode-client/pty-handoff.js +14 -0
  174. package/dist/harness-runtime/vendor/opencode-client/rpc-runtime.d.ts +20 -0
  175. package/dist/harness-runtime/vendor/opencode-client/rpc-runtime.js +14 -0
  176. package/dist/harness-runtime/vendor/opencode-client/service-contender.d.ts +37 -0
  177. package/dist/harness-runtime/vendor/opencode-client/service-contender.js +10 -0
  178. package/dist/harness-runtime/vendor/opencode-client/service-probe.d.ts +70 -0
  179. package/dist/harness-runtime/vendor/opencode-client/service-probe.js +16 -0
  180. package/dist/harness-runtime/vendor/opencode-client/service-timing.d.ts +12 -0
  181. package/dist/harness-runtime/vendor/opencode-client/service-timing.js +10 -0
  182. package/dist/harness-runtime/vendor/opencode-client/service-version.d.ts +2 -0
  183. package/dist/harness-runtime/vendor/opencode-client/service-version.js +6 -0
  184. package/dist/harness-runtime/vendor/opencode-client/service.d.ts +52 -0
  185. package/dist/harness-runtime/vendor/opencode-client/service.js +1 -0
  186. package/dist/harness-runtime/vendor/opencode-client/shared-events.d.ts +11 -0
  187. package/dist/harness-runtime/vendor/opencode-client/shared-events.js +8 -0
  188. package/dist/harness-runtime/vendor/opencode-client/solid/connection.d.ts +37 -0
  189. package/dist/harness-runtime/vendor/opencode-client/solid/connection.js +10 -0
  190. package/dist/harness-runtime/vendor/opencode-client/solid/data.d.ts +211 -0
  191. package/dist/harness-runtime/vendor/opencode-client/solid/data.js +18 -0
  192. package/dist/harness-runtime/vendor/opencode-client/solid/index.d.ts +3 -0
  193. package/dist/harness-runtime/vendor/opencode-client/solid/index.js +32 -0
  194. package/dist/harness-runtime/vendor/opencode-client/solid/pty.d.ts +22 -0
  195. package/dist/harness-runtime/vendor/opencode-client/solid/pty.js +8 -0
  196. package/dist/harness-runtime/vendor/opencode-plugin.mjs +3 -0
  197. package/dist/protocol/local-files.js +18 -9
  198. package/dist/runtime/context.js +8 -4
  199. package/dist/schemas/agent-profile.schema.json +1 -1
  200. package/dist/schemas/code-review-result.schema.json +1 -1
  201. package/dist/schemas/code-verification.schema.json +7 -5
  202. package/dist/schemas/code-work-batch.schema.json +2 -2
  203. package/dist/schemas/discovery-result.schema.json +184 -0
  204. package/dist/schemas/execution-bundle.schema.json +205 -0
  205. package/dist/schemas/execution-settings.schema.json +162 -0
  206. package/dist/schemas/flow-descriptor.schema.json +54 -0
  207. package/dist/schemas/flow-run.schema.json +28 -0
  208. package/dist/schemas/harness-catalog.schema.json +80 -0
  209. package/dist/schemas/mb-init-compose-result.schema.json +457 -0
  210. package/dist/schemas/mb-init-discover-result.schema.json +500 -0
  211. package/dist/schemas/mb-init-finish-result.schema.json +422 -0
  212. package/dist/schemas/mb-init-preflight-result.schema.json +367 -0
  213. package/dist/schemas/mb-init-review-result.schema.json +396 -0
  214. package/dist/schemas/mb-init-verify-result.schema.json +391 -0
  215. package/dist/schemas/memory-bootstrap-receipt-v2.schema.json +67 -0
  216. package/dist/schemas/memory-bootstrap-receipt.schema.json +354 -0
  217. package/dist/schemas/memory-composition-work-result.schema.json +38 -0
  218. package/dist/schemas/memory-repair-work-result.schema.json +49 -0
  219. package/dist/schemas/memory-review-correction-plan.schema.json +30 -0
  220. package/dist/schemas/memory-review-work-result.schema.json +43 -0
  221. package/dist/schemas/model-catalog.schema.json +153 -0
  222. package/dist/schemas/plan-review-decision.schema.json +3 -3
  223. package/dist/schemas/plan-review-result.schema.json +2 -2
  224. package/dist/schemas/profile-settings.schema.json +122 -0
  225. package/dist/schemas/release-manifest.schema.json +291 -0
  226. package/dist/schemas/run-control-receipt.schema.json +118 -0
  227. package/dist/schemas/run-control-request.schema.json +36 -0
  228. package/dist/schemas/run-engine-binding.schema.json +1 -1
  229. package/dist/schemas/vnext-protocol-plan.schema.json +2 -2
  230. package/dist/services/branch-context.js +3 -3
  231. package/dist/services/canon.js +11 -8
  232. package/dist/services/check-runner-evidence.js +168 -0
  233. package/dist/services/cleanup.js +133 -40
  234. package/dist/services/cli-operation-classifier.js +25 -62
  235. package/dist/services/cli-upgrade-allowlist.js +27 -0
  236. package/dist/services/code-checks.js +1156 -107
  237. package/dist/services/codex-hook-delivery.js +32 -0
  238. package/dist/services/command-context.js +80 -0
  239. package/dist/services/compatibility-preflight.js +13 -3
  240. package/dist/services/config.js +11 -8
  241. package/dist/services/continuation-outcome.js +20 -0
  242. package/dist/services/controller-child-capacity.js +13 -0
  243. package/dist/services/controller-child-observer.js +115 -0
  244. package/dist/services/controller-execution.js +88 -0
  245. package/dist/services/controller-fanout.js +202 -0
  246. package/dist/services/dashboard.js +143 -58
  247. package/dist/services/descriptor-coordinator-session.js +35 -0
  248. package/dist/services/descriptor-root-work.js +53 -0
  249. package/dist/services/descriptor-run-settlement.js +105 -0
  250. package/dist/services/descriptor-run-start.js +449 -0
  251. package/dist/services/descriptor-stage-coordinator.js +197 -0
  252. package/dist/services/descriptor-stage-settlement.js +222 -0
  253. package/dist/services/descriptor-stage-start.js +139 -0
  254. package/dist/services/discovery/agy-discovery.js +2 -0
  255. package/dist/services/discovery/codex-discovery.js +2 -0
  256. package/dist/services/discovery/droid-discovery.js +2 -0
  257. package/dist/services/discovery/grok-discovery.js +2 -0
  258. package/dist/services/discovery/opencode-discovery.js +2 -0
  259. package/dist/services/discovery/static.js +31 -0
  260. package/dist/services/discovery/zcode-discovery.js +2 -0
  261. package/dist/services/engines.js +294 -153
  262. package/dist/services/eval-snapshots.js +1215 -116
  263. package/dist/services/execution-bundles.js +263 -0
  264. package/dist/services/execution-policy.js +185 -0
  265. package/dist/services/executor-onboarding.js +56 -0
  266. package/dist/services/external-work-launch.js +308 -0
  267. package/dist/services/flow-catalog.js +21 -0
  268. package/dist/services/flow-descriptors.js +82 -0
  269. package/dist/services/frozen-engine.js +250 -0
  270. package/dist/services/generic-execution.js +164 -0
  271. package/dist/services/harness-adapter.js +355 -36
  272. package/dist/services/harness-config.js +2 -0
  273. package/dist/services/harness-discovery.js +29 -0
  274. package/dist/services/harness-doctor.js +75 -0
  275. package/dist/services/harness-probe-effects.js +51 -0
  276. package/dist/services/harness-version-probe.js +100 -0
  277. package/dist/services/hooks.js +771 -325
  278. package/dist/services/hybrid-contracts.js +483 -0
  279. package/dist/services/ids.js +29 -1
  280. package/dist/services/installation-lock.js +95 -0
  281. package/dist/services/installed-flow-source.js +183 -0
  282. package/dist/services/lanes.js +78 -65
  283. package/dist/services/lifecycle-command.js +1 -288
  284. package/dist/services/lifecycle-contract.js +1 -0
  285. package/dist/services/lifecycle-invocations.js +2007 -0
  286. package/dist/services/managed-daemon-binding.js +38 -0
  287. package/dist/services/managed-lease-monitor.js +133 -0
  288. package/dist/services/managed-processes.js +475 -55
  289. package/dist/services/memory-accepted-composition.js +77 -0
  290. package/dist/services/memory-baseline-preservation.js +65 -0
  291. package/dist/services/memory-completed-correction-wave.js +320 -0
  292. package/dist/services/memory-compose-handler.js +258 -0
  293. package/dist/services/memory-composition-acceptance.js +199 -0
  294. package/dist/services/memory-composition-graph.js +134 -0
  295. package/dist/services/memory-composition-registration.js +146 -0
  296. package/dist/services/memory-composition-work-custody.js +151 -0
  297. package/dist/services/memory-composition-work-results.js +96 -0
  298. package/dist/services/memory-corrected-review-acceptance.js +129 -0
  299. package/dist/services/memory-corrected-review-batch.js +30 -0
  300. package/dist/services/memory-corrected-review-findings.js +106 -0
  301. package/dist/services/memory-corrected-review-inputs.js +210 -0
  302. package/dist/services/memory-corrected-review-projection.js +78 -0
  303. package/dist/services/memory-corrected-review-receipt.js +170 -0
  304. package/dist/services/memory-corrected-review-registration.js +191 -0
  305. package/dist/services/memory-correction-check-receipt.js +219 -0
  306. package/dist/services/memory-correction-input-owner.js +6 -0
  307. package/dist/services/memory-discover-handler.js +104 -0
  308. package/dist/services/memory-finish-evidence.js +435 -0
  309. package/dist/services/memory-finish-handler.js +215 -0
  310. package/dist/services/memory-finish-integration.js +280 -0
  311. package/dist/services/memory-finish-readiness.js +122 -0
  312. package/dist/services/memory-git-integration.js +151 -0
  313. package/dist/services/memory-historical-corrected-review.js +206 -0
  314. package/dist/services/memory-historical-original-review.js +130 -0
  315. package/dist/services/memory-historical-repair-registration.js +99 -0
  316. package/dist/services/memory-historical-second-review.js +128 -0
  317. package/dist/services/memory-init-bootstrap-readiness.js +210 -0
  318. package/dist/services/memory-init-contexts.js +89 -0
  319. package/dist/services/memory-init-controller-start.js +84 -0
  320. package/dist/services/memory-init-engine-release.js +223 -0
  321. package/dist/services/memory-init-git-policy.js +136 -0
  322. package/dist/services/memory-init-lint-capability.js +185 -0
  323. package/dist/services/memory-init-native-capacity.js +296 -0
  324. package/dist/services/memory-init-preflight.js +330 -0
  325. package/dist/services/memory-init-project-bootstrap.js +599 -0
  326. package/dist/services/memory-init-request-candidate.js +157 -0
  327. package/dist/services/memory-init-request-input.js +66 -0
  328. package/dist/services/memory-init-resource-storage.js +125 -0
  329. package/dist/services/memory-init-run-allocation.js +81 -0
  330. package/dist/services/memory-init-run-bundle.js +98 -0
  331. package/dist/services/memory-init-start.js +181 -0
  332. package/dist/services/memory-init-startup-custody.js +80 -0
  333. package/dist/services/memory-init-startup-input.js +240 -0
  334. package/dist/services/memory-init-storage.js +81 -0
  335. package/dist/services/memory-init-stores.js +359 -0
  336. package/dist/services/memory-init-worktrunk-capability.js +178 -0
  337. package/dist/services/memory-layer-publication.js +611 -0
  338. package/dist/services/memory-layer.js +156 -0
  339. package/dist/services/memory-lint-config.js +85 -0
  340. package/dist/services/memory-native-review-copy.js +165 -0
  341. package/dist/services/memory-normal-flow-entry.js +75 -0
  342. package/dist/services/memory-ordinary-defaults.js +112 -0
  343. package/dist/services/memory-preflight-bootstrap-custody.js +115 -0
  344. package/dist/services/memory-preflight-handler.js +259 -0
  345. package/dist/services/memory-repair-current-view.js +299 -0
  346. package/dist/services/memory-repair-document-delta.js +451 -0
  347. package/dist/services/memory-repair-history.js +491 -0
  348. package/dist/services/memory-repair-physical-binding.js +312 -0
  349. package/dist/services/memory-repair-publication-files.js +152 -0
  350. package/dist/services/memory-repair-publication-intent.js +27 -0
  351. package/dist/services/memory-repair-publication-plan.js +160 -0
  352. package/dist/services/memory-repair-publication-source.js +225 -0
  353. package/dist/services/memory-repair-publication.js +794 -0
  354. package/dist/services/memory-repair-registration.js +239 -0
  355. package/dist/services/memory-repair-work-baseline.js +413 -0
  356. package/dist/services/memory-repair-work-completion.js +97 -0
  357. package/dist/services/memory-repair-work-custody.js +73 -0
  358. package/dist/services/memory-repair-work-results.js +89 -0
  359. package/dist/services/memory-repair-workspace.js +252 -0
  360. package/dist/services/memory-repair-writer-copy.js +735 -0
  361. package/dist/services/memory-result-binding.js +173 -0
  362. package/dist/services/memory-retained-review-authority.js +129 -0
  363. package/dist/services/memory-retained-review-results.js +70 -0
  364. package/dist/services/memory-retained-second-baseline.js +102 -0
  365. package/dist/services/memory-retained-second-repairs.js +156 -0
  366. package/dist/services/memory-review-acceptance.js +214 -0
  367. package/dist/services/memory-review-correction-plan.js +171 -0
  368. package/dist/services/memory-review-handler.js +585 -0
  369. package/dist/services/memory-review-inputs.js +200 -0
  370. package/dist/services/memory-review-receipt.js +180 -0
  371. package/dist/services/memory-review-registration.js +108 -0
  372. package/dist/services/memory-review-work-custody.js +124 -0
  373. package/dist/services/memory-review-work-input.js +72 -0
  374. package/dist/services/memory-review-work-results.js +73 -0
  375. package/dist/services/memory-second-corrected-review-inputs.js +165 -0
  376. package/dist/services/memory-second-correction-plan.js +76 -0
  377. package/dist/services/memory-second-repair-baseline.js +91 -0
  378. package/dist/services/memory-second-repair-launch.js +614 -0
  379. package/dist/services/memory-second-repair-registration.js +88 -0
  380. package/dist/services/memory-second-repair-source.js +187 -0
  381. package/dist/services/memory-second-review-acceptance.js +115 -0
  382. package/dist/services/memory-source-observations.js +117 -0
  383. package/dist/services/memory-stage-dispatch.js +28 -0
  384. package/dist/services/memory-stage-results.js +199 -0
  385. package/dist/services/memory-verification-acceptance.js +135 -0
  386. package/dist/services/memory-verification-checks.js +252 -0
  387. package/dist/services/memory-verification-receipt.js +159 -0
  388. package/dist/services/memory-verify-handler.js +135 -0
  389. package/dist/services/memory-work-result-preparation.js +63 -0
  390. package/dist/services/merge-queue.js +228 -149
  391. package/dist/services/merge-server.js +161 -35
  392. package/dist/services/migrations.js +37 -16
  393. package/dist/services/model-catalog.js +109 -0
  394. package/dist/services/model-discovery.js +46 -0
  395. package/dist/services/native-daemon-history.js +49 -0
  396. package/dist/services/native-session-control.js +39 -0
  397. package/dist/services/operator-profiles.js +236 -0
  398. package/dist/services/plan-runtime.js +26 -11
  399. package/dist/services/plans.js +28 -25
  400. package/dist/services/projects.js +37 -17
  401. package/dist/services/prompts.js +28 -17
  402. package/dist/services/protocols.js +163 -87
  403. package/dist/services/provider-capacity.js +385 -0
  404. package/dist/services/recovery-observation-budget.js +121 -0
  405. package/dist/services/recovery-snapshot-database.js +560 -0
  406. package/dist/services/release-archive-producer.js +52 -0
  407. package/dist/services/release-archive.js +143 -0
  408. package/dist/services/release-cache.js +142 -0
  409. package/dist/services/release-compatibility.js +18 -0
  410. package/dist/services/release-download.js +67 -0
  411. package/dist/services/release-envelope.js +104 -0
  412. package/dist/services/release-manifest.js +45 -0
  413. package/dist/services/release-payload.js +114 -0
  414. package/dist/services/release-producer.js +63 -0
  415. package/dist/services/release-provenance.js +160 -0
  416. package/dist/services/release-registry.js +66 -0
  417. package/dist/services/release-source.js +194 -0
  418. package/dist/services/repair-intents.js +42 -0
  419. package/dist/services/review-copy.js +165 -0
  420. package/dist/services/review-deferral.js +23 -0
  421. package/dist/services/run-control-receipt.js +56 -0
  422. package/dist/services/run-control-worker.js +411 -0
  423. package/dist/services/run-control.js +1056 -0
  424. package/dist/services/run-controller-adapter.js +399 -0
  425. package/dist/services/run-controller-capture.js +186 -0
  426. package/dist/services/run-controller-process.js +208 -0
  427. package/dist/services/run-controller-recovery.js +289 -0
  428. package/dist/services/run-controller-state.js +97 -0
  429. package/dist/services/run-controller.js +1205 -0
  430. package/dist/services/run-engine-bindings.js +1 -1
  431. package/dist/services/run-fork.js +147 -0
  432. package/dist/services/run-health.js +149 -0
  433. package/dist/services/run-observations.js +152 -0
  434. package/dist/services/run-projection.js +22 -64
  435. package/dist/services/run-recovery-guard.js +77 -0
  436. package/dist/services/run-recovery-runtime.js +112 -0
  437. package/dist/services/run-recovery.js +489 -0
  438. package/dist/services/run-start-custody.js +177 -0
  439. package/dist/services/runs.js +1019 -247
  440. package/dist/services/runtime-budget.js +430 -0
  441. package/dist/services/runtime-command.js +43 -0
  442. package/dist/services/runtime-scope-capture.js +52 -0
  443. package/dist/services/runtime-scope-control.js +468 -0
  444. package/dist/services/runtime-scope-resume.js +599 -0
  445. package/dist/services/runtime-scope-stop.js +210 -0
  446. package/dist/services/runtime-scope-worker.js +287 -0
  447. package/dist/services/runtime-service.js +96 -0
  448. package/dist/services/schema-validation.js +10 -8
  449. package/dist/services/scope-writer-cleanup.js +27 -0
  450. package/dist/services/sessions.js +35 -69
  451. package/dist/services/setup.js +377 -0
  452. package/dist/services/stage-blocker.js +17 -7
  453. package/dist/services/stage-context.js +193 -19
  454. package/dist/services/stage-handlers.js +74 -0
  455. package/dist/services/stage-lifecycle.js +100 -108
  456. package/dist/services/stage-pause.js +89 -75
  457. package/dist/services/stage-report-renderer.js +234 -6
  458. package/dist/services/stage-work-graph.js +52 -0
  459. package/dist/services/tooling.js +32 -23
  460. package/dist/services/usage.js +49 -101
  461. package/dist/services/verification-matrix.js +374 -0
  462. package/dist/services/vnext-code-review.js +223 -88
  463. package/dist/services/vnext-code.js +327 -226
  464. package/dist/services/vnext-contracts.js +10 -1
  465. package/dist/services/vnext-execution-profile.js +80 -5
  466. package/dist/services/vnext-fanout.js +311 -19
  467. package/dist/services/vnext-merge.js +332 -111
  468. package/dist/services/vnext-plan-review.js +279 -127
  469. package/dist/services/vnext-plan.js +229 -104
  470. package/dist/services/vnext-protocolize.js +411 -86
  471. package/dist/services/vnext-specify.js +119 -81
  472. package/dist/services/vnext-workspace-policy.js +7 -2
  473. package/dist/services/work-registry.js +1627 -199
  474. package/dist/services/workspace-bootstrap.js +52 -15
  475. package/dist/services/workspace-files.js +85 -0
  476. package/dist/services/worktrees.js +98 -44
  477. package/dist/shared/entity-references.js +8 -0
  478. package/dist/shared/errors.js +17 -0
  479. package/dist/storage/agent-output.js +26 -0
  480. package/dist/storage/database.js +891 -39
  481. package/dist/storage/foreign-key-transaction.js +39 -0
  482. package/dist/storage/immutable-input.js +45 -0
  483. package/dist/storage/import-guard.js +102 -0
  484. package/dist/storage/native-cleanup.js +52 -0
  485. package/dist/storage/paths.js +17 -4
  486. package/dist/storage/projection-lock.js +73 -0
  487. package/dist/storage/work-references.js +23 -0
  488. package/dist/storage/writer-contract.js +89 -0
  489. package/dist/storage/writer-migration.js +85 -0
  490. package/package.json +43 -13
  491. package/tools/create-canon-release.mjs +33 -0
  492. package/tools/produce-hybrid-release.mjs +41 -0
  493. package/tools/repair-paused-run-status.mjs +84 -0
@@ -166,11 +166,11 @@ function gitFacts(workspacePath) {
166
166
  if (!fs.existsSync(workspacePath)) {
167
167
  return { status: "missing_workspace", actual_branch: null, dirty: null, path: workspacePath };
168
168
  }
169
- const branch = spawnSync("git", ["-C", workspacePath, "rev-parse", "--abbrev-ref", "HEAD"], { encoding: "utf8" });
169
+ const branch = spawnSync("git", ["-C", workspacePath, "rev-parse", "--abbrev-ref", "HEAD"], { encoding: "utf8", timeout: 5000 });
170
170
  if (branch.status !== 0) {
171
- return { status: "not_git_checkout", actual_branch: null, dirty: null, path: workspacePath };
171
+ return { status: branch.error || branch.signal ? "observation_unavailable" : "not_git_checkout", actual_branch: null, dirty: null, path: workspacePath };
172
172
  }
173
- const status = spawnSync("git", ["-C", workspacePath, "status", "--porcelain"], { encoding: "utf8" });
173
+ const status = spawnSync("git", ["-C", workspacePath, "status", "--porcelain"], { encoding: "utf8", timeout: 5000 });
174
174
  return {
175
175
  status: "ok",
176
176
  actual_branch: branch.stdout.trim() || null,
@@ -1,27 +1,30 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { spawnSync } from "node:child_process";
4
+ import { AppError } from "../shared/errors.js";
5
+ import { prepareError } from "../cli/input-preparation.js";
4
6
  const canonRootKey = "canon.root";
5
- export function registerCanonRoot(context, input) {
7
+ export function prepareCanonRegistration(context, input) {
8
+ if (typeof input.root !== "string" || !input.root.trim())
9
+ return prepareError(new AppError("validation", "Canon root must be a non-empty path", 2));
6
10
  const resolution = resolveCanonRoot(context, { explicitRoot: input.root, allowRegistered: false });
7
11
  if (!resolution.ok || !resolution.canon) {
8
- return {
9
- ...resolution,
10
- action: "register",
11
- exit_code: 1
12
- };
12
+ return prepareError(new AppError("validation", "Canon root cannot be registered", 2, { root: input.root, blockers: resolution.blockers, candidates: resolution.candidates, bootstrap: resolution.bootstrap }));
13
13
  }
14
+ return resolution.canon;
15
+ }
16
+ export function registerCanonRoot(context, input, canon = prepareCanonRegistration(context, input)) {
14
17
  const now = context.now();
15
18
  context.db.run(`INSERT INTO runtime_config (key, value_json, created_at, updated_at)
16
19
  VALUES (?, ?, ?, ?)
17
20
  ON CONFLICT(key) DO UPDATE SET
18
21
  value_json = excluded.value_json,
19
- updated_at = excluded.updated_at`, [canonRootKey, JSON.stringify({ root: resolution.canon.root }), now, now]);
22
+ updated_at = excluded.updated_at`, [canonRootKey, JSON.stringify({ root: canon.root }), now, now]);
20
23
  return {
21
24
  ok: true,
22
25
  action: "register",
23
26
  canon: {
24
- ...resolution.canon,
27
+ ...canon,
25
28
  source: "registered"
26
29
  },
27
30
  db_path: context.db.path
@@ -0,0 +1,168 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { parse } from "shell-quote";
4
+ import { AppError } from "../shared/errors.js";
5
+ import { validateObservationDuration } from "../harness-runtime/lib/observation-clock.mjs";
6
+ function object(value) { return Boolean(value) && typeof value === "object" && !Array.isArray(value); }
7
+ function boundedString(value, bytes) { return typeof value === "string" && Buffer.byteLength(value) <= bytes; }
8
+ export function runnerFailureValid(value) {
9
+ if (!object(value) || typeof value.category !== "string" || !["assertion", "tooling", "unclassified"].includes(value.category))
10
+ return false;
11
+ for (const key of ["code", "message", "file", "test_id", "native_code", "classification_reason"])
12
+ if (value[key] !== undefined && !boundedString(value[key], 4096))
13
+ return false;
14
+ if (value.stack !== undefined && !boundedString(value.stack, 16384) || value.origin !== undefined && (typeof value.origin !== "string" || !["candidate", "tooling", "unknown"].includes(value.origin)) || value.phase !== undefined && (typeof value.phase !== "string" || !["test", "setup", "teardown", "collection", "runner"].includes(value.phase)))
15
+ return false;
16
+ if (value.repair_evidence !== undefined) {
17
+ const proof = value.repair_evidence;
18
+ if (!object(proof) || !boundedString(proof.rule, 128) || !boundedString(proof.native_code, 128) || !boundedString(proof.file, 4096) || !object(proof.world))
19
+ return false;
20
+ const world = proof.world;
21
+ if (world.schema_id !== "dd-tasks/test-world-ready@1" || !boundedString(world.invocation_id, 4096) || typeof world.world_id !== "string" || !/^[a-f0-9]{32}$/.test(world.world_id) || typeof world.migrations_sha256 !== "string" || !/^[a-f0-9]{64}$/.test(world.migrations_sha256) || world.ready !== true || world.setup_completed !== true || !boundedString(world.role, 128) || !world.role)
22
+ return false;
23
+ }
24
+ return true;
25
+ }
26
+ function candidateFile(root, file) {
27
+ if (!root || !path.isAbsolute(file))
28
+ return false;
29
+ try {
30
+ const relative = path.relative(fs.realpathSync(root), fs.realpathSync(file));
31
+ return relative !== "" && relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative) && fs.statSync(file).isFile();
32
+ }
33
+ catch {
34
+ return false;
35
+ }
36
+ }
37
+ export function candidateFailureFiles(evidence, root) {
38
+ return [...new Set((evidence?.failures ?? []).flatMap(failure => failure.file && candidateFile(root, failure.file) ? [failure.file] : []))];
39
+ }
40
+ function candidateFailure(failure, evidence, root, capturedFiles) {
41
+ const owned = (file) => capturedFiles === undefined ? candidateFile(root, file) : capturedFiles.includes(file);
42
+ if (failure.origin === "tooling" || failure.origin === "unknown")
43
+ return false;
44
+ if (failure.category === "assertion")
45
+ return failure.origin === undefined || failure.origin === "candidate" && failure.file !== undefined && owned(failure.file);
46
+ const proof = failure.repair_evidence;
47
+ return failure.category === "unclassified" && failure.origin === "candidate" && ["test", "setup"].includes(failure.phase ?? "") && proof?.rule === "candidate_sql_integrity" && ["23502", "23503", "23505", "23514"].includes(proof.native_code) && failure.native_code === proof.native_code && failure.file === proof.file && proof.world.invocation_id === evidence.binding.invocation_id && owned(proof.file);
48
+ }
49
+ export function runnerObservationDurations(environment) {
50
+ // Both observers leave the launcher its bounded physical cleanup tail.
51
+ return { startupMs: validateObservationDuration(validateObservationDuration(Number(environment.DD_FLOW_TEST_STARTUP_SILENCE_MS ?? 120_000)) + 10_000),
52
+ executionMs: validateObservationDuration(validateObservationDuration(Number(environment.DD_FLOW_TEST_EXECUTION_SILENCE_MS ?? 600_000)) + 10_000) };
53
+ }
54
+ /** Baseline uses argv rather than a shell alias. Recognize only a literal,
55
+ * unique execution contract; never guess shell expansion or compound syntax. */
56
+ export function runnerContractForArgv(profile, command, args) {
57
+ if (profile.schema_id !== "dd-flow/code-check-profile@7" || !profile.runner_evidence_by_alias)
58
+ return null;
59
+ if (!runnerContractsValid(profile.runner_evidence_by_alias))
60
+ throw new AppError("invalid_code_check_profile", "Invalid baseline runner evidence contract", 2);
61
+ const matches = [];
62
+ for (const [alias, contract] of Object.entries(profile.runner_evidence_by_alias)) {
63
+ const source = profile.aliases?.[alias];
64
+ if (typeof source !== "string")
65
+ throw new AppError("invalid_code_check_profile", "Baseline runner evidence references an unknown alias", 2, { alias });
66
+ let expansion = false;
67
+ const tokens = parse(source, () => { expansion = true; return ""; });
68
+ if (!expansion && tokens.every(token => typeof token === "string") && JSON.stringify(tokens) === JSON.stringify([command, ...args]))
69
+ matches.push({ alias, command: source, contract });
70
+ }
71
+ if (matches.some(match => JSON.stringify(match.contract) !== JSON.stringify(matches[0].contract)))
72
+ throw new AppError("runner_evidence_ambiguous", "Baseline argv matches conflicting runner evidence contracts", 2, { aliases: matches.map(match => match.alias) });
73
+ return matches[0] ?? null;
74
+ }
75
+ /** Shared by CODE and EVAL baseline. Logs, repeated cursors and foreign records
76
+ * are diagnostic bytes, not runner progress. Partial final lines await append. */
77
+ export function readRunnerProgress(file, invocationId, lastSequence = 0, now = Date.now()) {
78
+ try {
79
+ const stat = fs.lstatSync(file);
80
+ if (!stat.isFile() || stat.size > 1024 * 1024)
81
+ return [];
82
+ const accepted = [];
83
+ for (const line of fs.readFileSync(file, "utf8").split("\n").slice(0, -1)) {
84
+ let value;
85
+ try {
86
+ value = JSON.parse(line);
87
+ }
88
+ catch {
89
+ continue;
90
+ }
91
+ if (!value || typeof value !== "object" || Array.isArray(value) || value.schema_id !== "dd-flow/check-runner-progress@1" || value.invocation_id !== invocationId
92
+ || !Number.isSafeInteger(value.sequence) || value.sequence <= lastSequence || typeof value.at !== "string" || !Number.isFinite(Date.parse(value.at)) || Date.parse(value.at) > now
93
+ || !["startup", "execution"].includes(value.phase)
94
+ || !["setup_step_completed", "run_started", "module_collected", "test_started", "test_completed", "step_completed", "run_completed"].includes(value.event))
95
+ continue;
96
+ accepted.push(value);
97
+ lastSequence = value.sequence;
98
+ }
99
+ return accepted;
100
+ }
101
+ catch {
102
+ return [];
103
+ }
104
+ }
105
+ export function runnerContractsValid(value) {
106
+ if (value === undefined)
107
+ return true;
108
+ if (!value || typeof value !== "object" || Array.isArray(value))
109
+ return false;
110
+ return Object.entries(value).every(([alias, raw]) => {
111
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
112
+ return false;
113
+ const contract = raw;
114
+ return alias.startsWith("@check/") && contract.schema_id === "dd-flow/check-runner-evidence@1" && typeof contract.path === "string" && contract.path.length > 0 && contract.path.split("/")[0] !== "engine-outcome.json" && !contract.path.includes("\\") && !path.isAbsolute(contract.path) && !contract.path.split("/").some(part => part === ".." || part === "" || part === ".");
115
+ });
116
+ }
117
+ export function readRunnerEvidence(root, contract, binding) {
118
+ try {
119
+ let current = root;
120
+ if (!fs.lstatSync(current).isDirectory())
121
+ return null;
122
+ for (const part of contract.path.split(/[\\/]/)) {
123
+ current = path.join(current, part);
124
+ if (fs.lstatSync(current).isSymbolicLink())
125
+ return null;
126
+ }
127
+ const stat = fs.statSync(current);
128
+ if (!stat.isFile() || stat.size > 1024 * 1024)
129
+ return null;
130
+ const evidence = JSON.parse(fs.readFileSync(current, "utf8"));
131
+ if (evidence.schema_id !== contract.schema_id || !evidence.binding || Object.entries(binding).some(([key, value]) => evidence.binding[key] !== value)
132
+ || typeof evidence.runner?.name !== "string" || !evidence.runner.name || typeof evidence.runner.version !== "string" || !evidence.runner.version || typeof evidence.completed !== "boolean" || !evidence.counts
133
+ || ![evidence.counts.passed, evidence.counts.failed, evidence.counts.skipped].every(n => Number.isSafeInteger(n) && n >= 0)
134
+ || !Array.isArray(evidence.failures) || evidence.failures.length > 64 || evidence.failures.some(f => !runnerFailureValid(f))
135
+ || evidence.counts.failed > evidence.failures.length || evidence.observation_unknown !== undefined && typeof evidence.observation_unknown !== "boolean")
136
+ return null;
137
+ return evidence;
138
+ }
139
+ catch {
140
+ return null;
141
+ }
142
+ }
143
+ export function runnerEvidencePass(evidence) { return Boolean(evidence?.completed && !evidence.observation_unknown && evidence.counts.failed === 0 && evidence.failures.length === 0); }
144
+ export function checkRepairDisposition(receipt) {
145
+ if (receipt.status === "running" || receipt.status === "aborted" || receipt.runner_evidence?.observation_unknown)
146
+ return "settlement_required";
147
+ if (receipt.status === "passed")
148
+ return "passed";
149
+ if (receipt.execution_failures?.length || receipt.mutation_paths?.length || receipt.before_fingerprint !== receipt.after_fingerprint || receipt.diagnostic_evidence_valid === false)
150
+ return "tooling_blocker";
151
+ if (!Number.isSafeInteger(receipt.exit_code) || receipt.exit_code === 0)
152
+ return "unclassified";
153
+ const evidence = receipt.runner_evidence;
154
+ if (!evidence || !Array.isArray(evidence.failures) || !evidence.failures.length || receipt.exit_code === 0 || evidence.failures.some(f => !f || !["assertion", "tooling", "unclassified"].includes(f.category)))
155
+ return "unclassified";
156
+ if (evidence.failures.some(f => f.category === "tooling"))
157
+ return "tooling_blocker";
158
+ if (!evidence.completed)
159
+ return "unclassified";
160
+ if (evidence.failures.some(f => !runnerFailureValid(f) || !candidateFailure(f, evidence, receipt.candidate_root, receipt.candidate_files)))
161
+ return "unclassified";
162
+ return "source_repair";
163
+ }
164
+ export function assertSourceRepairEligible(receipts) {
165
+ const blockers = receipts.filter(receipt => checkRepairDisposition(receipt) !== "source_repair");
166
+ if (!receipts.length || blockers.length)
167
+ throw new AppError("check_gate_blocked", "Checks require tooling diagnosis or settlement before product repair; retained nonzero is not a candidate-source verdict", 2, { failures: receipts, dispositions: receipts.map(receipt => ({ check_receipt_id: receipt.id, disposition: checkRepairDisposition(receipt) })), outcome: "blocked", recoverable: false, instruction: "Inspect retained runner evidence. Do not change product code to repair tooling, unknown observation or unclassified failure. Retry only after establishing and correcting the cause." });
168
+ }
@@ -2,13 +2,13 @@ import fs from "node:fs";
2
2
  import { spawnSync } from "node:child_process";
3
3
  import path from "node:path";
4
4
  import { AppError } from "../shared/errors.js";
5
- import { parseJsonObject } from "../shared/json.js";
6
- import { ensureReadableFile } from "../storage/database.js";
7
- import { projectCheckoutRoot, resolveProjectRoot } from "../storage/paths.js";
5
+ import { prepareJsonFile } from "../cli/input-preparation.js";
6
+ import { projectCheckoutRoot, resolveProjectRoot, runtimeStateJsonPath } from "../storage/paths.js";
8
7
  import { appendAudit } from "./audit.js";
9
8
  import { requireProjectByRoot } from "./projects.js";
10
- import { protocolRunDiagnostics, readProtocolRuntimeState, requireProtocol } from "./protocols.js";
9
+ import { persistProtocolState, prepareProtocolRuntimeRepair, protocolRunDiagnostics, readProtocolRuntimeState, requireProtocol } from "./protocols.js";
11
10
  import { publicSessionIdentity } from "./session-identity.js";
11
+ import { publishRunProjections } from "./runs.js";
12
12
  const actionKinds = new Set([
13
13
  "repair_protocol_state",
14
14
  "cancel_queue_job",
@@ -25,7 +25,8 @@ export function cleanupScan(context, input) {
25
25
  const protocols = context.db.all("SELECT id, status, stage, state_path FROM protocols WHERE project_id = ? ORDER BY id ASC", [project.id]);
26
26
  for (const protocol of protocols) {
27
27
  const terminal = ["closed", "cancelled"].includes(protocol.status) || ["closed", "cancelled"].includes(protocol.stage);
28
- if (!fs.existsSync(protocol.state_path)) {
28
+ const requiresMigration = protocol.state_path !== runtimeStateJsonPath(context.ddFlowHome, project.id, protocol.id);
29
+ if (!fs.existsSync(protocol.state_path) || requiresMigration) {
29
30
  pushFinding(findings, actions, "missing_protocol_runtime_state", "warning", {
30
31
  protocol_id: protocol.id,
31
32
  path: protocol.state_path,
@@ -43,6 +44,8 @@ export function cleanupScan(context, input) {
43
44
  }
44
45
  }
45
46
  const fullProtocol = requireProtocol(context, protocol.id, project.id);
47
+ if (requiresMigration)
48
+ continue;
46
49
  const state = readProtocolRuntimeState(context, fullProtocol).state;
47
50
  const diagnostics = protocolRunDiagnostics(context, fullProtocol, state).diagnostics;
48
51
  for (const diagnostic of diagnostics.filter((entry) => entry.code === "protocol_run_stage_mismatch")) {
@@ -170,36 +173,55 @@ export function cleanupScan(context, input) {
170
173
  plan: { project_root: project.root, project_id: project.id, actions }
171
174
  };
172
175
  }
173
- export function cleanupApply(context, input) {
174
- const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
176
+ export function prepareCleanupApply(context, input) {
175
177
  const reason = input.reason.trim();
176
178
  if (!reason) {
177
179
  throw new AppError("validation", "cleanup apply requires --reason", 2);
178
180
  }
179
- ensureReadableFile(input.planFile);
180
- const plan = validateCleanupPlan(parseJsonObject(fs.readFileSync(input.planFile, "utf8"), input.planFile), project);
181
+ const value = prepareJsonFile(input.planFile, "cleanup plan").value;
182
+ if (!value || typeof value !== "object" || Array.isArray(value))
183
+ throw new AppError("validation", "cleanup plan must be an object", 2);
184
+ const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
185
+ const plan = validateCleanupPlan(value, project);
181
186
  validateNoDestructiveSurprises(context, plan, input.force);
187
+ return { project, reason, plan };
188
+ }
189
+ export function cleanupApply(context, input) {
190
+ const { project, reason, plan } = input.prepared ?? prepareCleanupApply(context, input);
191
+ // A supplied plan can outlive its initial inspection. Refresh external
192
+ // workspace evidence before reserving the writer, then recheck SQL below.
193
+ if (input.prepared)
194
+ validateNoDestructiveSurprises(context, plan, input.force);
195
+ const prepared = prepareCleanupEffects(context, plan);
182
196
  const now = context.now();
183
197
  const results = [];
184
- context.db.exec("BEGIN IMMEDIATE");
198
+ let executing = false, committed = false;
185
199
  try {
186
- for (const action of plan.actions) {
187
- results.push(applyAction(context, action, reason, input.force, now));
188
- }
189
- const changed = results.filter((result) => result.changed).length;
190
- if (changed > 0) {
191
- appendAudit(context, {
192
- projectId: project.id,
193
- eventType: "cleanup.applied",
194
- reason,
195
- forced: input.force,
196
- payload: { project_id: project.id, action_count: plan.actions.length, changed, results }
197
- });
198
- }
199
- context.db.exec("COMMIT");
200
+ context.db.writeTransaction(() => {
201
+ context.db.afterCommit(() => { committed = true; });
202
+ validateNoDestructiveSurprises(context, plan, input.force, false);
203
+ prepared.assertUnchanged();
204
+ for (const action of plan.actions) {
205
+ executing = true;
206
+ results.push(applyAction(context, action, reason, input.force, now, prepared));
207
+ }
208
+ const changed = results.filter((result) => result.changed).length;
209
+ if (changed > 0) {
210
+ appendAudit(context, {
211
+ projectId: project.id,
212
+ eventType: "cleanup.applied",
213
+ reason,
214
+ forced: input.force,
215
+ payload: { project_id: project.id, action_count: plan.actions.length, changed, results }
216
+ });
217
+ }
218
+ });
200
219
  }
201
220
  catch (error) {
202
- context.db.exec("ROLLBACK");
221
+ if (committed || error instanceof AppError && error.details.effect === "committed")
222
+ throw new AppError(error instanceof AppError ? error.code : "cleanup_publication_failed", error instanceof Error ? error.message : "Committed cleanup publication failed", error instanceof AppError ? error.exitCode : 1, { ...(error instanceof AppError ? error.details : {}), effect: "committed", publication_pending: true, attempted_results: results, cause: String(error) });
223
+ if (executing)
224
+ throw new AppError("cleanup_apply_failed", "Cleanup failed after execution began; inspect retained artifacts before retrying", 1, { effect: "unknown", database_rolled_back: true, attempted_results: results, cause: error instanceof Error ? error.message : String(error), ...(error instanceof AppError ? { cause_code: error.code } : {}) });
203
225
  throw error;
204
226
  }
205
227
  return {
@@ -248,18 +270,36 @@ function validateCleanupPlan(value, project) {
248
270
  throw new AppError("validation", "close_flow_session requires a native session identity", 2);
249
271
  }
250
272
  }
273
+ const requiredField = action.kind === "discard_stale_run" ? "run_id"
274
+ : ["repair_protocol_state", "cancel_queue_job", "close_worktree_record"].includes(String(action.kind)) ? "protocol_id" : undefined;
275
+ if (requiredField && (typeof action[requiredField] !== "string" || !action[requiredField].trim()))
276
+ throw new AppError("validation", `cleanup ${String(action.kind)} requires ${requiredField}`, 2);
277
+ const numericField = action.kind === "expire_lane_lock" ? "lock_id" : action.kind === "expire_lane_waiter" ? "waiter_id" : undefined;
278
+ if (numericField && (!Number.isSafeInteger(action[numericField]) || action[numericField] < 1))
279
+ throw new AppError("validation", `cleanup ${String(action.kind)} requires a positive integer ${numericField}`, 2);
280
+ if (action.worktree_path !== undefined && (typeof action.worktree_path !== "string" || !path.isAbsolute(action.worktree_path)))
281
+ throw new AppError("validation", "cleanup worktree_path must be an absolute path", 2);
251
282
  return action;
252
283
  });
253
284
  return { project_root: project.root, project_id: project.id, actions };
254
285
  }
255
- function validateNoDestructiveSurprises(context, plan, force) {
256
- const destructive = plan.actions.find((action) => action.kind === "close_worktree_record" && action.worktree_path && dirtyGit(action.worktree_path));
286
+ function validateNoDestructiveSurprises(context, plan, force, inspectFiles = true) {
287
+ for (const action of plan.actions) {
288
+ if (action.kind === "repair_protocol_state")
289
+ requireProtocol(context, action.protocol_id, action.project_id);
290
+ if (action.kind === "discard_stale_run") {
291
+ const run = context.db.get("SELECT index_json, status FROM runs WHERE project_id = ? AND id = ?", [action.project_id, action.run_id]);
292
+ if (run && ["running", "discarded"].includes(run.status))
293
+ cleanupRunIndex(run.index_json);
294
+ }
295
+ }
296
+ const destructive = inspectFiles && plan.actions.find((action) => action.kind === "close_worktree_record" && action.worktree_path && dirtyGit(action.worktree_path));
257
297
  if (destructive && !force) {
258
298
  throw new AppError("worktree_dirty", "cleanup apply refuses dirty worktree actions without --force --reason", 1, {
259
299
  path: destructive.worktree_path
260
300
  });
261
301
  }
262
- const unsafeWorktree = plan.actions.find((action) => action.kind === "close_worktree_record" && action.worktree_path && !safeCleanupWorktreePath(context, action.project_id, action.worktree_path));
302
+ const unsafeWorktree = inspectFiles && plan.actions.find((action) => action.kind === "close_worktree_record" && action.worktree_path && !safeCleanupWorktreePath(context, action.project_id, action.worktree_path));
263
303
  if (unsafeWorktree) {
264
304
  throw new AppError("worktree_cleanup_unsafe", "cleanup apply refuses unsafe worktree path", 1, {
265
305
  path: unsafeWorktree.worktree_path
@@ -278,12 +318,53 @@ function validateNoDestructiveSurprises(context, plan, force) {
278
318
  });
279
319
  }
280
320
  }
281
- function applyAction(context, action, reason, force, now) {
321
+ function prepareCleanupEffects(context, plan) {
322
+ const checks = [], files = new Map();
323
+ const repairs = new Map();
324
+ const read = (sql, params) => {
325
+ const value = context.db.get(sql, params), encoded = JSON.stringify(value);
326
+ checks.push(() => { if (JSON.stringify(context.db.get(sql, params)) !== encoded)
327
+ throw new AppError("cleanup_plan_changed", "Cleanup target changed during preparation", 1, { effect: "no_effect" }); });
328
+ return value;
329
+ };
330
+ const remember = (file) => { if (file !== undefined)
331
+ files.set(file, fs.existsSync(file)); };
332
+ for (const action of plan.actions) {
333
+ if (action.kind === "repair_protocol_state") {
334
+ read("SELECT * FROM protocols WHERE project_id = ? AND id = ?", [action.project_id, action.protocol_id]);
335
+ const protocol = requireProtocol(context, action.protocol_id, action.project_id);
336
+ remember(protocol.state_path);
337
+ repairs.set(action, prepareProtocolRuntimeRepair(context, protocol));
338
+ }
339
+ if (action.kind === "discard_stale_run") {
340
+ const run = read("SELECT * FROM runs WHERE project_id = ? AND id = ?", [action.project_id, action.run_id]);
341
+ if (run) {
342
+ remember(run.runtime_path);
343
+ remember(run.run_dir);
344
+ remember(run.workspace_root);
345
+ }
346
+ }
347
+ if (action.kind === "close_worktree_record") {
348
+ const record = read("SELECT * FROM worktree_records WHERE project_id = ? AND protocol_id = ?", [action.project_id, action.protocol_id]);
349
+ remember(action.worktree_path ?? record?.worktree_path);
350
+ }
351
+ }
352
+ return { repairs, assertUnchanged: () => { for (const check of checks)
353
+ check(); }, exists: (file) => {
354
+ if (!files.has(file))
355
+ throw new AppError("cleanup_plan_changed", "Cleanup target has no prepared filesystem observation", 1, { path: file, effect: "no_effect" });
356
+ return files.get(file);
357
+ } };
358
+ }
359
+ function applyAction(context, action, reason, force, now, prepared) {
282
360
  if (action.kind === "repair_protocol_state" && action.protocol_id) {
283
361
  const protocol = requireProtocol(context, action.protocol_id, action.project_id);
284
362
  const beforePath = protocol.state_path;
285
- const existed = fs.existsSync(beforePath);
286
- readProtocolRuntimeState(context, protocol);
363
+ const existed = prepared.exists(beforePath), repair = prepared.repairs.get(action);
364
+ context.db.run("UPDATE protocols SET state_path = ?, plan_path = ? WHERE project_id = ? AND id = ?", [repair.statePath, repair.planPath, action.project_id, action.protocol_id]);
365
+ persistProtocolState(context, { ...protocol, state_path: repair.statePath, plan_path: repair.planPath }, repair.state);
366
+ protocol.state_path = repair.statePath;
367
+ appendAudit(context, { protocolId: protocol.id, projectId: protocol.project_id, eventType: "protocol.runtime_state_repaired", payload: { protocol_id: protocol.id, path: repair.statePath, repaired_from: repair.previousStatePath, source: "sqlite" } });
287
368
  return {
288
369
  kind: action.kind,
289
370
  changed: !existed || beforePath !== protocol.state_path,
@@ -338,21 +419,19 @@ function applyAction(context, action, reason, force, now) {
338
419
  if (run.status === "discarded") {
339
420
  const index = discardedRunIndex(run.index_json, reason, now);
340
421
  context.db.run("UPDATE runs SET verdict = 'discarded', next_action = ?, index_json = ?, updated_at = ?, completed_at = COALESCE(completed_at, ?) WHERE project_id = ? AND id = ?", [reason, JSON.stringify(index), now, now, action.project_id, action.run_id]);
341
- if (fs.existsSync(run.runtime_path))
342
- fs.writeFileSync(run.runtime_path, `${JSON.stringify(index, null, 2)}\n`);
422
+ context.db.afterCommit(() => publishRunProjections(context, action.project_id, action.run_id));
343
423
  return { kind: action.kind, changed: true, run_id: action.run_id };
344
424
  }
345
425
  if (run.status !== "running") {
346
426
  return { kind: action.kind, changed: false, skipped: true, reason: `already_${run.status}`, run_id: action.run_id };
347
427
  }
348
- if (fs.existsSync(run.runtime_path) || fs.existsSync(run.run_dir) || fs.existsSync(run.workspace_root)) {
428
+ if (prepared.exists(run.runtime_path) || prepared.exists(run.run_dir) || prepared.exists(run.workspace_root)) {
349
429
  return { kind: action.kind, changed: false, skipped: true, reason: "runtime_or_workspace_reappeared", run_id: action.run_id };
350
430
  }
351
431
  const index = discardedRunIndex(run.index_json, reason, now);
352
432
  context.db.run(`UPDATE runs SET status = 'discarded', verdict = 'discarded', next_action = ?, index_json = ?, updated_at = ?, completed_at = ?
353
433
  WHERE project_id = ? AND id = ? AND status = 'running'`, [reason, JSON.stringify(index), now, now, action.project_id, action.run_id]);
354
- fs.mkdirSync(run.run_dir, { recursive: true });
355
- fs.writeFileSync(run.runtime_path, `${JSON.stringify(index, null, 2)}\n`);
434
+ context.db.afterCommit(() => publishRunProjections(context, action.project_id, action.run_id));
356
435
  context.db.run(`UPDATE sessions SET status = 'stopped', stop_reason = ?, updated_at = ?, stopped_at = ?
357
436
  WHERE project_id = ? AND run_id = ? AND status IN ('pending', 'active', 'waiting_user', 'blocked', 'stopping')`, [reason, now, now, action.project_id, action.run_id]);
358
437
  context.db.run("UPDATE flow_session_segments SET ended_at = ? WHERE project_id = ? AND run_id = ? AND ended_at IS NULL", [now, action.project_id, action.run_id]);
@@ -394,7 +473,7 @@ function applyAction(context, action, reason, force, now) {
394
473
  }
395
474
  if (action.kind === "close_worktree_record" && action.protocol_id) {
396
475
  const worktreePath = action.worktree_path ?? context.db.get("SELECT worktree_path FROM worktree_records WHERE project_id = ? AND protocol_id = ?", [action.project_id, action.protocol_id])?.worktree_path;
397
- const status = worktreePath && fs.existsSync(worktreePath) ? "kept" : "removed";
476
+ const status = worktreePath && prepared.exists(worktreePath) ? "kept" : "removed";
398
477
  const result = context.db.run(`UPDATE worktree_records SET status = ?, updated_at = ?, closed_at = ?
399
478
  WHERE project_id = ? AND protocol_id = ? AND status = 'active'`, [status, now, now, action.project_id, action.protocol_id]);
400
479
  return {
@@ -407,22 +486,36 @@ function applyAction(context, action, reason, force, now) {
407
486
  return { kind: action.kind, changed: false, skipped: true, reason: "invalid_action" };
408
487
  }
409
488
  function discardedRunIndex(indexJson, reason, now) {
410
- const index = JSON.parse(indexJson);
489
+ const index = cleanupRunIndex(indexJson);
411
490
  index.schema_id = "dd-flow/flow-run@3";
412
491
  index.status = "discarded";
413
492
  index.verdict = "discarded";
414
493
  index.next_action = reason;
415
494
  index.updated_at = now;
495
+ index.runtime_revision = (typeof index.runtime_revision === "number" ? index.runtime_revision : 0) + 1;
416
496
  index.completed_at ??= now;
417
497
  index.finished_at ??= now;
418
498
  return index;
419
499
  }
500
+ function cleanupRunIndex(indexJson) {
501
+ try {
502
+ const index = JSON.parse(indexJson);
503
+ if (!index || typeof index !== "object" || Array.isArray(index))
504
+ throw new Error("RUN index must be an object");
505
+ return index;
506
+ }
507
+ catch (error) {
508
+ throw new AppError("runtime_state_invalid", "Cleanup cannot read retained RUN state", 1, { cause: String(error) });
509
+ }
510
+ }
420
511
  function dirtyGit(worktreePath) {
421
512
  if (!fs.existsSync(worktreePath)) {
422
513
  return false;
423
514
  }
424
- const result = spawnSync("git", ["-C", worktreePath, "status", "--porcelain"], { encoding: "utf8" });
425
- return result.status === 0 && result.stdout.trim().length > 0;
515
+ const result = spawnSync("git", ["-C", worktreePath, "status", "--porcelain"], { encoding: "utf8", timeout: 5000 });
516
+ if (result.error || result.status !== 0)
517
+ throw new AppError("workspace_status_failed", "Cannot establish worktree status before cleanup", 1, { path: worktreePath, cause: result.error?.message ?? result.stderr });
518
+ return result.stdout.trim().length > 0;
426
519
  }
427
520
  function safeCleanupWorktreePath(context, projectId, worktreePath) {
428
521
  if (!fs.existsSync(worktreePath)) {
@@ -1,3 +1,8 @@
1
+ import { parseCommandArgs } from "./lifecycle-command.js";
2
+ import { commandKeyForPositions, routerNativeRoute } from "../cli/command-metadata.js";
3
+ import { hasHelpIntent } from "../cli/help.js";
4
+ import { isUpgradeAllowlisted } from "./cli-upgrade-allowlist.js";
5
+ export { isUpgradeAllowlisted } from "./cli-upgrade-allowlist.js";
1
6
  export function classifyCliOperation(args, env = process.env) {
2
7
  const positional = positionalArgs(args);
3
8
  const [family = null, command = null, action = null] = positional;
@@ -5,6 +10,8 @@ export function classifyCliOperation(args, env = process.env) {
5
10
  if (isMbUpgradeMode(args, env)) {
6
11
  return { mode: "mb_upgrade", operation, family, command, action };
7
12
  }
13
+ if (family === "run" && command === "reconcile" && !args.includes("--apply"))
14
+ return { mode: "read_only_diagnostics", operation, family, command, action };
8
15
  if (isRouterNative(args)) {
9
16
  return { mode: "router_native", operation, family, command, action };
10
17
  }
@@ -14,19 +21,7 @@ export function classifyCliOperation(args, env = process.env) {
14
21
  return { mode: "normal_write", operation, family, command, action };
15
22
  }
16
23
  function positionalArgs(args) {
17
- const positional = [];
18
- for (let index = 0; index < args.length; index += 1) {
19
- const value = args[index];
20
- if (!value)
21
- continue;
22
- if (value.startsWith("--")) {
23
- if (!value.includes("=") && args[index + 1] && !args[index + 1]?.startsWith("--"))
24
- index += 1;
25
- continue;
26
- }
27
- positional.push(value);
28
- }
29
- return positional;
24
+ return parseCommandArgs(args).positional;
30
25
  }
31
26
  export function isReadOnlyCliOperation(args, env = process.env) {
32
27
  return classifyCliOperation(args, env).mode === "read_only_diagnostics";
@@ -57,59 +52,19 @@ function isMbUpgradeMode(args, env) {
57
52
  }
58
53
  return marked && isUpgradeAllowlisted(args);
59
54
  }
60
- function isUpgradeAllowlisted(args) {
61
- const positional = positionalArgs(args);
62
- const [family, command] = positional;
63
- if (family === "status")
64
- return true;
65
- if (family === "engine" && command === "resolve")
66
- return true;
67
- if (family === "migration" && ["impact", "plan", "report", "verify", "apply"].includes(command ?? ""))
68
- return true;
69
- if (family === "memory" && command === "permissions" && positional[2] === "preflight")
70
- return true;
71
- if (family === "run" && command === "start")
72
- return optionValue(args, "flow-kind") === "mb-upgrade";
73
- if (family === "stage" && ["start", "pause", "resume", "finish"].includes(command ?? ""))
74
- return hasRunBinding(args);
75
- if (family === "session" && ["status", "register", "stop", "usage"].includes(command ?? ""))
76
- return hasRunBinding(args);
77
- if (family === "dashboard" && ["data", "render", "refresh", "open"].includes(command ?? ""))
78
- return true;
79
- return false;
80
- }
81
- function hasRunBinding(args) {
82
- return Boolean(optionValue(args, "run") || positionalArgs(args).some((value) => value.startsWith("RUN-")));
83
- }
84
55
  function optionValue(args, name) {
85
- for (let index = args.length - 1; index >= 0; index -= 1) {
86
- const arg = args[index];
87
- if (arg === `--${name}`)
88
- return args[index + 1] ?? null;
89
- if (arg?.startsWith(`--${name}=`))
90
- return arg.slice(name.length + 3);
91
- }
92
- return null;
56
+ const values = parseCommandArgs(args).options.get(name);
57
+ const value = values?.[values.length - 1];
58
+ return value && value !== "__dd_flow_flag__" ? value : null;
93
59
  }
94
- function isRouterNative(args) {
60
+ export function isRouterNative(args) {
95
61
  const first = args[0];
96
62
  if (!first || first === "--version")
97
63
  return true;
98
- if (first === "--help" || first === "-h")
64
+ if (hasHelpIntent(args))
99
65
  return true;
100
- if (args.includes("--help") || args.includes("-h"))
101
- return true;
102
- if (["engine", "version", "schema"].includes(first))
103
- return true;
104
- return (first === "codex" && args[1] === "hook" && args[2] === "handle")
105
- || (first === "zcode" && args[1] === "event" && args[2] === "handle")
106
- || (first === "zcode" && args[1] === "usage" && args[2] === "ingest")
107
- || (first === "grok" && args[1] === "event" && args[2] === "handle")
108
- || (first === "grok" && args[1] === "usage" && args[2] === "ingest")
109
- || (first === "opencode" && args[1] === "event" && args[2] === "handle")
110
- || (first === "opencode" && args[1] === "usage" && args[2] === "ingest")
111
- || (first === "agy" && args[1] === "event" && args[2] === "handle")
112
- || (first === "agy" && args[1] === "usage" && args[2] === "ingest");
66
+ const key = commandKeyForPositions(positionalArgs(args));
67
+ return key !== undefined && routerNativeRoute(key);
113
68
  }
114
69
  function isReadOnlyDiagnostic(family, command, action) {
115
70
  if (!family)
@@ -119,13 +74,21 @@ function isReadOnlyDiagnostic(family, command, action) {
119
74
  if (family === "canon")
120
75
  return command === "status" || command === "resolve";
121
76
  if (family === "project")
122
- return command === "status" || command === "resolve";
77
+ return command === "status" || command === "resolve" || (command === "config" && action === "status");
123
78
  if (family === "protocol")
124
79
  return ["status", "branch-status", "ready", "blockers", "implement"].includes(command ?? "");
125
80
  if (family === "plan")
126
81
  return command === "status";
127
82
  if (family === "run")
128
- return command === "status" || command === "list";
83
+ return ["status", "list", "timeline"].includes(command ?? "") || (["config", "flags", "drive", "control"].includes(command ?? "") && action === "status") || (command === "vars" && ["ls", "get"].includes(action ?? "")) || (command === "recovery" && action === "inspect");
84
+ if (family === "runtime" && command === "process" && ["status", "check-admission"].includes(action ?? ""))
85
+ return true;
86
+ if (family === "runtime" && command === "scope" && action === "status")
87
+ return true;
88
+ if (family === "work")
89
+ return ["status", "ls", "show"].includes(command ?? "");
90
+ if (family === "stage")
91
+ return command === "fanout" && action === "status";
129
92
  if (family === "lane") {
130
93
  return (command === "status" ||
131
94
  command === "waiters" ||