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
@@ -5,8 +5,12 @@ import path from "node:path";
5
5
  import { spawnSync } from "node:child_process";
6
6
  import { createRequire } from "node:module";
7
7
  import { fileURLToPath } from "node:url";
8
- import { isSeq, parse as parseYaml, parseDocument } from "yaml";
9
- import { inspectHookConfig, mergeHookConfig } from "./toml-hooks.mjs";
8
+ import { isSeq, parse as parseYaml, parseDocument, stringify as stringifyYaml } from "yaml";
9
+ import { assertPathInside, atomicWrite, filesUnder } from "./fs-utils.mjs";
10
+ import { inspectCodeGraph } from "./codegraph.mjs";
11
+ import { executeDeliveryStateCommand } from "./delivery-state.mjs";
12
+ import { planPrototypeMigration } from "./prototype-migration.mjs";
13
+ import { inspectHookConfig, managedHookConfigProjection, mergeHookConfig } from "./toml-hooks.mjs";
10
14
  import {
11
15
  LEGACY_WORKSPACE_CONFIG,
12
16
  WORKSPACE_CONFIG,
@@ -22,6 +26,10 @@ const PACKAGE_ROOT = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
22
26
  const MANAGED_START = "<!-- ai-delivery:start -->";
23
27
  const MANAGED_END = "<!-- ai-delivery:end -->";
24
28
  const IGNORE_ENTRIES = [".workflow/delivery/runtime/"];
29
+ const RAW_GIT_ATTRIBUTE_TARGETS = Object.freeze([
30
+ "ref/ui/README.md",
31
+ "ref/ui/LICENSE",
32
+ ]);
25
33
  const GIT_ATTRIBUTE_ENTRIES = [
26
34
  "/.workflow/** text=auto eol=lf",
27
35
  "/.agents/skills/ai-delivery-*/** text=auto eol=lf",
@@ -29,13 +37,43 @@ const GIT_ATTRIBUTE_ENTRIES = [
29
37
  "/AGENTS.md text eol=lf",
30
38
  "/.gitignore text eol=lf",
31
39
  "/.gitattributes text eol=lf",
40
+ ...RAW_GIT_ATTRIBUTE_TARGETS.map((file) => `/${file} -text`),
32
41
  ];
33
42
  const OBSOLETE_IGNORE_ENTRIES = new Set([".ai-delivery-task", ".ai-delivery/backups/"]);
34
43
  const INSTALL_MANIFEST = path.join(".workflow", "config", "install-manifest.json");
44
+ const UPGRADE_RUNTIME = path.join(".workflow", "delivery", "runtime", "upgrades");
45
+ const PRESERVED_DETAIL_LIMIT = 200;
46
+ const PRESERVED_TRANSIENT_ROOTS = Object.freeze([".workflow/delivery/runtime"]);
47
+ const DOCTOR_ATTRIBUTE_BATCH_SIZE = 5_000;
48
+ const DOCTOR_DETAIL_LIMIT = 200;
49
+ const FORMAL_STATE_INSPECT_MAX_BUFFER = 16 * 1024 * 1024;
50
+ const INSTALL_MANIFEST_SCHEMA = 6;
51
+ const UI_CONTRACT_VERSION = "prototype-first-ui-v1";
52
+ const FORMAL_STATE_YAML = {
53
+ parse: parseYaml,
54
+ stringify: (value) => stringifyYaml(value, { lineWidth: 0 }),
55
+ };
56
+ const MERGED_MANAGED_SURFACES = Object.freeze([
57
+ { key: "agents", path: "AGENTS.md" },
58
+ { key: "hook_config", path: ".codex/config.toml" },
59
+ { key: "gitignore", path: ".gitignore" },
60
+ { key: "gitattributes", path: ".gitattributes" },
61
+ ]);
35
62
  const ACTIVE_TASK = path.join(".workflow", "delivery", "runtime", "active-task.json");
36
63
  const LEGACY_ACTIVE_TASK = ".ai-delivery-task";
37
64
  const WORKFLOW_CONTENT_DIRECTORIES = ["product", "architecture", "experience", "iterations", "releases"];
38
- const CONTROL_DIRECTORIES = ["tasks", "checkpoints", "artifacts", "gates", "versions", "releases", "events", "archives"];
65
+ const CONTROL_DIRECTORIES = [
66
+ "tasks", "checkpoints", "artifacts", "gates", "versions", "releases", "events", "archives",
67
+ "migrations", "repairs",
68
+ ];
69
+ const ENSURED_CONTROL_DIRECTORIES = ["migrations", "repairs"];
70
+ const RETIRED_MANAGED_ASSETS = Object.freeze([
71
+ {
72
+ path: ".workflow/tools/viewer",
73
+ kind: "retired-directory",
74
+ manifest_key: "viewer",
75
+ },
76
+ ]);
39
77
  const ESSENTIAL_STARTUP_SKILLS = [
40
78
  "ai-delivery-assemble-release",
41
79
  "ai-delivery-bootstrap",
@@ -45,6 +83,79 @@ const ESSENTIAL_STARTUP_SKILLS = [
45
83
  "ai-delivery-orchestrate",
46
84
  "ai-delivery-orchestrate-release",
47
85
  ];
86
+ const WORKFLOW_MANAGER_LAUNCHER_FILES = Object.freeze([
87
+ "manager.mjs",
88
+ "start.cmd",
89
+ "start.ps1",
90
+ "start.sh",
91
+ ]);
92
+ const WORKFLOW_MANAGER_RUNTIME_MODULES = Object.freeze([
93
+ "workflow-manager-facts.mjs",
94
+ "workflow-manager-contracts.mjs",
95
+ "workflow-manager-transaction.mjs",
96
+ "workflow-manager-systems.mjs",
97
+ "workflow-manager-mock.mjs",
98
+ "workflow-manager-prototypes.mjs",
99
+ "workflow-manager-governance.mjs",
100
+ ]);
101
+ const PROTOTYPE_EVIDENCE_RUNTIME_MODULES = Object.freeze([
102
+ "prototype-review-target.mjs",
103
+ "prototype-page-review.mjs",
104
+ "prototype-revision.mjs",
105
+ "visual-standard.mjs",
106
+ "artifact-files.mjs",
107
+ "yaml-runtime.mjs",
108
+ ]);
109
+ const V2_PROTOTYPE_CATALOG_TEMPLATE_FILES = Object.freeze([
110
+ "prototype-terminals.csv",
111
+ "prototype-pages.csv",
112
+ "prototype-review-scenes.csv",
113
+ "prototype-trigger-conditions.csv",
114
+ "prototype-routes.csv",
115
+ "ui-mapping.csv",
116
+ "prototype-review-units.csv",
117
+ "prototype-review-unit-pages.csv",
118
+ "prototype-components.csv",
119
+ "prototype-data-contracts.csv",
120
+ "prototype-component-candidates.csv",
121
+ "prototype-component-bindings.csv",
122
+ "prototype-implementation-bindings.csv",
123
+ "prototype-source-inspection-snapshots.csv",
124
+ ]);
125
+ const EXPERIENCE_TEMPLATE_ROOT = "skills/ai-delivery-design-experience/assets/experience-template";
126
+ const V2_PROTOTYPE_DELIVERY_SURFACE_FILES = Object.freeze([
127
+ "docs/agents/workflow-manager-agent-interface.md",
128
+ "lib/workflow-manager.mjs",
129
+ "lib/workflow-manager-public/index.html",
130
+ "skills/ai-delivery-design-experience/SKILL.md",
131
+ ...[...WORKFLOW_MANAGER_RUNTIME_MODULES, ...PROTOTYPE_EVIDENCE_RUNTIME_MODULES]
132
+ .map((name) => `lib/${name}`),
133
+ ...WORKFLOW_MANAGER_LAUNCHER_FILES
134
+ .map((name) => `skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/manager/${name}`),
135
+ ...["prototype.yaml", "prototype-revision.yaml", "frontend-change-scope.yaml", ...V2_PROTOTYPE_CATALOG_TEMPLATE_FILES]
136
+ .map((name) => `${EXPERIENCE_TEMPLATE_ROOT}/${name}`),
137
+ ]);
138
+ const REQUIRED_PACKAGE_SURFACE_FILES = Object.freeze([
139
+ "LICENSE",
140
+ "bin/ai-delivery.mjs",
141
+ "docs/PROTOTYPE-MIGRATION.zh-CN.md",
142
+ "lib/mock-scenario-pack.mjs",
143
+ "lib/mock-system.mjs",
144
+ "lib/project-migration.mjs",
145
+ "lib/prototype-migration.mjs",
146
+ "lib/prototype-page-review.mjs",
147
+ "lib/prototype-review-target.mjs",
148
+ "skills/ai-delivery-checkpoint-task/scripts/hook-event.mjs",
149
+ "skills/ai-delivery-orchestrate/SKILL.md",
150
+ "skills/ai-delivery-orchestrate/assets/project-template/AGENTS.md",
151
+ "skills/ai-delivery-orchestrate/assets/project-template/gitignore.template",
152
+ "skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/mock/mock.mjs",
153
+ "skills/ai-delivery-orchestrate/assets/project-template/ref/ui/README.md",
154
+ "skills/ai-delivery-orchestrate/assets/project-template/ref/ui/LICENSE",
155
+ "skills/ai-delivery-orchestrate/assets/project-template/ref/ui/UPSTREAM.yaml",
156
+ "skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/linear.app/DESIGN.md",
157
+ ...V2_PROTOTYPE_DELIVERY_SURFACE_FILES,
158
+ ]);
48
159
  const DELIVERY_SKILLS = Object.freeze([
49
160
  "ai-delivery-assemble-release",
50
161
  "ai-delivery-bootstrap",
@@ -132,40 +243,13 @@ function validateTarget(target, options = {}) {
132
243
  }
133
244
 
134
245
  function validateManagedPath(target, destination) {
135
- const realTarget = fs.realpathSync(target);
136
- let existingAncestor = destination;
137
- while (!fs.existsSync(existingAncestor)) {
138
- const parent = path.dirname(existingAncestor);
139
- if (parent === existingAncestor) break;
140
- existingAncestor = parent;
141
- }
142
- const realAncestor = fs.realpathSync(existingAncestor);
143
- if (!isInside(realAncestor, realTarget)) {
144
- throw new Error(`Managed path resolves outside the project: ${destination} -> ${realAncestor}`);
246
+ try {
247
+ assertPathInside(target, destination);
248
+ } catch (error) {
249
+ throw new Error(`Managed path resolves outside the project: ${error.message}`);
145
250
  }
146
251
  }
147
252
 
148
- function atomicWrite(file, content) {
149
- fs.mkdirSync(path.dirname(file), { recursive: true });
150
- const temp = `${file}.${process.pid}.${Date.now()}.tmp`;
151
- fs.writeFileSync(temp, content, "utf8");
152
- fs.renameSync(temp, file);
153
- }
154
-
155
- function filesUnder(root) {
156
- if (!fs.existsSync(root)) return [];
157
- const files = [];
158
- const visit = (directory) => {
159
- for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
160
- const absolute = path.join(directory, entry.name);
161
- if (entry.isDirectory()) visit(absolute);
162
- else if (entry.isFile()) files.push(absolute);
163
- }
164
- };
165
- visit(root);
166
- return files.sort((left, right) => left.localeCompare(right));
167
- }
168
-
169
253
  function normalizedManagedContent(file) {
170
254
  const content = fs.readFileSync(file);
171
255
  return content.includes(0) ? content : Buffer.from(content.toString("utf8").replaceAll("\r\n", "\n"));
@@ -178,7 +262,8 @@ function managedFilesEqual(left, right) {
178
262
  function directoryDigest(root) {
179
263
  const hash = crypto.createHash("sha256");
180
264
  for (const file of filesUnder(root)) {
181
- hash.update(path.relative(root, file).replaceAll("\\", "/"));
265
+ const relative = path.relative(root, file).replaceAll("\\", "/");
266
+ hash.update(relative);
182
267
  hash.update("\0");
183
268
  hash.update(normalizedManagedContent(file));
184
269
  hash.update("\0");
@@ -190,6 +275,39 @@ function managedFileDigest(file) {
190
275
  return crypto.createHash("sha256").update(normalizedManagedContent(file)).digest("hex");
191
276
  }
192
277
 
278
+ function contentDigest(content) {
279
+ const value = Buffer.isBuffer(content) ? content : Buffer.from(String(content).replaceAll("\r\n", "\n"));
280
+ return crypto.createHash("sha256").update(value).digest("hex");
281
+ }
282
+
283
+ function pathDigest(target) {
284
+ if (!fs.existsSync(target)) return null;
285
+ if (fs.statSync(target).isDirectory()) return directoryDigest(target);
286
+ if (fs.statSync(target).isFile()) return managedFileDigest(target);
287
+ throw new Error(`Managed asset is not a regular file or directory: ${target}`);
288
+ }
289
+
290
+ function exactTreeDigest(root) {
291
+ if (!fs.existsSync(root)) return null;
292
+ const hash = crypto.createHash("sha256");
293
+ for (const file of filesUnder(root)) {
294
+ hash.update(path.relative(root, file).replaceAll("\\", "/"));
295
+ hash.update("\0");
296
+ hash.update(fs.readFileSync(file));
297
+ hash.update("\0");
298
+ }
299
+ return hash.digest("hex");
300
+ }
301
+
302
+ function exactPathDigest(target) {
303
+ if (!fs.existsSync(target)) return null;
304
+ if (fs.statSync(target).isDirectory()) return exactTreeDigest(target);
305
+ if (fs.statSync(target).isFile()) {
306
+ return crypto.createHash("sha256").update(fs.readFileSync(target)).digest("hex");
307
+ }
308
+ throw new Error(`Managed asset is not a regular file or directory: ${target}`);
309
+ }
310
+
193
311
  function sourceSetDigest(sources) {
194
312
  const hash = crypto.createHash("sha256");
195
313
  for (const item of [...sources].sort((left, right) => left.relative.localeCompare(right.relative))) {
@@ -324,6 +442,14 @@ function stateRuntimeSources(packageRoot, sourceTemplate) {
324
442
  source: path.join(packageRoot, "lib", "delivery-state.mjs"),
325
443
  relative: "delivery-state.mjs",
326
444
  },
445
+ {
446
+ source: path.join(packageRoot, "lib", "history-anchor.mjs"),
447
+ relative: "history-anchor.mjs",
448
+ },
449
+ {
450
+ source: path.join(packageRoot, "lib", "fs-utils.mjs"),
451
+ relative: "fs-utils.mjs",
452
+ },
327
453
  ...filesUnder(yamlRoot).map((source) => ({
328
454
  source,
329
455
  relative: path.join("vendor", "yaml", path.relative(yamlRoot, source)),
@@ -344,19 +470,341 @@ function evolutionRuntimeSources(packageRoot, sourceTemplate) {
344
470
  ];
345
471
  }
346
472
 
473
+ function mockRuntimeSources(packageRoot, sourceTemplate) {
474
+ return [
475
+ {
476
+ source: path.join(sourceTemplate, ".workflow", "tools", "mock", "mock.mjs"),
477
+ relative: "mock.mjs",
478
+ },
479
+ ...[
480
+ "mock-system.mjs",
481
+ "mock-scenario-pack.mjs",
482
+ "prototype-revision.mjs",
483
+ "artifact-files.mjs",
484
+ "workspace.mjs",
485
+ "yaml-runtime.mjs",
486
+ "fs-utils.mjs",
487
+ "stable-value.mjs",
488
+ ].map((name) => ({
489
+ source: path.join(packageRoot, "lib", name),
490
+ relative: name,
491
+ })),
492
+ ];
493
+ }
494
+
495
+ function packagedRuntimeModules(packageRoot, modules) {
496
+ return modules.map((name) => ({
497
+ source: path.join(packageRoot, "lib", name),
498
+ relative: name,
499
+ }));
500
+ }
501
+
502
+ function frontendGuardRuntimeSources(packageRoot, sourceTemplate) {
503
+ return [
504
+ {
505
+ source: path.join(sourceTemplate, ".workflow", "tools", "frontend-guard", "frontend-guard.mjs"),
506
+ relative: "frontend-guard.mjs",
507
+ },
508
+ {
509
+ source: path.join(packageRoot, "lib", "frontend-guard.mjs"),
510
+ relative: "frontend-guard-runtime.mjs",
511
+ },
512
+ ...packagedRuntimeModules(packageRoot, PROTOTYPE_EVIDENCE_RUNTIME_MODULES),
513
+ ];
514
+ }
515
+
516
+ function uiAcceptanceRuntimeSources(packageRoot, sourceTemplate) {
517
+ return [
518
+ {
519
+ source: path.join(sourceTemplate, ".workflow", "tools", "ui-acceptance", "ui-acceptance.mjs"),
520
+ relative: "ui-acceptance.mjs",
521
+ },
522
+ {
523
+ source: path.join(packageRoot, "lib", "ui-acceptance.mjs"),
524
+ relative: "ui-acceptance-runtime.mjs",
525
+ },
526
+ ...packagedRuntimeModules(packageRoot, [
527
+ ...PROTOTYPE_EVIDENCE_RUNTIME_MODULES,
528
+ "mock-scenario-pack.mjs",
529
+ "mock-system.mjs",
530
+ "png-evidence.mjs",
531
+ "fs-utils.mjs",
532
+ "workspace.mjs",
533
+ "stable-value.mjs",
534
+ ]),
535
+ ];
536
+ }
537
+
347
538
  function bootstrapRuntimeSources(packageRoot, sourceTemplate) {
348
539
  return [
349
540
  {
350
541
  source: path.join(sourceTemplate, ".workflow", "tools", "bootstrap", "bootstrap.mjs"),
351
542
  relative: "bootstrap.mjs",
352
543
  },
353
- ...["project-bootstrap.mjs", "workspace.mjs", "yaml-runtime.mjs", "delivery-state.mjs"].map((name) => ({
544
+ ...[
545
+ "project-bootstrap.mjs", "workspace.mjs", "yaml-runtime.mjs", "delivery-state.mjs",
546
+ "history-anchor.mjs", "fs-utils.mjs",
547
+ ].map((name) => ({
548
+ source: path.join(packageRoot, "lib", name),
549
+ relative: name,
550
+ })),
551
+ ];
552
+ }
553
+
554
+ function managerRuntimeSources(packageRoot, sourceTemplate) {
555
+ const publicRoot = path.join(packageRoot, "lib", "workflow-manager-public");
556
+ return [
557
+ {
558
+ source: path.join(sourceTemplate, ".workflow", "tools", "manager", "manager.mjs"),
559
+ relative: "manager.mjs",
560
+ },
561
+ {
562
+ source: path.join(packageRoot, "docs", "agents", "workflow-manager-agent-interface.md"),
563
+ relative: "AGENT-INTERFACE.md",
564
+ },
565
+ ...WORKFLOW_MANAGER_LAUNCHER_FILES.filter((name) => name !== "manager.mjs").map((name) => ({
566
+ source: path.join(sourceTemplate, ".workflow", "tools", "manager", name),
567
+ relative: name,
568
+ })),
569
+ {
570
+ source: path.join(packageRoot, "lib", "workflow-manager.mjs"),
571
+ relative: "workflow-manager-runtime.mjs",
572
+ },
573
+ ...WORKFLOW_MANAGER_RUNTIME_MODULES.map((name) => ({
354
574
  source: path.join(packageRoot, "lib", name),
355
575
  relative: name,
356
576
  })),
577
+ ...packagedRuntimeModules(packageRoot, PROTOTYPE_EVIDENCE_RUNTIME_MODULES),
578
+ ...filesUnder(publicRoot).map((source) => ({
579
+ source,
580
+ relative: path.join("public", path.relative(publicRoot, source)),
581
+ })),
357
582
  ];
358
583
  }
359
584
 
585
+ function installationSources(packageRoot) {
586
+ const sourceSkills = path.join(packageRoot, "skills");
587
+ const sourceTemplate = path.join(sourceSkills, "ai-delivery-orchestrate", "assets", "project-template");
588
+ const sourceWorkflow = path.join(sourceTemplate, ".workflow");
589
+ const sourceDesignReferenceLibrary = path.join(sourceTemplate, "ref", "ui");
590
+ const gitignoreTemplate = path.join(sourceTemplate, "gitignore.template");
591
+ if (!fs.existsSync(gitignoreTemplate) || !fs.statSync(gitignoreTemplate).isFile()) {
592
+ throw new Error("Project template is missing gitignore.template.");
593
+ }
594
+ const expectedGitignoreTemplate = `${IGNORE_ENTRIES.join("\n")}\n`;
595
+ if (fs.readFileSync(gitignoreTemplate, "utf8").replaceAll("\r\n", "\n")
596
+ !== expectedGitignoreTemplate) {
597
+ throw new Error("Project gitignore.template does not match the managed runtime ignore contract.");
598
+ }
599
+ validateDesignReferenceLibrary(sourceDesignReferenceLibrary);
600
+ const skillNames = packagedSkillNames(packageRoot);
601
+ return {
602
+ sourceSkills,
603
+ sourceTemplate,
604
+ sourceWorkflow,
605
+ skillNames,
606
+ sourceHook: path.join(sourceSkills, "ai-delivery-checkpoint-task", "scripts", "hook-event.mjs"),
607
+ sourceHookConfig: path.join(sourceSkills, "ai-delivery-checkpoint-task", "assets", "codex-hook-config.toml"),
608
+ sourceDesignReferenceLibrary,
609
+ bootstrapSources: bootstrapRuntimeSources(packageRoot, sourceTemplate),
610
+ stateSources: stateRuntimeSources(packageRoot, sourceTemplate),
611
+ evolutionSources: evolutionRuntimeSources(packageRoot, sourceTemplate),
612
+ mockSources: mockRuntimeSources(packageRoot, sourceTemplate),
613
+ frontendGuardSources: frontendGuardRuntimeSources(packageRoot, sourceTemplate),
614
+ uiAcceptanceSources: uiAcceptanceRuntimeSources(packageRoot, sourceTemplate),
615
+ managerSources: managerRuntimeSources(packageRoot, sourceTemplate),
616
+ };
617
+ }
618
+
619
+ function validateDesignReferenceLibrary(source) {
620
+ for (const relative of ["README.md", "LICENSE", "UPSTREAM.yaml"]) {
621
+ if (!fs.existsSync(path.join(source, relative))) {
622
+ throw new Error(`Design Reference Library is missing required ${relative}.`);
623
+ }
624
+ }
625
+ const upstream = parseYaml(fs.readFileSync(path.join(source, "UPSTREAM.yaml"), "utf8"));
626
+ if (!/^[a-f0-9]{40}$/.test(String(upstream?.source_commit || ""))) {
627
+ throw new Error("Design Reference Library UPSTREAM.yaml must pin a full source commit.");
628
+ }
629
+ if (upstream?.license !== "MIT" || upstream?.license_ref !== "LICENSE") {
630
+ throw new Error("Design Reference Library must declare its MIT license and LICENSE path.");
631
+ }
632
+ if (!/^MIT License\b/.test(fs.readFileSync(path.join(source, "LICENSE"), "utf8"))) {
633
+ throw new Error("Design Reference Library LICENSE content is missing or invalid.");
634
+ }
635
+ for (const [relative, checksumField] of [
636
+ ["README.md", "source_readme_sha256"],
637
+ ["LICENSE", "license_sha256"],
638
+ ]) {
639
+ const expected = String(upstream?.[checksumField] || "");
640
+ const actual = crypto.createHash("sha256").update(fs.readFileSync(path.join(source, relative))).digest("hex");
641
+ if (!/^[a-f0-9]{64}$/.test(expected) || actual !== expected) {
642
+ throw new Error(`Design Reference Library ${relative} checksum does not match UPSTREAM.yaml.`);
643
+ }
644
+ }
645
+ const references = filesUnder(path.join(source, "design-md"))
646
+ .filter((file) => path.basename(file) === "DESIGN.md");
647
+ if (references.length < 2) {
648
+ throw new Error("Design Reference Library must contain selectable DESIGN.md references.");
649
+ }
650
+ }
651
+
652
+ function designReferenceDoctorCheck(target, packageSource, manifest) {
653
+ const installed = path.join(target, "ref", "ui");
654
+ try {
655
+ validateDesignReferenceLibrary(packageSource);
656
+ validateDesignReferenceLibrary(installed);
657
+ const expectedDigest = directoryDigest(packageSource);
658
+ const installedDigest = directoryDigest(installed);
659
+ const manifestDigest = manifest?.managed_assets?.design_reference_library?.sha256;
660
+ const ok = installedDigest === expectedDigest && manifestDigest === expectedDigest;
661
+ return {
662
+ ok,
663
+ scope: "design-reference-library",
664
+ message: ok
665
+ ? "Design Reference Library integrity and raw attribution checksums match the package baseline"
666
+ : "Design Reference Library integrity does not match the package baseline",
667
+ };
668
+ } catch (error) {
669
+ return {
670
+ ok: false,
671
+ scope: "design-reference-library",
672
+ message: `Design Reference Library integrity check failed: ${error.message}`,
673
+ };
674
+ }
675
+ }
676
+
677
+ function packageSurfaceDoctorCheck(packageRoot) {
678
+ const missing = [];
679
+ const empty = [];
680
+ for (const relative of REQUIRED_PACKAGE_SURFACE_FILES) {
681
+ const file = path.join(packageRoot, ...relative.split("/"));
682
+ if (!fs.existsSync(file) || !fs.statSync(file).isFile()) missing.push(relative);
683
+ else if (fs.statSync(file).size === 0) empty.push(relative);
684
+ }
685
+ const ok = missing.length === 0 && empty.length === 0;
686
+ return {
687
+ ok,
688
+ scope: "package-surface",
689
+ message: ok
690
+ ? `release package surface contains all ${REQUIRED_PACKAGE_SURFACE_FILES.length} required files`
691
+ : `release package surface is incomplete: missing=${missing.join(", ") || "none"}; empty=${empty.join(", ") || "none"}`,
692
+ };
693
+ }
694
+
695
+ function managedMarkdownContribution(text) {
696
+ const start = text.indexOf(MANAGED_START);
697
+ const end = text.indexOf(MANAGED_END);
698
+ if (start === -1 || end === -1 || end < start) {
699
+ throw new Error("AGENTS.md does not contain a complete ai-delivery managed block.");
700
+ }
701
+ return text.slice(start, end + MANAGED_END.length);
702
+ }
703
+
704
+ function mergedSurfaceManagedDigest(key, text, codePath) {
705
+ if (key === "agents") return contentDigest(managedMarkdownContribution(text));
706
+ if (key === "hook_config") return contentDigest(JSON.stringify(managedHookConfigProjection(text)));
707
+ const lines = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
708
+ if (key === "gitignore") {
709
+ const managedEntries = new Set([...IGNORE_ENTRIES, `/${codePath}/`, ...OBSOLETE_IGNORE_ENTRIES]);
710
+ return contentDigest(JSON.stringify(lines.filter((line) => managedEntries.has(line)).sort()));
711
+ }
712
+ return contentDigest(JSON.stringify(lines.filter((line) => GIT_ATTRIBUTE_ENTRIES.includes(line)).sort()));
713
+ }
714
+
715
+ function expectedMergedSurfaceText(surface, sources, codePath) {
716
+ if (surface.key === "agents") {
717
+ const template = fs.readFileSync(path.join(sources.sourceTemplate, "AGENTS.md"), "utf8");
718
+ return mergeManagedMarkdown("", template);
719
+ }
720
+ if (surface.key === "hook_config") return fs.readFileSync(sources.sourceHookConfig, "utf8");
721
+ if (surface.key === "gitignore") return mergeGitignore("", [`/${codePath}/`]);
722
+ return mergeGitattributes("");
723
+ }
724
+
725
+ function buildInstallManifest(sources, packageVersion, codeRepository, previousManifest = null) {
726
+ const inventory = managedAssetInventory(sources, previousManifest);
727
+ const named = Object.fromEntries(
728
+ inventory.filter((asset) => asset.manifest_key).map((asset) => [asset.manifest_key, asset]),
729
+ );
730
+ return {
731
+ schema_version: INSTALL_MANIFEST_SCHEMA,
732
+ package: "ai-delivery-workflow",
733
+ version: packageVersion,
734
+ scope: "workspace",
735
+ skill_root: ".agents/skills",
736
+ workflow_root: ".workflow",
737
+ bootstrap_cli: ".workflow/tools/bootstrap/bootstrap.mjs",
738
+ state_cli: ".workflow/tools/state/state.mjs",
739
+ evolution_cli: ".workflow/tools/evolution/evolve.mjs",
740
+ mock_cli: ".workflow/tools/mock/mock.mjs",
741
+ frontend_guard_cli: ".workflow/tools/frontend-guard/frontend-guard.mjs",
742
+ ui_acceptance_cli: ".workflow/tools/ui-acceptance/ui-acceptance.mjs",
743
+ manager_root: ".workflow/tools/manager",
744
+ manager_cli: ".workflow/tools/manager/manager.mjs",
745
+ hook_protocol_version: 1,
746
+ hook_cli: ".workflow/tools/hooks/hook-event.mjs",
747
+ code_repository: codeRepository,
748
+ manager_start: {
749
+ windows_cmd: ".workflow/tools/manager/start.cmd",
750
+ powershell: ".workflow/tools/manager/start.ps1",
751
+ unix: ".workflow/tools/manager/start.sh",
752
+ },
753
+ installed_skills: sources.skillNames,
754
+ managed_assets: {
755
+ skills: Object.fromEntries(inventory
756
+ .filter((asset) => asset.skill_name)
757
+ .map((asset) => [asset.skill_name, asset.expected_digest])),
758
+ hook: {
759
+ path: named.hook.path,
760
+ sha256: named.hook.expected_digest,
761
+ },
762
+ state_runtime: {
763
+ path: named.state_runtime.path,
764
+ sha256: named.state_runtime.expected_digest,
765
+ },
766
+ evolution_runtime: {
767
+ path: named.evolution_runtime.path,
768
+ sha256: named.evolution_runtime.expected_digest,
769
+ },
770
+ mock_runtime: {
771
+ path: named.mock_runtime.path,
772
+ sha256: named.mock_runtime.expected_digest,
773
+ },
774
+ frontend_guard_runtime: {
775
+ path: named.frontend_guard_runtime.path,
776
+ sha256: named.frontend_guard_runtime.expected_digest,
777
+ },
778
+ ui_acceptance_runtime: {
779
+ path: named.ui_acceptance_runtime.path,
780
+ sha256: named.ui_acceptance_runtime.expected_digest,
781
+ },
782
+ manager_runtime: {
783
+ path: named.manager_runtime.path,
784
+ sha256: named.manager_runtime.expected_digest,
785
+ },
786
+ bootstrap_runtime: {
787
+ path: named.bootstrap_runtime.path,
788
+ sha256: named.bootstrap_runtime.expected_digest,
789
+ },
790
+ design_reference_library: {
791
+ path: named.design_reference_library.path,
792
+ sha256: named.design_reference_library.expected_digest,
793
+ },
794
+ merged_surfaces: Object.fromEntries(
795
+ MERGED_MANAGED_SURFACES.map((surface) => {
796
+ const expected = expectedMergedSurfaceText(surface, sources, codeRepository.path);
797
+ return [surface.key, {
798
+ path: surface.path,
799
+ sha256: mergedSurfaceManagedDigest(surface.key, expected, codeRepository.path),
800
+ }];
801
+ }),
802
+ ),
803
+ },
804
+ installed_at: previousManifest?.installed_at || new Date().toISOString(),
805
+ };
806
+ }
807
+
360
808
  function managedRuntimeMatches(destination, sources) {
361
809
  if (!fs.existsSync(destination)) return false;
362
810
  const expected = new Map(sources.map((item) => [
@@ -410,6 +858,7 @@ function applyManagedRuntimePlan(target, plan, backupRoot, actions, dryRun) {
410
858
 
411
859
  export function initProject(packageRoot, target, options = {}) {
412
860
  const { dryRun = false, force = false, packageVersion = "0.0.0" } = options;
861
+ const sources = installationSources(packageRoot);
413
862
  validateTarget(target, { create: !dryRun });
414
863
  const backupRoot = path.join(
415
864
  target,
@@ -474,15 +923,22 @@ export function initProject(packageRoot, target, options = {}) {
474
923
  dryRun,
475
924
  });
476
925
 
477
- const sourceSkills = path.join(packageRoot, "skills");
478
- const sourceTemplate = path.join(sourceSkills, "ai-delivery-orchestrate", "assets", "project-template");
479
- const sourceWorkflow = path.join(sourceTemplate, ".workflow");
480
- const sourceHook = path.join(sourceSkills, "ai-delivery-checkpoint-task", "scripts", "hook-event.mjs");
481
- const sourceConfig = path.join(sourceSkills, "ai-delivery-checkpoint-task", "assets", "codex-hook-config.toml");
482
- const sourceViewer = path.join(sourceTemplate, ".workflow", "tools", "viewer");
483
- const bootstrapSources = bootstrapRuntimeSources(packageRoot, sourceTemplate);
484
- const stateSources = stateRuntimeSources(packageRoot, sourceTemplate);
485
- const evolutionSources = evolutionRuntimeSources(packageRoot, sourceTemplate);
926
+ const {
927
+ sourceSkills,
928
+ sourceTemplate,
929
+ sourceWorkflow,
930
+ sourceHook,
931
+ sourceHookConfig: sourceConfig,
932
+ sourceDesignReferenceLibrary,
933
+ bootstrapSources,
934
+ stateSources,
935
+ evolutionSources,
936
+ mockSources,
937
+ frontendGuardSources,
938
+ uiAcceptanceSources,
939
+ managerSources,
940
+ skillNames,
941
+ } = sources;
486
942
  const sourceStandardsIndex = path.join(sourceWorkflow, "standards", "index.yaml");
487
943
  const workflowRoot = path.join(target, ".workflow");
488
944
  const standardsIndexFile = path.join(workflowRoot, "standards", "index.yaml");
@@ -510,7 +966,7 @@ export function initProject(packageRoot, target, options = {}) {
510
966
  ]) {
511
967
  validateManagedPath(target, path.join(target, managedPath));
512
968
  }
513
- const skillNames = packagedSkillNames(packageRoot);
969
+ validateManagedPath(target, path.join(target, "ref", "ui"));
514
970
  if (!skillNames.length) throw new Error(`No packaged skills found under ${sourceSkills}`);
515
971
 
516
972
  const actions = [];
@@ -530,18 +986,29 @@ export function initProject(packageRoot, target, options = {}) {
530
986
  const hookConflict = fs.existsSync(hookDestination)
531
987
  && !managedFilesEqual(hookDestination, sourceHook);
532
988
  if (hookConflict && !reinstallManagedRuntime) conflicts.push(path.relative(target, hookDestination));
533
- const viewerDestination = path.join(target, ".workflow", "tools", "viewer");
534
- let viewerPlan = null;
535
- if (!fs.existsSync(viewerDestination)) {
536
- viewerPlan = { type: "copy", source: sourceViewer, destination: viewerDestination };
537
- } else if (directoryDigest(sourceViewer) !== directoryDigest(viewerDestination)) {
538
- if (reinstallManagedRuntime) viewerPlan = { type: "replace", source: sourceViewer, destination: viewerDestination };
539
- else conflicts.push(path.relative(target, viewerDestination));
989
+ const retiredViewerDestination = path.join(target, ".workflow", "tools", "viewer");
990
+ const retireLegacyViewer = hasLegacyRoot && fs.existsSync(retiredViewerDestination);
991
+ const designReferenceDestination = path.join(target, "ref", "ui");
992
+ let designReferencePlan = null;
993
+ if (!fs.existsSync(designReferenceDestination)) {
994
+ designReferencePlan = {
995
+ type: "copy", source: sourceDesignReferenceLibrary, destination: designReferenceDestination,
996
+ };
997
+ } else if (directoryDigest(sourceDesignReferenceLibrary) !== directoryDigest(designReferenceDestination)) {
998
+ if (reinstallManagedRuntime) {
999
+ designReferencePlan = {
1000
+ type: "replace", source: sourceDesignReferenceLibrary, destination: designReferenceDestination,
1001
+ };
1002
+ } else conflicts.push(path.relative(target, designReferenceDestination));
540
1003
  }
541
1004
  const runtimePlans = [
542
1005
  [".workflow/tools/state", stateSources],
543
1006
  [".workflow/tools/evolution", evolutionSources],
544
1007
  [".workflow/tools/bootstrap", bootstrapSources],
1008
+ [".workflow/tools/mock", mockSources],
1009
+ [".workflow/tools/frontend-guard", frontendGuardSources],
1010
+ [".workflow/tools/ui-acceptance", uiAcceptanceSources],
1011
+ [".workflow/tools/manager", managerSources],
545
1012
  ].map(([installPath, sources]) => planManagedRuntime({
546
1013
  target,
547
1014
  installPath,
@@ -631,14 +1098,19 @@ export function initProject(packageRoot, target, options = {}) {
631
1098
  } else if (hookConflict) {
632
1099
  backupAndReplace(target, hookDestination, sourceHook, backupRoot, actions, dryRun);
633
1100
  }
634
- if (viewerPlan?.type === "copy") {
635
- actions.push("install .workflow/tools/viewer");
1101
+ if (retireLegacyViewer) {
1102
+ backupAndRemove(target, retiredViewerDestination, backupRoot, actions, dryRun);
1103
+ }
1104
+ if (designReferencePlan?.type === "copy") {
1105
+ actions.push("install ref/ui Design Reference Library");
636
1106
  if (!dryRun) {
637
- fs.mkdirSync(path.dirname(viewerDestination), { recursive: true });
638
- fs.cpSync(sourceViewer, viewerDestination, { recursive: true });
1107
+ fs.mkdirSync(path.dirname(designReferenceDestination), { recursive: true });
1108
+ fs.cpSync(sourceDesignReferenceLibrary, designReferenceDestination, { recursive: true });
639
1109
  }
640
- } else if (viewerPlan?.type === "replace") {
641
- backupAndReplace(target, viewerDestination, sourceViewer, backupRoot, actions, dryRun);
1110
+ } else if (designReferencePlan?.type === "replace") {
1111
+ backupAndReplace(
1112
+ target, designReferenceDestination, sourceDesignReferenceLibrary, backupRoot, actions, dryRun,
1113
+ );
642
1114
  }
643
1115
  for (const runtimePlan of runtimePlans) {
644
1116
  applyManagedRuntimePlan(target, runtimePlan, backupRoot, actions, dryRun);
@@ -716,50 +1188,12 @@ export function initProject(packageRoot, target, options = {}) {
716
1188
  } catch {
717
1189
  existingManifest = null;
718
1190
  }
719
- const manifest = {
720
- schema_version: 5,
721
- package: "ai-delivery-workflow",
722
- version: packageVersion,
723
- scope: "workspace",
724
- skill_root: ".agents/skills",
725
- workflow_root: ".workflow",
726
- viewer_root: ".workflow/tools/viewer",
727
- bootstrap_cli: ".workflow/tools/bootstrap/bootstrap.mjs",
728
- state_cli: ".workflow/tools/state/state.mjs",
729
- evolution_cli: ".workflow/tools/evolution/evolve.mjs",
730
- hook_protocol_version: 1,
731
- hook_cli: ".workflow/tools/hooks/hook-event.mjs",
732
- code_repository: workspace.configuration.code_repository,
733
- viewer_start: {
734
- windows_cmd: ".workflow/tools/viewer/start.cmd",
735
- powershell: ".workflow/tools/viewer/start.ps1",
736
- unix: ".workflow/tools/viewer/start.sh",
737
- },
738
- installed_skills: skillNames,
739
- managed_assets: {
740
- skills: Object.fromEntries(skillNames.map((name) => [
741
- name,
742
- directoryDigest(path.join(sourceSkills, name)),
743
- ])),
744
- hook: {
745
- path: ".workflow/tools/hooks/hook-event.mjs",
746
- sha256: managedFileDigest(sourceHook),
747
- },
748
- state_runtime: {
749
- path: ".workflow/tools/state",
750
- sha256: sourceSetDigest(stateSources),
751
- },
752
- evolution_runtime: {
753
- path: ".workflow/tools/evolution",
754
- sha256: sourceSetDigest(evolutionSources),
755
- },
756
- bootstrap_runtime: {
757
- path: ".workflow/tools/bootstrap",
758
- sha256: sourceSetDigest(bootstrapSources),
759
- },
760
- },
761
- installed_at: existingManifest?.installed_at || new Date().toISOString(),
762
- };
1191
+ const manifest = buildInstallManifest(
1192
+ sources,
1193
+ packageVersion,
1194
+ workspace.configuration.code_repository,
1195
+ existingManifest,
1196
+ );
763
1197
  const manifestText = `${JSON.stringify(manifest, null, 2)}\n`;
764
1198
  if (!fs.existsSync(manifestFile) || fs.readFileSync(manifestFile, "utf8") !== manifestText) {
765
1199
  actions.push("write .workflow/config/install-manifest.json");
@@ -783,11 +1217,827 @@ export function initProject(packageRoot, target, options = {}) {
783
1217
 
784
1218
  return {
785
1219
  actions: [...workspace.actions, ...actions],
786
- summary: `${dryRun ? "Dry run" : "Initialized"} ${skillNames.length} workspace-scoped AI delivery skills in ${target}`
787
- + (dryRun
788
- ? ""
789
- : "\nWorkflow viewer: .workflow\\tools\\viewer\\start.cmd (Windows) or sh ./.workflow/tools/viewer/start.sh"),
1220
+ summary: `${dryRun ? "Dry run" : "Initialized"} ${skillNames.length} workspace-scoped AI delivery skills in ${target}`,
1221
+ };
1222
+ }
1223
+
1224
+ function readInstallManifest(target) {
1225
+ const manifestFile = path.join(target, INSTALL_MANIFEST);
1226
+ let manifest;
1227
+ try {
1228
+ manifest = JSON.parse(fs.readFileSync(manifestFile, "utf8"));
1229
+ } catch (error) {
1230
+ throw new Error(`Cannot read installed workflow manifest: ${error.message}`);
1231
+ }
1232
+ if (manifest.package !== "ai-delivery-workflow" || manifest.scope !== "workspace") {
1233
+ throw new Error("The installation manifest is not a compatible project-scoped ai-delivery-workflow installation.");
1234
+ }
1235
+ return { manifest, manifestFile };
1236
+ }
1237
+
1238
+ function installPath(target, relative) {
1239
+ return path.join(target, ...relative.split("/"));
1240
+ }
1241
+
1242
+ function managedAssetInventory(sources, previousManifest = {}) {
1243
+ const previous = previousManifest || {};
1244
+ const assets = sources.skillNames.map((name) => ({
1245
+ path: `.agents/skills/${name}`,
1246
+ kind: "directory",
1247
+ skill_name: name,
1248
+ source: path.join(sources.sourceSkills, name),
1249
+ expected_digest: directoryDigest(path.join(sources.sourceSkills, name)),
1250
+ previous_digest: previous.managed_assets?.skills?.[name] || null,
1251
+ }));
1252
+ assets.push(
1253
+ ...ENSURED_CONTROL_DIRECTORIES.map((directory) => ({
1254
+ path: `.workflow/control/${directory}`,
1255
+ kind: "ensure-directory",
1256
+ source: path.join(sources.sourceTemplate, ".workflow", "control", directory),
1257
+ expected_digest: directoryDigest(
1258
+ path.join(sources.sourceTemplate, ".workflow", "control", directory),
1259
+ ),
1260
+ previous_digest: null,
1261
+ })),
1262
+ ...ENSURED_CONTROL_DIRECTORIES.map((directory) => ({
1263
+ path: `.workflow/control/${directory}/.gitkeep`,
1264
+ kind: "ensure-directory-marker",
1265
+ source: path.join(sources.sourceTemplate, ".workflow", "control", directory, ".gitkeep"),
1266
+ expected_digest: managedFileDigest(
1267
+ path.join(sources.sourceTemplate, ".workflow", "control", directory, ".gitkeep"),
1268
+ ),
1269
+ previous_digest: null,
1270
+ })),
1271
+ {
1272
+ path: ".workflow/tools/hooks/hook-event.mjs",
1273
+ kind: "file",
1274
+ manifest_key: "hook",
1275
+ source: sources.sourceHook,
1276
+ expected_digest: managedFileDigest(sources.sourceHook),
1277
+ previous_digest: previous.managed_assets?.hook?.sha256 || null,
1278
+ },
1279
+ {
1280
+ path: ".workflow/tools/state",
1281
+ kind: "runtime",
1282
+ manifest_key: "state_runtime",
1283
+ sources: sources.stateSources,
1284
+ expected_digest: sourceSetDigest(sources.stateSources),
1285
+ previous_digest: previous.managed_assets?.state_runtime?.sha256 || null,
1286
+ },
1287
+ {
1288
+ path: ".workflow/tools/evolution",
1289
+ kind: "runtime",
1290
+ manifest_key: "evolution_runtime",
1291
+ sources: sources.evolutionSources,
1292
+ expected_digest: sourceSetDigest(sources.evolutionSources),
1293
+ previous_digest: previous.managed_assets?.evolution_runtime?.sha256 || null,
1294
+ },
1295
+ {
1296
+ path: ".workflow/tools/bootstrap",
1297
+ kind: "runtime",
1298
+ manifest_key: "bootstrap_runtime",
1299
+ sources: sources.bootstrapSources,
1300
+ expected_digest: sourceSetDigest(sources.bootstrapSources),
1301
+ previous_digest: previous.managed_assets?.bootstrap_runtime?.sha256 || null,
1302
+ },
1303
+ {
1304
+ path: ".workflow/tools/mock",
1305
+ kind: "runtime",
1306
+ manifest_key: "mock_runtime",
1307
+ sources: sources.mockSources,
1308
+ expected_digest: sourceSetDigest(sources.mockSources),
1309
+ previous_digest: previous.managed_assets?.mock_runtime?.sha256 || null,
1310
+ },
1311
+ {
1312
+ path: ".workflow/tools/frontend-guard",
1313
+ kind: "runtime",
1314
+ manifest_key: "frontend_guard_runtime",
1315
+ sources: sources.frontendGuardSources,
1316
+ expected_digest: sourceSetDigest(sources.frontendGuardSources),
1317
+ previous_digest: previous.managed_assets?.frontend_guard_runtime?.sha256 || null,
1318
+ },
1319
+ {
1320
+ path: ".workflow/tools/ui-acceptance",
1321
+ kind: "runtime",
1322
+ manifest_key: "ui_acceptance_runtime",
1323
+ sources: sources.uiAcceptanceSources,
1324
+ expected_digest: sourceSetDigest(sources.uiAcceptanceSources),
1325
+ previous_digest: previous.managed_assets?.ui_acceptance_runtime?.sha256 || null,
1326
+ },
1327
+ {
1328
+ path: ".workflow/tools/manager",
1329
+ kind: "runtime",
1330
+ manifest_key: "manager_runtime",
1331
+ sources: sources.managerSources,
1332
+ expected_digest: sourceSetDigest(sources.managerSources),
1333
+ previous_digest: previous.managed_assets?.manager_runtime?.sha256 || null,
1334
+ },
1335
+ {
1336
+ path: "ref/ui",
1337
+ kind: "directory",
1338
+ manifest_key: "design_reference_library",
1339
+ source: sources.sourceDesignReferenceLibrary,
1340
+ expected_digest: directoryDigest(sources.sourceDesignReferenceLibrary),
1341
+ previous_digest: previous.managed_assets?.design_reference_library?.sha256 || null,
1342
+ },
1343
+ );
1344
+ return assets;
1345
+ }
1346
+
1347
+ function upgradeMergedAssets(target, sources, manifest) {
1348
+ const codePath = manifest.code_repository?.path;
1349
+ if (!codePath || path.isAbsolute(codePath) || codePath.split(/[\\/]/).includes("..")) {
1350
+ throw new Error("The installation manifest contains an unsafe code repository path.");
1351
+ }
1352
+ const definitions = MERGED_MANAGED_SURFACES.map((surface) => {
1353
+ if (surface.key === "agents") {
1354
+ return { ...surface, desired(existing) {
1355
+ const template = fs.readFileSync(path.join(sources.sourceTemplate, "AGENTS.md"), "utf8");
1356
+ return mergeManagedMarkdown(existing, template);
1357
+ } };
1358
+ }
1359
+ if (surface.key === "hook_config") {
1360
+ return { ...surface, desired(existing, refreshManaged) {
1361
+ return existing
1362
+ ? mergeHookConfig(existing, { force: refreshManaged }).text
1363
+ : fs.readFileSync(sources.sourceHookConfig, "utf8");
1364
+ } };
1365
+ }
1366
+ if (surface.key === "gitignore") {
1367
+ return { ...surface, desired: (existing) => mergeGitignore(existing, [`/${codePath}/`]) };
1368
+ }
1369
+ return { ...surface, desired: (existing) => mergeGitattributes(existing) };
1370
+ });
1371
+ return definitions.map((definition) => {
1372
+ const destination = installPath(target, definition.path);
1373
+ const existing = fs.existsSync(destination) ? fs.readFileSync(destination, "utf8") : "";
1374
+ const previousDigest = manifest.managed_assets?.merged_surfaces?.[definition.key]?.sha256 || null;
1375
+ const currentManagedDigest = fs.existsSync(destination)
1376
+ ? mergedSurfaceManagedDigest(definition.key, existing, codePath)
1377
+ : null;
1378
+ const packagedManagedDigest = mergedSurfaceManagedDigest(
1379
+ definition.key,
1380
+ expectedMergedSurfaceText(definition, sources, codePath),
1381
+ codePath,
1382
+ );
1383
+ const desired = definition.desired(
1384
+ existing,
1385
+ !previousDigest
1386
+ || currentManagedDigest !== previousDigest
1387
+ || packagedManagedDigest !== previousDigest,
1388
+ );
1389
+ return {
1390
+ ...definition,
1391
+ destination,
1392
+ exists: fs.existsSync(destination),
1393
+ current_digest: fs.existsSync(destination) ? managedFileDigest(destination) : null,
1394
+ current_managed_digest: currentManagedDigest,
1395
+ previous_digest: previousDigest,
1396
+ desired,
1397
+ expected_digest: crypto.createHash("sha256")
1398
+ .update(Buffer.from(desired.replaceAll("\r\n", "\n")))
1399
+ .digest("hex"),
1400
+ };
1401
+ });
1402
+ }
1403
+
1404
+ function preservedWorkflowFiles(target, managedPaths) {
1405
+ const entries = [];
1406
+ let totalFiles = 0;
1407
+ const roots = [path.join(target, ".workflow"), path.join(target, ".agents")];
1408
+ const normalizedManaged = managedPaths.map((item) => item.replaceAll("\\", "/").replace(/\/$/, ""));
1409
+ const isWithin = (relative, prefixes) => prefixes.some(
1410
+ (prefix) => relative === prefix || relative.startsWith(`${prefix}/`),
1411
+ );
1412
+ for (const root of roots) {
1413
+ if (!fs.existsSync(root)) continue;
1414
+ const pending = [{ absolute: root, directory: true }];
1415
+ while (pending.length) {
1416
+ const current = pending.pop();
1417
+ const relative = path.relative(target, current.absolute).replaceAll("\\", "/");
1418
+ if (isWithin(relative, PRESERVED_TRANSIENT_ROOTS) || isWithin(relative, normalizedManaged)) {
1419
+ continue;
1420
+ }
1421
+ if (current.directory) {
1422
+ const children = fs.readdirSync(current.absolute, { withFileTypes: true })
1423
+ .filter((entry) => entry.isDirectory() || entry.isFile())
1424
+ .sort((left, right) => left.name.localeCompare(right.name));
1425
+ for (let index = children.length - 1; index >= 0; index -= 1) {
1426
+ const child = children[index];
1427
+ pending.push({
1428
+ absolute: path.join(current.absolute, child.name),
1429
+ directory: child.isDirectory(),
1430
+ });
1431
+ }
1432
+ continue;
1433
+ }
1434
+ if (relative === INSTALL_MANIFEST.replaceAll("\\", "/")) continue;
1435
+ totalFiles += 1;
1436
+ if (entries.length < PRESERVED_DETAIL_LIMIT) {
1437
+ entries.push({ path: relative, reason: "outside managed installation assets" });
1438
+ }
1439
+ }
1440
+ }
1441
+ return {
1442
+ entries,
1443
+ summary: {
1444
+ total_files: totalFiles,
1445
+ reported_files: entries.length,
1446
+ omitted_files: totalFiles - entries.length,
1447
+ excluded_transient_roots: [...PRESERVED_TRANSIENT_ROOTS],
1448
+ },
1449
+ };
1450
+ }
1451
+
1452
+ function planUpgrade(packageRoot, target, options) {
1453
+ const { force = false, packageVersion = "0.0.0" } = options;
1454
+ validateTarget(target);
1455
+ for (const managedPath of [".agents", ".codex", ".workflow", "AGENTS.md", ".gitignore", ".gitattributes"]) {
1456
+ validateManagedPath(target, path.join(target, managedPath));
1457
+ }
1458
+ const { manifest, manifestFile } = readInstallManifest(target);
1459
+ if (!Number.isInteger(manifest.schema_version)
1460
+ || manifest.schema_version < 5
1461
+ || manifest.schema_version > INSTALL_MANIFEST_SCHEMA) {
1462
+ throw new Error(
1463
+ `Installation manifest schema ${manifest.schema_version} is incompatible with upgrade; run the compatible migration or repair command first.`,
1464
+ );
1465
+ }
1466
+ const comparison = comparePackageVersions(packageVersion, manifest.version);
1467
+ if (comparison === null) {
1468
+ throw new Error(`Cannot compare installed package version ${manifest.version} with ${packageVersion}.`);
1469
+ }
1470
+ if (comparison < 0 && !force) {
1471
+ throw new Error(`Refusing package downgrade from ${manifest.version} to ${packageVersion}; use --force only after explicit approval.`);
1472
+ }
1473
+ const workspace = readWorkspaceConfiguration(target);
1474
+ const contractRetirement = executeDeliveryStateCommand([
1475
+ "contract", "retire", "--project", target, "--gate-id", "UX-LF", "--dry-run",
1476
+ ], { yaml: FORMAL_STATE_YAML });
1477
+ const prototypeMigration = planPrototypeMigration(target);
1478
+ const codeRepository = workspace.configuration.code_repository;
1479
+ if (["path", "repo_id", "remote", "default_branch"].some((key) =>
1480
+ (manifest.code_repository?.[key] ?? null) !== (codeRepository[key] ?? null))) {
1481
+ throw new Error(
1482
+ "The installation manifest code repository identity differs from authoritative .workflow/config/workspace.yaml; repair the installation state before upgrade.",
1483
+ );
1484
+ }
1485
+ const sources = installationSources(packageRoot);
1486
+ const exactAssets = managedAssetInventory(sources, manifest);
1487
+ const retiredAssets = RETIRED_MANAGED_ASSETS
1488
+ .filter((asset) => manifest.managed_assets?.[asset.manifest_key])
1489
+ .map((asset) => ({
1490
+ ...asset,
1491
+ previous_digest: manifest.managed_assets[asset.manifest_key].sha256 || null,
1492
+ }));
1493
+ const mergedAssets = upgradeMergedAssets(
1494
+ target,
1495
+ sources,
1496
+ { ...manifest, code_repository: codeRepository },
1497
+ );
1498
+ for (const asset of [...exactAssets, ...retiredAssets, ...mergedAssets]) {
1499
+ validateManagedPath(target, installPath(target, asset.path));
1500
+ }
1501
+ const additions = [];
1502
+ const replacements = [];
1503
+ const removals = [];
1504
+ const preserved = [];
1505
+ const conflicts = [];
1506
+ for (const asset of exactAssets) {
1507
+ const destination = installPath(target, asset.path);
1508
+ asset.destination = destination;
1509
+ if (asset.kind === "ensure-directory") {
1510
+ if (!fs.existsSync(destination)) {
1511
+ asset.current_digest = null;
1512
+ additions.push({ path: asset.path, kind: asset.kind });
1513
+ } else if (fs.statSync(destination).isDirectory()) {
1514
+ asset.current_digest = directoryDigest(destination);
1515
+ preserved.push({ path: asset.path, reason: "required control shard directory is present" });
1516
+ } else {
1517
+ asset.current_digest = pathDigest(destination);
1518
+ conflicts.push({
1519
+ path: asset.path,
1520
+ reason: "required control shard path is not a directory",
1521
+ forceable: false,
1522
+ });
1523
+ }
1524
+ continue;
1525
+ }
1526
+ const currentDigest = pathDigest(destination);
1527
+ asset.current_digest = currentDigest;
1528
+ if (currentDigest === null) additions.push({ path: asset.path, kind: asset.kind });
1529
+ else if (currentDigest === asset.expected_digest) {
1530
+ preserved.push({ path: asset.path, reason: "already current" });
1531
+ } else if (asset.previous_digest && currentDigest !== asset.previous_digest) {
1532
+ conflicts.push({ path: asset.path, reason: "managed asset differs from the installed manifest digest" });
1533
+ if (force) replacements.push({ path: asset.path, kind: asset.kind, forced: true });
1534
+ } else if (!asset.previous_digest) {
1535
+ conflicts.push({ path: asset.path, reason: "installed manifest does not prove ownership of the current bytes" });
1536
+ if (force) replacements.push({ path: asset.path, kind: asset.kind, forced: true });
1537
+ } else {
1538
+ replacements.push({ path: asset.path, kind: asset.kind, forced: false });
1539
+ }
1540
+ }
1541
+ for (const asset of mergedAssets) {
1542
+ if (!asset.exists) additions.push({ path: asset.path, kind: "merged-file" });
1543
+ else if (asset.current_digest === asset.expected_digest) {
1544
+ preserved.push({ path: asset.path, reason: "user-owned portions and managed contribution already current" });
1545
+ } else if (asset.previous_digest && asset.current_managed_digest !== asset.previous_digest) {
1546
+ conflicts.push({ path: asset.path, reason: "managed contribution differs from the installed manifest digest" });
1547
+ if (force) replacements.push({ path: asset.path, kind: "merged-file", forced: true });
1548
+ } else if (!asset.previous_digest) {
1549
+ conflicts.push({ path: asset.path, reason: "installed manifest does not prove ownership of the managed contribution" });
1550
+ if (force) replacements.push({ path: asset.path, kind: "merged-file", forced: true });
1551
+ } else {
1552
+ replacements.push({ path: asset.path, kind: "merged-file", forced: false });
1553
+ }
1554
+ }
1555
+ for (const asset of retiredAssets) {
1556
+ const destination = installPath(target, asset.path);
1557
+ asset.destination = destination;
1558
+ const currentDigest = pathDigest(destination);
1559
+ asset.current_digest = currentDigest;
1560
+ if (currentDigest === null) {
1561
+ preserved.push({ path: asset.path, reason: "retired managed asset is already absent" });
1562
+ } else if (asset.previous_digest && currentDigest === asset.previous_digest) {
1563
+ removals.push({ path: asset.path, kind: asset.kind, forced: false });
1564
+ } else {
1565
+ conflicts.push({
1566
+ path: asset.path,
1567
+ reason: asset.previous_digest
1568
+ ? "retired managed asset differs from the installed manifest digest"
1569
+ : "installed manifest does not prove ownership of the retired managed asset",
1570
+ });
1571
+ if (force) removals.push({ path: asset.path, kind: asset.kind, forced: true });
1572
+ }
1573
+ }
1574
+ const preservedWorkflow = preservedWorkflowFiles(
1575
+ target,
1576
+ [
1577
+ ...exactAssets
1578
+ .filter((asset) => asset.kind !== "ensure-directory")
1579
+ .map((asset) => asset.path),
1580
+ ...retiredAssets.map((asset) => asset.path),
1581
+ ...mergedAssets.map((asset) => asset.path),
1582
+ ],
1583
+ );
1584
+ for (const entry of preservedWorkflow.entries) preserved.push(entry);
1585
+ const changedPaths = [...additions, ...replacements, ...removals].map((item) => item.path);
1586
+ const manifestReplacement = {
1587
+ path: INSTALL_MANIFEST.replaceAll("\\", "/"),
1588
+ kind: "manifest",
1589
+ forced: false,
1590
+ };
1591
+ replacements.push(manifestReplacement);
1592
+ const backups = [
1593
+ ...changedPaths.filter((relative) => fs.existsSync(installPath(target, relative))),
1594
+ INSTALL_MANIFEST.replaceAll("\\", "/"),
1595
+ ].map((relative) => ({ path: relative }));
1596
+ return {
1597
+ manifest,
1598
+ manifestFile,
1599
+ codeRepository,
1600
+ sources,
1601
+ exactAssets,
1602
+ retiredAssets,
1603
+ mergedAssets,
1604
+ additions,
1605
+ replacements,
1606
+ removals,
1607
+ preserved,
1608
+ preservedSummary: preservedWorkflow.summary,
1609
+ conflicts,
1610
+ backups,
1611
+ comparison,
1612
+ contractRetirement,
1613
+ prototypeMigration,
1614
+ };
1615
+ }
1616
+
1617
+ function copyUpgradeAsset(asset, destination) {
1618
+ fs.rmSync(destination, { recursive: true, force: true });
1619
+ fs.mkdirSync(path.dirname(destination), { recursive: true });
1620
+ if (asset.kind === "runtime") installManagedRuntime(destination, asset.sources);
1621
+ else if (new Set(["directory", "ensure-directory"]).has(asset.kind)) {
1622
+ fs.cpSync(asset.source, destination, { recursive: true });
1623
+ }
1624
+ else fs.copyFileSync(asset.source, destination);
1625
+ }
1626
+
1627
+ function backupUpgradePath(target, upgradeRoot, relative) {
1628
+ const source = installPath(target, relative);
1629
+ if (!fs.existsSync(source)) return null;
1630
+ const backupRelative = path.posix.join("backup", relative.replaceAll("\\", "/"));
1631
+ const backup = installPath(upgradeRoot, backupRelative);
1632
+ fs.mkdirSync(path.dirname(backup), { recursive: true });
1633
+ fs.cpSync(source, backup, { recursive: true });
1634
+ return backupRelative;
1635
+ }
1636
+
1637
+ function restoreUpgradeAssets(target, upgradeRoot, assets) {
1638
+ for (const asset of [...assets].reverse()) {
1639
+ const destination = installPath(target, asset.path);
1640
+ fs.rmSync(destination, { recursive: true, force: true });
1641
+ if (asset.operation === "replace" || asset.operation === "remove") {
1642
+ const backup = installPath(upgradeRoot, asset.backup_path);
1643
+ fs.mkdirSync(path.dirname(destination), { recursive: true });
1644
+ fs.cpSync(backup, destination, { recursive: true });
1645
+ }
1646
+ }
1647
+ }
1648
+
1649
+ function applyReceiptBackedAsset({
1650
+ target,
1651
+ upgradeRoot,
1652
+ asset,
1653
+ operation,
1654
+ receiptAssets,
1655
+ apply,
1656
+ }) {
1657
+ const backupPath = backupUpgradePath(target, upgradeRoot, asset.path);
1658
+ const receiptAsset = {
1659
+ path: asset.path,
1660
+ operation,
1661
+ backup_path: backupPath,
1662
+ before_digest: exactPathDigest(asset.destination),
1663
+ after_digest: null,
790
1664
  };
1665
+ receiptAssets.push(receiptAsset);
1666
+ apply();
1667
+ receiptAsset.after_digest = exactPathDigest(asset.destination);
1668
+ }
1669
+
1670
+ function applyUpgradePlan(target, plan, packageVersion) {
1671
+ const upgradeId = `UPGRADE-${new Date().toISOString().replace(/[-:.]/g, "")}-${crypto.randomBytes(4).toString("hex")}`;
1672
+ const upgradeRoot = path.join(target, UPGRADE_RUNTIME, upgradeId);
1673
+ const formalEvents = path.join(target, ".workflow", "control", "events");
1674
+ const formalEventFingerprint = exactTreeDigest(formalEvents);
1675
+ const receiptAssets = [];
1676
+ fs.mkdirSync(upgradeRoot, { recursive: true });
1677
+ try {
1678
+ const additions = new Set(plan.additions.map((item) => item.path));
1679
+ const changes = new Set([
1680
+ ...plan.additions.map((item) => item.path),
1681
+ ...plan.replacements.map((item) => item.path),
1682
+ ...plan.removals.map((item) => item.path),
1683
+ ]);
1684
+ changes.delete(INSTALL_MANIFEST.replaceAll("\\", "/"));
1685
+ for (const asset of plan.exactAssets) {
1686
+ if (!changes.has(asset.path)) continue;
1687
+ applyReceiptBackedAsset({
1688
+ target,
1689
+ upgradeRoot,
1690
+ asset,
1691
+ operation: additions.has(asset.path) ? "add" : "replace",
1692
+ receiptAssets,
1693
+ apply: () => copyUpgradeAsset(asset, asset.destination),
1694
+ });
1695
+ }
1696
+ for (const asset of plan.mergedAssets) {
1697
+ if (!changes.has(asset.path)) continue;
1698
+ applyReceiptBackedAsset({
1699
+ target,
1700
+ upgradeRoot,
1701
+ asset,
1702
+ operation: additions.has(asset.path) ? "add" : "replace",
1703
+ receiptAssets,
1704
+ apply: () => atomicWrite(asset.destination, asset.desired),
1705
+ });
1706
+ }
1707
+ for (const asset of plan.retiredAssets) {
1708
+ if (!changes.has(asset.path)) continue;
1709
+ applyReceiptBackedAsset({
1710
+ target,
1711
+ upgradeRoot,
1712
+ asset,
1713
+ operation: "remove",
1714
+ receiptAssets,
1715
+ apply: () => fs.rmSync(asset.destination, { recursive: true, force: true }),
1716
+ });
1717
+ }
1718
+ const manifestRelative = INSTALL_MANIFEST.replaceAll("\\", "/");
1719
+ const manifestBackup = backupUpgradePath(target, upgradeRoot, manifestRelative);
1720
+ const nextManifest = buildInstallManifest(
1721
+ plan.sources,
1722
+ packageVersion,
1723
+ plan.codeRepository,
1724
+ plan.manifest,
1725
+ );
1726
+ nextManifest.last_upgrade = { upgrade_id: upgradeId, upgraded_at: new Date().toISOString() };
1727
+ const manifestReceipt = {
1728
+ path: manifestRelative,
1729
+ operation: "replace",
1730
+ backup_path: manifestBackup,
1731
+ before_digest: exactPathDigest(installPath(upgradeRoot, manifestBackup)),
1732
+ after_digest: null,
1733
+ };
1734
+ receiptAssets.push(manifestReceipt);
1735
+ atomicWrite(plan.manifestFile, `${JSON.stringify(nextManifest, null, 2)}\n`);
1736
+ manifestReceipt.after_digest = exactPathDigest(plan.manifestFile);
1737
+ const receipt = {
1738
+ schema_version: 1,
1739
+ upgrade_id: upgradeId,
1740
+ status: "applied",
1741
+ from_version: plan.manifest.version,
1742
+ to_version: packageVersion,
1743
+ applied_at: new Date().toISOString(),
1744
+ formal_event_tree_fingerprint: formalEventFingerprint,
1745
+ contract_retirement: {
1746
+ gate_id: plan.contractRetirement.gate_id,
1747
+ contract_version: UI_CONTRACT_VERSION,
1748
+ affected_iterations: plan.contractRetirement.affected_iterations,
1749
+ expected_scope_revisions: plan.contractRetirement.expected_scope_revisions,
1750
+ },
1751
+ assets: receiptAssets,
1752
+ };
1753
+ atomicWrite(path.join(upgradeRoot, "receipt.json"), `${JSON.stringify(receipt, null, 2)}\n`);
1754
+ const retirementArgs = [
1755
+ "contract", "retire", "--project", target,
1756
+ "--gate-id", "UX-LF",
1757
+ "--actor", "system:package-upgrade",
1758
+ "--reason", "prototype-first UI contract",
1759
+ "--contract-version", UI_CONTRACT_VERSION,
1760
+ ];
1761
+ const expectedRevisions = Object.values(plan.contractRetirement.expected_scope_revisions);
1762
+ if (expectedRevisions.length === 1) {
1763
+ retirementArgs.push("--expected-scope-revision", String(expectedRevisions[0]));
1764
+ }
1765
+ const contractRetirement = executeDeliveryStateCommand(retirementArgs, {
1766
+ yaml: FORMAL_STATE_YAML,
1767
+ });
1768
+ return { upgradeId, contractRetirement };
1769
+ } catch (error) {
1770
+ restoreUpgradeAssets(target, upgradeRoot, receiptAssets);
1771
+ fs.rmSync(upgradeRoot, { recursive: true, force: true });
1772
+ throw error;
1773
+ }
1774
+ }
1775
+
1776
+ function rollbackUpgrade(packageRoot, target, upgradeId) {
1777
+ validateTarget(target);
1778
+ if (!/^UPGRADE-[0-9A-Za-z_-]+$/.test(upgradeId || "")) {
1779
+ throw new Error("Invalid upgrade rollback identifier.");
1780
+ }
1781
+ const upgradeRoot = path.join(target, UPGRADE_RUNTIME, upgradeId);
1782
+ validateManagedPath(target, upgradeRoot);
1783
+ const receiptFile = path.join(upgradeRoot, "receipt.json");
1784
+ let receipt;
1785
+ try {
1786
+ receipt = JSON.parse(fs.readFileSync(receiptFile, "utf8"));
1787
+ } catch (error) {
1788
+ throw new Error(`Cannot read upgrade rollback receipt ${upgradeId}: ${error.message}`);
1789
+ }
1790
+ if (receipt.schema_version !== 1
1791
+ || receipt.upgrade_id !== upgradeId
1792
+ || receipt.status !== "applied"
1793
+ || !Array.isArray(receipt.assets)) {
1794
+ throw new Error(`Upgrade ${upgradeId} is not in an applied rollback state.`);
1795
+ }
1796
+ const sources = installationSources(packageRoot);
1797
+ const allowedPaths = new Set([
1798
+ ...managedAssetInventory(sources).map((asset) => asset.path),
1799
+ ...RETIRED_MANAGED_ASSETS.map((asset) => asset.path),
1800
+ ...MERGED_MANAGED_SURFACES.map((surface) => surface.path),
1801
+ INSTALL_MANIFEST.replaceAll("\\", "/"),
1802
+ ]);
1803
+ for (const asset of receipt.assets) {
1804
+ if (!allowedPaths.has(asset.path)
1805
+ || !new Set(["add", "replace", "remove"]).has(asset.operation)
1806
+ || (new Set(["replace", "remove"]).has(asset.operation) && !asset.backup_path)) {
1807
+ throw new Error(`Upgrade receipt contains an unsafe managed asset entry: ${asset.path || "<missing>"}.`);
1808
+ }
1809
+ const destination = installPath(target, asset.path);
1810
+ validateManagedPath(target, destination);
1811
+ if (asset.backup_path) {
1812
+ const backup = installPath(upgradeRoot, asset.backup_path);
1813
+ if (!isInside(backup, upgradeRoot)
1814
+ || !asset.backup_path.replaceAll("\\", "/").startsWith("backup/")) {
1815
+ throw new Error(`Upgrade receipt contains an unsafe backup path for ${asset.path}.`);
1816
+ }
1817
+ validateManagedPath(upgradeRoot, backup);
1818
+ }
1819
+ }
1820
+ const currentFingerprint = exactTreeDigest(path.join(target, ".workflow", "control", "events"));
1821
+ if (currentFingerprint !== receipt.formal_event_tree_fingerprint) {
1822
+ throw new Error(
1823
+ "A new-format formal transaction was written after this upgrade; rollback is blocked. Use forward repair.",
1824
+ );
1825
+ }
1826
+ for (const asset of receipt.assets) {
1827
+ if (exactPathDigest(installPath(target, asset.path)) !== asset.after_digest) {
1828
+ throw new Error(`Managed asset changed after upgrade (${asset.path}); rollback is blocked. Use forward repair.`);
1829
+ }
1830
+ if (asset.operation === "replace" || asset.operation === "remove") {
1831
+ const backup = installPath(upgradeRoot, asset.backup_path);
1832
+ if (exactPathDigest(backup) !== asset.before_digest) {
1833
+ throw new Error(`Upgrade backup changed after apply (${asset.path}); rollback is blocked. Use forward repair.`);
1834
+ }
1835
+ }
1836
+ }
1837
+ restoreUpgradeAssets(target, upgradeRoot, receipt.assets);
1838
+ receipt.status = "rolled_back";
1839
+ receipt.rolled_back_at = new Date().toISOString();
1840
+ atomicWrite(receiptFile, `${JSON.stringify(receipt, null, 2)}\n`);
1841
+ return {
1842
+ command: "upgrade rollback",
1843
+ upgrade_id: upgradeId,
1844
+ rolled_back: true,
1845
+ restored_version: receipt.from_version,
1846
+ };
1847
+ }
1848
+
1849
+ export function upgradeProject(packageRoot, target, options = {}) {
1850
+ if (options.rollback) return rollbackUpgrade(packageRoot, target, options.rollback);
1851
+ const { dryRun = false, force = false, packageVersion = "0.0.0" } = options;
1852
+ const plan = planUpgrade(packageRoot, target, { force, packageVersion });
1853
+ const report = {
1854
+ command: "upgrade",
1855
+ dry_run: dryRun,
1856
+ applied: false,
1857
+ upgrade_id: null,
1858
+ from_version: plan.manifest.version,
1859
+ to_version: packageVersion,
1860
+ changes: {
1861
+ additions: plan.additions,
1862
+ replacements: plan.replacements,
1863
+ removals: plan.removals,
1864
+ preserved: plan.preserved,
1865
+ preserved_summary: plan.preservedSummary,
1866
+ conflicts: plan.conflicts,
1867
+ backups: plan.backups,
1868
+ },
1869
+ compatibility: {
1870
+ installation_manifest: "compatible",
1871
+ package_version: plan.comparison > 0 ? "upgrade" : plan.comparison === 0 ? "same-version repair" : "forced downgrade",
1872
+ },
1873
+ code_repository: {
1874
+ path: plan.codeRepository.path,
1875
+ write_planned: false,
1876
+ },
1877
+ contract_retirement: {
1878
+ ...plan.contractRetirement,
1879
+ contract_version: UI_CONTRACT_VERSION,
1880
+ write_planned: plan.contractRetirement.affected_iterations.length > 0,
1881
+ },
1882
+ prototype_migration: {
1883
+ ...plan.prototypeMigration,
1884
+ required_follow_up: {
1885
+ command: "ai-delivery migrate",
1886
+ write_planned: false,
1887
+ },
1888
+ },
1889
+ };
1890
+ if (dryRun) return report;
1891
+ const blockingConflicts = plan.conflicts.filter((item) => !force || item.forceable === false);
1892
+ if (blockingConflicts.length) {
1893
+ throw new Error(
1894
+ `Managed files differ from the installed manifest:\n- ${blockingConflicts.map((item) =>
1895
+ `${item.path}: ${item.reason}`).join("\n- ")}\nRerun with --force only after explicit approval of forceable conflicts.`,
1896
+ );
1897
+ }
1898
+ const applied = applyUpgradePlan(target, plan, packageVersion);
1899
+ report.upgrade_id = applied.upgradeId;
1900
+ report.contract_retirement = {
1901
+ ...report.contract_retirement,
1902
+ ...applied.contractRetirement,
1903
+ contract_version: UI_CONTRACT_VERSION,
1904
+ write_planned: false,
1905
+ };
1906
+ report.applied = true;
1907
+ return report;
1908
+ }
1909
+
1910
+ function managedRuntimeDoctorCheck(target, options) {
1911
+ const installedRoot = path.join(target, ".workflow", "tools", options.directory);
1912
+ const expectedDigest = options.expectedSources.length
1913
+ ? sourceSetDigest(options.expectedSources)
1914
+ : null;
1915
+ const integrity = Boolean(expectedDigest)
1916
+ && fs.existsSync(installedRoot)
1917
+ && directoryDigest(installedRoot) === expectedDigest
1918
+ && options.manifestDigest === expectedDigest;
1919
+ const smoke = spawnSync(
1920
+ process.execPath,
1921
+ [path.join(installedRoot, options.cli), "--help"],
1922
+ { cwd: target, encoding: "utf8", windowsHide: true },
1923
+ );
1924
+ const smokeOk = smoke.status === 0 && options.helpPattern.test(smoke.stdout);
1925
+ return {
1926
+ ok: integrity && smokeOk,
1927
+ scope: options.scope,
1928
+ message: !integrity
1929
+ ? `project ${options.label} runtime integrity check failed`
1930
+ : smokeOk
1931
+ ? `project ${options.label} runtime integrity and CLI smoke check pass`
1932
+ : `project ${options.label} CLI smoke check failed: ${smoke.stderr.trim() || smoke.error?.message || "no valid output"}`,
1933
+ };
1934
+ }
1935
+
1936
+ function rawGitAttributePolicy(gitattributes) {
1937
+ const rawTargets = new Set(RAW_GIT_ATTRIBUTE_TARGETS.map((target) => `/${target}`));
1938
+ const invalid = [];
1939
+ const legacyPatterns = [];
1940
+ for (const line of gitattributes.split(/\r?\n/).map((entry) => entry.trim())) {
1941
+ if (!line || line.startsWith("#") || !/(?:^|\s)-text(?:\s|$)/.test(line)) continue;
1942
+ const [pattern] = line.split(/\s+/);
1943
+ if (rawTargets.has(pattern)) continue;
1944
+ const normalized = pattern.replace(/^\//, "");
1945
+ const segments = normalized.split("/");
1946
+ const fixedDirectory = segments.length >= 4
1947
+ && segments[0] === ".workflow"
1948
+ && segments[1] === "iterations"
1949
+ && !/[?*\[]/.test(segments[2])
1950
+ && segments.slice(0, -1).every((segment) => !/[?*\[]/.test(segment));
1951
+ const narrowFilename = !pattern.includes("**") && !/[?\[]/.test(segments.at(-1));
1952
+ if (fixedDirectory && narrowFilename) legacyPatterns.push(normalized);
1953
+ else invalid.push(line);
1954
+ }
1955
+ return { invalid, legacyPatterns };
1956
+ }
1957
+
1958
+ function matchesRegisteredLegacyPattern(relative, pattern) {
1959
+ const relativeSegments = relative.split("/");
1960
+ const patternSegments = pattern.split("/");
1961
+ if (relativeSegments.length !== patternSegments.length) return false;
1962
+ return patternSegments.every((segment, index) => {
1963
+ if (!segment.includes("*")) return segment === relativeSegments[index];
1964
+ const expression = segment.split("*")
1965
+ .map((part) => part.replace(/[.+^${}()|[\]\\]/g, "\\$&"))
1966
+ .join("[^/]*");
1967
+ return new RegExp(`^${expression}$`).test(relativeSegments[index]);
1968
+ });
1969
+ }
1970
+
1971
+ function isRawLegacyIterationEvidence(target, relative, text, eol, registeredPatterns) {
1972
+ return text === "unset"
1973
+ && eol === "lf"
1974
+ && relative.startsWith(".workflow/iterations/")
1975
+ && fs.existsSync(installPath(target, relative))
1976
+ && registeredPatterns.some((pattern) => matchesRegisteredLegacyPattern(relative, pattern));
1977
+ }
1978
+
1979
+ function filesUnderExcluding(root, excludedRoots) {
1980
+ if (!fs.existsSync(root)) return [];
1981
+ const files = [];
1982
+ const pending = [root];
1983
+ while (pending.length) {
1984
+ const current = pending.pop();
1985
+ if (excludedRoots.some((excluded) => isInside(current, excluded))) continue;
1986
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
1987
+ const absolute = path.join(current, entry.name);
1988
+ if (entry.isDirectory()) pending.push(absolute);
1989
+ else if (entry.isFile()) files.push(absolute);
1990
+ }
1991
+ }
1992
+ return files.sort((left, right) => left.localeCompare(right));
1993
+ }
1994
+
1995
+ function effectiveGitAttributeAudit(target, files, registeredLegacyPatterns) {
1996
+ const invalid = [];
1997
+ let invalidCount = 0;
1998
+ for (let index = 0; index < files.length; index += DOCTOR_ATTRIBUTE_BATCH_SIZE) {
1999
+ const batch = files.slice(index, index + DOCTOR_ATTRIBUTE_BATCH_SIZE);
2000
+ const result = spawnSync(
2001
+ "git",
2002
+ ["-c", "core.quotePath=false", "check-attr", "-z", "--stdin", "text", "eol"],
2003
+ {
2004
+ cwd: target,
2005
+ encoding: "utf8",
2006
+ input: `${batch.join("\0")}\0`,
2007
+ maxBuffer: 16 * 1024 * 1024,
2008
+ windowsHide: true,
2009
+ },
2010
+ );
2011
+ if (result.status !== 0) {
2012
+ return {
2013
+ failed: true,
2014
+ error: (result.stderr || result.error?.message || "git check-attr failed").trim(),
2015
+ invalid,
2016
+ invalidCount,
2017
+ };
2018
+ }
2019
+ const values = new Map();
2020
+ const tokens = result.stdout.split("\0");
2021
+ for (let offset = 0; offset + 2 < tokens.length; offset += 3) {
2022
+ const [file, attribute, value] = tokens.slice(offset, offset + 3);
2023
+ if (!file || !attribute) continue;
2024
+ if (!values.has(file)) values.set(file, {});
2025
+ values.get(file)[attribute] = value;
2026
+ }
2027
+ for (const file of batch) {
2028
+ const text = values.get(file)?.text;
2029
+ const eol = values.get(file)?.eol;
2030
+ const rawAttribution = RAW_GIT_ATTRIBUTE_TARGETS.includes(file);
2031
+ const valid = rawAttribution
2032
+ ? text === "unset" && eol === "unspecified"
2033
+ : isRawLegacyIterationEvidence(target, file, text, eol, registeredLegacyPatterns)
2034
+ || (text === "set" || text === "auto") && eol === "lf";
2035
+ if (valid) continue;
2036
+ invalidCount += 1;
2037
+ if (invalid.length < DOCTOR_DETAIL_LIMIT) invalid.push(file);
2038
+ }
2039
+ }
2040
+ return { failed: false, error: null, invalid, invalidCount };
791
2041
  }
792
2042
 
793
2043
  export function doctorProject(target, options = {}) {
@@ -803,12 +2053,19 @@ export function doctorProject(target, options = {}) {
803
2053
  } catch (error) {
804
2054
  checks.push({ ok: false, message: error.message });
805
2055
  }
2056
+ if (workspace) {
2057
+ checks.push(inspectCodeGraph(target, {
2058
+ runner: options.codeGraphRunner,
2059
+ command: options.codeGraphCommand,
2060
+ env: options.codeGraphEnv,
2061
+ }));
2062
+ }
806
2063
  const manifestFile = path.join(target, INSTALL_MANIFEST);
807
2064
  let manifest = null;
808
2065
  try {
809
2066
  manifest = JSON.parse(fs.readFileSync(manifestFile, "utf8"));
810
2067
  checks.push({
811
- ok: manifest.scope === "workspace" && manifest.schema_version === 5,
2068
+ ok: manifest.scope === "workspace" && manifest.schema_version === INSTALL_MANIFEST_SCHEMA,
812
2069
  message: "installation manifest uses workspace scope",
813
2070
  });
814
2071
  } catch (error) {
@@ -824,11 +2081,21 @@ export function doctorProject(target, options = {}) {
824
2081
  message: "workflow repository has an independent Git root",
825
2082
  });
826
2083
  const missingControlDirectories = CONTROL_DIRECTORIES.filter((directory) =>
827
- !fs.existsSync(path.join(target, ".workflow", "control", directory)));
2084
+ !fs.existsSync(path.join(target, ".workflow", "control", directory))
2085
+ || !fs.statSync(path.join(target, ".workflow", "control", directory)).isDirectory());
2086
+ const missingControlMarkers = ENSURED_CONTROL_DIRECTORIES.filter((directory) => {
2087
+ const directoryPath = path.join(target, ".workflow", "control", directory);
2088
+ const marker = path.join(directoryPath, ".gitkeep");
2089
+ return fs.existsSync(directoryPath)
2090
+ && fs.statSync(directoryPath).isDirectory()
2091
+ && (!fs.existsSync(marker) || !fs.statSync(marker).isFile());
2092
+ }).map((directory) => `${directory}/.gitkeep`);
828
2093
  checks.push({
829
- ok: missingControlDirectories.length === 0,
2094
+ ok: missingControlDirectories.length === 0 && missingControlMarkers.length === 0,
830
2095
  message: missingControlDirectories.length
831
2096
  ? `shared control shard directories are missing: ${missingControlDirectories.join(", ")}`
2097
+ : missingControlMarkers.length
2098
+ ? `shared control shard directories have missing markers: ${missingControlMarkers.join(", ")}`
832
2099
  : "shared task, artifact, Gate, version, and release shard directories are present",
833
2100
  });
834
2101
  checks.push({
@@ -845,12 +2112,17 @@ export function doctorProject(target, options = {}) {
845
2112
  });
846
2113
 
847
2114
  const packageRoot = options.packageRoot || PACKAGE_ROOT;
2115
+ const packageSurface = packageSurfaceDoctorCheck(packageRoot);
2116
+ checks.push(packageSurface);
848
2117
  let expectedSkills = ESSENTIAL_STARTUP_SKILLS;
849
2118
  let expectedSkillDigests = {};
850
2119
  let expectedHook = null;
851
2120
  let expectedBootstrapSources = [];
852
2121
  let expectedStateSources = [];
853
2122
  let expectedEvolutionSources = [];
2123
+ let expectedMockSources = [];
2124
+ let expectedFrontendGuardSources = [];
2125
+ let expectedUiAcceptanceSources = [];
854
2126
  try {
855
2127
  expectedSkills = packagedSkillNames(packageRoot);
856
2128
  expectedSkillDigests = Object.fromEntries(expectedSkills.map((name) => [
@@ -858,10 +2130,15 @@ export function doctorProject(target, options = {}) {
858
2130
  directoryDigest(path.join(packageRoot, "skills", name)),
859
2131
  ]));
860
2132
  expectedHook = path.join(packageRoot, "skills", "ai-delivery-checkpoint-task", "scripts", "hook-event.mjs");
861
- const sourceTemplate = path.join(packageRoot, "skills", "ai-delivery-orchestrate", "assets", "project-template");
862
- expectedBootstrapSources = bootstrapRuntimeSources(packageRoot, sourceTemplate);
863
- expectedStateSources = stateRuntimeSources(packageRoot, sourceTemplate);
864
- expectedEvolutionSources = evolutionRuntimeSources(packageRoot, sourceTemplate);
2133
+ if (packageSurface.ok) {
2134
+ const sourceTemplate = path.join(packageRoot, "skills", "ai-delivery-orchestrate", "assets", "project-template");
2135
+ expectedBootstrapSources = bootstrapRuntimeSources(packageRoot, sourceTemplate);
2136
+ expectedStateSources = stateRuntimeSources(packageRoot, sourceTemplate);
2137
+ expectedEvolutionSources = evolutionRuntimeSources(packageRoot, sourceTemplate);
2138
+ expectedMockSources = mockRuntimeSources(packageRoot, sourceTemplate);
2139
+ expectedFrontendGuardSources = frontendGuardRuntimeSources(packageRoot, sourceTemplate);
2140
+ expectedUiAcceptanceSources = uiAcceptanceRuntimeSources(packageRoot, sourceTemplate);
2141
+ }
865
2142
  } catch (error) {
866
2143
  checks.push({ ok: false, message: `packaged managed-asset baseline is unavailable: ${error.message}` });
867
2144
  }
@@ -890,6 +2167,20 @@ export function doctorProject(target, options = {}) {
890
2167
  ? `managed skill integrity failed: damaged=${damagedSkills.join(", ") || "none"}; manifest=${inconsistentSkillDigests.join(", ") || "none"}`
891
2168
  : "managed skill integrity matches the fixed package baseline",
892
2169
  });
2170
+ if (!packageSurface.ok) return { checks, ok: false };
2171
+ checks.push(designReferenceDoctorCheck(
2172
+ target,
2173
+ path.join(
2174
+ packageRoot,
2175
+ "skills",
2176
+ "ai-delivery-orchestrate",
2177
+ "assets",
2178
+ "project-template",
2179
+ "ref",
2180
+ "ui",
2181
+ ),
2182
+ manifest,
2183
+ ));
893
2184
  try {
894
2185
  const config = inspectHookConfig(fs.readFileSync(path.join(target, ".codex", "config.toml"), "utf8"));
895
2186
  checks.push({ ok: config.hooksEnabled, message: "project Codex hooks feature is enabled" });
@@ -908,28 +2199,11 @@ export function doctorProject(target, options = {}) {
908
2199
  ok: hookIntegrity,
909
2200
  message: hookIntegrity ? "project Hook integrity matches the package baseline" : "project Hook integrity check failed",
910
2201
  });
911
- const viewerFiles = [
912
- "server.mjs",
913
- "start.cmd",
914
- "start.ps1",
915
- "start.sh",
916
- "public/index.html",
917
- "public/app.js",
918
- "public/styles.css",
919
- ];
920
- const missingViewerFiles = viewerFiles.filter((file) =>
921
- !fs.existsSync(path.join(target, ".workflow", "tools", "viewer", file)));
922
- checks.push({
923
- ok: missingViewerFiles.length === 0,
924
- required: false,
925
- scope: "viewer",
926
- message: missingViewerFiles.length
927
- ? `workflow viewer is incomplete: ${missingViewerFiles.join(", ")}`
928
- : "project workflow viewer and startup scripts are present",
929
- });
930
2202
  const stateFiles = [
931
2203
  "state.mjs",
932
2204
  "delivery-state.mjs",
2205
+ "history-anchor.mjs",
2206
+ "fs-utils.mjs",
933
2207
  "vendor/yaml/package.json",
934
2208
  "vendor/yaml/dist/index.js",
935
2209
  ];
@@ -960,6 +2234,8 @@ export function doctorProject(target, options = {}) {
960
2234
  "workspace.mjs",
961
2235
  "yaml-runtime.mjs",
962
2236
  "delivery-state.mjs",
2237
+ "history-anchor.mjs",
2238
+ "fs-utils.mjs",
963
2239
  ];
964
2240
  const missingBootstrapFiles = bootstrapFiles.filter((file) =>
965
2241
  !fs.existsSync(path.join(installedBootstrapRoot, file)));
@@ -999,7 +2275,13 @@ export function doctorProject(target, options = {}) {
999
2275
  const result = spawnSync(
1000
2276
  process.execPath,
1001
2277
  [path.join(installedStateRoot, "state.mjs"), command],
1002
- { cwd: target, encoding: "utf8", windowsHide: true },
2278
+ {
2279
+ cwd: target,
2280
+ encoding: "utf8",
2281
+ maxBuffer: command === "inspect" ? FORMAL_STATE_INSPECT_MAX_BUFFER : undefined,
2282
+ windowsHide: true,
2283
+ stdio: ["ignore", "pipe", "pipe"],
2284
+ },
1003
2285
  );
1004
2286
  let output = null;
1005
2287
  try {
@@ -1007,7 +2289,9 @@ export function doctorProject(target, options = {}) {
1007
2289
  } catch {
1008
2290
  output = null;
1009
2291
  }
1010
- const validOutput = command === "verify" ? output?.valid === true : output?.command === "inspect";
2292
+ const validOutput = command === "verify"
2293
+ ? output?.valid === true
2294
+ : output?.command === "inspect";
1011
2295
  return { ...result, command, validOutput };
1012
2296
  });
1013
2297
  const smokeOk = smokeResults.every((result) => result.status === 0 && result.validOutput);
@@ -1042,6 +2326,47 @@ export function doctorProject(target, options = {}) {
1042
2326
  ? "workflow evolution CLI verify smoke check passes"
1043
2327
  : `workflow evolution CLI smoke check failed: ${evolutionSmoke.stderr.trim() || "no valid output"}`,
1044
2328
  });
2329
+ const installedMockRoot = path.join(target, ".workflow", "tools", "mock");
2330
+ const expectedMockDigest = expectedMockSources.length ? sourceSetDigest(expectedMockSources) : null;
2331
+ const mockIntegrity = Boolean(expectedMockDigest)
2332
+ && fs.existsSync(installedMockRoot)
2333
+ && directoryDigest(installedMockRoot) === expectedMockDigest
2334
+ && manifest?.managed_assets?.mock_runtime?.sha256 === expectedMockDigest;
2335
+ const mockSmoke = spawnSync(
2336
+ process.execPath,
2337
+ [path.join(installedMockRoot, "mock.mjs"), "--help"],
2338
+ { cwd: target, encoding: "utf8", windowsHide: true },
2339
+ );
2340
+ const mockSmokeOk = mockSmoke.status === 0
2341
+ && /mock <setup\|start\|stop\|status\|sync>/.test(mockSmoke.stdout)
2342
+ && /mock freeze.*--scenario-pack/.test(mockSmoke.stdout);
2343
+ checks.push({
2344
+ ok: mockIntegrity && mockSmokeOk,
2345
+ scope: "mock-system",
2346
+ message: !mockIntegrity
2347
+ ? "project Mock System runtime integrity check failed"
2348
+ : mockSmokeOk
2349
+ ? "project Mock System runtime integrity and CLI smoke check pass"
2350
+ : `project Mock System CLI smoke check failed: ${mockSmoke.stderr.trim() || "no valid output"}`,
2351
+ });
2352
+ checks.push(managedRuntimeDoctorCheck(target, {
2353
+ directory: "frontend-guard",
2354
+ expectedSources: expectedFrontendGuardSources,
2355
+ manifestDigest: manifest?.managed_assets?.frontend_guard_runtime?.sha256,
2356
+ cli: "frontend-guard.mjs",
2357
+ helpPattern: /frontend-guard <check-file\|verify-diff>/,
2358
+ scope: "frontend-guard",
2359
+ label: "frontend guard",
2360
+ }));
2361
+ checks.push(managedRuntimeDoctorCheck(target, {
2362
+ directory: "ui-acceptance",
2363
+ expectedSources: expectedUiAcceptanceSources,
2364
+ manifestDigest: manifest?.managed_assets?.ui_acceptance_runtime?.sha256,
2365
+ cli: "ui-acceptance.mjs",
2366
+ helpPattern: /ui-acceptance <plan\|verify>/,
2367
+ scope: "ui-acceptance",
2368
+ label: "UI Acceptance",
2369
+ }));
1045
2370
  const evolutionVerify = spawnSync(
1046
2371
  process.execPath,
1047
2372
  [path.join(installedEvolutionRoot, "evolve.mjs"), "verify"],
@@ -1083,6 +2408,7 @@ export function doctorProject(target, options = {}) {
1083
2408
  : "";
1084
2409
  const missingAttributes = GIT_ATTRIBUTE_ENTRIES.filter((entry) =>
1085
2410
  !gitattributes.split(/\r?\n/).some((line) => line.trim() === entry));
2411
+ const rawAttributePolicy = rawGitAttributePolicy(gitattributes);
1086
2412
  const attributeTargets = [...new Set([
1087
2413
  ".workflow/delivery/workflow-state.yaml",
1088
2414
  ".agents/skills/ai-delivery-orchestrate/SKILL.md",
@@ -1090,43 +2416,37 @@ export function doctorProject(target, options = {}) {
1090
2416
  "AGENTS.md",
1091
2417
  ".gitignore",
1092
2418
  ".gitattributes",
1093
- ...filesUnder(path.join(target, ".workflow"))
2419
+ ...RAW_GIT_ATTRIBUTE_TARGETS,
2420
+ ...filesUnderExcluding(
2421
+ path.join(target, ".workflow"),
2422
+ PRESERVED_TRANSIENT_ROOTS.map((root) => installPath(target, root)),
2423
+ )
1094
2424
  .map((file) => path.relative(target, file).replaceAll("\\", "/")),
1095
2425
  ...filesUnder(path.join(target, ".agents", "skills"))
1096
2426
  .map((file) => path.relative(target, file).replaceAll("\\", "/"))
1097
2427
  .filter((file) => file.startsWith(".agents/skills/ai-delivery-")),
1098
2428
  ])];
1099
- const attributeValues = new Map();
1100
- let effectiveAttributesFailed = false;
1101
- for (let index = 0; index < attributeTargets.length; index += 100) {
1102
- const batch = attributeTargets.slice(index, index + 100);
1103
- const effectiveAttributes = spawnSync(
1104
- "git",
1105
- ["-c", "core.quotePath=false", "check-attr", "text", "eol", "--", ...batch],
1106
- { cwd: target, encoding: "utf8", windowsHide: true },
1107
- );
1108
- if (effectiveAttributes.status !== 0) {
1109
- effectiveAttributesFailed = true;
1110
- break;
1111
- }
1112
- for (const line of effectiveAttributes.stdout.split(/\r?\n/).filter(Boolean)) {
1113
- const match = line.match(/^(.*): (text|eol): (.*)$/);
1114
- if (match) attributeValues.set(`${match[1]}:${match[2]}`, match[3]);
1115
- }
1116
- }
1117
- const invalidEffectiveAttributes = !effectiveAttributesFailed
1118
- ? attributeTargets.filter((file) => {
1119
- const text = attributeValues.get(`${file}:text`);
1120
- return !new Set(["set", "auto"]).has(text) || attributeValues.get(`${file}:eol`) !== "lf";
1121
- })
1122
- : [...attributeTargets];
2429
+ const attributeAudit = effectiveGitAttributeAudit(
2430
+ target,
2431
+ attributeTargets,
2432
+ rawAttributePolicy.legacyPatterns,
2433
+ );
2434
+ const omittedInvalidAttributes = attributeAudit.invalidCount - attributeAudit.invalid.length;
1123
2435
  checks.push({
1124
- ok: missingAttributes.length === 0 && invalidEffectiveAttributes.length === 0,
2436
+ ok: missingAttributes.length === 0
2437
+ && rawAttributePolicy.invalid.length === 0
2438
+ && !attributeAudit.failed
2439
+ && attributeAudit.invalidCount === 0,
1125
2440
  message: missingAttributes.length
1126
2441
  ? `project .gitattributes is missing byte-stable workflow rules: ${missingAttributes.join(", ")}`
1127
- : invalidEffectiveAttributes.length
1128
- ? `project .gitattributes is overridden for checksummed files: ${invalidEffectiveAttributes.join(", ")}`
1129
- : "project .gitattributes keeps checksummed workflow files at LF",
2442
+ : rawAttributePolicy.invalid.length
2443
+ ? `project .gitattributes is overridden by broad raw-byte rules or unsupported raw-byte rules: ${rawAttributePolicy.invalid.join(", ")}`
2444
+ : attributeAudit.failed
2445
+ ? `project .gitattributes effective check failed: ${attributeAudit.error}`
2446
+ : attributeAudit.invalidCount
2447
+ ? `project .gitattributes is overridden for checksummed files: ${attributeAudit.invalid.join(", ")}`
2448
+ + (omittedInvalidAttributes ? ` (+${omittedInvalidAttributes} omitted)` : "")
2449
+ : "project .gitattributes keeps managed checksummed files byte-stable",
1130
2450
  });
1131
2451
  if (workspace) {
1132
2452
  const expectedIgnore = `/${workspace.configuration.code_repository.path}/`;