@rig-c/create 0.0.0-stage → 1.0.1

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 +169 -2
  3. package/docs/CHECKS.md +1682 -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
@@ -0,0 +1,238 @@
1
+ /** Read Review through its CLI and retain the exact response bytes owned by create.
2
+ * Every Review read (feedback and parts) spawns through one path and is bound at check time by one stored-read validator. */
3
+ import { createHash } from 'node:crypto';
4
+ import { closeSync, fsyncSync, linkSync, mkdirSync, openSync, rmSync, writeFileSync } from 'node:fs';
5
+ import { existsSync, lstatSync, readFileSync, realpathSync } from './check-reads.ts';
6
+ import { basename, dirname, isAbsolute, join } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { REVIEW_COMMENTS_SESSION_SCHEMA, REVIEW_COMMENTS_COMPARE_SESSION_SCHEMA,
9
+ parseCommentsSession, parseCommentsCompareSession, projectSelectedCommentsFeedback } from '@rig-c/review/job-host';
10
+ import { jobRuntimeExecArgv } from './job-writer.ts';
11
+ import type { JsonObject } from './templates.ts';
12
+ export const REVIEW_READ_SINCE = '2026-11-08T00:00:00Z';
13
+ /** `unavailable`: the stored file is missing or not a regular file; `changed`: its bytes differ from the binding. */
14
+ export type ReviewReadReason = 'invalid' | 'unavailable' | 'changed';
15
+ export class ReviewReadError extends Error {
16
+ readonly code = 'REVIEW_READ_INVALID';
17
+ constructor(readonly why: string, readonly reason: ReviewReadReason = 'invalid') {
18
+ super(`evidence/review-reads: ${why}; retrieve the saved result through rig-review feedback --format json and retain its CLI receipt`);
19
+ }
20
+ }
21
+ export interface ReviewReadResult {
22
+ stdout: Buffer;
23
+ stderr: string;
24
+ exitCode: number;
25
+ tool: {
26
+ name: string;
27
+ version: string;
28
+ };
29
+ }
30
+ export type ReviewReadRunner = (dir: string, argv: string[]) => ReviewReadResult;
31
+ export interface StoredReviewRead {
32
+ path: string;
33
+ sha256: string;
34
+ }
35
+ const object = (value: unknown): value is JsonObject => value !== null && typeof value === 'object' && !Array.isArray(value);
36
+ const sha = (bytes: Buffer): string => createHash('sha256').update(bytes).digest('hex');
37
+ const invalid = (why: string, reason?: ReviewReadReason): never => { throw new ReviewReadError(why, reason); };
38
+ const message = (error: unknown): string => error instanceof Error ? error.message : String(error);
39
+ function safePath(dir: string, path: string): string {
40
+ if (isAbsolute(path) || path.includes('\\') || path.split('/').some(part => !part || part === '.' || part === '..'))
41
+ invalid('expected a literal project-relative response path');
42
+ let file = realpathSync(dir);
43
+ for (const [index, part] of path.split('/').entries()) {
44
+ file = join(file, part);
45
+ let stat;
46
+ try { stat = lstatSync(file); }
47
+ catch { return invalid(`${path} is unavailable`, 'unavailable'); }
48
+ if (stat.isSymbolicLink() || (index === path.split('/').length - 1 ? !stat.isFile() : !stat.isDirectory()))
49
+ invalid('response path contains a symlink or nonregular entry', 'unavailable');
50
+ }
51
+ return file;
52
+ }
53
+ function syncDirectory(path: string): void {
54
+ const fd = openSync(path, 'r');
55
+ try { fsyncSync(fd); } finally { closeSync(fd); }
56
+ }
57
+ function session(bytes: Buffer): JsonObject {
58
+ let value: unknown;
59
+ try {
60
+ value = JSON.parse(bytes.toString('utf8'));
61
+ }
62
+ catch {
63
+ return invalid('CLI stdout is not JSON');
64
+ }
65
+ if (object(value) && value.schema === REVIEW_COMMENTS_SESSION_SCHEMA)
66
+ return parseCommentsSession(value) as unknown as JsonObject;
67
+ if (object(value) && value.schema === REVIEW_COMMENTS_COMPARE_SESSION_SCHEMA)
68
+ return parseCommentsCompareSession(value) as unknown as JsonObject;
69
+ if (!object(value) || value.schema !== 'rig-review-session/1' || !Array.isArray(value.feedback))
70
+ return invalid('CLI stdout must contain a single review session');
71
+ return value;
72
+ }
73
+ /** The single-build source required by Create's existing Apply adapters. */
74
+ export function reviewSessionSource(value: JsonObject): JsonObject | null {
75
+ return (value.schema === 'rig-review-session/1' || value.schema === REVIEW_COMMENTS_SESSION_SCHEMA) && object(value.reviewedSource)
76
+ ? value.reviewedSource : null;
77
+ }
78
+
79
+ /** Select canonical feedback rows while retaining the original session and bytes. */
80
+ export function selectedReviewFeedback(value: JsonObject, itemId: string): JsonObject[] {
81
+ if (value.schema === REVIEW_COMMENTS_SESSION_SCHEMA) {
82
+ const parsed = parseCommentsSession(value);
83
+ const matches = parsed.comments.filter(comment => comment.anchor && 'frameStart' in comment.anchor && comment.anchor.id === itemId);
84
+ if (matches.length !== 1) return [];
85
+ const selected = projectSelectedCommentsFeedback(parsed, itemId);
86
+ return [{ ...selected.anchor, summary: selected.summary } as unknown as JsonObject];
87
+ }
88
+ return Array.isArray(value.feedback)
89
+ ? value.feedback.filter(object).filter(item => item.id === itemId) : [];
90
+ }
91
+
92
+ /** Review is resolved from the project, then its collection, then create's own installation. */
93
+ function reviewRoot(dir: string): string | undefined {
94
+ const collection = basename(dirname(dir)) === 'members' ? [join(dirname(dirname(dir)), 'node_modules/@rig-c/review')] : [];
95
+ let installed: string[] = [];
96
+ try { installed = [dirname(fileURLToPath(import.meta.resolve('@rig-c/review/package.json')))]; }
97
+ catch { /* Not installed with create. */ }
98
+ return [join(dir, 'node_modules/@rig-c/review'), ...collection, ...installed].find(path => existsSync(join(path, 'package.json')));
99
+ }
100
+ function runReview(dir: string, argv: string[]): ReviewReadResult {
101
+ const root = reviewRoot(dir);
102
+ if (!root)
103
+ return invalid('install @rig-c/review before querying it');
104
+ const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')) as {
105
+ name: string;
106
+ version: string;
107
+ bin: Record<string, string>;
108
+ };
109
+ if (pkg.name !== '@rig-c/review' || typeof pkg.version !== 'string' || typeof pkg.bin?.['rig-review'] !== 'string')
110
+ return invalid('installed Review has no rig-review CLI');
111
+ const result = Bun.spawnSync([process.execPath, ...jobRuntimeExecArgv(), join(root, pkg.bin['rig-review']), ...argv.slice(1)], { cwd: dir, stdout: 'pipe', stderr: 'pipe', timeout: 60_000 });
112
+ return { stdout: result.stdout, stderr: result.stderr.toString('utf8'), exitCode: result.exitCode, tool: { name: pkg.name, version: pkg.version } };
113
+ }
114
+ /** The one spawn path: resolve Review, run `rig-review <argv>`, then check its exit code and tool identity. */
115
+ function runReviewCli(dir: string, argv: string[], runner: ReviewReadRunner): ReviewReadResult {
116
+ let result: ReviewReadResult;
117
+ try {
118
+ result = runner(dir, [...argv]);
119
+ }
120
+ catch (error) {
121
+ if (error instanceof ReviewReadError)
122
+ throw error;
123
+ return invalid(`${argv[1]} could not run: ${message(error)}`);
124
+ }
125
+ if (result.exitCode !== 0)
126
+ return invalid(`${argv[1]} exited ${result.exitCode}: ${result.stderr.trim() || 'no diagnostic returned'}`);
127
+ if (result.tool?.name !== '@rig-c/review' || typeof result.tool.version !== 'string' || !result.tool.version.trim())
128
+ return invalid('CLI tool name and version are required');
129
+ return result;
130
+ }
131
+ /** Retain stdout once under its sha256. `created` is false when the same bytes were already stored. */
132
+ export function storeReviewRead(dir: string, bytes: Buffer): { file: StoredReviewRead; created: boolean } {
133
+ const digest = sha(bytes);
134
+ const path = `evidence/review-reads/${digest}.json`;
135
+ // Create one directory at a time, checking each ancestor before using it.
136
+ let parent = realpathSync(dir);
137
+ for (const part of ['evidence', 'review-reads']) {
138
+ parent = join(parent, part);
139
+ let made = false;
140
+ try {
141
+ mkdirSync(parent);
142
+ made = true;
143
+ }
144
+ catch (error) {
145
+ if ((error as NodeJS.ErrnoException).code !== 'EEXIST')
146
+ throw error;
147
+ }
148
+ const stat = lstatSync(parent);
149
+ if (!stat.isDirectory() || stat.isSymbolicLink())
150
+ return invalid('response store must use real directories');
151
+ if (made)
152
+ syncDirectory(dirname(parent));
153
+ }
154
+ const temporary = join(parent, `.review-read-${process.pid}-${crypto.randomUUID()}.tmp`);
155
+ let created = false;
156
+ try {
157
+ const fd = openSync(temporary, 'wx', 0o600);
158
+ try { writeFileSync(fd, bytes); fsyncSync(fd); } finally { closeSync(fd); }
159
+ try {
160
+ linkSync(temporary, join(parent, `${digest}.json`));
161
+ created = true;
162
+ }
163
+ catch (error) {
164
+ if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;
165
+ }
166
+ } finally {
167
+ rmSync(temporary, { force: true });
168
+ }
169
+ if (created)
170
+ syncDirectory(parent);
171
+ if (!readFileSync(safePath(dir, path)).equals(bytes))
172
+ return invalid('existing content-addressed response differs from stdout');
173
+ return { file: { path, sha256: digest }, created };
174
+ }
175
+ /** The one stored-read validator. Check consumes only create's stored stdout and never invokes Review. */
176
+ export function storedReviewRead(dir: string, stdout: unknown): Buffer {
177
+ try {
178
+ if (!object(stdout) || typeof stdout.sha256 !== 'string' || !/^[a-f0-9]{64}$/.test(stdout.sha256)
179
+ || stdout.path !== `evidence/review-reads/${stdout.sha256}.json`)
180
+ return invalid('stdout binding must name its content-addressed response');
181
+ const bytes = readFileSync(safePath(dir, stdout.path));
182
+ if (sha(bytes) !== stdout.sha256)
183
+ return invalid('stored stdout differs from its sha256', 'changed');
184
+ return bytes;
185
+ }
186
+ catch (error) {
187
+ if (error instanceof ReviewReadError)
188
+ throw error;
189
+ return invalid(message(error), 'unavailable');
190
+ }
191
+ }
192
+ /** Parts read: `rig-review parts <args> --format json`. Decoding and the draft binding belong to the parts route. */
193
+ export function readPartsReview(dir: string, args: string[], runner: ReviewReadRunner = runReview): ReviewReadResult {
194
+ return runReviewCli(dir, ['rig-review', 'parts', ...args, '--format', 'json'], runner);
195
+ }
196
+ export function readReview(dir: string, input: string, runner: ReviewReadRunner = runReview): {
197
+ receipt: JsonObject;
198
+ value: JsonObject;
199
+ file: JsonObject;
200
+ } {
201
+ try {
202
+ const argv = ['rig-review', 'feedback', input, '--format', 'json'];
203
+ const result = runReviewCli(dir, argv, runner);
204
+ const value = session(result.stdout);
205
+ const { file } = storeReviewRead(dir, result.stdout);
206
+ const receipt: JsonObject = { route: 'cli', argv, stdout: { ...file }, tool: result.tool };
207
+ return { receipt, value, file: { ...file } };
208
+ }
209
+ catch (error) {
210
+ if (error instanceof ReviewReadError)
211
+ throw error;
212
+ return invalid(message(error));
213
+ }
214
+ }
215
+ /** A feedback read's inline receipt, bound through the shared stored-read validator. */
216
+ export function retainedReviewRead(dir: string, receipt: unknown, expected?: unknown): JsonObject {
217
+ try {
218
+ if (!object(receipt) || Object.keys(receipt).sort().join(',') !== 'argv,route,stdout,tool' || receipt.route !== 'cli'
219
+ || !Array.isArray(receipt.argv) || receipt.argv.length !== 5 || receipt.argv.some(arg => typeof arg !== 'string')
220
+ || receipt.argv[0] !== 'rig-review' || receipt.argv[1] !== 'feedback' || !receipt.argv[2] || receipt.argv[3] !== '--format' || receipt.argv[4] !== 'json'
221
+ || !object(receipt.tool) || Object.keys(receipt.tool).sort().join(',') !== 'name,version' || receipt.tool.name !== '@rig-c/review' || typeof receipt.tool.version !== 'string' || !receipt.tool.version.trim()
222
+ || !object(receipt.stdout) || Object.keys(receipt.stdout).sort().join(',') !== 'path,sha256' || typeof receipt.stdout.sha256 !== 'string'
223
+ || !/^[a-f0-9]{64}$/.test(receipt.stdout.sha256) || receipt.stdout.path !== `evidence/review-reads/${receipt.stdout.sha256}.json`)
224
+ return invalid('invalid CLI receipt');
225
+ if (expected !== undefined && (!object(expected) || Object.keys(expected).sort().join(',') !== 'path,sha256' || expected.path !== receipt.stdout.path || expected.sha256 !== receipt.stdout.sha256))
226
+ return invalid('record binding differs from the CLI stdout binding');
227
+ return session(storedReviewRead(dir, receipt.stdout));
228
+ }
229
+ catch (error) {
230
+ if (error instanceof ReviewReadError)
231
+ throw error;
232
+ return invalid(message(error));
233
+ }
234
+ }
235
+ export function reviewReadLegacy(at: unknown): boolean {
236
+ const time = typeof at === 'string' ? Date.parse(at) : Number.NaN;
237
+ return !Number.isFinite(time) || time < Date.parse(REVIEW_READ_SINCE);
238
+ }
package/src/review.ts ADDED
@@ -0,0 +1,432 @@
1
+ import { assertCorrectionApproval } from './correction-writers.ts';
2
+ import { assertFigureMaskApproval } from './figure-mask.ts';
3
+ /**
4
+ * The two reviews and the hand-off between them (rig-c/create#8,
5
+ * docs/PROJECT.md § Stages, § The rig review rests on a still frame,
6
+ * § The hand-off to post-production).
7
+ *
8
+ * The rig is accepted first, on a still frame with a fixed camera; post work
9
+ * (camera, 2.5D depth, timing, composition) runs only on the rig version that
10
+ * acceptance names. What is recorded where:
11
+ *
12
+ * - **Which rig version was accepted** is the human `accepted` verdict with
13
+ * `review: "rig-review"` on that `versions[]` entry (kind `rig`). The
14
+ * `{ stage: "rig-review" }` sign-off moves the stage; the verdict names the
15
+ * version. Both are written in the same turn.
16
+ * - **What the acceptance rested on** is a `kind: "still"` entry in that
17
+ * version's `evidence[]`: `path`, `sha256`, `camera: "fixed"`, `frame`.
18
+ * - **Which rig the post work is on** is a post version's `rig_version`, and
19
+ * `rig-create handoff` is what post tooling asks before it runs.
20
+ *
21
+ * `checkReviews` runs inside `interview --check`; `handoff` is the command.
22
+ * Both read the record only: nothing is rendered or signed here.
23
+ */
24
+ import { existsSync, readFileSync } from './check-reads.ts';
25
+ import { join, resolve } from 'node:path';
26
+
27
+ import { draftVersionFindings } from './drafts.ts';
28
+ import { checkPreInterview } from './pre-interview.ts';
29
+ import { refuse, USAGE_EXIT } from './refusal.ts';
30
+ import { collectReopens, liveSignoff, reopenIndex, reopenTime, type Reopen } from './reopen.ts';
31
+ import { PAUSED_STAGE, RIG_REVIEW, stageIndex } from './stages.ts';
32
+ import type { Json, JsonObject } from './templates.ts';
33
+
34
+ /** The part of `interview --check`'s context this module reads and writes. */
35
+ export interface ReviewCtx {
36
+ readonly project: JsonObject;
37
+ readonly effectiveStage: string;
38
+ readonly refusals: { code: string; path: string; message: string }[];
39
+ }
40
+
41
+ const SHA256 = /^[0-9a-f]{64}$/;
42
+
43
+ export interface Acceptance {
44
+ readonly verdict: number;
45
+ readonly by: string;
46
+ readonly at: string;
47
+ readonly verbatim: string | null;
48
+ readonly source?: string;
49
+ readonly note?: string;
50
+ }
51
+
52
+ export interface Still {
53
+ readonly evidence: number;
54
+ readonly path: string;
55
+ readonly sha256: string;
56
+ /** Seconds into the motion, or `setup`: the renderer's setup pose, which an idle may pass through rather than start at. */
57
+ readonly frame: number | 'setup';
58
+ }
59
+
60
+ export interface RigVersion {
61
+ readonly preInterview: boolean;
62
+ readonly configLost?: { readonly note: string; readonly source: string };
63
+ readonly outputsLost?: { readonly note: string; readonly source: string };
64
+ readonly live: boolean;
65
+ readonly n: number;
66
+ readonly path: string;
67
+ /** When the version was built; a reopen or amendment after it makes the version history (`live: false`). */
68
+ readonly at: string | null;
69
+ /** The last human `accepted` verdict at `rig-review`, or `null`. */
70
+ readonly accepted: Acceptance | null;
71
+ /** The well-formed still-frame evidence entries. */
72
+ readonly stills: Still[];
73
+ readonly malformedStills: { index: number; why: string }[];
74
+ }
75
+
76
+ export interface PostVersion {
77
+ readonly n: number;
78
+ readonly path: string;
79
+ readonly at: string | null;
80
+ readonly live: boolean;
81
+ readonly rigVersion: Json | undefined;
82
+ }
83
+
84
+ export interface Versions {
85
+ readonly rigs: RigVersion[];
86
+ readonly posts: PostVersion[];
87
+ readonly malformed: { path: string; why: string }[];
88
+ }
89
+
90
+ function isObject(v: Json | undefined): v is JsonObject {
91
+ return v !== null && v !== undefined && typeof v === 'object' && !Array.isArray(v);
92
+ }
93
+ function asArray(v: Json | undefined): Json[] {
94
+ return Array.isArray(v) ? v : [];
95
+ }
96
+ function asString(v: Json | undefined): string | null {
97
+ return typeof v === 'string' ? v : null;
98
+ }
99
+ function isHuman(by: string | null): by is string {
100
+ return by !== null && by.toLowerCase() !== 'agent';
101
+ }
102
+
103
+ /** Human, non-provisional verdicts retain their original array indices. */
104
+ export function humanReviewVerdicts(value: Json | undefined, stage: string): { index: number; record: JsonObject }[] {
105
+ return asArray(value).flatMap((record, index) =>
106
+ isObject(record) && record.review === stage && isHuman(asString(record.by)) && record.verdict !== 'provisional'
107
+ ? [{ index, record }] : []);
108
+ }
109
+
110
+ /** A later human verdict withdraws any earlier acceptance. */
111
+ export function acceptedReviewVerdict(version: JsonObject, stage: string): number {
112
+ const last = humanReviewVerdicts(version.verdicts, stage).at(-1);
113
+ return last?.record.verdict === 'accepted' ? last.index : -1;
114
+ }
115
+
116
+ function readStill(e: JsonObject): Still | string {
117
+ const missing = ['path', 'sha256'].filter((k) => asString(e[k]) === null);
118
+ if (missing.length > 0) return `missing ${missing.join(', ')}`;
119
+ if (!SHA256.test(e.sha256 as string)) return 'sha256 is not 64 lowercase hex digits';
120
+ if (e.camera !== 'fixed') return `camera is ${JSON.stringify(e.camera ?? null)}; the rig review is on a fixed camera ("camera": "fixed")`;
121
+ if (e.frame === 'setup') return { evidence: -1, path: e.path as string, sha256: e.sha256 as string, frame: 'setup' };
122
+ if (typeof e.frame !== 'number' || !Number.isFinite(e.frame) || e.frame < 0) {
123
+ return `frame is ${JSON.stringify(e.frame ?? null)}; it is "setup" for the renderer's setup pose, or the time in seconds into the motion the still is posed at (0 is the motion's start, not necessarily the setup pose)`;
124
+ }
125
+ return { evidence: -1, path: e.path as string, sha256: e.sha256 as string, frame: e.frame };
126
+ }
127
+
128
+ /** Reads `versions[]` into rig and post versions, in record order. Paths name a version by its number: `versions[v2]`. */
129
+ export function readVersions(project: JsonObject): Versions {
130
+ const reopens = collectReopens(project, []);
131
+ const rigs: RigVersion[] = [];
132
+ const posts: PostVersion[] = [];
133
+ const malformed: { path: string; why: string }[] = [];
134
+ asArray(project.versions).forEach((v, index) => {
135
+ if (!isObject(v) || typeof v.n !== 'number' || !Number.isInteger(v.n)) {
136
+ malformed.push({ path: `versions[${index}]`, why: 'a version is an object with an integer n' });
137
+ return;
138
+ }
139
+ const path = `versions[v${v.n}]`;
140
+ if (v.kind === 'post') {
141
+ posts.push({ n: v.n, path, at: asString(v.at), rigVersion: v.rig_version, live: liveSignoff(reopens, 'post', Date.parse(asString(v.at) ?? '')) });
142
+ return;
143
+ }
144
+ if (v.kind !== 'rig') {
145
+ malformed.push({ path, why: `kind is 'rig' or 'post', got ${JSON.stringify(v.kind ?? null)}` });
146
+ return;
147
+ }
148
+ if (v.rig_version !== null && v.rig_version !== undefined) {
149
+ malformed.push({ path: `${path}.rig_version`, why: 'a rig version has rig_version null; only a post version names the rig it is built on' });
150
+ }
151
+ // The LAST human rig-review verdict decides: a later bounce withdraws an
152
+ // earlier acceptance. An acceptance must be dated, or the post-before-
153
+ // acceptance order cannot be checked.
154
+ let accepted: Acceptance | null = null;
155
+ for (const { index: verdict, record: d } of humanReviewVerdicts(v.verdicts, RIG_REVIEW)) {
156
+ if (d.verdict !== 'accepted') {
157
+ accepted = null;
158
+ continue;
159
+ }
160
+ const at = asString(d.at);
161
+ if (at === null || !Number.isFinite(Date.parse(at))) {
162
+ malformed.push({ path: `${path}.verdicts[${verdict}].at`, why: 'an acceptance carries a valid `at`; an undated acceptance authorises nothing' });
163
+ accepted = null;
164
+ continue;
165
+ }
166
+ accepted = { verdict, by: d.by as string, at, verbatim: asString(d.verbatim) ?? '' };
167
+ if (v.pre_interview === true) {
168
+ accepted = { ...accepted, verbatim: asString(d.verbatim), source: asString(d.source) ?? '', note: asString(d.note) ?? undefined };
169
+ }
170
+ }
171
+ const stills: Still[] = [];
172
+ const malformedStills: { index: number; why: string }[] = [];
173
+ asArray(v.evidence).forEach((e, i) => {
174
+ if (!isObject(e) || e.kind !== 'still') return;
175
+ const read = readStill(e);
176
+ if (typeof read === 'string') malformedStills.push({ index: i, why: read });
177
+ else stills.push({ ...read, evidence: i });
178
+ });
179
+ const outputsLost = v.pre_interview === true && isObject(v.outputs_lost)
180
+ ? { note: asString(v.outputs_lost.note) ?? '', source: asString(v.outputs_lost.source) ?? '' } : undefined;
181
+ const configLost = v.pre_interview === true && isObject(v.config_lost)
182
+ ? { note: asString(v.config_lost.note) ?? '', source: asString(v.config_lost.source) ?? '' } : undefined;
183
+ rigs.push({ configLost, outputsLost, n: v.n, path, at: asString(v.at), live: liveSignoff(reopens, RIG_REVIEW, Date.parse(asString(v.at) ?? '')), preInterview: v.pre_interview === true, accepted, stills, malformedStills });
184
+ });
185
+ return { rigs, posts, malformed };
186
+ }
187
+
188
+ /** The rig version with the highest number, accepted or not. */
189
+ function latestRig(versions: Versions): RigVersion | null {
190
+ return versions.rigs.filter(r => r.live).reduce<RigVersion | null>((a, r) => (a === null || r.n > a.n ? r : a), null);
191
+ }
192
+ /** The accepted rig version with the highest number. */
193
+ export function latestAccepted(versions: Versions): RigVersion | null {
194
+ return versions.rigs.filter((r) => r.live && r.accepted !== null).reduce<RigVersion | null>((a, r) => (a === null || r.n > a.n ? r : a), null);
195
+ }
196
+
197
+ /**
198
+ * When no live rig is accepted but an older one was: that accepted rig, and the first reopen or amendment after
199
+ * its build that made it history, named by its record path. An acceptance that lapsed this way is not a missing verdict.
200
+ */
201
+ function lapsedAcceptance(project: JsonObject, versions: Versions): string | null {
202
+ const rig = versions.rigs.filter(r => !r.live && r.accepted !== null).reduce<RigVersion | null>((a, r) => (a === null || r.n > a.n ? r : a), null);
203
+ if (rig === null || rig.accepted === null) return null;
204
+ const built = Date.parse(rig.at ?? '');
205
+ const cutoff = collectReopens(project, []).filter(r => stageIndex(r.stage) <= stageIndex(RIG_REVIEW) && !(built > r.at))
206
+ .reduce<Reopen | null>((a, r) => (a === null || r.at < a.at ? r : a), null);
207
+ if (cutoff === null) return null;
208
+ const when = new Date(cutoff.at).toISOString();
209
+ // collectReopens appends amendment cutoffs, which carry their amendments[] index.
210
+ const amendment = 'index' in cutoff && typeof cutoff.index === 'number' ? cutoff.index : -1;
211
+ const reopen = reopenIndex(project, cutoff);
212
+ const by = amendment >= 0 ? `the amendment at ${when} (amendments[${amendment}])`
213
+ : `the reopen to ${cutoff.stage} at ${when} (reopens[${reopen}])`;
214
+ return `${rig.path}.verdicts[${rig.accepted.verdict}]: rig v${rig.n} was accepted at ${rig.accepted.at}, but ${by} made it history, so no live rig version is accepted; register a new rig version with rig-create rig-version <n> and accept it with rig-create review <n> --verdict accepted --review rig-review`;
215
+ }
216
+
217
+ /**
218
+ * A rig-review signature never rests on a lapsed acceptance (rig-c/create#243): refused before the write when no live
219
+ * rig version is accepted and a reopen or amendment made the accepted one history. With no acceptance at all, the
220
+ * target selection refuses (SIGNOFF_TARGET_STALE).
221
+ */
222
+ export function assertRigReviewSignable(project: JsonObject, path: string): void {
223
+ const versions = readVersions(project);
224
+ if (latestAccepted(versions) !== null) return;
225
+ const lapsed = lapsedAcceptance(project, versions);
226
+ if (lapsed !== null) refuse('RIG_VERSION_UNACCEPTED', `${path}: rig-review would be signed on a lapsed acceptance: ${lapsed}, then sign rig-review`);
227
+ }
228
+
229
+ /** Why a post version's `rig_version` is not an accepted rig, or `null` when it is. */
230
+ function postBasisProblem(post: PostVersion, versions: Versions): string | null {
231
+ const rv = post.rigVersion;
232
+ if (typeof rv !== 'number' || !Number.isInteger(rv)) {
233
+ return `${post.path}.rig_version is ${JSON.stringify(rv ?? null)}; a post version names the n of the accepted rig version it is built on`;
234
+ }
235
+ const rig = versions.rigs.find((r) => r.n === rv);
236
+ if (rig === undefined) return `${post.path}.rig_version=v${rv} names no rig version`;
237
+ if (rig.configLost !== undefined) return `${post.path}.rig_version=v${rv}: ${rig.path}.config_lost records a lost config; build and accept a new rig with its retained config before post work`;
238
+ if (rig.outputsLost !== undefined) return `${post.path}.rig_version=v${rv}: ${rig.path}.outputs_lost records lost outputs; build and accept a new rig with surviving frozen outputs before post work`;
239
+ if (rig.accepted === null) {
240
+ return `${post.path}.rig_version=v${rv}: rig v${rv} has no accepted rig-review verdict; post work runs only on a rig version the user accepted at rig-review`;
241
+ }
242
+ const builtAt = post.at === null ? Number.NaN : Date.parse(post.at);
243
+ const acceptedAt = Date.parse(rig.accepted.at);
244
+ if (!Number.isFinite(builtAt)) {
245
+ return `${post.path}.at is ${JSON.stringify(post.at)}; a post version is dated, or it cannot be shown to follow rig v${rv}'s acceptance`;
246
+ }
247
+ if (builtAt < acceptedAt) {
248
+ return `${post.path}.rig_version=v${rv}: v${post.n} was built at ${post.at}, before rig v${rv} was accepted at ${rig.accepted.at}`;
249
+ }
250
+ return null;
251
+ }
252
+
253
+ // ------------------------------------------------------- interview --check
254
+
255
+ /** The review-order refusals, appended to `ctx.refusals` (codes in docs/CHECKS.md § Refusals). */
256
+ export function checkReviews(ctx: ReviewCtx): void {
257
+ const versions = readVersions(ctx.project);
258
+ const push = (code: string, path: string, message: string): void => {
259
+ ctx.refusals.push({ code, path, message });
260
+ };
261
+ for (const m of versions.malformed) push('VERSION_MALFORMED', m.path, `${m.path}: ${m.why}`);
262
+
263
+ for (const rig of versions.rigs) {
264
+ for (const m of rig.malformedStills) {
265
+ const path = `${rig.path}.evidence[${m.index}]`;
266
+ push('STILL_MALFORMED', path, `${path}: a still-frame record is { kind: "still", path, sha256, camera: "fixed", frame }; ${m.why}`);
267
+ }
268
+ if (rig.accepted !== null && rig.stills.length === 0) {
269
+ const path = `${rig.path}.verdicts[${rig.accepted.verdict}]`;
270
+ push(
271
+ 'RIG_ACCEPTED_WITHOUT_STILL',
272
+ path,
273
+ `${path}: rig v${rig.n} is accepted at rig-review, but its evidence[] has no still-frame record ({ kind: "still", path, sha256, camera: "fixed", frame }); the rig review rests on a still frame`,
274
+ );
275
+ }
276
+ }
277
+
278
+ for (const post of versions.posts) {
279
+ const problem = postBasisProblem(post, versions);
280
+ const since = reopenTime(collectReopens(ctx.project, []), RIG_REVIEW);
281
+ const basis = versions.rigs.find(r => r.n === post.rigVersion);
282
+ if (post.live && since !== -Infinity && basis !== undefined && !basis.live) {
283
+ push('POST_ON_SUPERSEDED_RIG', `${post.path}.rig_version`, `${post.path}: historical rig cannot authorise new post work; build and accept a new rig version`);
284
+ }
285
+ if (problem !== null) push('POST_ON_UNACCEPTED_RIG', `${post.path}.rig_version`, problem);
286
+ }
287
+
288
+ // Past rig-review, the rig the project stands on is the latest one, and it is accepted.
289
+ const reviewIdx = stageIndex(RIG_REVIEW);
290
+ const cur = stageIndex(ctx.effectiveStage);
291
+ if (ctx.effectiveStage !== PAUSED_STAGE && cur > reviewIdx) {
292
+ const latest = latestRig(versions);
293
+ const lapsed = latest === null ? lapsedAcceptance(ctx.project, versions) : null;
294
+ if (lapsed !== null) {
295
+ push('RIG_VERSION_UNACCEPTED', 'versions', `stage is past rig-review on a lapsed acceptance: ${lapsed}, at rig-review`);
296
+ } else if (latest === null) {
297
+ push('RIG_VERSION_UNACCEPTED', 'versions', `stage is past rig-review, but versions[] has no rig version for the review to have accepted`);
298
+ } else if (latest.accepted === null) {
299
+ push(
300
+ 'RIG_VERSION_UNACCEPTED',
301
+ latest.path,
302
+ `rig v${latest.n} is the latest rig version and has no accepted rig-review verdict, but the stage is past rig-review; a rig change after rig-review is a new first review — the stage goes back to rig-review`,
303
+ );
304
+ }
305
+ }
306
+
307
+ // The current post version is on the current accepted rig; older post versions are history.
308
+ const accepted = latestAccepted(versions);
309
+ const currentPost = versions.posts.filter(p => p.live).reduce<PostVersion | null>((a, p) => (a === null || p.n > a.n ? p : a), null);
310
+ if (accepted !== null && currentPost !== null && postBasisProblem(currentPost, versions) === null && currentPost.rigVersion !== accepted.n) {
311
+ push(
312
+ 'POST_ON_SUPERSEDED_RIG',
313
+ `${currentPost.path}.rig_version`,
314
+ `${currentPost.path}.rig_version=v${String(currentPost.rigVersion)}: rig v${accepted.n} was accepted since; the post work built on the old rig is redone on v${accepted.n}`,
315
+ );
316
+ }
317
+ }
318
+
319
+ // ------------------------------------------------------------------ handoff
320
+
321
+ export interface Handoff {
322
+ readonly ok: true;
323
+ readonly project_dir: string;
324
+ readonly rig_version: number;
325
+ readonly pre_interview: boolean;
326
+ readonly accepted: Acceptance;
327
+ readonly still: Still;
328
+ readonly outputs_lost?: readonly { readonly version: number; readonly note: string; readonly source: string }[];
329
+ }
330
+
331
+ /**
332
+ * The hand-off: the one rig version post work may run on, or a named refusal.
333
+ * With `rig`, also refuses when that is not the version.
334
+ */
335
+ export function handoff(projectDirArg: string, rig: number | null, now: number): Handoff {
336
+ const dir = resolve(projectDirArg);
337
+ const file = join(dir, 'project.json');
338
+ if (!existsSync(file)) refuse('PROJECT_JSON_MISSING', `${file} does not exist; run in a project folder or name one`);
339
+ let project: unknown;
340
+ try {
341
+ project = JSON.parse(readFileSync(file, 'utf8'));
342
+ } catch (e) {
343
+ refuse('PROJECT_JSON_INVALID', `${file}: ${e instanceof Error ? e.message : String(e)}`);
344
+ }
345
+ if (!isObject(project as Json)) refuse('PROJECT_JSON_INVALID', `${file}: not a JSON object`);
346
+ const record = project as JsonObject;
347
+ assertCorrectionApproval(dir, record, RIG_REVIEW, now);
348
+ const historyRefusals: ReviewCtx['refusals'] = draftVersionFindings(dir, record);
349
+ checkPreInterview({ dir, project: record, refusals: historyRefusals, warnings: [] });
350
+ const first = historyRefusals[0];
351
+ if (first !== undefined) refuse(first.code, first.message);
352
+
353
+ const reopens = collectReopens(record, historyRefusals);
354
+ if (historyRefusals[0] !== undefined) refuse(historyRefusals[0].code, historyRefusals[0].message);
355
+ const signed = asArray(record.signoffs).some(
356
+ (s) => isObject(s) && liveSignoff(reopens, RIG_REVIEW, Date.parse(asString(s.at) ?? '')) && s.stage === RIG_REVIEW && (s.checkpoint ?? null) === null && isHuman(asString(s.by)) && asString(s.verbatim) !== null,
357
+ );
358
+ if (!signed) refuse('RIG_REVIEW_UNSIGNED', `no human { "stage": "rig-review" } sign-off: no rig version has been handed to post-production`);
359
+
360
+ const versions = readVersions(record);
361
+ const accepted = latestAccepted(versions);
362
+ const latest = latestRig(versions);
363
+ if (accepted === null || accepted.accepted === null) {
364
+ const lapsed = lapsedAcceptance(record, versions);
365
+ refuse('RIG_VERSION_UNACCEPTED', lapsed !== null ? `rig-review is signed on a lapsed acceptance: ${lapsed} before post work runs`
366
+ : `rig-review is signed, but no rig version has an accepted rig-review verdict naming it`);
367
+ }
368
+ if (latest !== null && latest.n !== accepted.n) {
369
+ refuse(
370
+ 'RIG_VERSION_UNACCEPTED',
371
+ `${latest.path}: rig v${latest.n} was built after rig v${accepted.n} was accepted and has no rig-review acceptance of its own; review it on its still frame before post work runs`,
372
+ );
373
+ }
374
+ if (accepted.configLost !== undefined) {
375
+ refuse('PRE_INTERVIEW_CONFIG_LOST', `${accepted.path}.config_lost: ${accepted.configLost.note}; source: ${accepted.configLost.source}; build and accept a new rig with its retained config before post work`);
376
+ }
377
+ if (accepted.outputsLost !== undefined) {
378
+ refuse('PRE_INTERVIEW_OUTPUTS_LOST', `${accepted.path}.outputs_lost: ${accepted.outputsLost.note}; source: ${accepted.outputsLost.source}; build and accept a new rig with surviving frozen outputs before post work`);
379
+ }
380
+ const still = accepted.stills[0];
381
+ if (still === undefined) {
382
+ refuse('RIG_ACCEPTED_WITHOUT_STILL', `${accepted.path}: rig v${accepted.n} is accepted with no still-frame record in its evidence[]`);
383
+ }
384
+ if (rig !== null && rig !== accepted.n) {
385
+ const named = versions.rigs.find((r) => r.n === rig);
386
+ if (named !== undefined && named.accepted !== null) {
387
+ refuse('POST_ON_SUPERSEDED_RIG', `versions[v${rig}]: rig v${rig} was accepted, but rig v${accepted.n} was accepted since; post work runs on v${accepted.n}`);
388
+ }
389
+ refuse('POST_ON_UNACCEPTED_RIG', `versions[v${rig}]: ${named === undefined ? 'no rig version has that number' : `rig v${rig} has no accepted rig-review verdict`}; post work runs on rig v${accepted.n}`);
390
+ }
391
+ assertFigureMaskApproval(dir, record, accepted.n);
392
+ const losses = asArray(record.versions).flatMap((v) => isObject(v) && v.pre_interview === true && isObject(v.outputs_lost)
393
+ ? [{ version: v.n as number, note: v.outputs_lost.note as string, source: v.outputs_lost.source as string }] : []);
394
+ return { ...(losses.length > 0 ? { outputs_lost: losses } : {}), ok: true, project_dir: dir, rig_version: accepted.n, pre_interview: accepted.preInterview, accepted: accepted.accepted, still };
395
+ }
396
+
397
+ export const HANDOFF_HELP = `handoff:
398
+ [<project dir>] the project folder (default: the current directory)
399
+ --rig <n> refuse unless rig version <n> is the one post work may run on
400
+ --json print the hand-off as JSON (rig_version, accepted, still)
401
+ Post-production tooling runs this first. Exit 0 prints the accepted rig version; exit 1 is a named refusal
402
+ (RIG_REVIEW_UNSIGNED, RIG_VERSION_UNACCEPTED, RIG_ACCEPTED_WITHOUT_STILL, POST_ON_UNACCEPTED_RIG, POST_ON_SUPERSEDED_RIG).`;
403
+
404
+ export function handoffCommand(args: string[], now: number): { stdout: string; exit: number } {
405
+ let json = false;
406
+ let rig: number | null = null;
407
+ const positional: string[] = [];
408
+ for (let i = 0; i < args.length; i++) {
409
+ const arg = args[i] as string;
410
+ if (arg === '--json') json = true;
411
+ else if (arg === '--rig') {
412
+ const value = args[++i];
413
+ const n = value === undefined ? Number.NaN : Number(value.replace(/^v/, ''));
414
+ if (!Number.isInteger(n) || n < 1) refuse('HANDOFF_RIG_INVALID', `--rig takes a rig version number, got ${value ?? 'nothing'}`, USAGE_EXIT);
415
+ rig = n;
416
+ } else if (arg.startsWith('-')) refuse('UNKNOWN_OPTION', `handoff does not take ${arg}`, USAGE_EXIT);
417
+ else positional.push(arg);
418
+ }
419
+ if (positional.length > 1) refuse('HANDOFF_TOO_MANY_ARGUMENTS', `handoff takes one folder, got ${positional.join(' ')}`, USAGE_EXIT);
420
+ const h = handoff(positional[0] ?? process.cwd(), rig, now);
421
+ if (json) return { stdout: JSON.stringify(h, null, 2), exit: 0 };
422
+ return {
423
+ stdout: [
424
+ `handoff: post work is on rig v${h.rig_version}${h.pre_interview ? ' (pre-interview)' : ''}`,
425
+ ` accepted at rig-review by ${h.accepted.by} at ${h.accepted.at}: ${h.accepted.verbatim === null ? `record of the user: ${h.accepted.note}` : `"${h.accepted.verbatim}"`}`,
426
+ ...(h.pre_interview ? [` source: ${h.accepted.source}`] : []),
427
+ ...(h.outputs_lost ?? []).map((loss) => ` v${loss.version} (pre-interview) outputs lost: ${loss.note}; source: ${loss.source}`),
428
+ ` still frame ${h.still.path} (sha256 ${h.still.sha256}, fixed camera, ${h.still.frame === 'setup' ? 'setup pose' : `${h.still.frame} s`})`,
429
+ ].join('\n'),
430
+ exit: 0,
431
+ };
432
+ }