@deksden-com/dd-flow-cli 0.9.0-beta.15 → 0.9.0-beta.151

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 (560) hide show
  1. package/CHANGELOG.md +907 -0
  2. package/README.md +139 -10
  3. package/dist/build-info.json +8 -8
  4. package/dist/cli/argument-values.js +38 -0
  5. package/dist/cli/command-inputs.js +386 -0
  6. package/dist/cli/command-metadata.js +232 -0
  7. package/dist/cli/command-option-descriptions.js +192 -0
  8. package/dist/cli/completion-cli.js +70 -0
  9. package/dist/cli/completion.js +57 -0
  10. package/dist/cli/diagnostic-invocation.js +46 -0
  11. package/dist/cli/diagnostic.js +120 -0
  12. package/dist/cli/error-output.js +52 -0
  13. package/dist/cli/executor-onboarding.js +76 -0
  14. package/dist/cli/flows.js +29 -0
  15. package/dist/cli/harnesses.js +84 -0
  16. package/dist/cli/help.js +396 -19
  17. package/dist/cli/hook-ingress.js +102 -0
  18. package/dist/cli/input-preparation.js +165 -0
  19. package/dist/cli/memory-flow-admission.js +50 -0
  20. package/dist/cli/memory-init-invocation.js +85 -0
  21. package/dist/cli/memory-init.js +249 -0
  22. package/dist/cli/memory-storage-admission.js +54 -0
  23. package/dist/cli/memory-storage-qualification.js +105 -0
  24. package/dist/cli/memory-storage-transition.js +123 -0
  25. package/dist/cli/memory-upgrade.js +189 -0
  26. package/dist/cli/models.js +39 -0
  27. package/dist/cli/native-hook-ingress.js +90 -0
  28. package/dist/cli/native-hook-preparation.js +55 -0
  29. package/dist/cli/operator-profiles.js +61 -0
  30. package/dist/cli/output-options.js +35 -0
  31. package/dist/cli/response-file.js +72 -0
  32. package/dist/cli/run-cli.js +1734 -550
  33. package/dist/cli/runtime-maintenance-diagnostics.js +46 -0
  34. package/dist/cli/runtime-maintenance-ingress.js +52 -0
  35. package/dist/cli/runtime-process.js +153 -0
  36. package/dist/cli/setup.js +150 -0
  37. package/dist/cli.js +89 -2
  38. package/dist/domain/diagnostic-aspects.js +103 -0
  39. package/dist/domain/diagnostic-flows.js +8 -0
  40. package/dist/domain/stage-catalog.js +1 -1
  41. package/dist/domain/stage-handler-identities.js +28 -0
  42. package/dist/harness-runtime/DD-ZCODE.md +112 -0
  43. package/dist/harness-runtime/OPENCODE-LICENSE.txt +24 -0
  44. package/dist/harness-runtime/bin/dd-agy.mjs +56 -0
  45. package/dist/harness-runtime/bin/dd-codex.mjs +27 -0
  46. package/dist/harness-runtime/bin/dd-droid.mjs +34 -0
  47. package/dist/harness-runtime/bin/dd-grok.mjs +40 -0
  48. package/dist/harness-runtime/bin/dd-opencode.mjs +22 -0
  49. package/dist/harness-runtime/bin/dd-zcode.mjs +87 -0
  50. package/dist/harness-runtime/lib/adapter-timeouts.d.mts +5 -0
  51. package/dist/harness-runtime/lib/adapter-timeouts.mjs +34 -0
  52. package/dist/harness-runtime/lib/agy-error-boundary.mjs +79 -0
  53. package/dist/harness-runtime/lib/agy-hooks.d.mts +1 -0
  54. package/dist/harness-runtime/lib/agy-hooks.mjs +9 -0
  55. package/dist/harness-runtime/lib/agy-native-inspection.mjs +216 -0
  56. package/dist/harness-runtime/lib/agy-native_pb.js +117 -0
  57. package/dist/harness-runtime/lib/agy-terminal-envelope.mjs +67 -0
  58. package/dist/harness-runtime/lib/daemon-observation.d.mts +2 -0
  59. package/dist/harness-runtime/lib/daemon-observation.mjs +163 -0
  60. package/dist/harness-runtime/lib/daemon-operations.d.mts +2 -0
  61. package/dist/harness-runtime/lib/daemon-operations.mjs +462 -0
  62. package/dist/harness-runtime/lib/dd-agy-daemon.d.mts +6 -0
  63. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +1092 -0
  64. package/dist/harness-runtime/lib/dd-agy.mjs +86 -0
  65. package/dist/harness-runtime/lib/dd-codex-daemon.d.mts +1 -0
  66. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +231 -0
  67. package/dist/harness-runtime/lib/dd-codex.d.mts +1 -0
  68. package/dist/harness-runtime/lib/dd-codex.mjs +954 -0
  69. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +141 -0
  70. package/dist/harness-runtime/lib/dd-droid.mjs +626 -0
  71. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +555 -0
  72. package/dist/harness-runtime/lib/dd-grok.mjs +180 -0
  73. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +627 -0
  74. package/dist/harness-runtime/lib/dd-opencode.mjs +390 -0
  75. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +752 -0
  76. package/dist/harness-runtime/lib/dd-zcode.mjs +1200 -0
  77. package/dist/harness-runtime/lib/delegation-instructions.d.mts +14 -0
  78. package/dist/harness-runtime/lib/delegation-instructions.mjs +166 -0
  79. package/dist/harness-runtime/lib/dispatch-fence.mjs +14 -0
  80. package/dist/harness-runtime/lib/driver-recovery.mjs +140 -0
  81. package/dist/harness-runtime/lib/droid-observation.mjs +66 -0
  82. package/dist/harness-runtime/lib/entity-references.d.mts +2 -0
  83. package/dist/harness-runtime/lib/entity-references.mjs +9 -0
  84. package/dist/harness-runtime/lib/lease-renewal.d.mts +8 -0
  85. package/dist/harness-runtime/lib/lease-renewal.mjs +37 -0
  86. package/dist/harness-runtime/lib/lifecycle-command.d.mts +20 -0
  87. package/dist/harness-runtime/lib/lifecycle-command.mjs +273 -0
  88. package/dist/harness-runtime/lib/lifecycle-contract.d.mts +10 -0
  89. package/dist/harness-runtime/lib/lifecycle-contract.mjs +19 -0
  90. package/dist/harness-runtime/lib/managed-daemon.d.mts +16 -0
  91. package/dist/harness-runtime/lib/managed-daemon.mjs +698 -0
  92. package/dist/harness-runtime/lib/managed-flow-env.mjs +15 -0
  93. package/dist/harness-runtime/lib/model-observations.mjs +113 -0
  94. package/dist/harness-runtime/lib/native-children.d.mts +34 -0
  95. package/dist/harness-runtime/lib/native-children.mjs +265 -0
  96. package/dist/harness-runtime/lib/native-hook-command.d.mts +7 -0
  97. package/dist/harness-runtime/lib/native-hook-command.mjs +251 -0
  98. package/dist/harness-runtime/lib/native-request-proof.d.mts +2 -0
  99. package/dist/harness-runtime/lib/native-request-proof.mjs +65 -0
  100. package/dist/harness-runtime/lib/native-streams.mjs +45 -0
  101. package/dist/harness-runtime/lib/observation-clock.d.mts +18 -0
  102. package/dist/harness-runtime/lib/observation-clock.mjs +95 -0
  103. package/dist/harness-runtime/lib/opencode-plugin.mjs +85 -0
  104. package/dist/harness-runtime/lib/operation-context.mjs +5 -0
  105. package/dist/harness-runtime/lib/operation-errors.d.mts +16 -0
  106. package/dist/harness-runtime/lib/operation-errors.mjs +127 -0
  107. package/dist/harness-runtime/lib/operation-progress.d.mts +7 -0
  108. package/dist/harness-runtime/lib/operation-progress.mjs +114 -0
  109. package/dist/harness-runtime/lib/process-json.d.mts +5 -0
  110. package/dist/harness-runtime/lib/process-json.mjs +201 -0
  111. package/dist/harness-runtime/lib/process-snapshot.mjs +10 -0
  112. package/dist/harness-runtime/lib/provider-overload-policy.d.mts +23 -0
  113. package/dist/harness-runtime/lib/provider-overload-policy.mjs +242 -0
  114. package/dist/harness-runtime/lib/runner-events.mjs +202 -0
  115. package/dist/harness-runtime/lib/runner-lock.mjs +42 -0
  116. package/dist/harness-runtime/lib/session-settlement.mjs +30 -0
  117. package/dist/harness-runtime/lib/setup-progress.d.mts +9 -0
  118. package/dist/harness-runtime/lib/setup-progress.mjs +53 -0
  119. package/dist/harness-runtime/lib/tool-observations.d.mts +21 -0
  120. package/dist/harness-runtime/lib/tool-observations.mjs +228 -0
  121. package/dist/harness-runtime/lib/zcode-usage.mjs +55 -0
  122. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-1y4a697q.js +81 -0
  123. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-2ny4vxct.js +92 -0
  124. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-6mptkzbc.js +0 -0
  125. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-7x67cf1q.js +52 -0
  126. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-9rqn6x4v.js +16 -0
  127. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-9vdz0zwn.js +1661 -0
  128. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ecs5ftka.js +1466 -0
  129. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-frbwqjmf.js +145 -0
  130. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-gne7h7mq.js +50 -0
  131. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-gx9wnkzr.js +105 -0
  132. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-m2eywc8w.js +0 -0
  133. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-nwcpakzx.js +12 -0
  134. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-q4g3afnw.js +623 -0
  135. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-qav6mfdz.js +0 -0
  136. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-sxt88q69.js +12 -0
  137. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-t1xa31vm.js +39 -0
  138. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ve4aws36.js +103 -0
  139. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vmytr4q8.js +4 -0
  140. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vt9jbch8.js +36 -0
  141. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vyas3vgt.js +0 -0
  142. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-wjyeq25y.js +30 -0
  143. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-xgnj2w4v.js +20 -0
  144. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-y2etg5bs.js +9 -0
  145. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ydhz1zrh.js +238 -0
  146. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-z6qdwcsf.js +62 -0
  147. package/dist/harness-runtime/vendor/opencode-client/contract.d.ts +1 -0
  148. package/dist/harness-runtime/vendor/opencode-client/contract.js +12 -0
  149. package/dist/harness-runtime/vendor/opencode-client/effect/api/api.d.ts +2806 -0
  150. package/dist/harness-runtime/vendor/opencode-client/effect/api/api.js +0 -0
  151. package/dist/harness-runtime/vendor/opencode-client/effect/api.d.ts +4 -0
  152. package/dist/harness-runtime/vendor/opencode-client/effect/api.js +0 -0
  153. package/dist/harness-runtime/vendor/opencode-client/effect/client.d.ts +2530 -0
  154. package/dist/harness-runtime/vendor/opencode-client/effect/client.js +15 -0
  155. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client-error.d.ts +7 -0
  156. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client-error.js +6 -0
  157. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client.d.ts +2529 -0
  158. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client.js +8 -0
  159. package/dist/harness-runtime/vendor/opencode-client/effect/generated/index.d.ts +2 -0
  160. package/dist/harness-runtime/vendor/opencode-client/effect/generated/index.js +12 -0
  161. package/dist/harness-runtime/vendor/opencode-client/effect/index.d.ts +35 -0
  162. package/dist/harness-runtime/vendor/opencode-client/effect/index.js +75 -0
  163. package/dist/harness-runtime/vendor/opencode-client/effect/rpc.d.ts +20 -0
  164. package/dist/harness-runtime/vendor/opencode-client/effect/rpc.js +11 -0
  165. package/dist/harness-runtime/vendor/opencode-client/effect/service.d.ts +90 -0
  166. package/dist/harness-runtime/vendor/opencode-client/effect/service.js +187 -0
  167. package/dist/harness-runtime/vendor/opencode-client/package.json +84 -0
  168. package/dist/harness-runtime/vendor/opencode-client/promise/api.d.ts +21 -0
  169. package/dist/harness-runtime/vendor/opencode-client/promise/api.js +0 -0
  170. package/dist/harness-runtime/vendor/opencode-client/promise/client.d.ts +252 -0
  171. package/dist/harness-runtime/vendor/opencode-client/promise/client.js +14 -0
  172. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client-error.d.ts +8 -0
  173. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client-error.js +6 -0
  174. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client.d.ts +259 -0
  175. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client.js +7 -0
  176. package/dist/harness-runtime/vendor/opencode-client/promise/generated/index.d.ts +3 -0
  177. package/dist/harness-runtime/vendor/opencode-client/promise/generated/index.js +79 -0
  178. package/dist/harness-runtime/vendor/opencode-client/promise/generated/types.d.ts +8932 -0
  179. package/dist/harness-runtime/vendor/opencode-client/promise/generated/types.js +70 -0
  180. package/dist/harness-runtime/vendor/opencode-client/promise/index.d.ts +6 -0
  181. package/dist/harness-runtime/vendor/opencode-client/promise/index.js +83 -0
  182. package/dist/harness-runtime/vendor/opencode-client/promise/rpc.d.ts +32 -0
  183. package/dist/harness-runtime/vendor/opencode-client/promise/rpc.js +7 -0
  184. package/dist/harness-runtime/vendor/opencode-client/promise/service.d.ts +24 -0
  185. package/dist/harness-runtime/vendor/opencode-client/promise/service.js +187 -0
  186. package/dist/harness-runtime/vendor/opencode-client/pty-handoff.d.ts +12 -0
  187. package/dist/harness-runtime/vendor/opencode-client/pty-handoff.js +14 -0
  188. package/dist/harness-runtime/vendor/opencode-client/rpc-runtime.d.ts +20 -0
  189. package/dist/harness-runtime/vendor/opencode-client/rpc-runtime.js +14 -0
  190. package/dist/harness-runtime/vendor/opencode-client/service-contender.d.ts +37 -0
  191. package/dist/harness-runtime/vendor/opencode-client/service-contender.js +10 -0
  192. package/dist/harness-runtime/vendor/opencode-client/service-probe.d.ts +70 -0
  193. package/dist/harness-runtime/vendor/opencode-client/service-probe.js +16 -0
  194. package/dist/harness-runtime/vendor/opencode-client/service-timing.d.ts +12 -0
  195. package/dist/harness-runtime/vendor/opencode-client/service-timing.js +10 -0
  196. package/dist/harness-runtime/vendor/opencode-client/service-version.d.ts +2 -0
  197. package/dist/harness-runtime/vendor/opencode-client/service-version.js +6 -0
  198. package/dist/harness-runtime/vendor/opencode-client/service.d.ts +52 -0
  199. package/dist/harness-runtime/vendor/opencode-client/service.js +1 -0
  200. package/dist/harness-runtime/vendor/opencode-client/shared-events.d.ts +11 -0
  201. package/dist/harness-runtime/vendor/opencode-client/shared-events.js +8 -0
  202. package/dist/harness-runtime/vendor/opencode-client/solid/connection.d.ts +37 -0
  203. package/dist/harness-runtime/vendor/opencode-client/solid/connection.js +10 -0
  204. package/dist/harness-runtime/vendor/opencode-client/solid/data.d.ts +211 -0
  205. package/dist/harness-runtime/vendor/opencode-client/solid/data.js +18 -0
  206. package/dist/harness-runtime/vendor/opencode-client/solid/index.d.ts +3 -0
  207. package/dist/harness-runtime/vendor/opencode-client/solid/index.js +32 -0
  208. package/dist/harness-runtime/vendor/opencode-client/solid/pty.d.ts +22 -0
  209. package/dist/harness-runtime/vendor/opencode-client/solid/pty.js +8 -0
  210. package/dist/harness-runtime/vendor/opencode-plugin.mjs +3 -0
  211. package/dist/protocol/local-files.js +18 -9
  212. package/dist/runtime/context.js +8 -4
  213. package/dist/schemas/agent-profile.schema.json +1 -1
  214. package/dist/schemas/code-review-decision.schema.json +9 -0
  215. package/dist/schemas/code-review-result.schema.json +1 -1
  216. package/dist/schemas/code-verification.schema.json +7 -5
  217. package/dist/schemas/code-work-batch.schema.json +2 -2
  218. package/dist/schemas/diagnostic-stage-result.schema.json +354 -0
  219. package/dist/schemas/diagnostic-work-result.schema.json +296 -0
  220. package/dist/schemas/discovery-result.schema.json +184 -0
  221. package/dist/schemas/execution-bundle.schema.json +211 -0
  222. package/dist/schemas/execution-settings.schema.json +162 -0
  223. package/dist/schemas/flow-descriptor.schema.json +54 -0
  224. package/dist/schemas/flow-run.schema.json +29 -0
  225. package/dist/schemas/harness-catalog.schema.json +80 -0
  226. package/dist/schemas/mb-init-compose-result.schema.json +457 -0
  227. package/dist/schemas/mb-init-discover-result.schema.json +500 -0
  228. package/dist/schemas/mb-init-finish-result.schema.json +422 -0
  229. package/dist/schemas/mb-init-preflight-result.schema.json +367 -0
  230. package/dist/schemas/mb-init-review-result.schema.json +396 -0
  231. package/dist/schemas/mb-init-verify-result.schema.json +391 -0
  232. package/dist/schemas/mb-upgrade-stage-result.schema.json +863 -0
  233. package/dist/schemas/memory-bootstrap-receipt-v2.schema.json +67 -0
  234. package/dist/schemas/memory-bootstrap-receipt.schema.json +354 -0
  235. package/dist/schemas/memory-composition-work-result.schema.json +38 -0
  236. package/dist/schemas/memory-repair-work-result.schema.json +56 -0
  237. package/dist/schemas/memory-review-correction-plan.schema.json +30 -0
  238. package/dist/schemas/memory-review-work-result.schema.json +43 -0
  239. package/dist/schemas/model-catalog.schema.json +153 -0
  240. package/dist/schemas/plan-review-decision.schema.json +12 -3
  241. package/dist/schemas/plan-review-result.schema.json +2 -2
  242. package/dist/schemas/profile-settings.schema.json +122 -0
  243. package/dist/schemas/project-flow-pack-manifest.schema.json +1 -1
  244. package/dist/schemas/release-impact.schema.json +3 -3
  245. package/dist/schemas/release-manifest.schema.json +291 -0
  246. package/dist/schemas/run-control-receipt.schema.json +118 -0
  247. package/dist/schemas/run-control-request.schema.json +36 -0
  248. package/dist/schemas/run-engine-binding.schema.json +1 -1
  249. package/dist/schemas/stage-start-response.schema.json +4 -0
  250. package/dist/schemas/vnext-protocol-plan.schema.json +2 -2
  251. package/dist/services/accepted-intent.js +139 -0
  252. package/dist/services/branch-context.js +3 -3
  253. package/dist/services/canon.js +54 -15
  254. package/dist/services/check-environment.js +21 -0
  255. package/dist/services/check-runner-evidence.js +277 -0
  256. package/dist/services/check-runner-proof.js +305 -0
  257. package/dist/services/cleanup.js +133 -40
  258. package/dist/services/cli-operation-classifier.js +26 -63
  259. package/dist/services/cli-upgrade-allowlist.js +27 -0
  260. package/dist/services/code-checks.js +1401 -119
  261. package/dist/services/codex-hook-delivery.js +32 -0
  262. package/dist/services/command-context.js +80 -0
  263. package/dist/services/compatibility-preflight.js +16 -3
  264. package/dist/services/config.js +11 -8
  265. package/dist/services/continuation-outcome.js +20 -0
  266. package/dist/services/controller-child-capacity.js +71 -0
  267. package/dist/services/controller-child-observer.js +121 -0
  268. package/dist/services/controller-execution.js +94 -0
  269. package/dist/services/controller-fanout.js +226 -0
  270. package/dist/services/controller-memory-task.js +45 -0
  271. package/dist/services/controller-memory-worker.js +49 -0
  272. package/dist/services/dashboard.js +143 -58
  273. package/dist/services/descriptor-coordinator-session.js +35 -0
  274. package/dist/services/descriptor-root-work.js +53 -0
  275. package/dist/services/descriptor-run-settlement.js +107 -0
  276. package/dist/services/descriptor-run-start.js +475 -0
  277. package/dist/services/descriptor-stage-coordinator.js +250 -0
  278. package/dist/services/descriptor-stage-settlement.js +223 -0
  279. package/dist/services/descriptor-stage-start.js +175 -0
  280. package/dist/services/diagnostic-evidence.js +73 -0
  281. package/dist/services/diagnostic-report-publication.js +72 -0
  282. package/dist/services/diagnostic-report.js +67 -0
  283. package/dist/services/diagnostic-run-bundle.js +74 -0
  284. package/dist/services/diagnostic-snapshot.js +159 -0
  285. package/dist/services/diagnostic-stage-handlers.js +108 -0
  286. package/dist/services/diagnostic-stage-results.js +132 -0
  287. package/dist/services/diagnostic-start.js +84 -0
  288. package/dist/services/diagnostic-work-batch.js +34 -0
  289. package/dist/services/diagnostic-work-collection.js +97 -0
  290. package/dist/services/diagnostic-work-registration.js +101 -0
  291. package/dist/services/diagnostic-work-results.js +93 -0
  292. package/dist/services/discovery/agy-discovery.js +2 -0
  293. package/dist/services/discovery/codex-discovery.js +2 -0
  294. package/dist/services/discovery/droid-discovery.js +2 -0
  295. package/dist/services/discovery/grok-discovery.js +2 -0
  296. package/dist/services/discovery/opencode-discovery.js +2 -0
  297. package/dist/services/discovery/static.js +31 -0
  298. package/dist/services/discovery/zcode-discovery.js +2 -0
  299. package/dist/services/engines.js +354 -163
  300. package/dist/services/eval-snapshots.js +1316 -150
  301. package/dist/services/execution-bundles.js +275 -0
  302. package/dist/services/execution-policy.js +185 -0
  303. package/dist/services/executor-onboarding.js +56 -0
  304. package/dist/services/external-work-launch.js +310 -0
  305. package/dist/services/flow-catalog.js +30 -0
  306. package/dist/services/flow-descriptors.js +82 -0
  307. package/dist/services/frozen-engine.js +387 -0
  308. package/dist/services/generic-execution.js +164 -0
  309. package/dist/services/harness-adapter.js +355 -36
  310. package/dist/services/harness-config.js +2 -0
  311. package/dist/services/harness-discovery.js +29 -0
  312. package/dist/services/harness-doctor.js +75 -0
  313. package/dist/services/harness-probe-effects.js +51 -0
  314. package/dist/services/harness-version-probe.js +100 -0
  315. package/dist/services/hook-memory-routing.js +188 -0
  316. package/dist/services/hook-recovery-guard.js +14 -0
  317. package/dist/services/hook-session-observation.js +24 -0
  318. package/dist/services/hooks.js +791 -326
  319. package/dist/services/hybrid-contracts.js +474 -0
  320. package/dist/services/hybrid-digest.js +14 -0
  321. package/dist/services/ids.js +29 -1
  322. package/dist/services/installation-lock.js +95 -0
  323. package/dist/services/installed-flow-source.js +226 -0
  324. package/dist/services/lanes.js +78 -65
  325. package/dist/services/lifecycle-command.js +1 -288
  326. package/dist/services/lifecycle-contract.js +1 -0
  327. package/dist/services/lifecycle-invocations.js +2148 -0
  328. package/dist/services/managed-daemon-binding.js +38 -0
  329. package/dist/services/managed-lease-monitor.js +133 -0
  330. package/dist/services/managed-processes.js +449 -69
  331. package/dist/services/memory-accepted-composition.js +78 -0
  332. package/dist/services/memory-baseline-preservation.js +65 -0
  333. package/dist/services/memory-completed-correction-wave.js +328 -0
  334. package/dist/services/memory-compose-handler.js +400 -0
  335. package/dist/services/memory-composition-acceptance.js +375 -0
  336. package/dist/services/memory-composition-graph.js +165 -0
  337. package/dist/services/memory-composition-registration.js +161 -0
  338. package/dist/services/memory-composition-work-custody.js +156 -0
  339. package/dist/services/memory-composition-work-results.js +108 -0
  340. package/dist/services/memory-corrected-review-acceptance.js +129 -0
  341. package/dist/services/memory-corrected-review-batch.js +37 -0
  342. package/dist/services/memory-corrected-review-findings.js +108 -0
  343. package/dist/services/memory-corrected-review-inputs.js +215 -0
  344. package/dist/services/memory-corrected-review-projection.js +78 -0
  345. package/dist/services/memory-corrected-review-receipt.js +178 -0
  346. package/dist/services/memory-corrected-review-registration.js +192 -0
  347. package/dist/services/memory-correction-check-receipt.js +235 -0
  348. package/dist/services/memory-correction-input-owner.js +6 -0
  349. package/dist/services/memory-custody-snapshot.js +36 -0
  350. package/dist/services/memory-discover-handler.js +104 -0
  351. package/dist/services/memory-finish-evidence.js +457 -0
  352. package/dist/services/memory-finish-handler.js +229 -0
  353. package/dist/services/memory-finish-integration.js +450 -0
  354. package/dist/services/memory-finish-readiness.js +169 -0
  355. package/dist/services/memory-git-integration.js +220 -0
  356. package/dist/services/memory-historical-corrected-review.js +212 -0
  357. package/dist/services/memory-historical-original-review.js +143 -0
  358. package/dist/services/memory-historical-repair-registration.js +105 -0
  359. package/dist/services/memory-historical-second-review.js +147 -0
  360. package/dist/services/memory-init-bootstrap-readiness.js +210 -0
  361. package/dist/services/memory-init-contexts.js +131 -0
  362. package/dist/services/memory-init-controller-start.js +103 -0
  363. package/dist/services/memory-init-engine-release.js +223 -0
  364. package/dist/services/memory-init-git-policy.js +165 -0
  365. package/dist/services/memory-init-lint-capability.js +203 -0
  366. package/dist/services/memory-init-native-capacity.js +309 -0
  367. package/dist/services/memory-init-preflight.js +334 -0
  368. package/dist/services/memory-init-project-bootstrap.js +599 -0
  369. package/dist/services/memory-init-request-candidate.js +213 -0
  370. package/dist/services/memory-init-request-input.js +82 -0
  371. package/dist/services/memory-init-resource-storage.js +125 -0
  372. package/dist/services/memory-init-run-allocation.js +81 -0
  373. package/dist/services/memory-init-run-bundle.js +135 -0
  374. package/dist/services/memory-init-start.js +181 -0
  375. package/dist/services/memory-init-startup-custody.js +80 -0
  376. package/dist/services/memory-init-startup-input.js +240 -0
  377. package/dist/services/memory-init-storage.js +91 -0
  378. package/dist/services/memory-init-stores.js +359 -0
  379. package/dist/services/memory-init-worktrunk-capability.js +174 -0
  380. package/dist/services/memory-layer-publication.js +611 -0
  381. package/dist/services/memory-layer.js +221 -0
  382. package/dist/services/memory-lint-config.js +194 -0
  383. package/dist/services/memory-native-review-copy.js +103 -0
  384. package/dist/services/memory-normal-flow-entry.js +87 -0
  385. package/dist/services/memory-ordinary-defaults.js +129 -0
  386. package/dist/services/memory-preflight-bootstrap-custody.js +115 -0
  387. package/dist/services/memory-preflight-handler.js +274 -0
  388. package/dist/services/memory-repair-current-view.js +299 -0
  389. package/dist/services/memory-repair-document-delta.js +488 -0
  390. package/dist/services/memory-repair-history.js +496 -0
  391. package/dist/services/memory-repair-physical-binding.js +194 -0
  392. package/dist/services/memory-repair-publication-files.js +152 -0
  393. package/dist/services/memory-repair-publication-intent.js +27 -0
  394. package/dist/services/memory-repair-publication-plan.js +162 -0
  395. package/dist/services/memory-repair-publication-source.js +225 -0
  396. package/dist/services/memory-repair-publication.js +795 -0
  397. package/dist/services/memory-repair-registration.js +240 -0
  398. package/dist/services/memory-repair-work-baseline.js +415 -0
  399. package/dist/services/memory-repair-work-completion.js +117 -0
  400. package/dist/services/memory-repair-work-custody.js +73 -0
  401. package/dist/services/memory-repair-work-results.js +108 -0
  402. package/dist/services/memory-repair-workspace.js +261 -0
  403. package/dist/services/memory-repair-writer-copy.js +736 -0
  404. package/dist/services/memory-result-binding.js +239 -0
  405. package/dist/services/memory-retained-review-authority.js +138 -0
  406. package/dist/services/memory-retained-review-results.js +70 -0
  407. package/dist/services/memory-retained-second-baseline.js +102 -0
  408. package/dist/services/memory-retained-second-repairs.js +162 -0
  409. package/dist/services/memory-review-acceptance.js +214 -0
  410. package/dist/services/memory-review-correction-plan.js +206 -0
  411. package/dist/services/memory-review-handler.js +585 -0
  412. package/dist/services/memory-review-inputs.js +246 -0
  413. package/dist/services/memory-review-limits.js +10 -0
  414. package/dist/services/memory-review-receipt.js +180 -0
  415. package/dist/services/memory-review-registration.js +117 -0
  416. package/dist/services/memory-review-work-custody.js +126 -0
  417. package/dist/services/memory-review-work-input.js +91 -0
  418. package/dist/services/memory-review-work-results.js +95 -0
  419. package/dist/services/memory-second-corrected-review-inputs.js +167 -0
  420. package/dist/services/memory-second-correction-plan.js +77 -0
  421. package/dist/services/memory-second-repair-baseline.js +91 -0
  422. package/dist/services/memory-second-repair-launch.js +614 -0
  423. package/dist/services/memory-second-repair-registration.js +88 -0
  424. package/dist/services/memory-second-repair-source.js +187 -0
  425. package/dist/services/memory-second-review-acceptance.js +115 -0
  426. package/dist/services/memory-source-observations.js +117 -0
  427. package/dist/services/memory-stage-dispatch.js +65 -0
  428. package/dist/services/memory-stage-results.js +290 -0
  429. package/dist/services/memory-tool-probe.js +25 -0
  430. package/dist/services/memory-upgrade-analysis-handler.js +243 -0
  431. package/dist/services/memory-upgrade-bootstrap-readiness.js +251 -0
  432. package/dist/services/memory-upgrade-evidence.js +100 -0
  433. package/dist/services/memory-upgrade-inventory.js +442 -0
  434. package/dist/services/memory-upgrade-publication-custody.js +73 -0
  435. package/dist/services/memory-upgrade-publication.js +413 -0
  436. package/dist/services/memory-upgrade-selection.js +430 -0
  437. package/dist/services/memory-upgrade-start.js +60 -0
  438. package/dist/services/memory-upgrade-storage-readiness.js +340 -0
  439. package/dist/services/memory-verification-acceptance.js +156 -0
  440. package/dist/services/memory-verification-checks.js +317 -0
  441. package/dist/services/memory-verification-receipt.js +160 -0
  442. package/dist/services/memory-verify-handler.js +137 -0
  443. package/dist/services/memory-work-result-json.js +12 -0
  444. package/dist/services/memory-work-result-preparation.js +64 -0
  445. package/dist/services/merge-queue.js +228 -149
  446. package/dist/services/merge-server.js +163 -35
  447. package/dist/services/migrations.js +164 -114
  448. package/dist/services/model-catalog.js +109 -0
  449. package/dist/services/model-discovery.js +46 -0
  450. package/dist/services/native-daemon-history.js +50 -0
  451. package/dist/services/native-session-control.js +39 -0
  452. package/dist/services/operator-profiles.js +236 -0
  453. package/dist/services/physical-startup.js +45 -0
  454. package/dist/services/plan-runtime.js +26 -11
  455. package/dist/services/plans.js +28 -25
  456. package/dist/services/project-registry.js +97 -0
  457. package/dist/services/project-summary.js +1 -1
  458. package/dist/services/projects.js +17 -87
  459. package/dist/services/prompt-delivery.js +71 -0
  460. package/dist/services/prompts.js +28 -17
  461. package/dist/services/protocols.js +163 -87
  462. package/dist/services/provider-capacity.js +463 -0
  463. package/dist/services/recovery-observation-budget.js +121 -0
  464. package/dist/services/recovery-snapshot-database.js +588 -0
  465. package/dist/services/release-archive-producer.js +52 -0
  466. package/dist/services/release-archive.js +143 -0
  467. package/dist/services/release-cache.js +150 -0
  468. package/dist/services/release-compatibility.js +18 -0
  469. package/dist/services/release-download.js +67 -0
  470. package/dist/services/release-envelope.js +104 -0
  471. package/dist/services/release-manifest.js +45 -0
  472. package/dist/services/release-payload.js +114 -0
  473. package/dist/services/release-producer.js +63 -0
  474. package/dist/services/release-provenance.js +160 -0
  475. package/dist/services/release-registry.js +66 -0
  476. package/dist/services/release-source.js +194 -0
  477. package/dist/services/repair-intents.js +31 -0
  478. package/dist/services/repair-receipt.js +13 -0
  479. package/dist/services/review-copy.js +165 -0
  480. package/dist/services/review-deferral.js +23 -0
  481. package/dist/services/run-control-receipt.js +56 -0
  482. package/dist/services/run-control-worker.js +428 -0
  483. package/dist/services/run-control.js +1092 -0
  484. package/dist/services/run-controller-adapter.js +400 -0
  485. package/dist/services/run-controller-capture.js +186 -0
  486. package/dist/services/run-controller-process.js +205 -0
  487. package/dist/services/run-controller-recovery.js +295 -0
  488. package/dist/services/run-controller-state.js +97 -0
  489. package/dist/services/run-controller.js +1395 -0
  490. package/dist/services/run-engine-bindings.js +1 -1
  491. package/dist/services/run-fork.js +147 -0
  492. package/dist/services/run-health.js +149 -0
  493. package/dist/services/run-observations.js +152 -0
  494. package/dist/services/run-projection.js +22 -64
  495. package/dist/services/run-recovery-guard.js +107 -0
  496. package/dist/services/run-recovery-runtime.js +112 -0
  497. package/dist/services/run-recovery.js +456 -0
  498. package/dist/services/run-start-custody.js +189 -0
  499. package/dist/services/runs.js +1023 -246
  500. package/dist/services/runtime-budget.js +446 -0
  501. package/dist/services/runtime-command.js +43 -0
  502. package/dist/services/runtime-scope-capture.js +52 -0
  503. package/dist/services/runtime-scope-control.js +468 -0
  504. package/dist/services/runtime-scope-resume.js +605 -0
  505. package/dist/services/runtime-scope-stop.js +211 -0
  506. package/dist/services/runtime-scope-worker.js +314 -0
  507. package/dist/services/runtime-service.js +19 -14
  508. package/dist/services/schema-validation.js +10 -8
  509. package/dist/services/scope-writer-cleanup.js +27 -0
  510. package/dist/services/sessions.js +36 -91
  511. package/dist/services/setup.js +427 -0
  512. package/dist/services/stage-blocker.js +17 -7
  513. package/dist/services/stage-context.js +240 -18
  514. package/dist/services/stage-handlers.js +89 -0
  515. package/dist/services/stage-lifecycle.js +152 -116
  516. package/dist/services/stage-pause.js +202 -91
  517. package/dist/services/stage-report-renderer.js +234 -6
  518. package/dist/services/stage-work-graph.js +52 -0
  519. package/dist/services/tooling.js +41 -30
  520. package/dist/services/usage.js +49 -101
  521. package/dist/services/verification-matrix.js +393 -0
  522. package/dist/services/version-status.js +1 -1
  523. package/dist/services/vnext-code-review.js +235 -88
  524. package/dist/services/vnext-code.js +327 -226
  525. package/dist/services/vnext-contracts.js +10 -1
  526. package/dist/services/vnext-execution-profile.js +80 -5
  527. package/dist/services/vnext-fanout.js +355 -21
  528. package/dist/services/vnext-merge.js +332 -111
  529. package/dist/services/vnext-plan-review.js +287 -132
  530. package/dist/services/vnext-plan.js +228 -104
  531. package/dist/services/vnext-protocolize.js +533 -95
  532. package/dist/services/vnext-specify.js +123 -81
  533. package/dist/services/vnext-workspace-policy.js +7 -2
  534. package/dist/services/work-registry.js +1706 -200
  535. package/dist/services/workspace-bootstrap.js +53 -15
  536. package/dist/services/workspace-files.js +85 -0
  537. package/dist/services/worktrees.js +105 -48
  538. package/dist/shared/entity-references.js +1 -0
  539. package/dist/shared/errors.js +27 -0
  540. package/dist/storage/agent-output.js +26 -0
  541. package/dist/storage/data-preservation.js +65 -0
  542. package/dist/storage/database.js +930 -40
  543. package/dist/storage/foreign-key-transaction.js +39 -0
  544. package/dist/storage/immutable-input.js +45 -0
  545. package/dist/storage/import-guard.js +159 -0
  546. package/dist/storage/memory-upgrade-schema-bootstrap.js +38 -0
  547. package/dist/storage/native-cleanup.js +52 -0
  548. package/dist/storage/paired-writer-migration.js +424 -0
  549. package/dist/storage/paths.js +17 -4
  550. package/dist/storage/projection-lock.js +73 -0
  551. package/dist/storage/work-references.js +23 -0
  552. package/dist/storage/writer-contract.js +96 -0
  553. package/dist/storage/writer-migration.js +346 -0
  554. package/dist/storage/writer-restore.js +217 -0
  555. package/dist/verification-components.json +299 -0
  556. package/package.json +51 -13
  557. package/tools/create-canon-release.mjs +33 -0
  558. package/tools/probe-codex-history.mjs +37 -0
  559. package/tools/produce-hybrid-release.mjs +41 -0
  560. package/tools/repair-paused-run-status.mjs +84 -0
@@ -1,36 +1,54 @@
1
+ import { isControllerManagedInvocation, managedLifecycleCommand } from "./lifecycle-invocations.js";
2
+ import { stageSuccessor } from "./vnext-contracts.js";
1
3
  import crypto from "node:crypto";
4
+ import { assertDeliveredHitlReferences } from "./accepted-intent.js";
2
5
  import fs from "node:fs";
6
+ import os from "node:os";
3
7
  import path from "node:path";
4
- import { AppError } from "../shared/errors.js";
8
+ import { parse as parseYaml } from "yaml";
9
+ import { AppError, errorRecord, redactDiagnostic, diagnosticJson } from "../shared/errors.js";
5
10
  import { projectFeatureWorktreePath, resolveProjectRoot } from "../storage/paths.js";
6
11
  import { previewNextEntityId } from "./ids.js";
7
12
  import { registerProtocol } from "./protocols.js";
8
13
  import { requireProjectByRoot } from "./projects.js";
9
- import { advanceFlowRun, appendFlowRunTimelineEvent, attachFlowRunStage, completeFlowRunStage, gitFacts, rebindFlowRunWorkspace } from "./runs.js";
14
+ import { advanceFlowRun, appendFlowRunTimelineEvent, attachFlowRunStage, completeFlowRunStage, prepareFlowRunStageAttachment, prepareFlowRunStageCompletion, withStageSettlement, gitFacts, rebindFlowRunWorkspace, publishRunProjections } from "./runs.js";
15
+ import { resolveStageTransition, stageExecutionCwd } from "./execution-policy.js";
10
16
  import { validateSchema } from "./schema-validation.js";
11
17
  import { assertStageStartHookEvent, hookSessionIdentity } from "./hooks.js";
12
18
  import { bindStageCoordinatorWork, refreshRunWorkProjection } from "./work-registry.js";
13
19
  import { runTargetedMemoryBankLint } from "./stage-lifecycle.js";
14
- import { bootstrapManagedWorktree, createManagedWorktree, recordManagedWorktree } from "./worktrees.js";
20
+ import { bootstrapManagedWorktree, createManagedWorktree, prepareManagedWorktree, prepareManagedWorktreeBootstrapSources, recordManagedWorktree } from "./worktrees.js";
15
21
  import { loadVnextWorkspacePolicy, requireVnextWorkspaceRoute, vnextRunBranch } from "./vnext-workspace-policy.js";
16
22
  import { readVnextSpecifyResult } from "./vnext-specify.js";
17
23
  import { flowCommand, stagePauseCommand, stagePauseCommandTemplate } from "./stage-pause.js";
18
- import { writeStageReport } from "./stage-report-renderer.js";
19
- import { applyExternalStageContext } from "./stage-context.js";
24
+ import { acceptStageReport, prepareStageReport, publishStageReport } from "./stage-report-renderer.js";
25
+ import { hybridDigest } from "./hybrid-contracts.js";
26
+ import { assertRunMutationAllowed } from "./run-recovery.js";
27
+ import { currentStagePrompt, applyExternalStageContext, prepareExternalStageContext, renderRunOperationalContext } from "./stage-context.js";
28
+ import { prepareJsonSchema, prepareOutputFile } from "../cli/input-preparation.js";
29
+ import { stageLifecycleInstruction } from "../harness-runtime/lib/delegation-instructions.mjs";
30
+ import { withProjectionLock } from "../storage/projection-lock.js";
20
31
  const stage = "protocolize";
21
- function stageSessionMode(context, run) {
32
+ function stageSessionMode(context, run, projectRoot, targetWorkspaceRoot) {
22
33
  const row = context.db.get("SELECT index_json FROM runs WHERE project_id = ? AND id = ?", [run.project_id, run.id]);
23
34
  const value = row ? JSON.parse(row.index_json) : null;
24
- const mode = value?.execution_profile?.settings?.stage_session_mode;
35
+ const profile = value?.execution_profile;
36
+ const mode = profile?.settings.stage_session_mode;
25
37
  if (mode !== "same_session" && mode !== "new_session")
26
38
  throw new AppError("execution_profile_invalid", "RUN is missing its frozen stage session mode", 1, { run_id: run.id });
27
- return mode;
39
+ if (profile?.settings.execution) {
40
+ if (!profile.agent_profiles)
41
+ throw new AppError("execution_profile_not_frozen", "Stage handoff requires frozen RUN profiles", 1);
42
+ return resolveStageTransition({ ddFlowHome: context.ddFlowHome, policy: profile.settings.execution, profiles: profile.agent_profiles, projectRoot, workspaceRoot: targetWorkspaceRoot, fromStage: "protocolize", stage: "plan", stageSessionMode: mode, mergeMode: profile.settings.merge_mode }).session_mode;
43
+ }
44
+ return stageExecutionCwd({ stage: "protocolize", projectRoot, workspaceRoot: targetWorkspaceRoot })
45
+ === stageExecutionCwd({ stage: "plan", projectRoot, workspaceRoot: targetWorkspaceRoot }) ? mode : "new_session";
28
46
  }
29
47
  export function isVnextProtocolizeRun(context, input) {
30
48
  const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
31
49
  return Boolean(context.db.get("SELECT id FROM runs WHERE project_id = ? AND id = ? AND flow_kind = 'vnext_protocolize'", [project.id, input.runId]));
32
50
  }
33
- export function prepareVnextProtocolize(context, input) {
51
+ export function prepareVnextProtocolizeArtifacts(context, input) {
34
52
  const projectRoot = resolveProjectRoot(input.projectRoot);
35
53
  const project = requireProjectByRoot(context, projectRoot);
36
54
  const run = requireRun(context, projectRoot, input.runId);
@@ -42,45 +60,57 @@ export function prepareVnextProtocolize(context, input) {
42
60
  const specifyPath = path.join(runHome, "01-specify", "specify.json");
43
61
  const specifyMarkdownPath = path.join(runHome, "01-specify", "specify.md");
44
62
  const obligations = acceptedSpecifyObligations(context, projectRoot, run.id, specifyPath);
45
- fs.mkdirSync(stageRoot, { recursive: true });
46
63
  const resultPath = path.join(stageRoot, "protocolize-result.json");
47
64
  const promptPath = path.join(stageRoot, "prompt.md");
48
- const policy = provisionWorkspaceForProtocolize(context, projectRoot, project.id, run, stageRoot);
65
+ const templatePath = path.join(projectRoot, ".memory-bank", "dd-flow", "vnext", "protocolize.md");
66
+ if (!fs.existsSync(templatePath))
67
+ throw new AppError("runtime_artifact_missing", "vNext PROTOCOLIZE prompt template is missing", 1, { path: templatePath });
68
+ const template = fs.readFileSync(templatePath, "utf8");
69
+ const projectCatalog = catalog(projectRoot);
70
+ if (!["same_session", "new_session"].includes(input.handoff?.stage_handoff?.effective))
71
+ throw new AppError("runtime_context_invalid", "PROTOCOLIZE requires a frozen handoff policy", 1);
72
+ for (const file of [promptPath, path.join(stageRoot, "work-context.json"), ...(!fs.existsSync(resultPath) ? [resultPath] : [])])
73
+ prepareOutputFile(file);
74
+ const workspace = prepareWorkspaceForProtocolize(context, projectRoot, project.id, run, stageRoot);
75
+ return { projectRoot, project, run, work, stageRoot, specifyPath, specifyMarkdownPath, obligations, resultPath, promptPath, template, projectCatalog, workspace };
76
+ }
77
+ /** Materialize the next-stage packet; this is execution, not read-only CLI preparation. */
78
+ export async function prepareVnextProtocolize(context, input, prepared = prepareVnextProtocolizeArtifacts(context, input)) {
79
+ const { projectRoot, project, run, work, stageRoot, specifyPath, specifyMarkdownPath, obligations, resultPath, promptPath, template, projectCatalog } = prepared;
80
+ fs.mkdirSync(stageRoot, { recursive: true });
81
+ const policy = await provisionWorkspaceForProtocolize(context, projectRoot, run, prepared.workspace);
49
82
  const routedRun = requireRun(context, projectRoot, run.id);
50
- writeJson(path.join(stageRoot, "work-context.json"), { schema_id: "dd-flow/vnext-protocolize-context@2", system: { run_id: run.id, work_id: work.work_id, stage, handoff: input.handoff }, input: { accepted_specify_json: specifyPath, accepted_specify_markdown: specifyMarkdownPath }, catalog: catalog(projectRoot), workspace: { project_root: projectRoot, workspace_root: routedRun.workspace_root, stage_root: stageRoot, git_policy: policy } });
83
+ writeJson(path.join(stageRoot, "work-context.json"), { schema_id: "dd-flow/vnext-protocolize-context@2", system: { run_id: run.id, work_id: work.work_id, stage, handoff: input.handoff }, input: { accepted_specify_json: specifyPath, accepted_specify_markdown: specifyMarkdownPath }, catalog: projectCatalog, workspace: { project_root: projectRoot, workspace_root: routedRun.workspace_root, stage_root: stageRoot, git_policy: policy } });
51
84
  if (!fs.existsSync(resultPath))
52
85
  writeJson(resultPath, resultTemplate(obligations));
53
- const templatePath = path.join(projectRoot, ".memory-bank", "dd-flow", "vnext", "protocolize.md");
54
- if (!fs.existsSync(templatePath))
55
- throw new AppError("not_found", "vNext PROTOCOLIZE prompt template is missing", 1, { path: templatePath });
56
86
  const pauseCommand = stagePauseCommand(context, { runId: run.id, stage, workId: work.work_id, projectRoot });
57
- const prompt = renderPrompt({ projectRoot, runId: run.id, workId: work.work_id, specifyPath, obligations, resultPath, handoff: input.handoff, catalog: catalog(projectRoot), policy, template: fs.readFileSync(templatePath, "utf8"), pauseCommand, pauseCommandTemplate: stagePauseCommandTemplate(pauseCommand), flow: flowCommand(context) });
58
- fs.writeFileSync(promptPath, prompt);
59
- // Preparation is deliberately non-productive: the standalone stage-start
60
- // command atomically installs external context, binds the worker and opens
61
- // the lifecycle Stage. Do not make this prompt an implicit running stage.
87
+ const prompt = renderPrompt({ projectRoot, runId: run.id, workId: work.work_id, specifyPath, obligations, resultPath, handoff: input.handoff, catalog: projectCatalog, policy, template, pauseCommand, pauseCommandTemplate: stagePauseCommandTemplate(pauseCommand), finishCommand: managedLifecycleCommand(context, `${flowCommand(context)} stage finish ${run.id} --stage protocolize --project-root ${JSON.stringify(projectRoot)} --result-file ${JSON.stringify(resultPath)} --json`) });
88
+ const workspaceContract = ["<workspace_contract>", `- stage artifact directory: ${stageRoot}`, `- provisioned project workspace for later stages: ${policy.worktree_path ?? projectRoot}`, `- this PROTOCOLIZE Session remains in its launch directory: ${projectRoot}`, `Write only the named result file in the stage artifact directory; do not write project files, another RUN, another native home, branches, worktrees or Git commits. Do not change the Session directory to the newly provisioned worktree.`, "The CLI performs all protocol document materialization after the result is accepted.", "</workspace_contract>"].join("\n");
89
+ const profile = JSON.parse(context.db.get("SELECT index_json FROM runs WHERE id = ? AND project_id = ?", [run.id, project.id]).index_json).execution_profile;
90
+ const operational = profile?.operational_contract ? renderRunOperationalContext(requireRunHome(run), profile.operational_contract.sha256, profile.operational_contract.path) : "";
91
+ const promptWithWorkspaceContract = `${operational}\n\n${prompt.replace("</work_context>", `</work_context>\n\n${workspaceContract}`)}`;
92
+ fs.writeFileSync(promptPath, promptWithWorkspaceContract);
93
+ // Packet publication does not itself bind a Session or open the Stage.
62
94
  appendFlowRunTimelineEvent(context, project.id, run.id, { type: "stage_prepared", work_id: work.work_id, stage, handoff_mode: input.handoff.stage_handoff.effective });
63
- const directive = input.handoff.stage_handoff.effective === "same_session" ? { kind: "continue_current_session", stage, prompt_path: promptPath, worker_prompt_markdown: prompt } : { kind: "await_new_session", stage, work_id: work.work_id, prompt_path: promptPath, resume_command: `${flowCommand(context)} stage start ${run.id} --stage protocolize --project-root ${JSON.stringify(projectRoot)} --json` };
95
+ // The controller has already selected and bound this Stage's Session. The
96
+ // frozen handoff remains evidence, not authority to open another Session.
97
+ const currentSession = input.handoff.stage_handoff.effective === "same_session" || isControllerManagedInvocation(context, projectRoot, run.id);
98
+ const directive = currentSession ? { kind: "continue_current_session", stage, prompt_path: promptPath, worker_prompt_markdown: promptWithWorkspaceContract } : { kind: "await_new_session", stage, work_id: work.work_id, prompt_path: promptPath, resume_command: managedLifecycleCommand(context, `${flowCommand(context)} stage start ${run.id} --stage protocolize --project-root ${JSON.stringify(projectRoot)} --json`) };
64
99
  return { directive, prompt_path: promptPath };
65
100
  }
66
- export function startVnextProtocolize(context, input) {
101
+ export function prepareVnextProtocolizeStart(context, input) {
67
102
  const projectRoot = resolveProjectRoot(input.projectRoot);
68
103
  const project = requireProjectByRoot(context, projectRoot);
69
104
  const run = requireRun(context, projectRoot, input.runId);
70
- assertStageStartHookEvent(context, { projectId: project.id, eventKey: input.hookEventId, runId: run.id, stage, projectRoot, ...(input.contextSha256 ? { contextSha256: input.contextSha256 } : {}) });
71
105
  const work = context.db.get("SELECT * FROM works WHERE project_id = ? AND run_id = ? AND parent_work_id IS NULL ORDER BY created_at LIMIT 1", [project.id, run.id]);
72
106
  if (!work || work.status !== "running")
73
107
  throw new AppError("invalid_work_state", "PROTOCOLIZE is not ready for an Agent WorkSession", 1, { run_id: run.id });
74
108
  const protocolizeRoot = path.join(requireRunHome(run), "02-protocolize");
75
109
  const contextPath = path.join(protocolizeRoot, "work-context.json");
76
- if (!fs.existsSync(contextPath)) {
77
- const handoff = specifyHandoff(path.join(requireRunHome(run), "01-specify", "work-context.json"), work.work_id);
78
- prepareVnextProtocolize(context, { projectRoot, runId: run.id, workId: work.work_id, handoff });
79
- }
80
- const handoff = JSON.parse(fs.readFileSync(contextPath, "utf8"));
81
- const frozenContext = JSON.parse(fs.readFileSync(contextPath, "utf8"));
82
- const previous = context.db.get("SELECT ws.session_id, s.stop_reason FROM work_sessions ws LEFT JOIN sessions s ON s.project_id = ? AND s.session_id = ws.session_id WHERE ws.work_id = ? ORDER BY ws.created_at DESC LIMIT 1", [project.id, work.work_id]);
83
- const activeWorkSession = context.db.get("SELECT * FROM work_sessions WHERE work_id = ? AND status = 'running' ORDER BY created_at DESC LIMIT 1", [work.work_id]);
110
+ const frozenContext = fs.existsSync(contextPath) ? readProtocolizeContext(contextPath) : null;
111
+ const handoff = frozenContext?.system?.handoff ?? specifyHandoff(path.join(requireRunHome(run), "01-specify", "work-context.json"), work.work_id);
112
+ const packetInput = { projectRoot, runId: run.id, workId: work.work_id, handoff };
113
+ const packet = frozenContext ? null : prepareVnextProtocolizeArtifacts(context, packetInput);
84
114
  const stageAttached = (() => {
85
115
  const row = context.db.get("SELECT index_json FROM runs WHERE project_id = ? AND id = ?", [project.id, run.id]);
86
116
  try {
@@ -88,12 +118,35 @@ export function startVnextProtocolize(context, input) {
88
118
  return index.stage_runs?.some((item) => item.stage === stage) ?? false;
89
119
  }
90
120
  catch {
91
- return false;
121
+ throw new AppError("runtime_artifact_invalid", "RUN stage index is malformed", 1);
92
122
  }
93
123
  })();
94
- const mode = handoff.system?.handoff?.stage_handoff.effective;
124
+ if (!stageAttached)
125
+ prepareFlowRunStageAttachment(context, { projectRoot, runId: run.id, stage, dir: "02-protocolize", status: "running", dataSchemaId: "dd-flow/vnext-protocolize-result@3" });
126
+ const mode = handoff.stage_handoff.effective;
95
127
  const promptPath = path.join(protocolizeRoot, "prompt.md");
96
- const externalContext = applyExternalStageContext({ stageRoot: protocolizeRoot, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
128
+ if (frozenContext)
129
+ fs.readFileSync(promptPath, "utf8");
130
+ if (input.externalContext) {
131
+ prepareExternalStageContext({ stageRoot: protocolizeRoot, loaded: input.externalContext });
132
+ prepareOutputFile(promptPath);
133
+ }
134
+ return { projectRoot, project, run, work, protocolizeRoot, contextPath, frozenContext, packetInput, packet, stageAttached, mode, promptPath };
135
+ }
136
+ function readProtocolizeContext(file) {
137
+ try {
138
+ const value = JSON.parse(fs.readFileSync(file, "utf8"));
139
+ if (["same_session", "new_session"].includes(value?.system?.handoff?.stage_handoff?.effective))
140
+ return value;
141
+ }
142
+ catch { /* retained context is not a caller argument */ }
143
+ throw new AppError("runtime_context_invalid", "PROTOCOLIZE context is missing a valid frozen handoff", 1, { file });
144
+ }
145
+ export async function startVnextProtocolize(context, input, prepared = prepareVnextProtocolizeStart(context, input)) {
146
+ const { projectRoot, project, run, work, protocolizeRoot, contextPath, packetInput, packet, stageAttached, mode, promptPath } = prepared;
147
+ assertStageStartHookEvent(context, { projectId: project.id, eventKey: input.hookEventId, runId: run.id, stage, projectRoot, ...(input.contextSha256 ? { contextSha256: input.contextSha256 } : {}) });
148
+ const previous = context.db.get("SELECT ws.session_id, s.stop_reason FROM work_sessions ws LEFT JOIN sessions s ON s.project_id = ? AND s.session_id = ws.session_id WHERE ws.work_id = ? ORDER BY ws.created_at DESC LIMIT 1", [project.id, work.work_id]);
149
+ const activeWorkSession = context.db.get("SELECT * FROM work_sessions WHERE work_id = ? AND status = 'running' ORDER BY created_at DESC LIMIT 1", [work.work_id]);
97
150
  const sessionId = hookSessionIdentity(context, project.id, input.hookEventId).sessionId;
98
151
  // A stage-entry eval snapshot deliberately stops the canonical binding. Its
99
152
  // Subject starts from a fork of the captured starter Session, so the stale
@@ -103,6 +156,10 @@ export function startVnextProtocolize(context, input) {
103
156
  throw new AppError("handoff_session_mismatch", "Project policy requires PROTOCOLIZE to continue in the SPECIFY session", 1);
104
157
  if (mode === "new_session" && previous?.session_id === sessionId)
105
158
  throw new AppError("handoff_session_mismatch", "Project policy requires PROTOCOLIZE to start in a fresh session", 1);
159
+ if (packet)
160
+ await prepareVnextProtocolize(context, packetInput, packet);
161
+ const frozenContext = prepared.frozenContext ?? readProtocolizeContext(contextPath);
162
+ const externalContext = applyExternalStageContext({ context, stageRoot: protocolizeRoot, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
106
163
  const binding = bindStageCoordinatorWork(context, { workId: work.work_id, hookEventId: input.hookEventId, stage, promptPath, resultPath: path.join(requireRunHome(run), "02-protocolize", "stage-report.json"), ...(input.contextSha256 ? { contextSha256: input.contextSha256 } : {}) });
107
164
  // Same-session handoff deliberately reuses the root Work/Session that
108
165
  // SPECIFY opened. That is not evidence that PROTOCOLIZE is already attached.
@@ -110,11 +167,11 @@ export function startVnextProtocolize(context, input) {
110
167
  if (!stageAttached)
111
168
  attachFlowRunStage(context, { projectRoot, runId: run.id, stage, dir: "02-protocolize", status: "running", dataSchemaId: "dd-flow/vnext-protocolize-result@3" });
112
169
  if (mode === "same_session" && activeWorkSession?.session_id === sessionId) {
113
- return { ok: true, outcome: "work_session_required", run_id: run.id, work_id: work.work_id, id: activeWorkSession.id, prompt_path: promptPath, git_policy: frozenContext.workspace?.git_policy ?? null, worker_prompt_markdown: fs.readFileSync(promptPath, "utf8"), ...(externalContext ? { external_context: externalContext } : {}) };
170
+ return { ok: true, outcome: "work_session_required", run_id: run.id, work_id: work.work_id, id: activeWorkSession.id, git_policy: frozenContext.workspace?.git_policy ?? null, ...currentStagePrompt(context, promptPath), ...(externalContext ? { external_context: externalContext } : {}) };
114
171
  }
115
172
  const workSessionId = String(binding.work_session_id);
116
173
  appendFlowRunTimelineEvent(context, project.id, run.id, { type: "work_session_started", work_id: work.work_id, id: workSessionId, stage, session_id: sessionId });
117
- return { ok: true, outcome: "work_session_required", run_id: run.id, work_id: work.work_id, id: workSessionId, prompt_path: promptPath, git_policy: frozenContext.workspace?.git_policy ?? null, worker_prompt_markdown: fs.readFileSync(promptPath, "utf8"), ...(externalContext ? { external_context: externalContext } : {}) };
174
+ return { ok: true, outcome: "work_session_required", run_id: run.id, work_id: work.work_id, id: workSessionId, git_policy: frozenContext.workspace?.git_policy ?? null, ...currentStagePrompt(context, promptPath), ...(externalContext ? { external_context: externalContext } : {}) };
118
175
  }
119
176
  function specifyHandoff(file, workId) {
120
177
  try {
@@ -125,7 +182,7 @@ function specifyHandoff(file, workId) {
125
182
  catch { /* reported below */ }
126
183
  throw new AppError("runtime_context_invalid", "SPECIFY Work is missing its immutable handoff policy", 1, { work_id: workId });
127
184
  }
128
- export function finishVnextProtocolize(context, input) {
185
+ export async function finishVnextProtocolize(context, input) {
129
186
  const projectRoot = resolveProjectRoot(input.projectRoot);
130
187
  const project = requireProjectByRoot(context, projectRoot);
131
188
  const run = requireRun(context, projectRoot, input.runId);
@@ -133,44 +190,262 @@ export function finishVnextProtocolize(context, input) {
133
190
  if (!work)
134
191
  throw new AppError("not_found", "RUN has no PROTOCOLIZE Work", 1, { run_id: run.id });
135
192
  const root = path.join(requireRunHome(run), "02-protocolize");
136
- const resultFile = path.resolve(input.resultFile);
137
- inside(root, resultFile);
138
- validateSchema({ schemaName: "vnext-protocolize-result", file: resultFile, projectRoot, ddFlowHome: context.ddFlowHome, runId: run.id, runRoot: requireRunHome(run) });
139
- const result = readResult(resultFile);
140
- if (result.outcome !== "protocolized")
141
- throw new AppError("validation", "PROTOCOLIZE may finish only as protocolized; use stage pause for every user question", 2);
193
+ const resultFile = prepareProtocolizeResultPath(requireRunHome(run), input.resultFile);
194
+ const result = input.result ?? readResult(resultFile, { projectRoot, ddFlowHome: context.ddFlowHome, runId: run.id, runRoot: requireRunHome(run) });
195
+ validateSchema({ schemaName: "vnext-protocolize-result", file: resultFile, data: result, projectRoot, ddFlowHome: context.ddFlowHome, runId: run.id, runRoot: requireRunHome(run) });
196
+ const acceptedIndex = JSON.parse(context.db.get("SELECT index_json FROM runs WHERE project_id = ? AND id = ?", [project.id, run.id]).index_json);
197
+ if (acceptedIndex.stage_runs.some(item => item.stage === stage && item.status === "done")) {
198
+ publishStageReport(context, project.id, run.id, stage, root);
199
+ const file = path.join(root, "stage-report.json");
200
+ if (!fs.existsSync(file))
201
+ throw new AppError("protocolize_report_missing", "Completed PROTOCOLIZE has no accepted report to replay", 1, { run_id: run.id });
202
+ const accepted = JSON.parse(fs.readFileSync(file, "utf8"));
203
+ const authoredSha = accepted.semantic.materialization?.authored_result?.sha256;
204
+ if (authoredSha && authoredSha !== sha256(fs.readFileSync(resultFile)))
205
+ throw new AppError("protocolize_intent_conflict", "Completed PROTOCOLIZE result differs from accepted input", 1, { run_id: run.id });
206
+ const currentStage = acceptedIndex.current_stage ?? [...acceptedIndex.stage_runs].reverse().find(item => item.status === "running" || item.status === "done")?.stage;
207
+ const activeReplay = currentStage === stage && acceptedIndex.status === "running";
208
+ const resolvedPolicy = activeReplay ? requireVnextWorkspaceRoute(context, { projectRoot, runId: run.id, runHome: requireRunHome(run), workspaceRoot: run.workspace_root, stage: "protocolize" }) : accepted.mechanical?.workspace_route;
209
+ if (activeReplay) {
210
+ if (acceptedIndex.verdict !== "protocolized")
211
+ advanceFlowRun(context, { settlementStage: "protocolize", projectRoot, runId: run.id, status: "running", verdict: "protocolized", nextAction: "start_plan" });
212
+ refreshRunWorkProjection(context, project.id, run.id);
213
+ }
214
+ const next = activeReplay ? stageSuccessor(context, { projectRoot, runId: run.id, stage: "plan", command: `${flowCommand(context)} stage start ${run.id} --stage plan --project-root ${JSON.stringify(projectRoot)} --json`, metadata: { cwd: run.workspace_root, session_mode: resolvedPolicy.stage_session_mode } }) : { kind: "complete", outcome: "already_completed" };
215
+ return { ok: true, outcome: "protocolized", run_id: run.id, protocol_ids: accepted.semantic.acceptance, workspace_root: run.workspace_root, git_policy: resolvedPolicy, next_action: activeReplay ? "await_plan" : acceptedIndex.next_action, ...("command" in next && next.command ? { next_command: next.command } : {}), next,
216
+ artifacts: { result: resultFile, report_json: file, report_markdown: path.join(root, "stage-report.md"), report_html: path.join(root, "stage-report.html"), protocols: accepted.semantic.acceptance.map(id => path.join(run.workspace_root, ".memory-bank/protocol", id, "summary.md")) } };
217
+ }
218
+ const resolvedPolicy = requireVnextWorkspaceRoute(context, { projectRoot, runId: run.id, runHome: requireRunHome(run), workspaceRoot: run.workspace_root, stage: "protocolize" });
142
219
  const acceptedObligations = acceptedSpecifyObligations(context, projectRoot, run.id, path.join(requireRunHome(run), "01-specify", "specify.json"));
143
- const obligations = effectiveObligations(result, acceptedObligations);
144
- validateDelivery(projectRoot, result, obligations);
145
- const protocolIds = result.delivery.members.map((member) => allocateProtocolId(context, projectRoot, member.slug));
146
- const psetId = result.delivery.kind === "protocol_set" ? allocatePsetId(projectRoot, result.delivery.slug) : null;
147
- const resolvedPolicy = requireVnextWorkspaceRoute({ projectRoot, runId: run.id, runHome: requireRunHome(run), workspaceRoot: run.workspace_root, stage: "protocolize" });
220
+ let obligations;
221
+ try {
222
+ if (result.outcome !== "protocolized")
223
+ throw new AppError("validation", "PROTOCOLIZE may finish only as protocolized; use stage pause for every user question", 2);
224
+ assertDeliveredHitlReferences(context, { projectId: project.id, runId: run.id }, (result.obligation_amendments ?? []).map(amendment => amendment.hitl_ref));
225
+ obligations = effectiveObligations(result, acceptedObligations);
226
+ validateDelivery(projectRoot, result, obligations);
227
+ }
228
+ catch (error) {
229
+ if (!(error instanceof AppError) || error.code !== "validation" || error.exitCode !== 2)
230
+ throw error;
231
+ throw new AppError(error.code, error.message, error.exitCode, { ...error.details, phase: "prepare", effect: "no_effect", recoverable: true });
232
+ }
148
233
  const receipt = readWorkspaceRouteReceipt(root);
149
234
  const workspaceRoot = run.workspace_root;
150
- const changedFiles = materialize(workspaceRoot, run.id, requireRunHome(run), result, obligations, protocolIds, psetId);
235
+ const intent = await prepareProtocolizeIntent(context, run, resultFile, result, obligations);
236
+ publishProtocolizeIntent(context, project.id, intent);
237
+ const materialization = verifyProtocolizeMaterialization(intent, result, path.relative(requireRunHome(run), resultFile));
238
+ const protocolIds = intent.protocol_ids;
239
+ const changedFiles = intent.changed_files;
151
240
  for (const protocolId of protocolIds)
152
241
  registerProtocol(context, { handshakeId: protocolId, projectRoot, workspacePath: workspaceRoot });
153
242
  if (receipt.created && receipt.bootstrap) {
154
243
  for (const protocolId of protocolIds)
155
- recordManagedWorktree(context, { protocolId, projectRoot, branch: requiredPolicyValue(resolvedPolicy.feature_branch, "feature branch"), base: requiredPolicyValue(resolvedPolicy.base_ref, "base ref"), created: receipt.created, bootstrap: receipt.bootstrap });
244
+ ensureManagedWorktreeRecorded(context, { protocolId, projectRoot, branch: requiredPolicyValue(resolvedPolicy.feature_branch, "feature branch"), base: requiredPolicyValue(resolvedPolicy.base_ref, "base ref"), created: receipt.created, bootstrap: receipt.bootstrap });
156
245
  }
157
246
  const now = context.now();
158
- const workSession = completeWorkSession(context, work.work_id, resultFile, now);
247
+ const workSession = context.db.get("SELECT * FROM work_sessions WHERE work_id = ? AND status = 'running' ORDER BY created_at DESC LIMIT 1", [work.work_id]);
159
248
  const routedRun = requireRun(context, projectRoot, run.id);
160
249
  const report = stageReport(context, routedRun, resultFile, protocolIds, changedFiles, workSession?.session_id ?? null, now, resolvedPolicy);
250
+ Object.assign(report.semantic, { materialization });
161
251
  const reportJson = path.join(root, "stage-report.json");
162
252
  const reportMarkdown = path.join(root, "stage-report.md");
163
253
  const reportHtml = path.join(root, "stage-report.html");
164
- writeStageReport(root, report);
165
- validateSchema({ schemaName: "stage-report", file: reportJson, projectRoot, ddFlowHome: context.ddFlowHome, runId: run.id, runRoot: requireRunHome(run) });
166
- completeFlowRunStage(context, { projectRoot, runId: run.id, stage, status: "done", data: "protocolize-result.json", dataSchemaId: "dd-flow/vnext-protocolize-result@3", report: "stage-report.md", stageReport: "stage-report.html" });
167
- appendFlowRunTimelineEvent(context, project.id, run.id, { type: "protocol_documents_materialized", work_id: work.work_id, protocol_ids: protocolIds, stage });
168
- advanceFlowRun(context, { projectRoot, runId: run.id, status: "running", verdict: "protocolized", nextAction: "start_plan" });
254
+ const currentIndex = JSON.parse(context.db.get("SELECT index_json FROM runs WHERE project_id = ? AND id = ?", [project.id, run.id]).index_json);
255
+ const stageDone = currentIndex.stage_runs.some(item => item.stage === stage && item.status === "done");
256
+ if (stageDone) {
257
+ publishStageReport(context, project.id, run.id, stage, root);
258
+ if (!fs.existsSync(reportJson))
259
+ throw new AppError("protocolize_report_missing", "Completed PROTOCOLIZE has no accepted report to replay", 1, { run_id: run.id, report_path: reportJson });
260
+ }
261
+ else {
262
+ const preparedReport = prepareStageReport(report);
263
+ validateSchema({ schemaName: "stage-report", file: reportJson, data: preparedReport.normalized, projectRoot, ddFlowHome: context.ddFlowHome, runId: run.id, runRoot: requireRunHome(run) });
264
+ const completion = { projectRoot, runId: run.id, stage, status: "done", data: "protocolize-result.json", dataSchemaId: "dd-flow/vnext-protocolize-result@3", report: "stage-report.md", stageReport: "stage-report.html", dataSha256: crypto.createHash("sha256").update(fs.readFileSync(resultFile)).digest("hex") };
265
+ const preparedCompletion = prepareFlowRunStageCompletion(context, completion);
266
+ withStageSettlement(context, () => {
267
+ completeWorkSession(context, work.work_id, resultFile, now);
268
+ acceptStageReport(context, { projectId: project.id, runId: run.id, stage, root, prepared: preparedReport });
269
+ completeFlowRunStage(context, completion, preparedCompletion);
270
+ });
271
+ appendFlowRunTimelineEvent(context, project.id, run.id, { type: "protocol_documents_materialized", work_id: work.work_id, protocol_ids: protocolIds, stage });
272
+ }
273
+ if (currentIndex.verdict !== "protocolized")
274
+ advanceFlowRun(context, { settlementStage: "protocolize", projectRoot, runId: run.id, status: "running", verdict: "protocolized", nextAction: "start_plan" });
169
275
  refreshRunWorkProjection(context, project.id, run.id);
170
- const nextCommand = `${flowCommand(context)} stage start ${run.id} --stage plan --project-root ${JSON.stringify(projectRoot)} --json`;
171
- return { ok: true, outcome: "protocolized", run_id: run.id, protocol_ids: protocolIds, workspace_root: workspaceRoot, git_policy: resolvedPolicy, next_action: "await_plan", next_command: nextCommand, next: { kind: "start_stage", stage: "plan", command: nextCommand, cwd: workspaceRoot, session_mode: resolvedPolicy.stage_session_mode }, artifacts: { result: resultFile, report_json: reportJson, report_markdown: reportMarkdown, report_html: reportHtml, protocols: protocolIds.map((id) => path.join(workspaceRoot, ".memory-bank", "protocol", id, "summary.md")) } };
276
+ const next = stageSuccessor(context, { projectRoot, runId: run.id, stage: "plan", command: `${flowCommand(context)} stage start ${run.id} --stage plan --project-root ${JSON.stringify(projectRoot)} --json`, metadata: { cwd: workspaceRoot, session_mode: resolvedPolicy.stage_session_mode } });
277
+ return { ok: true, outcome: "protocolized", run_id: run.id, protocol_ids: protocolIds, workspace_root: workspaceRoot, git_policy: resolvedPolicy, next_action: "await_plan", ...(next.command ? { next_command: next.command } : {}), next, artifacts: { result: resultFile, report_json: reportJson, report_markdown: reportMarkdown, report_html: reportHtml, protocols: protocolIds.map((id) => path.join(workspaceRoot, ".memory-bank", "protocol", id, "summary.md")) } };
278
+ }
279
+ async function prepareProtocolizeIntent(context, run, resultFile, result, obligations) {
280
+ const row = context.db.get("SELECT index_json FROM runs WHERE project_id = ? AND id = ?", [run.project_id, run.id]);
281
+ const index = row ? JSON.parse(row.index_json) : null;
282
+ const attempt = index?.stage_runs?.find(item => item.stage === stage)?.attempt;
283
+ if (!attempt)
284
+ throw new AppError("protocolize_owner_missing", "PROTOCOLIZE has no owning Stage attempt", 1, { run_id: run.id });
285
+ const resultSha = sha256(fs.readFileSync(resultFile));
286
+ const retained = readProtocolizeIntent(context, run.project_id, run.id, attempt);
287
+ if (retained) {
288
+ if (retained.workspace_root !== run.workspace_root || retained.result_sha256 !== resultSha)
289
+ throw new AppError("protocolize_intent_conflict", "Accepted PROTOCOLIZE publication belongs to different result or workspace bytes", 1, { run_id: run.id, attempt });
290
+ return retained;
291
+ }
292
+ const projectRoot = run.workspace_root;
293
+ const protocolIds = allocateProtocolIds(context, run.project_id, projectRoot, result.delivery.members.map(member => member.slug));
294
+ const psetId = result.delivery.kind === "protocol_set" ? allocatePsetId(context, run.project_id, projectRoot, result.delivery.slug) : null;
295
+ const draft = fs.mkdtempSync(path.join(os.tmpdir(), "dd-flow-protocolize-"));
296
+ let draftFailure;
297
+ try {
298
+ // A draft must never follow a workspace symlink back into the live tree.
299
+ fs.cpSync(path.join(projectRoot, ".memory-bank"), path.join(draft, ".memory-bank"), { recursive: true, filter: source => !fs.lstatSync(source).isSymbolicLink() });
300
+ const feature = result.feature.epic_path && path.isAbsolute(result.feature.epic_path)
301
+ ? { ...result.feature, epic_path: path.join(draft, path.relative(projectRoot, result.feature.epic_path)) }
302
+ : result.feature;
303
+ const draftResult = { ...result, feature };
304
+ const materialized = await materialize(draft, run.id, requireRunHome(run), draftResult, obligations, protocolIds, psetId);
305
+ const changedFiles = materialized.files;
306
+ const files = changedFiles.map(relative => {
307
+ const target = path.join(draft, relative);
308
+ const bytes = fs.readFileSync(target, "utf8");
309
+ return { relative, before_sha256: materialized.before[path.relative(draft, target)] ?? null, after_sha256: sha256(bytes), bytes };
310
+ });
311
+ const candidate = { run_id: run.id, attempt, workspace_root: projectRoot, result_sha256: resultSha, protocol_ids: protocolIds, pset_id: psetId, changed_files: changedFiles, files };
312
+ return context.db.writeTransaction(() => {
313
+ const raced = readProtocolizeIntent(context, run.project_id, run.id, attempt);
314
+ if (raced) {
315
+ if (raced.workspace_root !== projectRoot || raced.result_sha256 !== resultSha)
316
+ throw new AppError("protocolize_intent_conflict", "A competing PROTOCOLIZE publication was accepted", 1, { run_id: run.id, attempt });
317
+ return raced;
318
+ }
319
+ const reserved = new Set(context.db.all("SELECT payload_json FROM audit_events WHERE project_id = ? AND event_type = 'flow_run.protocolize_intent'", [run.project_id])
320
+ .flatMap(row => { const prior = JSON.parse(row.payload_json); return [...prior.protocol_ids, ...(prior.pset_id ? [prior.pset_id] : [])]; }));
321
+ if ([...protocolIds, ...(psetId ? [psetId] : [])].some(id => reserved.has(id)))
322
+ throw new AppError("protocolize_id_reserved", "Another PROTOCOLIZE attempt reserved the selected document ID; retry to allocate a new one", 1, { run_id: run.id });
323
+ context.db.run("INSERT INTO audit_events (project_id, event_type, forced, payload_json, created_at) VALUES (?, 'flow_run.protocolize_intent', 0, ?, ?)", [run.project_id, JSON.stringify(candidate), context.now()]);
324
+ return candidate;
325
+ });
326
+ }
327
+ catch (error) {
328
+ draftFailure = error;
329
+ throw error;
330
+ }
331
+ finally {
332
+ cleanupProtocolizeDraft(draft, draftFailure);
333
+ }
334
+ }
335
+ function cleanupProtocolizeDraft(draft, failure) {
336
+ try {
337
+ fs.rmSync(draft, { recursive: true, force: true });
338
+ }
339
+ catch (cleanup) {
340
+ if (!failure)
341
+ throw new AppError("protocolize_draft_cleanup_failed", "PROTOCOLIZE draft cleanup failed", 1, { cause: errorRecord(cleanup), draft_path: draft, recoverable: false, effect: "unknown" });
342
+ const primary = failure;
343
+ primary.details = { ...primary.details, draft_cleanup_error: errorRecord(cleanup), draft_path: draft, recoverable: false };
344
+ }
345
+ }
346
+ function readProtocolizeIntent(context, projectId, runId, attempt) {
347
+ const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id = ? AND event_type = 'flow_run.protocolize_intent' ORDER BY id DESC", [projectId]);
348
+ return rows.map(row => JSON.parse(row.payload_json)).find(item => item.run_id === runId && item.attempt === attempt) ?? null;
172
349
  }
173
- function materialize(projectRoot, runId, runHome, result, obligations, ids, psetId) {
350
+ function publishProtocolizeIntent(context, projectId, intent) {
351
+ const memory = path.join(intent.workspace_root, ".memory-bank");
352
+ withProjectionLock(path.join(context.ddFlowHome, "locks", `protocolize-${projectId}.publish-lock`), () => {
353
+ for (const file of intent.files) {
354
+ if (!file.relative.startsWith(".memory-bank/") || sha256(file.bytes) !== file.after_sha256)
355
+ throw new AppError("protocolize_intent_invalid", "Accepted PROTOCOLIZE file is invalid", 1, { relative: file.relative });
356
+ const target = path.resolve(intent.workspace_root, file.relative);
357
+ inside(memory, target);
358
+ assertNoSymlinkPath(memory, target);
359
+ const current = fs.existsSync(target) ? sha256(fs.readFileSync(target)) : null;
360
+ if (current !== file.before_sha256 && current !== file.after_sha256)
361
+ throw new AppError("protocolize_publication_conflict", "PROTOCOLIZE target changed since publication was accepted", 1, { target });
362
+ }
363
+ for (const file of intent.files) {
364
+ const target = path.resolve(intent.workspace_root, file.relative);
365
+ if (fs.existsSync(target) && sha256(fs.readFileSync(target)) === file.after_sha256)
366
+ continue;
367
+ fs.mkdirSync(path.dirname(target), { recursive: true });
368
+ const temporary = `${target}.${crypto.randomUUID()}.tmp`;
369
+ try {
370
+ fs.writeFileSync(temporary, file.bytes, { flag: "wx" });
371
+ if (file.before_sha256 === null) {
372
+ try {
373
+ fs.linkSync(temporary, target);
374
+ }
375
+ catch (error) {
376
+ if (error.code !== "EEXIST" || sha256(fs.readFileSync(target)) !== file.after_sha256)
377
+ throw error;
378
+ }
379
+ }
380
+ else {
381
+ if (sha256(fs.readFileSync(target)) !== file.before_sha256)
382
+ throw new AppError("protocolize_publication_conflict", "PROTOCOLIZE target changed during publication", 1, { target });
383
+ fs.renameSync(temporary, target);
384
+ }
385
+ }
386
+ finally {
387
+ fs.rmSync(temporary, { force: true });
388
+ }
389
+ }
390
+ });
391
+ }
392
+ function assertNoSymlinkPath(memory, target) {
393
+ for (let cursor = memory;; cursor = path.join(cursor, path.relative(cursor, target).split(path.sep)[0])) {
394
+ try {
395
+ if (fs.lstatSync(cursor).isSymbolicLink())
396
+ throw new AppError("protocolize_publication_conflict", "PROTOCOLIZE publication cannot follow a workspace symlink", 1, { target, symlink: cursor });
397
+ }
398
+ catch (error) {
399
+ if (error.code !== "ENOENT")
400
+ throw error;
401
+ }
402
+ if (cursor === target)
403
+ break;
404
+ }
405
+ }
406
+ function ensureManagedWorktreeRecorded(context, input) {
407
+ const existing = context.db.get("SELECT wr.feature_branch, wr.base_ref, wr.worktree_path FROM worktree_records wr JOIN protocols p ON p.project_id = wr.project_id AND p.id = wr.protocol_id WHERE p.project_root = ? AND p.id = ? AND wr.status = 'active' ORDER BY wr.created_at DESC LIMIT 1", [input.projectRoot, input.protocolId]);
408
+ if (existing) {
409
+ if (existing.feature_branch !== input.branch || existing.base_ref !== input.base || existing.worktree_path !== input.created.worktreePath)
410
+ throw new AppError("worktree_already_exists", "Existing managed worktree record conflicts with the accepted PROTOCOLIZE route", 1, { protocol_id: input.protocolId });
411
+ return;
412
+ }
413
+ recordManagedWorktree(context, input);
414
+ }
415
+ function sha256(bytes) { return crypto.createHash("sha256").update(bytes).digest("hex"); }
416
+ export function verifyProtocolizeMaterialization(intent, result, authoredPath) {
417
+ const documents = intent.files.map(file => {
418
+ const target = path.resolve(intent.workspace_root, file.relative);
419
+ inside(path.join(intent.workspace_root, ".memory-bank"), target);
420
+ assertNoSymlinkPath(path.join(intent.workspace_root, ".memory-bank"), target);
421
+ const bytes = fs.readFileSync(target);
422
+ if (sha256(bytes) !== file.after_sha256)
423
+ throw new AppError("protocolize_materialization_invalid", "Published document differs from accepted intent", 1, { path: file.relative });
424
+ return { path: file.relative, sha256: file.after_sha256 };
425
+ });
426
+ const featurePaths = intent.changed_files.filter(file => /\/features\/FT-[^/]+\/index\.md$/.test(file));
427
+ const featurePath = result.feature.action === "not_applicable" ? null : featurePaths.length === 1 ? featurePaths[0] : null;
428
+ if (result.feature.action !== "not_applicable" && !featurePath)
429
+ throw new AppError("protocolize_materialization_invalid", "Accepted feature output is ambiguous or missing", 1);
430
+ const frontmatter = (relative) => {
431
+ const text = fs.readFileSync(path.join(intent.workspace_root, relative), "utf8");
432
+ const match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(text);
433
+ const value = match ? parseYaml(match[1]) : null;
434
+ if (!value || typeof value !== "object" || Array.isArray(value))
435
+ throw new AppError("protocolize_materialization_invalid", "Published document lacks frontmatter", 1, { path: relative });
436
+ return value;
437
+ };
438
+ const edges = featurePath ? intent.protocol_ids.map(protocolId => ({ feature_path: featurePath, protocol_id: protocolId, protocol_path: `.memory-bank/protocol/${protocolId}/summary.md` })) : [];
439
+ for (const edge of edges) {
440
+ const protocols = frontmatter(edge.feature_path).related_protocols;
441
+ const features = frontmatter(edge.protocol_path).related_features;
442
+ if (!Array.isArray(protocols) || !protocols.includes(edge.protocol_id) || !Array.isArray(features) || !features.includes(edge.feature_path))
443
+ throw new AppError("protocolize_materialization_invalid", "Owned feature/protocol links are not reciprocal", 1, edge);
444
+ }
445
+ return { schema_id: "dd-flow/protocolize-materialization@1", run_id: intent.run_id, stage: "protocolize", action: result.feature.action, feature_path: featurePath,
446
+ protocol_ids: intent.protocol_ids, authored_result: { role: "authored_input", run_relative_path: authoredPath, sha256: intent.result_sha256 }, documents, owned_links: edges, status: "passed" };
447
+ }
448
+ async function materialize(projectRoot, runId, runHome, result, obligations, ids, psetId) {
174
449
  const psetPath = psetId ? path.join(projectRoot, ".memory-bank", "protocol", "_set", `${psetId}.md`) : null;
175
450
  const created = [];
176
451
  const changed = new Set();
@@ -208,20 +483,115 @@ function materialize(projectRoot, runId, runHome, result, obligations, ids, pset
208
483
  const absolute = path.resolve(projectRoot, file);
209
484
  return created.some((directory) => absolute.startsWith(`${directory}${path.sep}`)) || absolute === psetPath;
210
485
  });
211
- runTargetedMemoryBankLint(projectRoot, materializedFiles);
212
- return files;
486
+ await runTargetedMemoryBankLint(projectRoot, materializedFiles, path.join(runHome, "diagnostics"));
487
+ return { files, before: Object.fromEntries([...originals].map(([file, bytes]) => [path.relative(projectRoot, file), bytes === null ? null : sha256(bytes)])) };
213
488
  }
214
489
  catch (error) {
215
- for (const [file, content] of originals) {
216
- if (content === null)
217
- fs.rmSync(file, { force: true });
218
- else
219
- fs.writeFileSync(file, content);
490
+ const primary = error instanceof Error ? error : new AppError("protocolize_materialization_failed", String(error));
491
+ const facts = {};
492
+ try {
493
+ facts.diagnostic_path = captureMaterializationFailure(projectRoot, runHome, [...changed].sort(), primary);
220
494
  }
221
- for (const dir of created.reverse())
222
- fs.rmSync(dir, { recursive: true, force: true });
223
- throw error;
495
+ catch (capture) {
496
+ facts.capture_error = errorRecord(capture);
497
+ }
498
+ const rollback = rollbackMaterialization(projectRoot, originals, created);
499
+ const target = primary;
500
+ target.details = { ...target.details, ...facts, rollback,
501
+ ...(rollback.unrestored.length ? { effect: "unknown", recoverable: false, disposition: "tooling_blocker" } : {}) };
502
+ throw primary;
503
+ }
504
+ }
505
+ export function rollbackMaterialization(projectRoot, originals, created) {
506
+ const restored = [], unrestored = [];
507
+ const restore = (file, action) => {
508
+ try {
509
+ action();
510
+ restored.push(path.relative(projectRoot, file));
511
+ }
512
+ catch (cause) {
513
+ unrestored.push({ path: path.relative(projectRoot, file), error: errorRecord(cause) });
514
+ }
515
+ };
516
+ for (const [file, content] of originals)
517
+ restore(file, () => { if (content === null)
518
+ fs.rmSync(file, { force: true });
519
+ else
520
+ fs.writeFileSync(file, content); });
521
+ for (const dir of [...created].reverse())
522
+ restore(dir, () => fs.rmSync(dir, { recursive: true, force: true }));
523
+ return { restored, unrestored };
524
+ }
525
+ export function captureMaterializationFailure(projectRoot, runHome, files, error) {
526
+ const directory = path.join(runHome, "diagnostics", `protocolize-${crypto.randomUUID()}`);
527
+ fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
528
+ let remaining = 1024 * 1024;
529
+ const documents = files.slice(0, 64).map(relative => {
530
+ const absolute = path.resolve(projectRoot, relative);
531
+ if (!absolute.startsWith(`${path.resolve(projectRoot)}${path.sep}`))
532
+ return { path: relative, unavailable: "outside_workspace" };
533
+ try {
534
+ assertNoSymlinkPath(path.resolve(projectRoot), absolute);
535
+ inside(fs.realpathSync(projectRoot), fs.realpathSync(absolute));
536
+ const info = fs.lstatSync(absolute);
537
+ if (!info.isFile())
538
+ return { path: relative, unavailable: "not_regular_file" };
539
+ // ponytail: bounded diagnostic hash, never chase a growing live file.
540
+ // Larger inputs retain a prefix hash explicitly marked incomplete.
541
+ const hash = crypto.createHash("sha256"), fd = fs.openSync(absolute, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
542
+ const chunk = Buffer.alloc(64 * 1024), parts = [];
543
+ let kept = 0, offset = 0, complete = false;
544
+ try {
545
+ const opened = fs.fstatSync(fd);
546
+ if (!opened.isFile() || opened.dev !== info.dev || opened.ino !== info.ino)
547
+ throw new AppError("diagnostic_input_changed", "Diagnostic input changed before capture");
548
+ const limit = Math.min(opened.size, 1024 * 1024);
549
+ while (offset < limit) {
550
+ const read = fs.readSync(fd, chunk, 0, Math.min(chunk.length, limit - offset), offset);
551
+ if (!read)
552
+ break;
553
+ offset += read;
554
+ hash.update(chunk.subarray(0, read));
555
+ if (kept < Math.min(remaining, 65536)) {
556
+ const part = Buffer.from(chunk.subarray(0, Math.min(read, Math.min(remaining, 65536) - kept)));
557
+ parts.push(part);
558
+ kept += part.length;
559
+ }
560
+ }
561
+ const after = fs.fstatSync(fd), current = fs.lstatSync(absolute);
562
+ complete = offset === opened.size && after.size === opened.size && after.mtimeMs === opened.mtimeMs && after.ctimeMs === opened.ctimeMs
563
+ && current.dev === opened.dev && current.ino === opened.ino;
564
+ }
565
+ finally {
566
+ fs.closeSync(fd);
567
+ }
568
+ const content = Buffer.from(redactDiagnostic(Buffer.concat(parts).toString("utf8"))).subarray(0, Math.min(remaining, 65536)).toString("utf8");
569
+ remaining -= Buffer.byteLength(content);
570
+ const digest = hash.digest("hex");
571
+ return { path: relative, sha256: complete ? digest : null, ...(!complete ? { prefix_sha256: digest, hashed_bytes: offset } : {}), hash_complete: complete, size: info.size, truncated: kept < info.size, content };
572
+ }
573
+ catch (cause) {
574
+ return { path: relative, unavailable: errorRecord(cause) };
575
+ }
576
+ });
577
+ const file = path.join(directory, "failure.json");
578
+ const capture = { schema_id: "dd-flow/protocolize-failure@1", error: errorRecord(error), documents, omitted_documents: Math.max(0, files.length - documents.length) };
579
+ let bytes = diagnosticJson(capture);
580
+ while (Buffer.byteLength(bytes) > 1024 * 1024) {
581
+ const contentLength = (document) => "content" in document ? String(document.content ?? "").length : 0;
582
+ const largest = documents.filter(document => contentLength(document) > 0).sort((a, b) => contentLength(b) - contentLength(a))[0];
583
+ if (largest && "content" in largest) {
584
+ largest.content = String(largest.content).slice(0, Math.floor(String(largest.content).length / 2));
585
+ largest.truncated = true;
586
+ }
587
+ else {
588
+ documents.pop();
589
+ capture.omitted_documents++;
590
+ }
591
+ bytes = diagnosticJson(capture);
224
592
  }
593
+ fs.writeFileSync(file, bytes, { flag: "wx", mode: 0o600 });
594
+ return file;
225
595
  }
226
596
  function protocolMarkdown(id, member, blockedBy, runId, psetPath, featurePath, result, obligations, rawIntakePath) {
227
597
  const a = member.primary_acceptance;
@@ -309,21 +679,27 @@ function appendIndexEntry(indexPath, childPath, label) {
309
679
  }
310
680
  function epicRootFor(projectRoot, value) { const resolved = path.resolve(projectRoot, value); const relative = path.relative(projectRoot, resolved); if (relative.startsWith("..") || path.isAbsolute(relative))
311
681
  throw new AppError("path_escape", "epic_path must stay inside the project", 2, { epic_path: value }); return path.basename(resolved) === "index.md" ? path.dirname(resolved) : resolved; }
312
- function readResult(file) {
313
- if (!fs.existsSync(file))
314
- throw new AppError("not_found", "PROTOCOLIZE result is missing", 1, { file });
315
- const value = JSON.parse(fs.readFileSync(file, "utf8"));
316
- return value;
682
+ function readResult(file, context) {
683
+ return prepareJsonSchema(file, "PROTOCOLIZE result", {
684
+ schemaName: "vnext-protocolize-result", projectRoot: context.projectRoot, ddFlowHome: context.ddFlowHome, runId: context.runId, runRoot: context.runRoot
685
+ }).value;
317
686
  }
318
687
  function acceptedSpecifyObligations(context, projectRoot, runId, specifyPath) {
319
688
  if (!fs.existsSync(specifyPath))
320
- throw new AppError("not_found", "PROTOCOLIZE requires accepted specify.json", 1, { path: specifyPath });
321
- validateSchema({ schemaName: "vnext-specify", file: specifyPath, projectRoot, ddFlowHome: context.ddFlowHome, runId, runRoot: requireRunHome(requireRun(context, projectRoot, runId)) });
322
- const specify = readVnextSpecifyResult(specifyPath);
323
- return [
324
- ...specify.requirements.map((obligation) => ({ ...obligation, kind: "requirement" })),
325
- ...specify.acceptance_criteria.map((obligation) => ({ ...obligation, kind: "acceptance" }))
326
- ];
689
+ throw new AppError("runtime_artifact_missing", "PROTOCOLIZE requires accepted specify.json", 1, { path: specifyPath });
690
+ try {
691
+ const specify = readVnextSpecifyResult(specifyPath);
692
+ validateSchema({ schemaName: "vnext-specify", file: specifyPath, data: specify, projectRoot, ddFlowHome: context.ddFlowHome, runId, runRoot: requireRunHome(requireRun(context, projectRoot, runId)) });
693
+ return [
694
+ ...specify.requirements.map((obligation) => ({ ...obligation, kind: "requirement" })),
695
+ ...specify.acceptance_criteria.map((obligation) => ({ ...obligation, kind: "acceptance" }))
696
+ ];
697
+ }
698
+ catch (error) {
699
+ if (!(error instanceof AppError) || !["validation", "schema_validation", "invalid_json"].includes(error.code))
700
+ throw error;
701
+ throw new AppError("runtime_artifact_invalid", "Accepted SPECIFY result is invalid", 1, { path: specifyPath, cause_code: error.code });
702
+ }
327
703
  }
328
704
  /** SPECIFY is historical input; only a HITL-backed protocol amendment may alter its effective wording. */
329
705
  function effectiveObligations(result, accepted) {
@@ -435,18 +811,24 @@ function resultTemplate(obligations = []) {
435
811
  }
436
812
  function renderPrompt(input) {
437
813
  const obligationList = input.obligations.map((obligation) => `- ${obligation.id}: ${obligation.statement}`).join("\n");
438
- return ["<work_context>", `- RUN: ${input.runId}`, `- Work: ${input.workId}`, `- Accepted SPECIFY JSON: ${input.specifyPath}`, `- Handoff: ${input.handoff.stage_handoff.effective}`, "</work_context>", "", "<accepted_obligations>", obligationList, "</accepted_obligations>", "", "<frozen_git_policy>", `- route: ${input.policy.route}`, `- integration branch: ${input.policy.integration_branch ?? "not applicable"}`, `- base ref: ${input.policy.base_ref ?? "not applicable"}`, `- feature branch: ${input.policy.feature_branch ?? "not applicable"}`, `- service worktree: ${input.policy.worktree_path ?? "not applicable"}`, `- bootstrap: ${input.policy.bootstrap_status}`, `- decision: ${input.policy.reason}`, "The CLI already created and bootstrapped this workspace at PROTOCOLIZE start. Do not create branches, worktrees, protocol files or Git commits yourself. This transition Work may run from the stable session; edit only the supplied RUN result file.", "</frozen_git_policy>", "", "<catalog>", input.catalog.length ? input.catalog.map((x) => `- ${x}`).join("\n") : "- inactive", "</catalog>", "", "<stage_instructions>", input.template.trim(), "</stage_instructions>", "", "<output_contract>", `Edit only ${input.resultPath}. Keep this exact JSON shape; CLI allocates ids and writes PRT/PSET/feature documents in the already provisioned workspace:\n\n\`\`\`json\n${JSON.stringify(resultTemplate(), null, 2)}\n\`\`\``, "Allocate every supplied R-* and AC-* exactly once in obligation_ownership. The CLI rejects unknown, duplicate or missing obligations, and every member must own at least one AC-*.", "If PROTOCOLIZE itself exposes a material user decision with no reasonable default, do not finish and do not return to SPECIFY. Run this exact one-command heredoc, replacing only its placeholder body. The heredoc is the permitted stdin form; do not use cat, a pipe, a temporary file or a second shell command:", "```sh", input.pauseCommandTemplate, "```", "Ask the returned user_message and stop. Resume this same PROTOCOLIZE Work using the exact command returned by pause.", `When the answer is incorporated and the result is protocolized, finish:\n\n${input.flow} stage finish ${input.runId} --stage protocolize --project-root ${JSON.stringify(input.projectRoot)} --result-file ${JSON.stringify(input.resultPath)} --json`, "</output_contract>", ""].join("\n");
814
+ return ["<work_context>", `- RUN: ${input.runId}`, `- Work: ${input.workId}`, `- Accepted SPECIFY JSON: ${input.specifyPath}`, `- Handoff: ${input.handoff.stage_handoff.effective}`, "</work_context>", "", "<accepted_obligations>", obligationList, "</accepted_obligations>", "", "<frozen_git_policy>", `- route: ${input.policy.route}`, `- integration branch: ${input.policy.integration_branch ?? "not applicable"}`, `- base ref: ${input.policy.base_ref ?? "not applicable"}`, `- feature branch: ${input.policy.feature_branch ?? "not applicable"}`, `- service worktree: ${input.policy.worktree_path ?? "not applicable"}`, `- bootstrap: ${input.policy.bootstrap_status}`, `- decision: ${input.policy.reason}`, "The CLI already created and bootstrapped this workspace at PROTOCOLIZE start. Do not create branches, worktrees, protocol files or Git commits yourself. This transition Work may run from the stable session; edit only the supplied RUN result file.", "</frozen_git_policy>", "", "<catalog>", input.catalog.length ? input.catalog.map((x) => `- ${x}`).join("\n") : "- inactive", "</catalog>", "", "<stage_instructions>", input.template.trim(), "</stage_instructions>", "", "<output_contract>", `Edit only ${input.resultPath}. Keep this exact JSON shape; CLI allocates ids and writes PRT/PSET/feature documents in the already provisioned workspace:\n\n\`\`\`json\n${JSON.stringify(resultTemplate(input.obligations), null, 2)}\n\`\`\``, "obligation_id values and their one-per-obligation entries are prefilled by the CLI. Preserve those IDs exactly; change only member_keys when semantic ownership requires it. The CLI rejects unknown, duplicate or missing obligations, and every member must own at least one AC-*.", "If PROTOCOLIZE itself exposes a material user decision with no reasonable default, do not finish and do not return to SPECIFY. Write the question packet to the named text file, then run the separate standalone lifecycle command below. Do not combine file creation and dd-flow in one shell command:", "```sh", input.pauseCommandTemplate, "```", "Ask the returned user_message and stop. Resume this same PROTOCOLIZE Work using the exact command returned by pause.", `When the answer is incorporated and the result is protocolized, finish:\n\n${input.finishCommand}`, stageLifecycleInstruction, "</output_contract>", ""].join("\n");
439
815
  }
440
- function provisionWorkspaceForProtocolize(context, projectRoot, projectId, run, stageRoot) {
816
+ function prepareWorkspaceForProtocolize(context, projectRoot, projectId, run, stageRoot) {
817
+ // A committed rebind may have survived a projection crash. Repair only from
818
+ // its SQL authority before considering physical creation again.
819
+ const stored = context.db.get("SELECT index_json FROM runs WHERE project_id = ? AND id = ?", [projectId, run.id]);
820
+ if (stored && JSON.parse(stored.index_json).workspace_route)
821
+ publishRunProjections(context, projectId, run.id);
441
822
  const receiptPath = path.join(stageRoot, "workspace-route.json");
442
823
  if (fs.existsSync(receiptPath))
443
- return readWorkspaceRouteReceipt(stageRoot).policy;
824
+ return { kind: "retained", policy: readWorkspaceRouteReceipt(stageRoot).policy };
825
+ prepareOutputFile(receiptPath);
444
826
  const config = loadVnextWorkspacePolicy(projectRoot);
445
827
  const stableFacts = gitFacts(projectRoot);
446
828
  if (config.workspace.route === "integration_branch_direct") {
447
- const policy = { route: "integration_branch_direct", integration_branch: config.workspace.integration_branch, base_ref: stableFacts.head, feature_branch: null, worktree_path: null, reason: "Project workspace configuration explicitly selects the integration checkout.", provision_stage: "protocolize_start", stage_session_mode: stageSessionMode(context, run), provisioned: true, bootstrap_status: "not_applicable" };
448
- writeJson(receiptPath, { schema_id: "dd-flow/vnext-workspace-route@1", policy, created: null, bootstrap: null });
449
- return policy;
829
+ const sessionMode = stageSessionMode(context, run, projectRoot, projectRoot);
830
+ const policy = { route: "integration_branch_direct", integration_branch: config.workspace.integration_branch, base_ref: stableFacts.head, feature_branch: null, worktree_path: null, reason: "Project workspace configuration explicitly selects the integration checkout.", provision_stage: "protocolize_start", stage_session_mode: sessionMode, provisioned: true, bootstrap_status: "not_applicable" };
831
+ return { kind: "direct", receiptPath, policy };
450
832
  }
451
833
  if (stableFacts.status === "unavailable" || !stableFacts.head || stableFacts.branch !== config.workspace.integration_branch) {
452
834
  throw new AppError("workspace_route_invalid", "Configured feature worktree requires a clean, known integration checkout", 1, { expected_integration_branch: config.workspace.integration_branch, actual: stableFacts, project_root: projectRoot });
@@ -454,11 +836,40 @@ function provisionWorkspaceForProtocolize(context, projectRoot, projectId, run,
454
836
  const branch = vnextRunBranch(config, run);
455
837
  const holder = path.basename(projectRoot).replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "project";
456
838
  const worktreePath = projectFeatureWorktreePath(context.ddFlowHome, projectId, `RUN-${run.short_id}`, holder);
457
- const created = createManagedWorktree(context, { projectRoot, branch, base: stableFacts.head, path: worktreePath });
458
- const bootstrap = bootstrapManagedWorktree(context, { projectRoot, worktreePath: created.worktreePath });
459
- rebindFlowRunWorkspace(context, { projectRoot, runId: run.id, workspaceRoot: created.worktreePath, reason: "PROTOCOLIZE start provisioned the configured feature-worktree route" });
460
- const policy = { route: "feature_worktree", integration_branch: config.workspace.integration_branch, base_ref: stableFacts.head, feature_branch: branch, worktree_path: created.worktreePath, reason: "Project workspace configuration requires a managed feature worktree before durable protocol documents exist.", provision_stage: "protocolize_start", stage_session_mode: stageSessionMode(context, run), provisioned: true, bootstrap_status: bootstrap.status };
461
- writeJson(receiptPath, { schema_id: "dd-flow/vnext-workspace-route@1", policy, created, bootstrap });
839
+ const sessionMode = stageSessionMode(context, run, projectRoot, worktreePath);
840
+ let create;
841
+ try {
842
+ create = prepareManagedWorktree({ projectRoot, branch, base: stableFacts.head, path: worktreePath });
843
+ }
844
+ catch (error) {
845
+ if (error instanceof AppError && error.exitCode === 2)
846
+ throw new AppError("workspace_route_invalid", "The frozen RUN workspace route cannot be provisioned", 1, { cause_code: error.code, run_id: run.id });
847
+ throw error;
848
+ }
849
+ prepareManagedWorktreeBootstrapSources(projectRoot);
850
+ return { kind: "feature", receiptPath, config, stableFacts, sessionMode, branch, create,
851
+ expectedRevision: stored ? JSON.parse(stored.index_json).runtime_revision ?? 0 : 0,
852
+ expectedGeneration: assertRunMutationAllowed(context, projectId, run.id) };
853
+ }
854
+ async function provisionWorkspaceForProtocolize(context, projectRoot, run, prepared) {
855
+ if (prepared.kind === "retained")
856
+ return prepared.policy;
857
+ const { receiptPath } = prepared;
858
+ if (prepared.kind === "direct") {
859
+ writeJson(receiptPath, { schema_id: "dd-flow/vnext-workspace-route@1", policy: prepared.policy, created: null, bootstrap: null });
860
+ return prepared.policy;
861
+ }
862
+ const { config, stableFacts, sessionMode, branch, create } = prepared;
863
+ const created = await createManagedWorktree(context, create, create);
864
+ const bootstrap = await bootstrapManagedWorktree(context, { projectRoot, worktreePath: created.worktreePath });
865
+ const policy = { route: "feature_worktree", integration_branch: config.workspace.integration_branch, base_ref: stableFacts.head, feature_branch: branch, worktree_path: created.worktreePath, reason: "Project workspace configuration requires a managed feature worktree before durable protocol documents exist.", provision_stage: "protocolize_start", stage_session_mode: sessionMode, provisioned: true, bootstrap_status: bootstrap.status };
866
+ const receipt = { schema_id: "dd-flow/vnext-workspace-route@1", policy,
867
+ created: { worktreePath: created.worktreePath, worktrunk: created.worktrunk, command: [], result: { exit_code: 0, stdout: "", stderr: "" }, response: { workspace_path: created.worktreePath } },
868
+ bootstrap: { status: bootstrap.status, commands: [] },
869
+ proof: { creation_sha256: hybridDigest(created), bootstrap_sha256: hybridDigest(bootstrap) } };
870
+ rebindFlowRunWorkspace(context, { projectRoot, runId: run.id, workspaceRoot: created.worktreePath, routeReceipt: receipt,
871
+ expectedRevision: prepared.expectedRevision, expectedGeneration: prepared.expectedGeneration,
872
+ reason: "PROTOCOLIZE start provisioned the configured feature-worktree route" });
462
873
  return policy;
463
874
  }
464
875
  function readWorkspaceRouteReceipt(root) {
@@ -475,8 +886,27 @@ function readWorkspaceRouteReceipt(root) {
475
886
  }
476
887
  function requiredPolicyValue(value, label) { if (!value)
477
888
  throw new AppError("runtime_missing", `PROTOCOLIZE Git policy is missing ${label}`, 1); return value; }
478
- function allocateProtocolId(context, projectRoot, name) { return previewNextEntityId(context, { projectRoot, type: "protocol", slug: name }).entity.id; }
479
- function allocatePsetId(projectRoot, value) { const root = path.join(projectRoot, ".memory-bank", "protocol", "_set"); const used = fs.existsSync(root) ? fs.readdirSync(root).map((file) => /^PSET-(\d+)-/.exec(file)?.[1]).filter((item) => Boolean(item)).map(Number) : []; return `PSET-${String(Math.max(0, ...used) + 1).padStart(3, "0")}-${slug(value)}`; }
889
+ function allocateProtocolIds(context, projectId, projectRoot, names) {
890
+ if (!names.length)
891
+ return [];
892
+ const first = previewNextEntityId(context, { projectRoot, type: "protocol", slug: names[0] }).entity.id;
893
+ const used = protocolizeReservedNumbers(context, projectId, "protocol_ids", /^PRT-(\d+)-/);
894
+ const sequence = Math.max(Number(/^PRT-(\d+)-/.exec(first)?.[1] ?? 1) - 1, ...used) + 1;
895
+ return names.map((name, index) => `PRT-${String(sequence + index).padStart(3, "0")}-${slug(name)}`);
896
+ }
897
+ function allocatePsetId(context, projectId, projectRoot, value) {
898
+ const root = path.join(projectRoot, ".memory-bank", "protocol", "_set");
899
+ const used = fs.existsSync(root) ? fs.readdirSync(root).map((file) => /^PSET-(\d+)-/.exec(file)?.[1]).filter((item) => Boolean(item)).map(Number) : [];
900
+ return `PSET-${String(Math.max(0, ...used, ...protocolizeReservedNumbers(context, projectId, "pset_id", /^PSET-(\d+)-/)) + 1).padStart(3, "0")}-${slug(value)}`;
901
+ }
902
+ function protocolizeReservedNumbers(context, projectId, field, pattern) {
903
+ const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id = ? AND event_type = 'flow_run.protocolize_intent'", [projectId]);
904
+ return rows.flatMap(row => {
905
+ const value = JSON.parse(row.payload_json);
906
+ const ids = field === "protocol_ids" ? value.protocol_ids : value.pset_id ? [value.pset_id] : [];
907
+ return ids.map(id => Number(pattern.exec(id)?.[1])).filter(Number.isFinite);
908
+ });
909
+ }
480
910
  function catalog(projectRoot) {
481
911
  const memory = path.join(projectRoot, ".memory-bank");
482
912
  const candidates = [
@@ -502,7 +932,15 @@ function requireRun(context, projectRoot, id) { const project = requireProjectBy
502
932
  throw new AppError("not_found", "RUN is not registered", 1, { run_id: id }); return run; }
503
933
  function requireRunHome(run) { if (!run.run_root)
504
934
  throw new AppError("runtime_missing", "RUN artifact root is unavailable", 1); return run.run_root; }
505
- function inside(root, file) { const relative = path.relative(root, file); if (relative.startsWith("..") || path.isAbsolute(relative))
935
+ export function prepareProtocolizeResultPath(runHome, file) {
936
+ const resolved = path.resolve(file);
937
+ inside(path.join(runHome, "02-protocolize"), resolved);
938
+ const canonical = path.join(runHome, "02-protocolize", "protocolize-result.json");
939
+ if (resolved !== canonical)
940
+ throw new AppError("protocolize_result_path_invalid", "PROTOCOLIZE result must be the canonical Stage data file named by its completion receipt", 2, { result_file: resolved, expected: canonical });
941
+ return resolved;
942
+ }
943
+ function inside(root, file) { const relative = path.relative(root, file); if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative))
506
944
  throw new AppError("path_escape", "Result must be inside the protocolize workspace", 2); }
507
945
  function writeJson(file, value) { const tmp = `${file}.${crypto.randomUUID()}.tmp`; fs.writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`); fs.renameSync(tmp, file); }
508
946
  function slug(value) { const normalized = value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, ""); if (!normalized)