openplanr 3.4.0 → 3.5.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 (336) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/cli/commands/design.d.ts +3 -0
  3. package/dist/cli/commands/design.d.ts.map +1 -0
  4. package/dist/cli/commands/design.js +137 -0
  5. package/dist/cli/commands/design.js.map +1 -0
  6. package/dist/cli/commands/groups/operations.d.ts.map +1 -1
  7. package/dist/cli/commands/groups/operations.js +2 -0
  8. package/dist/cli/commands/groups/operations.js.map +1 -1
  9. package/dist/dashboard/.vite/manifest.json +1 -1
  10. package/dist/dashboard/assets/{index-CvQAlBy3.js → index-B-l_941W.js} +2 -2
  11. package/dist/dashboard/assets/{index-CvQAlBy3.js.map → index-B-l_941W.js.map} +1 -1
  12. package/dist/dashboard/dashboard-manifest.json +13 -13
  13. package/dist/dashboard/index.html +1 -1
  14. package/dist/services/design-artifact-service.d.ts +22 -0
  15. package/dist/services/design-artifact-service.d.ts.map +1 -1
  16. package/dist/services/design-artifact-service.js +76 -16
  17. package/dist/services/design-artifact-service.js.map +1 -1
  18. package/dist/services/runtime-manager/runtime-package.js +1 -1
  19. package/dist/services/runtime-manager/runtime-package.js.map +1 -1
  20. package/docs/CLI.md +29 -0
  21. package/lib/host-packages/adapter-registry.json +5 -5
  22. package/lib/host-packages/capability-map.json +1 -1
  23. package/lib/host-packages/claude/.claude-plugin/marketplace.json +2 -2
  24. package/lib/host-packages/claude/openplanr/.claude-plugin/plugin.json +1 -1
  25. package/lib/host-packages/claude/openplanr/.openplanr-content.json +24 -340
  26. package/lib/host-packages/claude/openplanr/README.md +8 -8
  27. package/lib/host-packages/claude/openplanr/skills/artifact/references/design-sharing.md +2 -2
  28. package/lib/host-packages/claude/openplanr/skills/design/SKILL.md +23 -12
  29. package/lib/host-packages/claude/openplanr/skills/design/references/design-system.md +2 -2
  30. package/lib/host-packages/claude/openplanr/skills/design/references/generation.md +3 -3
  31. package/lib/host-packages/claude/openplanr/skills/design/references/hosted-canvas.md +6 -3
  32. package/lib/host-packages/claude/openplanr/skills/design/references/review.md +6 -6
  33. package/lib/host-packages/claude/openplanr/skills/design/references/team-review.md +3 -4
  34. package/lib/host-packages/claude/openplanr/skills/design/references/utilities.md +78 -38
  35. package/lib/host-packages/claude/openplanr/skills/design-loop/SKILL.md +15 -7
  36. package/lib/host-packages/claude/openplanr/skills/design-loop/references/design-system.md +2 -2
  37. package/lib/host-packages/claude/openplanr/skills/design-loop/references/generation.md +3 -3
  38. package/lib/host-packages/claude/openplanr/skills/design-loop/references/review.md +6 -6
  39. package/lib/host-packages/claude/openplanr/skills/design-loop/references/team-review.md +3 -4
  40. package/lib/host-packages/claude/openplanr/skills/design-loop/references/utilities.md +78 -38
  41. package/lib/host-packages/claude/openplanr/skills/design-review/SKILL.md +25 -5
  42. package/lib/host-packages/claude/openplanr/skills/design-review/references/design-system.md +2 -2
  43. package/lib/host-packages/claude/openplanr/skills/design-review/references/generation.md +3 -3
  44. package/lib/host-packages/claude/openplanr/skills/design-review/references/review.md +6 -6
  45. package/lib/host-packages/claude/openplanr/skills/design-review/references/team-review.md +3 -4
  46. package/lib/host-packages/claude/openplanr/skills/design-review/references/utilities.md +78 -38
  47. package/lib/host-packages/claude/openplanr/skills/plan/SKILL.md +7 -3
  48. package/lib/host-packages/cursor/openplanr/.openplanr-content.json +23 -339
  49. package/lib/host-packages/cursor/openplanr/manifest.json +1 -1
  50. package/lib/host-packages/cursor/openplanr/rules/planr-artifact/references/design-sharing.md +2 -2
  51. package/lib/host-packages/cursor/openplanr/rules/planr-design/references/design-system.md +2 -2
  52. package/lib/host-packages/cursor/openplanr/rules/planr-design/references/generation.md +3 -3
  53. package/lib/host-packages/cursor/openplanr/rules/planr-design/references/hosted-canvas.md +6 -3
  54. package/lib/host-packages/cursor/openplanr/rules/planr-design/references/review.md +6 -6
  55. package/lib/host-packages/cursor/openplanr/rules/planr-design/references/team-review.md +3 -4
  56. package/lib/host-packages/cursor/openplanr/rules/planr-design/references/utilities.md +78 -38
  57. package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/references/design-system.md +2 -2
  58. package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/references/generation.md +3 -3
  59. package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/references/review.md +6 -6
  60. package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/references/team-review.md +3 -4
  61. package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/references/utilities.md +78 -38
  62. package/lib/host-packages/cursor/openplanr/rules/planr-design-loop.mdc +15 -7
  63. package/lib/host-packages/cursor/openplanr/rules/planr-design-review/references/design-system.md +2 -2
  64. package/lib/host-packages/cursor/openplanr/rules/planr-design-review/references/generation.md +3 -3
  65. package/lib/host-packages/cursor/openplanr/rules/planr-design-review/references/review.md +6 -6
  66. package/lib/host-packages/cursor/openplanr/rules/planr-design-review/references/team-review.md +3 -4
  67. package/lib/host-packages/cursor/openplanr/rules/planr-design-review/references/utilities.md +78 -38
  68. package/lib/host-packages/cursor/openplanr/rules/planr-design-review.mdc +25 -5
  69. package/lib/host-packages/cursor/openplanr/rules/planr-design.mdc +23 -12
  70. package/lib/host-packages/cursor/openplanr/rules/planr-plan.mdc +7 -3
  71. package/lib/host-packages/openai/.claude-plugin/marketplace.json +2 -2
  72. package/lib/host-packages/openai/openplanr/.codex-plugin/plugin.json +1 -1
  73. package/lib/host-packages/openai/openplanr/.openplanr-content.json +27 -343
  74. package/lib/host-packages/openai/openplanr/skills/artifact/references/design-sharing.md +2 -2
  75. package/lib/host-packages/openai/openplanr/skills/design/SKILL.md +23 -12
  76. package/lib/host-packages/openai/openplanr/skills/design/openplanr.skill.json +0 -11
  77. package/lib/host-packages/openai/openplanr/skills/design/references/design-system.md +2 -2
  78. package/lib/host-packages/openai/openplanr/skills/design/references/generation.md +3 -3
  79. package/lib/host-packages/openai/openplanr/skills/design/references/hosted-canvas.md +6 -3
  80. package/lib/host-packages/openai/openplanr/skills/design/references/review.md +6 -6
  81. package/lib/host-packages/openai/openplanr/skills/design/references/team-review.md +3 -4
  82. package/lib/host-packages/openai/openplanr/skills/design/references/utilities.md +78 -38
  83. package/lib/host-packages/openai/openplanr/skills/design-loop/SKILL.md +15 -7
  84. package/lib/host-packages/openai/openplanr/skills/design-loop/openplanr.skill.json +0 -11
  85. package/lib/host-packages/openai/openplanr/skills/design-loop/references/design-system.md +2 -2
  86. package/lib/host-packages/openai/openplanr/skills/design-loop/references/generation.md +3 -3
  87. package/lib/host-packages/openai/openplanr/skills/design-loop/references/review.md +6 -6
  88. package/lib/host-packages/openai/openplanr/skills/design-loop/references/team-review.md +3 -4
  89. package/lib/host-packages/openai/openplanr/skills/design-loop/references/utilities.md +78 -38
  90. package/lib/host-packages/openai/openplanr/skills/design-review/SKILL.md +25 -5
  91. package/lib/host-packages/openai/openplanr/skills/design-review/openplanr.skill.json +0 -11
  92. package/lib/host-packages/openai/openplanr/skills/design-review/references/design-system.md +2 -2
  93. package/lib/host-packages/openai/openplanr/skills/design-review/references/generation.md +3 -3
  94. package/lib/host-packages/openai/openplanr/skills/design-review/references/review.md +6 -6
  95. package/lib/host-packages/openai/openplanr/skills/design-review/references/team-review.md +3 -4
  96. package/lib/host-packages/openai/openplanr/skills/design-review/references/utilities.md +78 -38
  97. package/lib/host-packages/openai/openplanr/skills/plan/SKILL.md +7 -3
  98. package/lib/host-packages/openai/openplanr/skills/plan/openplanr.skill.json +0 -11
  99. package/package.json +2 -2
  100. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-artifact-shell.mjs +0 -1772
  101. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-document.mjs +0 -2937
  102. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-entities.mjs +0 -439
  103. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-handoff.mjs +0 -2370
  104. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-loopback-server.mjs +0 -359
  105. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-parse5-parser.mjs +0 -3970
  106. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-parse5-tokenizer.mjs +0 -3501
  107. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-review-state-store.mjs +0 -45
  108. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-review.mjs +0 -3376
  109. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-runtime-home.mjs +0 -38
  110. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-sandbox-guards.mjs +0 -1102
  111. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-share-cli-bridge.mjs +0 -321
  112. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-shared-artifact-support-dependencies-design-support-protocol-contracts-d707e49b.mjs +0 -1652
  113. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-shared-artifact-support-protocol-contracts-9f6918b0.mjs +0 -1037
  114. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-shared-design-support-0bac378d.mjs +0 -137
  115. package/lib/host-packages/claude/openplanr/runtime/design/scripts/design.mjs +0 -1500
  116. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/notices/THIRD_PARTY_NOTICES.txt +0 -2023
  117. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/lib/artifact/ui/studio-shell.css +0 -2
  118. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/01-module-runtime.js +0 -18
  119. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/02-artifact-review-ui.js +0 -814
  120. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/03-protocol-contracts.js +0 -10
  121. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/04-artifact-review-ui.js +0 -2568
  122. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources.json +0 -60
  123. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/package.json +0 -65
  124. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/enhancements.css +0 -388
  125. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/handoff-center.css +0 -81
  126. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/share.css +0 -1
  127. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/share.js +0 -139
  128. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.css +0 -1159
  129. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/01-module-runtime.js +0 -30
  130. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/02-react.js +0 -900
  131. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/03-react-dom-client-fiber-foundations.js +0 -2261
  132. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/04-react-dom-client-reconciliation-hooks.js +0 -1595
  133. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/05-react-dom-client-render-phases.js +0 -1467
  134. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/06-react-dom-client-commit-effects.js +0 -1860
  135. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/07-react-dom-client-work-loop-scheduling.js +0 -914
  136. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/08-react-dom-client-dom-events-properties-hydration.js +0 -2578
  137. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/09-react-dom-client-public-roots.js +0 -582
  138. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/10-react.js +0 -17
  139. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/11-artifact-review-ui.js +0 -12
  140. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/12-protocol-contracts.js +0 -285
  141. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/13-design-review-experience.js +0 -2773
  142. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/14-studio-shell.js +0 -134
  143. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/15-canvas-engine.js +0 -2207
  144. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/16-design-review-experience.js +0 -16
  145. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/17-studio-shell.js +0 -8
  146. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/18-interface-primitives.js +0 -3883
  147. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/19-interface-primitives.js +0 -966
  148. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/20-studio-shell.js +0 -520
  149. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/21-design-studio.js +0 -1597
  150. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources.json +0 -311
  151. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/package.json +0 -163
  152. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/registries/artifact-theme.json +0 -64
  153. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.0.0/design-manifest.schema.json +0 -90
  154. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.1.0/artifact-envelope.schema.json +0 -142
  155. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.1.0/artifact-review.schema.json +0 -97
  156. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.10.0/design-review-bundle.schema.json +0 -584
  157. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.10.0/design-review-context.schema.json +0 -191
  158. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.10.0/design-review-handoff.schema.json +0 -407
  159. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.10.0/design-review-metadata-payload.schema.json +0 -106
  160. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-handoff-readiness.schema.json +0 -398
  161. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-implementation-handoff.schema.json +0 -518
  162. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-planning-lineage.schema.json +0 -106
  163. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-review-metadata-payload.schema.json +0 -107
  164. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.14.0/artifact-theme.schema.json +0 -316
  165. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.16.0/artifact-envelope.schema.json +0 -408
  166. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.16.0/design-review-bundle.schema.json +0 -590
  167. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.22.0/design-document.schema.json +0 -506
  168. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.22.0/design-review-bundle.schema.json +0 -822
  169. package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0/design-review-bundle.schema.json +0 -346
  170. package/lib/host-packages/claude/openplanr/runtime/plan/scripts/design-protocol-contracts.mjs +0 -1232
  171. package/lib/host-packages/claude/openplanr/runtime/plan/scripts/design.mjs +0 -2611
  172. package/lib/host-packages/claude/openplanr/runtime/plan/scripts/runtime/notices/THIRD_PARTY_NOTICES.txt +0 -21
  173. package/lib/host-packages/claude/openplanr/runtime/plan/scripts/runtime/packages/protocol/package.json +0 -163
  174. package/lib/host-packages/claude/openplanr/runtime/plan/scripts/runtime/packages/protocol/schemas/v1.1.0/artifact-review.schema.json +0 -97
  175. package/lib/host-packages/claude/openplanr/skills/design/scripts/design.mjs +0 -12
  176. package/lib/host-packages/claude/openplanr/skills/design-loop/scripts/design.mjs +0 -12
  177. package/lib/host-packages/claude/openplanr/skills/design-review/scripts/design.mjs +0 -12
  178. package/lib/host-packages/claude/openplanr/skills/plan/scripts/design.mjs +0 -12
  179. package/lib/host-packages/cursor/openplanr/rules/planr-design/scripts/design.mjs +0 -12
  180. package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/scripts/design.mjs +0 -12
  181. package/lib/host-packages/cursor/openplanr/rules/planr-design-review/scripts/design.mjs +0 -12
  182. package/lib/host-packages/cursor/openplanr/rules/planr-plan/scripts/design.mjs +0 -12
  183. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-artifact-shell.mjs +0 -1772
  184. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-document.mjs +0 -2937
  185. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-entities.mjs +0 -439
  186. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-handoff.mjs +0 -2370
  187. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-loopback-server.mjs +0 -359
  188. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-parse5-parser.mjs +0 -3970
  189. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-parse5-tokenizer.mjs +0 -3501
  190. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-review-state-store.mjs +0 -45
  191. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-review.mjs +0 -3376
  192. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-runtime-home.mjs +0 -38
  193. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-sandbox-guards.mjs +0 -1102
  194. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-share-cli-bridge.mjs +0 -321
  195. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-shared-artifact-support-dependencies-design-support-protocol-contracts-d707e49b.mjs +0 -1652
  196. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-shared-artifact-support-protocol-contracts-9f6918b0.mjs +0 -1037
  197. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-shared-design-support-0bac378d.mjs +0 -137
  198. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design.mjs +0 -1500
  199. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/notices/THIRD_PARTY_NOTICES.txt +0 -2023
  200. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/lib/artifact/ui/studio-shell.css +0 -2
  201. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/01-module-runtime.js +0 -18
  202. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/02-artifact-review-ui.js +0 -814
  203. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/03-protocol-contracts.js +0 -10
  204. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/04-artifact-review-ui.js +0 -2568
  205. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources.json +0 -60
  206. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/package.json +0 -65
  207. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/enhancements.css +0 -388
  208. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/handoff-center.css +0 -81
  209. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/share.css +0 -1
  210. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/share.js +0 -139
  211. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.css +0 -1159
  212. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/01-module-runtime.js +0 -30
  213. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/02-react.js +0 -900
  214. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/03-react-dom-client-fiber-foundations.js +0 -2261
  215. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/04-react-dom-client-reconciliation-hooks.js +0 -1595
  216. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/05-react-dom-client-render-phases.js +0 -1467
  217. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/06-react-dom-client-commit-effects.js +0 -1860
  218. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/07-react-dom-client-work-loop-scheduling.js +0 -914
  219. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/08-react-dom-client-dom-events-properties-hydration.js +0 -2578
  220. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/09-react-dom-client-public-roots.js +0 -582
  221. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/10-react.js +0 -17
  222. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/11-artifact-review-ui.js +0 -12
  223. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/12-protocol-contracts.js +0 -285
  224. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/13-design-review-experience.js +0 -2773
  225. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/14-studio-shell.js +0 -134
  226. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/15-canvas-engine.js +0 -2207
  227. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/16-design-review-experience.js +0 -16
  228. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/17-studio-shell.js +0 -8
  229. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/18-interface-primitives.js +0 -3883
  230. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/19-interface-primitives.js +0 -966
  231. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/20-studio-shell.js +0 -520
  232. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/21-design-studio.js +0 -1597
  233. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources.json +0 -311
  234. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/package.json +0 -163
  235. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/registries/artifact-theme.json +0 -64
  236. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.0.0/design-manifest.schema.json +0 -90
  237. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.1.0/artifact-envelope.schema.json +0 -142
  238. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.1.0/artifact-review.schema.json +0 -97
  239. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.10.0/design-review-bundle.schema.json +0 -584
  240. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.10.0/design-review-context.schema.json +0 -191
  241. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.10.0/design-review-handoff.schema.json +0 -407
  242. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.10.0/design-review-metadata-payload.schema.json +0 -106
  243. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-handoff-readiness.schema.json +0 -398
  244. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-implementation-handoff.schema.json +0 -518
  245. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-planning-lineage.schema.json +0 -106
  246. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-review-metadata-payload.schema.json +0 -107
  247. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.14.0/artifact-theme.schema.json +0 -316
  248. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.16.0/artifact-envelope.schema.json +0 -408
  249. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.16.0/design-review-bundle.schema.json +0 -590
  250. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.22.0/design-document.schema.json +0 -506
  251. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.22.0/design-review-bundle.schema.json +0 -822
  252. package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0/design-review-bundle.schema.json +0 -346
  253. package/lib/host-packages/cursor/openplanr/runtime/plan/scripts/design-protocol-contracts.mjs +0 -1232
  254. package/lib/host-packages/cursor/openplanr/runtime/plan/scripts/design.mjs +0 -2611
  255. package/lib/host-packages/cursor/openplanr/runtime/plan/scripts/runtime/notices/THIRD_PARTY_NOTICES.txt +0 -21
  256. package/lib/host-packages/cursor/openplanr/runtime/plan/scripts/runtime/packages/protocol/package.json +0 -163
  257. package/lib/host-packages/cursor/openplanr/runtime/plan/scripts/runtime/packages/protocol/schemas/v1.1.0/artifact-review.schema.json +0 -97
  258. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-artifact-shell.mjs +0 -1772
  259. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-document.mjs +0 -2937
  260. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-entities.mjs +0 -439
  261. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-handoff.mjs +0 -2370
  262. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-loopback-server.mjs +0 -359
  263. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-parse5-parser.mjs +0 -3970
  264. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-parse5-tokenizer.mjs +0 -3501
  265. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-review-state-store.mjs +0 -45
  266. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-review.mjs +0 -3376
  267. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-runtime-home.mjs +0 -38
  268. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-sandbox-guards.mjs +0 -1102
  269. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-share-cli-bridge.mjs +0 -321
  270. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-shared-artifact-support-dependencies-design-support-protocol-contracts-d707e49b.mjs +0 -1652
  271. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-shared-artifact-support-protocol-contracts-9f6918b0.mjs +0 -1037
  272. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-shared-design-support-0bac378d.mjs +0 -137
  273. package/lib/host-packages/openai/openplanr/runtime/design/scripts/design.mjs +0 -1500
  274. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/notices/THIRD_PARTY_NOTICES.txt +0 -2023
  275. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/lib/artifact/ui/studio-shell.css +0 -2
  276. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/01-module-runtime.js +0 -18
  277. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/02-artifact-review-ui.js +0 -814
  278. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/03-protocol-contracts.js +0 -10
  279. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/04-artifact-review-ui.js +0 -2568
  280. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources.json +0 -60
  281. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/package.json +0 -65
  282. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/enhancements.css +0 -388
  283. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/handoff-center.css +0 -81
  284. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/share.css +0 -1
  285. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/share.js +0 -139
  286. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.css +0 -1159
  287. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/01-module-runtime.js +0 -30
  288. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/02-react.js +0 -900
  289. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/03-react-dom-client-fiber-foundations.js +0 -2261
  290. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/04-react-dom-client-reconciliation-hooks.js +0 -1595
  291. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/05-react-dom-client-render-phases.js +0 -1467
  292. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/06-react-dom-client-commit-effects.js +0 -1860
  293. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/07-react-dom-client-work-loop-scheduling.js +0 -914
  294. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/08-react-dom-client-dom-events-properties-hydration.js +0 -2578
  295. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/09-react-dom-client-public-roots.js +0 -582
  296. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/10-react.js +0 -17
  297. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/11-artifact-review-ui.js +0 -12
  298. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/12-protocol-contracts.js +0 -285
  299. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/13-design-review-experience.js +0 -2773
  300. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/14-studio-shell.js +0 -134
  301. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/15-canvas-engine.js +0 -2207
  302. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/16-design-review-experience.js +0 -16
  303. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/17-studio-shell.js +0 -8
  304. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/18-interface-primitives.js +0 -3883
  305. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/19-interface-primitives.js +0 -966
  306. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/20-studio-shell.js +0 -520
  307. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources/21-design-studio.js +0 -1597
  308. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/studio.js.sources.json +0 -311
  309. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/package.json +0 -163
  310. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/registries/artifact-theme.json +0 -64
  311. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.0.0/design-manifest.schema.json +0 -90
  312. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.1.0/artifact-envelope.schema.json +0 -142
  313. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.1.0/artifact-review.schema.json +0 -97
  314. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.10.0/design-review-bundle.schema.json +0 -584
  315. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.10.0/design-review-context.schema.json +0 -191
  316. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.10.0/design-review-handoff.schema.json +0 -407
  317. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.10.0/design-review-metadata-payload.schema.json +0 -106
  318. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-handoff-readiness.schema.json +0 -398
  319. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-implementation-handoff.schema.json +0 -518
  320. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-planning-lineage.schema.json +0 -106
  321. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-review-metadata-payload.schema.json +0 -107
  322. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.14.0/artifact-theme.schema.json +0 -316
  323. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.16.0/artifact-envelope.schema.json +0 -408
  324. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.16.0/design-review-bundle.schema.json +0 -590
  325. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.22.0/design-document.schema.json +0 -506
  326. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.22.0/design-review-bundle.schema.json +0 -822
  327. package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0/design-review-bundle.schema.json +0 -346
  328. package/lib/host-packages/openai/openplanr/runtime/plan/scripts/design-protocol-contracts.mjs +0 -1232
  329. package/lib/host-packages/openai/openplanr/runtime/plan/scripts/design.mjs +0 -2611
  330. package/lib/host-packages/openai/openplanr/runtime/plan/scripts/runtime/notices/THIRD_PARTY_NOTICES.txt +0 -21
  331. package/lib/host-packages/openai/openplanr/runtime/plan/scripts/runtime/packages/protocol/package.json +0 -163
  332. package/lib/host-packages/openai/openplanr/runtime/plan/scripts/runtime/packages/protocol/schemas/v1.1.0/artifact-review.schema.json +0 -97
  333. package/lib/host-packages/openai/openplanr/skills/design/scripts/design.mjs +0 -12
  334. package/lib/host-packages/openai/openplanr/skills/design-loop/scripts/design.mjs +0 -12
  335. package/lib/host-packages/openai/openplanr/skills/design-review/scripts/design.mjs +0 -12
  336. package/lib/host-packages/openai/openplanr/skills/plan/scripts/design.mjs +0 -12
@@ -1,2370 +0,0 @@
1
- import {
2
- DESIGN_HANDOFF_CONTENT_SCHEMA,
3
- DESIGN_HANDOFF_SCHEMA,
4
- assertPlainData,
5
- assertReviewExperience,
6
- canonicalizeJson,
7
- deepFreeze,
8
- emptyReviewContext,
9
- reviewDigest,
10
- sha256Hex
11
- } from "./design-shared-artifact-support-dependencies-design-support-protocol-contracts-d707e49b.mjs";
12
- import {
13
- ARTIFACT_ERROR_CODES,
14
- PipelineError,
15
- canonicalSerialize,
16
- digestArtifactEnvelope,
17
- validateArtifactEnvelope,
18
- validateArtifactReview,
19
- validateJson
20
- } from "./design-shared-artifact-support-protocol-contracts-9f6918b0.mjs";
21
- import {
22
- assertDesignShareAvailable,
23
- getDesignShareStatus,
24
- publishDesignReviewMetadata
25
- } from "./design-share-cli-bridge.mjs";
26
- import {
27
- atomicJson,
28
- currentDesign,
29
- designSpecPath,
30
- hash,
31
- readJson
32
- } from "./design-shared-design-support-0bac378d.mjs";
33
- import {
34
- planrHome
35
- } from "./design-runtime-home.mjs";
36
- import {
37
- acquireStartLock
38
- } from "./design-loopback-server.mjs";
39
-
40
- // packages/design/lib/design/feedback-reader.mjs
41
- import { join as join2 } from "node:path";
42
-
43
- // packages/artifact/lib/artifact/import.mjs
44
- import {
45
- existsSync as existsSync2,
46
- lstatSync as lstatSync2,
47
- mkdirSync as mkdirSync2,
48
- readFileSync as readFileSync2,
49
- realpathSync,
50
- renameSync as renameSync2,
51
- rmSync as rmSync2,
52
- statSync as statSync2,
53
- writeFileSync as writeFileSync2
54
- } from "node:fs";
55
- import { homedir } from "node:os";
56
- import { dirname as dirname2, isAbsolute, join, relative, resolve } from "node:path";
57
-
58
- // packages/protocol/src/names.mjs
59
- var PLANNING_FOLDER = ".planr";
60
- var CLI_COMMAND = "openplanr";
61
-
62
- // packages/artifact/lib/artifact/internal/feedback.mjs
63
- var FEEDBACK_FILE = "feedback.json";
64
-
65
- // packages/artifact/lib/artifact/merge.mjs
66
- var ARTIFACT_REVIEW_STATE_VERSION = "1.0.0";
67
- var ARTIFACT_REVIEW_STATE_KIND = "artifact-review-state";
68
- var SHA256_RE = /^[a-f0-9]{64}$/;
69
- var ARTIFACT_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
70
- function conflict(message2, details = void 0) {
71
- throw new PipelineError(
72
- ARTIFACT_ERROR_CODES.MERGE_CONFLICT,
73
- message2,
74
- "Keep both reviews under different stable IDs or resolve the conflicting item explicitly.",
75
- details
76
- );
77
- }
78
- function assertUniqueReviewItemIds(review) {
79
- const pinIds = /* @__PURE__ */ new Set();
80
- for (const pin of review.pins ?? []) {
81
- if (pinIds.has(pin.id)) {
82
- throw new PipelineError(
83
- ARTIFACT_ERROR_CODES.REVIEW_INVALID,
84
- "Artifact review contains a duplicate pin ID."
85
- );
86
- }
87
- pinIds.add(pin.id);
88
- const replyIds = /* @__PURE__ */ new Set();
89
- for (const reply of pin.replies ?? []) {
90
- if (replyIds.has(reply.id)) {
91
- throw new PipelineError(
92
- ARTIFACT_ERROR_CODES.REVIEW_INVALID,
93
- "Artifact review contains a duplicate reply ID in one thread."
94
- );
95
- }
96
- replyIds.add(reply.id);
97
- }
98
- }
99
- return review;
100
- }
101
- function clone(value) {
102
- return structuredClone(value);
103
- }
104
- function same(a, b) {
105
- return canonicalSerialize(a) === canonicalSerialize(b);
106
- }
107
- function timeValue(value) {
108
- if (typeof value !== "string") return Number.NEGATIVE_INFINITY;
109
- const parsed = Date.parse(value);
110
- return Number.isFinite(parsed) ? parsed : Number.NEGATIVE_INFINITY;
111
- }
112
- function compareStable(a, b, idKey) {
113
- const aTime = a.createdAt ?? a.updatedAt ?? "";
114
- const bTime = b.createdAt ?? b.updatedAt ?? "";
115
- return aTime.localeCompare(bTime) || String(a[idKey]).localeCompare(String(b[idKey]));
116
- }
117
- function replyMap(replies) {
118
- const map = /* @__PURE__ */ new Map();
119
- for (const reply of replies ?? []) {
120
- const previous = map.get(reply.id);
121
- if (previous && !same(previous, reply)) {
122
- conflict("A reply ID refers to different immutable reply content.", { entity: "reply" });
123
- }
124
- map.set(reply.id, clone(reply));
125
- }
126
- return map;
127
- }
128
- function mergeReplies(stored = [], incoming = []) {
129
- const merged = replyMap(stored);
130
- for (const reply of incoming) {
131
- const previous = merged.get(reply.id);
132
- if (previous && !same(previous, reply)) {
133
- conflict("A reply ID refers to different immutable reply content.", { entity: "reply" });
134
- }
135
- if (!previous) merged.set(reply.id, clone(reply));
136
- }
137
- return [...merged.values()].sort((a, b) => compareStable(a, b, "id"));
138
- }
139
- var immutablePin = (pin) => ({
140
- id: pin.id,
141
- author: pin.author,
142
- artifactId: pin.artifactId,
143
- ...pin.variant === void 0 ? {} : { variant: pin.variant },
144
- region: pin.region,
145
- viewport: pin.viewport,
146
- ...pin.anchor === void 0 ? {} : { anchor: pin.anchor },
147
- createdAt: pin.createdAt
148
- });
149
- var mutablePin = (pin) => ({
150
- intent: pin.intent,
151
- status: pin.status,
152
- comment: pin.comment
153
- });
154
- function mergePin(stored, incoming) {
155
- if (!same(immutablePin(stored), immutablePin(incoming))) {
156
- conflict("A pin ID refers to different immutable geometry, author, or artifact content.", {
157
- entity: "pin"
158
- });
159
- }
160
- const replies = mergeReplies(stored.replies, incoming.replies);
161
- const storedTime = timeValue(stored.updatedAt);
162
- const incomingTime = timeValue(incoming.updatedAt);
163
- let selected = stored;
164
- if (incomingTime > storedTime) selected = incoming;
165
- if (incomingTime === storedTime && !same(mutablePin(stored), mutablePin(incoming))) {
166
- conflict("A pin has divergent mutable content at the same update time.", { entity: "pin" });
167
- }
168
- return { ...clone(selected), replies };
169
- }
170
- function mergePins(stored = [], incoming = []) {
171
- const merged = /* @__PURE__ */ new Map();
172
- for (const pin of stored) merged.set(pin.id, clone(pin));
173
- for (const pin of incoming) {
174
- const previous = merged.get(pin.id);
175
- merged.set(pin.id, previous ? mergePin(previous, pin) : clone(pin));
176
- }
177
- return [...merged.values()].sort((a, b) => compareStable(a, b, "id"));
178
- }
179
- var immutableReview = (review) => ({
180
- schemaVersion: review.schemaVersion,
181
- reviewId: review.reviewId,
182
- reviewOf: review.reviewOf,
183
- ...review.createdAt === void 0 ? {} : { createdAt: review.createdAt }
184
- });
185
- var mutableReview = (review) => ({ decision: review.decision, overall: review.overall });
186
- function mergeArtifactReviews(stored, incoming) {
187
- validateArtifactReview(stored);
188
- validateArtifactReview(incoming);
189
- assertUniqueReviewItemIds(stored);
190
- assertUniqueReviewItemIds(incoming);
191
- if (stored.reviewId !== incoming.reviewId) {
192
- conflict("Only reviews with the same review ID can be revision-merged.", {
193
- entity: "review"
194
- });
195
- }
196
- if (same(stored, incoming)) return stored;
197
- if (!same(immutableReview(stored), immutableReview(incoming))) {
198
- conflict("A review ID refers to a different digest or creation identity.", {
199
- entity: "review"
200
- });
201
- }
202
- const storedTime = timeValue(stored.updatedAt);
203
- const incomingTime = timeValue(incoming.updatedAt);
204
- let selected = stored;
205
- if (incomingTime > storedTime) selected = incoming;
206
- if (incomingTime === storedTime && !same(mutableReview(stored), mutableReview(incoming))) {
207
- conflict("A review has divergent verdict or overall feedback at the same update time.", {
208
- entity: "review"
209
- });
210
- }
211
- const merged = {
212
- ...clone(selected),
213
- pins: mergePins(stored.pins, incoming.pins)
214
- };
215
- validateArtifactReview(merged);
216
- return merged;
217
- }
218
- function validateReviewLedger(ledger) {
219
- if (!ledger || typeof ledger !== "object" || Array.isArray(ledger) || ledger.schemaVersion !== ARTIFACT_REVIEW_STATE_VERSION || ledger.kind !== ARTIFACT_REVIEW_STATE_KIND || !ARTIFACT_ID_RE.test(ledger.artifactId ?? "") || !SHA256_RE.test(ledger.currentReviewOf ?? "") || !Array.isArray(ledger.reviews)) {
220
- throw new PipelineError(
221
- ARTIFACT_ERROR_CODES.REVIEW_INVALID,
222
- "Artifact review state is not a valid versioned review ledger."
223
- );
224
- }
225
- const ids = /* @__PURE__ */ new Set();
226
- for (const entry of ledger.reviews) {
227
- if (!entry || typeof entry !== "object" || Array.isArray(entry) || typeof entry.stale !== "boolean" || !entry.review) {
228
- throw new PipelineError(
229
- ARTIFACT_ERROR_CODES.REVIEW_INVALID,
230
- "Artifact review ledger entry is invalid."
231
- );
232
- }
233
- validateArtifactReview(entry.review);
234
- assertUniqueReviewItemIds(entry.review);
235
- if (ids.has(entry.review.reviewId)) {
236
- throw new PipelineError(
237
- ARTIFACT_ERROR_CODES.REVIEW_INVALID,
238
- "Artifact review ledger IDs must be unique."
239
- );
240
- }
241
- ids.add(entry.review.reviewId);
242
- }
243
- return ledger;
244
- }
245
- function createReviewLedger({ artifactId, currentReviewOf, reviews = [] } = {}) {
246
- const ledger = {
247
- schemaVersion: ARTIFACT_REVIEW_STATE_VERSION,
248
- kind: ARTIFACT_REVIEW_STATE_KIND,
249
- artifactId,
250
- currentReviewOf,
251
- reviews: reviews.map((entry) => ({ review: clone(entry.review), stale: Boolean(entry.stale) }))
252
- };
253
- ledger.reviews.sort((a, b) => compareStable(a.review, b.review, "reviewId"));
254
- return validateReviewLedger(ledger);
255
- }
256
- function mergeReviewLedger(ledger, incoming, { stale = false } = {}) {
257
- validateReviewLedger(ledger);
258
- const reviews = Array.isArray(incoming) ? incoming : [incoming];
259
- const byId = new Map(
260
- ledger.reviews.map((entry) => [
261
- entry.review.reviewId,
262
- {
263
- review: clone(entry.review),
264
- stale: entry.stale
265
- }
266
- ])
267
- );
268
- for (const review of reviews) {
269
- validateArtifactReview(review);
270
- const previous = byId.get(review.reviewId);
271
- if (previous) {
272
- byId.set(review.reviewId, {
273
- review: mergeArtifactReviews(previous.review, review),
274
- stale: previous.stale || Boolean(stale)
275
- });
276
- } else {
277
- byId.set(review.reviewId, { review: clone(review), stale: Boolean(stale) });
278
- }
279
- }
280
- const merged = createReviewLedger({
281
- artifactId: ledger.artifactId,
282
- currentReviewOf: ledger.currentReviewOf,
283
- reviews: [...byId.values()]
284
- });
285
- return same(ledger, merged) ? ledger : merged;
286
- }
287
- function effectiveReviewDecision(value) {
288
- const entries = Array.isArray(value) ? value : value?.reviews ?? [];
289
- const decisions = entries.map((entry) => entry.review?.decision ?? entry.decision);
290
- if (decisions.includes("changes_requested")) return "changes_requested";
291
- if (decisions.includes("pending")) return "pending";
292
- return decisions.length > 0 && decisions.every((decision) => decision === "approved") ? "approved" : "pending";
293
- }
294
-
295
- // packages/artifact/lib/artifact/review.mjs
296
- import { randomBytes, randomUUID } from "node:crypto";
297
- import {
298
- existsSync,
299
- lstatSync,
300
- mkdirSync,
301
- readFileSync,
302
- renameSync,
303
- rmSync,
304
- statSync,
305
- writeFileSync
306
- } from "node:fs";
307
- import { dirname } from "node:path";
308
- var ARTIFACT_REVIEW_MAX_STATE_BYTES = 5 * 1024 * 1024;
309
- var reviewPathQueues = /* @__PURE__ */ new Map();
310
- function finalEntry(path, fs = { lstatSync }) {
311
- try {
312
- return fs.lstatSync(path);
313
- } catch (error) {
314
- if (error?.code === "ENOENT") return null;
315
- throw error;
316
- }
317
- }
318
- function clone2(value) {
319
- return structuredClone(value);
320
- }
321
- function canonicalObject(value) {
322
- if (Array.isArray(value)) return value.map(canonicalObject);
323
- if (!value || typeof value !== "object") return value;
324
- return Object.fromEntries(
325
- Object.keys(value).sort().map((key) => [key, canonicalObject(value[key])])
326
- );
327
- }
328
- function stableItemSort(a, b) {
329
- return String(a.createdAt ?? a.updatedAt ?? "").localeCompare(
330
- String(b.createdAt ?? b.updatedAt ?? "")
331
- ) || String(a.id ?? a.reviewId).localeCompare(String(b.id ?? b.reviewId));
332
- }
333
- function normalizeArtifactReview(review) {
334
- let normalized;
335
- try {
336
- normalized = clone2(review);
337
- } catch {
338
- throw new PipelineError(
339
- ARTIFACT_ERROR_CODES.REVIEW_INVALID,
340
- "Artifact review is not cloneable."
341
- );
342
- }
343
- try {
344
- validateArtifactReview(normalized);
345
- assertUniqueReviewItemIds(normalized);
346
- } catch (error) {
347
- if (error instanceof PipelineError) {
348
- throw new PipelineError(
349
- ARTIFACT_ERROR_CODES.REVIEW_INVALID,
350
- "Artifact review does not satisfy the Protocol v1.1 review contract.",
351
- "",
352
- { issues: error.details?.issues ?? [] }
353
- );
354
- }
355
- throw error;
356
- }
357
- normalized.pins = normalized.pins.map((pin) => ({
358
- ...pin,
359
- replies: [...pin.replies].sort(stableItemSort)
360
- })).sort(stableItemSort);
361
- validateArtifactReview(normalized);
362
- return normalized;
363
- }
364
- function serializeReviewState(value) {
365
- const normalized = value?.kind === "artifact-review-state" ? validateReviewLedger(clone2(value)) : normalizeArtifactReview(value);
366
- return `${JSON.stringify(canonicalObject(normalized), null, 2)}
367
- `;
368
- }
369
- function readArtifactReviewState(path, { allowMissing = false } = {}) {
370
- const entry = finalEntry(path);
371
- if (!entry) {
372
- if (allowMissing) return null;
373
- throw new PipelineError(
374
- ARTIFACT_ERROR_CODES.REVIEW_IMPORT,
375
- "Artifact review state does not exist."
376
- );
377
- }
378
- try {
379
- if (entry.isSymbolicLink() || !entry.isFile()) {
380
- throw new PipelineError(
381
- ARTIFACT_ERROR_CODES.SYMLINK_ESCAPE,
382
- "Artifact review state destination is not a regular file."
383
- );
384
- }
385
- if (statSync(path).size > ARTIFACT_REVIEW_MAX_STATE_BYTES) {
386
- throw new PipelineError(
387
- ARTIFACT_ERROR_CODES.REQUEST_LIMIT,
388
- `Artifact review state exceeds ${ARTIFACT_REVIEW_MAX_STATE_BYTES} bytes.`
389
- );
390
- }
391
- return validateReviewLedger(JSON.parse(readFileSync(path, "utf8")));
392
- } catch (error) {
393
- if (error instanceof PipelineError) throw error;
394
- throw new PipelineError(
395
- ARTIFACT_ERROR_CODES.REVIEW_INVALID,
396
- "Artifact review state is malformed."
397
- );
398
- }
399
- }
400
- function writeArtifactReviewState(path, ledger, { fileSystem = {}, suffix = `${process.pid}.${randomBytes(8).toString("hex")}` } = {}) {
401
- validateReviewLedger(ledger);
402
- const fs = { existsSync, lstatSync, mkdirSync, writeFileSync, renameSync, rmSync, ...fileSystem };
403
- const temporary = `${path}.${suffix}.tmp`;
404
- const serialized = serializeReviewState(ledger);
405
- if (Buffer.byteLength(serialized, "utf8") > ARTIFACT_REVIEW_MAX_STATE_BYTES) {
406
- throw new PipelineError(
407
- ARTIFACT_ERROR_CODES.REQUEST_LIMIT,
408
- `Artifact review state exceeds ${ARTIFACT_REVIEW_MAX_STATE_BYTES} bytes.`
409
- );
410
- }
411
- try {
412
- const initial = finalEntry(path, fs);
413
- if (initial) {
414
- const target = initial;
415
- if (target.isSymbolicLink() || !target.isFile()) throw new Error("unsafe final target");
416
- }
417
- fs.mkdirSync(dirname(path), { recursive: true, mode: 448 });
418
- fs.writeFileSync(temporary, serialized, { mode: 384, flag: "wx" });
419
- const final = finalEntry(path, fs);
420
- if (final) {
421
- const target = final;
422
- if (target.isSymbolicLink() || !target.isFile()) throw new Error("unsafe final target");
423
- }
424
- fs.renameSync(temporary, path);
425
- } catch {
426
- try {
427
- fs.rmSync(temporary, { force: true });
428
- } catch {
429
- }
430
- throw new PipelineError(
431
- ARTIFACT_ERROR_CODES.REVIEW_WRITE,
432
- "Artifact review state could not be written atomically.",
433
- "Check destination permissions and retry; the previous review state was left unchanged."
434
- );
435
- }
436
- return ledger;
437
- }
438
- function withArtifactReviewLock(path, action3) {
439
- const previous = reviewPathQueues.get(path) ?? Promise.resolve();
440
- const run = async () => {
441
- let release;
442
- try {
443
- release = await acquireStartLock(`${path}.lock`, { timeout: 15e3, stale: 3e4 });
444
- return await action3();
445
- } catch (error) {
446
- if (error instanceof PipelineError) throw error;
447
- throw new PipelineError(
448
- ARTIFACT_ERROR_CODES.REVIEW_WRITE,
449
- "Artifact review state is busy or its cross-process lock is unavailable.",
450
- "Wait for the active review write to finish, then retry."
451
- );
452
- } finally {
453
- release?.();
454
- }
455
- };
456
- const operation = previous.then(run, run);
457
- const tail = operation.then(
458
- () => void 0,
459
- () => void 0
460
- );
461
- reviewPathQueues.set(path, tail);
462
- tail.finally(() => {
463
- if (reviewPathQueues.get(path) === tail) reviewPathQueues.delete(path);
464
- });
465
- return operation;
466
- }
467
- function normalizedExportInput(value) {
468
- if (value?.kind === "artifact-review-state") return validateReviewLedger(clone2(value));
469
- if (value?.review && value?.artifacts) return normalizeArtifactReview(value.review);
470
- return normalizeArtifactReview(value);
471
- }
472
- function markdownText(value) {
473
- return String(value ?? "").replace(/\r\n?/g, "\n").trim();
474
- }
475
- function renderPinMarkdown(pin, number) {
476
- const region = `${pin.region.x}, ${pin.region.y}, ${pin.region.w}, ${pin.region.h}`;
477
- const lines = [
478
- `### ${number}. ${pin.intent.toUpperCase()} \xB7 ${pin.status}`,
479
- "",
480
- `- Pin: \`${pin.id}\``,
481
- `- Artifact: \`${pin.artifactId}\`${pin.variant ? ` \xB7 variant \`${pin.variant}\`` : ""}`,
482
- `- Author: ${pin.author.name}`,
483
- `- Region: \`${region}\` at ${pin.viewport.width}\xD7${pin.viewport.height}`,
484
- ...pin.anchor ? [
485
- `- Anchor: \`${pin.anchor.planrId}\`${pin.anchor.screen ? ` \xB7 screen \`${pin.anchor.screen}\`` : ""}`
486
- ] : [],
487
- `- Updated: ${pin.updatedAt}`,
488
- "",
489
- markdownText(pin.comment)
490
- ];
491
- if (pin.replies.length > 0) {
492
- lines.push("", "#### Thread", "");
493
- for (const reply of pin.replies) {
494
- lines.push(`- **${reply.author.name}** (${reply.createdAt}): ${markdownText(reply.comment)}`);
495
- }
496
- }
497
- return lines.join("\n");
498
- }
499
- function reviewMarkdown(review, headingLevel = 2, stale = false) {
500
- const mark = "#".repeat(headingLevel);
501
- const lines = [
502
- `${mark} Review ${review.reviewId}${stale ? " \xB7 STALE" : ""}`,
503
- "",
504
- `- Digest: \`${review.reviewOf}\``,
505
- `- Decision: **${review.decision}**`,
506
- `- Pins: ${review.pins.length}`,
507
- ...review.createdAt ? [`- Created: ${review.createdAt}`] : [],
508
- ...review.updatedAt ? [`- Updated: ${review.updatedAt}`] : [],
509
- "",
510
- `${mark}# Overall feedback`,
511
- "",
512
- markdownText(review.overall) || "_No overall feedback._"
513
- ];
514
- if (review.pins.length > 0) {
515
- lines.push("", `${mark}# Pins`, "");
516
- review.pins.forEach((pin, index) => lines.push(renderPinMarkdown(pin, index + 1), ""));
517
- if (lines.at(-1) === "") lines.pop();
518
- }
519
- return lines.join("\n");
520
- }
521
- function exportArtifactReview(value, { format = "json" } = {}) {
522
- let normalized;
523
- try {
524
- normalized = normalizedExportInput(value);
525
- } catch (error) {
526
- if (error instanceof PipelineError) throw error;
527
- throw new PipelineError(
528
- ARTIFACT_ERROR_CODES.REVIEW_EXPORT,
529
- "Artifact review cannot be exported."
530
- );
531
- }
532
- if (format === "json") return serializeReviewState(normalized);
533
- if (format !== "markdown") {
534
- throw new PipelineError(
535
- ARTIFACT_ERROR_CODES.REVIEW_EXPORT,
536
- "Artifact review export format must be json or markdown."
537
- );
538
- }
539
- if (normalized.kind !== "artifact-review-state") {
540
- return `# OpenPlanr Artifact Review
541
-
542
- ${reviewMarkdown(normalized)}
543
- `;
544
- }
545
- const lines = [
546
- "# OpenPlanr Artifact Reviews",
547
- "",
548
- `- Artifact: \`${normalized.artifactId}\``,
549
- `- Current digest: \`${normalized.currentReviewOf}\``,
550
- `- Effective decision: **${effectiveReviewDecision(normalized)}**`,
551
- `- Reviews: ${normalized.reviews.length}`
552
- ];
553
- for (const entry of normalized.reviews) {
554
- lines.push("", reviewMarkdown(entry.review, 2, entry.stale));
555
- }
556
- return `${lines.join("\n")}
557
- `;
558
- }
559
-
560
- // packages/artifact/lib/artifact/import.mjs
561
- var ARTIFACT_ID_RE2 = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
562
- function pathError(code, message2) {
563
- throw new PipelineError(
564
- code,
565
- message2,
566
- "Choose a real, non-symlinked project or user review destination."
567
- );
568
- }
569
- function pathEntry(path, fs = { lstatSync: lstatSync2 }) {
570
- try {
571
- return fs.lstatSync(path);
572
- } catch (error) {
573
- if (error?.code === "ENOENT") return null;
574
- throw error;
575
- }
576
- }
577
- function inside(base, candidate) {
578
- const rel = relative(base, candidate);
579
- return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
580
- }
581
- function parseablePlanrConfig(root) {
582
- const path = join(root, PLANNING_FOLDER, "config.json");
583
- if (!existsSync2(path)) return false;
584
- try {
585
- if (lstatSync2(path).isSymbolicLink()) return false;
586
- const value = JSON.parse(readFileSync2(path, "utf8"));
587
- return value && typeof value === "object" && !Array.isArray(value) && (typeof value.projectName === "string" && value.projectName.trim() !== "" || value.idPrefix && typeof value.idPrefix === "object" && !Array.isArray(value.idPrefix) && Object.keys(value.idPrefix).length > 0);
588
- } catch {
589
- return false;
590
- }
591
- }
592
- function hasGitMarker(root) {
593
- const marker = join(root, ".git");
594
- if (!existsSync2(marker)) return false;
595
- try {
596
- const stat = lstatSync2(marker);
597
- if (stat.isSymbolicLink()) return false;
598
- if (stat.isDirectory()) {
599
- const head = join(marker, "HEAD");
600
- return existsSync2(head) && lstatSync2(head).isFile();
601
- }
602
- if (!stat.isFile() || stat.size > 4096) return false;
603
- const match = /^gitdir:\s*(.+?)\s*$/u.exec(readFileSync2(marker, "utf8"));
604
- if (!match) return false;
605
- const gitDir = resolve(root, match[1]);
606
- return existsSync2(gitDir) && statSync2(gitDir).isDirectory() && existsSync2(join(gitDir, "HEAD")) && lstatSync2(join(gitDir, "HEAD")).isFile();
607
- } catch {
608
- return false;
609
- }
610
- }
611
- function findArtifactProjectRoot(start = process.cwd(), { env = process.env } = {}) {
612
- let current;
613
- try {
614
- current = realpathSync(resolve(start));
615
- } catch {
616
- pathError(
617
- ARTIFACT_ERROR_CODES.REVIEW_IMPORT,
618
- "Artifact review working directory does not exist."
619
- );
620
- }
621
- const homeCandidate = resolve(env.HOME ?? homedir());
622
- let home = homeCandidate;
623
- try {
624
- home = realpathSync(homeCandidate);
625
- } catch {
626
- }
627
- while (true) {
628
- if (current !== home && (parseablePlanrConfig(current) || hasGitMarker(current)))
629
- return current;
630
- const parent = dirname2(current);
631
- if (parent === current) return null;
632
- current = parent;
633
- }
634
- }
635
- function assertSafeDestination(base, relativeParts) {
636
- const absoluteBase = resolve(base);
637
- if (existsSync2(absoluteBase) && lstatSync2(absoluteBase).isSymbolicLink()) {
638
- pathError(
639
- ARTIFACT_ERROR_CODES.SYMLINK_ESCAPE,
640
- "Artifact review destination base is a symlink."
641
- );
642
- }
643
- let realBase = absoluteBase;
644
- if (existsSync2(absoluteBase)) realBase = realpathSync(absoluteBase);
645
- let current = absoluteBase;
646
- for (const part of relativeParts) {
647
- if (!part || part === "." || part === ".." || part.includes("/") || part.includes("\\") || part.includes("\0")) {
648
- pathError(
649
- ARTIFACT_ERROR_CODES.PATH_TRAVERSAL,
650
- "Artifact review destination contains an unsafe segment."
651
- );
652
- }
653
- current = join(current, part);
654
- const entry = pathEntry(current);
655
- if (!entry) continue;
656
- if (entry.isSymbolicLink()) {
657
- pathError(
658
- ARTIFACT_ERROR_CODES.SYMLINK_ESCAPE,
659
- "Artifact review destination contains a symlink."
660
- );
661
- }
662
- if (!inside(realBase, realpathSync(current))) {
663
- pathError(
664
- ARTIFACT_ERROR_CODES.PATH_TRAVERSAL,
665
- "Artifact review destination escapes its storage root."
666
- );
667
- }
668
- }
669
- if (!inside(absoluteBase, current)) {
670
- pathError(
671
- ARTIFACT_ERROR_CODES.PATH_TRAVERSAL,
672
- "Artifact review destination escapes its storage root."
673
- );
674
- }
675
- return current;
676
- }
677
- function resolveArtifactReviewDestination({
678
- cwd = process.cwd(),
679
- env = process.env,
680
- artifactId,
681
- designDir
682
- } = {}) {
683
- if (!ARTIFACT_ID_RE2.test(artifactId ?? "")) {
684
- pathError(
685
- ARTIFACT_ERROR_CODES.PATH_TRAVERSAL,
686
- "Artifact review ID is unsafe for local storage."
687
- );
688
- }
689
- if (designDir !== void 0) {
690
- const lexical = resolve(designDir);
691
- if (!existsSync2(lexical) || !statSync2(lexical).isDirectory()) {
692
- pathError(ARTIFACT_ERROR_CODES.REVIEW_IMPORT, "Design review destination does not exist.");
693
- }
694
- if (lstatSync2(lexical).isSymbolicLink()) {
695
- pathError(
696
- ARTIFACT_ERROR_CODES.SYMLINK_ESCAPE,
697
- "Design review destination must be a real directory."
698
- );
699
- }
700
- const requested = realpathSync(lexical);
701
- return Object.freeze({
702
- kind: "design",
703
- artifactId,
704
- directory: requested,
705
- path: join(requested, FEEDBACK_FILE),
706
- reviewStatePath: join(requested, "artifact-review-state.json")
707
- });
708
- }
709
- const projectRoot = findArtifactProjectRoot(cwd, { env });
710
- if (projectRoot) {
711
- const directory2 = assertSafeDestination(projectRoot, [
712
- PLANNING_FOLDER,
713
- "artifacts",
714
- artifactId
715
- ]);
716
- return Object.freeze({
717
- kind: "project",
718
- artifactId,
719
- root: projectRoot,
720
- directory: directory2,
721
- path: join(directory2, "review-state.json")
722
- });
723
- }
724
- const root = resolve(planrHome(env));
725
- const directory = assertSafeDestination(root, ["artifacts", artifactId]);
726
- return Object.freeze({
727
- kind: "user",
728
- artifactId,
729
- root,
730
- directory,
731
- path: join(directory, "review-state.json")
732
- });
733
- }
734
- function reviewsFromDecoded(value) {
735
- if (!value || typeof value !== "object" || Array.isArray(value)) {
736
- throw new PipelineError(
737
- ARTIFACT_ERROR_CODES.REVIEW_IMPORT,
738
- "Decoded review payload is not an object."
739
- );
740
- }
741
- if (value.kind === "artifact-review-state") {
742
- validateReviewLedger(value);
743
- return value.reviews.map((entry) => ({ review: entry.review, stale: entry.stale }));
744
- }
745
- if (value.artifacts && value.viewer) {
746
- validateArtifactEnvelope(value);
747
- if (!value.review) {
748
- throw new PipelineError(
749
- ARTIFACT_ERROR_CODES.REVIEW_IMPORT,
750
- "Decoded artifact envelope contains no review."
751
- );
752
- }
753
- return [{ review: value.review, stale: false }];
754
- }
755
- if (value.review && !value.reviewId) return reviewsFromDecoded(value.review);
756
- validateArtifactReview(value);
757
- return [{ review: value, stale: false }];
758
- }
759
- async function decodeArtifactReviewSources(sources, { decodeSource, withMetadata = false } = {}) {
760
- const list2 = Array.isArray(sources) ? sources : [sources];
761
- if (list2.length === 0) {
762
- throw new PipelineError(
763
- ARTIFACT_ERROR_CODES.REVIEW_IMPORT,
764
- "At least one artifact review source is required."
765
- );
766
- }
767
- const entries = [];
768
- for (let index = 0; index < list2.length; index += 1) {
769
- const source = list2[index];
770
- let decoded = source;
771
- if (typeof source === "string") {
772
- if (typeof decodeSource !== "function") {
773
- throw new PipelineError(
774
- ARTIFACT_ERROR_CODES.REVIEW_DECODER_REQUIRED,
775
- "String and URL review sources require an injected async decoder.",
776
- "Provide decodeSource(source, { index }); the review lifecycle has no transport dependency.",
777
- { sourceIndex: index }
778
- );
779
- }
780
- try {
781
- decoded = await decodeSource(source, { index });
782
- } catch {
783
- throw new PipelineError(
784
- ARTIFACT_ERROR_CODES.REVIEW_IMPORT,
785
- "Artifact review source could not be decoded.",
786
- "Verify the immutable review link and retry.",
787
- { sourceIndex: index }
788
- );
789
- }
790
- }
791
- try {
792
- entries.push(
793
- ...reviewsFromDecoded(decoded).map((entry) => ({
794
- review: structuredClone(entry.review),
795
- stale: Boolean(entry.stale)
796
- }))
797
- );
798
- } catch (error) {
799
- if (error instanceof PipelineError && error.code === ARTIFACT_ERROR_CODES.REVIEW_IMPORT) {
800
- error.details = { sourceIndex: index };
801
- throw error;
802
- }
803
- throw new PipelineError(
804
- ARTIFACT_ERROR_CODES.REVIEW_IMPORT,
805
- "Decoded artifact review does not satisfy the Protocol v1.1 contract.",
806
- "",
807
- { sourceIndex: index }
808
- );
809
- }
810
- }
811
- return withMetadata ? entries : entries.map((entry) => entry.review);
812
- }
813
-
814
- // packages/design/lib/design/feedback-reader.mjs
815
- var designReviewKey = (document) => `design-${hash(document.id).slice(0, 24)}`;
816
- function designReviewPath(file, env = process.env, options = {}) {
817
- const { root, document } = currentDesign(file, options);
818
- return resolveArtifactReviewDestination({
819
- cwd: root,
820
- env,
821
- artifactId: designReviewKey(document)
822
- }).path;
823
- }
824
- function readDesignFeedback(file, env = process.env, options = {}) {
825
- const current = currentDesign(file, options);
826
- const ledger = readArtifactReviewState(designReviewPath(file, env, options), {
827
- allowMissing: true
828
- });
829
- const digest2 = digestArtifactEnvelope(current.envelope);
830
- const pins = (ledger?.reviews ?? []).flatMap(
831
- (entry) => entry.review.pins.map((pin) => {
832
- const target = current.entries.find((item) => item.artifactId === pin.artifactId);
833
- const screen = target && current.document.screens.find((item) => item.id === target.screenId);
834
- const anchorMissing = pin.anchor?.planrId && screen?.anchors?.length && !screen.anchors.includes(pin.anchor.planrId) && pin.anchor.planrId !== screen.id;
835
- return {
836
- ...pin,
837
- reviewId: entry.review.reviewId,
838
- reviewOf: entry.review.reviewOf,
839
- ...entry.review.reviewId.startsWith("shared-") ? { revisionId: entry.review.reviewId.slice(7) } : {},
840
- stale: entry.stale || entry.review.reviewOf !== digest2 || !target || Boolean(anchorMissing),
841
- ...target ? { screenId: target.screenId, variantId: target.variantId } : {}
842
- };
843
- })
844
- );
845
- return {
846
- revision: current.revision,
847
- reviewPath: designReviewPath(file, env, options),
848
- pins,
849
- state: readJson(join2(current.root, ".design/studio-state.json"), {
850
- state: {},
851
- stateVersion: 0
852
- }).state,
853
- ledger,
854
- shared: readJson(join2(current.root, ".design/shared-feedback.json"), null)
855
- };
856
- }
857
-
858
- // packages/design/lib/design/handoff-reader.mjs
859
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
860
- import { dirname as dirname3, join as join3 } from "node:path";
861
-
862
- // packages/design/lib/design/handoff-format.mjs
863
- var safeMd = (text2) => String(text2).replaceAll("[", "\\[").replaceAll("]", "\\]").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
864
- function renderMarkdown(draft, title2) {
865
- const lines = [
866
- `# ${safeMd(title2)} \u2014 review handoff`,
867
- "",
868
- `Status: ${draft.status}. Source revision: ${draft.basis.sourceRevision}.`,
869
- "",
870
- draft.content.summary || "Owner summary has not been written.",
871
- ""
872
- ];
873
- for (const [key, label] of [
874
- ["agreedChanges", "Agreed changes"],
875
- ["openQuestions", "Open questions"],
876
- ["deferred", "Deferred"],
877
- ["rejected", "Rejected"]
878
- ]) {
879
- lines.push(`## ${label}`, "");
880
- for (const item of draft.content[key]) {
881
- lines.push(
882
- `- ${safeMd(item.refinement ?? item.text)} \u2014 ${safeMd(item.author ?? "Reviewer")}${item.stale ? " \xB7 original revision" : ""} [source comment](${item.source})`
883
- );
884
- if (item.refinement) lines.push(` Original comment: ${safeMd(item.text)}`);
885
- }
886
- if (!draft.content[key].length) lines.push("None recorded.");
887
- lines.push("");
888
- }
889
- lines.push(
890
- "## Overall review notes",
891
- "",
892
- ...draft.reviewNotes.map((note) => `- ${safeMd(note.text)} (${safeMd(note.reviewId)})`),
893
- ""
894
- );
895
- lines.push(
896
- "## Verification gaps",
897
- "",
898
- ...draft.verificationGaps.length ? draft.verificationGaps.map((value) => `- ${safeMd(value)}`) : ["None recorded."],
899
- "",
900
- "Plan and Ship remain separate user invocations.",
901
- ""
902
- );
903
- return lines.join("\n");
904
- }
905
-
906
- // packages/protocol/src/design-handoff-contracts.mjs
907
- var DESIGN_HANDOFF_PROTOCOL_VERSION = "1.11.0";
908
- var DESIGN_HANDOFF_CONTRACT_VERSION = "1.0.0";
909
- var DESIGN_HANDOFF_AUTHORITY = "prepare-plan";
910
- var DESIGN_HANDOFF_CHECK_IDS = Object.freeze([
911
- "current-revision",
912
- "selected-direction",
913
- "design-specification",
914
- "rendered-verification",
915
- "review-freshness",
916
- "review-dispositions",
917
- "unresolved-blockers",
918
- "approved-review-handoff"
919
- ]);
920
- var DESIGN_HANDOFF_SOURCE_KINDS = Object.freeze([
921
- "design-revision",
922
- "selected-direction",
923
- "design-specification",
924
- "rendered-verification",
925
- "review-context",
926
- "review-feedback",
927
- "review-metadata",
928
- "review-handoff",
929
- "screen",
930
- "frame",
931
- "component",
932
- "state",
933
- "flow",
934
- "token",
935
- "review-decision",
936
- "element-anchor"
937
- ]);
938
- var DESIGN_HANDOFF_REQUIREMENT_KINDS = Object.freeze([
939
- "behavior",
940
- "visual-state",
941
- "responsive",
942
- "accessibility",
943
- "content-data-assumption",
944
- "constraint",
945
- "verification-intent"
946
- ]);
947
- var DESIGN_HANDOFF_CONTRACT_FILES = Object.freeze({
948
- "design-handoff-readiness": "design-handoff-readiness.schema.json",
949
- "design-implementation-handoff": "design-implementation-handoff.schema.json",
950
- "design-planning-lineage": "design-planning-lineage.schema.json"
951
- });
952
- var text = { type: "string", minLength: 1, maxLength: 16384 };
953
- var title = { ...text, maxLength: 240 };
954
- var id = {
955
- type: "string",
956
- minLength: 1,
957
- maxLength: 160,
958
- pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
959
- };
960
- var digest = { type: "string", pattern: "^sha256:[a-f0-9]{64}$" };
961
- var relativePath = { type: "string", minLength: 1, maxLength: 4096 };
962
- var timestamp = { type: "string", format: "date-time" };
963
- var list = (items, maxItems = 1e3) => ({ type: "array", items, maxItems });
964
- var closed = (properties, required = Object.keys(properties)) => ({
965
- type: "object",
966
- additionalProperties: false,
967
- properties,
968
- required
969
- });
970
- var contract = (name, body) => ({
971
- $schema: "https://json-schema.org/draft/2020-12/schema",
972
- $id: `https://openplanr.dev/schemas/v1.11.0/${name}.schema.json`,
973
- "x-openplanr-contract": { id: name, version: DESIGN_HANDOFF_PROTOCOL_VERSION },
974
- ...body
975
- });
976
- var action = closed({ id, label: title });
977
- var anchor = {
978
- oneOf: [
979
- closed({ section: id }),
980
- closed({ screenId: id }),
981
- closed({ screenId: id, elementId: id }),
982
- closed({ reviewId: id, pinId: id })
983
- ]
984
- };
985
- var evidenceReference = closed(
986
- {
987
- id,
988
- kind: { enum: DESIGN_HANDOFF_SOURCE_KINDS },
989
- path: relativePath,
990
- revision: digest,
991
- digest,
992
- anchor
993
- },
994
- ["id", "kind", "path"]
995
- );
996
- var readinessCheck = closed(
997
- {
998
- id: { enum: DESIGN_HANDOFF_CHECK_IDS },
999
- status: { enum: ["pass", "attention", "blocked", "stale"] },
1000
- message: title,
1001
- evidenceRefs: list(id, 64),
1002
- recoveryAction: action
1003
- },
1004
- ["id", "status", "message", "evidenceRefs"]
1005
- );
1006
- var readinessRecord = closed({
1007
- kind: { const: "openplanr-design-handoff-readiness" },
1008
- schemaVersion: { const: DESIGN_HANDOFF_CONTRACT_VERSION },
1009
- scope: { const: "design-originated" },
1010
- authority: { const: "none" },
1011
- designId: id,
1012
- sourceRevision: { anyOf: [digest, { type: "null" }] },
1013
- selectedVariant: { anyOf: [id, { type: "null" }] },
1014
- status: { enum: ["ready", "attention", "blocked", "stale"] },
1015
- continuation: closed({
1016
- action: { const: DESIGN_HANDOFF_AUTHORITY },
1017
- available: { type: "boolean" }
1018
- }),
1019
- checks: list(readinessCheck, DESIGN_HANDOFF_CHECK_IDS.length),
1020
- evidence: list(evidenceReference, 1e4),
1021
- blockers: list({ enum: DESIGN_HANDOFF_CHECK_IDS }, DESIGN_HANDOFF_CHECK_IDS.length),
1022
- nextActions: list(action, DESIGN_HANDOFF_CHECK_IDS.length)
1023
- });
1024
- var readinessAbsence = closed({
1025
- kind: { const: "openplanr-design-handoff-readiness-absence" },
1026
- schemaVersion: { const: DESIGN_HANDOFF_CONTRACT_VERSION },
1027
- status: { const: "absent" },
1028
- reason: { enum: ["not-computed", "not-applicable", "unavailable"] },
1029
- message: title,
1030
- nextAction: action
1031
- });
1032
- var DESIGN_HANDOFF_READINESS_SCHEMA = contract("design-handoff-readiness", {
1033
- oneOf: [readinessRecord, readinessAbsence]
1034
- });
1035
- var requirement = closed({
1036
- id: { type: "string", pattern: "^REQ-[0-9]{3,}$" },
1037
- kind: { enum: DESIGN_HANDOFF_REQUIREMENT_KINDS },
1038
- statement: text,
1039
- sourceRefs: { ...list(id, 256), minItems: 1 },
1040
- verification: { ...list(text, 256), minItems: 1 }
1041
- });
1042
- var implementationHandoff = closed(
1043
- {
1044
- kind: { const: "openplanr-design-implementation-handoff" },
1045
- schemaVersion: { const: DESIGN_HANDOFF_CONTRACT_VERSION },
1046
- id,
1047
- version: { type: "integer", minimum: 1 },
1048
- status: { enum: ["draft", "approved", "superseded", "revoked"] },
1049
- authority: { const: DESIGN_HANDOFF_AUTHORITY },
1050
- title,
1051
- basis: closed({
1052
- designId: id,
1053
- sourceRevision: digest,
1054
- selectedVariant: id,
1055
- readiness: closed({ status: { enum: ["ready", "attention", "blocked", "stale"] }, digest }),
1056
- reviewHandoff: closed({ version: { type: "integer", minimum: 1 }, contentDigest: digest })
1057
- }),
1058
- sources: list(evidenceReference, 1e4),
1059
- requirements: { ...list(requirement, 1e4), minItems: 1 },
1060
- contentDigest: digest,
1061
- markdown: { type: "string", maxLength: 2097152 },
1062
- approval: closed({
1063
- actorId: id,
1064
- approvedAt: timestamp,
1065
- contentDigest: digest,
1066
- authority: { const: DESIGN_HANDOFF_AUTHORITY }
1067
- }),
1068
- supersededBy: closed({ id, version: { type: "integer", minimum: 1 }, contentDigest: digest }),
1069
- revocation: closed({ actorId: id, revokedAt: timestamp, reason: text })
1070
- },
1071
- [
1072
- "kind",
1073
- "schemaVersion",
1074
- "id",
1075
- "version",
1076
- "status",
1077
- "authority",
1078
- "title",
1079
- "basis",
1080
- "sources",
1081
- "requirements",
1082
- "contentDigest",
1083
- "markdown"
1084
- ]
1085
- );
1086
- implementationHandoff.allOf = [
1087
- {
1088
- if: { properties: { status: { const: "approved" } }, required: ["status"] },
1089
- then: {
1090
- required: ["approval"],
1091
- not: { anyOf: [{ required: ["supersededBy"] }, { required: ["revocation"] }] }
1092
- }
1093
- },
1094
- {
1095
- if: { properties: { status: { const: "superseded" } }, required: ["status"] },
1096
- then: { required: ["approval", "supersededBy"], not: { required: ["revocation"] } }
1097
- },
1098
- {
1099
- if: { properties: { status: { const: "revoked" } }, required: ["status"] },
1100
- then: { required: ["approval", "revocation"], not: { required: ["supersededBy"] } }
1101
- },
1102
- {
1103
- if: { properties: { status: { const: "draft" } }, required: ["status"] },
1104
- then: {
1105
- not: {
1106
- anyOf: [
1107
- { required: ["approval"] },
1108
- { required: ["supersededBy"] },
1109
- { required: ["revocation"] }
1110
- ]
1111
- }
1112
- }
1113
- }
1114
- ];
1115
- var DESIGN_IMPLEMENTATION_HANDOFF_SCHEMA = contract(
1116
- "design-implementation-handoff",
1117
- implementationHandoff
1118
- );
1119
- var lineageMapping = closed({
1120
- requirementId: { type: "string", pattern: "^REQ-[0-9]{3,}$" },
1121
- acceptanceRefs: {
1122
- ...list(
1123
- closed({
1124
- storyId: { type: "string", pattern: "^US-[0-9]{3,}$" },
1125
- acceptanceId: { type: "string", pattern: "^AC-[0-9]{3,}$" }
1126
- }),
1127
- 256
1128
- ),
1129
- minItems: 1
1130
- },
1131
- taskIds: { ...list({ type: "string", pattern: "^T-[0-9]{3,}$" }, 256), minItems: 1 }
1132
- });
1133
- var DESIGN_PLANNING_LINEAGE_SCHEMA = contract(
1134
- "design-planning-lineage",
1135
- closed({
1136
- kind: { const: "openplanr-design-planning-lineage" },
1137
- schemaVersion: { const: DESIGN_HANDOFF_CONTRACT_VERSION },
1138
- handoff: closed({ id, version: { type: "integer", minimum: 1 }, contentDigest: digest }),
1139
- specId: { type: "string", pattern: "^SPEC-[0-9]{3,}$" },
1140
- mappings: { ...list(lineageMapping, 1e4), minItems: 1 }
1141
- })
1142
- );
1143
- var DESIGN_HANDOFF_SCHEMAS = deepFreeze({
1144
- "design-handoff-readiness": DESIGN_HANDOFF_READINESS_SCHEMA,
1145
- "design-implementation-handoff": DESIGN_IMPLEMENTATION_HANDOFF_SCHEMA,
1146
- "design-planning-lineage": DESIGN_PLANNING_LINEAGE_SCHEMA
1147
- });
1148
- function distinct(items, select, label) {
1149
- const values = items.map(select);
1150
- if (new Set(values).size !== values.length) throw new TypeError(`Duplicate ${label}.`);
1151
- }
1152
- function isDesignHandoffRelativePath(value) {
1153
- return typeof value === "string" && value.length > 0 && value.length <= 4096 && !/^(?:[A-Za-z]:|\/|[A-Za-z][A-Za-z0-9+.-]*:)|[\\?#%\u0000-\u001f\u007f]/u.test(value) && value.split("/").every(
1154
- (part) => part && part !== "." && part !== ".." && !["__proto__", "prototype", "constructor"].includes(part)
1155
- );
1156
- }
1157
- function assertDesignHandoffContract(value, schemaOrName) {
1158
- const schema = typeof schemaOrName === "string" ? DESIGN_HANDOFF_SCHEMAS[schemaOrName] : schemaOrName;
1159
- if (!schema) throw new TypeError("Unknown design handoff contract.");
1160
- assertPlainData(value, "Design handoff data");
1161
- canonicalizeJson(value);
1162
- const errors = validateJson(value, schema);
1163
- if (errors.length)
1164
- throw new TypeError(
1165
- `Invalid ${schema["x-openplanr-contract"]?.id ?? "design handoff data"}: ${errors.slice(0, 5).map((error) => `${error.path} (${error.rule})`).join("; ")}`
1166
- );
1167
- return value;
1168
- }
1169
- function assertEvidenceReferences(evidence2) {
1170
- distinct(evidence2, (item) => item.id, "evidence reference identity");
1171
- for (const item of evidence2)
1172
- if (!isDesignHandoffRelativePath(item.path))
1173
- throw new TypeError("Design handoff evidence requires a repository-relative logical path.");
1174
- }
1175
- function assertProductCopy(value) {
1176
- if (/\b(?:hash|digest|checksum|sha[- ]?256|canonical(?:ize|ization)?)\b/iu.test(value))
1177
- throw new TypeError("Design readiness guidance must use product language.");
1178
- }
1179
- function assertDesignHandoffReadiness(value) {
1180
- assertDesignHandoffContract(value, DESIGN_HANDOFF_READINESS_SCHEMA);
1181
- if (value.kind.endsWith("-absence")) {
1182
- assertProductCopy(`${value.message} ${value.nextAction.label}`);
1183
- return value;
1184
- }
1185
- assertEvidenceReferences(value.evidence);
1186
- distinct(value.checks, (item) => item.id, "readiness check identity");
1187
- const expected = DESIGN_HANDOFF_CHECK_IDS.join("\n");
1188
- if (value.checks.map((item) => item.id).join("\n") !== expected)
1189
- throw new TypeError("Design readiness must contain every stable check in canonical order.");
1190
- const evidenceIds = new Set(value.evidence.map((item) => item.id));
1191
- for (const check of value.checks) {
1192
- distinct(check.evidenceRefs, (item) => item, "readiness evidence reference");
1193
- if (check.evidenceRefs.some((reference) => !evidenceIds.has(reference)))
1194
- throw new TypeError("Design readiness references missing evidence.");
1195
- assertProductCopy(`${check.message} ${check.recoveryAction?.label ?? ""}`);
1196
- }
1197
- const priority2 = { pass: 0, attention: 1, blocked: 2, stale: 3 };
1198
- const worst = value.checks.reduce(
1199
- (current, check) => priority2[check.status] > priority2[current] ? check.status : current,
1200
- "pass"
1201
- );
1202
- const expectedStatus = worst === "pass" ? "ready" : worst;
1203
- if (value.status !== expectedStatus)
1204
- throw new TypeError("Design readiness summary does not match its checks.");
1205
- const blockingIds = value.checks.filter((check) => ["blocked", "stale"].includes(check.status)).map((check) => check.id);
1206
- if (value.blockers.join("\n") !== blockingIds.join("\n"))
1207
- throw new TypeError("Design readiness blockers do not match its blocking checks.");
1208
- const actionable = value.checks.filter((check) => check.status !== "pass");
1209
- if (value.nextActions.length !== actionable.length || value.nextActions.some((actionValue, index) => {
1210
- const recoveryAction = actionable[index].recoveryAction;
1211
- return actionValue.id !== recoveryAction?.id || actionValue.label !== recoveryAction?.label;
1212
- }))
1213
- throw new TypeError("Design readiness next actions do not match its checks.");
1214
- if (value.continuation.available !== ["ready", "attention"].includes(value.status))
1215
- throw new TypeError("Design readiness continuation availability does not match its status.");
1216
- return value;
1217
- }
1218
- function designImplementationHandoffDigest(value) {
1219
- const projection = {
1220
- kind: value.kind,
1221
- schemaVersion: value.schemaVersion,
1222
- id: value.id,
1223
- version: value.version,
1224
- authority: value.authority,
1225
- title: value.title,
1226
- basis: value.basis,
1227
- sources: value.sources,
1228
- requirements: value.requirements,
1229
- markdown: value.markdown
1230
- };
1231
- return `sha256:${sha256Hex(canonicalizeJson(projection))}`;
1232
- }
1233
- function assertDesignImplementationHandoff(value) {
1234
- assertDesignHandoffContract(value, DESIGN_IMPLEMENTATION_HANDOFF_SCHEMA);
1235
- assertEvidenceReferences(value.sources);
1236
- distinct(value.requirements, (item) => item.id, "implementation requirement identity");
1237
- const sourceIds = new Set(value.sources.map((item) => item.id));
1238
- for (const item of value.requirements) {
1239
- distinct(item.sourceRefs, (reference) => reference, "requirement source reference");
1240
- if (item.sourceRefs.some((reference) => !sourceIds.has(reference)))
1241
- throw new TypeError("Implementation requirement references missing evidence.");
1242
- }
1243
- if (value.contentDigest !== designImplementationHandoffDigest(value))
1244
- throw new TypeError(
1245
- "Implementation handoff content does not match its recorded integrity value."
1246
- );
1247
- if (value.approval?.contentDigest !== void 0 && value.approval.contentDigest !== value.contentDigest)
1248
- throw new TypeError("Implementation handoff approval does not match its content.");
1249
- if (value.status !== "draft" && value.basis.readiness.status !== "ready")
1250
- throw new TypeError(
1251
- "Only a ready implementation handoff can be approved or retained as approved history."
1252
- );
1253
- if (value.supersededBy && value.supersededBy.id === value.id && value.supersededBy.version <= value.version)
1254
- throw new TypeError("A superseding handoff must identify a newer package version.");
1255
- return value;
1256
- }
1257
-
1258
- // packages/design/lib/design/handoff-readiness.mjs
1259
- var CHECKS = Object.freeze([
1260
- "current-revision",
1261
- "selected-direction",
1262
- "design-specification",
1263
- "rendered-verification",
1264
- "review-freshness",
1265
- "review-dispositions",
1266
- "unresolved-blockers",
1267
- "approved-review-handoff"
1268
- ]);
1269
- var action2 = Object.freeze({
1270
- "current-revision": Object.freeze({ id: "render-design", label: "Render the current design" }),
1271
- "selected-direction": Object.freeze({
1272
- id: "select-direction",
1273
- label: "Choose one ready direction"
1274
- }),
1275
- "design-specification": Object.freeze({
1276
- id: "complete-design-specification",
1277
- label: "Complete the design specification"
1278
- }),
1279
- "rendered-verification": Object.freeze({
1280
- id: "verify-rendered-design",
1281
- label: "Verify the rendered design"
1282
- }),
1283
- "review-freshness": Object.freeze({
1284
- id: "refresh-review",
1285
- label: "Refresh review against the current design"
1286
- }),
1287
- "review-dispositions": Object.freeze({
1288
- id: "resolve-review-decisions",
1289
- label: "Record the remaining review decisions"
1290
- }),
1291
- "unresolved-blockers": Object.freeze({
1292
- id: "resolve-review-blockers",
1293
- label: "Resolve the blocking feedback"
1294
- }),
1295
- "approved-review-handoff": Object.freeze({
1296
- id: "approve-review-handoff",
1297
- label: "Prepare and approve the current review handoff"
1298
- })
1299
- });
1300
- var message = Object.freeze({
1301
- "current-revision": Object.freeze({
1302
- pass: "The current design has a completed render.",
1303
- blocked: "Render the design before preparing work for engineering."
1304
- }),
1305
- "selected-direction": Object.freeze({
1306
- pass: "One ready design direction is selected.",
1307
- blocked: "Choose one ready design direction before continuing.",
1308
- stale: "The selected direction changed after the review handoff was prepared."
1309
- }),
1310
- "design-specification": Object.freeze({
1311
- pass: "The design specification is complete for this revision.",
1312
- blocked: "Complete the design specification before continuing.",
1313
- stale: "The design specification belongs to an earlier revision."
1314
- }),
1315
- "rendered-verification": Object.freeze({
1316
- pass: "The rendered design is verified for this revision.",
1317
- blocked: "Complete rendered design verification before continuing.",
1318
- stale: "Rendered verification belongs to an earlier revision."
1319
- }),
1320
- "review-freshness": Object.freeze({
1321
- pass: "Review feedback is current for this revision.",
1322
- blocked: "Review feedback contains ambiguous identities or anchors.",
1323
- stale: "Review feedback belongs to an earlier revision."
1324
- }),
1325
- "review-dispositions": Object.freeze({
1326
- pass: "Every current review comment has a recorded outcome.",
1327
- attention: "Some non-blocking review comments still need an owner decision.",
1328
- blocked: "Review decisions are incomplete or ambiguous."
1329
- }),
1330
- "unresolved-blockers": Object.freeze({
1331
- pass: "No blocking feedback remains open.",
1332
- blocked: "Blocking feedback must be resolved before continuing.",
1333
- stale: "A prior decision must be reviewed against the current design."
1334
- }),
1335
- "approved-review-handoff": Object.freeze({
1336
- pass: "The current review handoff is approved.",
1337
- blocked: "Prepare and approve the review handoff before continuing.",
1338
- stale: "The approved review handoff no longer matches the current design."
1339
- })
1340
- });
1341
- var priority = Object.freeze({ pass: 0, attention: 1, blocked: 2, stale: 3 });
1342
- var digestPattern = /^(?:sha256:)?[a-f0-9]{64}$/u;
1343
- var normalizeDigest = (value, label) => {
1344
- if (typeof value !== "string" || !digestPattern.test(value))
1345
- throw new TypeError(`${label} must be a lowercase SHA-256 value.`);
1346
- return value.startsWith("sha256:") ? value : `sha256:${value}`;
1347
- };
1348
- var optionalDigest = (value, label) => value === void 0 || value === null ? void 0 : normalizeDigest(value, label);
1349
- var plainClone = (value) => value === void 0 ? void 0 : JSON.parse(canonicalizeJson(value));
1350
- var logicalPath = (value, fallback, label) => {
1351
- const result = value ?? fallback;
1352
- if (!isDesignHandoffRelativePath(result) || /[?#%]/u.test(result))
1353
- throw new TypeError(`${label} must be a repository-relative logical path.`);
1354
- return result;
1355
- };
1356
- var evidenceDigest = (value) => `sha256:${sha256Hex(canonicalizeJson(value))}`;
1357
- function ensureUnique(items, select, label) {
1358
- const values = items.map(select);
1359
- if (new Set(values).size !== values.length) throw new TypeError(`Duplicate ${label}.`);
1360
- }
1361
- function evidence(id2, kind, path, value, { revision, anchor: anchor2 } = {}) {
1362
- return {
1363
- id: id2,
1364
- kind,
1365
- path,
1366
- ...revision ? { revision: normalizeDigest(revision, `${id2} revision`) } : {},
1367
- digest: optionalDigest(value?.digest, `${id2} integrity`) ?? evidenceDigest(value),
1368
- ...anchor2 ? { anchor: anchor2 } : {}
1369
- };
1370
- }
1371
- function checked(id2, status, evidenceRefs = []) {
1372
- return {
1373
- id: id2,
1374
- status,
1375
- message: message[id2][status],
1376
- evidenceRefs,
1377
- ...status === "pass" ? {} : { recoveryAction: action2[id2] }
1378
- };
1379
- }
1380
- function selectedDirection(document, studioState) {
1381
- const selected = studioState?.selectedVariant ?? document?.selectedVariant ?? null;
1382
- const variants = Array.isArray(document?.variants) ? document.variants : [];
1383
- ensureUnique(variants, (item) => item?.id, "design direction identity");
1384
- const matches = variants.filter((item) => item?.id === selected && item?.status === "ready");
1385
- return { selected, ready: matches.length === 1 };
1386
- }
1387
- function normalizedPins(review, revision) {
1388
- const pins = Array.isArray(review?.pins) ? review.pins.map((pin) => ({ ...pin })) : [];
1389
- ensureUnique(
1390
- pins,
1391
- (pin) => `${pin.revisionId ?? pin.reviewId ?? ""}:${pin.id ?? ""}`,
1392
- "review comment identity"
1393
- );
1394
- for (const pin of pins) {
1395
- if (typeof pin.id !== "string" || !pin.id)
1396
- throw new TypeError("Review comments require stable identities.");
1397
- if (pin.elementId && !pin.screenId)
1398
- throw new TypeError("An element review anchor must identify its screen.");
1399
- if (pin.anchorCount !== void 0 && pin.anchorCount !== 1)
1400
- throw new TypeError("Review comments must identify exactly one anchor.");
1401
- }
1402
- return pins.sort(
1403
- (left, right) => `${left.revisionId ?? left.reviewId}:${left.id}`.localeCompare(
1404
- `${right.revisionId ?? right.reviewId}:${right.id}`
1405
- )
1406
- ).map((pin) => ({
1407
- ...pin,
1408
- current: !pin.stale && (!revision || !pin.revisionId || normalizeDigest(pin.revisionId, "review revision") === revision)
1409
- }));
1410
- }
1411
- function reviewState(input, revision) {
1412
- if (!input) return { pins: [], stale: false, ambiguous: false };
1413
- const pins = normalizedPins(input, revision);
1414
- const recordedRevision = optionalDigest(input.revision, "review revision");
1415
- return {
1416
- pins,
1417
- stale: input.current === false || recordedRevision !== void 0 && recordedRevision !== revision || pins.some((pin) => !pin.current),
1418
- ambiguous: input.ambiguous === true
1419
- };
1420
- }
1421
- function handoffState(handoff, designId, revision, selectedVariant) {
1422
- if (!handoff) return "blocked";
1423
- if (handoff.status !== "approved" || !handoff.approval || handoff.approval.contentHash !== handoff.contentHash)
1424
- return "blocked";
1425
- const basisRevision = optionalDigest(handoff.basis?.sourceRevision, "review handoff revision");
1426
- if (handoff.current === false || handoff.basis?.designId !== designId || basisRevision !== revision || handoff.basis?.selectedVariant !== selectedVariant)
1427
- return "stale";
1428
- return "pass";
1429
- }
1430
- function compileDesignHandoffReadiness(input) {
1431
- if (input === null || input === void 0) return designHandoffReadinessAbsence();
1432
- const source = plainClone(input);
1433
- const document = source.document;
1434
- if (!document || document.kind !== "openplanr-design-document" || !["1.0.0", "1.1.0"].includes(document.schemaVersion) || typeof document.id !== "string" || !document.id)
1435
- throw new TypeError("Readiness requires one supported design document.");
1436
- const revision = source.sourceRevision === null || source.sourceRevision === void 0 ? null : normalizeDigest(source.sourceRevision, "design revision");
1437
- const direction = selectedDirection(document, source.studioState);
1438
- const evidenceItems = [];
1439
- if (revision)
1440
- evidenceItems.push(
1441
- evidence(
1442
- "design-revision",
1443
- "design-revision",
1444
- logicalPath(source.documentPath, "design-document.json", "design document path"),
1445
- document,
1446
- { revision }
1447
- )
1448
- );
1449
- const directionEvidence = direction.selected && revision ? evidence(
1450
- "selected-direction",
1451
- "selected-direction",
1452
- logicalPath(source.studioStatePath, ".design/studio-state.json", "Studio state path"),
1453
- { selectedVariant: direction.selected },
1454
- { revision, anchor: { section: direction.selected } }
1455
- ) : null;
1456
- if (directionEvidence) evidenceItems.push(directionEvidence);
1457
- const specification = source.specification;
1458
- if (specification)
1459
- evidenceItems.push(
1460
- evidence(
1461
- "design-specification",
1462
- "design-specification",
1463
- logicalPath(specification.path, "design-spec.md", "design specification path"),
1464
- specification,
1465
- { revision: specification.revision }
1466
- )
1467
- );
1468
- const verification = source.verification;
1469
- if (verification)
1470
- evidenceItems.push(
1471
- evidence(
1472
- "rendered-verification",
1473
- "rendered-verification",
1474
- logicalPath(verification.path, ".design/verification/current.json", "verification path"),
1475
- verification,
1476
- { revision: verification.revision }
1477
- )
1478
- );
1479
- const review = reviewState(source.review, revision);
1480
- if (source.review)
1481
- evidenceItems.push(
1482
- evidence(
1483
- "review-feedback",
1484
- "review-feedback",
1485
- logicalPath(source.review.path, ".design/review.json", "review feedback path"),
1486
- source.review,
1487
- { revision: source.review.revision }
1488
- )
1489
- );
1490
- const handoff = source.reviewHandoff;
1491
- if (handoff)
1492
- evidenceItems.push(
1493
- evidence(
1494
- "review-handoff",
1495
- "review-handoff",
1496
- logicalPath(handoff.path, "review-handoff.json", "review handoff path"),
1497
- handoff,
1498
- { revision: handoff.basis?.sourceRevision }
1499
- )
1500
- );
1501
- const checks = [];
1502
- checks.push(
1503
- checked("current-revision", revision ? "pass" : "blocked", revision ? ["design-revision"] : [])
1504
- );
1505
- const handoffDirection = handoff?.basis?.selectedVariant;
1506
- const directionStatus = !direction.ready ? "blocked" : handoffDirection && handoffDirection !== direction.selected ? "stale" : "pass";
1507
- checks.push(
1508
- checked("selected-direction", directionStatus, directionEvidence ? ["selected-direction"] : [])
1509
- );
1510
- let specificationStatus = "blocked";
1511
- if (specification?.complete === true) {
1512
- const specificationRevision = optionalDigest(
1513
- specification.revision,
1514
- "design specification revision"
1515
- );
1516
- specificationStatus = revision && specificationRevision && specificationRevision !== revision ? "stale" : "pass";
1517
- }
1518
- checks.push(
1519
- checked(
1520
- "design-specification",
1521
- specificationStatus,
1522
- specification ? ["design-specification"] : []
1523
- )
1524
- );
1525
- let verificationStatus = "blocked";
1526
- if (verification) {
1527
- const verificationRevision = optionalDigest(
1528
- verification.revision,
1529
- "rendered verification revision"
1530
- );
1531
- if (revision && verificationRevision && verificationRevision !== revision)
1532
- verificationStatus = "stale";
1533
- else if (verification.status === "verified") verificationStatus = "pass";
1534
- }
1535
- checks.push(
1536
- checked(
1537
- "rendered-verification",
1538
- verificationStatus,
1539
- verification ? ["rendered-verification"] : []
1540
- )
1541
- );
1542
- const reviewRefs = source.review ? ["review-feedback"] : [];
1543
- const freshnessStatus = review.ambiguous ? "blocked" : review.stale ? "stale" : "pass";
1544
- checks.push(checked("review-freshness", freshnessStatus, reviewRefs));
1545
- const undecided = review.pins.filter(
1546
- (pin) => pin.current && !["accepted", "deferred", "rejected"].includes(pin.disposition)
1547
- );
1548
- const invalidDisposition = review.pins.some(
1549
- (pin) => pin.disposition && !["accepted", "deferred", "rejected"].includes(pin.disposition)
1550
- );
1551
- const dispositionStatus = invalidDisposition || review.ambiguous ? "blocked" : undecided.length ? "attention" : "pass";
1552
- checks.push(checked("review-dispositions", dispositionStatus, reviewRefs));
1553
- const openBlockers = review.pins.filter(
1554
- (pin) => pin.current && ["blocker", "change-request"].includes(pin.category) && !["accepted", "deferred", "rejected"].includes(pin.disposition)
1555
- );
1556
- const staleAccepted = review.pins.some((pin) => !pin.current && pin.disposition === "accepted");
1557
- const blockerStatus = staleAccepted ? "stale" : openBlockers.length ? "blocked" : "pass";
1558
- checks.push(checked("unresolved-blockers", blockerStatus, reviewRefs));
1559
- const approvalStatus = handoffState(handoff, document.id, revision, direction.selected);
1560
- checks.push(
1561
- checked("approved-review-handoff", approvalStatus, handoff ? ["review-handoff"] : [])
1562
- );
1563
- if (checks.map((item) => item.id).join("\n") !== CHECKS.join("\n"))
1564
- throw new TypeError("Readiness checks are not in canonical order.");
1565
- const worst = checks.reduce(
1566
- (current, item) => priority[item.status] > priority[current] ? item.status : current,
1567
- "pass"
1568
- );
1569
- const status = worst === "pass" ? "ready" : worst;
1570
- const blockers = checks.filter((item) => ["blocked", "stale"].includes(item.status)).map((item) => item.id);
1571
- const nextActions = checks.filter((item) => item.status !== "pass").map((item) => item.recoveryAction);
1572
- return assertDesignHandoffReadiness({
1573
- kind: "openplanr-design-handoff-readiness",
1574
- schemaVersion: "1.0.0",
1575
- scope: "design-originated",
1576
- authority: "none",
1577
- designId: document.id,
1578
- sourceRevision: revision,
1579
- selectedVariant: direction.selected,
1580
- status,
1581
- continuation: { action: "prepare-plan", available: ["ready", "attention"].includes(status) },
1582
- checks,
1583
- evidence: evidenceItems.sort((left, right) => left.id.localeCompare(right.id)),
1584
- blockers,
1585
- nextActions
1586
- });
1587
- }
1588
- function designHandoffReadinessAbsence(reason = "not-computed") {
1589
- return assertDesignHandoffReadiness({
1590
- kind: "openplanr-design-handoff-readiness-absence",
1591
- schemaVersion: "1.0.0",
1592
- status: "absent",
1593
- reason,
1594
- message: "No design handoff readiness has been prepared.",
1595
- nextAction: {
1596
- id: "inspect-design",
1597
- label: "Open the design when you want to prepare a handoff"
1598
- }
1599
- });
1600
- }
1601
- function designHandoffReadinessDigest(value) {
1602
- assertDesignHandoffReadiness(value);
1603
- return `sha256:${sha256Hex(canonicalizeJson(value))}`;
1604
- }
1605
-
1606
- // packages/design/lib/design/handoff-resolution.mjs
1607
- var OUTCOMES = /* @__PURE__ */ new Set(["accepted", "open", "blocking", "deferred", "declined"]);
1608
- var CATEGORIES = /* @__PURE__ */ new Set(["question", "suggestion", "change-request", "blocker"]);
1609
- var DISPOSITIONS = /* @__PURE__ */ new Set(["accepted", "deferred", "rejected", "declined"]);
1610
- var MAX_COMMENTS = 1e4;
1611
- var MAX_ISSUES = 1e4;
1612
- var digestPattern2 = /^[a-f0-9]{64}$/u;
1613
- var revisionOf = (pin) => pin.revisionId ?? pin.reviewId;
1614
- var keyOf = (pin) => `${revisionOf(pin)}:${pin.id}`;
1615
- var compare = (left, right) => left.localeCompare(right, "en");
1616
- var clone3 = (value) => {
1617
- assertPlainData(value, "Review resolution data");
1618
- return JSON.parse(canonicalizeJson(value));
1619
- };
1620
- function issue(code, severity, message2, { pinId, revisionId, recoveryAction } = {}) {
1621
- return {
1622
- code,
1623
- severity,
1624
- message: message2,
1625
- ...revisionId ? { revisionId } : {},
1626
- ...pinId ? { pinId } : {},
1627
- recoveryAction: recoveryAction ?? {
1628
- id: "refresh-review-resolution",
1629
- label: "Refresh review decisions"
1630
- }
1631
- };
1632
- }
1633
- function validatePin(pin) {
1634
- if (!pin || typeof pin !== "object" || Array.isArray(pin))
1635
- throw new TypeError("Every review comment must be an object.");
1636
- if (typeof pin.id !== "string" || !pin.id || pin.id.length > 160)
1637
- throw new TypeError("Every review comment requires a stable identity.");
1638
- const revisionId = revisionOf(pin);
1639
- if (typeof revisionId !== "string" || !revisionId || revisionId.length > 160)
1640
- throw new TypeError("Every review comment requires its original revision identity.");
1641
- if (typeof pin.reviewOf !== "string" || !digestPattern2.test(pin.reviewOf))
1642
- throw new TypeError("Every review comment requires its original review basis.");
1643
- if (pin.screenId !== void 0 && (typeof pin.screenId !== "string" || !pin.screenId))
1644
- throw new TypeError("Review screen references must be stable identities.");
1645
- if (pin.elementId !== void 0 && (typeof pin.elementId !== "string" || !pin.elementId || !pin.screenId))
1646
- throw new TypeError("Review element references require a stable screen identity.");
1647
- }
1648
- function scopedMetadata(metadata2, pin, duplicatePinIds, diagnostics) {
1649
- const revisionId = revisionOf(pin);
1650
- const scoped = metadata2.byRevision?.[revisionId] ?? {};
1651
- let category = scoped.categories?.[pin.id];
1652
- let disposition = scoped.dispositions?.[pin.id];
1653
- if (category === void 0 && Object.hasOwn(metadata2.categories ?? {}, pin.id)) {
1654
- if (duplicatePinIds.has(pin.id))
1655
- diagnostics.push(
1656
- issue(
1657
- "AMBIGUOUS_LEGACY_CATEGORY",
1658
- "blocked",
1659
- "A legacy category cannot be matched to one original revision.",
1660
- { pinId: pin.id, revisionId }
1661
- )
1662
- );
1663
- else category = metadata2.categories[pin.id];
1664
- }
1665
- if (disposition === void 0 && Object.hasOwn(metadata2.dispositions ?? {}, pin.id)) {
1666
- if (duplicatePinIds.has(pin.id))
1667
- diagnostics.push(
1668
- issue(
1669
- "AMBIGUOUS_LEGACY_DISPOSITION",
1670
- "blocked",
1671
- "A legacy owner decision cannot be matched to one original revision.",
1672
- { pinId: pin.id, revisionId }
1673
- )
1674
- );
1675
- else disposition = metadata2.dispositions[pin.id];
1676
- }
1677
- if (category !== void 0 && !CATEGORIES.has(category)) {
1678
- diagnostics.push(
1679
- issue("UNKNOWN_CATEGORY", "blocked", "The comment category is not supported.", {
1680
- pinId: pin.id,
1681
- revisionId
1682
- })
1683
- );
1684
- category = void 0;
1685
- }
1686
- const dispositionValue = typeof disposition === "string" ? disposition : disposition?.disposition;
1687
- if (dispositionValue !== void 0 && !DISPOSITIONS.has(dispositionValue)) {
1688
- diagnostics.push(
1689
- issue("UNKNOWN_DISPOSITION", "blocked", "The owner decision is not supported.", {
1690
- pinId: pin.id,
1691
- revisionId
1692
- })
1693
- );
1694
- disposition = void 0;
1695
- }
1696
- return {
1697
- category,
1698
- disposition,
1699
- dispositionValue: typeof disposition === "string" ? disposition : disposition?.disposition
1700
- };
1701
- }
1702
- function unknownMetadata(metadata2, known, diagnostics) {
1703
- for (const [revisionId, value] of Object.entries(metadata2.byRevision ?? {}).sort(
1704
- ([left], [right]) => compare(left, right)
1705
- )) {
1706
- for (const field of ["categories", "dispositions"]) {
1707
- for (const pinId of Object.keys(value?.[field] ?? {}).sort(compare)) {
1708
- if (!known.has(`${revisionId}:${pinId}`))
1709
- diagnostics.push(
1710
- issue(
1711
- "UNKNOWN_COMMENT_METADATA",
1712
- "blocked",
1713
- "Review metadata targets a comment that is not present in the recorded review history.",
1714
- { revisionId, pinId }
1715
- )
1716
- );
1717
- }
1718
- }
1719
- }
1720
- }
1721
- function compileDesignHandoffResolution(input) {
1722
- const source = clone3(input ?? {});
1723
- const pins = Array.isArray(source.pins) ? source.pins : [];
1724
- if (pins.length > MAX_COMMENTS)
1725
- throw new RangeError("Review resolution exceeds the comment limit.");
1726
- const metadata2 = source.metadata && typeof source.metadata === "object" && !Array.isArray(source.metadata) ? source.metadata : {};
1727
- const diagnostics = [];
1728
- const counts = /* @__PURE__ */ new Map();
1729
- for (const pin of pins) {
1730
- validatePin(pin);
1731
- counts.set(pin.id, (counts.get(pin.id) ?? 0) + 1);
1732
- }
1733
- const duplicatePinIds = new Set([...counts].filter(([, count]) => count > 1).map(([id2]) => id2));
1734
- const known = /* @__PURE__ */ new Set();
1735
- for (const pin of pins) {
1736
- const key = keyOf(pin);
1737
- if (known.has(key)) throw new TypeError(`Duplicate review comment identity: ${key}.`);
1738
- known.add(key);
1739
- }
1740
- unknownMetadata(metadata2, known, diagnostics);
1741
- const items = [...pins].sort((left, right) => compare(keyOf(left), keyOf(right))).map((pin) => {
1742
- const revisionId = revisionOf(pin);
1743
- const current = pin.stale !== true && (!source.currentReviewOf || pin.reviewOf === source.currentReviewOf);
1744
- const resolved = scopedMetadata(metadata2, pin, duplicatePinIds, diagnostics);
1745
- let outcome;
1746
- if (resolved.dispositionValue === "accepted") outcome = "accepted";
1747
- else if (resolved.dispositionValue === "deferred") outcome = "deferred";
1748
- else if (["rejected", "declined"].includes(resolved.dispositionValue)) outcome = "declined";
1749
- else if (["blocker", "change-request"].includes(resolved.category)) outcome = "blocking";
1750
- else outcome = "open";
1751
- if (!OUTCOMES.has(outcome)) throw new Error("Review resolution produced an invalid outcome.");
1752
- if (!current && outcome === "accepted")
1753
- diagnostics.push(
1754
- issue(
1755
- "STALE_ACCEPTED_DECISION",
1756
- "stale",
1757
- "An accepted change belongs to an earlier design revision and must be reviewed again.",
1758
- {
1759
- pinId: pin.id,
1760
- revisionId,
1761
- recoveryAction: {
1762
- id: "review-stale-decision",
1763
- label: "Review this decision against the current design"
1764
- }
1765
- }
1766
- )
1767
- );
1768
- if (current && outcome === "blocking")
1769
- diagnostics.push(
1770
- issue(
1771
- "UNRESOLVED_BLOCKING_COMMENT",
1772
- "blocked",
1773
- "A blocking comment still needs an owner decision.",
1774
- {
1775
- pinId: pin.id,
1776
- revisionId,
1777
- recoveryAction: {
1778
- id: "resolve-blocking-comment",
1779
- label: "Record the owner decision"
1780
- }
1781
- }
1782
- )
1783
- );
1784
- return {
1785
- id: keyOf(pin),
1786
- pinId: pin.id,
1787
- reviewId: pin.reviewId,
1788
- revisionId,
1789
- reviewOf: pin.reviewOf,
1790
- current,
1791
- category: resolved.category ?? "question",
1792
- outcome,
1793
- implementationScope: current && outcome === "accepted",
1794
- anchor: pin.elementId ? { screenId: pin.screenId, elementId: pin.elementId } : pin.screenId ? { screenId: pin.screenId } : pin.anchor?.planrId ? { planrId: pin.anchor.planrId } : { artifactId: pin.artifactId },
1795
- source: {
1796
- text: pin.comment,
1797
- author: clone3(pin.author),
1798
- status: pin.status,
1799
- ...resolved.disposition && typeof resolved.disposition === "object" ? { decision: clone3(resolved.disposition) } : {}
1800
- }
1801
- };
1802
- });
1803
- if (source.historyComplete === false)
1804
- diagnostics.push(
1805
- issue("INCOMPLETE_REVIEW_HISTORY", "blocked", "The complete review history is unavailable.", {
1806
- recoveryAction: {
1807
- id: "restore-review-history",
1808
- label: "Restore the complete review history"
1809
- }
1810
- })
1811
- );
1812
- if (source.synchronizationPending === true)
1813
- diagnostics.push(
1814
- issue(
1815
- "SYNCHRONIZATION_PENDING",
1816
- "blocked",
1817
- "A review decision is still waiting to synchronize.",
1818
- {
1819
- recoveryAction: {
1820
- id: "retry-review-sync",
1821
- label: "Retry review synchronization"
1822
- }
1823
- }
1824
- )
1825
- );
1826
- for (const value of (source.synchronizationIssues ?? []).slice(0, MAX_ISSUES))
1827
- diagnostics.push(
1828
- issue(
1829
- "UNTRUSTED_HOSTED_FEEDBACK",
1830
- "blocked",
1831
- value?.reason || "Hosted feedback could not be validated.",
1832
- {
1833
- pinId: value?.pinId,
1834
- revisionId: value?.revisionId,
1835
- recoveryAction: {
1836
- id: "inspect-review-sync",
1837
- label: "Inspect the rejected hosted feedback"
1838
- }
1839
- }
1840
- )
1841
- );
1842
- if ((source.synchronizationIssues ?? []).length > MAX_ISSUES)
1843
- throw new RangeError("Review resolution exceeds the synchronization issue limit.");
1844
- diagnostics.sort(
1845
- (left, right) => compare(
1846
- `${left.code}:${left.revisionId ?? ""}:${left.pinId ?? ""}:${left.message}`,
1847
- `${right.code}:${right.revisionId ?? ""}:${right.pinId ?? ""}:${right.message}`
1848
- )
1849
- );
1850
- const severity = new Set(diagnostics.map((value) => value.severity));
1851
- const status = severity.has("stale") ? "stale" : severity.has("blocked") ? "blocked" : items.some((item) => item.outcome === "open") ? "attention" : "ready";
1852
- return {
1853
- kind: "openplanr-design-handoff-resolution",
1854
- schemaVersion: "1.0.0",
1855
- currentReviewOf: source.currentReviewOf ?? null,
1856
- status,
1857
- complete: source.historyComplete !== false && !["blocked", "stale"].includes(status),
1858
- items,
1859
- implementationScope: items.filter((item) => item.implementationScope).map((item) => item.id),
1860
- diagnostics
1861
- };
1862
- }
1863
- function canApproveDesignHandoffResolution(value) {
1864
- return Boolean(value?.complete && ["ready", "attention"].includes(value.status));
1865
- }
1866
-
1867
- // packages/design/lib/design/handoff-reader.mjs
1868
- var sections = ["agreedChanges", "openQuestions", "deferred", "rejected"];
1869
- var metadataPath = (current) => join3(current.root, ".design/review-metadata.json");
1870
- var revisionOf2 = (pin) => pin.revisionId ?? pin.reviewId;
1871
- var pinKey = (pin) => `${revisionOf2(pin)}:${pin.id}`;
1872
- function metadata(current, feedback) {
1873
- const local = readJson(metadataPath(current), { version: 0, byRevision: {} });
1874
- const byRevision = structuredClone(feedback.shared?.metadataByRevision ?? {});
1875
- for (const [revision, value] of Object.entries(local.byRevision ?? {})) {
1876
- const remote = byRevision[revision] ?? {};
1877
- byRevision[revision] = {
1878
- categories: { ...remote.categories, ...value.categories },
1879
- dispositions: { ...remote.dispositions, ...value.dispositions }
1880
- };
1881
- }
1882
- const counts = /* @__PURE__ */ new Map();
1883
- for (const pin of feedback.pins) counts.set(pin.id, (counts.get(pin.id) ?? 0) + 1);
1884
- const categories = {}, dispositions = {};
1885
- for (const pin of feedback.pins) {
1886
- const value = byRevision[revisionOf2(pin)];
1887
- if (counts.get(pin.id) === 1 && value) {
1888
- if (Object.hasOwn(value.categories ?? {}, pin.id))
1889
- Object.defineProperty(categories, pin.id, {
1890
- value: value.categories[pin.id],
1891
- enumerable: true
1892
- });
1893
- if (Object.hasOwn(value.dispositions ?? {}, pin.id))
1894
- Object.defineProperty(dispositions, pin.id, {
1895
- value: value.dispositions[pin.id],
1896
- enumerable: true
1897
- });
1898
- }
1899
- if (counts.get(pin.id) === 1 && !Object.hasOwn(categories, pin.id) && Object.hasOwn(local.categories ?? {}, pin.id))
1900
- Object.defineProperty(categories, pin.id, {
1901
- value: local.categories[pin.id],
1902
- enumerable: true
1903
- });
1904
- if (counts.get(pin.id) === 1 && !Object.hasOwn(dispositions, pin.id) && Object.hasOwn(local.dispositions ?? {}, pin.id))
1905
- Object.defineProperty(dispositions, pin.id, {
1906
- value: local.dispositions[pin.id],
1907
- enumerable: true
1908
- });
1909
- }
1910
- return {
1911
- version: local.version,
1912
- categories,
1913
- dispositions,
1914
- byRevision,
1915
- legacy: {
1916
- categories: structuredClone(local.categories ?? {}),
1917
- dispositions: structuredClone(local.dispositions ?? {})
1918
- }
1919
- };
1920
- }
1921
- function findPin(pins, id2, revision) {
1922
- const candidates = pins.filter(
1923
- (pin) => pin.id === id2 && (!revision || revisionOf2(pin) === revision)
1924
- );
1925
- if (candidates.length !== 1)
1926
- throw new Error(
1927
- "The comment identity is missing or ambiguous. Include its original revisionId."
1928
- );
1929
- return candidates[0];
1930
- }
1931
- function resolutionFor(value, shareStatus) {
1932
- return compileDesignHandoffResolution({
1933
- currentReviewOf: value.basis.reviewOf,
1934
- pins: value.feedback.pins,
1935
- metadata: {
1936
- byRevision: value.metadata.byRevision,
1937
- categories: value.metadata.legacy.categories,
1938
- dispositions: value.metadata.legacy.dispositions
1939
- },
1940
- historyComplete: !value.feedback.shared?.issues?.length,
1941
- synchronizationPending: Boolean(shareStatus?.pendingReviewMetadata),
1942
- synchronizationIssues: value.feedback.shared?.issues ?? []
1943
- });
1944
- }
1945
- function snapshot(file, env, shareOptions = {}) {
1946
- const current = currentDesign(file, shareOptions), feedback = readDesignFeedback(file, env, shareOptions), meta = metadata(current, feedback);
1947
- const reviewContext = current.reviewContext ?? emptyReviewContext(current.document);
1948
- const basis = {
1949
- designId: current.document.id,
1950
- sourceRevision: current.revision,
1951
- contextDigest: current.contextDigest ?? reviewDigest(reviewContext),
1952
- reviewOf: digestArtifactEnvelope(current.envelope),
1953
- selectedVariant: feedback.state.selectedVariant ?? current.document.selectedVariant,
1954
- feedbackDigest: reviewDigest({
1955
- pins: feedback.pins,
1956
- metadata: {
1957
- byRevision: meta.byRevision,
1958
- categories: meta.categories,
1959
- dispositions: meta.dispositions
1960
- },
1961
- overall: (feedback.ledger?.reviews ?? []).map((entry) => ({
1962
- reviewId: entry.review.reviewId,
1963
- reviewOf: entry.review.reviewOf,
1964
- overall: entry.review.overall
1965
- })),
1966
- directions: feedback.shared?.directions ?? []
1967
- }),
1968
- verificationDigest: reviewDigest(current.verification),
1969
- feedbackWatermark: Math.max(
1970
- 0,
1971
- ...(feedback.shared?.events ?? []).map((event) => event.sequence ?? 0)
1972
- )
1973
- };
1974
- let shareStatus = null;
1975
- try {
1976
- shareStatus = getDesignShareStatus(file, { env, ...shareOptions });
1977
- } catch (error) {
1978
- if (error.code === "E_DESIGN_PUBLICATION_PENDING") throw error;
1979
- }
1980
- const value = { current, feedback, metadata: meta, reviewContext, basis };
1981
- return { ...value, resolution: resolutionFor(value, shareStatus), shareStatus };
1982
- }
1983
- function readDesignExperience(file, { env = process.env } = {}) {
1984
- const value = snapshot(file, env);
1985
- return {
1986
- ok: true,
1987
- capabilities: { owner: true, revisions: true, handoff: true },
1988
- revision: value.current.revision,
1989
- reviewContext: value.reviewContext,
1990
- contextDigest: value.basis.contextDigest,
1991
- fingerprints: value.current.fingerprints ?? [],
1992
- metadata: value.metadata,
1993
- loadingHistory: false
1994
- };
1995
- }
1996
- function readDesignHandoff(file, { env = process.env, ...shareOptions } = {}) {
1997
- const value = snapshot(file, env, shareOptions);
1998
- const path = join3(dirname3(designSpecPath(value.current.root)), "review-handoff.json");
1999
- const draft = readJson(path, null);
2000
- if (draft) {
2001
- assertDraft(draft);
2002
- const markdownPath = path.replace(/\.json$/u, ".md");
2003
- if (!existsSync3(markdownPath) || readFileSync3(markdownPath, "utf8") !== draft.markdown)
2004
- throw new Error(
2005
- "The handoff Markdown differs from its approved JSON. Rebuild the handoff projection before using it in Plan."
2006
- );
2007
- }
2008
- return {
2009
- ok: true,
2010
- path,
2011
- revision: value.current.revision,
2012
- draft,
2013
- current: Boolean(draft && reviewDigest(draft.basis) === reviewDigest(value.basis)),
2014
- metadata: value.metadata,
2015
- feedback: { pins: value.feedback.pins },
2016
- basis: value.basis,
2017
- resolution: value.resolution
2018
- };
2019
- }
2020
- function readDesignHandoffReadiness(file, { env = process.env, ...shareOptions } = {}) {
2021
- const value = snapshot(file, env, shareOptions);
2022
- const path = join3(dirname3(designSpecPath(value.current.root)), "review-handoff.json");
2023
- const handoff = readJson(path, null);
2024
- if (handoff) assertDraft(handoff);
2025
- const outcomes = new Map(value.resolution.items.map((item) => [item.id, item]));
2026
- const pins = value.feedback.pins.map((pin) => {
2027
- const resolved = outcomes.get(pinKey(pin));
2028
- const disposition = resolved?.outcome === "accepted" ? "accepted" : resolved?.outcome === "deferred" ? "deferred" : resolved?.outcome === "declined" ? "rejected" : void 0;
2029
- return {
2030
- id: pin.id,
2031
- ...pin.screenId ? { screenId: pin.screenId } : {},
2032
- ...pin.anchor?.planrId ? { elementId: pin.anchor.planrId } : {},
2033
- category: resolved?.category,
2034
- ...disposition ? { disposition } : {},
2035
- stale: Boolean(pin.stale)
2036
- };
2037
- });
2038
- const specificationPath = designSpecPath(value.current.root);
2039
- const specification = existsSync3(specificationPath) ? {
2040
- path: "design-spec.md",
2041
- revision: value.current.revision,
2042
- digest: `sha256:${hash(readFileSync3(specificationPath))}`,
2043
- complete: true
2044
- } : void 0;
2045
- const studioState = readJson(join3(value.current.root, ".design/studio-state.json"), {
2046
- state: {}
2047
- }).state;
2048
- const readiness = compileDesignHandoffReadiness({
2049
- document: value.current.document,
2050
- documentPath: "design-document.json",
2051
- sourceRevision: value.current.revision,
2052
- studioState,
2053
- studioStatePath: ".design/studio-state.json",
2054
- specification,
2055
- verification: { path: ".design/verification/current.json", ...value.current.verification },
2056
- review: {
2057
- path: ".design/review.json",
2058
- revision: value.current.revision,
2059
- current: pins.every((pin) => !pin.stale),
2060
- pins
2061
- },
2062
- reviewHandoff: handoff ? {
2063
- ...handoff,
2064
- path: "review-handoff.json",
2065
- digest: `sha256:${handoff.contentHash}`,
2066
- current: reviewDigest(handoff.basis) === reviewDigest(value.basis)
2067
- } : void 0
2068
- });
2069
- return { ok: true, readiness, digest: designHandoffReadinessDigest(readiness) };
2070
- }
2071
- function assertDraft(draft) {
2072
- assertReviewExperience(draft, DESIGN_HANDOFF_SCHEMA);
2073
- const expected = reviewDigest({
2074
- title: draft.title,
2075
- reviewNotes: draft.reviewNotes,
2076
- basis: draft.basis,
2077
- content: draft.content,
2078
- affectedScreens: draft.affectedScreens ?? [],
2079
- verificationGaps: draft.verificationGaps ?? []
2080
- });
2081
- if (draft.contentHash !== expected || draft.status === "approved" && draft.approval?.contentHash !== expected)
2082
- throw new Error(
2083
- "The handoff content does not match its approval digest. Refine it through the handoff utility."
2084
- );
2085
- if (draft.markdown !== renderMarkdown(draft, draft.title))
2086
- throw new Error("The handoff Markdown does not match its approved content.");
2087
- return draft;
2088
- }
2089
-
2090
- // packages/design/lib/design/handoff.mjs
2091
- import { existsSync as existsSync4, writeFileSync as writeFileSync3 } from "node:fs";
2092
- import { dirname as dirname4, join as join4 } from "node:path";
2093
- var conflict2 = (message2) => Object.assign(new Error(message2), { statusCode: 409 });
2094
- function sourceItem(pin, shareUrl) {
2095
- const source = pin.revisionId ? `${shareUrl ?? ""}#revision=${encodeURIComponent(pin.revisionId)}&pin=${encodeURIComponent(pin.id)}` : `#review=${encodeURIComponent(pin.reviewId)}&pin=${encodeURIComponent(pin.id)}`;
2096
- return {
2097
- pinId: pin.id,
2098
- ...pin.screenId ? { screenId: pin.screenId } : {},
2099
- reviewId: pin.reviewId,
2100
- ...pin.revisionId ? { revisionId: pin.revisionId } : {},
2101
- text: pin.comment,
2102
- author: pin.author.name,
2103
- reviewOf: pin.reviewOf,
2104
- stale: Boolean(pin.stale),
2105
- source
2106
- };
2107
- }
2108
- function contentFromFeedback(value, shareUrl) {
2109
- const content = { summary: "", agreedChanges: [], openQuestions: [], deferred: [], rejected: [] };
2110
- const pins = new Map(value.feedback.pins.map((pin) => [pinKey(pin), pin]));
2111
- for (const resolved of value.resolution.items) {
2112
- const pin = pins.get(resolved.id);
2113
- const item = sourceItem(pin, shareUrl);
2114
- if (resolved.outcome === "accepted") content.agreedChanges.push(item);
2115
- else if (resolved.outcome === "deferred") content.deferred.push(item);
2116
- else if (resolved.outcome === "declined") content.rejected.push(item);
2117
- else content.openQuestions.push(item);
2118
- }
2119
- return content;
2120
- }
2121
- function enrichContent(content, value, shareUrl) {
2122
- assertReviewExperience(content, DESIGN_HANDOFF_CONTENT_SCHEMA);
2123
- const seen = /* @__PURE__ */ new Set();
2124
- const enriched = { summary: content.summary };
2125
- for (const key of sections)
2126
- enriched[key] = content[key].map((item) => {
2127
- const pin = findPin(value.feedback.pins, item.pinId, item.revisionId ?? item.reviewId);
2128
- if (seen.has(pinKey(pin)))
2129
- throw new Error("Handoff items must cite distinct recorded comments.");
2130
- seen.add(pinKey(pin));
2131
- const resolved = value.resolution.items.find((itemValue) => itemValue.id === pinKey(pin));
2132
- const expected = key === "agreedChanges" ? ["accepted"] : key === "deferred" ? ["deferred"] : key === "rejected" ? ["declined"] : ["open", "blocking"];
2133
- if (!resolved || !expected.includes(resolved.outcome))
2134
- throw new Error(
2135
- "Record the owner disposition before moving a comment into this handoff section."
2136
- );
2137
- const refinement = item.refinement ?? (item.text !== pin.comment ? item.text : void 0);
2138
- return { ...sourceItem(pin, shareUrl), ...refinement !== void 0 ? { refinement } : {} };
2139
- });
2140
- for (const pin of value.feedback.pins)
2141
- if (!seen.has(pinKey(pin)))
2142
- throw new Error(
2143
- `Keep every recorded review comment in the handoff; ${pinKey(pin)} is missing.`
2144
- );
2145
- return enriched;
2146
- }
2147
- function normalizedLocalMetadata(local, pins) {
2148
- const byRevision = structuredClone(local.byRevision ?? {});
2149
- const counts = /* @__PURE__ */ new Map();
2150
- for (const pin of pins) counts.set(pin.id, (counts.get(pin.id) ?? 0) + 1);
2151
- for (const field of ["categories", "dispositions"]) {
2152
- for (const [pinId, item] of Object.entries(local[field] ?? {})) {
2153
- if (counts.get(pinId) !== 1) continue;
2154
- const pin = pins.find((value) => value.id === pinId);
2155
- const revision = revisionOf2(pin);
2156
- const scoped = byRevision[revision] ?? { categories: {}, dispositions: {} };
2157
- byRevision[revision] = { ...scoped, [field]: { ...scoped[field], [pinId]: item } };
2158
- }
2159
- }
2160
- return { version: local.version ?? 0, byRevision };
2161
- }
2162
- async function preserveReviewErrors(path, action3) {
2163
- let failure, result;
2164
- await withArtifactReviewLock(path, async () => {
2165
- try {
2166
- result = await action3();
2167
- } catch (error) {
2168
- failure = error;
2169
- }
2170
- });
2171
- if (failure) throw failure;
2172
- return result;
2173
- }
2174
- async function updateDesignHandoff(file, input, { env = process.env, ...shareOptions } = {}) {
2175
- if (!input || !["draft", "update", "approve", "category", "disposition"].includes(input.action))
2176
- throw new Error("Unknown design handoff action.");
2177
- const initial = currentDesign(file);
2178
- const unlockRender = await acquireStartLock(join4(initial.root, ".design/render.lock"));
2179
- let outgoing;
2180
- try {
2181
- const unlock = await acquireStartLock(join4(initial.root, ".design/handoff.lock"));
2182
- try {
2183
- await preserveReviewErrors(designReviewPath(file, env), async () => {
2184
- const value = snapshot(file, env, shareOptions);
2185
- if (input.revision !== value.current.revision)
2186
- throw conflict2("The design changed. Refresh the review before updating its handoff.");
2187
- if (["category", "disposition"].includes(input.action)) {
2188
- if (input.version !== value.metadata.version)
2189
- throw conflict2("Review organization changed in another window. Reload before saving.");
2190
- const pin = findPin(value.feedback.pins, input.pinId, input.revisionId ?? input.reviewId);
2191
- if (!pin) throw new Error("The comment is no longer available.");
2192
- const choices = input.action === "category" ? ["question", "suggestion", "change-request", "blocker"] : ["accepted", "deferred", "rejected"];
2193
- if (!choices.includes(input[input.action])) throw new Error(`Invalid ${input.action}.`);
2194
- if (typeof (input.reason ?? "") !== "string" || (input.reason ?? "").length > 16384)
2195
- throw new Error("Disposition reason is too long.");
2196
- if (pin.revisionId) assertDesignShareAvailable({ env, ...shareOptions });
2197
- const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2198
- const local = normalizedLocalMetadata(
2199
- readJson(metadataPath(value.current), { version: 0, byRevision: {} }),
2200
- value.feedback.pins
2201
- );
2202
- const revision = revisionOf2(pin);
2203
- const scoped = local.byRevision?.[revision] ?? { categories: {}, dispositions: {} };
2204
- const valueForRevision = { ...scoped };
2205
- if (input.action === "category")
2206
- valueForRevision.categories = { ...scoped.categories, [pin.id]: input.category };
2207
- else
2208
- valueForRevision.dispositions = {
2209
- ...scoped.dispositions,
2210
- [pin.id]: {
2211
- disposition: input.disposition,
2212
- reason: input.reason ?? "",
2213
- updatedAt,
2214
- author: "Design owner"
2215
- }
2216
- };
2217
- const next = {
2218
- version: value.metadata.version + 1,
2219
- byRevision: { ...local.byRevision, [revision]: valueForRevision }
2220
- };
2221
- atomicJson(metadataPath(value.current), next);
2222
- if (pin.revisionId)
2223
- outgoing = {
2224
- revisionId: pin.revisionId,
2225
- payload: {
2226
- schemaVersion: input.category === "change-request" ? "1.1.0" : "1.0.0",
2227
- kind: input.action,
2228
- author: "Design owner",
2229
- reviewOf: pin.reviewOf,
2230
- pinId: pin.id,
2231
- [input.action]: input[input.action],
2232
- ...input.action === "disposition" ? { reason: input.reason ?? "" } : {},
2233
- updatedAt
2234
- }
2235
- };
2236
- return;
2237
- }
2238
- const path = join4(dirname4(designSpecPath(value.current.root)), "review-handoff.json");
2239
- const previous = readJson(path, null);
2240
- if (previous) assertDraft(previous);
2241
- if (input.version !== (previous?.version ?? 0))
2242
- throw conflict2("The handoff changed in another window. Reload before saving.");
2243
- let shareUrl;
2244
- try {
2245
- shareUrl = getDesignShareStatus(file, { env, ...shareOptions }).url;
2246
- } catch {
2247
- }
2248
- if (input.action === "approve") {
2249
- if (!previous || input.contentHash !== previous.contentHash || reviewDigest(previous.basis) !== reviewDigest(value.basis))
2250
- throw conflict2(
2251
- "The handoff is out of date. Rebuild and review the current draft before approving."
2252
- );
2253
- if (!canApproveDesignHandoffResolution(value.resolution)) {
2254
- const diagnostic = value.resolution.diagnostics[0];
2255
- throw conflict2(
2256
- diagnostic?.message ?? "Resolve the blocking review decisions before approving this handoff."
2257
- );
2258
- }
2259
- if (!previous.content.summary.trim())
2260
- throw new Error("Write or refine the handoff summary before approving it.");
2261
- const approved = {
2262
- ...previous,
2263
- version: previous.version + 1,
2264
- status: "approved",
2265
- approval: { contentHash: previous.contentHash, at: (/* @__PURE__ */ new Date()).toISOString() }
2266
- };
2267
- approved.markdown = renderMarkdown(approved, value.current.document.title);
2268
- assertReviewExperience(approved, DESIGN_HANDOFF_SCHEMA);
2269
- const archive = join4(
2270
- value.current.root,
2271
- ".design/handoff-approvals",
2272
- `${approved.contentHash}.json`
2273
- );
2274
- if (!existsSync4(archive)) atomicJson(archive, approved);
2275
- atomicJson(path, approved);
2276
- writeFileSync3(path.replace(/\.json$/u, ".md"), approved.markdown);
2277
- return;
2278
- }
2279
- const content = input.action === "draft" ? contentFromFeedback(value, shareUrl) : enrichContent(input.content, value, shareUrl);
2280
- if (input.action === "update" && (!previous || reviewDigest(previous.basis) !== reviewDigest(value.basis)))
2281
- throw conflict2("The review changed. Rebuild the draft before refining it.");
2282
- const affectedScreens = [
2283
- ...new Set(
2284
- sections.flatMap((key) => content[key].map((item) => item.screenId).filter(Boolean))
2285
- )
2286
- ];
2287
- const verificationGaps = value.current.verification.status === "verified" ? [] : [`Rendered design verification: ${value.current.verification.status}.`];
2288
- for (const issue2 of value.current.verification.issues ?? [])
2289
- if (issue2.message && verificationGaps.length < 256) verificationGaps.push(issue2.message);
2290
- const reviewNotes = (value.feedback.ledger?.reviews ?? []).filter((entry) => entry.review.overall?.trim()).map((entry) => ({ reviewId: entry.review.reviewId, text: entry.review.overall }));
2291
- const title2 = value.current.document.title;
2292
- const draft = {
2293
- title: title2,
2294
- reviewNotes,
2295
- kind: "openplanr-design-review-handoff",
2296
- schemaVersion: "1.0.0",
2297
- version: (previous?.version ?? 0) + 1,
2298
- status: "draft",
2299
- basis: value.basis,
2300
- content,
2301
- affectedScreens,
2302
- verificationGaps,
2303
- contentHash: reviewDigest({
2304
- title: title2,
2305
- reviewNotes,
2306
- basis: value.basis,
2307
- content,
2308
- affectedScreens,
2309
- verificationGaps
2310
- }),
2311
- markdown: ""
2312
- };
2313
- draft.markdown = renderMarkdown(draft, value.current.document.title);
2314
- assertReviewExperience(draft, DESIGN_HANDOFF_SCHEMA);
2315
- atomicJson(path, draft);
2316
- writeFileSync3(path.replace(/\.json$/u, ".md"), draft.markdown);
2317
- });
2318
- } finally {
2319
- unlock();
2320
- }
2321
- } finally {
2322
- unlockRender();
2323
- }
2324
- let synchronization;
2325
- if (outgoing) {
2326
- try {
2327
- synchronization = await publishDesignReviewMetadata(file, outgoing.payload, {
2328
- revisionId: outgoing.revisionId,
2329
- env,
2330
- ...shareOptions
2331
- });
2332
- } catch (error) {
2333
- synchronization = {
2334
- pending: false,
2335
- error: error.message,
2336
- ...error.code ? { code: error.code } : {}
2337
- };
2338
- }
2339
- }
2340
- return {
2341
- ...readDesignHandoff(file, { env, ...shareOptions }),
2342
- ...synchronization ? { synchronization } : {}
2343
- };
2344
- }
2345
-
2346
- export {
2347
- PLANNING_FOLDER,
2348
- CLI_COMMAND,
2349
- createReviewLedger,
2350
- mergeReviewLedger,
2351
- effectiveReviewDecision,
2352
- ARTIFACT_REVIEW_MAX_STATE_BYTES,
2353
- normalizeArtifactReview,
2354
- readArtifactReviewState,
2355
- writeArtifactReviewState,
2356
- withArtifactReviewLock,
2357
- exportArtifactReview,
2358
- resolveArtifactReviewDestination,
2359
- decodeArtifactReviewSources,
2360
- designReviewKey,
2361
- designReviewPath,
2362
- readDesignFeedback,
2363
- isDesignHandoffRelativePath,
2364
- designImplementationHandoffDigest,
2365
- assertDesignImplementationHandoff,
2366
- readDesignExperience,
2367
- readDesignHandoff,
2368
- readDesignHandoffReadiness,
2369
- updateDesignHandoff
2370
- };