rig-create 0.0.0-stage → 1.0.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 (555) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +163 -2
  3. package/docs/CHECKS.md +1665 -0
  4. package/docs/COLLECTIONS.md +337 -0
  5. package/docs/CRAFT.md +277 -0
  6. package/docs/INTERVIEW.md +886 -0
  7. package/docs/LESSONS.md +172 -0
  8. package/docs/PROJECT.md +2657 -0
  9. package/docs/SOURCE.md +225 -0
  10. package/package.json +44 -4
  11. package/src/agent-bounce.ts +27 -0
  12. package/src/agent-guide.ts +237 -0
  13. package/src/amend.ts +193 -0
  14. package/src/applied-review.ts +104 -0
  15. package/src/ask-coverage.ts +69 -0
  16. package/src/assemble-answers.ts +53 -0
  17. package/src/cg-gates.ts +211 -0
  18. package/src/check-reads.ts +106 -0
  19. package/src/check.ts +1059 -0
  20. package/src/checkpoint-evidence-writer.ts +125 -0
  21. package/src/checkpoint-evidence.ts +152 -0
  22. package/src/choice-writers.ts +271 -0
  23. package/src/choices.ts +378 -0
  24. package/src/cli.ts +371 -0
  25. package/src/collection-answer.ts +136 -0
  26. package/src/collection-binding-reader.ts +496 -0
  27. package/src/collection-binding.ts +478 -0
  28. package/src/collection.ts +348 -0
  29. package/src/config-inputs.ts +155 -0
  30. package/src/config-lost.ts +135 -0
  31. package/src/correction-writers.ts +207 -0
  32. package/src/corrections.ts +685 -0
  33. package/src/diff-classes.ts +219 -0
  34. package/src/diff-map.ts +248 -0
  35. package/src/direction-citation.ts +13 -0
  36. package/src/direction-writers.ts +119 -0
  37. package/src/direction.ts +266 -0
  38. package/src/drafts.ts +363 -0
  39. package/src/feedback.ts +744 -0
  40. package/src/figure-mask.ts +143 -0
  41. package/src/fix-ledger.ts +355 -0
  42. package/src/init.ts +265 -0
  43. package/src/inputs.ts +160 -0
  44. package/src/intake.ts +158 -0
  45. package/src/interview.ts +53 -0
  46. package/src/invariants.ts +132 -0
  47. package/src/job-acts.ts +80 -0
  48. package/src/job-advance.ts +65 -0
  49. package/src/job-close.ts +226 -0
  50. package/src/job-host.ts +529 -0
  51. package/src/job-outbox.ts +383 -0
  52. package/src/job-publication.ts +701 -0
  53. package/src/job-runtime.ts +99 -0
  54. package/src/job-writer.ts +123 -0
  55. package/src/meshes-bust.ts +63 -0
  56. package/src/next.ts +1506 -0
  57. package/src/overrides.ts +42 -0
  58. package/src/package-upgrades.ts +32 -0
  59. package/src/packages-upgrade-check.ts +6 -0
  60. package/src/packages-upgrade.ts +168 -0
  61. package/src/parts-act.ts +96 -0
  62. package/src/parts-check.ts +242 -0
  63. package/src/parts-review.ts +196 -0
  64. package/src/parts-sections.ts +98 -0
  65. package/src/parts-verdict-record.ts +265 -0
  66. package/src/parts-verdict.ts +33 -0
  67. package/src/pause.ts +118 -0
  68. package/src/pins-check.ts +619 -0
  69. package/src/pins.ts +17 -0
  70. package/src/plan-bar-amendments.ts +54 -0
  71. package/src/plan-history.ts +147 -0
  72. package/src/plan-writers.ts +202 -0
  73. package/src/plate-receipt.ts +110 -0
  74. package/src/pre-interview.ts +302 -0
  75. package/src/prepared-inputs.ts +113 -0
  76. package/src/progress-writers.ts +181 -0
  77. package/src/provenance.ts +301 -0
  78. package/src/record-direction-target.ts +26 -0
  79. package/src/record-input.ts +18 -0
  80. package/src/refusal-baseline.ts +55 -0
  81. package/src/refusal.ts +30 -0
  82. package/src/reopen-lapses.ts +80 -0
  83. package/src/reopen.ts +144 -0
  84. package/src/republish.ts +93 -0
  85. package/src/restart.ts +141 -0
  86. package/src/review-act-runtime.ts +16 -0
  87. package/src/review-act.ts +592 -0
  88. package/src/review-package.ts +524 -0
  89. package/src/review-read.ts +238 -0
  90. package/src/review.ts +432 -0
  91. package/src/rig-version-date.ts +22 -0
  92. package/src/rig-version.ts +1047 -0
  93. package/src/run-record.ts +232 -0
  94. package/src/scene-authority.ts +410 -0
  95. package/src/scene-writers.ts +144 -0
  96. package/src/scene.ts +284 -0
  97. package/src/setup-painting.ts +46 -0
  98. package/src/signable.ts +65 -0
  99. package/src/signoff-answers.ts +76 -0
  100. package/src/signoff-checkpoint-act.ts +95 -0
  101. package/src/signoff-target.ts +127 -0
  102. package/src/signoff.ts +331 -0
  103. package/src/split-captions.ts +154 -0
  104. package/src/stage-progress.ts +379 -0
  105. package/src/stages.ts +148 -0
  106. package/src/step-hash.ts +19 -0
  107. package/src/structure-snapshot.ts +109 -0
  108. package/src/structure.ts +166 -0
  109. package/src/templates.ts +16 -0
  110. package/src/waypoints.ts +163 -0
  111. package/templates/agent-guide.md +108 -0
  112. package/templates/collection-agent-guide.md +72 -0
  113. package/templates/collection.json +252 -0
  114. package/templates/example-chain/AGENTS.md +134 -0
  115. package/templates/example-chain/CLAUDE.md +3 -0
  116. package/templates/example-chain/README.md +13 -0
  117. package/templates/example-chain/chain.json +2869 -0
  118. package/templates/example-chain/checkpoints/0a-interview-options/AGENTS.md +134 -0
  119. package/templates/example-chain/checkpoints/0a-interview-options/CLAUDE.md +3 -0
  120. package/templates/example-chain/checkpoints/0a-interview-options/evidence/.gitkeep +0 -0
  121. package/templates/example-chain/checkpoints/0a-interview-options/evidence/approval.txt +1 -0
  122. package/templates/example-chain/checkpoints/0a-interview-options/evidence/interview/rig_type.json +59 -0
  123. package/templates/example-chain/checkpoints/0a-interview-options/evidence/split-config.json +1 -0
  124. package/templates/example-chain/checkpoints/0a-interview-options/inputs/painting.png +0 -0
  125. package/templates/example-chain/checkpoints/0a-interview-options/inputs/references/.gitkeep +0 -0
  126. package/templates/example-chain/checkpoints/0a-interview-options/package.json +9 -0
  127. package/templates/example-chain/checkpoints/0a-interview-options/project.json +295 -0
  128. package/templates/example-chain/checkpoints/0a-interview-options.json +127 -0
  129. package/templates/example-chain/checkpoints/0b-plan-pending/AGENTS.md +134 -0
  130. package/templates/example-chain/checkpoints/0b-plan-pending/CLAUDE.md +3 -0
  131. package/templates/example-chain/checkpoints/0b-plan-pending/evidence/.gitkeep +0 -0
  132. package/templates/example-chain/checkpoints/0b-plan-pending/evidence/approval.txt +1 -0
  133. package/templates/example-chain/checkpoints/0b-plan-pending/evidence/interview/assemble.json +309 -0
  134. package/templates/example-chain/checkpoints/0b-plan-pending/evidence/interview/reference.json +59 -0
  135. package/templates/example-chain/checkpoints/0b-plan-pending/evidence/interview/rig_type.json +59 -0
  136. package/templates/example-chain/checkpoints/0b-plan-pending/evidence/interview/seethrough.json +186 -0
  137. package/templates/example-chain/checkpoints/0b-plan-pending/evidence/split-config.json +1 -0
  138. package/templates/example-chain/checkpoints/0b-plan-pending/inputs/painting.png +0 -0
  139. package/templates/example-chain/checkpoints/0b-plan-pending/inputs/references/.gitkeep +0 -0
  140. package/templates/example-chain/checkpoints/0b-plan-pending/package.json +9 -0
  141. package/templates/example-chain/checkpoints/0b-plan-pending/project.json +685 -0
  142. package/templates/example-chain/checkpoints/0b-plan-pending.json +81 -0
  143. package/templates/example-chain/checkpoints/1-parts-verdict/AGENTS.md +134 -0
  144. package/templates/example-chain/checkpoints/1-parts-verdict/CLAUDE.md +3 -0
  145. package/templates/example-chain/checkpoints/1-parts-verdict/evidence/.gitkeep +0 -0
  146. package/templates/example-chain/checkpoints/1-parts-verdict/evidence/approval.txt +1 -0
  147. package/templates/example-chain/checkpoints/1-parts-verdict/evidence/interview/assemble.json +309 -0
  148. package/templates/example-chain/checkpoints/1-parts-verdict/evidence/interview/reference.json +59 -0
  149. package/templates/example-chain/checkpoints/1-parts-verdict/evidence/interview/rig_type.json +59 -0
  150. package/templates/example-chain/checkpoints/1-parts-verdict/evidence/interview/seethrough.json +186 -0
  151. package/templates/example-chain/checkpoints/1-parts-verdict/evidence/parts-check.json +127 -0
  152. package/templates/example-chain/checkpoints/1-parts-verdict/evidence/split-config.json +1 -0
  153. package/templates/example-chain/checkpoints/1-parts-verdict/inputs/painting.png +0 -0
  154. package/templates/example-chain/checkpoints/1-parts-verdict/inputs/references/.gitkeep +0 -0
  155. package/templates/example-chain/checkpoints/1-parts-verdict/package.json +9 -0
  156. package/templates/example-chain/checkpoints/1-parts-verdict/project.json +955 -0
  157. package/templates/example-chain/checkpoints/1-parts-verdict.json +204 -0
  158. package/templates/example-chain/checkpoints/2-rigging-answers/AGENTS.md +134 -0
  159. package/templates/example-chain/checkpoints/2-rigging-answers/CLAUDE.md +3 -0
  160. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/.gitkeep +0 -0
  161. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/approval.txt +1 -0
  162. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/diff-components.json +34 -0
  163. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/interview/assemble.json +309 -0
  164. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/interview/bones.json +190 -0
  165. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/interview/reference.json +59 -0
  166. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/interview/rig_type.json +59 -0
  167. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/interview/seethrough.json +186 -0
  168. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/parts-check.json +127 -0
  169. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/parts-review.json +88 -0
  170. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/review-reads/a12dea02d32508acc99386f894af76a14f1c85f45f804db2aa6b4dea5297973f.json +59 -0
  171. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/split-config.json +1 -0
  172. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/structure.json +30 -0
  173. package/templates/example-chain/checkpoints/2-rigging-answers/inputs/painting.png +0 -0
  174. package/templates/example-chain/checkpoints/2-rigging-answers/inputs/references/.gitkeep +0 -0
  175. package/templates/example-chain/checkpoints/2-rigging-answers/package.json +9 -0
  176. package/templates/example-chain/checkpoints/2-rigging-answers/project.json +1198 -0
  177. package/templates/example-chain/checkpoints/2-rigging-answers.json +254 -0
  178. package/templates/example-chain/checkpoints/3-rig-review/AGENTS.md +134 -0
  179. package/templates/example-chain/checkpoints/3-rig-review/CLAUDE.md +3 -0
  180. package/templates/example-chain/checkpoints/3-rig-review/evidence/.gitkeep +0 -0
  181. package/templates/example-chain/checkpoints/3-rig-review/evidence/approval.txt +1 -0
  182. package/templates/example-chain/checkpoints/3-rig-review/evidence/diff-components.json +34 -0
  183. package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/assemble.json +309 -0
  184. package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/bones.json +190 -0
  185. package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/constraints.json +272 -0
  186. package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/meshes.json +190 -0
  187. package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/motion.json +272 -0
  188. package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/reference.json +59 -0
  189. package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/rig_type.json +59 -0
  190. package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/seethrough.json +186 -0
  191. package/templates/example-chain/checkpoints/3-rig-review/evidence/parts-check.json +127 -0
  192. package/templates/example-chain/checkpoints/3-rig-review/evidence/parts-review.json +88 -0
  193. package/templates/example-chain/checkpoints/3-rig-review/evidence/review-reads/a12dea02d32508acc99386f894af76a14f1c85f45f804db2aa6b4dea5297973f.json +59 -0
  194. package/templates/example-chain/checkpoints/3-rig-review/evidence/setup.png +0 -0
  195. package/templates/example-chain/checkpoints/3-rig-review/evidence/split-config.json +1 -0
  196. package/templates/example-chain/checkpoints/3-rig-review/evidence/structure.json +30 -0
  197. package/templates/example-chain/checkpoints/3-rig-review/evidence/v1/config.json +88 -0
  198. package/templates/example-chain/checkpoints/3-rig-review/evidence/v1/interview-requirements.json +8 -0
  199. package/templates/example-chain/checkpoints/3-rig-review/inputs/painting.png +0 -0
  200. package/templates/example-chain/checkpoints/3-rig-review/inputs/references/.gitkeep +0 -0
  201. package/templates/example-chain/checkpoints/3-rig-review/package.json +9 -0
  202. package/templates/example-chain/checkpoints/3-rig-review/project.json +1909 -0
  203. package/templates/example-chain/checkpoints/3-rig-review.json +424 -0
  204. package/templates/example-chain/checkpoints/4-direction-post/AGENTS.md +134 -0
  205. package/templates/example-chain/checkpoints/4-direction-post/CLAUDE.md +3 -0
  206. package/templates/example-chain/checkpoints/4-direction-post/evidence/.gitkeep +0 -0
  207. package/templates/example-chain/checkpoints/4-direction-post/evidence/approval.txt +1 -0
  208. package/templates/example-chain/checkpoints/4-direction-post/evidence/diff-components.json +34 -0
  209. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/assemble.json +309 -0
  210. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/bones.json +190 -0
  211. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/camera_depth.json +102 -0
  212. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/constraints.json +272 -0
  213. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/meshes.json +190 -0
  214. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/motion.json +272 -0
  215. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/post_composition.json +122 -0
  216. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/post_timing.json +102 -0
  217. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/reference.json +59 -0
  218. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/rig_type.json +59 -0
  219. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/seethrough.json +186 -0
  220. package/templates/example-chain/checkpoints/4-direction-post/evidence/parts-check.json +127 -0
  221. package/templates/example-chain/checkpoints/4-direction-post/evidence/parts-review.json +88 -0
  222. package/templates/example-chain/checkpoints/4-direction-post/evidence/review-reads/a12dea02d32508acc99386f894af76a14f1c85f45f804db2aa6b4dea5297973f.json +59 -0
  223. package/templates/example-chain/checkpoints/4-direction-post/evidence/setup.png +0 -0
  224. package/templates/example-chain/checkpoints/4-direction-post/evidence/split-config.json +1 -0
  225. package/templates/example-chain/checkpoints/4-direction-post/evidence/structure.json +30 -0
  226. package/templates/example-chain/checkpoints/4-direction-post/evidence/v1/config.json +88 -0
  227. package/templates/example-chain/checkpoints/4-direction-post/evidence/v1/interview-requirements.json +8 -0
  228. package/templates/example-chain/checkpoints/4-direction-post/inputs/painting.png +0 -0
  229. package/templates/example-chain/checkpoints/4-direction-post/inputs/references/.gitkeep +0 -0
  230. package/templates/example-chain/checkpoints/4-direction-post/package.json +9 -0
  231. package/templates/example-chain/checkpoints/4-direction-post/project.json +2302 -0
  232. package/templates/example-chain/checkpoints/4-direction-post.json +456 -0
  233. package/templates/example-chain/evidence/.gitkeep +0 -0
  234. package/templates/example-chain/evidence/approval.txt +1 -0
  235. package/templates/example-chain/evidence/captions.json +101 -0
  236. package/templates/example-chain/evidence/diff-components.json +34 -0
  237. package/templates/example-chain/evidence/direction/v2.json +74 -0
  238. package/templates/example-chain/evidence/direction-input.json +74 -0
  239. package/templates/example-chain/evidence/interview/assemble.json +309 -0
  240. package/templates/example-chain/evidence/interview/bones.json +190 -0
  241. package/templates/example-chain/evidence/interview/camera_depth.json +102 -0
  242. package/templates/example-chain/evidence/interview/constraints.json +272 -0
  243. package/templates/example-chain/evidence/interview/meshes.json +190 -0
  244. package/templates/example-chain/evidence/interview/motion.json +272 -0
  245. package/templates/example-chain/evidence/interview/post_composition.json +122 -0
  246. package/templates/example-chain/evidence/interview/post_timing.json +102 -0
  247. package/templates/example-chain/evidence/interview/reference.json +59 -0
  248. package/templates/example-chain/evidence/interview/rig_type.json +59 -0
  249. package/templates/example-chain/evidence/interview/seethrough.json +186 -0
  250. package/templates/example-chain/evidence/parts-check.json +127 -0
  251. package/templates/example-chain/evidence/parts-review.json +88 -0
  252. package/templates/example-chain/evidence/review-reads/a12dea02d32508acc99386f894af76a14f1c85f45f804db2aa6b4dea5297973f.json +59 -0
  253. package/templates/example-chain/evidence/scene/s1.json +65 -0
  254. package/templates/example-chain/evidence/scene-citations.json +9 -0
  255. package/templates/example-chain/evidence/setup.png +0 -0
  256. package/templates/example-chain/evidence/split-config.json +1 -0
  257. package/templates/example-chain/evidence/structure.json +30 -0
  258. package/templates/example-chain/evidence/v1/config.json +88 -0
  259. package/templates/example-chain/evidence/v1/interview-requirements.json +8 -0
  260. package/templates/example-chain/inputs/background.png +0 -0
  261. package/templates/example-chain/inputs/painting.png +0 -0
  262. package/templates/example-chain/inputs/references/.gitkeep +0 -0
  263. package/templates/example-chain/package.json +9 -0
  264. package/templates/example-chain/project.json +2593 -0
  265. package/templates/example-diff/diff-components.json +71 -0
  266. package/templates/example-diff/recomposite_rig.png +0 -0
  267. package/templates/example-direction/approval.txt +1 -0
  268. package/templates/example-direction/derived/review/v1/out/check/build/rectangle.png +0 -0
  269. package/templates/example-direction/derived/review/v1/out/check/build/run.json +40 -0
  270. package/templates/example-direction/derived/review/v1/out/check/build/skeleton.atlas +8 -0
  271. package/templates/example-direction/derived/review/v1/out/check/build/skeleton.json +73 -0
  272. package/templates/example-direction/derived/review/v1/out/check/build/skeleton.model.json +195 -0
  273. package/templates/example-direction/evidence/direction/v2.json +69 -0
  274. package/templates/example-direction/evidence/direction/v3.json +59 -0
  275. package/templates/example-direction/evidence/images/rectangle.png +0 -0
  276. package/templates/example-direction/evidence/input.json +69 -0
  277. package/templates/example-direction/evidence/motion.json +33 -0
  278. package/templates/example-direction/evidence/patch.json +23 -0
  279. package/templates/example-direction/evidence/rig-config.json +55 -0
  280. package/templates/example-direction/evidence/still.png +0 -0
  281. package/templates/example-direction/project.json +372 -0
  282. package/templates/example-direction/reasoning.txt +1 -0
  283. package/templates/example-direction/words.txt +1 -0
  284. package/templates/example-draft/README.md +28 -0
  285. package/templates/example-draft/config.json +71 -0
  286. package/templates/example-draft/derived/drafts/lantern/config.json +71 -0
  287. package/templates/example-draft/derived/drafts/lantern/input/painting_in_rig.png +0 -0
  288. package/templates/example-draft/derived/drafts/lantern/input/parts/background.png +0 -0
  289. package/templates/example-draft/derived/drafts/lantern/input/parts/eye_l.png +0 -0
  290. package/templates/example-draft/derived/drafts/lantern/input/parts/eye_r.png +0 -0
  291. package/templates/example-draft/derived/drafts/lantern/input/parts/face.png +0 -0
  292. package/templates/example-draft/derived/drafts/lantern/input/parts/lantern.png +0 -0
  293. package/templates/example-draft/derived/drafts/lantern/input/parts/torso.png +0 -0
  294. package/templates/example-draft/derived/drafts/lantern/input/parts.json +188 -0
  295. package/templates/example-draft/derived/drafts/lantern/input/recomposite_rig.png +0 -0
  296. package/templates/example-draft/derived/drafts/lantern/interview-requirements.json +8 -0
  297. package/templates/example-draft/derived/drafts/lantern/out/check/build/run.json +40 -0
  298. package/templates/example-draft/derived/drafts/lantern/out/check/build/skeleton.atlas +28 -0
  299. package/templates/example-draft/derived/drafts/lantern/out/check/build/skeleton.json +193 -0
  300. package/templates/example-draft/derived/drafts/lantern/out/check/build/skeleton.model.json +483 -0
  301. package/templates/example-draft/derived/drafts/lantern/out/check/build/skeleton.png +0 -0
  302. package/templates/example-draft/derived/drafts/lantern/out/images/background.png +0 -0
  303. package/templates/example-draft/derived/drafts/lantern/out/images/eye_l.png +0 -0
  304. package/templates/example-draft/derived/drafts/lantern/out/images/eye_r.png +0 -0
  305. package/templates/example-draft/derived/drafts/lantern/out/images/face.png +0 -0
  306. package/templates/example-draft/derived/drafts/lantern/out/images/lantern.png +0 -0
  307. package/templates/example-draft/derived/drafts/lantern/out/images/torso.png +0 -0
  308. package/templates/example-draft/derived/drafts/lantern/out/mesh_report.json +1 -0
  309. package/templates/example-draft/derived/drafts/lantern/out/motion.json +137 -0
  310. package/templates/example-draft/derived/drafts/lantern/out/rig.json +102 -0
  311. package/templates/example-draft/derived/drafts/lantern/out/run.json +52 -0
  312. package/templates/example-draft/derived/parts/rig/painting_in_rig.png +0 -0
  313. package/templates/example-draft/derived/parts/rig/parts/background.png +0 -0
  314. package/templates/example-draft/derived/parts/rig/parts/eye_l.png +0 -0
  315. package/templates/example-draft/derived/parts/rig/parts/eye_r.png +0 -0
  316. package/templates/example-draft/derived/parts/rig/parts/face.png +0 -0
  317. package/templates/example-draft/derived/parts/rig/parts/lantern.png +0 -0
  318. package/templates/example-draft/derived/parts/rig/parts/torso.png +0 -0
  319. package/templates/example-draft/derived/parts/rig/parts.json +188 -0
  320. package/templates/example-draft/derived/parts/rig/recomposite_rig.png +0 -0
  321. package/templates/example-draft/derived/review/v1/config.json +71 -0
  322. package/templates/example-draft/derived/review/v1/interview-requirements.json +8 -0
  323. package/templates/example-draft/derived/review/v1/out/check/build/run.json +40 -0
  324. package/templates/example-draft/derived/review/v1/out/check/build/skeleton.atlas +28 -0
  325. package/templates/example-draft/derived/review/v1/out/check/build/skeleton.json +193 -0
  326. package/templates/example-draft/derived/review/v1/out/check/build/skeleton.model.json +483 -0
  327. package/templates/example-draft/derived/review/v1/out/check/build/skeleton.png +0 -0
  328. package/templates/example-draft/derived/review/v1/out/parts/background.png +0 -0
  329. package/templates/example-draft/derived/review/v1/out/parts/eye_l.png +0 -0
  330. package/templates/example-draft/derived/review/v1/out/parts/eye_r.png +0 -0
  331. package/templates/example-draft/derived/review/v1/out/parts/face.png +0 -0
  332. package/templates/example-draft/derived/review/v1/out/parts/lantern.png +0 -0
  333. package/templates/example-draft/derived/review/v1/out/parts/torso.png +0 -0
  334. package/templates/example-draft/derived/review/v1/out/parts.json +188 -0
  335. package/templates/example-draft/derived/review/v1/out/rig/images/background.png +0 -0
  336. package/templates/example-draft/derived/review/v1/out/rig/images/eye_l.png +0 -0
  337. package/templates/example-draft/derived/review/v1/out/rig/images/eye_r.png +0 -0
  338. package/templates/example-draft/derived/review/v1/out/rig/images/face.png +0 -0
  339. package/templates/example-draft/derived/review/v1/out/rig/images/lantern.png +0 -0
  340. package/templates/example-draft/derived/review/v1/out/rig/images/torso.png +0 -0
  341. package/templates/example-draft/derived/review/v1/out/rig/mesh_report.json +1 -0
  342. package/templates/example-draft/derived/review/v1/out/rig/motion.json +137 -0
  343. package/templates/example-draft/derived/review/v1/out/rig/rig.json +102 -0
  344. package/templates/example-draft/derived/review/v1/out/rig/run.json +52 -0
  345. package/templates/example-draft/derived/review/v2/config.json +71 -0
  346. package/templates/example-draft/derived/review/v2/interview-requirements.json +8 -0
  347. package/templates/example-draft/derived/review/v2/out/check/build/run.json +40 -0
  348. package/templates/example-draft/derived/review/v2/out/check/build/skeleton.atlas +28 -0
  349. package/templates/example-draft/derived/review/v2/out/check/build/skeleton.json +193 -0
  350. package/templates/example-draft/derived/review/v2/out/check/build/skeleton.model.json +483 -0
  351. package/templates/example-draft/derived/review/v2/out/check/build/skeleton.png +0 -0
  352. package/templates/example-draft/derived/review/v2/out/parts/background.png +0 -0
  353. package/templates/example-draft/derived/review/v2/out/parts/eye_l.png +0 -0
  354. package/templates/example-draft/derived/review/v2/out/parts/eye_r.png +0 -0
  355. package/templates/example-draft/derived/review/v2/out/parts/face.png +0 -0
  356. package/templates/example-draft/derived/review/v2/out/parts/lantern.png +0 -0
  357. package/templates/example-draft/derived/review/v2/out/parts/torso.png +0 -0
  358. package/templates/example-draft/derived/review/v2/out/parts.json +188 -0
  359. package/templates/example-draft/derived/review/v2/out/rig/images/background.png +0 -0
  360. package/templates/example-draft/derived/review/v2/out/rig/images/eye_l.png +0 -0
  361. package/templates/example-draft/derived/review/v2/out/rig/images/eye_r.png +0 -0
  362. package/templates/example-draft/derived/review/v2/out/rig/images/face.png +0 -0
  363. package/templates/example-draft/derived/review/v2/out/rig/images/lantern.png +0 -0
  364. package/templates/example-draft/derived/review/v2/out/rig/images/torso.png +0 -0
  365. package/templates/example-draft/derived/review/v2/out/rig/mesh_report.json +1 -0
  366. package/templates/example-draft/derived/review/v2/out/rig/motion.json +137 -0
  367. package/templates/example-draft/derived/review/v2/out/rig/rig.json +102 -0
  368. package/templates/example-draft/derived/review/v2/out/rig/run.json +52 -0
  369. package/templates/example-draft/evidence/diff-components.json +71 -0
  370. package/templates/example-draft/evidence/direction/v4.json +64 -0
  371. package/templates/example-draft/evidence/direction-input-v4.json +64 -0
  372. package/templates/example-draft/evidence/fix-ledger/lantern.json +17 -0
  373. package/templates/example-draft/evidence/parts-check.json +127 -0
  374. package/templates/example-draft/evidence/parts-review.json +88 -0
  375. package/templates/example-draft/evidence/review-reads/4ed0507381809746443021554e012032be5b5f8417da0f2e525e9b6df16e9f05.json +59 -0
  376. package/templates/example-draft/evidence/scene/s1.json +65 -0
  377. package/templates/example-draft/evidence/scene-background.png +0 -0
  378. package/templates/example-draft/evidence/scene-citations.json +9 -0
  379. package/templates/example-draft/evidence/structure-decisions.json +9 -0
  380. package/templates/example-draft/evidence/v1/interview-config.json +71 -0
  381. package/templates/example-draft/evidence/v1/interview-requirements.json +8 -0
  382. package/templates/example-draft/evidence/v1/review-config.json +71 -0
  383. package/templates/example-draft/evidence/v2/interview-config.json +71 -0
  384. package/templates/example-draft/evidence/v2/interview-requirements.json +8 -0
  385. package/templates/example-draft/evidence/v2/review-config.json +71 -0
  386. package/templates/example-draft/input/painting_in_rig.png +0 -0
  387. package/templates/example-draft/input/parts/background.png +0 -0
  388. package/templates/example-draft/input/parts/eye_l.png +0 -0
  389. package/templates/example-draft/input/parts/eye_r.png +0 -0
  390. package/templates/example-draft/input/parts/face.png +0 -0
  391. package/templates/example-draft/input/parts/lantern.png +0 -0
  392. package/templates/example-draft/input/parts/torso.png +0 -0
  393. package/templates/example-draft/input/parts.json +188 -0
  394. package/templates/example-draft/input/recomposite_rig.png +0 -0
  395. package/templates/example-draft/inputs/painting.png +0 -0
  396. package/templates/example-draft/interview-requirements.json +8 -0
  397. package/templates/example-draft/out/check/build/run.json +40 -0
  398. package/templates/example-draft/out/check/build/skeleton.atlas +28 -0
  399. package/templates/example-draft/out/check/build/skeleton.json +193 -0
  400. package/templates/example-draft/out/check/build/skeleton.model.json +483 -0
  401. package/templates/example-draft/out/check/build/skeleton.png +0 -0
  402. package/templates/example-draft/out/images/background.png +0 -0
  403. package/templates/example-draft/out/images/eye_l.png +0 -0
  404. package/templates/example-draft/out/images/eye_r.png +0 -0
  405. package/templates/example-draft/out/images/face.png +0 -0
  406. package/templates/example-draft/out/images/lantern.png +0 -0
  407. package/templates/example-draft/out/images/torso.png +0 -0
  408. package/templates/example-draft/out/mesh_report.json +1 -0
  409. package/templates/example-draft/out/motion.json +137 -0
  410. package/templates/example-draft/out/rig.json +102 -0
  411. package/templates/example-draft/out/run.json +52 -0
  412. package/templates/example-draft/outputs/v1/out/check/build/run.json +40 -0
  413. package/templates/example-draft/outputs/v1/out/check/build/skeleton.atlas +28 -0
  414. package/templates/example-draft/outputs/v1/out/check/build/skeleton.json +193 -0
  415. package/templates/example-draft/outputs/v1/out/check/build/skeleton.model.json +483 -0
  416. package/templates/example-draft/outputs/v1/out/check/build/skeleton.png +0 -0
  417. package/templates/example-draft/outputs/v1/out/painting_in_rig.png +0 -0
  418. package/templates/example-draft/outputs/v1/out/parts/background.png +0 -0
  419. package/templates/example-draft/outputs/v1/out/parts/eye_l.png +0 -0
  420. package/templates/example-draft/outputs/v1/out/parts/eye_r.png +0 -0
  421. package/templates/example-draft/outputs/v1/out/parts/face.png +0 -0
  422. package/templates/example-draft/outputs/v1/out/parts/lantern.png +0 -0
  423. package/templates/example-draft/outputs/v1/out/parts/torso.png +0 -0
  424. package/templates/example-draft/outputs/v1/out/parts.json +188 -0
  425. package/templates/example-draft/outputs/v1/out/rig/images/background.png +0 -0
  426. package/templates/example-draft/outputs/v1/out/rig/images/eye_l.png +0 -0
  427. package/templates/example-draft/outputs/v1/out/rig/images/eye_r.png +0 -0
  428. package/templates/example-draft/outputs/v1/out/rig/images/face.png +0 -0
  429. package/templates/example-draft/outputs/v1/out/rig/images/lantern.png +0 -0
  430. package/templates/example-draft/outputs/v1/out/rig/images/torso.png +0 -0
  431. package/templates/example-draft/outputs/v1/out/rig/mesh_report.json +1 -0
  432. package/templates/example-draft/outputs/v1/out/rig/motion.json +137 -0
  433. package/templates/example-draft/outputs/v1/out/rig/rig.json +102 -0
  434. package/templates/example-draft/outputs/v1/out/rig/run.json +52 -0
  435. package/templates/example-draft/outputs/v2/out/check/build/run.json +40 -0
  436. package/templates/example-draft/outputs/v2/out/check/build/skeleton.atlas +28 -0
  437. package/templates/example-draft/outputs/v2/out/check/build/skeleton.json +193 -0
  438. package/templates/example-draft/outputs/v2/out/check/build/skeleton.model.json +483 -0
  439. package/templates/example-draft/outputs/v2/out/check/build/skeleton.png +0 -0
  440. package/templates/example-draft/outputs/v2/out/painting_in_rig.png +0 -0
  441. package/templates/example-draft/outputs/v2/out/parts/background.png +0 -0
  442. package/templates/example-draft/outputs/v2/out/parts/eye_l.png +0 -0
  443. package/templates/example-draft/outputs/v2/out/parts/eye_r.png +0 -0
  444. package/templates/example-draft/outputs/v2/out/parts/face.png +0 -0
  445. package/templates/example-draft/outputs/v2/out/parts/lantern.png +0 -0
  446. package/templates/example-draft/outputs/v2/out/parts/torso.png +0 -0
  447. package/templates/example-draft/outputs/v2/out/parts.json +188 -0
  448. package/templates/example-draft/outputs/v2/out/rig/images/background.png +0 -0
  449. package/templates/example-draft/outputs/v2/out/rig/images/eye_l.png +0 -0
  450. package/templates/example-draft/outputs/v2/out/rig/images/eye_r.png +0 -0
  451. package/templates/example-draft/outputs/v2/out/rig/images/face.png +0 -0
  452. package/templates/example-draft/outputs/v2/out/rig/images/lantern.png +0 -0
  453. package/templates/example-draft/outputs/v2/out/rig/images/torso.png +0 -0
  454. package/templates/example-draft/outputs/v2/out/rig/mesh_report.json +1 -0
  455. package/templates/example-draft/outputs/v2/out/rig/motion.json +137 -0
  456. package/templates/example-draft/outputs/v2/out/rig/rig.json +102 -0
  457. package/templates/example-draft/outputs/v2/out/rig/run.json +52 -0
  458. package/templates/example-interview-choice.json +1068 -0
  459. package/templates/example-interview-options/constraint.json +376 -0
  460. package/templates/example-interview-options/example-assemble.json +376 -0
  461. package/templates/example-interview-options/example-bones.json +190 -0
  462. package/templates/example-interview-options/example-camera-depth.json +99 -0
  463. package/templates/example-interview-options/example-constraints.json +272 -0
  464. package/templates/example-interview-options/example-meshes.json +190 -0
  465. package/templates/example-interview-options/example-motion.json +272 -0
  466. package/templates/example-interview-options/example-picks.json +15 -0
  467. package/templates/example-interview-options/example-post-composition.json +110 -0
  468. package/templates/example-interview-options/example-post-timing.json +105 -0
  469. package/templates/example-interview-options/example-reference.json +63 -0
  470. package/templates/example-interview-options/example-rig-type.json +63 -0
  471. package/templates/example-interview-options/example-seethrough.json +190 -0
  472. package/templates/example-interview-options/fullscreen.json +63 -0
  473. package/templates/example-interview-options/other-explicit.json +63 -0
  474. package/templates/example-interview-options/other-pending.json +63 -0
  475. package/templates/example-interview-options/other-picks.json +16 -0
  476. package/templates/example-interview.md +213 -0
  477. package/templates/example-invariants/invariants.json +1339 -0
  478. package/templates/example-prepared/prepared.svg +1 -0
  479. package/templates/example-prepared/source.svg +1 -0
  480. package/templates/example-project.json +4259 -0
  481. package/templates/example-remesh/AGENTS.md +134 -0
  482. package/templates/example-remesh/CLAUDE.md +3 -0
  483. package/templates/example-remesh/evidence/.gitkeep +0 -0
  484. package/templates/example-remesh/evidence/approval.txt +1 -0
  485. package/templates/example-remesh/evidence/captions.json +101 -0
  486. package/templates/example-remesh/evidence/diff-components.json +34 -0
  487. package/templates/example-remesh/evidence/direction/v4.json +74 -0
  488. package/templates/example-remesh/evidence/direction/v5.json +74 -0
  489. package/templates/example-remesh/evidence/direction-input-v4.json +74 -0
  490. package/templates/example-remesh/evidence/direction-input-v5.json +74 -0
  491. package/templates/example-remesh/evidence/interview/assemble.json +309 -0
  492. package/templates/example-remesh/evidence/interview/bones.json +190 -0
  493. package/templates/example-remesh/evidence/interview/camera_depth.json +103 -0
  494. package/templates/example-remesh/evidence/interview/constraints.json +272 -0
  495. package/templates/example-remesh/evidence/interview/meshes.json +190 -0
  496. package/templates/example-remesh/evidence/interview/motion.json +272 -0
  497. package/templates/example-remesh/evidence/interview/post_composition.json +123 -0
  498. package/templates/example-remesh/evidence/interview/post_timing.json +103 -0
  499. package/templates/example-remesh/evidence/interview/reference.json +59 -0
  500. package/templates/example-remesh/evidence/interview/rig_type.json +59 -0
  501. package/templates/example-remesh/evidence/interview/seethrough.json +186 -0
  502. package/templates/example-remesh/evidence/parts-check.json +127 -0
  503. package/templates/example-remesh/evidence/parts-review.json +88 -0
  504. package/templates/example-remesh/evidence/reopen.txt +1 -0
  505. package/templates/example-remesh/evidence/review-reads/6a17db694db96f1b6049a61a017e0c8e66757b982b8851e0187e561cfcf9af68.json +59 -0
  506. package/templates/example-remesh/evidence/scene/s1.json +69 -0
  507. package/templates/example-remesh/evidence/scene-citations.json +9 -0
  508. package/templates/example-remesh/evidence/setup.png +0 -0
  509. package/templates/example-remesh/evidence/split-config.json +1 -0
  510. package/templates/example-remesh/evidence/structure.json +30 -0
  511. package/templates/example-remesh/evidence/v1/registration/at-source.json +44 -0
  512. package/templates/example-remesh/evidence/v1/registration/citations.json +68 -0
  513. package/templates/example-remesh/evidence/v1/registration/compile-report.json +368 -0
  514. package/templates/example-remesh/evidence/v1/registration/config-source.json +88 -0
  515. package/templates/example-remesh/evidence/v1/registration/config.json +88 -0
  516. package/templates/example-remesh/evidence/v1/registration/interview-requirements.json +8 -0
  517. package/templates/example-remesh/evidence/v1/registration/package-lock.json +11 -0
  518. package/templates/example-remesh/evidence/v1/registration/path-adapter.json +1 -0
  519. package/templates/example-remesh/evidence/v1/registration/rigc-run-1.json +44 -0
  520. package/templates/example-remesh/evidence/v2/registration/at-source.json +44 -0
  521. package/templates/example-remesh/evidence/v2/registration/citations.json +68 -0
  522. package/templates/example-remesh/evidence/v2/registration/compile-report.json +368 -0
  523. package/templates/example-remesh/evidence/v2/registration/config-source.json +88 -0
  524. package/templates/example-remesh/evidence/v2/registration/config.json +88 -0
  525. package/templates/example-remesh/evidence/v2/registration/interview-requirements.json +8 -0
  526. package/templates/example-remesh/evidence/v2/registration/package-lock.json +11 -0
  527. package/templates/example-remesh/evidence/v2/registration/path-adapter.json +1 -0
  528. package/templates/example-remesh/evidence/v2/registration/rigc-run-1.json +44 -0
  529. package/templates/example-remesh/evidence/v3/registration/at-source.json +44 -0
  530. package/templates/example-remesh/evidence/v3/registration/citations.json +68 -0
  531. package/templates/example-remesh/evidence/v3/registration/compile-report.json +368 -0
  532. package/templates/example-remesh/evidence/v3/registration/config-source.json +88 -0
  533. package/templates/example-remesh/evidence/v3/registration/config.json +88 -0
  534. package/templates/example-remesh/evidence/v3/registration/interview-requirements.json +8 -0
  535. package/templates/example-remesh/evidence/v3/registration/package-lock.json +11 -0
  536. package/templates/example-remesh/evidence/v3/registration/path-adapter.json +1 -0
  537. package/templates/example-remesh/evidence/v3/registration/rigc-run-1.json +44 -0
  538. package/templates/example-remesh/inputs/background.png +0 -0
  539. package/templates/example-remesh/inputs/painting.png +0 -0
  540. package/templates/example-remesh/inputs/references/.gitkeep +0 -0
  541. package/templates/example-remesh/package.json +9 -0
  542. package/templates/example-remesh/project.json +3916 -0
  543. package/templates/example-reopened/project.json +4568 -0
  544. package/templates/example-review/config.json +1 -0
  545. package/templates/example-review/out/check/build/skeleton.model.json +1 -0
  546. package/templates/example-review/out/parts/piece.svg +1 -0
  547. package/templates/example-review/out/parts.json +1 -0
  548. package/templates/example-review/out/rig/mesh_report.json +1 -0
  549. package/templates/example-review/out/rig/motion.json +1 -0
  550. package/templates/example-review/out/rig/rig.json +1 -0
  551. package/templates/example-split/captions.json +357 -0
  552. package/templates/example-split/config.json +215 -0
  553. package/templates/legacy-example-project.json +1071 -0
  554. package/templates/project.json +199 -0
  555. package/templates/shared/conventions.json +23 -0
package/src/stages.ts ADDED
@@ -0,0 +1,148 @@
1
+ import type { JsonObject } from './templates.ts';
2
+
3
+ /**
4
+ * The project's stages, in order, and how each one is left — the one place the
5
+ * order is written (docs/PROJECT.md § Stages). `interview --check` reads it;
6
+ * the review order and the post hand-off (src/review.ts) read `REVIEW_ACCEPTS`
7
+ * below rather than naming stages themselves.
8
+ *
9
+ * `closed_by`:
10
+ * - `signoff`: the stage ends only on a human `signoffs[]` entry naming it
11
+ * with `checkpoint: null`. A project whose `stage` is past such a stage
12
+ * without that entry is refused (`STAGE_UNSIGNED`).
13
+ * - `bar`: the stage ends when the last plan step it runs meets its bar; no
14
+ * stage-level sign-off, but a step whose checkpoint is on needs a
15
+ * `{ stage, checkpoint: <step id> }` entry before the next step runs
16
+ * (`CHECKPOINT_UNSIGNED`).
17
+ * - `none`: terminal.
18
+ *
19
+ * The rig phase runs in two `bar` stages: `parts` runs the steps that make the
20
+ * parts (`steps`), `rigging` runs the rest. A phase's stage without `steps`
21
+ * runs every step of the phase the other stages do not name.
22
+ */
23
+ export interface StageSpec {
24
+ readonly id: string;
25
+ readonly closed_by: 'signoff' | 'bar' | 'none';
26
+ /** For a `bar` stage: the plan phase whose steps run in it. */
27
+ readonly phase?: 'rig' | 'post';
28
+ /** For a `bar` stage that runs only some of its phase's steps: their ids. */
29
+ readonly steps?: readonly string[];
30
+ }
31
+
32
+ export const STAGES: readonly StageSpec[] = [
33
+ { id: 'interview', closed_by: 'signoff' },
34
+ { id: 'plan-approved', closed_by: 'signoff' },
35
+ { id: 'parts', closed_by: 'bar', phase: 'rig', steps: ['seethrough', 'assemble'] },
36
+ { id: 'parts-review', closed_by: 'signoff' },
37
+ { id: 'rigging-interview', closed_by: 'signoff' },
38
+ { id: 'rigging', closed_by: 'bar', phase: 'rig' },
39
+ { id: 'rig-review', closed_by: 'signoff' },
40
+ { id: 'post-interview', closed_by: 'signoff' },
41
+ { id: 'post', closed_by: 'bar', phase: 'post' },
42
+ { id: 'direction-review', closed_by: 'signoff' },
43
+ { id: 'accepted', closed_by: 'none' },
44
+ ] as const;
45
+
46
+ /** Stopped by a human sign-off; outside the order. Resuming starts at the first stage whose sign-off is missing. */
47
+ export const PAUSED_STAGE = 'paused';
48
+
49
+ /** What a record's `stage_values` is expected to hold: the order, then `paused`. */
50
+ export const STAGE_VALUES: readonly string[] = [...STAGES.map((s) => s.id), PAUSED_STAGE];
51
+
52
+ /**
53
+ * The stages skipped when every post item is answered not applicable
54
+ * (PROJECT.md § When post is not passed): the project moves from
55
+ * `post-interview` straight to `accepted`.
56
+ */
57
+ export const SKIPPED_WHEN_NO_POST: readonly string[] = ['post', 'direction-review'];
58
+
59
+ /** Default new-order phase approvals; parts steps and legacy records use approvalStageOfStep. */
60
+ export const PHASE_APPROVED_BY: Readonly<Record<'rig' | 'post', string>> = {
61
+ rig: 'rigging-interview',
62
+ post: 'post-interview',
63
+ };
64
+
65
+ /** The stage a step runs in (a `bar` stage of its phase), i.e. the `stage` its checkpoint sign-off names. */
66
+ export function stageOfStep(phase: 'rig' | 'post', id: string): string {
67
+ const ofPhase = STAGES.filter((s) => s.phase === phase);
68
+ const found = ofPhase.find((s) => s.steps?.includes(id) === true) ?? ofPhase.find((s) => s.steps === undefined);
69
+ if (found === undefined) throw new Error(`no stage runs ${phase} step ${id}`);
70
+ return found.id;
71
+ }
72
+
73
+ /** The sign-off a person gives on the assembled parts before any rig is built (rig-c/create#48). */
74
+ export const PARTS_REVIEW = 'parts-review';
75
+
76
+ /**
77
+ * When `parts` and `parts-review` entered the order. A project whose first
78
+ * `plan-approved` sign-off is dated before this passed that point when the two
79
+ * stages did not exist: it is reported (`PARTS_REVIEW_PREDATES_STAGE`, a
80
+ * warning), not refused, and no sign-off is assumed for it.
81
+ */
82
+ export const PARTS_REVIEW_SINCE = '2026-11-04T00:00:00Z';
83
+
84
+ /** History entries before this date (or without a readable date) retain free-form bounces with a warning. */
85
+ export const AGENT_BOUNCE_SINCE = '2026-11-05T00:00:00Z';
86
+
87
+ export function stageIndex(id: string): number {
88
+ return STAGES.findIndex((s) => s.id === id);
89
+ }
90
+
91
+ /**
92
+ * The two reviews and the version kind each one accepts (PROJECT.md § Stages):
93
+ * `rig-review` accepts one `kind: "rig"` version on its still frame, and post
94
+ * work runs only on it; `direction-review` accepts one `kind: "post"` version.
95
+ * A verdict's `review` names one of these keys.
96
+ */
97
+ export const REVIEW_ACCEPTS: Readonly<Record<'rig-review' | 'direction-review', 'rig' | 'post'>> = {
98
+ 'rig-review': 'rig',
99
+ 'direction-review': 'post',
100
+ };
101
+
102
+ /** The review that accepts the rig version post work runs on. */
103
+ export const RIG_REVIEW = 'rig-review' satisfies keyof typeof REVIEW_ACCEPTS;
104
+
105
+
106
+ export const STAGE_ORDER_SPEC = 'rig-create/stage-order@1';
107
+ export const CHOICE_ORDER_SPEC = 'rig-create/stage-order@2';
108
+ export const PARTS_STEPS: readonly string[] = ['seethrough', 'assemble'];
109
+ export const GUIDE_INTERVIEW: Readonly<Record<string, string>> = {
110
+ rig_type: 'interview', reference: 'interview', seethrough: 'interview', assemble: 'interview',
111
+ bones: 'rigging-interview', meshes: 'rigging-interview', constraints: 'rigging-interview', motion: 'rigging-interview',
112
+ };
113
+
114
+ /** Adoption is explicit, independent of dates and answer representation. */
115
+ export function hasStageOrder(project: JsonObject): boolean {
116
+ const interview = project.interview;
117
+ if (interview === null || typeof interview !== 'object' || Array.isArray(interview)) return false;
118
+ const order = interview.order;
119
+ return order !== null && typeof order === 'object' && !Array.isArray(order) && (order.spec === STAGE_ORDER_SPEC || order.spec === CHOICE_ORDER_SPEC);
120
+ }
121
+
122
+ /** Legacy phase=rig approvals retain their original all-step meaning. */
123
+ export function approvalStageOfStep(phase: 'rig' | 'post', id: string, newOrder = false): string {
124
+ if (phase === 'post') return PHASE_APPROVED_BY.post;
125
+ if (!newOrder || PARTS_STEPS.includes(id)) return 'plan-approved';
126
+ return PHASE_APPROVED_BY.rig;
127
+ }
128
+
129
+ /** Whether a step belongs to the plan published for this approval stage. */
130
+ export function stepCoveredByApproval(project: JsonObject, step: JsonObject, stage: string | null): boolean {
131
+ const phase = stage === 'post-interview' ? 'post' : 'rig';
132
+ return step.phase === phase && approvalStageOfStep(phase, String(step.id), hasStageOrder(project) || stage === 'rigging-interview') === stage;
133
+ }
134
+
135
+ export function interviewStageOfGuide(id: string, post = false, newOrder = false): string {
136
+ if (post) return 'post-interview';
137
+ return newOrder ? GUIDE_INTERVIEW[id] ?? 'rigging-interview' : 'interview';
138
+ }
139
+
140
+ /** Answers use interview membership, including framing ids that are not executable steps. */
141
+ export function approvalStageOfGuide(id: string, post = false, newOrder = false): string {
142
+ const interview = interviewStageOfGuide(id, post, newOrder);
143
+ return interview === 'interview' ? 'plan-approved' : interview;
144
+ }
145
+
146
+ /** Frozen order used by projects that have not adopted the split interview. */
147
+ export const LEGACY_STAGES = STAGES.filter(s => s.id !== 'rigging-interview');
148
+ export const LEGACY_STAGE_VALUES: readonly string[] = [...LEGACY_STAGES.map(s => s.id), PAUSED_STAGE];
@@ -0,0 +1,19 @@
1
+ import { createHash } from 'node:crypto';
2
+
3
+ import type { Json, JsonObject } from './templates.ts';
4
+
5
+ const FIELDS = ['id', 'phase', 'order', 'does', 'produces', 'rests_on', 'bar', 'checkpoint_proposed', 'checkpoint'] as const;
6
+
7
+ export function sorted(value: Json): Json {
8
+ if (Array.isArray(value)) return value.map(sorted);
9
+ if (value !== null && typeof value === 'object') {
10
+ return Object.fromEntries(Object.keys(value).sort().map((key) => [key, sorted(value[key])]));
11
+ }
12
+ return value;
13
+ }
14
+
15
+ /** SHA-256 of the present approval fields, recursively key-sorted JSON, encoded as UTF-8. */
16
+ export function stepHash(step: JsonObject): string {
17
+ const fields = Object.fromEntries(FIELDS.filter((key) => key in step).map((key) => [key, step[key]]));
18
+ return createHash('sha256').update(JSON.stringify(sorted(fields))).digest('hex');
19
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * The parts-review structure-decisions snapshot, written from the bound draft
3
+ * (rig-c/create#229). The parts verdict names no per-declaration approval: an
4
+ * approved verdict approves the reviewed draft, so the snapshot carries every
5
+ * `meshes.<part>` declaration of that draft's config, copied exactly. The
6
+ * writer measures and derives no mesh.
7
+ */
8
+ import { lstatSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
9
+ import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
10
+
11
+ import { asArray, isObject, type Finding } from './check.ts';
12
+ import { readProject } from './feedback.ts';
13
+ import { IGNORED_DIRS } from './init.ts';
14
+ import { recordedPartsVerdict } from './parts-verdict-record.ts';
15
+ import { refuse, USAGE_EXIT } from './refusal.ts';
16
+ import { retainedJson, STRUCTURE_SCHEMA, structurePathIsIgnored, validateStructureSnapshot } from './structure.ts';
17
+ import type { Json, JsonObject } from './templates.ts';
18
+
19
+ export const STRUCTURE_SNAPSHOT_HELP = `structure-snapshot:
20
+ [<project dir>] --draft <id> --out <path>
21
+ Write ${STRUCTURE_SCHEMA} from the recorded draft <id>: one entry per
22
+ meshes.<part> of its config, copied exactly, or entries:[] without meshes.
23
+ The latest recorded parts-review API receipt for --draft must approve it.
24
+ --out is project-relative and outside ignored roots, for example evidence/.
25
+ Writes only --out; an existing --out with the same bytes is left as it is.
26
+ Pass the same --draft and --out to signoff parts-review --structure-file.`;
27
+
28
+ const pointer = (part: string): string => `/meshes/${part.replaceAll('~', '~0').replaceAll('/', '~1')}`;
29
+
30
+ /** Where --out may be written, refusing before any byte is: a literal path under the project, outside ignored roots. */
31
+ function outTarget(dir: string, out: string): string {
32
+ const at = `structure-snapshot --out ${out}`;
33
+ const segments = out.split('/');
34
+ if (isAbsolute(out) || out.includes('\\') || segments.some(s => s === '' || s === '.' || s === '..')) {
35
+ refuse('STRUCTURE_SNAPSHOT_OUT_INVALID', `${at}: expected a literal project-relative POSIX path; name a file under the project, for example evidence/structure-decisions.json`);
36
+ }
37
+ const root = realpathSync(dir);
38
+ // The longest existing prefix: it carries the links the write would follow.
39
+ let existing = '';
40
+ for (const segment of segments) {
41
+ const next = existing === '' ? segment : `${existing}/${segment}`;
42
+ try { lstatSync(join(dir, next)); } catch { break; }
43
+ existing = next;
44
+ }
45
+ if (existing !== '') {
46
+ let real: string;
47
+ try { real = realpathSync(join(dir, existing)); } catch { refuse('STRUCTURE_SNAPSHOT_OUT_INVALID', `${at}: ${existing} is a link that leads nowhere; name a file under the project`); }
48
+ const rel = relative(root, real);
49
+ if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) refuse('STRUCTURE_SNAPSHOT_OUT_INVALID', `${at}: ${existing} leads out of the project; name a file under the project`);
50
+ if (existing !== out && !lstatSync(real).isDirectory()) refuse('STRUCTURE_SNAPSHOT_OUT_INVALID', `${at}: ${existing} is not a folder; name a file under a folder of the project`);
51
+ }
52
+ const ignored: readonly string[] = [...IGNORED_DIRS, 'node_modules', '.git'];
53
+ if (ignored.includes(segments[0] as string) || (existing !== '' && structurePathIsIgnored(dir, existing))) {
54
+ refuse('STRUCTURE_SNAPSHOT_OUT_IGNORED', `${at}: the snapshot must survive a clone, and ${out} is under an ignored root; write it outside ignored roots, for example evidence/`);
55
+ }
56
+ const file = resolve(dir, out);
57
+ if (existing === out && !lstatSync(file).isFile()) refuse('STRUCTURE_SNAPSHOT_OUT_INVALID', `${at}: ${out} exists and is not a regular file; name a new file or remove that entry`);
58
+ return file;
59
+ }
60
+
61
+ export function structureSnapshotCommand(args: string[]): { stdout: string; exit: number } {
62
+ if (args.some(arg => arg === '--verdict' || arg.startsWith('--verdict='))) refuse('PARTS_VERDICT_FILE_ROUTE_REMOVED', 'project.json.drafts: --verdict was removed; record the API response with parts-review and select it with --draft', USAGE_EXIT);
63
+ const opts: Record<string, string> = {};
64
+ const positional: string[] = [];
65
+ for (let i = 0; i < args.length; i++) {
66
+ const arg = args[i] as string;
67
+ if (['--draft', '--out'].includes(arg)) {
68
+ const value = args[++i];
69
+ if (arg in opts || value === undefined || value === '' || value.startsWith('--')) refuse('STRUCTURE_SNAPSHOT_USAGE', `structure-snapshot: ${arg} needs one non-empty value; ${STRUCTURE_SNAPSHOT_HELP}`, USAGE_EXIT);
70
+ opts[arg] = value;
71
+ } else if (arg.startsWith('-')) refuse('STRUCTURE_SNAPSHOT_USAGE', `structure-snapshot: unsupported ${arg}; ${STRUCTURE_SNAPSHOT_HELP}`, USAGE_EXIT);
72
+ else positional.push(arg);
73
+ }
74
+ const draftId = opts['--draft'];
75
+ const out = opts['--out'];
76
+ if (positional.length > 1 || draftId === undefined || out === undefined) refuse('STRUCTURE_SNAPSHOT_USAGE', `structure-snapshot: needs --draft and --out and at most one project folder; ${STRUCTURE_SNAPSHOT_HELP}`, USAGE_EXIT);
77
+ const dir = resolve(positional[0] ?? process.cwd());
78
+ const { project } = readProject(dir);
79
+ const file = outTarget(dir, out);
80
+
81
+ const index = asArray(project.drafts).findIndex(d => isObject(d) && d.id === draftId);
82
+ const draft = asArray(project.drafts)[index];
83
+ if (!isObject(draft)) refuse('STRUCTURE_SNAPSHOT_DRAFT_UNKNOWN', `drafts[${draftId}]: no recorded draft has this id; record the reviewed draft with draft-rig, then name its id with --draft`);
84
+ let config: JsonObject;
85
+ try { config = retainedJson(dir, draft.config); } catch (error) {
86
+ refuse('STRUCTURE_SNAPSHOT_CONFIG_CHANGED', `drafts[${index}].config: ${error instanceof Error ? error.message : String(error)}; the snapshot copies the config the draft recorded, so restore those bytes, or record the changed config as a new draft with draft-rig and review it`);
87
+ }
88
+
89
+ recordedPartsVerdict(dir, project, draftId);
90
+
91
+ const meshes = config.meshes;
92
+ if (meshes !== undefined && !isObject(meshes)) refuse('STRUCTURE_DECISIONS_INVALID', `drafts[${index}].config meshes: expected an object of part declarations; review the labelled draft, retain its exact structure decisions, and sign again`);
93
+ const entries: Json[] = Object.entries(meshes ?? {}).map(([part, value]) => ({ path: pointer(part), value: structuredClone(value) }));
94
+ const snapshot: JsonObject = { schema: STRUCTURE_SCHEMA, draft_id: draftId, config: structuredClone(draft.config) as Json, entries };
95
+ const errors: Finding[] = [];
96
+ validateStructureSnapshot(dir, project, draftId, snapshot, out, errors);
97
+ if (errors[0] !== undefined) refuse(errors[0].code, errors[0].message);
98
+
99
+ const bytes = `${JSON.stringify(snapshot, null, 2)}\n`;
100
+ let present: Buffer | undefined;
101
+ try { present = readFileSync(file); } catch { present = undefined; }
102
+ if (present !== undefined) {
103
+ if (present.equals(Buffer.from(bytes))) return { stdout: `structure-snapshot: ${out} already holds this snapshot of draft ${draftId}; unchanged`, exit: 0 };
104
+ refuse('STRUCTURE_SNAPSHOT_OUT_EXISTS', `structure-snapshot --out ${out}: the file holds different bytes; a retained snapshot is never overwritten, so name a new file`);
105
+ }
106
+ mkdirSync(dirname(file), { recursive: true });
107
+ writeFileSync(file, bytes);
108
+ return { stdout: `structure-snapshot: wrote ${out} for draft ${draftId} with ${entries.length} ${entries.length === 1 ? 'entry' : 'entries'}`, exit: 0 };
109
+ }
@@ -0,0 +1,166 @@
1
+ /** Human-approved structure declarations, without applying producer patches or measuring meshes. */
2
+ import { createHash } from 'node:crypto';
3
+ import { readFileSync, realpathSync } from './check-reads.ts';
4
+ import { isAbsolute, relative, resolve, sep } from 'node:path';
5
+ import { isDeepStrictEqual } from 'node:util';
6
+ import { asArray, collectItems, collectSignoffs, isObject, loadCollection, type Finding } from './check.ts';
7
+ import { draftFileUnavailable } from './drafts.ts';
8
+ import { IGNORED_DIRS } from './init.ts';
9
+ import { collectReopens, liveSignoff } from './reopen.ts';
10
+ import { hasStageOrder, interviewStageOfGuide, stageIndex } from './stages.ts';
11
+ import type { Json, JsonObject } from './templates.ts';
12
+
13
+ export const STRUCTURE_SCHEMA = 'rig-create/structure-decisions@0';
14
+ const hash = (b: Buffer): string => createHash('sha256').update(b).digest('hex');
15
+ const add = (out: Finding[], code: string, path: string, why: string): void => { out.push({ code, path, message: `${path}: ${why}; review the labelled draft, retain its exact structure decisions, and sign again` }); };
16
+
17
+ export function retainedJson(dir: string, binding: Json | undefined): JsonObject {
18
+ if (!isObject(binding) || typeof binding.path !== 'string' || typeof binding.sha256 !== 'string' || !/^[a-f0-9]{64}$/.test(binding.sha256) || isAbsolute(binding.path)) throw new Error('expected project-relative path and sha256');
19
+ const file = realpathSync(resolve(dir, binding.path));
20
+ const rel = relative(realpathSync(dir), file);
21
+ if (rel === '' || rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) throw new Error('file escapes project');
22
+ const bytes = readFileSync(file);
23
+ if (hash(bytes) !== binding.sha256) throw new Error(`${binding.path}: bytes changed`);
24
+ const json: Json = JSON.parse(bytes.toString('utf8'));
25
+ if (!isObject(json)) throw new Error('expected an object');
26
+ return json;
27
+ }
28
+
29
+ export function structurePathIsIgnored(dir: string, path: string): boolean {
30
+ const ignored: readonly string[] = [...IGNORED_DIRS, 'node_modules', '.git'];
31
+ return [relative(resolve(dir), resolve(dir, path)), relative(realpathSync(dir), realpathSync(resolve(dir, path)))]
32
+ .some(rel => ignored.includes(rel.split(sep)[0] as string));
33
+ }
34
+
35
+ export function latestPartsApproval(project: JsonObject, at = Infinity): JsonObject | undefined {
36
+ const reopens = collectReopens(project, []).filter(r => r.at <= at);
37
+ return collectSignoffs(project, []).filter(s => s.stage === 'parts-review' && s.checkpoint === null && s.at <= at && liveSignoff(reopens, s.stage, s.at))
38
+ .sort((a, b) => b.at - a.at || b.index - a.index)[0]?.record;
39
+ }
40
+
41
+ export function checkStructure(dir: string, project: JsonObject, signoff: JsonObject, path: string, errors: Finding[]): JsonObject | null {
42
+ if (signoff.structure === undefined || signoff.draft_id === undefined) {
43
+ add(errors, 'STRUCTURE_DECISIONS_MISSING', path, 'parts approval needs --draft and --structure-file, with explicit entries:[] when unchanged'); return null;
44
+ }
45
+ let snapshot: JsonObject;
46
+ try { snapshot = retainedJson(dir, signoff.structure); } catch (error) { add(errors, 'STRUCTURE_DECISIONS_INVALID', `${path}.structure`, error instanceof Error ? error.message : String(error)); return null; }
47
+ return validateStructureSnapshot(dir, project, signoff.draft_id, snapshot, `${path}.structure`, errors);
48
+ }
49
+
50
+ /**
51
+ * The rules a structure snapshot meets, shared by the check and the
52
+ * structure-snapshot writer: it binds the named draft and its config, and
53
+ * every entry is a distinct exact `meshes.<part>` declaration of that config
54
+ * when the config is available. `at` is the path findings name.
55
+ */
56
+ export function validateStructureSnapshot(dir: string, project: JsonObject, draftId: Json | undefined, snapshot: JsonObject, at: string, errors: Finding[]): JsonObject | null {
57
+ try {
58
+ const draft = asArray(project.drafts).find(d => isObject(d) && d.id === draftId);
59
+ if (!isObject(draft) || snapshot.schema !== STRUCTURE_SCHEMA || snapshot.draft_id !== draft.id || !isDeepStrictEqual(snapshot.config, draft.config) || !Array.isArray(snapshot.entries)) throw new Error('snapshot must bind the named draft and its config');
60
+ if (!isObject(draft.config) || typeof draft.config.path !== 'string') throw new Error('draft config binding is malformed');
61
+ const config = draftFileUnavailable(dir, draft.config.path, `derived/drafts/${String(draft.id)}`)
62
+ ? undefined : retainedJson(dir, snapshot.config);
63
+ const seen = new Set<string>();
64
+ for (const raw of snapshot.entries) {
65
+ if (!isObject(raw) || typeof raw.path !== 'string' || !/^\/meshes\/(?:[^~/]|~[01])+$/.test(raw.path) || seen.has(raw.path) || !('value' in raw)) throw new Error('entries require distinct complete /meshes/<part> declarations');
66
+ seen.add(raw.path);
67
+ const part = raw.path.slice('/meshes/'.length).replaceAll('~1', '/').replaceAll('~0', '~');
68
+ if (config !== undefined && (!isObject(config.meshes) || !isDeepStrictEqual(config.meshes[part], raw.value))) {
69
+ add(errors, 'STRUCTURE_DECISIONS_CONFLICT', at, `${raw.path} differs from the reviewed config`); return null;
70
+ }
71
+ }
72
+ return snapshot;
73
+ } catch (error) { add(errors, 'STRUCTURE_DECISIONS_INVALID', at, error instanceof Error ? error.message : String(error)); return null; }
74
+ }
75
+
76
+ /** Resolve shared context outside the answer union, ready for later choice-answer schemas. */
77
+ export function riggingContext(dir: string, project: JsonObject, at: string, errors: Finding[]): JsonObject | null {
78
+ const review = latestPartsApproval(project);
79
+ if (review === undefined || typeof review.at !== 'string' || !Number.isFinite(Date.parse(review.at)) || !isObject(review.evidence) || !(Date.parse(at) >= Date.parse(review.at))) {
80
+ add(errors, 'RIGGING_INTERVIEW_CONTEXT', 'interview.rigging_context', 'a live dated parts review must precede the rigging interview'); return null;
81
+ }
82
+ if (checkStructure(dir, project, review, 'signoffs[parts-review]', errors) === null) return null;
83
+ for (const item of collectItems(project, { collection: loadCollection(dir) })) {
84
+ if (interviewStageOfGuide(item.id, item.kind === 'post', true) !== 'rigging-interview' || item.state.state === 'silent') continue;
85
+ const state = item.state;
86
+ if (state.state !== 'user' && state.state !== 'delegated') continue;
87
+ const when = typeof state.answer.at === 'string' ? Date.parse(state.answer.at) : NaN;
88
+ if (!Number.isFinite(when) || when < Date.parse(review.at) || when > Date.parse(at)) add(errors, 'RIG_GUIDE_BEFORE_PARTS', item.path, 'fresh rig guides must follow the accepted parts and precede their approval; preserve earlier answers as history');
89
+ }
90
+ return { accepted_parts: { signoff_at: review.at, evidence: review.evidence }, structure: review.structure as Json };
91
+ }
92
+
93
+ /** The meshes answer must be covered by a live later interview approval at build time. */
94
+ export function acceptsStructureDelta(project: JsonObject, review: JsonObject, at = Infinity): boolean {
95
+ const partsAt = Date.parse(String(review.at));
96
+ const interview = isObject(project.interview) ? project.interview : {};
97
+ const meshes = asArray(interview.waypoints).find(w => isObject(w) && w.id === 'meshes');
98
+ const guide = isObject(meshes) && isObject(meshes.guide) ? meshes.guide : {};
99
+ const guideAt = Date.parse(String(guide.at));
100
+ const reopens = collectReopens(project, []).filter(r => r.at <= at);
101
+ return guideAt > partsAt && collectSignoffs(project, []).some(s =>
102
+ s.stage === 'rigging-interview' && s.record.checkpoint === null && s.at > partsAt
103
+ && s.at <= at && guideAt <= s.at && liveSignoff(reopens, s.stage, s.at));
104
+ }
105
+
106
+ /** Snapshot order is retained; null denotes a declaration absent from the version. */
107
+ export function structureDelta(snapshot: JsonObject, config: JsonObject): JsonObject[] {
108
+ return asArray(snapshot.entries).flatMap(entry => {
109
+ if (!isObject(entry) || typeof entry.path !== 'string') return [];
110
+ const part = entry.path.slice('/meshes/'.length).replaceAll('~1', '/').replaceAll('~0', '~');
111
+ const value = isObject(config.meshes) ? config.meshes[part] : undefined;
112
+ return isDeepStrictEqual(value, entry.value) ? [] : [{ entry: entry.path, snapshot: entry.value!, version: value ?? null }];
113
+ });
114
+ }
115
+
116
+ /** Writer and checker share the approval gate and exact file-derived delta. */
117
+ export function approvedStructureDelta(dir: string, project: JsonObject, at: number, config: JsonObject): JsonObject[] {
118
+ const interview = isObject(project.interview) ? project.interview : {};
119
+ const adoptedAt = isObject(interview.order) ? Date.parse(String(interview.order.adopted_at)) : NaN;
120
+ if (!(at >= adoptedAt)) return [];
121
+ const review = latestPartsApproval(project, at);
122
+ if (!hasStageOrder(project) || review === undefined || !acceptsStructureDelta(project, review, at)) return [];
123
+ const errors: Finding[] = [];
124
+ const snapshot = checkStructure(dir, project, review, 'signoffs[parts-review]', errors);
125
+ return snapshot === null ? [] : structureDelta(snapshot, config);
126
+ }
127
+
128
+ export function checkRiggingContext(dir: string, project: JsonObject, effectiveStage: string, errors: Finding[], warnings: Finding[] = []): void {
129
+ const review = latestPartsApproval(project);
130
+ if (review !== undefined && (hasStageOrder(project) || review.structure !== undefined)) checkStructure(dir, project, review, 'signoffs[parts-review]', errors);
131
+ if (!hasStageOrder(project)) return;
132
+ const interview = isObject(project.interview) ? project.interview : {};
133
+ if (!isObject(interview.order) || typeof interview.order.adopted_at !== 'string' || !Number.isFinite(Date.parse(interview.order.adopted_at))) add(errors, 'RIGGING_INTERVIEW_CONTEXT', 'interview.order', 'order adoption needs a clock date');
134
+ const reopens = collectReopens(project, []);
135
+ const approvals = collectSignoffs(project, []).filter(s => s.stage === 'rigging-interview' && s.checkpoint === null && liveSignoff(reopens, s.stage, s.at));
136
+ for (const approval of approvals.sort((a, b) => b.at - a.at || b.index - a.index).slice(0, 1)) {
137
+ const context = riggingContext(dir, project, String(approval.record.at), errors);
138
+ if (context !== null && (!isDeepStrictEqual(approval.record.accepted_parts, context.accepted_parts) || !isDeepStrictEqual(approval.record.structure, context.structure))) add(errors, 'RIGGING_INTERVIEW_CONTEXT', `signoffs[${approval.index}]`, 'approval does not bind the current parts review and structure');
139
+ }
140
+ if (stageIndex(effectiveStage) < stageIndex('rigging-interview')) return;
141
+ if (review === undefined) add(errors, 'RIGGING_INTERVIEW_CONTEXT', 'interview.rigging_context', 'review parts before asking rigging questions');
142
+ // Bind production config declarations, not geometry. Historical pre-adoption builds remain history.
143
+ const adoptedAt = isObject(interview.order) ? Date.parse(String(interview.order.adopted_at)) : NaN;
144
+ for (const version of asArray(project.versions)) {
145
+ if (!isObject(version) || version.kind !== 'rig' || !(Date.parse(String(version.at)) >= adoptedAt)) continue;
146
+ const historicalReview = latestPartsApproval(project, Date.parse(String(version.at)));
147
+ if (historicalReview === undefined) { add(errors, 'RIGGING_INTERVIEW_CONTEXT', `versions[v${String(version.n)}]`, 'no parts structure approval was live when this rig was built'); continue; }
148
+ const snapshot = checkStructure(dir, project, historicalReview, `versions[v${String(version.n)}].structure`, errors);
149
+ if (snapshot === null) continue;
150
+ const provenance = isObject(version.provenance) ? version.provenance : {};
151
+ if (!isObject(provenance.config)) continue; // Existing provenance reader owns absent configs.
152
+ let config: JsonObject;
153
+ try { config = retainedJson(dir, provenance.config); } catch { continue; }
154
+ const delta = structureDelta(snapshot, config);
155
+ const path = `versions[v${String(version.n)}].provenance`;
156
+ if (delta.length > 0 && !acceptsStructureDelta(project, historicalReview, Date.parse(String(version.at)))) {
157
+ errors.push({ code: 'STRUCTURE_DECISIONS_CONFLICT', path: `${path}.config`, message: `${path}.config: mesh/chest declaration overrides the person-approved structure; answer the rigging meshes guide, or renew parts-review` });
158
+ continue;
159
+ }
160
+ if ((delta.length > 0 || provenance.structure_delta !== undefined) && !isDeepStrictEqual(provenance.structure_delta, delta)) {
161
+ add(errors, 'STRUCTURE_DELTA_MISMATCH', `${path}.structure_delta`, 'recorded delta differs from the snapshot and retained version config; register the rig again');
162
+ continue;
163
+ }
164
+ if (delta.length > 0) warnings.push({ code: 'STRUCTURE_DELTA_REVIEW', path: `${path}.structure_delta`, message: `${path}.structure_delta: ${delta.length} changed structure entries; the rig-review verdict and sign-off must cover changed structure` });
165
+ }
166
+ }
@@ -0,0 +1,16 @@
1
+ import { readFileSync } from './check-reads.ts';
2
+ import { join } from 'node:path';
3
+
4
+ /** The repository's templates/ folder, read from the source tree. */
5
+ export const TEMPLATES_DIR = join(import.meta.dir, '..', 'templates');
6
+
7
+ export type Json = null | boolean | number | string | Json[] | { [key: string]: Json };
8
+ export type JsonObject = { [key: string]: Json };
9
+
10
+ export function readTemplate(relPath: string): JsonObject {
11
+ const parsed: unknown = JSON.parse(readFileSync(join(TEMPLATES_DIR, relPath), 'utf8'));
12
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
13
+ throw new Error(`template ${relPath} is not a JSON object`);
14
+ }
15
+ return parsed as JsonObject;
16
+ }
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Where each top-level section of a `@rig-c/parts` config goes: a rig waypoint,
3
+ * a post item, or nowhere (not a direction call). This is the table in
4
+ * docs/INTERVIEW.md § Waypoints, as data, and the ONLY place the mapping is
5
+ * written. The section list itself is not here: it is read from the installed
6
+ * parts package (src/parts-sections.ts), so a section parts adds that is not
7
+ * on this table is refused by name (`SECTION_UNPLACED`) — a new section is a
8
+ * new question before it is a new line here.
9
+ *
10
+ * `alternates` is not listed: @rig-c/parts 2.4.0 accepts it only inside
11
+ * `assemble` (`assemble.alternates`), so it is part of the `assemble`
12
+ * waypoint's decision, not a section of its own.
13
+ */
14
+ export type Placement =
15
+ | { readonly kind: 'waypoint'; readonly waypoint: string }
16
+ | { readonly kind: 'post'; readonly item: string }
17
+ | { readonly kind: 'none'; readonly why: string };
18
+
19
+ const wp = (waypoint: string): Placement => ({ kind: 'waypoint', waypoint });
20
+ const post = (item: string): Placement => ({ kind: 'post', item });
21
+ const none = (why: string): Placement => ({ kind: 'none', why });
22
+
23
+ export const SECTION_PLACEMENT: Readonly<Record<string, Placement>> = {
24
+ seethrough: wp('seethrough'),
25
+ assemble: wp('assemble'),
26
+ limbs: wp('assemble'),
27
+ scene: wp('assemble'),
28
+ waist: wp('assemble'),
29
+ bones: wp('bones'),
30
+ meshes: wp('meshes'),
31
+ regions: wp('meshes'),
32
+ constraints: wp('constraints'),
33
+ stops: wp('constraints'),
34
+ cloth: wp('constraints'),
35
+ motion: wp('motion'),
36
+ turn: post('camera_depth'),
37
+ depth: post('camera_depth'),
38
+ key: none('a name'),
39
+ generation: none('a record of a run'),
40
+ proposed_rig_scale: none('a value the tool proposes'),
41
+ requirements: none(
42
+ "a path to a checks file. Declare setup_painting.required: true, with limits from parts' own pixel_count per region "
43
+ + 'on the build accepted at the split checkpoint, recorded as a "no worse than the accepted split" bar with its source. '
44
+ + 'A busy project declares setup_painting.figure_mask from a named matte of the original painting at alpha ≥ 128 (parts coverage convention) minus any foreground plane, recorded with derived add. '
45
+ + 'Translated guide sentences land here, after they are asked',
46
+ ),
47
+ motion_seams: none('an observation'),
48
+ note: none('an annotation; parts accepts it beside any object and reads nothing from it'),
49
+ };
50
+
51
+ /** Pipeline order of the rig waypoints; a waypoint not listed here sorts last, by name. */
52
+ export const WAYPOINT_ORDER: readonly string[] = ['seethrough', 'assemble', 'bones', 'meshes', 'constraints', 'motion'];
53
+
54
+ /** The framing questions asked before the waypoints (INTERVIEW.md F1, F2). */
55
+ export const FRAMING_IDS: readonly string[] = ['rig_type', 'reference'];
56
+
57
+ /**
58
+ * Post items with no config section of their own (INTERVIEW.md § Post-production).
59
+ * `camera_depth` is not listed: it is derived from the `turn` and `depth`
60
+ * sections through the table above.
61
+ */
62
+ export const SECTIONLESS_POST_ITEMS: readonly string[] = ['post_timing', 'post_composition'];
63
+
64
+ export interface DerivedWaypoint {
65
+ readonly id: string;
66
+ /** The parts sections placed on it, in parts' order. */
67
+ readonly config: readonly string[];
68
+ }
69
+
70
+ export interface DerivedSet {
71
+ readonly waypoints: readonly DerivedWaypoint[];
72
+ readonly post_items: readonly DerivedWaypoint[];
73
+ /** Sections present in parts and absent from the table. */
74
+ readonly unplaced: readonly string[];
75
+ /** Sections the table places on a waypoint or post item that parts no longer has (a retired `none` entry is not reported: nothing was asked there). */
76
+ readonly retired: readonly string[];
77
+ }
78
+
79
+ function byOrder(a: string, b: string): number {
80
+ const ia = WAYPOINT_ORDER.indexOf(a);
81
+ const ib = WAYPOINT_ORDER.indexOf(b);
82
+ if (ia === -1 && ib === -1) return a.localeCompare(b);
83
+ if (ia === -1) return 1;
84
+ if (ib === -1) return -1;
85
+ return ia - ib;
86
+ }
87
+
88
+ /** Places every section parts has; the result is what a project's interview is checked against. */
89
+ export function deriveWaypoints(sections: readonly string[]): DerivedSet {
90
+ const waypoints = new Map<string, string[]>();
91
+ const items = new Map<string, string[]>();
92
+ const unplaced: string[] = [];
93
+ for (const section of sections) {
94
+ const placement = SECTION_PLACEMENT[section];
95
+ if (placement === undefined) {
96
+ unplaced.push(section);
97
+ } else if (placement.kind === 'waypoint') {
98
+ waypoints.set(placement.waypoint, [...(waypoints.get(placement.waypoint) ?? []), section]);
99
+ } else if (placement.kind === 'post') {
100
+ items.set(placement.item, [...(items.get(placement.item) ?? []), section]);
101
+ }
102
+ }
103
+ for (const item of SECTIONLESS_POST_ITEMS) if (!items.has(item)) items.set(item, []);
104
+ const present = new Set(sections);
105
+ return {
106
+ waypoints: [...waypoints.entries()].sort(([a], [b]) => byOrder(a, b)).map(([id, config]) => ({ id, config })),
107
+ post_items: [...items.entries()].map(([id, config]) => ({ id, config })),
108
+ unplaced,
109
+ retired: Object.keys(SECTION_PLACEMENT).filter((s) => !present.has(s) && SECTION_PLACEMENT[s]?.kind !== 'none'),
110
+ };
111
+ }
112
+
113
+ /** A direction ask, from INTERVIEW.md's waypoint descriptions, not a config field. */
114
+ export interface WaypointAsk {
115
+ readonly id: string;
116
+ readonly text: string;
117
+ readonly required_for?: 'fullscreen.game_logic' | 'other';
118
+ }
119
+
120
+ export const WAYPOINT_ASKS: Readonly<Record<string, readonly WaypointAsk[]>> = {
121
+ seethrough: [
122
+ { id: 'layers', text: 'What must the layer split keep together or apart?' },
123
+ { id: 'resolution', text: 'What resolution should the layer passes use?' },
124
+ { id: 'head_crop', text: 'What head crop should be used?' },
125
+ { id: 'seed', text: 'What seed should be used?' },
126
+ ],
127
+ assemble: [
128
+ { id: 'projection', text: 'Core keeps the part mask as the boundary. Visible follows the painting rim and can take backdrop pixels. Which projection should assemble use?' },
129
+ { id: 'ground', text: 'Plain preserves the existing rim projection. Busy limits backdrop pickup around thin parts. Is the ground plain or busy? A busy project declares setup_painting.figure_mask from a named matte of the original painting at alpha ≥ 128 (parts coverage convention) minus any foreground plane, recorded with derived add.' },
130
+ { id: 'cut', text: 'Which parts are cut, and how finely?' },
131
+ { id: 'emphasis', text: 'Which parts need detail or must stay exactly as painted?' },
132
+ { id: 'draw_order', text: 'What is the parts draw order?' },
133
+ { id: 'authored', text: 'What is authored new?' },
134
+ { id: 'occluders', text: 'What is kept under occluders?' },
135
+ { id: 'outfits', text: 'Which outfit sets exist?' },
136
+ ],
137
+ bones: [
138
+ { id: 'chains', text: 'Which parts get chains or stay rigid?' },
139
+ { id: 'placement', text: 'Where do bones sit?' },
140
+ { id: 'length', text: 'How long are the chains?' },
141
+ ],
142
+ meshes: [
143
+ { id: 'bend', text: 'Which parts bend smoothly?' },
144
+ { id: 'rigid', text: 'Which parts stay rigid?' },
145
+ { id: 'density', text: 'What mesh density should be used?' },
146
+ ],
147
+ constraints: [
148
+ { id: 'effects', text: 'Which parts move on physics, wind or other effects?' },
149
+ { id: 'strength', text: 'How strongly do those effects act?' },
150
+ { id: 'extent', text: 'How far may the affected parts move?' },
151
+ { id: 'gravity', text: 'What gravity applies?' },
152
+ { id: 'stops', text: 'Where must chains stop?' },
153
+ ],
154
+ motion: [
155
+ { id: 'tracks', text: 'Which moves? Name each one.', required_for: 'other' },
156
+ { id: 'states', text: 'Which game states drive the motion?', required_for: 'fullscreen.game_logic' },
157
+ { id: 'transitions', text: 'What moves it from one state to the next?', required_for: 'fullscreen.game_logic' },
158
+ { id: 'extent', text: 'How far does each motion move?' },
159
+ { id: 'speed', text: 'How fast does each motion move?' },
160
+ { id: 'loop', text: 'How long is the motion loop?' },
161
+ { id: 'blink', text: 'Should it blink?', required_for: 'other' },
162
+ ],
163
+ };