@rig-c/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
@@ -0,0 +1,42 @@
1
+ /** Member-local departures bind the collection answer they departed from. */
2
+ import { asArray, answered, classify, collectItems, collectionAnswer, collectionQuestion, isObject, type Finding } from './check.ts';
3
+ import type { JsonObject } from './templates.ts';
4
+
5
+ export const OVERRIDDEN_ANSWERS_SINCE = '2026-11-06T00:00:00Z';
6
+
7
+ /** Shared by the record check and the writer, before any write. */
8
+ export function overrideFindings(answer: JsonObject, collection: JsonObject | null, path: string): Finding[] {
9
+ const findings: Finding[] = [];
10
+ const add = (code: string, why: string, remedy: string): void => {
11
+ findings.push({ code, path, message: `${path}: ${why}; ${remedy}` });
12
+ };
13
+ const ref = isObject(answer.overrides) ? answer.overrides : {};
14
+ if (collection === null || typeof collection.name !== 'string' || ref.collection !== collection.name) {
15
+ add('OVERRIDE_COLLECTION_INVALID', 'overrides.collection does not name this member\'s collection', 'restore the parent collection and record its name in overrides.collection');
16
+ } else {
17
+ const question = typeof ref.question === 'string' ? collectionQuestion(collection, ref.question) : undefined;
18
+ const current = typeof ref.question === 'string' ? collectionAnswer(collection, ref.question) : undefined;
19
+ const answers = [current, ...asArray(question?.history)].filter(isObject).filter(a => answered(classify(a, { collection: null })));
20
+ if (question === undefined || answers.length === 0) {
21
+ add('OVERRIDE_QUESTION_INVALID', 'overrides.question names no answered collection question', 'reference an answered collection question, retaining its original answer in history');
22
+ } else if (typeof ref.collection_answer_at !== 'string' || !Number.isFinite(Date.parse(ref.collection_answer_at)) || !answers.some(a => a.at === ref.collection_answer_at)) {
23
+ add('OVERRIDE_ANSWER_STALE', 'overrides.collection_answer_at matches no current or historical collection answer', 'restore the referenced collection answer and its exact at timestamp, or ask and record a fresh override');
24
+ }
25
+ }
26
+ if (typeof answer.why_verbatim !== 'string' || !answer.why_verbatim.trim()) {
27
+ add('OVERRIDE_REASON_MISSING', 'why_verbatim is not a nonempty string', 'ask the user why this member differs and record their exact words with interview-answer --why-file');
28
+ }
29
+ return findings;
30
+ }
31
+
32
+ export function checkOverrides(project: JsonObject, collection: JsonObject | null, refusals: Finding[], warnings: Finding[]): void {
33
+ for (const item of collectItems(project, { collection })) {
34
+ const records = [{ answer: item.raw, path: item.path }, ...asArray(item.record.history).map((answer, i) => ({ answer, path: `${item.path.replace(/\.(answer|guide)$/, '')}.history[${i}]` }))];
35
+ for (const { answer, path } of records) {
36
+ if (!isObject(answer) || answer.basis !== 'overridden') continue;
37
+ const at = typeof answer.at === 'string' ? Date.parse(answer.at) : NaN;
38
+ const target = at < Date.parse(OVERRIDDEN_ANSWERS_SINCE) ? warnings : refusals;
39
+ target.push(...overrideFindings(answer, collection, path));
40
+ }
41
+ }
42
+ }
@@ -0,0 +1,32 @@
1
+ /** Optional package migration history: old records need no adoption date. */
2
+ import { isObject, type Finding } from './check.ts';
3
+ import type { JsonObject } from './templates.ts';
4
+
5
+ export const PACKAGE_NAMES = ['@rig-c/core', '@rig-c/parts', '@rig-c/review'] as const;
6
+ export const exactVersion = (value: unknown): value is string => typeof value === 'string' && /^\d+\.\d+\.\d+$/.test(value);
7
+ export function compareVersions(a: string, b: string): number {
8
+ const right = b.split('.').map(Number);
9
+ for (const [index, value] of a.split('.').map(Number).entries()) {
10
+ if (value !== right[index]) return value - right[index]!;
11
+ }
12
+ return 0;
13
+ }
14
+
15
+ export function packageUpgradeFindings(project: JsonObject): Finding[] {
16
+ const history = project.package_upgrades;
17
+ if (history === undefined) return [];
18
+ const bad = (path: string): Finding => ({ code: 'PACKAGE_UPGRADE_MALFORMED', path, message: `${path}: invalid package upgrade history; restore the writer's from/to versions, operator, timestamp and lock integrity (rig-create packages upgrade)` });
19
+ if (!Array.isArray(history)) return [bad('package_upgrades')];
20
+ const out: Finding[] = [];
21
+ for (const [index, row] of history.entries()) {
22
+ const path = `package_upgrades[${index}]`;
23
+ if (!isObject(row) || typeof row.by !== 'string' || !row.by.trim() || typeof row.at !== 'string' || !Number.isFinite(Date.parse(row.at)) || typeof row.lock_sha256 !== 'string' || !/^[a-f0-9]{64}$/.test(row.lock_sha256) || !Array.isArray(row.packages) || row.packages.length !== PACKAGE_NAMES.length) {
24
+ out.push(bad(path)); continue;
25
+ }
26
+ for (const [i, name] of PACKAGE_NAMES.entries()) {
27
+ const pin = row.packages[i];
28
+ if (!isObject(pin) || pin.name !== name || (pin.from !== null && !exactVersion(pin.from)) || !exactVersion(pin.to) || (typeof pin.from === 'string' && compareVersions(pin.from, pin.to) > 0)) out.push(bad(`${path}.packages[${i}]`));
29
+ }
30
+ }
31
+ return out;
32
+ }
@@ -0,0 +1,6 @@
1
+ /** A fresh process avoids parts module-cache reuse across an install swap. */
2
+ import { checkProject } from './check.ts';
3
+ if (import.meta.main) {
4
+ const input = await Bun.stdin.json();
5
+ process.stdout.write(JSON.stringify(checkProject(input.dir, input.project, { now: input.now, readOnly: true })));
6
+ }
@@ -0,0 +1,168 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { closeSync, existsSync, lstatSync, mkdtempSync, openSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join, resolve } from 'node:path';
5
+ import { isObject, type CheckReport } from './check.ts';
6
+ import { acquireBoundJobWrite, jobRuntimeExecArgv } from './job-writer.ts';
7
+ import { PACKAGE_NAMES, compareVersions, exactVersion, packageUpgradeFindings } from './package-upgrades.ts';
8
+ import { PINNED_PACKAGES } from './pins.ts';
9
+ import { Refusal, refuse, USAGE_EXIT } from './refusal.ts';
10
+ import type { JsonObject } from './templates.ts';
11
+
12
+ /** Value spans permit literal edits without reserializing unrelated JSON. */
13
+ export function jsonSpans(text: string): Map<string, [number, number]> {
14
+ JSON.parse(text);
15
+ let at = 0;
16
+ const spans = new Map<string, [number, number]>();
17
+ const ws = () => { while (/\s/.test(text[at] ?? '') && at < text.length) at++; };
18
+ const string = () => { const start = at++; while (at < text.length) { if (text[at++] === '"') break; if (text[at - 1] === '\\') at++; } return JSON.parse(text.slice(start, at)) as string; };
19
+ const value = (path: string[]) => {
20
+ ws(); const start = at;
21
+ if (text[at] === '{') {
22
+ at++; ws(); const keys = new Set<string>();
23
+ while (text[at] !== '}') {
24
+ const key = string();
25
+ if (keys.has(key)) refuse('PACKAGE_UPGRADE_INPUT', 'package.json/project.json: duplicate JSON keys; remove the ambiguity before packages upgrade');
26
+ keys.add(key); ws(); at++; value([...path, key]); ws();
27
+ if (text[at] !== ',') break;
28
+ at++; ws();
29
+ }
30
+ at++;
31
+ } else if (text[at] === '[') {
32
+ at++; ws(); let index = 0;
33
+ while (text[at] !== ']') { value([...path, String(index++)]); ws(); if (text[at] !== ',') break; at++; ws(); }
34
+ at++;
35
+ } else if (text[at] === '"') string();
36
+ else { while (at < text.length && !/[\s,\]}]/.test(text[at]!)) at++; }
37
+ spans.set(JSON.stringify(path), [start, at]);
38
+ };
39
+ value([]); return spans;
40
+ }
41
+ function edit(text: string, path: string[], replacement: string): string {
42
+ const spans = jsonSpans(text);
43
+ const span = spans.get(JSON.stringify(path));
44
+ if (span) return text.slice(0, span[0]) + replacement + text.slice(span[1]);
45
+ const parent = path.slice(0, -1);
46
+ const container = spans.get(JSON.stringify(parent));
47
+ if (!container) refuse('PACKAGE_UPGRADE_INPUT', 'package.json/project.json: missing object; restore the project manifest before packages upgrade');
48
+ const object = JSON.parse(text.slice(...container));
49
+ const pos = container[1] - 1;
50
+ return text.slice(0, pos) + `${Object.keys(object).length ? ',' : ''}\n ${JSON.stringify(path.at(-1))}: ${replacement}\n` + text.slice(pos);
51
+ }
52
+ function appendUpgrade(text: string, row: object): string {
53
+ const span = jsonSpans(text).get(JSON.stringify(['package_upgrades']));
54
+ if (!span) return edit(text, ['package_upgrades'], JSON.stringify([row]));
55
+ const prior = JSON.parse(text.slice(...span));
56
+ const at = span[1] - 1;
57
+ return text.slice(0, at) + `${prior.length ? ',' : ''}${JSON.stringify(row)}` + text.slice(at);
58
+ }
59
+ function checked(dir: string, project: JsonObject, now: number): CheckReport {
60
+ const child = Bun.spawnSync([process.execPath, ...jobRuntimeExecArgv(), join(import.meta.dir, 'packages-upgrade-check.ts')], { stdin: Buffer.from(JSON.stringify({ dir, project, now })), stdout: 'pipe', stderr: 'pipe' });
61
+ if (child.exitCode !== 0) refuse('PACKAGE_UPGRADE_CHECK_FAILED', 'project.json: check process failed; restore the installed packages and retry packages upgrade');
62
+ return JSON.parse(child.stdout.toString()) as CheckReport;
63
+ }
64
+
65
+ export function packagesCommand(args: string[], now: number): string {
66
+ if (args[0] !== 'upgrade') refuse('PACKAGE_UPGRADE_USAGE', 'project.json: use packages upgrade [dir] --by <operator>', USAGE_EXIT);
67
+ let by = ''; let dirArg: string | undefined;
68
+ for (let i = 1; i < args.length; i++) {
69
+ const arg = args[i]!;
70
+ if (arg === '--by' && !by && args[i + 1] && !args[i + 1]!.startsWith('-')) by = args[++i]!;
71
+ else if (!arg.startsWith('-') && dirArg === undefined) dirArg = arg;
72
+ else refuse('PACKAGE_UPGRADE_USAGE', 'project.json: use packages upgrade [dir] --by <operator>', USAGE_EXIT);
73
+ }
74
+ if (!by.trim()) refuse('PACKAGE_UPGRADE_USAGE', 'project.json: packages upgrade requires --by <operator>', USAGE_EXIT);
75
+ const dir = resolve(dirArg ?? process.cwd());
76
+ const release = acquireBoundJobWrite(dir);
77
+ const lock = join(dir, '.rig-create-packages.lock');
78
+ let fd: number;
79
+ try { fd = openSync(lock, 'wx'); } catch { release(); return refuse('PACKAGE_UPGRADE_CONFLICT', 'project.json: another package writer holds the lock; finish it before packages upgrade'); }
80
+ let scratch: string | undefined;
81
+ let swapped = false; let committed = false;
82
+ const backup = join(dir, `.node_modules-${randomUUID()}`);
83
+ const paths = ['package.json', 'bun.lock', 'project.json'];
84
+ const originals = new Map<string, Buffer | null>();
85
+ try {
86
+ for (const path of paths) originals.set(path, existsSync(join(dir, path)) ? readFileSync(join(dir, path)) : null);
87
+ const pkgBytes = originals.get('package.json'); const recordBytes = originals.get('project.json');
88
+ if (!pkgBytes || !recordBytes) refuse('PACKAGE_UPGRADE_INPUT', 'project.json/package.json: missing record or manifest; restore them before packages upgrade');
89
+ let pkg: JsonObject; let project: JsonObject;
90
+ try { pkg = JSON.parse(pkgBytes.toString()); project = JSON.parse(recordBytes.toString()); jsonSpans(pkgBytes.toString()); jsonSpans(recordBytes.toString()); }
91
+ catch { return refuse('PACKAGE_UPGRADE_INPUT', 'project.json/package.json: invalid JSON; restore valid objects before packages upgrade'); }
92
+ if (!isObject(project) || !isObject(pkg)) refuse('PACKAGE_UPGRADE_INPUT', 'project.json/package.json: expected JSON objects; restore valid objects before packages upgrade');
93
+ if (project.schema !== 'rig-create/project@0' || !pkg.dependencies || typeof pkg.dependencies !== 'object' || Array.isArray(pkg.dependencies)) refuse('PACKAGE_UPGRADE_INPUT', 'project.json/package.json: expected a project and dependencies object; restore them before packages upgrade');
94
+ const malformed = packageUpgradeFindings(project)[0];
95
+ if (malformed) refuse(malformed.code, malformed.message);
96
+ const dependencies = pkg.dependencies as JsonObject;
97
+ const pins = { ...PINNED_PACKAGES, '@rig-c/review': JSON.parse(readFileSync(join(import.meta.dir, '../package.json'), 'utf8')).dependencies['@rig-c/review'] as string };
98
+ const packages = PACKAGE_NAMES.map(name => {
99
+ const from = dependencies[name] ?? null; const to = pins[name];
100
+ if (!exactVersion(to) || from !== null && !exactVersion(from)) refuse('PACKAGE_UPGRADE_INPUT', `package.json.dependencies[${name}]: expected an exact release version; resolve ranges before packages upgrade`);
101
+ if (from !== null && compareVersions(from as string, to) > 0) refuse('PACKAGE_UPGRADE_DOWNGRADE', `package.json.dependencies[${name}]: ${from} is newer than ${to}; use a newer Create release`);
102
+ for (const section of ['devDependencies', 'optionalDependencies', 'peerDependencies']) {
103
+ if (pkg[section] && typeof pkg[section] === 'object' && name in pkg[section]) refuse('PACKAGE_UPGRADE_INPUT', `package.json.${section}[${name}]: duplicate pin location; keep the release pin in dependencies before packages upgrade`);
104
+ }
105
+ return { name, from, to };
106
+ });
107
+ if (packages.every(pin => pin.from === pin.to)) refuse('PACKAGE_UPGRADE_CURRENT', 'package.json.dependencies: all packages already match Create release pins; no upgrade to record');
108
+ // A staging install must not resolve local dependencies against a different directory.
109
+ for (const section of ['dependencies', 'devDependencies', 'optionalDependencies']) {
110
+ const entries = pkg[section];
111
+ if (entries && typeof entries === 'object') for (const value of Object.values(entries)) if (typeof value === 'string' && /^(?:file:|link:|workspace:|\.\.?\/|\/)/.test(value)) refuse('PACKAGE_UPGRADE_INPUT', `package.json.${section}: local dependencies cannot be staged safely; publish or resolve them before packages upgrade`);
112
+ }
113
+ if (pkg.workspaces) refuse('PACKAGE_UPGRADE_INPUT', 'package.json.workspaces: upgrade a standalone project install; resolve workspace ownership before packages upgrade');
114
+ const before = checked(dir, project, now);
115
+ if (before.stage === null) refuse('PACKAGE_UPGRADE_CHECK_FAILED', 'project.json: check stopped before validating the project; repair its schema and stage before packages upgrade');
116
+ let manifest = pkgBytes.toString();
117
+ for (const pin of packages) manifest = edit(manifest, ['dependencies', pin.name], JSON.stringify(pin.to));
118
+ scratch = mkdtempSync(join(tmpdir(), 'rig-create-packages-'));
119
+ writeFileSync(join(scratch, 'package.json'), manifest);
120
+ const oldLock = originals.get('bun.lock');
121
+ if (oldLock) writeFileSync(join(scratch, 'bun.lock'), oldLock);
122
+ const install = Bun.spawnSync([process.execPath, 'install', '--ignore-scripts'], { cwd: scratch, stdout: 'pipe', stderr: 'pipe' });
123
+ if (install.exitCode !== 0 || !existsSync(join(scratch, 'bun.lock'))) refuse('PACKAGE_UPGRADE_INSTALL', 'package.json/bun.lock: staged bun install failed; check registry access and package versions before retrying packages upgrade');
124
+ const nextLock = readFileSync(join(scratch, 'bun.lock'));
125
+ const row = { by, at: new Date(now).toISOString(), packages, lock_sha256: createHash('sha256').update(nextLock).digest('hex') };
126
+ const history = [...(project.package_upgrades as JsonObject[] | undefined ?? []), row];
127
+ const candidate = { ...project, package_upgrades: history };
128
+ const invalid = packageUpgradeFindings(candidate)[0];
129
+ if (invalid) refuse(invalid.code, invalid.message);
130
+ for (const [path, bytes] of originals) {
131
+ const current = existsSync(join(dir, path)) ? readFileSync(join(dir, path)) : null;
132
+ if (bytes === null ? current !== null : !current?.equals(bytes)) refuse('PACKAGE_UPGRADE_CONFLICT', `project.json: ${path} changed during installation; retry packages upgrade against the current files`);
133
+ }
134
+ if (existsSync(join(dir, 'node_modules'))) {
135
+ if (!lstatSync(join(dir, 'node_modules')).isDirectory()) refuse('PACKAGE_UPGRADE_INPUT', 'package.json: node_modules must be a project-owned directory; restore a standalone install before packages upgrade');
136
+ renameSync(join(dir, 'node_modules'), backup);
137
+ }
138
+ swapped = true;
139
+ renameSync(join(scratch, 'node_modules'), join(dir, 'node_modules'));
140
+ // The check sees the candidate install and package pins, but never a committed history row.
141
+ writeFileSync(join(dir, 'package.json'), manifest);
142
+ writeFileSync(join(dir, 'bun.lock'), nextLock);
143
+ const after = checked(dir, candidate, now);
144
+ const counts = new Map<string, number>();
145
+ const key = (finding: CheckReport['refusals'][number]) => JSON.stringify(finding);
146
+ for (const finding of before.refusals) counts.set(key(finding), (counts.get(key(finding)) ?? 0) + 1);
147
+ for (const finding of after.refusals) {
148
+ const count = counts.get(key(finding)) ?? 0;
149
+ if (!count) refuse('PACKAGE_UPGRADE_REFUSAL', `project.json: ${finding.code} at ${finding.path} changed after upgrading ${packages.filter(pin => pin.from !== pin.to).map(pin => pin.name).join(', ')}; keep the original pins and resolve this finding before retrying (${finding.message})`);
150
+ counts.set(key(finding), count - 1);
151
+ }
152
+ writeFileSync(join(dir, 'project.json'), appendUpgrade(recordBytes.toString(), row));
153
+ committed = true;
154
+ return `packages: upgraded ${packages.map(pin => `${pin.name} ${pin.from ?? '(absent)'} → ${pin.to}`).join(', ')}; recorded package_upgrades[${history.length - 1}]`;
155
+ } catch (error) {
156
+ if (error instanceof Refusal) throw error;
157
+ return refuse('PACKAGE_UPGRADE_INSTALL', 'project.json/package.json: package migration could not complete; restore filesystem access and retry packages upgrade');
158
+ } finally {
159
+ if (swapped && !committed) {
160
+ rmSync(join(dir, 'node_modules'), { recursive: true, force: true });
161
+ if (existsSync(backup)) renameSync(backup, join(dir, 'node_modules'));
162
+ for (const [path, bytes] of originals) if (bytes === null) rmSync(join(dir, path), { force: true }); else writeFileSync(join(dir, path), bytes);
163
+ }
164
+ if (committed) rmSync(backup, { recursive: true, force: true });
165
+ if (scratch) rmSync(scratch, { recursive: true, force: true });
166
+ closeSync(fd); rmSync(lock); release();
167
+ }
168
+ }
@@ -0,0 +1,96 @@
1
+ import { PARTS_CLI_RECEIPT } from './parts-verdict-record.ts';
2
+ /** Typed semantic evidence retained by the parts-review API route. */
3
+ import { dirname, join } from 'node:path';
4
+ import { readFileSync } from './check-reads.ts';
5
+ import { isDeepStrictEqual } from 'node:util';
6
+ import { publicationFile, publishedDraft } from './job-publication.ts';
7
+ import { jobHash, publicationVersionHash } from './job-outbox.ts';
8
+ import { decodeReviewAct, partsActIssues, type PartsVerdictAct } from './review-act.ts';
9
+ import { stepHash } from './step-hash.ts';
10
+ import { refuse } from './refusal.ts';
11
+ import type { JsonObject } from './templates.ts';
12
+
13
+ const object = (value: unknown): value is JsonObject => value !== null && typeof value === 'object' && !Array.isArray(value);
14
+ const fail = (why: string): never => refuse('REVIEW_ACT_SOURCE_STALE', `project.json.review_acts: ${why}; restore the reviewed draft or record and review a new draft id`);
15
+
16
+ export function partsActDraftHash(project: JsonObject, act: PartsVerdictAct): string {
17
+ const hash = publicationVersionHash(Buffer.from(JSON.stringify(project)), { path: '', selector: act.source.selector });
18
+ return hash ?? fail('reviewed draft is absent');
19
+ }
20
+
21
+ export function partsActReceipt(project: JsonObject, act: PartsVerdictAct): JsonObject | undefined {
22
+ return Array.isArray(project.review_acts) ? project.review_acts.find(entry => object(entry) && entry.jobId === act.jobId && entry.actId === act.actId) as JsonObject | undefined : undefined;
23
+ }
24
+
25
+ /** File hashes are checked as declared. No enumeration or producer measurement. */
26
+ export function assertPartsActSource(root: string, project: JsonObject, act: PartsVerdictAct, options: { comparePlan?: boolean } = {}): void {
27
+ let draft: JsonObject;
28
+ try { draft = publishedDraft(project, act.source.selector.id); }
29
+ catch { return fail('reviewed draft is absent or ambiguous'); }
30
+ const parts = draft.parts as JsonObject;
31
+ const bound = [draft.config, ...(draft.config_inputs as JsonObject[] ?? []), ...(parts.files as JsonObject[]), ...(draft.files as JsonObject[])];
32
+ const check = (identity: { path: string; sha256: string }): void => {
33
+ try { if (jobHash(publicationFile(root, identity.path)) !== identity.sha256) fail(`${identity.path} changed`); }
34
+ catch { fail(`${identity.path} is absent or changed`); }
35
+ };
36
+ for (const raw of bound) {
37
+ if (!object(raw) || typeof raw.path !== 'string' || typeof raw.sha256 !== 'string') return fail('draft file identity is malformed');
38
+ check({ path: String(raw.path), sha256: String(raw.sha256) });
39
+ }
40
+ if (act.source.parts.manifest.path !== `${parts.path}/parts.json`) fail('parts manifest differs from the selected draft');
41
+ const manifestIdentity = (parts.files as JsonObject[]).find(file => file.path === act.source.parts.manifest.path);
42
+ if (manifestIdentity?.sha256 !== act.source.parts.manifest.sha256) fail('manifest hash differs from the draft');
43
+ check(act.source.parts.manifest);
44
+ for (const image of act.source.parts.images) check({ path: join(dirname(act.source.parts.manifest.path), image.path), sha256: image.sha256 });
45
+ const manifest = JSON.parse(publicationFile(root, act.source.parts.manifest.path).toString('utf8')) as { parts: { name: string }[] };
46
+ const names = manifest.parts.map(part => part.name).sort();
47
+ const issues = partsActIssues(act);
48
+ if (issues !== null) {
49
+ const manifestNames = new Set(names);
50
+ if (issues.some(issue => issue.parts.some(part => !manifestNames.has(part.name)))) fail('verdict part membership differs from the bound manifest');
51
+ } else if (act.schema === 'rig-review/act@2' && !isDeepStrictEqual(names, act.value.parts.map(part => part.partId).sort())) fail('verdict part membership differs from the bound manifest');
52
+ if (act.source.model) {
53
+ if (!(draft.files as JsonObject[]).some(file => file.path === act.source.model!.path && file.sha256 === act.source.model!.sha256)) fail('displayed model is outside the selected draft');
54
+ check(act.source.model);
55
+ }
56
+ if (act.source.direction) check(act.source.direction);
57
+ if (act.source.plan && options.comparePlan !== false) {
58
+ const steps = object(project.plan) && Array.isArray(project.plan.steps) ? project.plan.steps : [];
59
+ if (!isDeepStrictEqual(act.source.plan.steps, steps.map(step => ({ id: object(step) ? step.id : null, hash: object(step) ? stepHash(step) : null })))) fail('displayed plan changed');
60
+ }
61
+ const publication = JSON.parse(publicationFile(root, `review-draft-${draft.id}.json`).toString('utf8')) as JsonObject;
62
+ if (publication.id !== act.source.package.id || publication.revision !== act.source.package.revision
63
+ || act.source.package.revision !== partsActDraftHash(project, act)
64
+ || (publication.model ?? null) !== (act.source.model?.path ?? null)) fail('published package identity changed');
65
+ }
66
+
67
+ export function partsActFindings(root: string, project: JsonObject): { code: string; path: string; message: string }[] {
68
+ if (!Array.isArray(project.review_acts)) return [];
69
+ return project.review_acts.flatMap((entry, index) => {
70
+ if (!object(entry) || entry.kind !== 'parts-verdict' || entry.schema === PARTS_CLI_RECEIPT) return [];
71
+ const path = `review_acts[${index}].evidence`;
72
+ try {
73
+ if (entry.schema !== 'rig-create/review-act@1' || typeof entry.jobId !== 'string' || typeof entry.actId !== 'string'
74
+ || !object(entry.evidence) || entry.evidence.path !== `evidence/review-acts/${jobHash(JSON.stringify([entry.jobId, entry.actId]))}.json`
75
+ || entry.evidence.sha256 !== entry.sha256) throw Error();
76
+ const bytes = publicationFile(root, entry.evidence.path);
77
+ if (jobHash(bytes) !== entry.sha256 || bytes.toString('utf8') !== entry.bytes) throw Error();
78
+ decodeReviewAct(bytes.toString('utf8'), 'parts-verdict');
79
+ return [];
80
+ } catch { return [{ code: 'REVIEW_ACT_LEDGER_INVALID', path, message: `${path}: retained evidence differs from its act receipt; restore the original evidence and ledger together` }]; }
81
+ });
82
+ }
83
+
84
+ export function checkPartsActEvidence(root: string, bytes: Buffer, evidenceHash: string, candidate?: JsonObject): string {
85
+ const act = decodeReviewAct(bytes.toString('utf8'), 'parts-verdict');
86
+ const project = candidate ?? JSON.parse(readFileSync(join(root, 'project.json'), 'utf8')) as JsonObject;
87
+ const receipt = partsActReceipt(project, act);
88
+ if (!receipt || receipt.kind !== 'parts-verdict' || receipt.sha256 !== evidenceHash || receipt.bytes !== bytes.toString('utf8')) {
89
+ refuse('REVIEW_ACT_LEDGER_INVALID', 'project.json.review_acts: parts evidence has no matching recorded receipt; ingest it with parts-review --from-review');
90
+ }
91
+ // Later plan approvals can fill checkpoints without changing the reviewed draft.
92
+ assertPartsActSource(root, project, act, { comparePlan: false });
93
+ if (partsActDraftHash(project, act) !== receipt.version_hash) fail('recorded draft entry changed');
94
+ if (act.value.verdict !== 'approved') refuse('PARTS_VERDICT_NOT_APPROVED', 'signoffs.parts-review.evidence: semantic verdict is not approved; review the corrected draft and record a separate approval');
95
+ return join(root, act.source.parts.manifest.path);
96
+ }
@@ -0,0 +1,242 @@
1
+ /**
2
+ * The parts-stage check report that a `parts` → `parts-review` move binds
3
+ * (docs/PROJECT.md § Recording forward progress, docs/CHECKS.md § Parts-stage check at the parts → parts-review move).
4
+ *
5
+ * The report is `@rig-c/parts`' `rig-parts check-parts` output, spec `rig-parts-parts-check/1`.
6
+ * create reads it bound by the sha256 of its bytes and by its spec, copies `exit_code`, `holes` and
7
+ * `reported` from it, and never runs or recomputes it. Matching the report to the parts on disk hashes
8
+ * the files under the assembled root and compares them with the report's own `inputs` rows; that is a
9
+ * binding, not a measurement. The writer and the check share `partsCheckFindings`.
10
+ */
11
+ import { lstatSync, readdirSync, readFileSync, realpathSync, statSync } from './check-reads.ts';
12
+ import { isAbsolute, join, relative, resolve } from 'node:path';
13
+ import { isDeepStrictEqual } from 'node:util';
14
+ import { asArray, isObject, type Ctx, type Finding } from './check.ts';
15
+ import { sha256 } from './choices.ts';
16
+ import { IGNORED_DIRS } from './init.ts';
17
+ import type { Json, JsonObject } from './templates.ts';
18
+
19
+ export const PARTS_CHECK_SPEC = 'rig-parts-parts-check/1';
20
+
21
+ /** Dates the parts-check requirement by the advance's own `at`. */
22
+ export const PARTS_CHECK_SINCE = '2026-11-08T00:00:00Z';
23
+
24
+ const REPORT_KEYS = ['spec', 'stage', 'inputs', 'parts', 'refusals', 'reported', 'holes', 'exit_code'];
25
+ const RECORD_KEYS = ['path', 'sha256', 'spec', 'parts', 'exit_code', 'holes', 'reported'];
26
+ const ROW_KEYS: Record<string, readonly string[]> = {
27
+ inputs: ['path', 'status', 'sha256'],
28
+ refusals: ['code', 'object', 'found', 'required', 'detail'],
29
+ reported: ['code', 'set', 'part', 'source', 'count', 'spans'],
30
+ holes: ['code', 'object', 'reason', 'line'],
31
+ };
32
+ const STATUSES = ['read', 'missing', 'unreadable', 'absent'];
33
+ /** The roots a clone does not carry, as the path is written; a link inside the project is followed as for the parts verdict. */
34
+ const isIgnoredRoot = (root: string): boolean => [...IGNORED_DIRS, 'node_modules', '.git'].includes(root);
35
+ const RUN = 'run rig-parts check-parts --parts <assembled dir> --out <dir outside it>, retain parts-check.json under the project (for example evidence/) and advance with --parts-check <report> --parts <assembled dir>';
36
+
37
+ /** The move whose arrival needs a passing parts-stage check. */
38
+ export function isPartsCheckMove(entry: JsonObject): boolean {
39
+ return entry.from_stage === 'parts' && entry.stage === 'parts-review';
40
+ }
41
+
42
+ const literal = (value: Json | undefined): value is string => typeof value === 'string' && value !== ''
43
+ && !isAbsolute(value) && !value.includes('\\') && value.split('/').every(segment => segment !== '' && segment !== '.' && segment !== '..');
44
+ const code = (value: Json | undefined): boolean => typeof value === 'string' && /^[A-Z][A-Z0-9_]*$/.test(value);
45
+ const finding = (code: string, path: string, why: string, remedy: string): Finding => ({ code, path, message: `${path}: ${why}; ${remedy}` });
46
+
47
+ /** Read the retained report bytes at a project-relative path, the way both the writer and the check do. */
48
+ export function readPartsCheckBytes(dir: string, path: Json | undefined, at: string): Buffer | Finding {
49
+ const unread = (why: string): Finding => finding('PARTS_CHECK_UNREAD', at, why, RUN);
50
+ if (!literal(path)) return unread('the report needs a literal project-relative path');
51
+ // Contained as the parts verdict is: as written, and with every link on its way followed.
52
+ let file: string;
53
+ try {
54
+ const root = realpathSync(dir);
55
+ file = realpathSync(resolve(dir, path));
56
+ const rel = relative(root, file);
57
+ if (rel === '' || rel.startsWith('..') || isAbsolute(rel)) return unread(`${path} leaves the project through a link`);
58
+ if (!statSync(file).isFile()) return unread(`${path} is not a regular file`);
59
+ } catch { return unread(`${path} is not there`); }
60
+ if (isIgnoredRoot(path.split('/')[0]!)) return unread(`${path} is under an ignored root and would not survive a clone`);
61
+ try {
62
+ return readFileSync(file);
63
+ } catch (error) {
64
+ return unread(`${path} cannot be read (${error instanceof Error ? error.message : String(error)})`);
65
+ }
66
+ }
67
+
68
+ function parseReport(bytes: Buffer): JsonObject | null {
69
+ try {
70
+ const value = JSON.parse(bytes.toString('utf8')) as Json;
71
+ return isObject(value) ? value : null;
72
+ } catch { return null; }
73
+ }
74
+
75
+ /** Shape of `rig-parts-parts-check/1` as STABILITY.md states it; no field is recomputed. */
76
+ function reportShape(report: JsonObject): string | null {
77
+ if (report.spec !== PARTS_CHECK_SPEC) return `spec is ${JSON.stringify(report.spec ?? null)}, not ${PARTS_CHECK_SPEC}`;
78
+ if (report.stage !== 'parts') return `stage is ${JSON.stringify(report.stage ?? null)}, not parts`;
79
+ if (Object.keys(report).join(',') !== REPORT_KEYS.join(',')) return `top-level keys must be ${REPORT_KEYS.join(', ')} in that order`;
80
+ if (!isObject(report.parts)) return 'parts must be an object';
81
+ if (!Number.isSafeInteger(report.exit_code)) return 'exit_code must be an integer';
82
+ for (const [key, keys] of Object.entries(ROW_KEYS)) {
83
+ const rows = report[key];
84
+ if (!Array.isArray(rows)) return `${key} must be a list`;
85
+ for (const [index, row] of rows.entries()) {
86
+ if (!isObject(row) || Object.keys(row).join(',') !== keys.join(',')) return `${key}[${index}] must have ${keys.join(', ')}`;
87
+ if (key !== 'inputs' && !code(row.code)) return `${key}[${index}].code must be a SCREAMING_SNAKE_CASE code`;
88
+ }
89
+ }
90
+ const paths = new Set<string>();
91
+ for (const [index, row] of (report.inputs as JsonObject[]).entries()) {
92
+ if (!literal(row.path) || paths.has(row.path)) return `inputs[${index}].path must be a unique relative POSIX path`;
93
+ paths.add(row.path);
94
+ if (!STATUSES.includes(String(row.status))) return `inputs[${index}].status must be ${STATUSES.join(', ')}`;
95
+ if (row.status === 'read' ? !(typeof row.sha256 === 'string' && /^[a-f0-9]{64}$/.test(row.sha256)) : row.sha256 !== null) {
96
+ return `inputs[${index}].sha256 must be a sha256 for a read file and null otherwise`;
97
+ }
98
+ }
99
+ for (const [index, row] of (report.holes as JsonObject[]).entries()) {
100
+ if (['object', 'reason', 'line'].some(key => typeof row[key] !== 'string')) return `holes[${index}] needs text object, reason and line`;
101
+ }
102
+ return null;
103
+ }
104
+
105
+ /** Every regular file under the root, by relative POSIX path, hashed; a symlink or special file is named. */
106
+ function inventory(root: string): { files: Map<string, string>; other: string[] } {
107
+ const files = new Map<string, string>();
108
+ const other: string[] = [];
109
+ const walk = (abs: string, rel: string): void => {
110
+ for (const entry of readdirSync(abs, { withFileTypes: true }).sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))) {
111
+ const path = rel === '' ? entry.name : `${rel}/${entry.name}`;
112
+ const stat = lstatSync(join(abs, entry.name));
113
+ if (stat.isDirectory()) walk(join(abs, entry.name), path);
114
+ else if (stat.isFile()) files.set(path, sha256(readFileSync(join(abs, entry.name))));
115
+ else other.push(path);
116
+ }
117
+ };
118
+ walk(root, '');
119
+ return { files, other };
120
+ }
121
+
122
+ /** Why the report no longer matches the assembled parts on disk, or null when it does. */
123
+ function staleness(dir: string, partsRoot: string, inputs: JsonObject[]): string[] | null {
124
+ let root: string;
125
+ try {
126
+ root = resolve(realpathSync(dir), partsRoot);
127
+ const stat = lstatSync(root);
128
+ if (stat.isSymbolicLink() || !stat.isDirectory()) return [`${partsRoot} is not a directory`];
129
+ } catch { return [`${partsRoot} is not a directory`]; }
130
+ const { files, other } = inventory(root);
131
+ const manifest = files.has('parts.json') ? parseReport(readFileSync(join(root, 'parts.json'))) : null;
132
+ const scenes = [manifest?.scene, ...asArray(manifest?.alternates).filter(isObject).map(set => set.scene)];
133
+ // The producer excludes this declared root mask from its report inputs (#1177).
134
+ const excludesFigureMask = scenes.some(scene => isObject(scene) && scene.figure_mask === 'figure_rig.png');
135
+ const reasons = other.map(path => `${path} is not a regular file`);
136
+ const named = new Set<string>();
137
+ for (const row of inputs) {
138
+ const path = String(row.path);
139
+ named.add(path);
140
+ const now = files.get(path);
141
+ if (row.status === 'read') {
142
+ if (now === undefined) reasons.push(`${path} is gone`);
143
+ else if (now !== row.sha256) reasons.push(`${path} changed after the check`);
144
+ } else if (row.status === 'absent') {
145
+ if (now !== undefined) reasons.push(`${path} was absent at the check and exists now`);
146
+ } else reasons.push(`${path} was ${String(row.status)} at the check`);
147
+ }
148
+ for (const path of files.keys()) {
149
+ if (!named.has(path) && !(excludesFigureMask && path === 'figure_rig.png')) reasons.push(`${path} is not in the report's inputs`);
150
+ }
151
+ return reasons.length === 0 ? null : reasons;
152
+ }
153
+
154
+ /**
155
+ * Validate one recorded `parts_check`. Always: the closed shape, the retained bytes hashing to `sha256`,
156
+ * the report's spec and shape, the copied fields, and a passing report. At the move only: the report
157
+ * matches the parts under `parts` on disk now. The first failing rule is the finding.
158
+ */
159
+ export function partsCheckFindings(dir: string, record: Json | undefined, path: string, atMove: boolean): Finding[] {
160
+ const invalid = (why: string): Finding[] => [finding('PARTS_CHECK_INVALID', path, why, `record the report through advance; ${RUN}`)];
161
+ if (!isObject(record) || Object.keys(record).length !== RECORD_KEYS.length || !RECORD_KEYS.every(key => key in record)) {
162
+ return invalid(`must have exactly ${RECORD_KEYS.join(', ')}`);
163
+ }
164
+ if (!literal(record.parts)) return invalid('parts must be the literal project-relative assembled root');
165
+ if (typeof record.sha256 !== 'string' || !/^[a-f0-9]{64}$/.test(record.sha256)) return invalid('sha256 must be the report bytes\' sha256');
166
+ const bytes = readPartsCheckBytes(dir, record.path, `${path}.path`);
167
+ if (!Buffer.isBuffer(bytes)) return [bytes];
168
+ if (sha256(bytes) !== record.sha256) {
169
+ return [finding('PARTS_CHECK_CHANGED', `${path}.sha256`, `${String(record.path)} no longer hashes to the recorded sha256`, 'restore the retained report bytes the move recorded; a new check is recorded by a new parts → parts-review move')];
170
+ }
171
+ const report = parseReport(bytes);
172
+ if (report === null) return [finding('PARTS_CHECK_UNREAD', `${path}.path`, `${String(record.path)} is not a JSON object`, RUN)];
173
+ const shape = reportShape(report);
174
+ if (shape !== null) return invalid(`${String(record.path)}: ${shape}`);
175
+ if (record.spec !== report.spec || record.exit_code !== report.exit_code || !isDeepStrictEqual(record.holes, report.holes) || !isDeepStrictEqual(record.reported, report.reported)) {
176
+ return invalid('spec, exit_code, holes and reported must be copied verbatim from the retained report');
177
+ }
178
+ const refusals = asArray(report.refusals).filter(isObject);
179
+ if (report.exit_code !== 0 || refusals.length > 0) {
180
+ const codes = refusals.map(row => String(row.code));
181
+ return [finding('PARTS_CHECK_FAILED', path, `the report records exit_code ${String(report.exit_code)}${codes.length === 0 ? '' : ` and refusals ${codes.join(', ')}`}`, 'fix the refused parts, rerun rig-parts check-parts and advance with the passing report')];
182
+ }
183
+ if (atMove) {
184
+ const reasons = staleness(dir, record.parts, report.inputs as JsonObject[]);
185
+ if (reasons !== null) {
186
+ const shown = reasons.slice(0, 3).join('; ') + (reasons.length > 3 ? `; and ${reasons.length - 3} more` : '');
187
+ return [finding('PARTS_CHECK_STALE', path, `the report does not match the parts under ${record.parts}: ${shown}`, 'rerun rig-parts check-parts on the assembled parts as they are now and advance with that report')];
188
+ }
189
+ }
190
+ return [];
191
+ }
192
+
193
+ /** The writer's copy of the report: path and sha256 of its bytes, and spec, exit_code, holes and reported as written. */
194
+ export function partsCheckRecord(dir: string, reportPath: string, partsRoot: string, path: string): { record: JsonObject } | { refusal: Finding } {
195
+ const bytes = readPartsCheckBytes(dir, reportPath, `${path}.path`);
196
+ if (!Buffer.isBuffer(bytes)) return { refusal: bytes };
197
+ const report = parseReport(bytes);
198
+ if (report === null) return { refusal: finding('PARTS_CHECK_UNREAD', `${path}.path`, `${reportPath} is not a JSON object`, RUN) };
199
+ const copy = (value: Json | undefined): Json => value === undefined ? null : structuredClone(value);
200
+ return { record: { path: reportPath, sha256: sha256(bytes), spec: copy(report.spec), parts: partsRoot,
201
+ exit_code: copy(report.exit_code), holes: copy(report.holes), reported: copy(report.reported) } };
202
+ }
203
+
204
+ /**
205
+ * Each `parts` → `parts-review` advance binds a passing report. Missing on a move dated from
206
+ * `PARTS_CHECK_SINCE`, or on the move being written, is refused; on an earlier move it WARNs.
207
+ * A `parts_check` on any other move is refused. Staleness is judged only for the move being written.
208
+ */
209
+ export function checkPartsChecks(ctx: Ctx, forwardWrite = false): void {
210
+ const advances = asArray(ctx.project.advances);
211
+ const since = Date.parse(PARTS_CHECK_SINCE);
212
+ for (const [index, entry] of advances.entries()) {
213
+ if (!isObject(entry)) continue;
214
+ const path = `advances[${index}].parts_check`;
215
+ const atMove = forwardWrite && index === advances.length - 1;
216
+ if (entry.parts_check === undefined) {
217
+ if (!isPartsCheckMove(entry)) continue;
218
+ if (atMove || !(Date.parse(String(entry.at)) < since)) {
219
+ ctx.refusals.push(finding('PARTS_CHECK_MISSING', path, 'a parts → parts-review move needs a passing parts-stage check report', RUN));
220
+ } else {
221
+ ctx.warnings.push(finding('PARTS_CHECK_MISSING', path, `the move predates ${PARTS_CHECK_SINCE} and binds no parts-stage check report`, 'preserve the historical move; the next parts → parts-review move binds a report'));
222
+ }
223
+ continue;
224
+ }
225
+ if (!isPartsCheckMove(entry)) {
226
+ ctx.refusals.push(finding('PARTS_CHECK_INVALID', path, 'a parts-stage check report is recorded only on a parts → parts-review move', 'restore the move as advance wrote it'));
227
+ continue;
228
+ }
229
+ ctx.refusals.push(...partsCheckFindings(ctx.dir, entry.parts_check, path, atMove));
230
+ }
231
+ }
232
+
233
+ /** One named line per HOLE and per reported row, for the advance output. */
234
+ export function partsCheckLines(record: JsonObject, path: string): string[] {
235
+ const holes = asArray(record.holes).filter(isObject);
236
+ const reported = asArray(record.reported).filter(isObject);
237
+ return [
238
+ ` parts check ${String(record.path)}: exit_code ${String(record.exit_code)}, ${holes.length} HOLE, ${reported.length} reported; a HOLE is not a pass`,
239
+ ...holes.map((row, index) => ` ${String(row.line)} (${path}.holes[${index}])`),
240
+ ...reported.map((row, index) => ` REPORTED ${String(row.code)}: part ${String(row.part)}, source ${String(row.source)}, count ${String(row.count)} (${path}.reported[${index}])`),
241
+ ];
242
+ }