@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,302 @@
1
+ /** Checked migration history for versions built before the interview (#39). */
2
+ import { createHash } from 'node:crypto';
3
+ import { readFileSync, realpathSync } from './check-reads.ts';
4
+ import { isAbsolute, relative, resolve, sep } from 'node:path';
5
+ import { isDeepStrictEqual } from 'node:util';
6
+
7
+ import { asArray, asString, isObject, type Finding } from './check.ts';
8
+ import { REVIEW_ACCEPTS } from './stages.ts';
9
+ import type { JsonObject } from './templates.ts';
10
+
11
+ /** Exact legacy spellings only. An unlisted value requires a documented mapping. */
12
+ export const PRE_INTERVIEW_VERDICTS: Readonly<Record<string, string>> = Object.freeze({
13
+ accepted: 'accepted',
14
+ Accepted: 'accepted',
15
+ 'accepted (sent to owner)': 'accepted',
16
+ 'Accepted (sent to owner)': 'accepted',
17
+ 'accepted (HQ-provisional, sent to owner)': 'provisional',
18
+ 'accepted-provisional': 'provisional',
19
+ 'ok for now, polish wanted': 'accepted',
20
+ 'close, polish wanted': 'provisional',
21
+ bounced: 'bounced',
22
+ Bounced: 'bounced',
23
+ provisional: 'provisional',
24
+ Provisional: 'provisional',
25
+ });
26
+
27
+ /** Extra record requirements apply only to pre-interview versions. */
28
+ function preInterviewVerdictProblems(d: JsonObject): string[] {
29
+ const problems: string[] = [];
30
+ for (const k of ['by', 'at', 'source']) if (typeof d[k] !== 'string' || d[k].trim() === '') problems.push(`no ${k}`);
31
+ if (typeof d.at === 'string' && !Number.isFinite(Date.parse(d.at))) problems.push('at is not a valid timestamp');
32
+ if (d.verbatim === null) {
33
+ if (typeof d.note !== 'string' || d.note.trim() === '') problems.push('a record of the user needs its note beside verbatim: null');
34
+ } else if (typeof d.verbatim !== 'string' || d.verbatim.trim() === '') problems.push('verbatim is the user words, or null with the record in note');
35
+ return problems;
36
+ }
37
+
38
+ interface HistoryCtx {
39
+ readonly project: JsonObject;
40
+ readonly dir: string;
41
+ readonly refusals: Finding[];
42
+ readonly warnings: Finding[];
43
+ }
44
+
45
+ function inside(root: string, file: string): boolean {
46
+ const rel = relative(root, file);
47
+ return rel !== '' && rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
48
+ }
49
+
50
+ export interface OutputBinding {
51
+ readonly path: string;
52
+ readonly sha256: string;
53
+ }
54
+
55
+ export interface PreInterviewOutputsInspection {
56
+ readonly refusals: Finding[];
57
+ readonly malformed: Finding[];
58
+ readonly otherRefusals: Finding[];
59
+ readonly bindings: OutputBinding[];
60
+ readonly missing: OutputBinding[];
61
+ readonly readable: string[];
62
+ readonly allMissing: boolean;
63
+ }
64
+
65
+ /** A loss declaration is usable on a pre-interview version with recorded missing bindings disjoint from surviving outputs, or legacy { note, source } beside outputs: []. */
66
+ export function validOutputsLoss(version: JsonObject, dir: string): boolean {
67
+ const loss = version.outputs_lost;
68
+ if (version.pre_interview !== true || !isObject(loss)
69
+ || !Object.keys(loss).every((k) => k === 'note' || k === 'source' || k === 'recorded')
70
+ || typeof loss.note !== 'string' || loss.note.trim() === ''
71
+ || typeof loss.source !== 'string' || loss.source.trim() === ''
72
+ || !Array.isArray(version.outputs)) {
73
+ return false;
74
+ }
75
+ if (loss.recorded === undefined) return version.outputs.length === 0;
76
+ if (!Array.isArray(loss.recorded) || loss.recorded.length === 0) return false;
77
+ const outputFiles = new Set<string>();
78
+ for (const out of version.outputs) {
79
+ if (isObject(out) && typeof out.path === 'string' && out.path.trim() !== '') {
80
+ outputFiles.add(resolve(dir, out.path));
81
+ }
82
+ }
83
+ const paths = new Set<string>();
84
+ for (const raw of loss.recorded) {
85
+ if (!isObject(raw) || !Object.keys(raw).every((k) => k === 'path' || k === 'sha256')
86
+ || typeof raw.path !== 'string' || raw.path.trim() === ''
87
+ || typeof raw.sha256 !== 'string' || !/^[0-9a-f]{64}$/.test(raw.sha256)) {
88
+ return false;
89
+ }
90
+ const file = resolve(dir, raw.path);
91
+ if (isAbsolute(raw.path) || !inside(dir, file) || paths.has(file) || outputFiles.has(file)) return false;
92
+ paths.add(file);
93
+ try {
94
+ if (!inside(realpathSync(dir), realpathSync(file))) return false;
95
+ readFileSync(file);
96
+ return false;
97
+ } catch {
98
+ // Missing on disk.
99
+ }
100
+ }
101
+ return true;
102
+ }
103
+
104
+ export const OUTPUTS_LOST_MALFORMED_MESSAGE =
105
+ 'record { note, source, recorded } with nonempty strings and missing { path, sha256 } bindings disjoint from surviving outputs (or { note, source } beside outputs: []) on a pre-interview version; restore surviving output records otherwise';
106
+
107
+ /** Shared output validator for check and `version outputs-lost`. */
108
+ export function inspectPreInterviewOutputs(dir: string, version: JsonObject, label: string): PreInterviewOutputsInspection {
109
+ const refusals: Finding[] = [];
110
+ const malformed: Finding[] = [];
111
+ const otherRefusals: Finding[] = [];
112
+ const bindings: OutputBinding[] = [];
113
+ const missing: OutputBinding[] = [];
114
+ const readable: string[] = [];
115
+ const push = (target: Finding[], code: string, path: string, message: string): void => {
116
+ const finding = { code, path, message: `${path}: ${message}` };
117
+ refusals.push(finding);
118
+ target.push(finding);
119
+ };
120
+ const hasOutputsLost = Object.hasOwn(version, 'outputs_lost');
121
+ if (hasOutputsLost) {
122
+ if (!validOutputsLoss(version, dir)) {
123
+ push(malformed, 'PRE_INTERVIEW_OUTPUTS_LOST_MALFORMED', `${label}.outputs_lost`, OUTPUTS_LOST_MALFORMED_MESSAGE);
124
+ }
125
+ if (!Array.isArray(version.outputs) || version.outputs.length === 0) {
126
+ return { refusals, malformed, otherRefusals, bindings, missing, readable, allMissing: false };
127
+ }
128
+ } else if (!Array.isArray(version.outputs) || version.outputs.length === 0) {
129
+ push(malformed, 'PRE_INTERVIEW_OUTPUT_MALFORMED', `${label}.outputs`, 'a pre-interview version lists its frozen output files with path, sha256 and bytes');
130
+ return { refusals, malformed, otherRefusals, bindings, missing, readable, allMissing: false };
131
+ }
132
+ const paths = new Set<string>();
133
+ const outcomes: (
134
+ | { kind: 'malformed'; path: string; message: string }
135
+ | { kind: 'outside'; path: string }
136
+ | { kind: 'missing'; path: string; rawPath: string }
137
+ | { kind: 'changed'; path: string; rawPath: string; actual: string; length: number }
138
+ | { kind: 'ok' }
139
+ )[] = [];
140
+ version.outputs.forEach((raw, index) => {
141
+ const path = `${label}.outputs[${index}]`;
142
+ if (!isObject(raw) || typeof raw.path !== 'string' || raw.path.trim() === '' || typeof raw.sha256 !== 'string'
143
+ || !/^[0-9a-f]{64}$/.test(raw.sha256) || typeof raw.bytes !== 'number' || !Number.isSafeInteger(raw.bytes) || raw.bytes < 0) {
144
+ outcomes.push({ kind: 'malformed', path, message: 'an output is { path, sha256 (64 lowercase hex digits), bytes (a nonnegative integer) }' });
145
+ return;
146
+ }
147
+ const file = resolve(dir, raw.path);
148
+ if (isAbsolute(raw.path) || !inside(dir, file) || paths.has(file)) {
149
+ outcomes.push({ kind: 'malformed', path, message: 'each output names a distinct file relative to the project' });
150
+ return;
151
+ }
152
+ paths.add(file);
153
+ bindings.push({ path: raw.path, sha256: raw.sha256 });
154
+ let bytes: Buffer;
155
+ try {
156
+ if (!inside(realpathSync(dir), realpathSync(file))) {
157
+ outcomes.push({ kind: 'outside', path });
158
+ return;
159
+ }
160
+ bytes = readFileSync(file);
161
+ } catch {
162
+ missing.push({ path: raw.path, sha256: raw.sha256 });
163
+ outcomes.push({ kind: 'missing', path, rawPath: raw.path });
164
+ return;
165
+ }
166
+ const actual = createHash('sha256').update(bytes).digest('hex');
167
+ if (actual !== raw.sha256 || bytes.length !== raw.bytes) {
168
+ outcomes.push({ kind: 'changed', path, rawPath: raw.path, actual, length: bytes.length });
169
+ return;
170
+ }
171
+ readable.push(raw.path);
172
+ outcomes.push({ kind: 'ok' });
173
+ });
174
+ const allMissing = version.pre_interview === true && !hasOutputsLost && missing.length === version.outputs.length && readable.length === 0;
175
+ const canRecordLoss = version.pre_interview === true
176
+ && !hasOutputsLost
177
+ && missing.length > 0
178
+ && outcomes.every((o) => o.kind === 'missing' || o.kind === 'ok');
179
+ const lostRemedy = canRecordLoss && typeof version.n === 'number'
180
+ ? `; if this pre-interview version's frozen outputs did not survive, record the loss with rig-create version outputs-lost ${String(version.n)} --note <text> --source <text>`
181
+ : '';
182
+ for (const outcome of outcomes) {
183
+ if (outcome.kind === 'malformed') {
184
+ push(malformed, 'PRE_INTERVIEW_OUTPUT_MALFORMED', outcome.path, outcome.message);
185
+ } else if (outcome.kind === 'outside') {
186
+ push(otherRefusals, 'PRE_INTERVIEW_OUTPUT_UNREAD', outcome.path, 'the output resolves outside the project');
187
+ } else if (outcome.kind === 'missing') {
188
+ refusals.push({
189
+ code: 'PRE_INTERVIEW_OUTPUT_UNREAD',
190
+ path: outcome.path,
191
+ message: `${outcome.path}: ${outcome.rawPath} cannot be read; restore the frozen file before checking the migration${lostRemedy}`,
192
+ });
193
+ } else if (outcome.kind === 'changed') {
194
+ push(otherRefusals, 'PRE_INTERVIEW_OUTPUT_CHANGED', outcome.path, `${outcome.rawPath} has sha256 ${outcome.actual} and ${outcome.length} bytes, different from its frozen record`);
195
+ }
196
+ }
197
+ return { refusals, malformed, otherRefusals, bindings, missing, readable, allMissing };
198
+ }
199
+
200
+ function checkOutputs(ctx: HistoryCtx, version: JsonObject, label: string): void {
201
+ ctx.refusals.push(...inspectPreInterviewOutputs(ctx.dir, version, label).refusals);
202
+ }
203
+
204
+ function checkOriginals(ctx: HistoryCtx, version: JsonObject, label: string): void {
205
+ const push = (code: string, path: string, message: string): void => {
206
+ ctx.refusals.push({ code, path, message: `${path}: ${message}` });
207
+ };
208
+ if (!Array.isArray(version.original_verdicts)) {
209
+ push('PRE_INTERVIEW_HISTORY_MISSING', `${label}.original_verdicts`, 'keep the complete original verdicts array, unchanged and in order');
210
+ return;
211
+ }
212
+ if (!Array.isArray(version.verdicts)) {
213
+ push('VERDICT_MALFORMED', `${label}.verdicts`, 'verdicts is a list of verdicts in create shape');
214
+ }
215
+ const originals = version.original_verdicts;
216
+ const verdicts = asArray(version.verdicts);
217
+ verdicts.forEach((d, index) => {
218
+ const path = `${label}.verdicts[${index}]`;
219
+ const problems = isObject(d) ? preInterviewVerdictProblems(d) : ['a verdict is an object'];
220
+ if (isObject(d)) {
221
+ if (!['accepted', 'bounced', 'provisional'].includes(d.verdict as string)) problems.push('verdict is accepted, bounced or provisional');
222
+ if (typeof d.review !== 'string' || !Object.hasOwn(REVIEW_ACCEPTS, d.review)) problems.push('review is rig-review or direction-review');
223
+ if (!Array.isArray(d.on_items)) problems.push('on_items is a list');
224
+ }
225
+ if (problems.length > 0) push('VERDICT_MALFORMED', path, problems.join(', '));
226
+ });
227
+ originals.forEach((original, index) => {
228
+ const path = `${label}.verdicts[${index}]`;
229
+ const converted = verdicts[index];
230
+ if (!isObject(converted) || !isDeepStrictEqual(converted.as_recorded, original)) {
231
+ push('PRE_INTERVIEW_VERDICT_LOST', path, `as_recorded must preserve original_verdicts[${index}] unchanged, at the same index`);
232
+ }
233
+ const value = isObject(original) ? asString(original.verdict) : null;
234
+ if (value === null || !Object.hasOwn(PRE_INTERVIEW_VERDICTS, value)) {
235
+ push('PRE_INTERVIEW_VERDICT_UNKNOWN', `${label}.original_verdicts[${index}]`, `original verdict ${JSON.stringify(value)} has no mapping in PRE_INTERVIEW_VERDICTS; do not infer an acceptance`);
236
+ } else if (isObject(converted) && converted.verdict !== PRE_INTERVIEW_VERDICTS[value]) {
237
+ push('PRE_INTERVIEW_VERDICT_MISMATCH', `${path}.verdict`, `${JSON.stringify(value)} maps to ${PRE_INTERVIEW_VERDICTS[value]}, got ${JSON.stringify(converted.verdict ?? null)}`);
238
+ }
239
+ });
240
+ verdicts.slice(originals.length).forEach((verdict, index) => {
241
+ if (isObject(verdict) && Object.hasOwn(verdict, 'as_recorded')) {
242
+ push('PRE_INTERVIEW_VERDICT_LOST', `${label}.verdicts[${originals.length + index}]`, 'as_recorded has no corresponding original verdict; new reviews are appended without as_recorded');
243
+ }
244
+ });
245
+ }
246
+
247
+ /** A loss declaration is usable only when the historical config is absent. */
248
+ export function validConfigLoss(version: JsonObject): boolean {
249
+ const loss = version.config_lost;
250
+ const provenance = isObject(version.provenance) ? version.provenance : {};
251
+ return version.pre_interview === true && isObject(loss)
252
+ && typeof loss.note === 'string' && loss.note.trim() !== ''
253
+ && typeof loss.source === 'string' && loss.source.trim() !== ''
254
+ && provenance.config == null;
255
+ }
256
+
257
+ /** Also used by handoff so unchecked imported history cannot authorize post work. */
258
+ export function checkPreInterview(ctx: HistoryCtx): void {
259
+ const legacy = ctx.project['x-pre_create'];
260
+ if (isObject(legacy) && Object.hasOwn(legacy, 'versions') && (!Array.isArray(legacy.versions) || legacy.versions.length > 0)) {
261
+ ctx.warnings.push({ code: 'PRE_INTERVIEW_UNMIGRATED', path: 'x-pre_create.versions', message: 'x-pre_create.versions awaits migration. Follow docs/PROJECT.md, Migrating work done before the interview, steps 1–6: preserve original verdicts and frozen outputs, move historical versions first into versions[] with pre_interview: true, normalize verdicts with sources, verify outputs, connect answers_feedback, then run interview --check and handoff' });
262
+ }
263
+ const versions = asArray(ctx.project.versions);
264
+ const originalIds = new Set<string | number>();
265
+ let createSeen = false;
266
+ let previous = 0;
267
+ const hasPreInterview = versions.some((v) => isObject(v) && v.pre_interview === true);
268
+ versions.forEach((v, index) => {
269
+ if (!isObject(v)) return;
270
+ const label = typeof v.n === 'number' ? `versions[v${v.n}]` : `versions[${index}]`;
271
+ if (Object.hasOwn(v, 'config_lost') && !validConfigLoss(v)) {
272
+ ctx.refusals.push({ code: 'PRE_INTERVIEW_CONFIG_LOST_MALFORMED', path: `${label}.config_lost`, message: `${label}.config_lost: record { note, source } with nonempty strings only on a pre-interview version without provenance.config; restore a surviving config instead` });
273
+ }
274
+ if (v.pre_interview !== undefined && typeof v.pre_interview !== 'boolean') {
275
+ ctx.refusals.push({ code: 'VERSION_MALFORMED', path: `${label}.pre_interview`, message: `${label}.pre_interview is a boolean flag` });
276
+ }
277
+ if (hasPreInterview && (typeof v.n !== 'number' || !Number.isSafeInteger(v.n) || v.n <= previous)) {
278
+ ctx.refusals.push({ code: 'PRE_INTERVIEW_ORDER', path: label, message: `${label}: migrated versions have positive, strictly increasing n in record order` });
279
+ }
280
+ if (typeof v.n === 'number') previous = v.n;
281
+ if (v.pre_interview !== true) {
282
+ if (Object.hasOwn(v, 'outputs_lost')) {
283
+ ctx.refusals.push({ code: 'PRE_INTERVIEW_OUTPUTS_LOST_MALFORMED', path: `${label}.outputs_lost`, message: `${label}.outputs_lost: ${OUTPUTS_LOST_MALFORMED_MESSAGE}` });
284
+ }
285
+ createSeen = true;
286
+ return;
287
+ }
288
+ if (createSeen) {
289
+ ctx.refusals.push({ code: 'PRE_INTERVIEW_ORDER', path: label, message: `${label}: every pre-interview version comes before the first create-built version` });
290
+ }
291
+ if (!Object.hasOwn(v, 'original_n') || (v.original_n !== null && typeof v.original_n !== 'number' && typeof v.original_n !== 'string')) {
292
+ ctx.refusals.push({ code: 'PRE_INTERVIEW_ORIGINAL_N_MISSING', path: `${label}.original_n`, message: `${label}.original_n: copy the source version id unchanged as a number, string or null` });
293
+ } else if (v.original_n !== null) {
294
+ if (originalIds.has(v.original_n)) {
295
+ ctx.refusals.push({ code: 'PRE_INTERVIEW_ORIGINAL_N_DUPLICATE', path: `${label}.original_n`, message: `${label}.original_n: two migrated versions share the same non-null source id; restore their distinct original ids` });
296
+ }
297
+ originalIds.add(v.original_n);
298
+ }
299
+ checkOriginals(ctx, v, label);
300
+ checkOutputs(ctx, v, label);
301
+ });
302
+ }
@@ -0,0 +1,113 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { writeFileSync } from 'node:fs';
3
+ import { readFileSync } from './check-reads.ts';
4
+ import { isAbsolute, relative, resolve, sep } from 'node:path';
5
+
6
+ import { asArray, isObject, readJsonObject, type Ctx } from './check.ts';
7
+ import { checkPlateReceipt, checkPlateRegistrations, readPlateReceipt } from './plate-receipt.ts';
8
+ import { refuse, USAGE_EXIT } from './refusal.ts';
9
+ import type { Json, JsonObject } from './templates.ts';
10
+
11
+ const nonempty = (v: Json | undefined): v is string => typeof v === 'string' && v.trim() !== '';
12
+ const hash = (bytes: Uint8Array): string => createHash('sha256').update(bytes).digest('hex');
13
+
14
+ function inside(dir: string, path: string): boolean {
15
+ const rel = relative(dir, resolve(dir, path));
16
+ return !isAbsolute(path) && rel !== '' && rel !== 'project.json' && rel !== '..' && !rel.startsWith(`..${sep}`);
17
+ }
18
+
19
+ function readHash(dir: string, path: string): string {
20
+ return hash(readFileSync(resolve(dir, path)));
21
+ }
22
+
23
+ /** Only the tagged prepared-input records carry this file/hash contract; legacy intermediates are unchanged. */
24
+ export function checkPreparedInputs(ctx: Pick<Ctx, 'dir' | 'project' | 'refusals' | 'warnings'>): void {
25
+ asArray(ctx.project.derived).forEach((entry, i) => {
26
+ if (!isObject(entry)) return;
27
+ checkPlateReceipt(ctx, entry, `derived[${i}]`);
28
+ if (entry.kind !== 'prepared-input') return;
29
+ const path = `derived[${i}]`;
30
+ const sources = entry.sources === undefined ? [entry.source] : asArray(entry.sources);
31
+ const files = [
32
+ ...sources.map((source, index) => ({
33
+ record: isObject(source) ? source : {},
34
+ at: entry.sources === undefined ? `${path}.source` : `${path}.sources[${index}]`,
35
+ optional: false,
36
+ })),
37
+ { record: entry, at: path, optional: true },
38
+ ];
39
+ if (sources.length === 0 || !nonempty(entry.operation) || files.some(({ record }) => !nonempty(record.path) || !inside(ctx.dir, record.path) || typeof record.sha256 !== 'string' || !/^[a-f0-9]{64}$/i.test(record.sha256)) || sources.some((source) => isObject(source) && typeof source.path === 'string' && typeof entry.path === 'string' && resolve(ctx.dir, source.path) === resolve(ctx.dir, entry.path))) {
40
+ ctx.refusals.push({ code: 'DERIVED_MALFORMED', path, message: `${path}: prepared-input needs operation, source or sources { path, sha256 }, and derived path and sha256; paths are distinct project-relative files other than project.json` });
41
+ return;
42
+ }
43
+ for (const { record, at, optional } of files) {
44
+ const name = record.path as string;
45
+ let actual: string;
46
+ try { actual = readHash(ctx.dir, name); } catch (error) {
47
+ if (optional && error instanceof Error && 'code' in error && error.code === 'ENOENT') {
48
+ ctx.warnings.push({ code: 'DERIVED_RESULT_MISSING', path: `${at}.path`, message: `${at}: '${name}' is absent; regenerate with the recorded operation: ${entry.operation as string}` });
49
+ continue;
50
+ }
51
+ ctx.refusals.push({ code: 'DERIVED_FILE_UNREADABLE', path: `${at}.path`, message: `${at}: cannot read '${name}'; restore the recorded file` });
52
+ continue;
53
+ }
54
+ if (actual !== (record.sha256 as string).toLowerCase()) ctx.refusals.push({ code: 'DERIVED_FILE_CHANGED', path: `${at}.sha256`, message: `${at}: '${name}' no longer matches its recorded sha256` });
55
+ }
56
+ });
57
+ checkPlateRegistrations(ctx);
58
+ }
59
+
60
+ export const DERIVED_HELP = `derived add [<project dir>] --source <file> [--source <file> …] --file <file> --operation <text> [--receipt <path>] [--json]
61
+ Records an operation already run by another tool, with the files' sha256s.
62
+ Paths are relative to the project. No files are transformed; no stage moves.`;
63
+
64
+ export function derivedCommand(args: string[]): { stdout: string; exit: number } {
65
+ if (args[0] !== 'add') refuse('DERIVED_USAGE', DERIVED_HELP, USAGE_EXIT);
66
+ const opts: Record<string, string> = {};
67
+ const positional: string[] = [];
68
+ const sources: string[] = [];
69
+ let json = false;
70
+ for (let i = 1; i < args.length; i++) {
71
+ const arg = args[i] as string;
72
+ if (arg === '--json') json = true;
73
+ else if (['--source', '--file', '--operation', '--receipt'].includes(arg)) {
74
+ const value = args[++i];
75
+ if ((arg !== '--source' && opts[arg] !== undefined) || value === undefined || value.startsWith('--') || value.trim() === '') refuse('DERIVED_USAGE', `${arg} needs one non-empty value`, USAGE_EXIT);
76
+ opts[arg] = value;
77
+ if (arg === '--source') sources.push(value);
78
+ } else if (arg.startsWith('-')) refuse('UNKNOWN_OPTION', `derived add does not take ${arg}`, USAGE_EXIT);
79
+ else positional.push(arg);
80
+ }
81
+ if (positional.length > 1 || ['--source', '--file', '--operation'].some((k) => opts[k] === undefined)) refuse('DERIVED_USAGE', DERIVED_HELP, USAGE_EXIT);
82
+ const dir = resolve(positional[0] ?? process.cwd());
83
+ const file = resolve(dir, 'project.json');
84
+ const read = readJsonObject(file);
85
+ if (!read.ok) refuse('PROJECT_JSON_INVALID', `${file}: ${read.error}`);
86
+ const project = read.value;
87
+ if (project.schema !== 'rig-create/project@0') refuse('SCHEMA_UNKNOWN', 'derived add reads rig-create/project@0');
88
+ if (project.derived !== undefined && !Array.isArray(project.derived)) refuse('DERIVED_MALFORMED', 'derived must be an array');
89
+ const output = opts['--file'] as string;
90
+ if (![...sources, output].every((p) => inside(dir, p)) || sources.some((p) => resolve(dir, p) === resolve(dir, output))) refuse('DERIVED_PATH_INVALID', 'source and derived file must be distinct project-relative files other than project.json');
91
+ const hashes = [...sources, output].map((p) => {
92
+ try { return readHash(dir, p); } catch { refuse('DERIVED_FILE_UNREADABLE', `cannot read '${p}'; no record written`); }
93
+ });
94
+ const inputs = sources.map((path, i) => ({ path, sha256: hashes[i] as string }));
95
+ const entry: JsonObject = {
96
+ kind: 'prepared-input',
97
+ ...(sources.length === 1 ? { source: inputs[0] as JsonObject } : { sources: inputs }),
98
+ path: output,
99
+ sha256: hashes[sources.length] as string,
100
+ operation: opts['--operation'] as string,
101
+ };
102
+ if (opts['--receipt'] !== undefined) {
103
+ try { entry.receipt = readPlateReceipt(dir, opts['--receipt']).binding; }
104
+ catch (error) { refuse('PLATE_RECEIPT_INVALID', `derived.receipt: ${(error as Error).message}; supply an intact project-relative producer receipt; no record written`); }
105
+ }
106
+ project.derived = [...asArray(project.derived), entry];
107
+ const ctx: Pick<Ctx, 'dir' | 'project' | 'refusals' | 'warnings'> = { dir, project: { ...project, derived: [entry], scenes: [] }, refusals: [], warnings: [] };
108
+ checkPreparedInputs(ctx);
109
+ const finding = ctx.refusals[0];
110
+ if (finding) refuse(finding.code, finding.message);
111
+ writeFileSync(file, `${JSON.stringify(project, null, 2)}\n`);
112
+ return { stdout: json ? JSON.stringify(entry, null, 2) : `derived: recorded '${sources.join("', '")}' → '${output}' (${entry.operation as string}); stage unchanged`, exit: 0 };
113
+ }
@@ -0,0 +1,181 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { checkpointRequired } from './plan-history.ts';
3
+ import { readFileSync, writeFileSync } from 'node:fs';
4
+ import { resolve } from 'node:path';
5
+ import { asArray, checkProject, collectItems, collectSignoffs, isObject, loadCollection, postNotPassed, type Finding } from './check.ts';
6
+ import { readProject } from './feedback.ts';
7
+ import { acquireAdvanceJobWrite, commitAdvance } from './job-advance.ts';
8
+ import { collectReopens, liveSignoff } from './reopen.ts';
9
+ import { refuse, USAGE_EXIT } from './refusal.ts';
10
+ import { findingKey, newFindings, carriedRefusals } from './refusal-baseline.ts';
11
+ import { collectAmendments } from './amend.ts';
12
+ import { LEGACY_STAGE_VALUES, PARTS_REVIEW_SINCE, PAUSED_STAGE, STAGE_VALUES, hasStageOrder, interviewStageOfGuide, stageIndex, stageOfStep } from './stages.ts';
13
+ import { advanceOrder, furthestStageSignoff, isSignoffIdentity, moveOwner, signoffIdentity, statusTransitionFinding, STEP_STATUSES } from './stage-progress.ts';
14
+ import { isPartsCheckMove, partsCheckLines, partsCheckRecord } from './parts-check.ts';
15
+ import type { JsonObject } from './templates.ts';
16
+
17
+ export const PROGRESS_HELP = `advance:
18
+ <stage> [<project dir>] [--source <text>] [--parts-check <report> --parts <assembled dir>]
19
+ Move one authorized stage forward and append advances[] with its stable signoff identity.
20
+ The person's signoff is the move's authority; advance takes no --by or words of its own.
21
+ parts -> parts-review requires both: a passing rig-parts check-parts report retained
22
+ under the project and the project-relative assembled root it was run on.
23
+ step-status:
24
+ <step> <status> [<project dir>] --by <who> [--note-file <path>]
25
+ Record pending -> in-progress -> at-checkpoint -> done and status_history[].
26
+ The optional note is verbatim UTF-8, 1–1,000 bytes, with no controls except newline.
27
+ A checkpointed done needs a live human checkpoint signature.
28
+ An amended project also runs steps in the check's effective stage, keeping its stored stage.
29
+ Both accept --json and validate the complete candidate before writing.`;
30
+
31
+ function options(args: string[], command: string): { positional: string[]; opts: Record<string, string>; json: boolean } {
32
+ const positional: string[] = [];
33
+ const opts: Record<string, string> = {};
34
+ let json = false;
35
+ for (let i = 0; i < args.length; i++) {
36
+ const arg = args[i]!;
37
+ if (arg === '--json') json = true;
38
+ else if (command === 'advance' && (arg === '--by' || arg === '--verbatim-file')) {
39
+ refuse('ADVANCE_WORDS_REMOVED', `advances: advance no longer takes ${arg}; the move records the live stage signoff it rests on, so record the person's approval with signoff (from the review tool: signoff <stage> --from-review <actId>) and advance without words`, USAGE_EXIT);
40
+ } else if ((command === 'advance' ? ['--source', '--parts-check', '--parts'] : ['--by', '--note-file']).includes(arg)) {
41
+ const value = args[++i];
42
+ if (arg in opts || value === undefined || value.trim() === '') refuse('PROGRESS_OPTION_INVALID', `${command}: ${arg} needs one value; see help`, USAGE_EXIT);
43
+ opts[arg] = value;
44
+ } else if (arg.startsWith('-')) refuse('UNKNOWN_OPTION', `${command}: unsupported ${arg}; see help`, USAGE_EXIT);
45
+ else positional.push(arg);
46
+ }
47
+ if (command !== 'advance' && opts['--by'] === undefined) refuse('PROGRESS_OPTION_INVALID', `${command}: supply --by; see help`, USAGE_EXIT);
48
+ return { positional, opts, json };
49
+ }
50
+
51
+ /**
52
+ * Validate the complete candidate, then write. `advance` passes no baseline: a stage boundary needs 0 refusals
53
+ * (only the arrival's own INTERVIEW_INCOMPLETE is allowed). `step-status` passes the record as read, and only a
54
+ * refusal that record did not already carry blocks its write.
55
+ */
56
+ function save(dir: string, file: string, project: JsonObject, now: number, forwardWrite = false, before?: JsonObject): void {
57
+ project.stage_values = [...(hasStageOrder(project) ? STAGE_VALUES : LEGACY_STAGE_VALUES)];
58
+ const allowed = before === undefined ? new Map<string, number>() : carriedRefusals(dir, before, { now, forwardWrite });
59
+ if (forwardWrite && ['rigging-interview', 'post-interview'].includes(String(project.stage))) {
60
+ for (const item of collectItems(project, { collection: loadCollection(dir) })) {
61
+ if (item.state.state !== 'silent' || interviewStageOfGuide(item.id, item.kind === 'post', hasStageOrder(project)) !== project.stage) continue;
62
+ const key = findingKey({ code: 'INTERVIEW_INCOMPLETE', path: item.path });
63
+ allowed.set(key, (allowed.get(key) ?? 0) + 1);
64
+ }
65
+ }
66
+ const first = newFindings(allowed, checkProject(dir, project, { now, forwardWrite }).refusals)[0];
67
+ if (first !== undefined) refuse(first.code, first.message);
68
+ if (forwardWrite) commitAdvance(dir, file, project);
69
+ else writeFileSync(file, `${JSON.stringify(project, null, 2)}\n`);
70
+ }
71
+
72
+ export function advanceCommand(args: string[], now: number): { stdout: string; exit: number } {
73
+ const { positional, opts, json } = options(args, 'advance');
74
+ if (positional.length < 1 || positional.length > 2) refuse('PROGRESS_OPTION_INVALID', 'stage: advance needs a target and optional project directory; see help', USAGE_EXIT);
75
+ const dir = resolve(positional[1] ?? process.cwd());
76
+ const releaseJob = acquireAdvanceJobWrite(dir, positional[0]!);
77
+ try {
78
+ const { file, project } = readProject(dir);
79
+ const history: Finding[] = [];
80
+ const reopens = collectReopens(project, history);
81
+ const latest = furthestStageSignoff(collectSignoffs(project, history).filter(s => liveSignoff(reopens, s.stage, s.at)));
82
+ if (latest !== undefined && !Number.isFinite(latest.at)) refuse('STAGE_SIGNOFF_DATE_INVALID', `signoffs[${latest.index}].at: frontier approval needs a readable date; restore its original date before advancing`);
83
+ if (latest === undefined) refuse('ADVANCE_UNAUTHORIZED', 'stage: no live stage-level human signoff authorizes a move; sign the current stage first');
84
+ const preParts = !hasStageOrder(project) && stageIndex(String(project.stage)) > stageIndex('parts-review')
85
+ && collectSignoffs(project, []).some(s => s.stage === 'plan-approved' && s.checkpoint === null && s.at < Date.parse(PARTS_REVIEW_SINCE));
86
+ const steps = asArray(isObject(project.plan) ? project.plan.steps : undefined).filter(isObject);
87
+ // The entry records the signoff it rests on and no person's words: the authority is that signature.
88
+ const entry: JsonObject = { stage: positional[0]!, from_stage: project.stage ?? null, at: new Date(now).toISOString(),
89
+ post_not_passed: postNotPassed({ items: collectItems(project, { collection: loadCollection(dir) }) }),
90
+ order: preParts ? 'pre-parts' : hasStageOrder(project) ? 'split' : 'legacy', signoffs_before: asArray(project.signoffs).filter(isObject).map(signoffIdentity).filter(isSignoffIdentity), signoff: signoffIdentity(latest.record),
91
+ steps_before: steps.map(s => ({ id: s.id ?? null, phase: s.phase ?? null, ...(s.status === undefined ? {} : { status: s.status }) })) };
92
+ if (opts['--source'] !== undefined) entry.source = opts['--source'];
93
+ // A live signature at an earlier stage is not this move's authority; name the stage to sign, not the candidate.
94
+ // A malformed signoff or reopen may be that signature, so the candidate check names it instead.
95
+ const order = advanceOrder(entry.order);
96
+ const owner = moveOwner(order, entry.from_stage);
97
+ if (history.length === 0 && owner !== undefined && order.findIndex(s => s.id === latest.stage) < order.findIndex(s => s.id === owner)) {
98
+ refuse('ADVANCE_UNAUTHORIZED', `stage: no live ${owner} signoff authorizes the move from ${String(entry.from_stage)}; the latest live stage signoff is ${latest.stage} (signoffs[${latest.index}]); record rig-create signoff ${owner} first, then advance`);
99
+ }
100
+ if (project.advances !== undefined && !Array.isArray(project.advances)) refuse('ADVANCE_MALFORMED', 'advances: must be a list; preserve existing history');
101
+ const checkPath = `advances[${asArray(project.advances).length}].parts_check`;
102
+ const report = opts['--parts-check'];
103
+ const root = opts['--parts']?.replace(/\/+$/, '');
104
+ if (!isPartsCheckMove(entry) && (report !== undefined || root !== undefined)) {
105
+ refuse('PROGRESS_OPTION_INVALID', `${checkPath}: --parts-check and --parts are accepted only on the move from parts to parts-review; see help`, USAGE_EXIT);
106
+ }
107
+ if ((report === undefined) !== (root === undefined)) {
108
+ refuse('PROGRESS_OPTION_INVALID', `${checkPath}: supply --parts-check and --parts together; see help`, USAGE_EXIT);
109
+ }
110
+ // Without both flags the check refuses the move as PARTS_CHECK_MISSING, in check order.
111
+ if (report !== undefined && root !== undefined) {
112
+ const read = partsCheckRecord(dir, report, root, checkPath);
113
+ if ('refusal' in read) refuse(read.refusal.code, read.refusal.message);
114
+ entry.parts_check = read.record;
115
+ }
116
+ project.advances = [...asArray(project.advances), entry];
117
+ project.stage = entry.stage!;
118
+ save(dir, file, project, now, true);
119
+ const lines = isObject(entry.parts_check) ? partsCheckLines(entry.parts_check, checkPath) : [];
120
+ return { stdout: json ? JSON.stringify(entry, null, 2) : [`advance: ${String(entry.from_stage)} -> ${String(entry.stage)}`, ...lines].join('\n'), exit: 0 };
121
+ } finally {
122
+ releaseJob();
123
+ }
124
+ }
125
+
126
+ export function stepStatusCommand(args: string[], now: number): { stdout: string; exit: number } {
127
+ const { positional, opts, json } = options(args, 'step-status');
128
+ if (positional.length < 2 || positional.length > 3) refuse('PROGRESS_OPTION_INVALID', 'plan.steps: step-status needs step, status and optional project directory; see help', USAGE_EXIT);
129
+ const dir = resolve(positional[2] ?? process.cwd());
130
+ const { file, project } = readProject(dir);
131
+ const before = structuredClone(project);
132
+ const matches = asArray(isObject(project.plan) ? project.plan.steps : undefined).filter((s): s is JsonObject => isObject(s) && s.id === positional[0]);
133
+ const step = matches[0];
134
+ if (matches.length !== 1 || step === undefined || (step.phase !== 'rig' && step.phase !== 'post')) refuse('STEP_STATUS_TRANSITION', `plan.steps[${positional[0]}]: no unique executable step; restore the plan before step-status`);
135
+ const stage = stageOfStep(step.phase, String(step.id));
136
+ // Amend retains project.stage while its approvals lapse; the work resumes at the check's effective stage (PROJECT.md).
137
+ const resumes = project.stage !== PAUSED_STAGE && collectAmendments(project, []).length > 0
138
+ ? checkProject(dir, structuredClone(project), { now }).effective_stage : null;
139
+ const running = resumes !== null && resumes !== project.stage ? [String(project.stage), resumes] : [String(project.stage)];
140
+ const oldOrder = !hasStageOrder(project) && running.includes('rigging') && collectSignoffs(project, []).some(s => s.stage === 'plan-approved' && s.at < Date.parse(PARTS_REVIEW_SINCE));
141
+ if (!running.includes(stage) && !(oldOrder && step.phase === 'rig')) {
142
+ refuse('STEP_STATUS_TRANSITION', running.length > 1
143
+ ? `plan.steps[${positional[0]}]: runs in ${stage}; the amended project resumes at ${String(resumes)} (stage ${String(project.stage)} retained); sign the lapsed approvals and finish the earlier bars first`
144
+ : `plan.steps[${positional[0]}]: runs in ${stage}, current stage is ${String(project.stage)}; advance to its execution stage first`);
145
+ }
146
+ const enabled = checkpointRequired(project, step, now);
147
+ const entry: JsonObject = { from_status: step.status ?? 'pending', status: positional[1]!, by: opts['--by']!, at: new Date(now).toISOString(), checkpoint_required: enabled, signoff: null };
148
+ if (opts['--note-file'] !== undefined) {
149
+ const notePath = opts['--note-file'];
150
+ try {
151
+ const bytes = readFileSync(notePath);
152
+ if (bytes.length === 0 || bytes.length > 1000) throw new Error('invalid size');
153
+ // Keep a leading BOM too: the stored text and digest bind the original UTF-8 bytes.
154
+ entry.note = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(bytes);
155
+ entry.note_sha256 = createHash('sha256').update(bytes).digest('hex');
156
+ } catch {
157
+ refuse('PROGRESS_OPTION_INVALID', `plan.steps[${positional[0]}].status_history: ${notePath}: supply a readable non-empty UTF-8 --note-file of at most 1,000 bytes`, USAGE_EXIT);
158
+ }
159
+ }
160
+ const from = STEP_STATUSES.indexOf(entry.from_status as typeof STEP_STATUSES[number]);
161
+ if (from >= 0 && STEP_STATUSES.indexOf(entry.status as typeof STEP_STATUSES[number]) !== from + 1) {
162
+ const next = STEP_STATUSES[from + 1];
163
+ refuse('STEP_STATUS_TRANSITION', next === undefined
164
+ ? `plan.steps[${positional[0]}].status: ${String(entry.from_status)} is the last status, so ${String(entry.status)} cannot follow it; to run the step again record rig-create reopen ${stage}`
165
+ : `plan.steps[${positional[0]}].status: ${String(entry.status)} cannot follow ${String(entry.from_status)}; statuses move one at a time through ${STEP_STATUSES.join(' → ')}, so record rig-create step-status ${String(step.id)} ${next} next`);
166
+ }
167
+ const invalid = statusTransitionFinding(entry, `plan.steps[${positional[0]}].status_history`);
168
+ if (invalid !== null) refuse(invalid.code, invalid.message);
169
+ if (entry.status === 'done' && enabled) {
170
+ const reopens = collectReopens(project, []);
171
+ const signature = collectSignoffs(project, []).filter(s => s.checkpoint === step.id && (s.stage === stage || (oldOrder && s.stage === 'rigging'))
172
+ && liveSignoff(reopens, s.stage, s.at) && s.at <= Date.parse(String(entry.at))).sort((a, b) => b.at - a.at || b.index - a.index)[0];
173
+ if (signature === undefined) refuse('CHECKPOINT_UNSIGNED', `plan.steps[${positional[0]}]: done requires its live human checkpoint signoff; record signoff ${stage} --checkpoint ${String(step.id)} first`);
174
+ entry.signoff = signoffIdentity(signature.record);
175
+ }
176
+ if (step.status_history !== undefined && !Array.isArray(step.status_history)) refuse('STEP_STATUS_TRANSITION', `plan.steps[${positional[0]}].status_history: must be a list; preserve existing history`);
177
+ step.status_history = [...asArray(step.status_history), entry];
178
+ step.status = entry.status!;
179
+ save(dir, file, project, now, false, before);
180
+ return { stdout: json ? JSON.stringify(entry, null, 2) : `step-status: ${positional[0]} ${String(entry.status)}`, exit: 0 };
181
+ }