rig-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,125 @@
1
+ import { closeSync, openSync, renameSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { existsSync, lstatSync, readFileSync, realpathSync } from './check-reads.ts';
3
+ import { dirname, join, resolve } from 'node:path';
4
+ import { checkProject, isObject } from './check.ts';
5
+ import { parseCheckpointEvidence, parseCheckpointTable, readCheckpointEvidence } from './checkpoint-evidence.ts';
6
+ import { readProject } from './feedback.ts';
7
+ import { acquireBoundJobWrite } from './job-writer.ts';
8
+ import { baselineOf, newFindings } from './refusal-baseline.ts';
9
+ import { readVerbatim } from './record-input.ts';
10
+ import { refuse, USAGE_EXIT } from './refusal.ts';
11
+ import type { Json, JsonObject } from './templates.ts';
12
+
13
+ export const CHECKPOINT_EVIDENCE_HELP = `checkpoint-evidence [<project dir>] (--file <packet-list.json> | --clear) [--json]
14
+ Attach an ordered sealed packet list as plan.checkpoint_evidence. Assets already live
15
+ inside the project. Checkpoint IDs name their existing plan steps. Replays write nothing.
16
+ --clear removes the optional field so plan steps can be edited or removed; --json prints null.
17
+ Publication includes packets only for admitted checkpoint subjects. Review validates PNGs.
18
+ checkpoint-table [<project dir>] --file <producer.json> --rows <field> --out <relative.json>
19
+ Present every object row and every column in order. Nested values become lossless JSON
20
+ text cells. No measurements or classifications change. Existing output is immutable.`;
21
+
22
+ function options(args: string[], table: boolean): { root: string; opts: Record<string, string>; json: boolean; clear: boolean } {
23
+ const opts: Record<string, string> = {};
24
+ const positional: string[] = [];
25
+ let json = false;
26
+ let clear = false;
27
+ for (let index = 0; index < args.length; index++) {
28
+ const arg = args[index]!;
29
+ if (arg === '--json' && !json) json = true;
30
+ else if (arg === '--clear' && !table && !clear) clear = true;
31
+ else if ((table ? ['--file', '--rows', '--out'] : ['--file']).includes(arg) && opts[arg] === undefined) {
32
+ const value = args[++index];
33
+ if (!value || value.startsWith('--')) usage();
34
+ opts[arg] = value;
35
+ } else if (arg.startsWith('-')) usage();
36
+ else positional.push(arg);
37
+ }
38
+ if (positional.length > 1 || (!opts['--file'] && !clear) || (opts['--file'] && clear)
39
+ || table && (!opts['--rows'] || !opts['--out'])) usage();
40
+ return { root: realpathSync(resolve(positional[0] ?? process.cwd())), opts, json, clear };
41
+ }
42
+
43
+ function usage(): never {
44
+ refuse('CHECKPOINT_EVIDENCE_USAGE', `plan.checkpoint_evidence: supply the required file options; ${CHECKPOINT_EVIDENCE_HELP}`, USAGE_EXIT);
45
+ }
46
+
47
+ function readJson(file: string): Json {
48
+ try { return JSON.parse(readVerbatim(file)) as Json; }
49
+ catch { return refuse('CHECKPOINT_EVIDENCE_INVALID', 'plan.checkpoint_evidence: input must be valid UTF-8 JSON; supply the producer presentation file'); }
50
+ }
51
+
52
+ export function checkpointEvidenceCommand(args: string[], now: number): { stdout: string; exit: number } {
53
+ const { root, opts, json, clear } = options(args, false);
54
+ const raw = clear ? undefined : readJson(resolve(root, opts['--file']!));
55
+ const release = acquireBoundJobWrite(root);
56
+ const lock = join(root, '.checkpoint-evidence.lock');
57
+ let descriptor: number;
58
+ try { descriptor = openSync(lock, 'wx'); }
59
+ catch { release(); refuse('CHECKPOINT_EVIDENCE_WRITE_CONFLICT', 'plan.checkpoint_evidence: another attachment writer holds the lock; wait and retry'); }
60
+ const temporary = join(root, `.checkpoint-evidence-${crypto.randomUUID()}.tmp`);
61
+ try {
62
+ const { file, project } = readProject(root);
63
+ const original = readFileSync(file);
64
+ const packets = clear ? undefined : parseCheckpointEvidence(raw, project);
65
+ const next = structuredClone(project);
66
+ if (clear) {
67
+ if (isObject(next.plan)) delete next.plan.checkpoint_evidence;
68
+ } else (next.plan as JsonObject).checkpoint_evidence = packets as unknown as Json;
69
+ // The same checker validates every stored declaration and raw file seal.
70
+ readCheckpointEvidence(root, next);
71
+ const fresh = newFindings(baselineOf(checkProject(root, project, { now, readOnly: true })), checkProject(root, next, { now, readOnly: true }).refusals)[0];
72
+ if (fresh) refuse(fresh.code, fresh.message);
73
+ const previous = isObject(project.plan) ? project.plan.checkpoint_evidence : undefined;
74
+ if (JSON.stringify(previous) !== JSON.stringify(packets)) {
75
+ writeFileSync(temporary, `${JSON.stringify(next, null, 2)}\n`, { flag: 'wx' });
76
+ // Check the raw assets again after candidate validation, before committing.
77
+ readCheckpointEvidence(root, next);
78
+ if (!readFileSync(file).equals(original)) refuse('CHECKPOINT_EVIDENCE_WRITE_CONFLICT', 'project.json: changed during checkpoint attachment; reload and retry');
79
+ renameSync(temporary, file);
80
+ }
81
+ return { stdout: json ? JSON.stringify(packets ?? null, null, 2)
82
+ : packets ? `checkpoint-evidence: retained ${packets.length} packet(s)` : 'checkpoint-evidence: cleared', exit: 0 };
83
+ } finally {
84
+ rmSync(temporary, { force: true });
85
+ closeSync(descriptor);
86
+ rmSync(lock);
87
+ release();
88
+ }
89
+ }
90
+
91
+ /** A generic, lossless presentation conversion of a producer's existing object rows. */
92
+ export function checkpointTableCommand(args: string[]): { stdout: string; exit: number } {
93
+ const { root, opts, json } = options(args, true);
94
+ const source = readJson(resolve(root, opts['--file']!));
95
+ const rows = isObject(source) ? source[opts['--rows']!] : undefined;
96
+ if (!Array.isArray(rows) || !rows.length || rows.some(row => !isObject(row))) {
97
+ refuse('CHECKPOINT_EVIDENCE_TABLE_INVALID', 'plan.checkpoint_evidence.table: --rows must name a nonempty ordered array of producer objects; preserve the original rows');
98
+ }
99
+ const columns = Object.keys(rows[0] as JsonObject);
100
+ if (rows.some(row => Object.keys(row as JsonObject).length !== columns.length || columns.some(column => !Object.hasOwn(row as JsonObject, column)))) {
101
+ refuse('CHECKPOINT_EVIDENCE_TABLE_INVALID', 'plan.checkpoint_evidence.table: producer rows have different columns; supply a complete rectangular producer table');
102
+ }
103
+ const table = { schema: 'rig-review-checkpoint-table/1', columns, rows: rows.map(row => columns.map(column => {
104
+ const value = (row as JsonObject)[column]!;
105
+ return typeof value === 'object' && value !== null ? JSON.stringify(value) : value;
106
+ })) };
107
+ const bytes = Buffer.from(`${JSON.stringify(table, null, 2)}\n`);
108
+ parseCheckpointTable(bytes);
109
+ const name = opts['--out']!;
110
+ const target = join(root, name);
111
+ let regularParent = false;
112
+ try { regularParent = realpathSync(dirname(target)) === dirname(target); } catch { /* Named below. */ }
113
+ if (!name || name.length > 4096 || /[\\:%?#\x00-\x1f\x7f]/.test(name) || name.split('/').some(piece => !piece || piece === '.' || piece === '..')
114
+ || ['project.json', 'package.json', 'bun.lock'].includes(name) || !name.endsWith('.json') || !regularParent) {
115
+ refuse('CHECKPOINT_EVIDENCE_PATH_REFUSED', 'plan.checkpoint_evidence.table: --out needs a new contained JSON file in an existing regular directory; retain the source and choose a presentation path');
116
+ }
117
+ try {
118
+ if (existsSync(target)) {
119
+ if (realpathSync(target) !== target || !lstatSync(target).isFile() || !readFileSync(target).equals(bytes)) throw Error();
120
+ } else writeFileSync(target, bytes, { flag: 'wx' });
121
+ } catch {
122
+ refuse('CHECKPOINT_EVIDENCE_WRITE_CONFLICT', 'plan.checkpoint_evidence.table: output is occupied or differs; preserve it and choose a new presentation path');
123
+ }
124
+ return { stdout: json ? JSON.stringify({ path: name, rows: rows.length, columns: columns.length }) : `checkpoint-table: ${name} (${rows.length} rows, ${columns.length} columns)`, exit: 0 };
125
+ }
@@ -0,0 +1,152 @@
1
+ /** Sealed producer presentation. Create binds bytes and never derives measurements. */
2
+ import { createHash } from 'node:crypto';
3
+ import { lstatSync, readFileSync, realpathSync, statSync } from './check-reads.ts';
4
+ import { join } from 'node:path';
5
+ import { asArray, isObject, type Finding } from './check.ts';
6
+ import { Refusal, refuse } from './refusal.ts';
7
+ import type { JsonObject } from './templates.ts';
8
+
9
+ type Seal = { readonly path: string; readonly sha256: string };
10
+ export type CheckpointEvidence = {
11
+ readonly schema: 'rig-review-checkpoint-evidence/1';
12
+ readonly checkpointId: string;
13
+ readonly planStepId: string;
14
+ readonly tiles: readonly (Seal & { readonly id: string; readonly label: string })[];
15
+ readonly table: Seal & { readonly label?: string };
16
+ readonly routesText: string;
17
+ readonly recommendationText: string;
18
+ };
19
+ export type CheckpointTable = {
20
+ schema: 'rig-review-checkpoint-table/1';
21
+ columns: string[];
22
+ rows: (string | number | boolean | null)[][];
23
+ };
24
+ const RECORD_PATH = 'plan.checkpoint_evidence';
25
+ const hash = (bytes: Buffer): string => createHash('sha256').update(bytes).digest('hex');
26
+ const object = (value: unknown): value is Record<string, unknown> => value !== null && typeof value === 'object' && !Array.isArray(value);
27
+ const text = (value: unknown, max: number): value is string => typeof value === 'string' && value.length > 0
28
+ && value.length <= max && !/[\x00-\x08\x0b-\x1f\x7f]/.test(value);
29
+ const id = (value: unknown): value is string => typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(value);
30
+
31
+ function invalid(detail: string): never {
32
+ refuse('CHECKPOINT_EVIDENCE_INVALID', `${RECORD_PATH}: ${detail}; supply the sealed checkpoint packet through checkpoint-evidence --file`);
33
+ }
34
+
35
+ function exact(value: unknown, required: string[], optional: string[] = []): Record<string, unknown> {
36
+ if (!object(value) || required.some(key => !Object.hasOwn(value, key)) || Object.keys(value).some(key => !required.includes(key) && !optional.includes(key))) invalid('unexpected or missing declaration fields');
37
+ return value;
38
+ }
39
+
40
+ function seal(value: Record<string, unknown>): Seal {
41
+ if (typeof value.path !== 'string' || value.path.length > 4096 || !value.path
42
+ || /[\\:%?#\x00-\x1f\x7f]/.test(value.path) || value.path.split('/').some(piece => !piece || piece === '.' || piece === '..')
43
+ || typeof value.sha256 !== 'string' || !/^[a-f0-9]{64}$/.test(value.sha256)) invalid('a file needs its literal portable project-relative path and lowercase SHA-256');
44
+ return { path: value.path, sha256: value.sha256 };
45
+ }
46
+
47
+ /** Fixed key order, exact strings and list order. Absence is handled by the caller. */
48
+ export function parseCheckpointEvidence(raw: unknown, project: JsonObject): CheckpointEvidence[] {
49
+ if (!Array.isArray(raw) || raw.length < 1 || raw.length > 8) invalid('expected 1–8 ordered packets');
50
+ const steps = asArray(isObject(project.plan) ? project.plan.steps : undefined).filter(isObject);
51
+ const seen = new Set<string>();
52
+ const packets = raw.map((value): CheckpointEvidence => {
53
+ const packet = exact(value, ['schema', 'checkpointId', 'planStepId', 'tiles', 'table', 'routesText', 'recommendationText']);
54
+ if (packet.schema !== 'rig-review-checkpoint-evidence/1' || !id(packet.checkpointId) || !id(packet.planStepId)
55
+ || seen.has(packet.checkpointId) || packet.checkpointId !== packet.planStepId
56
+ || steps.filter(step => step.id === packet.planStepId).length !== 1) invalid('checkpointId must name its unique existing plan step without an alias');
57
+ seen.add(packet.checkpointId);
58
+ if (!Array.isArray(packet.tiles) || packet.tiles.length < 1 || packet.tiles.length > 8) invalid('expected 1–8 ordered tiles');
59
+ const tileIds = new Set<string>();
60
+ const tiles = packet.tiles.map(value => {
61
+ const tile = exact(value, ['id', 'label', 'path', 'sha256']);
62
+ if (!id(tile.id) || tileIds.has(tile.id) || !text(tile.label, 512)) invalid('tile IDs must be unique and labels must be bounded nonempty text');
63
+ tileIds.add(tile.id);
64
+ return { id: tile.id, label: tile.label, ...seal(tile) };
65
+ });
66
+ const table = exact(packet.table, ['path', 'sha256'], ['label']);
67
+ if ('label' in table && !text(table.label, 512)) invalid('table label must be bounded nonempty text');
68
+ if (!text(packet.routesText, 4096) || !text(packet.recommendationText, 4096)) invalid('routes and recommendation must be bounded nonempty text');
69
+ return { schema: 'rig-review-checkpoint-evidence/1', checkpointId: packet.checkpointId, planStepId: packet.planStepId,
70
+ tiles, table: { ...seal(table), ...('label' in table ? { label: table.label as string } : {}) },
71
+ routesText: packet.routesText, recommendationText: packet.recommendationText };
72
+ });
73
+ if (Buffer.byteLength(JSON.stringify(packets)) > 32 * 1024) invalid('canonical declaration exceeds 32 KiB');
74
+ return packets;
75
+ }
76
+
77
+ /** Validate presentation grammar only. All supplied scalar values and ordering survive. */
78
+ export function parseCheckpointTable(bytes: Buffer): CheckpointTable {
79
+ const bad = (): never => refuse('CHECKPOINT_EVIDENCE_TABLE_INVALID', `${RECORD_PATH}.table: expected a bounded rig-review-checkpoint-table/1 document with ordered scalar cells; retain all producer rows and columns through checkpoint-table`);
80
+ try {
81
+ if (bytes.length > 256 * 1024) return bad();
82
+ const value: unknown = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
83
+ if (!object(value) || Object.keys(value).length !== 3 || value.schema !== 'rig-review-checkpoint-table/1'
84
+ || !Array.isArray(value.columns) || value.columns.length < 1 || value.columns.length > 32
85
+ || value.columns.some(column => !text(column, 512)) || !Array.isArray(value.rows) || value.rows.length > 2048
86
+ || value.rows.length * value.columns.length > 16384) return bad();
87
+ const width = value.columns.length;
88
+ for (const row of value.rows) {
89
+ if (!Array.isArray(row) || row.length !== width || row.some(cell => typeof cell === 'string'
90
+ ? cell.length > 2048 || /[\x00-\x08\x0b-\x1f\x7f]/.test(cell)
91
+ : cell !== null && typeof cell !== 'boolean' && !(typeof cell === 'number' && Number.isFinite(cell)))) return bad();
92
+ }
93
+ return value as CheckpointTable;
94
+ } catch (error) {
95
+ if (error instanceof Refusal) throw error;
96
+ return bad();
97
+ }
98
+ }
99
+
100
+ /** Contained regular files, bounded before read. Review owns full PNG decoding at admission. */
101
+ export function checkpointFile(root: string, file: Seal, limit: number): Buffer {
102
+ const path = `${RECORD_PATH}[${file.path}]`;
103
+ let bytes: Buffer;
104
+ try {
105
+ const canonicalRoot = realpathSync(root);
106
+ const target = join(canonicalRoot, file.path);
107
+ if (realpathSync(target) !== target || !lstatSync(target).isFile() || statSync(target).size > limit) throw Error();
108
+ bytes = readFileSync(target);
109
+ if (bytes.length > limit) throw Error();
110
+ } catch {
111
+ refuse('CHECKPOINT_EVIDENCE_PATH_REFUSED', `${path}: file must be a bounded regular project file without symlinks; restore its sealed bytes`);
112
+ }
113
+ if (hash(bytes) !== file.sha256) refuse('CHECKPOINT_EVIDENCE_HASH_MISMATCH', `${path}: raw bytes differ from the declared SHA-256; restore the sealed file or attach a newly sealed packet`);
114
+ return bytes;
115
+ }
116
+
117
+ export function readCheckpointEvidence(root: string, project: JsonObject): CheckpointEvidence[] | undefined {
118
+ if (!isObject(project.plan) || !Object.hasOwn(project.plan, 'checkpoint_evidence')) return undefined;
119
+ const packets = parseCheckpointEvidence(project.plan.checkpoint_evidence, project);
120
+ let tableBytes = 0;
121
+ for (const packet of packets) {
122
+ for (const tile of packet.tiles) checkpointFile(root, tile, 8 * 1024 * 1024);
123
+ const bytes = checkpointFile(root, packet.table, 256 * 1024);
124
+ tableBytes += bytes.length;
125
+ if (tableBytes > 512 * 1024) invalid('raw tables exceed the 512 KiB publication bound');
126
+ parseCheckpointTable(bytes);
127
+ }
128
+ return packets;
129
+ }
130
+
131
+ export function checkCheckpointEvidence(root: string, project: JsonObject, refusals: Finding[]): void {
132
+ try { readCheckpointEvidence(root, project); }
133
+ catch (error) {
134
+ if (!(error instanceof Refusal)) throw error;
135
+ refusals.push({ code: error.code, path: RECORD_PATH, message: error.message });
136
+ }
137
+ }
138
+
139
+ /** Publish only declared subjects, with their exact asset closure. No fallback association. */
140
+ export function checkpointEvidenceContext(root: string, project: JsonObject,
141
+ subjects: readonly { readonly kind: string; readonly checkpointId?: string; readonly planStepId?: string }[], files: Seal[]): { checkpointEvidence?: readonly CheckpointEvidence[] } {
142
+ const packets = readCheckpointEvidence(root, project)?.filter(packet => subjects.some(subject => subject.kind === 'checkpoint'
143
+ && subject.checkpointId === packet.checkpointId && (subject.planStepId ?? subject.checkpointId) === packet.planStepId));
144
+ if (!packets?.length) return {};
145
+ for (const packet of packets) {
146
+ for (const { path, sha256 } of [...packet.tiles, packet.table]) {
147
+ if (files.some(file => file.path === path && file.sha256 !== sha256)) invalid('closure contains conflicting file seals');
148
+ if (!files.some(file => file.path === path)) files.push({ path, sha256 });
149
+ }
150
+ }
151
+ return { checkpointEvidence: packets };
152
+ }
@@ -0,0 +1,271 @@
1
+ import { overrideFindings } from './overrides.ts';
2
+ import { intakeItem, intakeWarnings, INTAKE_QUESTION } from './intake.ts';
3
+ /** Public record writers; validation precedes every project write. */
4
+ import { readFileSync, renameSync, writeFileSync } from 'node:fs';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { acquireBoundJobWrite, jobRuntimeExecArgv } from './job-writer.ts';
7
+ import { assertActSubjectDeclared, decodeReviewAct, reviewActFindings, reviewActTransport, type ReviewActTransport, type InterviewPicksAct } from './review-act.ts';
8
+ import { dirname, join, resolve } from 'node:path';
9
+ import { isDeepStrictEqual as equal } from 'node:util';
10
+ import { asArray, checkProject, collectItems, collectSignoffs, isObject, loadCollection, collectionAnswer, type Item } from './check.ts';
11
+ import { approvalStageOfGuide } from './stages.ts';
12
+ import { collectReopens, liveSignoff } from './reopen.ts';
13
+ import { readProject } from './feedback.ts';
14
+ import { refuse, USAGE_EXIT } from './refusal.ts';
15
+ import { baselineOf, findingKey, newFindings, stoppedEarly } from './refusal-baseline.ts';
16
+ import { CHOICE_ORDER_SPEC, CHOICE_SPEC, ChoiceError, assembleFragments, choiceAdoption, choiceVerbatim, literalFile, optionFile, refusalBaseline, selection, sha256, validateChoice, validateContext, validateSet } from './choices.ts';
17
+ import type { Json, JsonObject } from './templates.ts';
18
+
19
+ export const CHOICE_HELP = `interview-options <question> [<dir>] --file <retained project-relative JSON>
20
+ interview-options --adopt [<dir>]
21
+ interview-answer <question> [<dir>] --from-review <actId> [--why-file <file>]
22
+ interview-answer <question> [<dir>] --set <path> --sha256 <shown sha> --by <human>
23
+ [--pick <ask>=<option>...] [--other <ask>=<file> --other-fragment <ask>=<JSON file>]...
24
+ [--note-file <file>] [--source <location>] [--why-file <file> for a member override]
25
+ interview-decision <question> [<dir>] --by <agent> --reasoning-file <file>
26
+ --fragment <ask>=<JSON file>...
27
+ Retain the shown set first. Each ask is picked explicitly; no recommended option is preselected.`;
28
+ function usage(why: string): never { refuse('INTERVIEW_OPTIONS_USAGE', `interview: ${why}; ${CHOICE_HELP}`, USAGE_EXIT); }
29
+ interface Args { positional: string[]; one: Record<string,string>; many: Record<string,Map<string,string>>; adopt: boolean; }
30
+ function parse(args: string[], allowed: string[], repeat: string[] = []): Args {
31
+ const parsed: Args = { positional: [], one: {}, many: {}, adopt: false };
32
+ for (let i=0;i<args.length;i++) {
33
+ const arg = args[i] as string;
34
+ if (arg === '--adopt' && allowed.includes(arg)) { if (parsed.adopt) usage('duplicate --adopt'); parsed.adopt = true; }
35
+ else if (repeat.includes(arg) || allowed.includes(arg)) {
36
+ const v = args[++i]; if (v === undefined || v.startsWith('--')) usage(`${arg} needs a value`);
37
+ if (repeat.includes(arg)) {
38
+ const eq = v.indexOf('='); if (eq < 1 || eq === v.length-1) usage(`${arg} needs ask=value`);
39
+ const key = v.slice(0,eq); const values = parsed.many[arg] ?? new Map<string,string>();
40
+ if (values.has(key)) usage(`${arg}: duplicate ask ${key}`);
41
+ values.set(key,v.slice(eq+1)); parsed.many[arg] = values;
42
+ } else { if (parsed.one[arg] !== undefined) usage(`duplicate ${arg}`); parsed.one[arg] = v; }
43
+ } else if (arg.startsWith('-')) usage(`unknown flag ${arg}`);
44
+ else parsed.positional.push(arg);
45
+ }
46
+ return parsed;
47
+ }
48
+ function itemAt(project: JsonObject, dir: string, question: string | undefined): Item {
49
+ if (!question) usage('question required');
50
+ if (question === INTAKE_QUESTION) {
51
+ if (project.stage !== 'parts') usage('intake_suitability is offered only in parts after recording a preflight');
52
+ if (intakeWarnings(dir,project).some(w => w.code === 'INTAKE_REPORT_INVALID')) refuse('INTAKE_REPORT_INVALID','intake.reports: restore bound producer bytes before drafting choices');
53
+ const intake = intakeItem(project); if (!intake) usage('record a preflight with intake-report first'); return intake;
54
+ }
55
+ const found = collectItems(project, { collection: loadCollection(dir) }).find(i => i.id === question);
56
+ if (!found) usage(`unknown question ${question}`);
57
+ return found;
58
+ }
59
+ function readText(file: string): string { try { return readFileSync(file,'utf8'); } catch { return usage(`cannot read ${file}`); } }
60
+ function jsonFile(file: string): Json { try { return JSON.parse(readText(file)) as Json; } catch { return usage(`invalid JSON file ${file}`); } }
61
+ function run(action: () => string): string {
62
+ try { return action(); } catch (e) { if (e instanceof ChoiceError) refuse(e.code, `interview: ${e.message}; retain the shown set and explicitly record each ask`); throw e; }
63
+ }
64
+ /** Partial interviews may retain unrelated pre-existing refusals, but a writer cannot add a new one. */
65
+ export function save(file: string, dir: string, original: JsonObject, candidate: JsonObject, now: number, baseline: JsonObject[] = []): void {
66
+ const asRead = checkProject(dir,original,{now});
67
+ const counts = baselineOf(asRead);
68
+ // A pending answer's stored baseline widens the record's own; it never stands in for a check that stopped early.
69
+ if (!stoppedEarly(asRead)) for (const f of baseline) {
70
+ const k = findingKey({ code: String(f.code), path: String(f.path) });
71
+ counts.set(k,Math.max(counts.get(k) ?? 0,Number(f.count)));
72
+ }
73
+ const first = newFindings(counts,checkProject(dir,candidate,{now}).refusals)[0];
74
+ if (first) refuse(first.code,first.message);
75
+ const tmp = join(dirname(file), `.interview-${process.pid}-${crypto.randomUUID()}.tmp`);
76
+ writeFileSync(tmp,`${JSON.stringify(candidate,null,2)}\n`, { flag: 'wx' }); renameSync(tmp,file);
77
+ }
78
+ function checkNewAnswer(dir: string, project: JsonObject, item: Item): void {
79
+ if (item.path === 'intake.choice.answer') {
80
+ if (choiceAdoption(project, item) === null) refuse('INTERVIEW_CHOICE_REQUIRED', 'intake.choice: adopt choices before recording a new choice');
81
+ return;
82
+ }
83
+ const stage = approvalStageOfGuide(item.id,item.kind === 'post',true);
84
+ const reopens = collectReopens(project,[]);
85
+ if (collectSignoffs(project,[]).some(s => s.stage === stage && s.checkpoint === null && liveSignoff(reopens,s.stage,s.at))) refuse('INTERVIEW_CHOICE_INVALID', `${item.path}: approval is still live; reopen ${stage === 'plan-approved' ? 'interview' : stage} before changing this answer`);
86
+ if (choiceAdoption(project, item) === null) refuse('INTERVIEW_CHOICE_REQUIRED', `${item.path}: adopt order@2 with interview-options --adopt before recording new choices`);
87
+ if (isObject(item.raw) && item.raw.basis === 'inherited') {
88
+ // This changes the local member record, never its resolved collection source.
89
+ if (!loadCollection(dir)) refuse('INTERVIEW_CHOICE_INVALID', `${item.path}: collection source unavailable; restore it before recording a member-local choice`);
90
+ }
91
+ }
92
+
93
+ export function interviewOptionsCommand(args: string[], now: number): string { return run(() => {
94
+ const opts = parse(args,['--file','--adopt']);
95
+ if (opts.adopt) {
96
+ if (opts.positional.length > 1 || Object.keys(opts.one).length) usage('--adopt takes only a project folder');
97
+ const dir = resolve(opts.positional[0] ?? process.cwd()); const {file,project} = readProject(dir); const next = structuredClone(project);
98
+ const interview = isObject(next.interview) ? next.interview : null;
99
+ if (interview && interview.order === undefined && isObject(interview.post)) {
100
+ if (project.stage !== 'post-interview') refuse('INTERVIEW_CHOICE_INVALID', 'interview.post: advance to post-interview before adopting post choices');
101
+ if (interview.post.choices_adopted_at !== undefined) {
102
+ if (!Number.isFinite(Date.parse(String(interview.post.choices_adopted_at)))) refuse('INTERVIEW_CHOICE_INVALID', 'interview.post: restore the original valid choices_adopted_at');
103
+ return 'interview: post choices already adopted; original date preserved';
104
+ }
105
+ interview.post.choices_adopted_at = new Date(now).toISOString();
106
+ save(file,dir,project,next,now); return 'interview: post choices adopted; stage order and earlier answers preserved';
107
+ }
108
+ if (!interview || !isObject(interview.order) || !['rig-create/stage-order@1', CHOICE_ORDER_SPEC].includes(String(interview.order.spec))) refuse('INTERVIEW_CHOICE_INVALID','interview.order: first adopt the split stage order with a fresh parts/rigging interview; then adopt choices');
109
+ if (interview.order.spec === CHOICE_ORDER_SPEC) return 'interview: choices already adopted; original dates preserved';
110
+ interview.order = {...interview.order,spec:CHOICE_ORDER_SPEC,choices_adopted_at:new Date(now).toISOString()};
111
+ save(file,dir,project,next,now); return 'interview: choices adopted; earlier answers preserved';
112
+ }
113
+ if (opts.positional.length > 2 || !opts.one['--file']) usage('question and --file required');
114
+ const dir = resolve(opts.positional[1] ?? process.cwd()); const {file,project} = readProject(dir); const next = structuredClone(project);
115
+ const item = itemAt(next,dir,opts.positional[0]); const path = opts.one['--file'];
116
+ let binding: JsonObject;
117
+ try { binding = {path,sha256:sha256(readFileSync(literalFile(dir,path)))}; } catch (e) { throw new ChoiceError('INTERVIEW_OPTIONS_FILE',String(e)); }
118
+ const {set} = optionFile(dir,binding); validateSet(next,item,set,loadCollection(dir)); validateContext(dir,next,set,now,true);
119
+ if (choiceAdoption(next, item) === null) refuse('INTERVIEW_CHOICE_REQUIRED',`${item.path}: adopt choices before drafting a new question`);
120
+ // A retained path/id may never be reused with a different hash, even after answer replacement.
121
+ for (const other of [...collectItems(next,{collection:loadCollection(dir)}), ...(item.path === 'intake.choice.answer' ? [item] : [])]) {
122
+ const bindings = [other.record.pending_options, ...[other.raw,...asArray(other.record.history)].filter(isObject).map(a => isObject(a.choice) ? a.choice.set : undefined)];
123
+ for (const old of bindings) if (isObject(old)) {
124
+ if (old.path === path && !equal(old,binding)) refuse('INTERVIEW_OPTIONS_FILE',`${item.path}: option path is already bound to other bytes; retain the original and use a new set id/path`);
125
+ const retained = optionFile(dir,old).set;
126
+ if (retained.id === set.id && !equal(old,binding)) refuse('INTERVIEW_OPTIONS_FILE',`${item.path}: option id is already retained; use a new id/path for a redraft`);
127
+ }
128
+ }
129
+ item.record.pending_options = binding; save(file,dir,project,next,now); return JSON.stringify(binding);
130
+ }); }
131
+
132
+ export interface InterviewAnswerHooks {
133
+ fetch?: (transport: ReviewActTransport) => { bytes: string };
134
+ }
135
+ export function interviewAnswerCommand(args: string[], now: number, hooks: InterviewAnswerHooks = {}): string { return run(() => {
136
+ if (args.includes('--picks-file')) refuse('INTERVIEW_PICKS_FILE_REMOVED', 'project.json: --picks-file was removed; use interview-answer <question> --from-review <actId>', USAGE_EXIT);
137
+ const opts = parse(args,['--set','--sha256','--by','--note-file','--source','--from-review','--why-file'],['--pick','--other','--other-fragment']);
138
+ if (opts.positional.length > 2 || (!opts.one['--from-review'] && (!opts.one['--set'] || !opts.one['--sha256'] || !opts.one['--by']))) usage('question and --from-review, or --set/--sha256/--by required');
139
+ const dir = resolve(opts.positional[1] ?? process.cwd());
140
+ const release = acquireBoundJobWrite(dir);
141
+ try {
142
+ const {file,project} = readProject(dir); const original = readFileSync(file); const next = structuredClone(project);
143
+ const item = itemAt(next,dir,opts.positional[0]);
144
+ let payload: JsonObject | null = null;
145
+ let fetched: { bytes: string } | undefined;
146
+ let act: InterviewPicksAct | undefined;
147
+ if (opts.one['--from-review']) {
148
+ if (Object.keys(opts.many).length || opts.one['--note-file']) usage('--from-review is exclusive with pick/Other/note flags');
149
+ const invalid = reviewActFindings(next)[0];
150
+ if (invalid) refuse(invalid.code,invalid.message);
151
+ const transport: ReviewActTransport = { ...reviewActTransport(dir, opts.one['--from-review']), kind: 'interview-picks' };
152
+ if (hooks.fetch) fetched = hooks.fetch(transport);
153
+ else {
154
+ const child = Bun.spawnSync([process.execPath, ...jobRuntimeExecArgv(), fileURLToPath(new URL('./review-act-runtime.ts', import.meta.url))], {
155
+ stdin: Buffer.from(JSON.stringify(transport)), stdout: 'pipe', stderr: 'pipe',
156
+ });
157
+ let response: { ok: boolean; bytes: string; code: string; message: string };
158
+ try { response = JSON.parse(child.stdout.toString()); }
159
+ catch { return refuse('REVIEW_ACT_FETCH_REFUSED', 'project.json.review_acts: fetch adapter failed; restore the live Review job'); }
160
+ if (!response.ok || child.exitCode) refuse(response.code, response.message);
161
+ fetched = response;
162
+ }
163
+ act = decodeReviewAct(fetched.bytes, 'interview-picks');
164
+ if (act.jobId !== transport.jobId || act.actId !== transport.actId || Date.parse(act.at) > now
165
+ || act.value.questionId !== item.id) refuse('REVIEW_ACT_INVALID', `${item.path}: fetched identity, question or date differs; fetch the correct interview act`);
166
+ const existing = asArray(next.review_acts).filter(isObject).find(entry => entry.jobId === act!.jobId && entry.actId === act!.actId);
167
+ if (existing) {
168
+ if (existing.bytes !== fetched.bytes) refuse('REVIEW_ACT_REPLAY_CONFLICT', 'project.json.review_acts: immutable interview act changed; restore the original receipt');
169
+ const retained = [item.raw,...asArray(item.record.history)].filter(isObject).find(answer => answer.at === act!.at
170
+ && sha256(Buffer.from(JSON.stringify(answer.choice))) === existing.version_hash);
171
+ if (!retained) refuse('REVIEW_ACT_LEDGER_INVALID', `${item.path}: original interview answer is absent; restore its history`);
172
+ return JSON.stringify(retained);
173
+ }
174
+ if (act.schema === 'rig-review/act@4') assertActSubjectDeclared(dir, act, item.path);
175
+ if (act.schema === 'rig-review/act@4' && act.source.projectHash !== sha256(original)) refuse('REVIEW_ACT_SOURCE_STALE', `${item.path}: displayed record differs; publish the current question again`);
176
+ const registered = item.record.pending_options;
177
+ if (!isObject(registered) || registered.sha256 !== act.source.optionSet.sha256) refuse('INTERVIEW_OPTIONS_STALE', `${item.path}: displayed option set differs; refresh the questionnaire`);
178
+ const {set} = optionFile(dir,registered);
179
+ if (set.id !== act.source.optionSet.id) refuse('INTERVIEW_OPTIONS_STALE', `${item.path}: displayed option set identity differs; refresh the questionnaire`);
180
+ payload = { set: registered, picks: act.value.selections.map(row => ({ ask: row.itemId, option_id: row.optionId,
181
+ ...(row.otherText === null ? {} : {other_text:row.otherText}) })), note: act.value.note,
182
+ by: act.authority.reviewer.role === 'owner' ? 'user' : 'delegate', at: act.at };
183
+ }
184
+ checkNewAnswer(dir,next,item);
185
+ const binding = payload ? payload.set as JsonObject : {path:opts.one['--set'] as string,sha256:opts.one['--sha256'] as string};
186
+ const registered = item.record.pending_options;
187
+ if (isObject(registered) && binding.sha256 !== registered.sha256) refuse('INTERVIEW_OPTIONS_STALE',`${item.path}: displayed set sha differs from the registered set; refresh the questionnaire`);
188
+ if (payload && ((opts.one['--set'] && opts.one['--set'] !== binding.path) || (opts.one['--sha256'] && opts.one['--sha256'] !== binding.sha256))) refuse('INTERVIEW_OPTIONS_STALE',`${item.path}: review act set differs from the displayed set; refresh the questionnaire`);
189
+ if (payload && opts.one['--by'] && opts.one['--by'] !== payload.by) usage('--by differs from review act human identity');
190
+ if (!equal(registered,binding)) refuse('INTERVIEW_CHOICE_INVALID',`${item.path}: displayed set is not the registered pending set; register/refresh the shown options`);
191
+ const {set} = optionFile(dir,binding); validateSet(next,item,set,loadCollection(dir)); validateContext(dir,next,set,now,true);
192
+ const offered = asArray(set.asks).filter(isObject);
193
+ const reviewPicks = payload ? (payload.picks as Json[]).filter(isObject) : null;
194
+ const selected = reviewPicks ? new Map(reviewPicks.map(p => [String(p.ask),String(p.option_id)])) : opts.many['--pick'] ?? new Map<string,string>();
195
+ if (selected.size !== offered.length || [...selected.keys()].some(k => !offered.some(a => a.id === k)) || (reviewPicks && !equal(reviewPicks.map(p=>p.ask),offered.map(a=>a.id)))) throw new ChoiceError('INTERVIEW_CHOICE_INVALID','pick exactly once per applicable ask in questionnaire order');
196
+ const picks: JsonObject[] = offered.map(ask => {
197
+ const option = asArray(ask.options).filter(isObject).find(o => o.id === selected.get(String(ask.id)));
198
+ if (!option) throw new ChoiceError('INTERVIEW_CHOICE_INVALID',`unknown option for ${String(ask.id)}`);
199
+ const pick: JsonObject = {ask:ask.id as Json,option_id:option.id as Json,shown:{label:option.label as Json,effect:option.effect as Json}};
200
+ const fromReview = reviewPicks?.find(p=>p.ask===ask.id);
201
+ if (option.kind === 'other') {
202
+ const textPath = opts.many['--other']?.get(String(ask.id)); const fragment = opts.many['--other-fragment']?.get(String(ask.id));
203
+ const words = fromReview?.other_text ?? (textPath ? readText(textPath) : undefined);
204
+ if (typeof words !== 'string' || !words.trim()) throw new ChoiceError('INTERVIEW_CHOICE_INVALID','Other requires exact nonempty text');
205
+ pick.other_text = words;
206
+ if (fragment) pick.other_fragment = jsonFile(fragment);
207
+ } else if (fromReview?.other_text !== undefined) throw new ChoiceError('INTERVIEW_CHOICE_INVALID','other_text is only for Other');
208
+ return pick;
209
+ });
210
+ for (const flag of ['--other','--other-fragment']) for (const ask of opts.many[flag]?.keys() ?? []) if (!picks.some(p => p.ask === ask && 'other_text' in p)) usage(`${flag} supplied for non-Other ask ${ask}`);
211
+ const choice: JsonObject = {spec:CHOICE_SPEC,set:binding,picks};
212
+ if (payload?.note !== undefined) choice.note = payload.note;
213
+ else if (opts.one['--note-file']) choice.note = readText(opts.one['--note-file']);
214
+ const result = selection(set,choice);
215
+ const answer: JsonObject = {delegated:result.delegated,by:payload?.by ?? opts.one['--by'],at:payload?.at ?? new Date(now).toISOString(),verbatim:choiceVerbatim(picks,choice.note),source:opts.one['--source'] ?? 'review questionnaire',choice,asks:result.asks};
216
+ if (result.value !== undefined) answer.value = result.value;
217
+ if (result.autos.length) answer.agent_decision = {covers:result.autos.join(',')};
218
+ if (isObject(item.raw) && typeof item.raw.basis === 'string') answer.basis = item.raw.basis === 'inherited' ? (item.state.state === 'silent' ? 'own' : 'overridden') : item.raw.basis;
219
+ if (answer.basis === 'overridden') {
220
+ const collection = loadCollection(dir);
221
+ const prior = item.raw as JsonObject;
222
+ const inherited = prior.basis === 'inherited' ? prior : prior.overrides === undefined ? asArray(item.record.history).filter(isObject).reverse().find(a => a.basis === 'inherited') : undefined;
223
+ if (inherited !== undefined) {
224
+ const from = isObject(inherited.from) ? inherited.from : {};
225
+ const source = collection !== null && typeof from.question === 'string' ? collectionAnswer(collection, from.question) : undefined;
226
+ answer.overrides = { collection: from.collection ?? null, question: from.question ?? null, collection_answer_at: isObject(source) ? source.at ?? null : null };
227
+ } else if (prior.overrides !== undefined) answer.overrides = structuredClone(prior.overrides);
228
+ answer.why_verbatim = null;
229
+ if (opts.one['--why-file']) {
230
+ try { answer.why_verbatim = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(readFileSync(opts.one['--why-file'])); }
231
+ catch { refuse('OVERRIDE_REASON_MISSING', `${item.path}: --why-file must be readable UTF-8; ask for and retain the user's exact nonblank reason`); }
232
+ }
233
+ const findings = overrideFindings(answer, collection, item.path);
234
+ if (findings.length) refuse(findings[0]!.code, findings[0]!.message);
235
+ } else if (opts.one['--why-file']) usage('--why-file applies only to a member override');
236
+ if (result.autos.length || result.pending.length) {
237
+ answer.refusal_baseline = [...baselineOf(checkProject(dir,project,{now}))].map(([key,count]) => {
238
+ const [code,path] = JSON.parse(key) as [string,string];
239
+ return {code,path,count};
240
+ });
241
+ }
242
+ validateChoice(dir,next,item,answer);
243
+ if (item.raw !== null && item.raw !== undefined) {
244
+ const prior = [item.raw];
245
+ if (isObject(item.raw) && item.raw.basis === 'inherited' && (item.state.state === 'user' || item.state.state === 'delegated')) prior.push(structuredClone(item.state.answer));
246
+ item.record.history = [...asArray(item.record.history),...prior];
247
+ }
248
+ item.record[item.kind === 'framing' ? 'answer' : 'guide'] = answer; delete item.record.pending_options;
249
+ if (act && fetched) next.review_acts = [...asArray(next.review_acts), {schema:'rig-create/review-act@1',kind:'interview-picks',
250
+ jobId:act.jobId,actId:act.actId,question:item.id,bytes:fetched.bytes,sha256:sha256(Buffer.from(fetched.bytes)),
251
+ version_hash:sha256(Buffer.from(JSON.stringify(choice)))}];
252
+ if (!readFileSync(file).equals(original)) refuse('REVIEW_ACT_SOURCE_STALE', 'project.json: record changed during interview fetch; retry against the current job');
253
+ save(file,dir,project,next,now); return JSON.stringify(answer);
254
+ } finally { release(); }
255
+ }); }
256
+
257
+ export function interviewDecisionCommand(args: string[], now: number): string { return run(() => {
258
+ const opts = parse(args,['--by','--reasoning-file'],['--fragment']);
259
+ if (opts.positional.length > 2 || !opts.one['--by'] || !opts.one['--reasoning-file']) usage('question, --by and --reasoning-file required');
260
+ const dir = resolve(opts.positional[1] ?? process.cwd()); const {file,project} = readProject(dir); const next = structuredClone(project);
261
+ const item = itemAt(next,dir,opts.positional[0]); checkNewAnswer(dir,next,item);
262
+ if (!isObject(item.raw) || !isObject(item.raw.choice)) refuse('INTERVIEW_CHOICE_INVALID',`${item.path}: explicitly choose auto before writing a decision`);
263
+ const {set} = optionFile(dir,item.raw.choice.set); validateContext(dir,next,set,now,true);
264
+ const result = selection(set,item.raw.choice); const supplied = opts.many['--fragment'] ?? new Map<string,string>();
265
+ if (!result.autos.length || supplied.size !== result.autos.length || [...supplied.keys()].some(k => !result.autos.includes(k))) usage('supply exactly the auto asks with --fragment');
266
+ const fragments = result.autos.map(ask => ({ask,fragment:jsonFile(supplied.get(ask) as string)}));
267
+ const pending = isObject(item.raw.agent_decision) && equal(Object.keys(item.raw.agent_decision),['covers']);
268
+ const baseline = pending ? refusalBaseline(item.raw) : [];
269
+ item.raw.agent_decision = {by:opts.one['--by'],at:new Date(now).toISOString(),reasoning:readText(opts.one['--reasoning-file']),covers:result.autos.join(','),fragments,value:assembleFragments(String((set.assembly as JsonObject).rule),fragments.map(f=>f.fragment))};
270
+ validateChoice(dir,next,item,item.raw); save(file,dir,project,next,now,baseline); return JSON.stringify(item.raw.agent_decision);
271
+ }); }