ai-delivery-workflow 0.2.2 → 0.4.0

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 (361) hide show
  1. package/README.md +45 -14
  2. package/bin/ai-delivery.mjs +181 -6
  3. package/bin/benchmark-autonomy.mjs +217 -0
  4. package/bin/release-check.mjs +382 -0
  5. package/docs/CLI-PARAMETER-REFERENCE.zh-CN.md +346 -0
  6. package/docs/CODEGRAPH-INTEGRATION.zh-CN.md +89 -0
  7. package/docs/DUAL-REPOSITORY-WORKSPACE.zh-CN.md +2 -2
  8. package/docs/EVOLUTION-MAINTENANCE.zh-CN.md +7 -7
  9. package/docs/EVOLUTION-USER-GUIDE.zh-CN.md +2 -2
  10. package/docs/FILE-REFERENCE.zh-CN.md +120 -39
  11. package/docs/MAINTENANCE-VERIFICATION.zh-CN.md +51 -0
  12. package/docs/MOCK-SYSTEM.zh-CN.md +141 -0
  13. package/docs/NPM-RELEASE-MANAGEMENT.zh-CN.md +106 -0
  14. package/docs/PROJECT-MANUAL.zh-CN.md +154 -95
  15. package/docs/PROTOTYPE-MIGRATION.zh-CN.md +164 -0
  16. package/docs/STATE-CLI-MAINTENANCE.zh-CN.md +35 -17
  17. package/docs/STATE-CLI-USER-GUIDE.zh-CN.md +115 -39
  18. package/docs/VIEWER-MAINTENANCE.zh-CN.md +101 -324
  19. package/docs/VIEWER-USER-GUIDE.zh-CN.md +68 -190
  20. package/docs/adr/0001-prototype-first-ui-governance.md +3 -0
  21. package/docs/adr/0002-microcks-as-default-mock-system.md +3 -0
  22. package/docs/adr/0003-workflow-manager-governed-control-surface.md +46 -0
  23. package/docs/adr/0004-operational-carbon-visual-standard.md +9 -0
  24. package/docs/adr/0005-prototype-management-as-routed-pages.md +7 -0
  25. package/docs/agents/domain.md +12 -0
  26. package/docs/agents/issue-tracker.md +27 -0
  27. package/docs/agents/triage-labels.md +11 -0
  28. package/docs/agents/workflow-manager-acceptance-matrix.md +93 -0
  29. package/docs/agents/workflow-manager-agent-interface.md +145 -0
  30. package/docs/agents/workflow-manager-current-state.md +205 -0
  31. package/lib/artifact-files.mjs +47 -0
  32. package/lib/codegraph.mjs +405 -0
  33. package/lib/delivery-state.mjs +3079 -198
  34. package/lib/frontend-guard.mjs +288 -0
  35. package/lib/fs-utils.mjs +53 -0
  36. package/lib/history-anchor.mjs +98 -0
  37. package/lib/mock-scenario-pack.mjs +687 -0
  38. package/lib/mock-system.mjs +897 -0
  39. package/lib/npm-package-result.mjs +11 -0
  40. package/lib/png-evidence.mjs +149 -0
  41. package/lib/project-bootstrap.mjs +96 -55
  42. package/lib/project-installer.mjs +1484 -164
  43. package/lib/project-migration.mjs +1193 -0
  44. package/lib/prototype-migration.mjs +713 -0
  45. package/lib/prototype-page-review.mjs +384 -0
  46. package/lib/prototype-review-target.mjs +334 -0
  47. package/lib/prototype-revision.mjs +812 -0
  48. package/lib/stable-value.mjs +15 -0
  49. package/lib/toml-hooks.mjs +69 -0
  50. package/lib/ui-acceptance.mjs +565 -0
  51. package/lib/visual-standard.mjs +232 -0
  52. package/lib/workflow-manager-contracts.mjs +367 -0
  53. package/lib/workflow-manager-facts.mjs +778 -0
  54. package/lib/workflow-manager-governance.mjs +514 -0
  55. package/lib/workflow-manager-mock.mjs +183 -0
  56. package/lib/workflow-manager-prototypes.mjs +1847 -0
  57. package/lib/workflow-manager-public/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  58. package/lib/workflow-manager-public/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  59. package/lib/workflow-manager-public/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
  60. package/lib/workflow-manager-public/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
  61. package/lib/workflow-manager-public/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
  62. package/lib/workflow-manager-public/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
  63. package/lib/workflow-manager-public/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
  64. package/lib/workflow-manager-public/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
  65. package/lib/workflow-manager-public/assets/index-Chlg6r8f.css +1 -0
  66. package/lib/workflow-manager-public/assets/index-E-hGB5ZD.js +13 -0
  67. package/lib/workflow-manager-public/fonts/OFL-1.1.txt +90 -0
  68. package/lib/workflow-manager-public/index.html +14 -0
  69. package/lib/workflow-manager-systems.mjs +519 -0
  70. package/lib/workflow-manager-transaction.mjs +397 -0
  71. package/lib/workflow-manager.mjs +745 -0
  72. package/package.json +8 -2
  73. package/skills/ai-delivery-assemble-release/agents/openai.yaml +3 -3
  74. package/skills/ai-delivery-assemble-release/assets/release-material-index.csv +1 -1
  75. package/skills/ai-delivery-assemble-release/assets/version-inclusion.csv +1 -1
  76. package/skills/ai-delivery-bootstrap/SKILL.md +4 -4
  77. package/skills/ai-delivery-bootstrap/agents/openai.yaml +3 -3
  78. package/skills/ai-delivery-bootstrap/references/bootstrap-contract.md +4 -2
  79. package/skills/ai-delivery-checkpoint-task/SKILL.md +17 -3
  80. package/skills/ai-delivery-checkpoint-task/agents/openai.yaml +3 -3
  81. package/skills/ai-delivery-checkpoint-task/assets/runtime-template/task.json +7 -1
  82. package/skills/ai-delivery-checkpoint-task/references/checkpoint-contract.md +22 -8
  83. package/skills/ai-delivery-checkpoint-task/scripts/hook-event.mjs +91 -0
  84. package/skills/ai-delivery-checkpoint-task/scripts/task-state.mjs +1086 -51
  85. package/skills/ai-delivery-close-version/SKILL.md +9 -8
  86. package/skills/ai-delivery-close-version/agents/openai.yaml +3 -3
  87. package/skills/ai-delivery-close-version/assets/version-lineage.yaml +1 -1
  88. package/skills/ai-delivery-close-version/references/version-closeout-contract.md +2 -2
  89. package/skills/ai-delivery-define-product/SKILL.md +8 -4
  90. package/skills/ai-delivery-define-product/agents/openai.yaml +4 -4
  91. package/skills/ai-delivery-define-product/references/product-contract.md +2 -2
  92. package/skills/ai-delivery-deploy-production/agents/openai.yaml +3 -3
  93. package/skills/ai-delivery-design-architecture/SKILL.md +9 -5
  94. package/skills/ai-delivery-design-architecture/assets/architecture-template/prototype-architecture-validation.yaml +16 -16
  95. package/skills/ai-delivery-design-architecture/references/architecture-contract.md +1 -1
  96. package/skills/ai-delivery-design-experience/SKILL.md +36 -30
  97. package/skills/ai-delivery-design-experience/agents/openai.yaml +4 -4
  98. package/skills/ai-delivery-design-experience/assets/experience-template/LEGACY-COMPATIBILITY.md +16 -0
  99. package/skills/ai-delivery-design-experience/assets/experience-template/design-reference-selection.yaml +32 -0
  100. package/skills/ai-delivery-design-experience/assets/experience-template/experience-baseline.yaml +2 -2
  101. package/skills/ai-delivery-design-experience/assets/experience-template/formal-ui-confirmation.yaml +16 -3
  102. package/skills/ai-delivery-design-experience/assets/experience-template/frontend-change-scope.yaml +30 -0
  103. package/skills/ai-delivery-design-experience/assets/experience-template/interaction-contract.csv +1 -1
  104. package/skills/ai-delivery-design-experience/assets/experience-template/mock-admin-export.yaml +8 -0
  105. package/skills/ai-delivery-design-experience/assets/experience-template/mock-scenario-mappings.csv +1 -0
  106. package/skills/ai-delivery-design-experience/assets/experience-template/mock-scenario-pack-request.yaml +24 -0
  107. package/skills/ai-delivery-design-experience/assets/experience-template/product-prototype-reconciliation.yaml +2 -1
  108. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-adjustment-log.csv +1 -1
  109. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-component-bindings.csv +1 -0
  110. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-component-candidates.csv +1 -0
  111. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-components.csv +1 -0
  112. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-confirmation.yaml +18 -18
  113. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-data-contracts.csv +1 -0
  114. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-impact-assessment.yaml +5 -5
  115. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-implementation-bindings.csv +1 -0
  116. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-manifest.yaml +2 -0
  117. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-pages.csv +1 -0
  118. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-product-coverage.csv +1 -1
  119. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-review-comments.csv +1 -1
  120. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-review-decision.yaml +13 -12
  121. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-review-scenes.csv +1 -0
  122. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-review-sessions.csv +1 -1
  123. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-review-unit-pages.csv +1 -0
  124. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-review-units.csv +1 -0
  125. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-revision.yaml +88 -0
  126. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-routes.csv +1 -0
  127. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-set.yaml +2 -0
  128. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-source-inspection-snapshots.csv +1 -0
  129. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-terminals.csv +1 -0
  130. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-tool-candidates.csv +1 -1
  131. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-tool-selection.yaml +2 -3
  132. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-traceability.csv +1 -1
  133. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-trigger-conditions.csv +1 -0
  134. package/skills/ai-delivery-design-experience/assets/experience-template/prototype.yaml +6 -0
  135. package/skills/ai-delivery-design-experience/assets/experience-template/ui-framework-selection.yaml +17 -1
  136. package/skills/ai-delivery-design-experience/assets/experience-template/ui-handoff.yaml +6 -5
  137. package/skills/ai-delivery-design-experience/assets/experience-template/ui-mapping.csv +1 -0
  138. package/skills/ai-delivery-design-experience/assets/experience-template/visual-direction.yaml +4 -0
  139. package/skills/ai-delivery-design-experience/assets/experience-template/visual-reference-manifest.yaml +23 -0
  140. package/skills/ai-delivery-design-experience/assets/experience-template/visual-reference.html +163 -0
  141. package/skills/ai-delivery-design-experience/assets/experience-template/visual-system-confirmation.yaml +11 -0
  142. package/skills/ai-delivery-design-experience/references/experience-contract.md +26 -27
  143. package/skills/ai-delivery-design-experience/references/prototype-management-contract.md +42 -21
  144. package/skills/ai-delivery-design-tests/SKILL.md +13 -7
  145. package/skills/ai-delivery-design-tests/assets/test-design-template/prototype-test-scope.csv +1 -1
  146. package/skills/ai-delivery-design-tests/assets/test-design-template/prototype-test-validation.yaml +17 -16
  147. package/skills/ai-delivery-design-tests/references/test-design-contract.md +7 -5
  148. package/skills/ai-delivery-develop-iteration/SKILL.md +18 -11
  149. package/skills/ai-delivery-develop-iteration/agents/openai.yaml +3 -3
  150. package/skills/ai-delivery-develop-iteration/assets/development-template/candidate-manifest.yaml +36 -0
  151. package/skills/ai-delivery-develop-iteration/assets/development-template/task-prototype-traceability.csv +1 -1
  152. package/skills/ai-delivery-develop-iteration/references/development-contract.md +5 -3
  153. package/skills/ai-delivery-evolve-workflow/agents/openai.yaml +3 -3
  154. package/skills/ai-delivery-execute-test/SKILL.md +6 -2
  155. package/skills/ai-delivery-execute-test/agents/openai.yaml +4 -4
  156. package/skills/ai-delivery-execute-work-package/SKILL.md +10 -3
  157. package/skills/ai-delivery-execute-work-package/agents/openai.yaml +4 -4
  158. package/skills/ai-delivery-execute-work-package/references/work-package-contract.md +1 -1
  159. package/skills/ai-delivery-manage-git/agents/openai.yaml +3 -3
  160. package/skills/ai-delivery-manage-standards/agents/openai.yaml +4 -4
  161. package/skills/ai-delivery-orchestrate/SKILL.md +47 -29
  162. package/skills/ai-delivery-orchestrate/agents/openai.yaml +3 -3
  163. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/control/migrations/.gitkeep +0 -0
  164. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/control/repairs/.gitkeep +0 -0
  165. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/delivery/project.yaml +1 -0
  166. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/delivery/workflow-state.yaml +2 -0
  167. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/frontend-guard/frontend-guard.mjs +50 -0
  168. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/hooks/hook-event.mjs +91 -0
  169. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/manager/manager.mjs +64 -0
  170. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/manager/start.cmd +4 -0
  171. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/manager/start.ps1 +2 -0
  172. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/{viewer → manager}/start.sh +1 -1
  173. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/mock/mock.mjs +50 -0
  174. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/ui-acceptance/ui-acceptance.mjs +37 -0
  175. package/skills/ai-delivery-orchestrate/assets/project-template/AGENTS.md +32 -6
  176. package/skills/ai-delivery-orchestrate/assets/project-template/gitignore.template +1 -0
  177. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/CONTRIBUTING.md +25 -0
  178. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/LICENSE +21 -0
  179. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/README.md +250 -0
  180. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/UPSTREAM.yaml +19 -0
  181. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/airbnb/DESIGN.md +545 -0
  182. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/airbnb/README.md +5 -0
  183. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/airtable/DESIGN.md +554 -0
  184. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/airtable/README.md +5 -0
  185. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/apple/DESIGN.md +562 -0
  186. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/apple/README.md +5 -0
  187. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/binance/DESIGN.md +634 -0
  188. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/binance/README.md +5 -0
  189. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/bmw/DESIGN.md +544 -0
  190. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/bmw/README.md +5 -0
  191. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/bmw-m/DESIGN.md +503 -0
  192. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/bmw-m/README.md +5 -0
  193. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/bugatti/DESIGN.md +454 -0
  194. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/bugatti/README.md +5 -0
  195. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/cal/DESIGN.md +542 -0
  196. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/cal/README.md +5 -0
  197. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/claude/DESIGN.md +589 -0
  198. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/claude/README.md +5 -0
  199. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/clay/DESIGN.md +541 -0
  200. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/clay/README.md +5 -0
  201. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/clickhouse/DESIGN.md +544 -0
  202. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/clickhouse/README.md +5 -0
  203. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/cohere/DESIGN.md +451 -0
  204. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/cohere/README.md +5 -0
  205. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/coinbase/DESIGN.md +570 -0
  206. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/coinbase/README.md +5 -0
  207. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/composio/DESIGN.md +506 -0
  208. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/composio/README.md +5 -0
  209. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/cursor/DESIGN.md +537 -0
  210. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/cursor/README.md +5 -0
  211. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/dell-1996/DESIGN.md +632 -0
  212. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/dell-1996/README.md +5 -0
  213. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/elevenlabs/DESIGN.md +504 -0
  214. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/elevenlabs/README.md +5 -0
  215. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/expo/DESIGN.md +526 -0
  216. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/expo/README.md +5 -0
  217. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/ferrari/DESIGN.md +531 -0
  218. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/ferrari/README.md +5 -0
  219. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/figma/DESIGN.md +578 -0
  220. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/figma/README.md +5 -0
  221. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/framer/DESIGN.md +544 -0
  222. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/framer/README.md +5 -0
  223. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/hashicorp/DESIGN.md +575 -0
  224. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/hashicorp/README.md +5 -0
  225. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/hp/DESIGN.md +670 -0
  226. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/hp/README.md +5 -0
  227. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/ibm/DESIGN.md +550 -0
  228. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/ibm/README.md +5 -0
  229. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/intercom/DESIGN.md +546 -0
  230. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/intercom/README.md +5 -0
  231. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/kraken/DESIGN.md +125 -0
  232. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/kraken/README.md +5 -0
  233. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/lamborghini/DESIGN.md +288 -0
  234. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/lamborghini/README.md +5 -0
  235. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/linear.app/DESIGN.md +548 -0
  236. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/linear.app/README.md +5 -0
  237. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/lovable/DESIGN.md +298 -0
  238. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/lovable/README.md +5 -0
  239. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/mastercard/DESIGN.md +365 -0
  240. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/mastercard/README.md +5 -0
  241. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/meta/DESIGN.md +683 -0
  242. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/meta/README.md +5 -0
  243. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/minimax/DESIGN.md +746 -0
  244. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/minimax/README.md +5 -0
  245. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/mintlify/DESIGN.md +852 -0
  246. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/mintlify/README.md +5 -0
  247. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/miro/DESIGN.md +825 -0
  248. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/miro/README.md +5 -0
  249. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/mistral.ai/DESIGN.md +773 -0
  250. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/mistral.ai/README.md +5 -0
  251. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/mongodb/DESIGN.md +767 -0
  252. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/mongodb/README.md +5 -0
  253. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/nike/DESIGN.md +575 -0
  254. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/nike/README.md +5 -0
  255. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/nintendo-2001/DESIGN.md +649 -0
  256. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/nintendo-2001/README.md +5 -0
  257. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/notion/DESIGN.md +821 -0
  258. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/notion/README.md +5 -0
  259. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/nvidia/DESIGN.md +640 -0
  260. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/nvidia/README.md +5 -0
  261. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/ollama/DESIGN.md +539 -0
  262. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/ollama/README.md +5 -0
  263. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/opencode.ai/DESIGN.md +521 -0
  264. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/opencode.ai/README.md +5 -0
  265. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/pinterest/DESIGN.md +597 -0
  266. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/pinterest/README.md +5 -0
  267. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/playstation/DESIGN.md +661 -0
  268. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/playstation/README.md +5 -0
  269. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/posthog/DESIGN.md +690 -0
  270. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/posthog/README.md +5 -0
  271. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/raycast/DESIGN.md +669 -0
  272. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/raycast/README.md +5 -0
  273. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/renault/DESIGN.md +589 -0
  274. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/renault/README.md +5 -0
  275. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/replicate/DESIGN.md +616 -0
  276. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/replicate/README.md +5 -0
  277. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/resend/DESIGN.md +585 -0
  278. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/resend/README.md +5 -0
  279. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/revolut/DESIGN.md +636 -0
  280. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/revolut/README.md +5 -0
  281. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/runwayml/DESIGN.md +244 -0
  282. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/runwayml/README.md +5 -0
  283. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/sanity/DESIGN.md +357 -0
  284. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/sanity/README.md +5 -0
  285. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/sentry/DESIGN.md +551 -0
  286. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/sentry/README.md +5 -0
  287. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/shopify/DESIGN.md +516 -0
  288. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/shopify/README.md +5 -0
  289. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/slack/DESIGN.md +482 -0
  290. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/spacex/DESIGN.md +363 -0
  291. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/spacex/README.md +5 -0
  292. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/spotify/DESIGN.md +246 -0
  293. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/spotify/README.md +5 -0
  294. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/starbucks/DESIGN.md +580 -0
  295. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/starbucks/README.md +5 -0
  296. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/stripe/DESIGN.md +487 -0
  297. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/stripe/README.md +5 -0
  298. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/supabase/DESIGN.md +462 -0
  299. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/supabase/README.md +5 -0
  300. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/superhuman/DESIGN.md +448 -0
  301. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/superhuman/README.md +5 -0
  302. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/tesla/DESIGN.md +286 -0
  303. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/tesla/README.md +5 -0
  304. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/theverge/DESIGN.md +339 -0
  305. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/theverge/README.md +5 -0
  306. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/together.ai/DESIGN.md +633 -0
  307. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/together.ai/README.md +5 -0
  308. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/uber/DESIGN.md +636 -0
  309. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/uber/README.md +5 -0
  310. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/vercel/DESIGN.md +736 -0
  311. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/vercel/README.md +5 -0
  312. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/vodafone/DESIGN.md +538 -0
  313. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/vodafone/README.md +5 -0
  314. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/voltagent/DESIGN.md +521 -0
  315. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/voltagent/README.md +5 -0
  316. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/warp/DESIGN.md +526 -0
  317. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/warp/README.md +5 -0
  318. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/webflow/DESIGN.md +588 -0
  319. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/webflow/README.md +5 -0
  320. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/wired/DESIGN.md +497 -0
  321. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/wired/README.md +5 -0
  322. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/wise/DESIGN.md +544 -0
  323. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/wise/README.md +5 -0
  324. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/x.ai/DESIGN.md +465 -0
  325. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/x.ai/README.md +5 -0
  326. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/zapier/DESIGN.md +537 -0
  327. package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/zapier/README.md +5 -0
  328. package/skills/ai-delivery-orchestrate/references/formal-state-contract.md +65 -20
  329. package/skills/ai-delivery-orchestrate/references/workflow-model.md +7 -5
  330. package/skills/ai-delivery-orchestrate-release/SKILL.md +2 -2
  331. package/skills/ai-delivery-orchestrate-release/agents/openai.yaml +4 -4
  332. package/skills/ai-delivery-orchestrate-release/references/release-workflow-contract.md +1 -1
  333. package/skills/ai-delivery-plan-iteration/SKILL.md +11 -4
  334. package/skills/ai-delivery-plan-iteration/agents/openai.yaml +4 -4
  335. package/skills/ai-delivery-plan-iteration/assets/planning-template/scope-manifest.yaml +17 -0
  336. package/skills/ai-delivery-plan-iteration/assets/planning-template/slice-prototype-traceability.csv +1 -1
  337. package/skills/ai-delivery-plan-iteration/references/planning-contract.md +8 -2
  338. package/skills/ai-delivery-prepare-platform/SKILL.md +5 -1
  339. package/skills/ai-delivery-prepare-platform/agents/openai.yaml +4 -4
  340. package/skills/ai-delivery-prepare-release/agents/openai.yaml +4 -4
  341. package/skills/ai-delivery-review-change/SKILL.md +14 -4
  342. package/skills/ai-delivery-review-change/agents/openai.yaml +4 -4
  343. package/skills/ai-delivery-review-change/references/review-contract.md +6 -0
  344. package/skills/ai-delivery-triage-failure/SKILL.md +5 -1
  345. package/skills/ai-delivery-triage-failure/agents/openai.yaml +4 -4
  346. package/skills/ai-delivery-validate-artifacts/SKILL.md +6 -3
  347. package/skills/ai-delivery-validate-artifacts/agents/openai.yaml +4 -4
  348. package/skills/ai-delivery-validate-artifacts/references/artifact-contract.md +13 -13
  349. package/skills/ai-delivery-verify-candidate/SKILL.md +13 -6
  350. package/skills/ai-delivery-verify-candidate/agents/openai.yaml +3 -3
  351. package/skills/ai-delivery-verify-candidate/assets/verification-template/ui-acceptance-request.yaml +19 -0
  352. package/skills/ai-delivery-verify-candidate/assets/verification-template/ui-acceptance-results.yaml +53 -0
  353. package/skills/ai-delivery-verify-candidate/references/candidate-verification-contract.md +10 -2
  354. package/skills/ai-delivery-verify-production/agents/openai.yaml +3 -3
  355. package/skills/ai-delivery-design-experience/assets/experience-template/low-fidelity-confirmation.yaml +0 -32
  356. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/public/app.js +0 -546
  357. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/public/index.html +0 -199
  358. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/public/styles.css +0 -529
  359. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/server.mjs +0 -780
  360. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/start.cmd +0 -4
  361. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/start.ps1 +0 -2
@@ -3,6 +3,7 @@ import path from "node:path";
3
3
  import crypto from "node:crypto";
4
4
  import { spawnSync } from "node:child_process";
5
5
  import { createRequire } from "node:module";
6
+ import { pathToFileURL } from "node:url";
6
7
 
7
8
  const moduleRequire = createRequire(import.meta.url);
8
9
 
@@ -12,13 +13,48 @@ const VALID_STATUS = new Set([
12
13
  ]);
13
14
  const FINISH_STATUS = new Set(["completed", "blocked", "interrupted", "failed", "abandoned"]);
14
15
  const CLOSED_STATUS = new Set(["completed", "failed", "abandoned"]);
16
+ const RECOVERY_COMPLETE_STATUS = new Set(["completed", "abandoned"]);
15
17
  const RESUMABLE_STATUS = new Set([
16
18
  "preparing", "running", "checkpointed", "pending-review", "blocked", "interrupted",
17
19
  ]);
18
20
  const VALID_NEXT_ACTION_OWNER = new Set(["agent", "user", "external"]);
21
+ const VALID_PHASE = new Set([
22
+ "planning", "implementation", "review", "triage", "repair", "test", "verification", "handoff",
23
+ ]);
24
+ const VALID_PHASE_STATUS = new Set([
25
+ "active", "completed", "failed", "blocked", "interrupted", "abandoned",
26
+ ]);
27
+ const VALID_FINDING_SEVERITY = new Set(["info", "warning", "blocking"]);
28
+ const VALID_FINDING_STATUS = new Set(["open", "resolved", "accepted"]);
29
+ const VALID_COLLECTION_DECISION = new Set(["pass", "fail", "blocked"]);
30
+ const VALID_TIME_CATEGORY = new Set([
31
+ "implementation", "test", "review", "workflow", "user-wait", "external-wait", "rework",
32
+ ]);
33
+ const TASK_STATUS_PROJECTION = new Map([
34
+ ["completed", { attempt: "completed", phase: "completed" }],
35
+ ["failed", { attempt: "failed", phase: "failed" }],
36
+ ["abandoned", { attempt: "abandoned", phase: "abandoned" }],
37
+ ["blocked", { attempt: "paused", phase: "blocked" }],
38
+ ["interrupted", { attempt: "paused", phase: "interrupted" }],
39
+ ]);
19
40
  const REPEATABLE_ARGS = new Set([
20
41
  "scope", "input-artifact", "standard", "planned-output", "actual-output",
21
- "completed-step", "blocker", "evidence", "depends-on",
42
+ "completed-step", "blocker", "evidence", "depends-on", "acceptance", "finding-json",
43
+ "change-set-json",
44
+ ]);
45
+ const LEGACY_DEVELOPMENT_NODE_IDS = new Set([
46
+ "01-product-discovery",
47
+ "02-product-definition",
48
+ "03-architecture-initial",
49
+ "04-experience-design",
50
+ "05-architecture-reconcile",
51
+ "06-platform-preparation",
52
+ "07-iteration-planning",
53
+ "08-test-design",
54
+ "09-development",
55
+ "10-change-review",
56
+ "11-candidate-verification",
57
+ "12-version-closeout",
22
58
  ]);
23
59
 
24
60
  function parseArgs(argv) {
@@ -47,6 +83,13 @@ function requireValue(args, key) {
47
83
  return value;
48
84
  }
49
85
 
86
+ function assertNotLegacyDevelopmentNode(node) {
87
+ if (LEGACY_DEVELOPMENT_NODE_IDS.has(node)) {
88
+ throw new Error(`Legacy workflow node is read-only: ${node}`);
89
+ }
90
+ return node;
91
+ }
92
+
50
93
  function now() {
51
94
  return new Date().toISOString();
52
95
  }
@@ -67,6 +110,265 @@ function parseSequence(value) {
67
110
  return sequence;
68
111
  }
69
112
 
113
+ function parsePhase(value, fallback = null) {
114
+ const phase = value === undefined || value === null || value === "" ? fallback : value;
115
+ if (phase !== null && !VALID_PHASE.has(phase)) {
116
+ throw new Error(`Invalid attempt phase: ${phase}; use ${[...VALID_PHASE].join(", ")}.`);
117
+ }
118
+ return phase;
119
+ }
120
+
121
+ function parseElapsedMinutes(value) {
122
+ if (value === undefined || value === null || value === "") return null;
123
+ const minutes = Number(value);
124
+ if (!Number.isFinite(minutes) || minutes < 0) {
125
+ throw new Error(`Invalid non-negative elapsed minutes: ${value}`);
126
+ }
127
+ return minutes;
128
+ }
129
+
130
+ function normalizeRelativeFiles(values, label) {
131
+ if (!Array.isArray(values)) return [];
132
+ return [...new Set(values.map((value) => {
133
+ const normalized = String(value).trim().replaceAll("\\", "/");
134
+ if (!normalized || path.posix.isAbsolute(normalized) || normalized.split("/").includes("..")) {
135
+ throw new Error(`${label} must contain repository-relative file paths.`);
136
+ }
137
+ return normalized;
138
+ }))];
139
+ }
140
+
141
+ function normalizeFinding(value) {
142
+ let finding;
143
+ try {
144
+ finding = typeof value === "string" ? JSON.parse(value) : value;
145
+ } catch {
146
+ throw new Error("--finding-json must be valid JSON.");
147
+ }
148
+ if (!finding || typeof finding !== "object" || Array.isArray(finding)) {
149
+ throw new Error("--finding-json must contain one finding object.");
150
+ }
151
+ const findingId = sanitizeId(String(finding.finding_id || ""));
152
+ const summary = typeof finding.summary === "string" ? finding.summary.trim() : "";
153
+ if (!summary) throw new Error(`Finding ${findingId} requires a non-empty summary.`);
154
+ const severity = finding.severity || "warning";
155
+ if (!VALID_FINDING_SEVERITY.has(severity)) {
156
+ throw new Error(`Finding ${findingId} has invalid severity: ${severity}`);
157
+ }
158
+ const status = finding.status || "open";
159
+ if (!VALID_FINDING_STATUS.has(status)) {
160
+ throw new Error(`Finding ${findingId} has invalid status: ${status}`);
161
+ }
162
+ const evidence = Array.isArray(finding.evidence)
163
+ ? [...new Set(finding.evidence.map((item) => String(item).trim()).filter(Boolean))]
164
+ : [];
165
+ const changedFiles = normalizeRelativeFiles(finding.changed_files, `Finding ${findingId} changed_files`);
166
+ const changeSetId = finding.change_set_id ? sanitizeId(String(finding.change_set_id)) : null;
167
+ return {
168
+ finding_id: findingId,
169
+ severity,
170
+ status,
171
+ summary,
172
+ evidence,
173
+ ...(changedFiles.length ? { changed_files: changedFiles } : {}),
174
+ ...(changeSetId ? { change_set_id: changeSetId } : {}),
175
+ };
176
+ }
177
+
178
+ function normalizeChangeSet(value, approvalTimestamp = null) {
179
+ let changeSet;
180
+ try {
181
+ changeSet = typeof value === "string" ? JSON.parse(value) : value;
182
+ } catch {
183
+ throw new Error("--change-set-json must be valid JSON.");
184
+ }
185
+ if (!changeSet || typeof changeSet !== "object" || Array.isArray(changeSet)) {
186
+ throw new Error("--change-set-json must contain one change-set object.");
187
+ }
188
+ const changeSetId = sanitizeId(String(changeSet.change_set_id || ""));
189
+ const suppliedCollectionChecksum = changeSet.collection_checksum
190
+ ? String(changeSet.collection_checksum).toLowerCase()
191
+ : null;
192
+ if (suppliedCollectionChecksum && !/^[a-f0-9]{64}$/.test(suppliedCollectionChecksum)) {
193
+ throw new Error(`Change set ${changeSetId} collection_checksum must be a SHA-256 checksum.`);
194
+ }
195
+ const approvalActor = typeof changeSet.approval_actor === "string" ? changeSet.approval_actor.trim() : "";
196
+ if (!approvalActor) throw new Error(`Change set ${changeSetId} requires approval_actor.`);
197
+ const rationale = typeof changeSet.rationale === "string" ? changeSet.rationale.trim() : "";
198
+ if (!rationale) throw new Error(`Change set ${changeSetId} requires rationale.`);
199
+ const findingIds = Array.isArray(changeSet.finding_ids)
200
+ ? [...new Set(changeSet.finding_ids.map((item) => sanitizeId(String(item))))]
201
+ : [];
202
+ if (!findingIds.length) throw new Error(`Change set ${changeSetId} requires finding_ids.`);
203
+ const allowedFiles = normalizeRelativeFiles(changeSet.allowed_files, `Change set ${changeSetId} allowed_files`);
204
+ if (!allowedFiles.length) throw new Error(`Change set ${changeSetId} requires allowed_files.`);
205
+ const expectedEvidence = Array.isArray(changeSet.expected_evidence)
206
+ ? [...new Set(changeSet.expected_evidence.map((item) => String(item).trim()).filter(Boolean))]
207
+ : [];
208
+ if (!expectedEvidence.length) throw new Error(`Change set ${changeSetId} requires expected_evidence.`);
209
+ const common = {
210
+ change_set_id: changeSetId,
211
+ approval_actor: approvalActor,
212
+ rationale,
213
+ finding_ids: findingIds,
214
+ allowed_files: allowedFiles,
215
+ expected_evidence: expectedEvidence,
216
+ };
217
+ if (approvalTimestamp) {
218
+ const reviewAxes = Array.isArray(changeSet.review_axes)
219
+ ? [...new Set(changeSet.review_axes.map((item) => String(item).trim()).filter(Boolean))]
220
+ : [];
221
+ if (!reviewAxes.length) throw new Error(`Change set ${changeSetId} requires review_axes.`);
222
+ const collectionDecision = String(changeSet.collection_decision || "").trim();
223
+ if (!VALID_COLLECTION_DECISION.has(collectionDecision)) {
224
+ throw new Error(`Change set ${changeSetId} collection_decision must be pass, fail, or blocked.`);
225
+ }
226
+ return {
227
+ ...common,
228
+ review_axes: reviewAxes,
229
+ collection_decision: collectionDecision,
230
+ supplied_collection_checksum: suppliedCollectionChecksum,
231
+ approved_at: approvalTimestamp,
232
+ };
233
+ }
234
+
235
+ const repairBaseline = normalizeCodeBaseline(
236
+ changeSet.repair_baseline,
237
+ `Change set ${changeSetId} repair_baseline`,
238
+ );
239
+ const feedbackCollection = normalizeFeedbackCollection(changeSet.feedback_collection, changeSetId);
240
+ if (feedbackCollection.subject_checksum !== repairBaseline.subject_checksum) {
241
+ throw new Error(`Change set ${changeSetId} feedback collection does not match its repair baseline subject_checksum.`);
242
+ }
243
+ const collectionChecksum = checksumJson(feedbackCollection);
244
+ if (suppliedCollectionChecksum !== collectionChecksum) {
245
+ throw new Error(`Change set ${changeSetId} collection_checksum does not match its persisted feedback collection.`);
246
+ }
247
+ const collectionFindingIds = feedbackCollection.findings.map((finding) => finding.finding_id);
248
+ if (!sameStringSet(findingIds, collectionFindingIds)) {
249
+ throw new Error(`Change set ${changeSetId} finding_ids do not match its persisted feedback collection.`);
250
+ }
251
+ const approvedAt = changeSet.approved_at;
252
+ if (!approvedAt) throw new Error(`Change set ${changeSetId} requires approved_at.`);
253
+ return {
254
+ ...common,
255
+ feedback_collection: feedbackCollection,
256
+ collection_checksum: collectionChecksum,
257
+ repair_baseline: repairBaseline,
258
+ approved_at: approvedAt,
259
+ };
260
+ }
261
+
262
+ function normalizeCodeBaseline(value, label) {
263
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
264
+ throw new Error(`${label} must be an object.`);
265
+ }
266
+ const codeCommit = String(value.code_commit || "").toLowerCase();
267
+ if (!/^[a-f0-9]{40,64}$/.test(codeCommit)) {
268
+ throw new Error(`${label} requires a Git code_commit.`);
269
+ }
270
+ const dirtyFiles = Array.isArray(value.dirty_files) ? value.dirty_files.map((file) => {
271
+ if (!file || typeof file !== "object" || Array.isArray(file)) {
272
+ throw new Error(`${label} dirty_files must contain objects.`);
273
+ }
274
+ const [relativePath] = normalizeRelativeFiles([file.path], `${label} dirty_files`);
275
+ const checksum = file.checksum === null ? null : String(file.checksum || "").toLowerCase();
276
+ if (checksum !== null && !/^[a-f0-9]{64}$/.test(checksum)) {
277
+ throw new Error(`${label} has an invalid checksum for ${relativePath}.`);
278
+ }
279
+ return { path: relativePath, checksum };
280
+ }) : [];
281
+ dirtyFiles.sort((left, right) => left.path.localeCompare(right.path));
282
+ if (new Set(dirtyFiles.map((file) => file.path)).size !== dirtyFiles.length) {
283
+ throw new Error(`${label} contains duplicate dirty files.`);
284
+ }
285
+ const baseline = { code_commit: codeCommit, dirty_files: dirtyFiles };
286
+ const subjectChecksum = checksumJson(baseline);
287
+ if (String(value.subject_checksum || "").toLowerCase() !== subjectChecksum) {
288
+ throw new Error(`${label} subject_checksum does not match its persisted baseline.`);
289
+ }
290
+ return { ...baseline, subject_checksum: subjectChecksum };
291
+ }
292
+
293
+ function normalizeFeedbackCollection(value, changeSetId) {
294
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
295
+ throw new Error(`Change set ${changeSetId} requires feedback_collection.`);
296
+ }
297
+ const subjectChecksum = String(value.subject_checksum || "").toLowerCase();
298
+ if (!/^[a-f0-9]{64}$/.test(subjectChecksum)) {
299
+ throw new Error(`Change set ${changeSetId} feedback_collection requires subject_checksum.`);
300
+ }
301
+ const reviewAxes = Array.isArray(value.review_axes)
302
+ ? [...new Set(value.review_axes.map((item) => String(item).trim()).filter(Boolean))]
303
+ : [];
304
+ if (!reviewAxes.length) throw new Error(`Change set ${changeSetId} feedback_collection requires review_axes.`);
305
+ const decision = String(value.decision || "").trim();
306
+ if (!VALID_COLLECTION_DECISION.has(decision)) {
307
+ throw new Error(`Change set ${changeSetId} feedback_collection decision must be pass, fail, or blocked.`);
308
+ }
309
+ const findings = Array.isArray(value.findings) ? value.findings.map(normalizeFinding) : [];
310
+ if (!findings.length || findings.some((finding) => finding.status !== "open")) {
311
+ throw new Error(`Change set ${changeSetId} feedback_collection requires open findings.`);
312
+ }
313
+ findings.sort((left, right) => left.finding_id.localeCompare(right.finding_id));
314
+ if (new Set(findings.map((finding) => finding.finding_id)).size !== findings.length) {
315
+ throw new Error(`Change set ${changeSetId} feedback_collection contains duplicate findings.`);
316
+ }
317
+ return { subject_checksum: subjectChecksum, review_axes: reviewAxes, decision, findings };
318
+ }
319
+
320
+ function projectedTaskStatus(status, target) {
321
+ return TASK_STATUS_PROJECTION.get(status)?.[target] || "active";
322
+ }
323
+
324
+ function normalizeAttempt(taskId, attempt, fallbackNumber) {
325
+ const number = Number(attempt?.number || fallbackNumber);
326
+ const phases = Array.isArray(attempt?.phases) ? attempt.phases.map((phase, index) => {
327
+ const sequence = Number(phase?.sequence || index + 1);
328
+ const phaseName = parsePhase(phase?.phase, "implementation");
329
+ const normalized = {
330
+ sequence,
331
+ phase: phaseName,
332
+ status: VALID_PHASE_STATUS.has(phase?.status) ? phase.status : "active",
333
+ started_at: phase?.started_at || null,
334
+ finished_at: phase?.finished_at || null,
335
+ findings: Array.isArray(phase?.findings) ? phase.findings.map(normalizeFinding) : [],
336
+ };
337
+ if (phase?.review_subject_baseline) {
338
+ if (phaseName !== "review") {
339
+ throw new Error(`Task ${taskId} phase ${sequence} stores a review subject outside review.`);
340
+ }
341
+ normalized.review_subject_baseline = normalizeCodeBaseline(
342
+ phase.review_subject_baseline,
343
+ `Task ${taskId} review phase ${sequence} review_subject_baseline`,
344
+ );
345
+ }
346
+ return normalized;
347
+ }) : [];
348
+ const timeEntries = Array.isArray(attempt?.time_entries) ? attempt.time_entries.map((entry) => ({
349
+ category: VALID_TIME_CATEGORY.has(entry?.category) ? entry.category : "workflow",
350
+ elapsed_minutes: parseElapsedMinutes(entry?.elapsed_minutes) || 0,
351
+ phase: parsePhase(entry?.phase, phases.at(-1)?.phase || "implementation"),
352
+ recorded_at: entry?.recorded_at || null,
353
+ })) : [];
354
+ const timeByCategory = {};
355
+ for (const entry of timeEntries) {
356
+ timeByCategory[entry.category] = (timeByCategory[entry.category] || 0) + entry.elapsed_minutes;
357
+ }
358
+ return {
359
+ attempt_id: attempt?.attempt_id || `${taskId}/A${number}`,
360
+ number,
361
+ status: new Set(["active", "paused", "completed", "failed", "abandoned"]).has(attempt?.status)
362
+ ? attempt.status
363
+ : "active",
364
+ started_at: attempt?.started_at || null,
365
+ finished_at: attempt?.finished_at || null,
366
+ phases,
367
+ time_entries: timeEntries,
368
+ time_by_category: timeByCategory,
369
+ };
370
+ }
371
+
70
372
  function parseNextActionOwner(value, fallback = "agent") {
71
373
  const owner = value === undefined ? fallback : value;
72
374
  if (!VALID_NEXT_ACTION_OWNER.has(owner)) {
@@ -137,10 +439,82 @@ function statePaths(args) {
137
439
  return { projectRoot, stateRoot, workflowRoot, runtime, marker };
138
440
  }
139
441
 
442
+ let activeWriteTransaction = null;
443
+
444
+ function stagedContent(file) {
445
+ const staged = activeWriteTransaction?.entries.get(path.resolve(file));
446
+ if (staged?.action === "write") return staged.content;
447
+ if (staged?.action === "delete") return null;
448
+ return fs.existsSync(file) ? fs.readFileSync(file) : null;
449
+ }
450
+
451
+ function stageWrite(file, content) {
452
+ if (!activeWriteTransaction) return false;
453
+ const target = path.resolve(file);
454
+ activeWriteTransaction.entries.set(target, {
455
+ action: "write", file: target, content: Buffer.from(content),
456
+ });
457
+ return true;
458
+ }
459
+
460
+ function stageDelete(file) {
461
+ if (!activeWriteTransaction) return false;
462
+ const target = path.resolve(file);
463
+ activeWriteTransaction.entries.set(target, { action: "delete", file: target });
464
+ return true;
465
+ }
466
+
467
+ function commitWriteTransaction() {
468
+ if (!activeWriteTransaction) throw new Error("Task Writer transaction is not active");
469
+ const entries = [...activeWriteTransaction.entries.values()];
470
+ const snapshots = entries.map((entry) => {
471
+ if (fs.existsSync(entry.file) && !fs.statSync(entry.file).isFile()) {
472
+ throw new Error(`Task Writer transaction target must be a file: ${entry.file}`);
473
+ }
474
+ return {
475
+ file: entry.file,
476
+ existed: fs.existsSync(entry.file),
477
+ content: fs.existsSync(entry.file) ? fs.readFileSync(entry.file) : null,
478
+ };
479
+ });
480
+ const temporary = new Map();
481
+ try {
482
+ for (const entry of entries.filter((item) => item.action === "write")) {
483
+ fs.mkdirSync(path.dirname(entry.file), { recursive: true });
484
+ const temp = `${entry.file}.${process.pid}.${crypto.randomUUID()}.tmp`;
485
+ fs.writeFileSync(temp, entry.content, { flag: "wx" });
486
+ temporary.set(entry.file, temp);
487
+ }
488
+ for (const entry of entries) {
489
+ if (entry.action === "write") fs.renameSync(temporary.get(entry.file), entry.file);
490
+ else fs.rmSync(entry.file, { force: true });
491
+ if (process.env.AI_DELIVERY_TEST_TASK_TRANSACTION_FAILURE_SUFFIX
492
+ && entry.file.endsWith(process.env.AI_DELIVERY_TEST_TASK_TRANSACTION_FAILURE_SUFFIX)) {
493
+ throw new Error("injected task transaction failure");
494
+ }
495
+ }
496
+ } catch (error) {
497
+ for (const temp of temporary.values()) fs.rmSync(temp, { force: true });
498
+ for (const snapshot of snapshots.reverse()) {
499
+ if (snapshot.existed) {
500
+ fs.mkdirSync(path.dirname(snapshot.file), { recursive: true });
501
+ fs.writeFileSync(snapshot.file, snapshot.content);
502
+ } else {
503
+ fs.rmSync(snapshot.file, { force: true });
504
+ }
505
+ }
506
+ throw error;
507
+ } finally {
508
+ activeWriteTransaction = null;
509
+ }
510
+ }
511
+
140
512
  function atomicWrite(file, value) {
513
+ const content = `${JSON.stringify(value, null, 2)}\n`;
514
+ if (stageWrite(file, content)) return;
141
515
  fs.mkdirSync(path.dirname(file), { recursive: true });
142
516
  const temp = `${file}.${process.pid}.${Date.now()}.tmp`;
143
- fs.writeFileSync(temp, `${JSON.stringify(value, null, 2)}\n`, "utf8");
517
+ fs.writeFileSync(temp, content, "utf8");
144
518
  fs.renameSync(temp, file);
145
519
  }
146
520
 
@@ -212,17 +586,20 @@ function portablePath(root, target) {
212
586
  : target;
213
587
  }
214
588
 
215
- function parseWorkspaceConfiguration(workflowRoot) {
216
- const workspaceFile = path.join(workflowRoot, "config", "workspace.yaml");
217
- if (!fs.existsSync(workspaceFile)) return null;
218
- let yaml;
589
+ function yamlLibrary(workflowRoot) {
219
590
  try {
220
- yaml = moduleRequire("yaml");
591
+ return moduleRequire("yaml");
221
592
  } catch {
222
593
  const vendorRoot = path.join(workflowRoot, "tools", "state", "vendor", "yaml");
223
594
  const vendorRequire = createRequire(path.join(vendorRoot, "package.json"));
224
- yaml = vendorRequire("./");
595
+ return vendorRequire("./");
225
596
  }
597
+ }
598
+
599
+ function parseWorkspaceConfiguration(workflowRoot) {
600
+ const workspaceFile = path.join(workflowRoot, "config", "workspace.yaml");
601
+ if (!fs.existsSync(workspaceFile)) return null;
602
+ const yaml = yamlLibrary(workflowRoot);
226
603
  return { file: workspaceFile, configuration: yaml.parse(fs.readFileSync(workspaceFile, "utf8")) };
227
604
  }
228
605
 
@@ -253,7 +630,97 @@ function configuredCodeState(workflowRoot) {
253
630
  return code;
254
631
  }
255
632
 
633
+ function workingTreeChecksum(repositoryRoot, relativePath) {
634
+ const absolute = path.resolve(repositoryRoot, ...relativePath.split("/"));
635
+ const relative = path.relative(repositoryRoot, absolute);
636
+ if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
637
+ throw new Error(`Git path escapes the configured code repository: ${relativePath}`);
638
+ }
639
+ if (!fs.existsSync(absolute)) return null;
640
+ const stat = fs.lstatSync(absolute);
641
+ if (stat.isSymbolicLink()) return sha256Content(`symlink:${fs.readlinkSync(absolute)}`);
642
+ if (stat.isFile()) return sha256Content(fs.readFileSync(absolute));
643
+ return sha256Content(`non-file:${relativePath}`);
644
+ }
645
+
646
+ function captureCodeBaseline(workflowRoot) {
647
+ const code = configuredCodeState(workflowRoot);
648
+ if (!code.head) throw new Error("Configured code repository requires a HEAD before approving a change set.");
649
+ const dirtyFiles = code.dirty_paths
650
+ .map((relativePath) => ({
651
+ path: relativePath,
652
+ checksum: workingTreeChecksum(code.root, relativePath),
653
+ }))
654
+ .sort((left, right) => left.path.localeCompare(right.path));
655
+ const baseline = { code_commit: code.head.toLowerCase(), dirty_files: dirtyFiles };
656
+ return { ...baseline, subject_checksum: checksumJson(baseline) };
657
+ }
658
+
659
+ function approveChangeSet(candidate, openFindings, reviewSubjectBaseline, workflowRoot) {
660
+ const currentSubject = captureCodeBaseline(workflowRoot);
661
+ if (currentSubject.subject_checksum !== reviewSubjectBaseline.subject_checksum) {
662
+ throw new Error(
663
+ `Change set ${candidate.change_set_id} review subject changed during feedback collection.`,
664
+ );
665
+ }
666
+ const repairBaseline = reviewSubjectBaseline;
667
+ const feedbackCollection = {
668
+ subject_checksum: repairBaseline.subject_checksum,
669
+ review_axes: candidate.review_axes,
670
+ decision: candidate.collection_decision,
671
+ findings: [...openFindings].sort((left, right) => left.finding_id.localeCompare(right.finding_id)),
672
+ };
673
+ const collectionChecksum = checksumJson(feedbackCollection);
674
+ if (candidate.supplied_collection_checksum && candidate.supplied_collection_checksum !== collectionChecksum) {
675
+ throw new Error(
676
+ `Change set ${candidate.change_set_id} collection_checksum does not match the computed feedback collection.`,
677
+ );
678
+ }
679
+ return {
680
+ change_set_id: candidate.change_set_id,
681
+ feedback_collection: feedbackCollection,
682
+ collection_checksum: collectionChecksum,
683
+ repair_baseline: repairBaseline,
684
+ approval_actor: candidate.approval_actor,
685
+ rationale: candidate.rationale,
686
+ finding_ids: candidate.finding_ids,
687
+ allowed_files: candidate.allowed_files,
688
+ expected_evidence: candidate.expected_evidence,
689
+ approved_at: candidate.approved_at,
690
+ };
691
+ }
692
+
693
+ function gitPathList(cwd, args, label) {
694
+ const result = spawnSync("git", args, { cwd, encoding: "utf8", windowsHide: true });
695
+ if (result.status !== 0) {
696
+ throw new Error(`${label}: ${(result.stderr || result.error?.message || "Git command failed.").trim()}`);
697
+ }
698
+ return result.stdout.split("\0").filter(Boolean).map((item) => item.replaceAll("\\", "/"));
699
+ }
700
+
701
+ function repairChangesSinceBaseline(workflowRoot, baseline) {
702
+ const code = configuredCodeState(workflowRoot);
703
+ const committedOrTracked = gitPathList(
704
+ code.root,
705
+ ["diff", "--name-only", "-z", baseline.code_commit, "--"],
706
+ `Cannot compare the repair baseline ${baseline.code_commit}`,
707
+ );
708
+ const candidates = new Set([
709
+ ...committedOrTracked,
710
+ ...code.dirty_paths,
711
+ ...baseline.dirty_files.map((file) => file.path),
712
+ ]);
713
+ const baselineDirty = new Map(baseline.dirty_files.map((file) => [file.path, file.checksum]));
714
+ return [...candidates].filter((relativePath) => {
715
+ const currentChecksum = workingTreeChecksum(code.root, relativePath);
716
+ return baselineDirty.has(relativePath)
717
+ ? currentChecksum !== baselineDirty.get(relativePath)
718
+ : true;
719
+ }).sort((left, right) => left.localeCompare(right));
720
+ }
721
+
256
722
  function writeSharedTask(runtime, workflowRoot, task) {
723
+ assertNotLegacyDevelopmentNode(task.node);
257
724
  const sharedFile = sharedTaskFile(workflowRoot, task.task_id);
258
725
  let previousRevision = 0;
259
726
  if (fs.existsSync(sharedFile)) {
@@ -313,22 +780,267 @@ function readTask(runtime, taskId) {
313
780
  function normalizeTask(task) {
314
781
  for (const key of [
315
782
  "scope", "input_artifacts", "standards", "planned_outputs", "actual_outputs",
316
- "completed_steps", "blockers", "evidence", "depends_on",
783
+ "completed_steps", "blockers", "evidence", "depends_on", "acceptance",
317
784
  ]) {
318
785
  if (!Array.isArray(task[key])) task[key] = [];
319
786
  task[key] = [...new Set(task[key])];
320
787
  }
321
788
  task.sequence = parseSequence(task.sequence);
322
789
  task.next_action_owner = parseNextActionOwner(task.next_action_owner);
790
+ task.responsibility = task.responsibility || task.skill || "agent";
791
+ task.derived_from = task.derived_from || null;
792
+ task.supersedes = task.supersedes || null;
793
+ if (!Array.isArray(task.attempts)) {
794
+ const legacyNumber = Number(task.attempt || 0);
795
+ task.attempts = legacyNumber > 0 && (task.started_at || task.status !== "queued")
796
+ ? [normalizeAttempt(task.task_id, {
797
+ number: legacyNumber,
798
+ status: projectedTaskStatus(task.status, "attempt"),
799
+ started_at: task.started_at || task.created_at || null,
800
+ finished_at: CLOSED_STATUS.has(task.status) ? task.finished_at || task.updated_at || null : null,
801
+ }, legacyNumber)]
802
+ : [];
803
+ } else {
804
+ task.attempts = task.attempts.map((attempt, index) => normalizeAttempt(task.task_id, attempt, index + 1));
805
+ }
806
+ task.change_sets = Array.isArray(task.change_sets) ? task.change_sets.map((item) => normalizeChangeSet(item)) : [];
807
+ task.attempt = task.attempts.at(-1)?.number || 0;
323
808
  return task;
324
809
  }
325
810
 
811
+ function logicalContract(task) {
812
+ return {
813
+ iteration_id: task.iteration_id,
814
+ node: task.node,
815
+ skill: task.skill,
816
+ goal: task.goal,
817
+ responsibility: task.responsibility,
818
+ scope: [...task.scope].sort(),
819
+ depends_on: [...task.depends_on].sort(),
820
+ acceptance: [...task.acceptance].sort(),
821
+ };
822
+ }
823
+
824
+ function logicalContractChanges(existing, candidate) {
825
+ const before = logicalContract(existing);
826
+ const after = logicalContract(candidate);
827
+ return Object.keys(before).filter((key) => JSON.stringify(before[key]) !== JSON.stringify(after[key]));
828
+ }
829
+
830
+ function currentAttempt(task) {
831
+ return task.attempts.at(-1) || null;
832
+ }
833
+
834
+ function createAttemptPhase(sequence, phase, timestamp) {
835
+ return {
836
+ sequence,
837
+ phase: parsePhase(phase, "implementation"),
838
+ status: "active",
839
+ started_at: timestamp,
840
+ finished_at: null,
841
+ findings: [],
842
+ };
843
+ }
844
+
845
+ function startAttempt(task, phase, timestamp) {
846
+ const number = (currentAttempt(task)?.number || 0) + 1;
847
+ const attempt = normalizeAttempt(task.task_id, {
848
+ number,
849
+ status: "active",
850
+ started_at: timestamp,
851
+ phases: [createAttemptPhase(1, phase, timestamp)],
852
+ time_entries: [],
853
+ }, number);
854
+ task.attempts.push(attempt);
855
+ task.attempt = number;
856
+ return attempt;
857
+ }
858
+
859
+ function enterAttemptPhase(attempt, phase, timestamp) {
860
+ const selected = parsePhase(phase, attempt.phases.at(-1)?.phase || "implementation");
861
+ let current = attempt.phases.at(-1);
862
+ if (!current || current.phase !== selected) {
863
+ if (current && current.status === "active") {
864
+ current.status = "completed";
865
+ current.finished_at = timestamp;
866
+ }
867
+ current = createAttemptPhase(attempt.phases.length + 1, selected, timestamp);
868
+ attempt.phases.push(current);
869
+ }
870
+ return current;
871
+ }
872
+
873
+ function recordAttemptProgress(task, args, taskStatus, finish, timestamp, workflowRoot) {
874
+ if (args["phase-status"] !== undefined) {
875
+ throw new Error("--phase-status is not supported; phase status is derived from the task transition.");
876
+ }
877
+ const priorFindings = new Map(currentFindings(task).map((finding) => [finding.finding_id, finding]));
878
+ let attempt = currentAttempt(task);
879
+ if (!attempt) attempt = startAttempt(task, args.phase, task.started_at || timestamp);
880
+ const phase = enterAttemptPhase(attempt, args.phase, timestamp);
881
+ if (phase.phase === "review" && !phase.review_subject_baseline) {
882
+ phase.review_subject_baseline = captureCodeBaseline(workflowRoot);
883
+ }
884
+ const submittedFindings = (args["finding-json"] || []).map(normalizeFinding);
885
+ const currentFindingStates = new Map(priorFindings);
886
+ for (const finding of submittedFindings) {
887
+ if (finding.status === "resolved" && !finding.change_set_id) {
888
+ throw new Error(`Resolved finding ${finding.finding_id} requires change_set_id.`);
889
+ }
890
+ if (finding.status === "resolved" && !finding.evidence.length) {
891
+ throw new Error(`Resolved finding ${finding.finding_id} requires evidence.`);
892
+ }
893
+ if (finding.status === "resolved" && priorFindings.get(finding.finding_id)?.status !== "open") {
894
+ throw new Error(`Resolved finding ${finding.finding_id} requires an existing open finding.`);
895
+ }
896
+ currentFindingStates.set(finding.finding_id, finding);
897
+ }
898
+ for (const rawChangeSet of args["change-set-json"] || []) {
899
+ if (phase.phase !== "review") {
900
+ throw new Error("Approved change sets must be recorded in the review phase.");
901
+ }
902
+ const candidate = normalizeChangeSet(rawChangeSet, timestamp);
903
+ if (task.change_sets.some((item) => item.change_set_id === candidate.change_set_id)) {
904
+ throw new Error(`Change set ${candidate.change_set_id} is already recorded and immutable.`);
905
+ }
906
+ const unavailable = candidate.finding_ids.filter((findingId) => currentFindingStates.get(findingId)?.status !== "open");
907
+ if (unavailable.length) {
908
+ throw new Error(`Change set ${candidate.change_set_id} requires open collected findings: ${unavailable.join(", ")}.`);
909
+ }
910
+ const openFindings = [...currentFindingStates.values()].filter((finding) => finding.status === "open");
911
+ const omitted = openFindings
912
+ .map((finding) => finding.finding_id)
913
+ .filter((findingId) => !candidate.finding_ids.includes(findingId));
914
+ if (omitted.length) {
915
+ throw new Error(
916
+ `Change set ${candidate.change_set_id} must include the complete feedback collection; missing: ${omitted.join(", ")}.`,
917
+ );
918
+ }
919
+ const changeSet = approveChangeSet(candidate, openFindings, phase.review_subject_baseline, workflowRoot);
920
+ task.change_sets.push(changeSet);
921
+ }
922
+ const approvedChangeSets = new Map(task.change_sets.map((item) => [item.change_set_id, item]));
923
+ for (const finding of submittedFindings) {
924
+ if (finding.status !== "resolved") continue;
925
+ const changeSet = approvedChangeSets.get(finding.change_set_id);
926
+ if (!changeSet) {
927
+ throw new Error(`Resolved finding ${finding.finding_id} references change set ${finding.change_set_id}, which is not approved.`);
928
+ }
929
+ if (!changeSet.finding_ids.includes(finding.finding_id)) {
930
+ throw new Error(`Resolved finding ${finding.finding_id} is not included in approved change set ${finding.change_set_id}.`);
931
+ }
932
+ if (phase.phase !== "review") {
933
+ throw new Error(`Resolved finding ${finding.finding_id} must be recorded during concentrated rereview.`);
934
+ }
935
+ if (!finding.changed_files?.length) {
936
+ throw new Error(`Resolved finding ${finding.finding_id} requires changed_files from its approved change set.`);
937
+ }
938
+ const actualChanges = repairChangesSinceBaseline(workflowRoot, changeSet.repair_baseline);
939
+ if (!sameStringSet(finding.changed_files, actualChanges)) {
940
+ const undeclared = actualChanges.filter((file) => !finding.changed_files.includes(file));
941
+ const notActual = finding.changed_files.filter((file) => !actualChanges.includes(file));
942
+ const details = [
943
+ ...(undeclared.length ? [`undeclared: ${undeclared.join(", ")}`] : []),
944
+ ...(notActual.length ? [`not actual: ${notActual.join(", ")}`] : []),
945
+ ].join("; ");
946
+ throw new Error(
947
+ `Resolved finding ${finding.finding_id} changed_files must exactly match actual Git changes since approval${details ? `; ${details}` : ""}.`,
948
+ );
949
+ }
950
+ const outsideFiles = finding.changed_files.filter((file) => !changeSet.allowed_files.includes(file));
951
+ if (outsideFiles.length) {
952
+ throw new Error(
953
+ `Resolved finding ${finding.finding_id} changed files outside approved change set ${finding.change_set_id}: ${outsideFiles.join(", ")}.`,
954
+ );
955
+ }
956
+ const missingEvidence = changeSet.expected_evidence.filter((item) => !finding.evidence.includes(item));
957
+ if (missingEvidence.length) {
958
+ throw new Error(
959
+ `Resolved finding ${finding.finding_id} is missing expected evidence for ${finding.change_set_id}: ${missingEvidence.join(", ")}.`,
960
+ );
961
+ }
962
+ }
963
+ phase.findings.push(...submittedFindings);
964
+ const elapsedMinutes = parseElapsedMinutes(args["elapsed-minutes"]);
965
+ const timeCategory = args["time-category"];
966
+ if ((elapsedMinutes === null) !== (timeCategory === undefined)) {
967
+ throw new Error("--time-category and --elapsed-minutes must be provided together.");
968
+ }
969
+ if (timeCategory !== undefined) {
970
+ if (!VALID_TIME_CATEGORY.has(timeCategory)) {
971
+ throw new Error(`Invalid time category: ${timeCategory}; use ${[...VALID_TIME_CATEGORY].join(", ")}.`);
972
+ }
973
+ attempt.time_entries.push({
974
+ category: timeCategory,
975
+ elapsed_minutes: elapsedMinutes,
976
+ phase: phase.phase,
977
+ recorded_at: timestamp,
978
+ });
979
+ attempt.time_by_category[timeCategory] = (attempt.time_by_category[timeCategory] || 0) + elapsedMinutes;
980
+ }
981
+ phase.status = finish ? projectedTaskStatus(taskStatus, "phase") : "active";
982
+ phase.finished_at = phase.status === "active" ? null : timestamp;
983
+ attempt.status = finish ? projectedTaskStatus(taskStatus, "attempt") : "active";
984
+ attempt.finished_at = new Set(["completed", "failed", "abandoned"]).has(attempt.status) ? timestamp : null;
985
+ return attempt;
986
+ }
987
+
988
+ function summarizeAttempts(task) {
989
+ const timeByCategory = {};
990
+ for (const attempt of task.attempts) {
991
+ for (const [category, minutes] of Object.entries(attempt.time_by_category || {})) {
992
+ timeByCategory[category] = (timeByCategory[category] || 0) + Number(minutes || 0);
993
+ }
994
+ }
995
+ return {
996
+ total: task.attempts.length,
997
+ active: task.attempts.filter((attempt) => attempt.status === "active").length,
998
+ paused: task.attempts.filter((attempt) => attempt.status === "paused").length,
999
+ completed: task.attempts.filter((attempt) => attempt.status === "completed").length,
1000
+ failed: task.attempts.filter((attempt) => attempt.status === "failed").length,
1001
+ abandoned: task.attempts.filter((attempt) => attempt.status === "abandoned").length,
1002
+ current_attempt_id: currentAttempt(task)?.attempt_id || null,
1003
+ current_phase: currentAttempt(task)?.phases.at(-1)?.phase || null,
1004
+ time_by_category: timeByCategory,
1005
+ };
1006
+ }
1007
+
1008
+ function currentFindings(task) {
1009
+ const findingsById = new Map();
1010
+ for (const attempt of task.attempts) {
1011
+ for (const phase of attempt.phases) {
1012
+ for (const finding of phase.findings) findingsById.set(finding.finding_id, finding);
1013
+ }
1014
+ }
1015
+ return [...findingsById.values()];
1016
+ }
1017
+
1018
+ function assertNoOpenBlockingFindings(task) {
1019
+ const blocking = currentFindings(task)
1020
+ .filter((finding) => finding.severity === "blocking" && finding.status === "open")
1021
+ .map((finding) => finding.finding_id);
1022
+ if (blocking.length) {
1023
+ throw new Error(`Task ${task.task_id} has open blocking findings: ${blocking.join(", ")}.`);
1024
+ }
1025
+ }
1026
+
326
1027
  function stableValue(value) {
327
1028
  if (Array.isArray(value)) return value.map(stableValue);
328
1029
  if (!value || typeof value !== "object") return value;
329
1030
  return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stableValue(value[key])]));
330
1031
  }
331
1032
 
1033
+ function checksumJson(value) {
1034
+ return sha256Content(JSON.stringify(stableValue(value)));
1035
+ }
1036
+
1037
+ function sameStringSet(left, right) {
1038
+ if (left.length !== right.length) return false;
1039
+ const sortedLeft = [...left].sort();
1040
+ const sortedRight = [...right].sort();
1041
+ return sortedLeft.every((item, index) => item === sortedRight[index]);
1042
+ }
1043
+
332
1044
  function comparableTaskState(task) {
333
1045
  const comparable = { ...task };
334
1046
  const stateRoot = path.resolve(comparable.state_root || ".");
@@ -370,8 +1082,11 @@ function taskSources(runtime, workflowRoot, stateRoot, taskId) {
370
1082
 
371
1083
  function appendEvent(runtime, taskId, type, details = {}) {
372
1084
  const file = path.join(runtime, "events", `${taskId}.jsonl`);
1085
+ const previous = stagedContent(file)?.toString("utf8") || "";
1086
+ const content = `${previous}${JSON.stringify({ at: now(), type, task_id: taskId, ...details })}\n`;
1087
+ if (stageWrite(file, content)) return;
373
1088
  fs.mkdirSync(path.dirname(file), { recursive: true });
374
- fs.appendFileSync(file, `${JSON.stringify({ at: now(), type, task_id: taskId, ...details })}\n`, "utf8");
1089
+ fs.writeFileSync(file, content, "utf8");
375
1090
  }
376
1091
 
377
1092
  function copyIfExists(source, destination) {
@@ -393,7 +1108,9 @@ function clearTaskMarker(task) {
393
1108
  if (!fs.existsSync(markerFile)) return;
394
1109
  try {
395
1110
  const marker = JSON.parse(fs.readFileSync(markerFile, "utf8"));
396
- if (marker.task_id === task.task_id) fs.unlinkSync(markerFile);
1111
+ if (marker.task_id === task.task_id) {
1112
+ if (!stageDelete(markerFile)) fs.unlinkSync(markerFile);
1113
+ }
397
1114
  } catch {
398
1115
  // Preserve an invalid marker for explicit reconciliation.
399
1116
  }
@@ -446,12 +1163,87 @@ function copyTaskToPortableArchive(stateRoot, workflowRoot, task, archiveDirecto
446
1163
  return portableTask;
447
1164
  }
448
1165
 
1166
+ function sha256Content(value) {
1167
+ return crypto.createHash("sha256").update(value).digest("hex");
1168
+ }
1169
+
1170
+ function formalVersionArchiveReferences(stateRoot, workflowRoot, iterationId, requestedCandidateId) {
1171
+ const stateCli = path.join(workflowRoot, "tools", "state", "state.mjs");
1172
+ if (!fs.existsSync(stateCli)) {
1173
+ throw new Error(`Formal-state CLI is missing: ${stateCli}`);
1174
+ }
1175
+ const command = [
1176
+ stateCli,
1177
+ "snapshot", "references",
1178
+ "--project", stateRoot,
1179
+ "--iteration-id", iterationId,
1180
+ ];
1181
+ if (requestedCandidateId) command.push("--candidate-id", requestedCandidateId);
1182
+ const result = spawnSync(process.execPath, command, {
1183
+ cwd: stateRoot,
1184
+ encoding: "utf8",
1185
+ windowsHide: true,
1186
+ });
1187
+ if (result.status !== 0) {
1188
+ const message = (result.stderr || result.error?.message || "Formal-state reference projection failed.")
1189
+ .trim()
1190
+ .replace(/^ai-delivery-state:\s*/, "");
1191
+ throw new Error(message);
1192
+ }
1193
+ let projection;
1194
+ try {
1195
+ projection = JSON.parse(result.stdout);
1196
+ } catch {
1197
+ throw new Error("Formal-state reference projection returned invalid JSON.");
1198
+ }
1199
+ if (projection?.command !== "snapshot references" || !projection.references) {
1200
+ throw new Error("Formal-state reference projection is incomplete.");
1201
+ }
1202
+ return projection.references;
1203
+ }
1204
+
1205
+ function archivedTaskReferences(stateRoot, archivedTasks) {
1206
+ const taskSummaries = [];
1207
+ const attemptReceipts = [];
1208
+ for (const task of archivedTasks) {
1209
+ const taskRef = `${task.archive_path}/task.json`;
1210
+ const taskFile = path.resolve(stateRoot, ...taskRef.split("/"));
1211
+ taskSummaries.push({
1212
+ task_id: task.task_id,
1213
+ status: task.status,
1214
+ finished_at: task.finished_at,
1215
+ ref: taskRef,
1216
+ checksum_algorithm: "sha256",
1217
+ checksum: sha256Content(fs.readFileSync(taskFile)),
1218
+ });
1219
+ for (const [index, attempt] of (task.attempts || []).entries()) {
1220
+ const receipt = stableValue({
1221
+ task_id: task.task_id,
1222
+ attempt_id: attempt.attempt_id,
1223
+ number: attempt.number,
1224
+ status: attempt.status,
1225
+ started_at: attempt.started_at,
1226
+ finished_at: attempt.finished_at,
1227
+ phase_count: Array.isArray(attempt.phases) ? attempt.phases.length : 0,
1228
+ });
1229
+ attemptReceipts.push({
1230
+ ...receipt,
1231
+ ref: `${taskRef}#/attempts/${index}`,
1232
+ checksum_algorithm: "sha256",
1233
+ receipt_checksum: sha256Content(JSON.stringify(stableValue(attempt))),
1234
+ });
1235
+ }
1236
+ }
1237
+ return { task_summaries: taskSummaries, attempt_receipts: attemptReceipts };
1238
+ }
1239
+
449
1240
  function writeCheckpoint(runtime, task) {
450
1241
  const stamp = now().replaceAll(":", "-");
451
1242
  atomicWrite(path.join(runtime, "checkpoints", task.task_id, `${stamp}.json`), task);
452
1243
  }
453
1244
 
454
1245
  function writeResume(runtime, task, reconciliation = null) {
1246
+ const attemptSummary = summarizeAttempts(task);
455
1247
  const lines = [
456
1248
  `# Task Recovery: ${task.task_id}`,
457
1249
  "",
@@ -459,6 +1251,9 @@ function writeResume(runtime, task, reconciliation = null) {
459
1251
  "## Recorded State", "", `- Status: ${task.status}`, `- Node: ${task.node}`, `- Skill: ${task.skill}`,
460
1252
  `- Sequence: ${task.sequence || "not recorded"}`,
461
1253
  `- Dependencies: ${task.depends_on.length ? task.depends_on.join(", ") : "none"}`,
1254
+ `- Attempts: ${attemptSummary.total}`,
1255
+ `- Current attempt: ${attemptSummary.current_attempt_id || "not started"}`,
1256
+ `- Current phase: ${attemptSummary.current_phase || "not started"}`,
462
1257
  `- Next action owner: ${task.next_action_owner}`,
463
1258
  `- Branch: ${task.branch || "not recorded"}`, `- Worktree: ${task.worktree || "not recorded"}`, "",
464
1259
  "## Completed Work And Evidence", "",
@@ -473,6 +1268,7 @@ function writeResume(runtime, task, reconciliation = null) {
473
1268
  lines.push("## Reconciled Workspace And Git State", "", "```json", JSON.stringify(reconciliation, null, 2), "```", "");
474
1269
  }
475
1270
  const file = path.join(runtime, "resumes", `${task.task_id}.md`);
1271
+ if (stageWrite(file, `${lines.join("\n")}\n`)) return;
476
1272
  fs.mkdirSync(path.dirname(file), { recursive: true });
477
1273
  fs.writeFileSync(file, `${lines.join("\n")}\n`, "utf8");
478
1274
  }
@@ -483,13 +1279,26 @@ function gitState(cwd) {
483
1279
  if (root.status !== 0) return { repository: false };
484
1280
  const branch = run(["branch", "--show-current"]);
485
1281
  const head = run(["rev-parse", "HEAD"]);
486
- const status = run(["status", "--porcelain=v1"]);
1282
+ const status = run(["status", "--porcelain=v1", "-z", "--untracked-files=all"]);
1283
+ const statusFields = status.stdout.split("\0");
1284
+ const dirtyPaths = [];
1285
+ for (let index = 0; index < statusFields.length; index += 1) {
1286
+ const record = statusFields[index];
1287
+ if (!record) continue;
1288
+ const state = record.slice(0, 2);
1289
+ dirtyPaths.push(record.slice(3).replaceAll("\\", "/"));
1290
+ if (state.includes("R") || state.includes("C")) {
1291
+ const source = statusFields[index + 1];
1292
+ if (source) dirtyPaths.push(source.replaceAll("\\", "/"));
1293
+ index += 1;
1294
+ }
1295
+ }
487
1296
  return {
488
1297
  repository: true,
489
1298
  root: root.stdout.trim(),
490
1299
  branch: branch.stdout.trim(),
491
1300
  head: head.status === 0 ? head.stdout.trim() : null,
492
- dirty_paths: status.stdout.split(/\r?\n/).filter(Boolean).map((line) => line.slice(3)),
1301
+ dirty_paths: [...new Set(dirtyPaths)].sort((left, right) => left.localeCompare(right)),
493
1302
  };
494
1303
  }
495
1304
 
@@ -507,6 +1316,19 @@ function readRuntimeTasks(runtime) {
507
1316
  .map((name) => normalizeTask(JSON.parse(fs.readFileSync(path.join(tasksDir, name), "utf8"))));
508
1317
  }
509
1318
 
1319
+ function readReconciledTasks(runtime, workflowRoot, stateRoot) {
1320
+ const localById = new Map(readRuntimeTasks(runtime).map((task) => [task.task_id, task]));
1321
+ const sharedById = new Map(readSharedTasks(workflowRoot, stateRoot).map((task) => [task.task_id, task]));
1322
+ const taskIds = [...new Set([...localById.keys(), ...sharedById.keys()])].sort();
1323
+ const tasks = taskIds.map((taskId) => {
1324
+ const local = localById.get(taskId) || null;
1325
+ const shared = sharedById.get(taskId) || null;
1326
+ if (local && shared) assertTaskSourcesMatch(local, shared);
1327
+ return local || shared;
1328
+ });
1329
+ return { localById, sharedById, tasks };
1330
+ }
1331
+
510
1332
  function dependencyState(task, tasksById) {
511
1333
  const missing = [];
512
1334
  const incomplete = [];
@@ -526,19 +1348,24 @@ function summarizeIteration(tasks, iterationId) {
526
1348
  const completed = iterationTasks.filter((task) => task.status === "completed");
527
1349
  const closed = iterationTasks.filter((task) => CLOSED_STATUS.has(task.status));
528
1350
  const active = iterationTasks.filter((task) => RESUMABLE_STATUS.has(task.status));
1351
+ const retryable = iterationTasks.filter((task) => task.status === "failed");
529
1352
  const queued = iterationTasks.filter((task) => task.status === "queued");
530
1353
  const ready = queued.filter((task) => dependencyState(task, tasksById).satisfied);
531
1354
  const waiting = queued
532
1355
  .filter((task) => !ready.includes(task))
533
1356
  .map((task) => ({ task_id: task.task_id, ...dependencyState(task, tasksById) }));
534
- const recommended = [...active, ...ready].sort(compareTasks)[0] || null;
1357
+ const recommended = active.sort(compareTasks)[0]
1358
+ || retryable.sort(compareTasks)[0]
1359
+ || ready.sort(compareTasks)[0]
1360
+ || null;
535
1361
  return {
536
1362
  iteration_id: iterationId,
537
1363
  total_tasks: iterationTasks.length,
538
1364
  completed_tasks: completed.length,
539
1365
  closed_tasks: closed.length,
540
- remaining_tasks: iterationTasks.length - closed.length,
1366
+ remaining_tasks: iterationTasks.filter((task) => !RECOVERY_COMPLETE_STATUS.has(task.status)).length,
541
1367
  active_task_ids: active.map((task) => task.task_id),
1368
+ retryable_task_ids: retryable.map((task) => task.task_id),
542
1369
  queued_task_ids: queued.map((task) => task.task_id),
543
1370
  ready_task_ids: ready.map((task) => task.task_id),
544
1371
  waiting_tasks: waiting,
@@ -552,6 +1379,7 @@ function summarizeIteration(tasks, iterationId) {
552
1379
  function commandInit(args) {
553
1380
  const taskId = sanitizeId(requireValue(args, "task-id"));
554
1381
  const iterationId = sanitizeId(requireValue(args, "iteration-id"));
1382
+ const node = assertNotLegacyDevelopmentNode(requireValue(args, "node"));
555
1383
  const { projectRoot, stateRoot, workflowRoot, runtime } = statePaths(args);
556
1384
  return withTaskLock(runtime, `version.${iterationId}`, () => withTaskLock(runtime, taskId, () => {
557
1385
  if (fs.existsSync(archivedTaskIndexFile(runtime, taskId))) {
@@ -561,32 +1389,83 @@ function commandInit(args) {
561
1389
  throw new Error(`Version is already archived and cannot accept new tasks: ${iterationId}`);
562
1390
  }
563
1391
  const file = taskFile(runtime, taskId);
564
- const existing = fs.existsSync(file)
1392
+ const local = fs.existsSync(file)
565
1393
  ? normalizeTask(JSON.parse(fs.readFileSync(file, "utf8")))
566
1394
  : null;
1395
+ const shared = readSharedTasks(workflowRoot, stateRoot).find((item) => item.task_id === taskId) || null;
1396
+ if (local && shared) assertTaskSourcesMatch(local, shared);
1397
+ const existing = local || shared;
567
1398
  if (existing && existing.iteration_id !== iterationId) {
568
1399
  throw new Error(`Task ${taskId} already belongs to iteration ${existing.iteration_id}`);
569
1400
  }
570
- const shared = readSharedTasks(workflowRoot, stateRoot).find((item) => item.task_id === taskId);
571
- if (existing && shared) assertTaskSourcesMatch(existing, shared);
572
1401
  if (existing) assertWaitStateTransfer(existing, args, "init");
573
- const status = args.status || "preparing";
1402
+ const status = args.status || "queued";
1403
+ if (!VALID_STATUS.has(status)) throw new Error(`Invalid status: ${status}`);
1404
+ if (CLOSED_STATUS.has(status)) {
1405
+ throw new Error(`init cannot set closed status ${status}; initialize, activate, and finish the task instead.`);
1406
+ }
1407
+ if (status !== "queued") {
1408
+ throw new Error(`init creates queued tasks only, not ${status}; use activate to start the task.`);
1409
+ }
574
1410
  const dependsOn = (args["depends-on"] || existing?.depends_on || []).map(sanitizeId);
575
1411
  if (dependsOn.includes(taskId)) throw new Error(`Task cannot depend on itself: ${taskId}`);
576
- const timestamp = now();
577
- const task = {
1412
+ const responsibility = args.responsibility || existing?.responsibility || requireValue(args, "skill");
1413
+ const contractFields = {
578
1414
  task_id: taskId,
579
1415
  iteration_id: iterationId,
580
- node: requireValue(args, "node"),
1416
+ node,
581
1417
  skill: requireValue(args, "skill"),
582
- status,
583
- sequence: parseSequence(args.sequence ?? existing?.sequence),
584
- depends_on: dependsOn,
585
- attempt: existing ? Number(existing.attempt || 0) + 1 : 1,
586
1418
  goal: requireValue(args, "goal"),
1419
+ responsibility,
587
1420
  scope: args.scope || existing?.scope || [],
1421
+ depends_on: dependsOn,
1422
+ acceptance: args.acceptance || existing?.acceptance || [],
1423
+ };
1424
+ const candidateContract = normalizeTask({
1425
+ ...(existing || {}),
1426
+ ...contractFields,
1427
+ next_action_owner: existing?.next_action_owner || "agent",
1428
+ });
1429
+ if (existing) {
1430
+ const changed = logicalContractChanges(existing, candidateContract);
1431
+ if (changed.length) {
1432
+ throw new Error(
1433
+ `Logical task contract changed for ${taskId}: ${changed.join(", ")}. Use a new task ID and link it with --derived-from or --supersedes.`,
1434
+ );
1435
+ }
1436
+ return { ...existing, idempotent: true };
1437
+ }
1438
+ const knownTasks = readReconciledTasks(runtime, workflowRoot, stateRoot).tasks;
1439
+ const duplicateContract = knownTasks.find((task) => logicalContractChanges(task, candidateContract).length === 0);
1440
+ if (duplicateContract) {
1441
+ throw new Error(
1442
+ `Task ${taskId} has the same logical contract as ${duplicateContract.task_id}; reuse ${duplicateContract.task_id}.`,
1443
+ );
1444
+ }
1445
+ const derivedFrom = args["derived-from"] && args["derived-from"] !== true
1446
+ ? sanitizeId(args["derived-from"])
1447
+ : null;
1448
+ const supersedes = args.supersedes && args.supersedes !== true ? sanitizeId(args.supersedes) : null;
1449
+ if (derivedFrom && supersedes) throw new Error("Use only one of --derived-from or --supersedes.");
1450
+ if (derivedFrom === taskId || supersedes === taskId) throw new Error(`Task ${taskId} cannot relate to itself.`);
1451
+ const relationTarget = derivedFrom || supersedes;
1452
+ if (relationTarget) {
1453
+ const known = knownTasks.some((task) => task.task_id === relationTarget)
1454
+ || fs.existsSync(archivedTaskIndexFile(runtime, relationTarget));
1455
+ if (!known) throw new Error(`Related task not found: ${relationTarget}`);
1456
+ }
1457
+ const timestamp = now();
1458
+ const task = {
1459
+ ...contractFields,
1460
+ status,
1461
+ sequence: parseSequence(args.sequence ?? existing?.sequence),
1462
+ attempt: 0,
1463
+ attempts: [],
1464
+ change_sets: [],
588
1465
  input_artifacts: args["input-artifact"] || existing?.input_artifacts || [],
589
1466
  standards: args.standard || existing?.standards || [],
1467
+ derived_from: derivedFrom,
1468
+ supersedes,
590
1469
  branch: args.branch || existing?.branch || null,
591
1470
  worktree: path.resolve(args.worktree || projectRoot),
592
1471
  state_root: stateRoot,
@@ -598,19 +1477,19 @@ function commandInit(args) {
598
1477
  next_action_owner: parseNextActionOwner(args["next-action-owner"], existing?.next_action_owner),
599
1478
  evidence: existing?.evidence || [],
600
1479
  created_at: existing?.created_at || timestamp,
601
- started_at: status === "queued" ? existing?.started_at || null : existing?.started_at || timestamp,
1480
+ started_at: null,
602
1481
  updated_at: timestamp,
603
- finished_at: CLOSED_STATUS.has(status) ? existing?.finished_at || timestamp : null,
1482
+ finished_at: null,
604
1483
  };
605
- if (!VALID_STATUS.has(task.status)) throw new Error(`Invalid status: ${task.status}`);
606
1484
  writeSharedTask(runtime, workflowRoot, task);
607
1485
  writeResume(runtime, task);
608
- if (RESUMABLE_STATUS.has(status)) {
609
- atomicWrite(taskMarkerFile(stateRoot), { task_id: taskId, state_root: stateRoot });
610
- } else {
611
- clearTaskMarker(task);
612
- }
613
- appendEvent(runtime, taskId, "task-initialized", { status: task.status, attempt: task.attempt });
1486
+ clearTaskMarker(task);
1487
+ appendEvent(runtime, taskId, "task-initialized", {
1488
+ status: task.status,
1489
+ attempt: task.attempt,
1490
+ derived_from: task.derived_from,
1491
+ supersedes: task.supersedes,
1492
+ });
614
1493
  return task;
615
1494
  }));
616
1495
  }
@@ -634,7 +1513,14 @@ function commandActivate(args) {
634
1513
  const currentSources = taskSources(runtime, workflowRoot, stateRoot, taskId);
635
1514
  const task = currentSources.task;
636
1515
  if (!task) throw new Error(`Task not found: ${taskId}`);
637
- if (!new Set(["queued", "preparing", "checkpointed", "blocked", "interrupted"]).has(task.status)) {
1516
+ const newAttempt = Boolean(args["new-attempt"]);
1517
+ if (task.status === "failed" && !newAttempt) {
1518
+ throw new Error(`Task ${taskId} failed; retry the same logical task with --new-attempt.`);
1519
+ }
1520
+ if (newAttempt && task.status !== "failed") {
1521
+ throw new Error(`--new-attempt is only valid when retrying a failed logical task; ${taskId} is ${task.status}.`);
1522
+ }
1523
+ if (!new Set(["queued", "preparing", "checkpointed", "blocked", "interrupted", "failed"]).has(task.status)) {
638
1524
  throw new Error(`Task ${taskId} cannot be activated from status ${task.status}`);
639
1525
  }
640
1526
  const sharedTasks = readSharedTasks(workflowRoot, stateRoot);
@@ -660,11 +1546,20 @@ function commandActivate(args) {
660
1546
  if (task.next_action_owner === "agent" && requestedOwner && requestedOwner !== "agent") {
661
1547
  throw new Error("activate --next-action-owner may only be agent; use checkpoint or finish to enter a wait state.");
662
1548
  }
663
- const firstStart = !task.started_at;
1549
+ const timestamp = now();
664
1550
  task.status = status;
665
- task.attempt = firstStart ? Number(task.attempt || 1) : Number(task.attempt || 0) + 1;
666
- task.started_at ||= now();
667
- task.updated_at = now();
1551
+ let attempt = currentAttempt(task);
1552
+ if (!attempt || newAttempt) attempt = startAttempt(task, args.phase, timestamp);
1553
+ else {
1554
+ attempt.status = "active";
1555
+ attempt.finished_at = null;
1556
+ const phase = enterAttemptPhase(attempt, args.phase, timestamp);
1557
+ phase.status = "active";
1558
+ phase.finished_at = null;
1559
+ }
1560
+ task.attempt = attempt.number;
1561
+ task.started_at ||= timestamp;
1562
+ task.updated_at = timestamp;
668
1563
  task.finished_at = null;
669
1564
  task.blockers = [];
670
1565
  if (args["next-action"] && args["next-action"] !== true) task.next_action = args["next-action"];
@@ -676,7 +1571,13 @@ function commandActivate(args) {
676
1571
  iteration_id: task.iteration_id,
677
1572
  state_root: task.state_root,
678
1573
  });
679
- appendEvent(runtime, taskId, "task-activated", { status, attempt: task.attempt });
1574
+ appendEvent(runtime, taskId, "task-activated", {
1575
+ status,
1576
+ attempt: task.attempt,
1577
+ attempt_id: attempt.attempt_id,
1578
+ phase: attempt.phases.at(-1)?.phase || null,
1579
+ new_attempt: newAttempt,
1580
+ });
680
1581
  return task;
681
1582
  }));
682
1583
  }
@@ -691,12 +1592,21 @@ function commandUpdate(args, finish = false) {
691
1592
  const task = readTask(runtime, taskId);
692
1593
  const shared = readSharedTasks(workflowRoot, stateRoot).find((item) => item.task_id === taskId);
693
1594
  if (shared) assertTaskSourcesMatch(task, shared);
1595
+ if (!RESUMABLE_STATUS.has(task.status)) {
1596
+ throw new Error(`Task ${taskId} is ${task.status}; activate it before checkpoint or finish.`);
1597
+ }
694
1598
  const requestedOwner = assertWaitStateTransfer(task, args, finish ? "finish" : "checkpoint");
695
1599
  const status = requireValue(args, "status");
696
1600
  if (!VALID_STATUS.has(status)) throw new Error(`Invalid status: ${status}`);
697
1601
  if (finish && !FINISH_STATUS.has(status)) {
698
1602
  throw new Error(`finish requires one of: ${[...FINISH_STATUS].join(", ")}`);
699
1603
  }
1604
+ if (!finish && CLOSED_STATUS.has(status)) {
1605
+ throw new Error(`checkpoint cannot set closed status ${status}; use finish instead.`);
1606
+ }
1607
+ if (!finish && status === "queued") {
1608
+ throw new Error("checkpoint cannot return an activated task to queued; queued tasks must be started through activate.");
1609
+ }
700
1610
  task.status = status;
701
1611
  task.next_action = requireValue(args, "next-action");
702
1612
  task.next_action_owner = requestedOwner || task.next_action_owner;
@@ -708,16 +1618,54 @@ function commandUpdate(args, finish = false) {
708
1618
  for (const output of args["actual-output"] || []) {
709
1619
  if (!task.actual_outputs.includes(output)) task.actual_outputs.push(output);
710
1620
  }
711
- task.updated_at = now();
712
- task.finished_at = finish ? now() : null;
1621
+ const timestamp = now();
1622
+ const attempt = recordAttemptProgress(task, args, status, finish, timestamp, workflowRoot);
1623
+ if (finish && status === "completed") assertNoOpenBlockingFindings(task);
1624
+ task.updated_at = timestamp;
1625
+ task.finished_at = finish ? timestamp : null;
713
1626
  writeSharedTask(runtime, workflowRoot, task);
714
1627
  writeResume(runtime, task);
715
- appendEvent(runtime, taskId, finish ? "task-finished" : "task-checkpointed", { status });
1628
+ appendEvent(runtime, taskId, finish ? "task-finished" : "task-checkpointed", {
1629
+ status,
1630
+ attempt: attempt.number,
1631
+ attempt_id: attempt.attempt_id,
1632
+ phase: attempt.phases.at(-1)?.phase || null,
1633
+ });
716
1634
  if (finish && CLOSED_STATUS.has(status)) clearTaskMarker(task);
717
1635
  return task;
718
1636
  });
719
1637
  }
720
1638
 
1639
+ function commandInspect(args) {
1640
+ const { runtime, workflowRoot, stateRoot } = statePaths(args);
1641
+ const requestedTask = args["task-id"] ? sanitizeId(args["task-id"]) : null;
1642
+ const requestedIteration = args["iteration-id"] ? sanitizeId(args["iteration-id"]) : null;
1643
+ const includeClosed = Boolean(args["include-closed"]);
1644
+ const { localById, sharedById, tasks: allTasks } = readReconciledTasks(runtime, workflowRoot, stateRoot);
1645
+ if (requestedTask && !allTasks.some((task) => task.task_id === requestedTask)) {
1646
+ throw new Error(`Task not found: ${requestedTask}`);
1647
+ }
1648
+ const tasksById = new Map(allTasks.map((task) => [task.task_id, task]));
1649
+ const selected = allTasks
1650
+ .filter((task) => !requestedTask || task.task_id === requestedTask)
1651
+ .filter((task) => !requestedIteration || task.iteration_id === requestedIteration)
1652
+ .filter((task) => includeClosed || !CLOSED_STATUS.has(task.status))
1653
+ .sort(compareTasks);
1654
+ const iterationIds = [...new Set(selected.map((task) => task.iteration_id))].sort();
1655
+ return {
1656
+ read_only: true,
1657
+ tasks: selected.map((task) => ({
1658
+ task,
1659
+ source: localById.has(task.task_id) && sharedById.has(task.task_id)
1660
+ ? "local-and-shared"
1661
+ : localById.has(task.task_id) ? "local" : "shared",
1662
+ dependency_state: dependencyState(task, tasksById),
1663
+ attempt_summary: summarizeAttempts(task),
1664
+ })),
1665
+ iterations: iterationIds.map((iterationId) => summarizeIteration(allTasks, iterationId)),
1666
+ };
1667
+ }
1668
+
721
1669
  function commandArchiveVersion(args) {
722
1670
  const { runtime, stateRoot, workflowRoot } = statePaths(args);
723
1671
  const iterationId = sanitizeId(requireValue(args, "iteration-id"));
@@ -748,6 +1696,7 @@ function commandArchiveVersion(args) {
748
1696
  .map((name) => normalizeTask(JSON.parse(fs.readFileSync(path.join(tasksDir, name), "utf8"))))
749
1697
  .filter((task) => task.iteration_id === iterationId);
750
1698
  if (!tasks.length) throw new Error(`No tasks found for version: ${iterationId}`);
1699
+ for (const task of tasks) assertNotLegacyDevelopmentNode(task.node);
751
1700
  const sharedById = new Map(readSharedTasks(workflowRoot, stateRoot).map((task) => [task.task_id, task]));
752
1701
  for (const task of tasks) {
753
1702
  const shared = sharedById.get(task.task_id);
@@ -758,6 +1707,9 @@ function commandArchiveVersion(args) {
758
1707
  if (notClosed.length) {
759
1708
  throw new Error(`Version ${iterationId} is not closed; active tasks: ${notClosed.map((task) => `${task.task_id}:${task.status}`).join(", ")}`);
760
1709
  }
1710
+ const formalReferences = formalVersionArchiveReferences(
1711
+ stateRoot, workflowRoot, iterationId, candidateId,
1712
+ );
761
1713
 
762
1714
  const archivedAt = now();
763
1715
  const writeRuntimeArchive = !Boolean(args["portable-only"]);
@@ -767,6 +1719,7 @@ function commandArchiveVersion(args) {
767
1719
  }
768
1720
  const archivedTasks = tasks.map((task) =>
769
1721
  copyTaskToPortableArchive(stateRoot, workflowRoot, task, portableArchiveDirectory, archivedAt));
1722
+ const taskReferences = archivedTaskReferences(stateRoot, archivedTasks);
770
1723
  const relativeArchive = path.relative(stateRoot, portableArchiveDirectory).replaceAll("\\", "/");
771
1724
  const manifest = {
772
1725
  iteration_id: iterationId,
@@ -777,6 +1730,13 @@ function commandArchiveVersion(args) {
777
1730
  task_count: archivedTasks.length,
778
1731
  archived_at: archivedAt,
779
1732
  archive_path: relativeArchive,
1733
+ references: {
1734
+ ...taskReferences,
1735
+ history_anchor: formalReferences?.history_anchor || null,
1736
+ scope: formalReferences?.scope || null,
1737
+ candidate: formalReferences?.candidate || null,
1738
+ gates: formalReferences?.gates || [],
1739
+ },
780
1740
  tasks: archivedTasks.map((task) => ({
781
1741
  task_id: task.task_id,
782
1742
  node: task.node,
@@ -816,11 +1776,12 @@ function commandRecover(args) {
816
1776
  const includeClosed = Boolean(args["include-closed"]);
817
1777
  const localTasks = readRuntimeTasks(runtime);
818
1778
  const localById = new Map(localTasks.map((task) => [task.task_id, task]));
1779
+ const missingLocalTaskIds = new Set();
819
1780
  for (const shared of readSharedTasks(workflowRoot, stateRoot)) {
820
1781
  const local = localById.get(shared.task_id);
821
1782
  if (!local) {
822
1783
  localById.set(shared.task_id, shared);
823
- if (shared.next_action_owner === "agent") atomicWrite(taskFile(runtime, shared.task_id), shared);
1784
+ if (shared.next_action_owner === "agent") missingLocalTaskIds.add(shared.task_id);
824
1785
  } else {
825
1786
  assertTaskSourcesMatch(local, shared);
826
1787
  }
@@ -831,10 +1792,15 @@ function commandRecover(args) {
831
1792
  ? allTasks.filter((task) => task.iteration_id === requestedIteration)
832
1793
  : allTasks;
833
1794
  const iterationIds = [...new Set(selectedTasks.map((task) => task.iteration_id))].sort();
1795
+ const reportableTasks = selectedTasks
1796
+ .filter((task) => !requested || task.task_id === requested)
1797
+ .filter((task) => includeClosed || !RECOVERY_COMPLETE_STATUS.has(task.status));
1798
+ for (const task of reportableTasks) assertNotLegacyDevelopmentNode(task.node);
1799
+ for (const task of reportableTasks) {
1800
+ if (missingLocalTaskIds.has(task.task_id)) atomicWrite(taskFile(runtime, task.task_id), task);
1801
+ }
834
1802
  const reports = [];
835
- for (const task of selectedTasks.sort(compareTasks)) {
836
- if (requested && task.task_id !== requested) continue;
837
- if (!includeClosed && CLOSED_STATUS.has(task.status)) continue;
1803
+ for (const task of reportableTasks.sort(compareTasks)) {
838
1804
  const heartbeatFile = path.join(runtime, "heartbeats", `${task.task_id}.json`);
839
1805
  const heartbeat = fs.existsSync(heartbeatFile) ? JSON.parse(fs.readFileSync(heartbeatFile, "utf8")) : null;
840
1806
  const latest = new Date(heartbeat?.updated_at || task.updated_at).getTime();
@@ -886,6 +1852,8 @@ function commandPublish(args) {
886
1852
  const sharedFile = sharedTaskFile(workflowRoot, taskId);
887
1853
  const checkpointDirectory = path.join(workflowRoot, "control", "checkpoints", taskId);
888
1854
  if (!fs.existsSync(sharedFile)) throw new Error(`Shared task state not found: ${taskId}`);
1855
+ const shared = JSON.parse(fs.readFileSync(sharedFile, "utf8"));
1856
+ assertNotLegacyDevelopmentNode(shared.node);
889
1857
 
890
1858
  const repository = gitState(stateRoot);
891
1859
  if (!repository.repository || path.resolve(repository.root).toLowerCase() !== path.resolve(stateRoot).toLowerCase()) {
@@ -926,7 +1894,6 @@ function commandPublish(args) {
926
1894
  if (upstream.status === 0 && upstream.stdout.trim() !== `origin/${expectedBranch}`) {
927
1895
  throw new Error(`Workflow upstream must be origin/${expectedBranch}, found ${upstream.stdout.trim()}.`);
928
1896
  }
929
- const shared = JSON.parse(fs.readFileSync(sharedFile, "utf8"));
930
1897
  const workflowDocumentRoot = path.join(stateRoot, ".workflow");
931
1898
  const taskOwnedArtifacts = [...(shared.actual_outputs || []), ...(shared.evidence || [])]
932
1899
  .map((value) => String(value).replaceAll("\\", "/"))
@@ -1007,6 +1974,32 @@ function taskEvolutionContext(args, command) {
1007
1974
  };
1008
1975
  }
1009
1976
 
1977
+ function assertWritableEvolutionTarget(args, command) {
1978
+ if (!new Set(["init", "activate", "checkpoint", "finish", "archive-version"]).has(command)) return;
1979
+ if (command === "init") {
1980
+ const node = args.node && args.node !== true ? String(args.node) : null;
1981
+ if (node) assertNotLegacyDevelopmentNode(node);
1982
+ return;
1983
+ }
1984
+ const { runtime, workflowRoot, stateRoot } = statePaths(args);
1985
+ if (command === "archive-version") {
1986
+ const iterationId = args["iteration-id"] && args["iteration-id"] !== true
1987
+ ? sanitizeId(args["iteration-id"])
1988
+ : null;
1989
+ const tasksDir = path.join(runtime, "tasks");
1990
+ if (!iterationId || !fs.existsSync(tasksDir)) return;
1991
+ for (const name of fs.readdirSync(tasksDir).filter((item) => item.endsWith(".json"))) {
1992
+ const task = normalizeTask(JSON.parse(fs.readFileSync(path.join(tasksDir, name), "utf8")));
1993
+ if (task.iteration_id === iterationId) assertNotLegacyDevelopmentNode(task.node);
1994
+ }
1995
+ return;
1996
+ }
1997
+ const taskId = args["task-id"] && args["task-id"] !== true ? sanitizeId(args["task-id"]) : null;
1998
+ if (!taskId) return;
1999
+ const task = taskSources(runtime, workflowRoot, stateRoot, taskId).task;
2000
+ if (task) assertNotLegacyDevelopmentNode(task.node);
2001
+ }
2002
+
1010
2003
  function dispatchTaskEvolution(context, phase, result = null, error = null) {
1011
2004
  if (!context) return { decision: "pass" };
1012
2005
  const identity = context.taskId || context.iterationId || "unspecified";
@@ -1049,24 +2042,66 @@ function dispatchTaskEvolution(context, phase, result = null, error = null) {
1049
2042
 
1050
2043
  const args = parseArgs(process.argv.slice(2));
1051
2044
  const command = args._[0];
1052
- const evolutionContext = taskEvolutionContext(args, command);
2045
+ let evolutionContext = null;
1053
2046
  try {
2047
+ assertWritableEvolutionTarget(args, command);
2048
+ evolutionContext = taskEvolutionContext(args, command);
1054
2049
  const before = dispatchTaskEvolution(evolutionContext, "before");
1055
2050
  if (before.decision === "block") {
1056
2051
  throw new Error(`Evolution Hook blocked ${command}: ${(before.messages || []).join("; ") || "no rationale"}`);
1057
2052
  }
2053
+ let managerTransactionDescriptor = null;
2054
+ if (args["manager-transaction"]) {
2055
+ if (!new Set(["init", "activate", "checkpoint", "finish"]).has(command)) {
2056
+ throw new Error("Manager task transaction is limited to task mutation commands");
2057
+ }
2058
+ try {
2059
+ managerTransactionDescriptor = JSON.parse(fs.readFileSync(0, "utf8"));
2060
+ } catch {
2061
+ throw new Error("Manager task transaction descriptor must be valid JSON");
2062
+ }
2063
+ activeWriteTransaction = { entries: new Map() };
2064
+ }
1058
2065
  let result;
1059
2066
  if (command === "init") result = commandInit(args);
1060
2067
  else if (command === "activate") result = commandActivate(args);
1061
2068
  else if (command === "checkpoint") result = commandUpdate(args, false);
1062
2069
  else if (command === "finish") result = commandUpdate(args, true);
2070
+ else if (command === "inspect") result = commandInspect(args);
1063
2071
  else if (command === "archive-version") result = commandArchiveVersion(args);
1064
2072
  else if (command === "recover") result = commandRecover(args);
1065
2073
  else if (command === "publish") result = commandPublish(args);
1066
- else throw new Error("Use init, activate, checkpoint, finish, archive-version, recover, or publish.");
2074
+ else throw new Error("Use init, activate, checkpoint, finish, inspect, archive-version, recover, or publish.");
2075
+ if (managerTransactionDescriptor) {
2076
+ const managerRuntime = path.join(
2077
+ path.resolve(args["state-root"] || args.root || findProjectRoot()),
2078
+ ".workflow", "tools", "manager", "workflow-manager-transaction.mjs",
2079
+ );
2080
+ if (!fs.existsSync(managerRuntime)) {
2081
+ throw new Error(`Workflow Manager transaction runtime is missing: ${managerRuntime}`);
2082
+ }
2083
+ const { prepareWorkflowManagerTaskTransactionWrites } = await import(pathToFileURL(managerRuntime).href);
2084
+ const prepared = prepareWorkflowManagerTaskTransactionWrites(
2085
+ path.resolve(args["state-root"] || args.root || findProjectRoot()),
2086
+ managerTransactionDescriptor,
2087
+ result,
2088
+ );
2089
+ for (const write of prepared.writes) stageWrite(write.file, write.content);
2090
+ commitWriteTransaction();
2091
+ result = {
2092
+ ...result,
2093
+ manager_transaction: {
2094
+ outcome: prepared.outcome,
2095
+ result: prepared.result,
2096
+ receipt: prepared.receipt,
2097
+ event: prepared.event,
2098
+ },
2099
+ };
2100
+ }
1067
2101
  dispatchTaskEvolution(evolutionContext, "after", result);
1068
2102
  process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
1069
2103
  } catch (error) {
2104
+ activeWriteTransaction = null;
1070
2105
  dispatchTaskEvolution(evolutionContext, "failed", null, error);
1071
2106
  process.stderr.write(`${error.message}\n`);
1072
2107
  process.exitCode = 1;