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
package/src/check.ts ADDED
@@ -0,0 +1,1059 @@
1
+ import { packageUpgradeFindings } from './package-upgrades.ts';
2
+ import { checkPublicationGenerations } from './job-publication.ts';
3
+ import { collectBarAmendments, effectiveBar } from './plan-bar-amendments.ts';
4
+ import { partsReadFindings, latestPartsVerdictFinding, PARTS_VERDICT_LATEST_SINCE, recordedPartsVerdict, recordedPartsFinding } from './parts-verdict-record.ts';
5
+ import { partsActFindings } from './parts-act.ts';
6
+ import { reviewActFindings } from './review-act.ts';
7
+ import { checkCheckpointEvidence } from './checkpoint-evidence.ts';
8
+ import { checkAdvances, checkAdvanceBars, checkPauses, checkProgressLinkage, checkStageProgress, checkStepStatusHistory } from './stage-progress.ts';
9
+ import { checkPartsChecks } from './parts-check.ts';
10
+ import { pendingPublicationHoles } from './job-outbox.ts';
11
+ import { checkSignoffAnswers } from './signoff-answers.ts';
12
+ import { checkSignoffTarget } from './signoff-target.ts';
13
+ import { checkOverrides } from './overrides.ts';
14
+ import { intakeWarnings } from './intake.ts';
15
+ import { checkDirections } from './direction.ts';
16
+ import { checkScenes } from './scene.ts';
17
+ import { checkCorrections } from './corrections.ts';
18
+ import { amendmentSummaries, collectAmendments, type AmendmentSummary } from './amend.ts';
19
+ /**
20
+ * `interview --check`: reads a project's `project.json` and reports, by name,
21
+ * every place the record breaks the contract in docs/INTERVIEW.md and
22
+ * docs/PROJECT.md — every refusal it finds, not the first. The codes and what
23
+ * clears each are listed in docs/CHECKS.md; a code added here is added there.
24
+ *
25
+ * The waypoint set is not a hand list: it is derived from the installed
26
+ * `@rig-c/parts` sections (src/parts-sections.ts) through the placement table
27
+ * (src/waypoints.ts). The stage order comes from src/stages.ts.
28
+ *
29
+ * Later cards plug in as further `check*` functions over the same `Ctx`:
30
+ * provenance per config section (src/provenance.ts, over `versions[].provenance`), the
31
+ * review order and post hand-off (src/review.ts, with src/stages.ts), feedback
32
+ * answered by the next version (#9, over `versions[].verdicts` and
33
+ * `answers_feedback`), collection pins (#7, over `collection.json`), and the
34
+ * CG gates a full-screen scene's rig versions declare (src/cg-gates.ts), and the
35
+ * bust's default or reasoned exclusion at `meshes` (src/meshes-bust.ts).
36
+ */
37
+ import { existsSync, readFileSync, recordCheckReads, type CheckRead } from './check-reads.ts';
38
+ import { basename, join, resolve } from 'node:path';
39
+ import { checkCollectionBinding, loadCollectionForDir, withCandidateProject } from './collection-binding-reader.ts';
40
+
41
+ import { PartsUnreadable, readPartsSections, type PartsSections } from './parts-sections.ts';
42
+ import { checkRiggingContext } from './structure.ts';
43
+ import { checkDrafts, draftVersionFindings } from './drafts.ts';
44
+ import { checkFixLedger, type LedgerSummary } from './fix-ledger.ts';
45
+ import { checkCgGates } from './cg-gates.ts';
46
+ import { checkFigureMask } from './figure-mask.ts';
47
+ import { checkSetupPainting } from './setup-painting.ts';
48
+ import { checkChoices } from './choices.ts';
49
+ import { translateInvariants, type InvariantsDocument } from './invariants.ts';
50
+ import { checkAskCoverage } from './ask-coverage.ts';
51
+ import { checkAgentBounces } from './agent-bounce.ts';
52
+ import { checkInputs } from './inputs.ts';
53
+ import { checkPreparedInputs } from './prepared-inputs.ts';
54
+ import { checkMeshesBust } from './meshes-bust.ts';
55
+ import { checkProvenance, formatProvenance, type ProvenanceSummary } from './provenance.ts';
56
+ import { checkFeedback, formatFeedback, type FeedbackSummary } from './feedback.ts';
57
+ import { checkDiffMap } from './diff-map.ts';
58
+ import { checkReviews, latestAccepted, readVersions } from './review.ts';
59
+ import { checkAppliedReview } from './applied-review.ts';
60
+ import { checkReviewPackages } from './review-package.ts';
61
+ import { rigRegistrationFindings } from './rig-version.ts';
62
+ import { checkPreInterview } from './pre-interview.ts';
63
+ import {
64
+ PARTS_REVIEW,
65
+ PARTS_REVIEW_SINCE,
66
+ PAUSED_STAGE,
67
+ approvalStageOfStep,
68
+ approvalStageOfGuide,
69
+ hasStageOrder,
70
+ interviewStageOfGuide,
71
+ LEGACY_STAGES,
72
+ LEGACY_STAGE_VALUES,
73
+ SKIPPED_WHEN_NO_POST,
74
+ STAGE_VALUES,
75
+ STAGES,
76
+ stageIndex,
77
+ stageOfStep,
78
+ } from './stages.ts';
79
+ import type { Json, JsonObject } from './templates.ts';
80
+ import { collectReopens, liveSignoff } from './reopen.ts';
81
+ import { reopenLapseWarnings } from './reopen-lapses.ts';
82
+ import { stepHash } from './step-hash.ts';
83
+ import { collectPlanReplaced, checkReplacedSignoffs, collectCheckpointWithdrawals, checkpointRequired } from './plan-history.ts';
84
+ import { deriveWaypoints, FRAMING_IDS, type DerivedSet } from './waypoints.ts';
85
+
86
+ export const PROJECT_SCHEMA = 'rig-create/project@0';
87
+ export const STAGE_ORDER_LEGACY_FINDING: Finding = { code: 'STAGE_ORDER_LEGACY', path: 'interview.order', message: 'interview.order: legacy interview order; preserve old rig guides as history, not as answers asked after parts; adopt explicitly with a fresh rigging-interview sign-off' };
88
+
89
+ export const COLLECTION_SCHEMA = 'rig-create/collection@0';
90
+
91
+ /** Heuristic only: these phrases name appearance calls, not measured step readings. */
92
+ export const APPEARANCE_IN_MOTION_PHRASES = [
93
+ 'perceived jiggle',
94
+ 'perceived motion',
95
+ 'looks right in the loop',
96
+ 'looks right in loop',
97
+ 'looks natural in motion',
98
+ 'natural motion',
99
+ 'smooth motion',
100
+ 'visible at 1x',
101
+ 'seen at 1x',
102
+ ] as const;
103
+
104
+ /** Numeric prose only: commands/URLs and viewing scales such as 1x are not readings. */
105
+ function hasNumericReading(bar: string): boolean {
106
+ const prose = bar.replace(/`[^`]*`/g, ' ').replace(/https?:\/\/\S+/g, ' ');
107
+ const number = String.raw`[+-]?(?:\d+(?:\.\d+)?|\.\d+)`;
108
+ const unit = String.raw`(?:px|pixels?|mm|cm|m|deg|degrees?|rad|radians?|ms|s|seconds?|frames?|world units?|units?)\b|[%°]`;
109
+ return new RegExp(String.raw`(?<![\w.])${number}\s*(?:${unit})`, 'i').test(prose)
110
+ || new RegExp(String.raw`(?:<=|>=|<|>|=|≤|≥)\s*${number}(?![\w.])|(?<![\w.])${number}\s*(?:<=|>=|<|>|=|≤|≥)`).test(prose);
111
+ }
112
+
113
+ /** Warning-only wording lint; it cannot watch motion or certify a numeric clause. */
114
+ export function appearanceBarWarnings(project: JsonObject, now = Date.now()): Finding[] {
115
+ const plan = isObject(project.plan) ? project.plan : {};
116
+ const warnings: Finding[] = [];
117
+ for (const raw of asArray(plan.steps)) {
118
+ if (!isObject(raw) || typeof raw.id !== 'string' || (raw.phase !== 'rig' && raw.phase !== 'post') || typeof raw.bar !== 'string') continue;
119
+ const bar = effectiveBar(project, raw, now);
120
+ const text = bar.toLowerCase().replace(/\s+/g, ' ');
121
+ const phrase = APPEARANCE_IN_MOTION_PHRASES.find((p) => new RegExp(String.raw`\b${p}\b`).test(text));
122
+ if (phrase === undefined || hasNumericReading(bar)) continue;
123
+ const path = `plan.steps[${raw.id}].bar`;
124
+ const review = raw.phase === 'rig' ? 'rig-review' : 'direction-review';
125
+ warnings.push({
126
+ code: 'STEP_APPEARANCE_REVIEW', path,
127
+ message: `${path}: "${phrase}" has no numeric reading; move the appearance call to a person's ${review}, with the step's measured readings as context (docs/INTERVIEW.md § Appearance belongs to a person's review)`,
128
+ });
129
+ }
130
+ return warnings;
131
+ }
132
+
133
+ export interface Finding {
134
+ readonly code: string;
135
+ /** The object refused, as a path into project.json: `interview.waypoints[bones].guide`, `plan.steps[assemble]`, `signoffs[2]`, `stage`. */
136
+ readonly path: string;
137
+ readonly message: string;
138
+ }
139
+
140
+ export interface CheckReport {
141
+ readonly ok: boolean;
142
+ readonly project_dir: string;
143
+ readonly stage: string | null;
144
+ /** The stage the checks were applied as: `stage`, or for `paused` the first stage whose sign-off is missing. */
145
+ readonly effective_stage: string | null;
146
+ readonly parts: { version: string; read_from: string; dir: string } | null;
147
+ readonly derived: { waypoints: string[]; post_items: string[] } | null;
148
+ /** Current guide translation, not a rig check or a stored project field. */
149
+ readonly invariants: InvariantsDocument | null;
150
+ /** Per version built from a config: whether the config file was read to list its sections (docs/CHECKS.md § Provenance). */
151
+ readonly provenance: ProvenanceSummary[];
152
+ /** Per human bounced verdict: its items counted by cause, and the versions that answer it (docs/CHECKS.md § Polish feedback). */
153
+ readonly feedback: FeedbackSummary[];
154
+ /** Present only on amended records, preserving the report shape of older projects. */
155
+ readonly amendments?: AmendmentSummary[];
156
+ /** Per draft bound to a ledger report: the fix_ledger rows it fixes and its known regressions (docs/CHECKS.md § Fix ledger). Present only when one is. */
157
+ readonly ledger?: LedgerSummary[];
158
+ readonly refusals: Finding[];
159
+ readonly warnings: Finding[];
160
+ /** Local delivery gaps, distinct from record-validation refusals. */
161
+ readonly holes?: Finding[];
162
+ /** Present only with `recordReads`: every project file the check read, as `{path, sha256}` sorted by path (src/check-reads.ts). */
163
+ readonly reads?: readonly CheckRead[];
164
+ }
165
+
166
+ type Phase = 'rig' | 'post';
167
+
168
+ export type AnswerState =
169
+ | { state: 'silent'; via?: string }
170
+ | { state: 'user'; answer: JsonObject }
171
+ | { state: 'delegated'; answer: JsonObject; decided: boolean }
172
+ | { state: 'not_applicable'; answer: JsonObject }
173
+ | { state: 'malformed'; why: string }
174
+ | { state: 'unresolved'; why: string };
175
+
176
+ export interface Item {
177
+ readonly id: string;
178
+ readonly path: string;
179
+ readonly kind: 'framing' | 'waypoint' | 'post';
180
+ readonly record: JsonObject;
181
+ readonly raw: Json;
182
+ readonly state: AnswerState;
183
+ }
184
+
185
+ interface Signoff {
186
+ readonly index: number;
187
+ readonly stage: string;
188
+ readonly checkpoint: string | null;
189
+ readonly at: number;
190
+ /** For a plan re-sign: the steps re-approved; `null` means every step. */
191
+ readonly steps: string[] | null;
192
+ /** The record as written, for the fields one stage reads (`parts-review`'s `evidence`). */
193
+ readonly record: JsonObject;
194
+ }
195
+
196
+ interface Step {
197
+ readonly index: number;
198
+ readonly id: string;
199
+ readonly path: string;
200
+ readonly phase: Phase;
201
+ readonly record: JsonObject;
202
+ }
203
+
204
+ export interface Ctx {
205
+ readonly dir: string;
206
+ readonly project: JsonObject;
207
+ readonly refusals: Finding[];
208
+ readonly warnings: Finding[];
209
+ readonly items: Item[];
210
+ readonly signoffs: Signoff[];
211
+ readonly steps: Step[];
212
+ readonly stage: string;
213
+ readonly effectiveStage: string;
214
+ readonly collection: JsonObject | null;
215
+ /**
216
+ * Set when the project passed `plan-approved` before `parts` and
217
+ * `parts-review` existed (src/stages.ts `PARTS_REVIEW_SINCE`): the date of
218
+ * its first `plan-approved` sign-off. Such a project is warned about, not
219
+ * refused, and its parts steps' checkpoints may name `rigging`, as they were
220
+ * signed then.
221
+ */
222
+ readonly predatesPartsReview: string | null;
223
+ }
224
+
225
+ // ---------------------------------------------------------------- JSON access
226
+
227
+ export function isObject(v: Json | undefined): v is JsonObject {
228
+ return v !== null && v !== undefined && typeof v === 'object' && !Array.isArray(v);
229
+ }
230
+ export function asArray(v: Json | undefined): Json[] {
231
+ return Array.isArray(v) ? v : [];
232
+ }
233
+ export function asString(v: Json | undefined): string | null {
234
+ return typeof v === 'string' ? v : null;
235
+ }
236
+ function time(v: Json | undefined): number {
237
+ const s = asString(v);
238
+ if (s === null) return Number.NaN;
239
+ return Date.parse(s);
240
+ }
241
+
242
+ type ReadJson = { ok: true; value: JsonObject } | { ok: false; error: string };
243
+
244
+ export function readJsonObject(file: string): ReadJson {
245
+ let parsed: unknown;
246
+ try {
247
+ parsed = JSON.parse(readFileSync(file, 'utf8'));
248
+ } catch (e) {
249
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
250
+ }
251
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) return { ok: false, error: 'not a JSON object' };
252
+ return { ok: true, value: parsed as JsonObject };
253
+ }
254
+
255
+ // ------------------------------------------------------------- answer states
256
+
257
+ /** Looks an answer up in a collection's interview by question id (COLLECTIONS.md § What the collection owns). */
258
+ /** The question record a collection answer sits on (its `history[]` lives there), or undefined. */
259
+ export function collectionQuestion(collection: JsonObject, question: string): JsonObject | undefined {
260
+ const interview = isObject(collection.interview) ? collection.interview : {};
261
+ const post = isObject(interview.post) ? interview.post : {};
262
+ for (const list of [interview.questions, interview.framing, interview.waypoints, post.items]) {
263
+ for (const q of asArray(list)) if (isObject(q) && q.id === question) return q;
264
+ }
265
+ return undefined;
266
+ }
267
+
268
+ export function collectionAnswer(collection: JsonObject, question: string): Json | undefined {
269
+ const interview = isObject(collection.interview) ? collection.interview : {};
270
+ for (const q of asArray(interview.questions)) if (isObject(q) && q.id === question) return q.answer;
271
+ for (const q of asArray(interview.framing)) if (isObject(q) && q.id === question) return q.answer;
272
+ for (const w of asArray(interview.waypoints)) if (isObject(w) && w.id === question) return w.guide;
273
+ const post = isObject(interview.post) ? interview.post : {};
274
+ for (const p of asArray(post.items)) if (isObject(p) && p.id === question) return p.guide;
275
+ return undefined;
276
+ }
277
+
278
+ export function classify(raw: Json | undefined, ctx: Pick<Ctx, 'collection'>, depth = 0): AnswerState {
279
+ if (raw === null || raw === undefined) return { state: 'silent' };
280
+ if (!isObject(raw)) return { state: 'malformed', why: 'an answer is an object or null' };
281
+ if ('deferred' in raw) return { state: 'malformed', why: 'deferral belongs only to a collection-scope question; record a member answer or delegation' };
282
+ if (raw.basis === 'inherited') {
283
+ const from = isObject(raw.from) ? raw.from : null;
284
+ const collectionName = from === null ? null : asString(from.collection);
285
+ const question = from === null ? null : asString(from.question);
286
+ if (collectionName === null || question === null) return { state: 'malformed', why: 'inherited needs from.collection and from.question' };
287
+ if (ctx.collection === null) {
288
+ return { state: 'unresolved', why: `inherits from collection '${collectionName}', but this project is not under <collection>/members/<name>/ with a collection.json` };
289
+ }
290
+ if (ctx.collection.name !== collectionName) {
291
+ return { state: 'unresolved', why: `inherits from collection '${collectionName}'; the parent collection is '${String(ctx.collection.name)}'` };
292
+ }
293
+ const answer = collectionAnswer(ctx.collection, question);
294
+ if (answer === undefined) return { state: 'unresolved', why: `inherits '${question}', which the collection '${collectionName}' does not ask` };
295
+ if (depth > 2) return { state: 'unresolved', why: 'inheritance chain too deep' };
296
+ if (isObject(answer) && 'deferred' in answer) return { state: 'silent', via: `collection '${collectionName}' deferred '${question}'; this member must answer it` };
297
+ const resolved = classify(answer, { collection: null }, depth + 1);
298
+ if (resolved.state === 'silent') return { state: 'silent', via: `collection '${collectionName}' answer '${question}' is null` };
299
+ if (resolved.state === 'user' && resolved.answer.value === 'per-member') {
300
+ return { state: 'silent', via: `collection '${collectionName}' answered '${question}' per-member; this member must answer it` };
301
+ }
302
+ return resolved;
303
+ }
304
+ if (raw.not_applicable === true) {
305
+ if (asString(raw.because) === null || raw.because === '') return { state: 'malformed', why: 'not applicable needs a reason in `because`' };
306
+ return { state: 'not_applicable', answer: raw };
307
+ }
308
+ if (raw.delegated === true) {
309
+ return { state: 'delegated', answer: raw, decided: isDecision(raw.agent_decision) };
310
+ }
311
+ if (raw.delegated === false) {
312
+ if (!('value' in raw) || !('verbatim' in raw)) return { state: 'malformed', why: 'a user answer carries verbatim and value' };
313
+ return { state: 'user', answer: raw };
314
+ }
315
+ return { state: 'malformed', why: 'neither a user answer (delegated: false), a delegation (delegated: true) nor not applicable' };
316
+ }
317
+
318
+ /**
319
+ * A written agent decision: it carries its choice (`value`) and a non-empty
320
+ * `reasoning`. An empty object is not a decision.
321
+ */
322
+ export function isDecision(raw: Json | undefined): boolean {
323
+ return isObject(raw) && 'value' in raw && typeof raw.reasoning === 'string' && raw.reasoning.trim() !== '';
324
+ }
325
+
326
+ /**
327
+ * The moments an answer changed: when it was given, when the agent's decision
328
+ * on it (full or partial delegation) was written, and every addition. A
329
+ * decision changed after a sign-off is a change the sign-off never covered.
330
+ * NaN dates are skipped.
331
+ */
332
+ function changeTimes(state: AnswerState): number[] {
333
+ if (state.state !== 'user' && state.state !== 'delegated' && state.state !== 'not_applicable') return [];
334
+ const times = [time(state.answer.at)];
335
+ if (isObject(state.answer.agent_decision)) times.push(time(state.answer.agent_decision.at));
336
+ for (const add of asArray(state.answer.additions)) if (isObject(add)) times.push(time(add.at));
337
+ return times.filter((t) => !Number.isNaN(t));
338
+ }
339
+
340
+ export function answered(state: AnswerState): boolean {
341
+ return state.state === 'user' || state.state === 'delegated' || state.state === 'not_applicable';
342
+ }
343
+
344
+ // ------------------------------------------------------------------ loading
345
+
346
+ export function loadCollection(dir: string, project?: JsonObject): JsonObject | null {
347
+ return loadCollectionForDir(dir, project);
348
+ }
349
+
350
+ export function collectItems(project: JsonObject, ctx: Pick<Ctx, 'collection'>): Item[] {
351
+ const interview = isObject(project.interview) ? project.interview : {};
352
+ const items: Item[] = [];
353
+ for (const q of asArray(interview.framing)) {
354
+ if (!isObject(q) || asString(q.id) === null) continue;
355
+ const id = q.id as string;
356
+ items.push({ id, kind: 'framing', path: `interview.framing[${id}].answer`, record: q, raw: q.answer ?? null, state: classify(q.answer, ctx) });
357
+ }
358
+ for (const w of asArray(interview.waypoints)) {
359
+ if (!isObject(w) || asString(w.id) === null) continue;
360
+ const id = w.id as string;
361
+ items.push({ id, kind: 'waypoint', path: `interview.waypoints[${id}].guide`, record: w, raw: w.guide ?? null, state: classify(w.guide, ctx) });
362
+ }
363
+ const post = isObject(interview.post) ? interview.post : {};
364
+ for (const p of asArray(post.items)) {
365
+ if (!isObject(p) || asString(p.id) === null) continue;
366
+ const id = p.id as string;
367
+ items.push({ id, kind: 'post', path: `interview.post.items[${id}].guide`, record: p, raw: p.guide ?? null, state: classify(p.guide, ctx) });
368
+ }
369
+ return items;
370
+ }
371
+
372
+ function collectSteps(project: JsonObject, refusals: Finding[]): Step[] {
373
+ const plan = isObject(project.plan) ? project.plan : {};
374
+ const steps: Step[] = [];
375
+ asArray(plan.steps).forEach((s, index) => {
376
+ const path = `plan.steps[${index}]`;
377
+ if (!isObject(s) || asString(s.id) === null) {
378
+ refusals.push({ code: 'STEP_MALFORMED', path, message: `${path}: a step is an object with a string id` });
379
+ return;
380
+ }
381
+ const id = s.id as string;
382
+ if (s.phase !== 'rig' && s.phase !== 'post') {
383
+ refusals.push({ code: 'STEP_MALFORMED', path: `plan.steps[${id}]`, message: `plan.steps[${id}]: phase is 'rig' or 'post', got ${JSON.stringify(s.phase ?? null)}` });
384
+ return;
385
+ }
386
+ steps.push({ index, id, path: `plan.steps[${id}]`, phase: s.phase, record: s });
387
+ });
388
+ return steps;
389
+ }
390
+
391
+ export function collectSignoffs(project: JsonObject, refusals: Finding[]): Signoff[] {
392
+ const out: Signoff[] = [];
393
+ asArray(project.signoffs).forEach((s, index) => {
394
+ const path = `signoffs[${index}]`;
395
+ if (!isObject(s)) {
396
+ refusals.push({ code: 'SIGNOFF_MALFORMED', path, message: `${path}: not an object` });
397
+ return;
398
+ }
399
+ const stage = asString(s.stage);
400
+ const missing = ['stage', 'by', 'at', 'verbatim'].filter((k) => asString(s[k]) === null);
401
+ if (missing.length > 0) {
402
+ refusals.push({ code: 'SIGNOFF_MALFORMED', path, message: `${path}: missing ${missing.join(', ')} (a sign-off is stage, checkpoint, by, at, verbatim)` });
403
+ return;
404
+ }
405
+ if ((s.by as string).toLowerCase() === 'agent') {
406
+ refusals.push({ code: 'SIGNOFF_NOT_HUMAN', path, message: `${path} (stage ${stage}): signed by 'agent'; a stage moves only on a human sign-off` });
407
+ return;
408
+ }
409
+ const checkpoint = asString(s.checkpoint);
410
+ const steps = Array.isArray(s.steps) ? s.steps.filter((x): x is string => typeof x === 'string') : null;
411
+ out.push({ index, stage: stage as string, checkpoint, at: time(s.at), steps, record: s });
412
+ });
413
+ return out;
414
+ }
415
+
416
+ // ------------------------------------------------------------------- helpers
417
+
418
+ function signoffsFor(ctx: Ctx, stage: string): Signoff[] {
419
+ return ctx.signoffs.filter((s) => s.stage === stage && s.checkpoint === null);
420
+ }
421
+ function signed(ctx: Ctx, stage: string): boolean {
422
+ return signoffsFor(ctx, stage).length > 0;
423
+ }
424
+ function effectiveIndex(ctx: Ctx): number {
425
+ return stageIndex(ctx.effectiveStage);
426
+ }
427
+ function stepFor(ctx: Ctx, id: string): Step | undefined {
428
+ return ctx.steps.find((s) => s.id === id);
429
+ }
430
+ export function itemFor(ctx: Ctx, id: string): Item | undefined {
431
+ return ctx.items.find((i) => i.id === id);
432
+ }
433
+ /** Every post item answered not applicable — the project may skip `post` and `direction-review`. */
434
+ export function postNotPassed(ctx: Pick<Ctx, 'items'>): boolean {
435
+ const post = ctx.items.filter((i) => i.kind === 'post');
436
+ return post.length > 0 && post.every((i) => i.state.state === 'not_applicable');
437
+ }
438
+
439
+ /**
440
+ * Where a paused project resumes, and what its checks are applied as: the
441
+ * first stage closed by a sign-off that has none, or, before it, a `bar` stage
442
+ * that names its steps (`parts`) while one of them in the plan is not `done`.
443
+ * Amendments resume at unfinished bars only within an amended stage suffix.
444
+ * Ordinary paused records retain their previous resume behavior.
445
+ */
446
+ function firstUnsignedStage(signoffs: Signoff[], steps: Step[], predatesPartsReview: boolean, newOrder = false, amendedFrom = Infinity, noPost = false): string {
447
+ const amended = Number.isFinite(amendedFrom);
448
+ for (const s of newOrder ? STAGES : LEGACY_STAGES) {
449
+ if (amended && noPost && SKIPPED_WHEN_NO_POST.includes(s.id)) continue;
450
+ // A project that predates parts-review resumes where it would have before the two stages existed.
451
+ if (predatesPartsReview && (s.id === PARTS_REVIEW || s.id === 'parts')) continue;
452
+ const enforceBar = amended ? stageIndex(s.id) >= amendedFrom : s.steps !== undefined;
453
+ if (s.closed_by === 'bar' && enforceBar) {
454
+ if (steps.some((x) => (amended ? stageOfStep(x.phase, x.id) === s.id : s.steps?.includes(x.id)) && (asString(x.record.status) ?? 'pending') !== 'done')) return s.id;
455
+ continue;
456
+ }
457
+ if (s.closed_by === 'signoff' && !signoffs.some((x) => x.stage === s.id && x.checkpoint === null)) return s.id;
458
+ }
459
+ return STAGES[STAGES.length - 1]?.id ?? 'accepted';
460
+ }
461
+
462
+ /**
463
+ * The date of the project's first `plan-approved` sign-off when it is before
464
+ * `PARTS_REVIEW_SINCE`, else `null`. Read from the sign-off, not from
465
+ * `stage_values` or a schema mark: leaving `plan-approved` is what put a
466
+ * project past the place the two stages now stand, and that sign-off is the
467
+ * dated, human act that did it. An undated sign-off does not count.
468
+ */
469
+ function planApprovedBeforePartsReview(signoffs: Signoff[]): string | null {
470
+ const since = Date.parse(PARTS_REVIEW_SINCE);
471
+ const times = signoffs.filter((s) => s.stage === 'plan-approved' && s.checkpoint === null && !Number.isNaN(s.at)).map((s) => s.at);
472
+ if (times.length === 0) return null;
473
+ const first = Math.min(...times);
474
+ return first < since ? new Date(first).toISOString() : null;
475
+ }
476
+
477
+ /** The stages a step's checkpoint sign-off may name: its stage, and for a parts step of a project that predates `parts`, `rigging` as well. */
478
+ function checkpointStages(ctx: Ctx, step: Step): string[] {
479
+ const stage = stageOfStep(step.phase, step.id);
480
+ const legacy = stageOfStep(step.phase, '');
481
+ return ctx.predatesPartsReview !== null && stage !== legacy ? [stage, legacy] : [stage];
482
+ }
483
+
484
+ // -------------------------------------------------------------------- checks
485
+
486
+ function checkDerived(ctx: Ctx, parts: PartsSections, derived: DerivedSet): void {
487
+ const interview = isObject(ctx.project.interview) ? ctx.project.interview : {};
488
+ for (const section of derived.unplaced) {
489
+ ctx.refusals.push({
490
+ code: 'SECTION_UNPLACED',
491
+ path: `@rig-c/parts ${parts.version} section '${section}'`,
492
+ message: `@rig-c/parts ${parts.version} has section '${section}', which the placement table does not place; decide whether it is a waypoint, a post item or not a direction call (src/waypoints.ts, docs/INTERVIEW.md § Waypoints)`,
493
+ });
494
+ }
495
+ for (const section of derived.retired) {
496
+ ctx.warnings.push({ code: 'SECTION_RETIRED', path: `src/waypoints.ts '${section}'`, message: `the placement table places '${section}', which @rig-c/parts ${parts.version} no longer has` });
497
+ }
498
+ const questionSet = asString(interview.question_set) ?? '';
499
+ if (!questionSet.includes(`@rig-c/parts ${parts.version}`)) {
500
+ ctx.warnings.push({
501
+ code: 'QUESTION_SET_STALE',
502
+ path: 'interview.question_set',
503
+ message: `interview.question_set is ${JSON.stringify(questionSet)}; the installed @rig-c/parts is ${parts.version}`,
504
+ });
505
+ }
506
+ const recordWaypoints = ctx.items.filter((i) => i.kind === 'waypoint');
507
+ for (const d of derived.waypoints) {
508
+ const w = recordWaypoints.find((i) => i.id === d.id);
509
+ if (w === undefined) {
510
+ ctx.refusals.push({ code: 'WAYPOINT_MISSING', path: `interview.waypoints[${d.id}]`, message: `interview.waypoints has no '${d.id}', which @rig-c/parts ${parts.version} passes (sections ${d.config.join(', ')}); the question was never asked` });
511
+ continue;
512
+ }
513
+ const listed = asArray(w.record.config).filter((c): c is string => typeof c === 'string').sort();
514
+ const expected = [...d.config].sort();
515
+ if (listed.join(',') !== expected.join(',')) {
516
+ ctx.warnings.push({ code: 'WAYPOINT_CONFIG_STALE', path: `interview.waypoints[${d.id}].config`, message: `interview.waypoints[${d.id}].config lists [${listed.join(', ')}]; @rig-c/parts ${parts.version} places [${expected.join(', ')}] there` });
517
+ }
518
+ }
519
+ for (const w of recordWaypoints) {
520
+ if (!derived.waypoints.some((d) => d.id === w.id)) {
521
+ ctx.warnings.push({ code: 'WAYPOINT_UNKNOWN', path: `interview.waypoints[${w.id}]`, message: `interview.waypoints[${w.id}] is not derived from @rig-c/parts ${parts.version}; no section is placed there` });
522
+ }
523
+ }
524
+ const recordPost = ctx.items.filter((i) => i.kind === 'post');
525
+ for (const d of derived.post_items) {
526
+ if (!recordPost.some((i) => i.id === d.id)) {
527
+ ctx.refusals.push({ code: 'POST_ITEM_MISSING', path: `interview.post.items[${d.id}]`, message: `interview.post.items has no '${d.id}'${d.config.length > 0 ? ` (sections ${d.config.join(', ')})` : ''}` });
528
+ }
529
+ }
530
+ for (const id of FRAMING_IDS) {
531
+ if (!ctx.items.some((i) => i.kind === 'framing' && i.id === id)) {
532
+ ctx.refusals.push({ code: 'FRAMING_MISSING', path: `interview.framing[${id}]`, message: `interview.framing has no '${id}'` });
533
+ }
534
+ }
535
+ }
536
+
537
+ function checkAnswers(ctx: Ctx): void {
538
+ const cur = effectiveIndex(ctx);
539
+ const interviewIdx = stageIndex('interview');
540
+ const postInterviewIdx = stageIndex('post-interview');
541
+ for (const item of ctx.items) {
542
+ const { state, path } = item;
543
+ const interviewStage = interviewStageOfGuide(item.id, item.kind === 'post', hasStageOrder(ctx.project));
544
+ const ownInterview = stageIndex(interviewStage);
545
+ if (state.state === 'malformed') {
546
+ ctx.refusals.push({ code: 'ANSWER_MALFORMED', path, message: `${path}: ${state.why}` });
547
+ continue;
548
+ }
549
+ if (state.state === 'unresolved') {
550
+ ctx.refusals.push({ code: 'INHERITED_UNRESOLVED', path, message: `${path}: ${state.why}` });
551
+ continue;
552
+ }
553
+ if (state.state === 'silent') {
554
+ if (hasStageOrder(ctx.project) && cur < ownInterview) continue;
555
+ const via = state.via === undefined ? '' : ` (${state.via})`;
556
+ if (item.kind === 'post') {
557
+ if (cur < postInterviewIdx) continue;
558
+ if (cur === postInterviewIdx) {
559
+ ctx.refusals.push({ code: 'INTERVIEW_INCOMPLETE', path, message: `${path}: not answered yet${via}; the post interview is incomplete` });
560
+ } else {
561
+ ctx.refusals.push({ code: 'POST_ITEM_SILENT', path, message: `${path}: no guide and no delegation${via}, yet the stage is past post-interview` });
562
+ }
563
+ continue;
564
+ }
565
+ if (cur <= (hasStageOrder(ctx.project) ? ownInterview : interviewIdx)) {
566
+ ctx.refusals.push({ code: 'INTERVIEW_INCOMPLETE', path, message: `${path}: not answered yet${via}; the interview is incomplete` });
567
+ } else {
568
+ const code = item.kind === 'framing' ? 'FRAMING_SILENT' : 'WAYPOINT_SILENT';
569
+ ctx.refusals.push({ code, path, message: `${path}: no guide and no delegation${via}, yet the stage is past interview` });
570
+ }
571
+ continue;
572
+ }
573
+ if (item.kind !== 'framing' && state.state === 'user' && 'agent_decision' in state.answer) {
574
+ const decision = state.answer.agent_decision;
575
+ const covers = isObject(decision) ? asString(decision.covers) : null;
576
+ if (covers === null || covers.trim() === '') {
577
+ ctx.refusals.push({ code: 'GUIDE_PARTIAL_UNMARKED', path, message: `${path}: a partial guide's agent_decision needs nonempty covers naming the remainder delegated to the agent` });
578
+ }
579
+ const approvedBy = approvalStageOfGuide(item.id, item.kind === 'post', hasStageOrder(ctx.project));
580
+ if (signed(ctx, approvedBy) && !isDecision(decision)) {
581
+ ctx.refusals.push({ code: 'DELEGATION_UNDECIDED', path, message: `${path}: the partially delegated remainder needs agent_decision.value and nonempty reasoning before ${approvedBy} is signed` });
582
+ }
583
+ }
584
+ if (state.state === 'delegated' && !state.decided) {
585
+ const approvedBy = approvalStageOfGuide(item.id, item.kind === 'post', hasStageOrder(ctx.project));
586
+ if (signed(ctx, approvedBy)) {
587
+ ctx.refusals.push({ code: 'DELEGATION_UNDECIDED', path, message: `${path}: delegated, but agent_decision is missing and ${approvedBy} is signed; the agent's decision is written before the plan is signed` });
588
+ }
589
+ }
590
+ }
591
+ }
592
+
593
+ const curStageIndex = (ctx: Ctx): number => stageIndex(ctx.effectiveStage);
594
+
595
+ function checkPlan(ctx: Ctx): void {
596
+ const planSigned = signed(ctx, 'plan-approved') || signed(ctx, 'rigging-interview');
597
+ if (planSigned && ctx.steps.length === 0) {
598
+ ctx.refusals.push({ code: 'PLAN_EMPTY', path: 'plan.steps', message: 'plan-approved is signed, but plan.steps is empty' });
599
+ }
600
+ const recordWaypointIds = new Set(ctx.items.filter((i) => i.kind === 'waypoint').map((i) => i.id));
601
+ const seen = new Set<string>();
602
+ for (const step of ctx.steps) {
603
+ const { path, record } = step;
604
+ if (seen.has(step.id)) ctx.refusals.push({ code: 'STEP_MALFORMED', path, message: `${path}: id '${step.id}' is used by more than one step` });
605
+ seen.add(step.id);
606
+ if (step.phase === 'rig' && !recordWaypointIds.has(step.id)) {
607
+ ctx.refusals.push({ code: 'STEP_NOT_A_WAYPOINT', path, message: `${path}: a rig step's id is the waypoint's; '${step.id}' is no waypoint of this interview` });
608
+ }
609
+ const laterDraft = hasStageOrder(ctx.project) && stageIndex(stageOfStep(step.phase, step.id)) > curStageIndex(ctx)
610
+ && (record.status === undefined || record.status === 'pending');
611
+ const restsOn = asArray(record.rests_on).filter((x): x is string => typeof x === 'string');
612
+ if (restsOn.length === 0) {
613
+ ctx.refusals.push({ code: 'STEP_NO_BASIS', path, message: `${path}: rests_on is empty; a step never rests on nothing` });
614
+ }
615
+ for (const id of restsOn) {
616
+ const item = itemFor(ctx, id);
617
+ if (item === undefined) {
618
+ ctx.refusals.push({ code: 'STEP_BASIS_UNKNOWN', path, message: `${path}: rests on '${id}', which is no framing, waypoint or post item of this interview` });
619
+ } else if (!answered(item.state) && !laterDraft) {
620
+ ctx.refusals.push({ code: 'STEP_BASIS_SILENT', path, message: `${path}: rests on '${id}', which has no answer (${item.path} is ${item.state.state})` });
621
+ }
622
+ }
623
+ const approvedBy = approvalStageOfStep(step.phase, step.id, hasStageOrder(ctx.project));
624
+ const approvals = signoffsFor(ctx, approvedBy);
625
+ const status = record.status === undefined ? 'pending' : record.status;
626
+ if (typeof status !== 'string' || !['pending', 'in-progress', 'at-checkpoint', 'done'].includes(status)) {
627
+ ctx.refusals.push({ code: 'STEP_STATUS_UNKNOWN', path: `${path}.status`, message: `${path}.status: unknown status ${JSON.stringify(status)}; use pending before approval, then in-progress, at-checkpoint or done` });
628
+ continue;
629
+ }
630
+ const checkpoint = classify(record.checkpoint, { collection: null });
631
+ if (approvals.length > 0) {
632
+ if (checkpoint.state === 'silent') {
633
+ ctx.refusals.push({ code: 'CHECKPOINT_UNANSWERED', path: `${path}.checkpoint`, message: `${path}.checkpoint is null, yet ${approvedBy} is signed; the user sets each step's checkpoint (or delegates it) before signing` });
634
+ } else if (
635
+ checkpoint.state === 'delegated' &&
636
+ (!checkpoint.decided || typeof (checkpoint.answer.agent_decision as JsonObject).value !== 'boolean')
637
+ ) {
638
+ ctx.refusals.push({ code: 'DELEGATION_UNDECIDED', path: `${path}.checkpoint`, message: `${path}.checkpoint: delegated, but agent_decision has no boolean value and reasoning, and ${approvedBy} is signed` });
639
+ }
640
+ }
641
+ if (checkpoint.state === 'malformed') {
642
+ ctx.refusals.push({ code: 'ANSWER_MALFORMED', path: `${path}.checkpoint`, message: `${path}.checkpoint: ${checkpoint.why}` });
643
+ }
644
+ const approval = latestSignoff(ctx, approvedBy, step.id);
645
+ if (approval !== undefined) {
646
+ const hashes = approval.record.step_hashes;
647
+ const signoffPath = `signoffs[${approval.index}]`;
648
+ if (hashes === undefined) {
649
+ ctx.warnings.push({ code: 'STEP_SIGNOFF_HASH_MISSING', path, message: `${path}: covering ${signoffPath} has no step_hashes. Signed contents cannot be verified. Sign again to record the current step` });
650
+ } else if (!isObject(hashes) || hashes[step.id] !== stepHash(record)) {
651
+ ctx.refusals.push({ code: 'STEP_EDITED_AFTER_SIGNOFF', path, message: `${path}: current step does not match ${signoffPath}.step_hashes[${step.id}]. Please sign again at ${approvedBy} to approve this step` });
652
+ }
653
+ }
654
+ if (status === 'pending') continue;
655
+ if (hasStageOrder(ctx.project) && step.phase === 'rig' && stageOfStep(step.phase, step.id) === 'rigging' && !signed(ctx, 'rigging-interview')) {
656
+ ctx.refusals.push({ code: 'RIGGING_INTERVIEW_UNSIGNED', path, message: `${path}: production rigging requires rigging-interview approval; leave this step pending until the person signs` });
657
+ }
658
+ // The step has run (or is running): it needs the approving sign-off, given after every answer it rests on last changed.
659
+ if (approval === undefined) {
660
+ ctx.refusals.push({ code: 'STEP_RUN_UNAPPROVED', path, message: `${path} is ${status}, but no ${approvedBy} sign-off covers it; use pending before approval` });
661
+ } else {
662
+ const latestApproval = Number.isNaN(approval.at) ? Number.NEGATIVE_INFINITY : approval.at;
663
+ for (const id of restsOn) {
664
+ const item = itemFor(ctx, id);
665
+ if (item === undefined) continue;
666
+ const changed = Math.max(...changeTimes(item.state), Number.NEGATIVE_INFINITY);
667
+ if (changed > latestApproval) {
668
+ ctx.refusals.push({
669
+ code: 'GUIDE_CHANGED_UNSIGNED',
670
+ path,
671
+ message: `${path} rests on '${id}', which changed at ${new Date(changed).toISOString()}, after the last ${approvedBy} sign-off covering the step (${latestApproval === Number.NEGATIVE_INFINITY ? 'undated' : new Date(latestApproval).toISOString()}); a changed guide sends the step back for a new sign-off`,
672
+ });
673
+ }
674
+ }
675
+ }
676
+ const on = checkpointRequired(ctx.project, record);
677
+ if (on && status === 'done') {
678
+ const stages = checkpointStages(ctx, step);
679
+ const stage = stages[0] as string;
680
+ if (!ctx.signoffs.some((s) => stages.includes(s.stage) && s.checkpoint === step.id)) {
681
+ ctx.refusals.push({ code: 'CHECKPOINT_UNSIGNED', path, message: `${path} is done with its checkpoint on, but no { "stage": "${stage}", "checkpoint": "${step.id}" } sign-off exists` });
682
+ }
683
+ }
684
+ }
685
+ if (planSigned) {
686
+ for (const item of ctx.items) {
687
+ if (item.kind !== 'waypoint' || !signed(ctx, approvalStageOfStep('rig', item.id, hasStageOrder(ctx.project)))) continue;
688
+ if ((item.state.state === 'user' || item.state.state === 'delegated') && !ctx.steps.some((s) => s.phase === 'rig' && s.id === item.id)) {
689
+ ctx.refusals.push({ code: 'STEP_MISSING_FOR_WAYPOINT', path: `plan.steps[${item.id}]`, message: `waypoint '${item.id}' has a guide but the signed plan has no rig step '${item.id}'; one step per waypoint the plan passes` });
690
+ }
691
+ }
692
+ }
693
+ }
694
+
695
+ function checkStages(ctx: Ctx): void {
696
+ const cur = effectiveIndex(ctx);
697
+ const paused = ctx.stage === PAUSED_STAGE;
698
+ const signoffLimit = collectAmendments(ctx.project, []).length > 0 ? stageIndex(ctx.stage) : cur;
699
+ const plan = isObject(ctx.project.plan) ? ctx.project.plan : {};
700
+ const draftedAt = time(plan.drafted_at);
701
+ const riggingApproval = latestSignoff(ctx, 'rigging-interview');
702
+ if (hasStageOrder(ctx.project) && riggingApproval !== undefined && Number.isFinite(time(plan.rigging_drafted_at)) && riggingApproval.at < time(plan.rigging_drafted_at)) {
703
+ const path = `signoffs[${riggingApproval.index}]`;
704
+ ctx.refusals.push({ code: 'SIGNOFF_BEFORE_PLAN', path, message: `${path}: sign the rigging plan at or after plan.rigging_drafted_at` });
705
+ }
706
+ const approval = latestSignoff(ctx, 'plan-approved');
707
+ if (approval !== undefined && Number.isFinite(draftedAt) && !(approval.at >= draftedAt)) {
708
+ const path = `signoffs[${approval.index}]`;
709
+ ctx.refusals.push({ code: 'SIGNOFF_BEFORE_PLAN', path, message: `${path}: no plan-approved sign-off is at or after plan.drafted_at ${String(plan.drafted_at)}; sign the drafted plan before running its steps` });
710
+ }
711
+ for (const s of ctx.signoffs) {
712
+ const path = `signoffs[${s.index}]`;
713
+ const idx = stageIndex(s.stage);
714
+ if (idx === -1 && s.stage !== PAUSED_STAGE) {
715
+ ctx.refusals.push({ code: 'SIGNOFF_UNKNOWN_STAGE', path, message: `${path}: stage '${s.stage}' is not a stage (${STAGE_VALUES.join(', ')})` });
716
+ continue;
717
+ }
718
+ if (!paused && idx > signoffLimit) {
719
+ ctx.refusals.push({ code: 'SIGNOFF_AHEAD_OF_STAGE', path, message: `${path}: signs '${s.stage}'${s.checkpoint === null ? '' : ` checkpoint '${s.checkpoint}'`}, which is later than the project's stage '${ctx.stage}'` });
720
+ }
721
+ if (s.checkpoint !== null) {
722
+ const step = stepFor(ctx, s.checkpoint);
723
+ if (step === undefined || !checkpointStages(ctx, step).includes(s.stage)) {
724
+ ctx.refusals.push({ code: 'SIGNOFF_UNKNOWN_CHECKPOINT', path, message: `${path}: checkpoint '${s.checkpoint}' names no plan step of the '${s.stage}' phase` });
725
+ }
726
+ }
727
+ }
728
+ if (paused) {
729
+ if (!ctx.signoffs.some((s) => s.stage === PAUSED_STAGE)) {
730
+ ctx.refusals.push({ code: 'PAUSED_UNSIGNED', path: 'stage', message: `stage is paused, but no { "stage": "paused" } sign-off stopped it` });
731
+ }
732
+ }
733
+ const skip = postNotPassed(ctx) ? new Set(SKIPPED_WHEN_NO_POST) : new Set<string>();
734
+ if (ctx.predatesPartsReview !== null) skip.add(PARTS_REVIEW);
735
+ STAGES.forEach((spec, idx) => {
736
+ if (!hasStageOrder(ctx.project) && spec.id === 'rigging-interview') return;
737
+ if (idx >= cur || spec.closed_by !== 'signoff' || skip.has(spec.id)) return;
738
+ if (signed(ctx, spec.id)) return;
739
+ if (spec.id === 'rigging-interview') {
740
+ ctx.refusals.push({ code: 'RIGGING_INTERVIEW_UNSIGNED', path: 'stage', message: `stage: rigging requires the person's rigging-interview sign-off; ask the rig guides after parts review and sign their plan` });
741
+ return;
742
+ }
743
+ if (spec.id === PARTS_REVIEW) {
744
+ ctx.refusals.push({ code: 'PARTS_REVIEW_UNSIGNED', path: 'stage', message: `stage is '${ctx.stage}', past '${PARTS_REVIEW}', but no person signed { "stage": "${PARTS_REVIEW}", "checkpoint": null }; ${PARTS_REVIEW_REMEDY}` });
745
+ return;
746
+ }
747
+ ctx.refusals.push({ code: 'STAGE_UNSIGNED', path: 'stage', message: `stage is '${ctx.stage}', past '${spec.id}', but no { "stage": "${spec.id}", "checkpoint": null } sign-off closed it` });
748
+ });
749
+ checkPartsStages(ctx);
750
+ }
751
+
752
+ const PARTS_REVIEW_REMEDY =
753
+ 'rigging does not start until the user has looked at the assembled parts (rig-review\'s parts view) and signed them, or the stage goes back to parts-review';
754
+
755
+ /**
756
+ * The parts are made and reviewed before any rig is built (rig-c/create#48):
757
+ * past `parts`, every step it runs is `done` (its bar met); no step of
758
+ * `rigging` has run before `parts-review` is signed; and a project that passed
759
+ * `plan-approved` before the two stages existed is reported, not refused.
760
+ *
761
+ * Once rigging has started (the stage is past `parts-review`, or a rigging
762
+ * step is past `pending`) and until a rig version is accepted at rig-review
763
+ * (while the stage is at most `rigging`, or no rig version is accepted yet),
764
+ * the latest `parts-review` sign-off binds its retained API response (src/parts-verdict-record.ts). An in-flight
765
+ * project keeps the warning above and is not asked for one.
766
+ */
767
+ function checkPartsStages(ctx: Ctx): void {
768
+ const cur = effectiveIndex(ctx);
769
+ const partsIdx = stageIndex('parts');
770
+ const reviewIdx = stageIndex(PARTS_REVIEW);
771
+ if (ctx.predatesPartsReview !== null) {
772
+ if (cur > reviewIdx && !signed(ctx, PARTS_REVIEW)) {
773
+ const name = asString(ctx.project.name) ?? basename(ctx.dir);
774
+ ctx.warnings.push({
775
+ code: 'PARTS_REVIEW_PREDATES_STAGE',
776
+ path: 'signoffs',
777
+ message: `project '${name}' passed plan-approved at ${ctx.predatesPartsReview}, before the parts and parts-review stages existed (${PARTS_REVIEW_SINCE}); its parts were not reviewed before rigging, and no sign-off is assumed for them`,
778
+ });
779
+ }
780
+ // A legacy project may omit a map, but a map it does carry is checked in full.
781
+ if (latestSignoff(ctx, PARTS_REVIEW)?.record.diff_map !== undefined || ctx.project.diff_map !== undefined) checkPartsReviewEvidence(ctx);
782
+ return;
783
+ }
784
+ for (const step of ctx.steps) {
785
+ const stage = stageOfStep(step.phase, step.id);
786
+ const status = asString(step.record.status) ?? 'pending';
787
+ if (stage === 'parts' && cur > partsIdx && status !== 'done') {
788
+ ctx.refusals.push({ code: 'PARTS_BAR_UNMET', path: step.path, message: `${step.path} is ${status}, yet the stage is past 'parts'; the parts stage ends when every step it runs (${STAGES[partsIdx]?.steps?.join(', ') ?? ''}) has met its bar, the parts check passing on the assembled parts` });
789
+ }
790
+ if (stage === 'rigging' && cur <= reviewIdx && status !== 'pending' && !signed(ctx, PARTS_REVIEW)) {
791
+ ctx.refusals.push({ code: 'PARTS_REVIEW_UNSIGNED', path: step.path, message: `${step.path} is ${status}, but no person signed { "stage": "${PARTS_REVIEW}", "checkpoint": null }; ${PARTS_REVIEW_REMEDY}` });
792
+ }
793
+ }
794
+ checkPartsReviewEvidence(ctx);
795
+ }
796
+
797
+ function checkPartsReviewEvidence(ctx: Ctx): void {
798
+ const cur = effectiveIndex(ctx);
799
+ const reviewIdx = stageIndex(PARTS_REVIEW);
800
+ const riggingStarted =
801
+ cur > reviewIdx || ctx.steps.some((s) => stageOfStep(s.phase, s.id) === 'rigging' && (asString(s.record.status) ?? 'pending') !== 'pending');
802
+ // The parts gate rigging until a rig version is accepted at rig-review; after that a part change is a new rig
803
+ // version, which RIG_VERSION_UNACCEPTED refuses (src/review.ts), and derived/ need not survive a clone.
804
+ const gating = cur <= stageIndex('rigging') || latestAccepted(readVersions(ctx.project)) === null;
805
+ if (!riggingStarted || !gating) return;
806
+ const latest = latestSignoff(ctx, PARTS_REVIEW);
807
+ if (latest === undefined) return;
808
+ const at = `signoffs[${latest.index}]`;
809
+ let partsFile: string | undefined;
810
+ if (!recordedPartsFinding(ctx.dir, ctx.project, latest.record, at)) partsFile = recordedPartsVerdict(ctx.dir, ctx.project, latest.record.draft_id, latest.record.evidence, { requireAvailable: false }).partsFile;
811
+ checkDiffMap(ctx.dir, latest.record, at, partsFile, ctx.refusals, ctx.warnings, ctx.project);
812
+ }
813
+
814
+ /**
815
+ * Every parts-review signature, reopened ones included, binds a retained API receipt.
816
+ * Legacy saved-file bindings WARN without opening their files.
817
+ */
818
+ function checkPartsVerdictDrafts(ctx: Ctx, history: readonly Signoff[]): void {
819
+ for (const s of history) {
820
+ if (s.stage !== PARTS_REVIEW) continue;
821
+ const finding = recordedPartsFinding(ctx.dir, ctx.project, s.record, `signoffs[${s.index}]`);
822
+ if (finding) (finding.code === 'PARTS_VERDICT_FILE_LEGACY' ? ctx.warnings : ctx.refusals).push(finding);
823
+ }
824
+ }
825
+
826
+ /** The latest stage-level sign-off, optionally covering a step, by date then array position. Undated counts as earliest. */
827
+ function latestSignoff(ctx: Ctx, stage: string, stepId?: string): Signoff | undefined {
828
+ let best: Signoff | undefined;
829
+ for (const s of signoffsFor(ctx, stage)) {
830
+ if (stepId !== undefined && s.steps !== null && !s.steps.includes(stepId)) continue;
831
+ const at = Number.isNaN(s.at) ? -Infinity : s.at;
832
+ const bestAt = best === undefined || Number.isNaN(best.at) ? -Infinity : best.at;
833
+ if (best === undefined || at >= bestAt) best = s;
834
+ }
835
+ return best;
836
+ }
837
+
838
+ // ---------------------------------------------------------------------- entry
839
+
840
+ /**
841
+ * With a candidate record, validate the would-be write against the same project files without writing it.
842
+ * `options.now` is the instant the check runs at (ms since the epoch); see README.md "The clock".
843
+ * With `options.recordReads`, the report also lists every project file the check read (`reads`).
844
+ * `recordPublication` checks the decision record and its answer/approval evidence,
845
+ * without revalidating producer artifacts. Stage resolution and record predicates
846
+ * are shared; the default remains the full project check.
847
+ */
848
+ export function checkProject(projectDirArg: string, candidate: JsonObject | undefined, options: { now: number; forwardWrite?: boolean; readOnly?: boolean; recordReads?: boolean; recordPublication?: boolean }): CheckReport {
849
+ const run = (): CheckReport => candidate === undefined
850
+ ? checkProjectOnce(projectDirArg, candidate, options)
851
+ : withCandidateProject(projectDirArg, candidate, () => checkProjectOnce(projectDirArg, candidate, options));
852
+ if (!options.recordReads) return run();
853
+ const { value, reads } = recordCheckReads(projectDirArg, run);
854
+ return { ...value, reads };
855
+ }
856
+
857
+ function checkProjectOnce(projectDirArg: string, candidate: JsonObject | undefined, options: { now: number; forwardWrite?: boolean; readOnly?: boolean; recordPublication?: boolean }): CheckReport {
858
+ const dir = resolve(projectDirArg);
859
+ const refusals: Finding[] = [];
860
+ const warnings: Finding[] = [];
861
+ const holes = pendingPublicationHoles(dir, options.readOnly);
862
+ const fail = (partial: Partial<CheckReport> = {}): CheckReport => ({
863
+ ok: false,
864
+ project_dir: dir,
865
+ stage: null,
866
+ effective_stage: null,
867
+ parts: null,
868
+ derived: null,
869
+ invariants: null,
870
+ provenance: [],
871
+ feedback: [],
872
+ refusals,
873
+ warnings,
874
+ ...(holes.length ? { holes } : {}),
875
+ ...partial,
876
+ });
877
+
878
+ const file = join(dir, 'project.json');
879
+ if (candidate === undefined && !existsSync(file)) {
880
+ refusals.push({ code: 'PROJECT_JSON_MISSING', path: file, message: `${file} does not exist; run the check in a project folder or name one` });
881
+ return fail();
882
+ }
883
+ const read = candidate === undefined ? readJsonObject(file) : { ok: true as const, value: candidate };
884
+ if (!read.ok) {
885
+ refusals.push({ code: 'PROJECT_JSON_INVALID', path: file, message: `${file}: ${read.error}` });
886
+ return fail();
887
+ }
888
+ const project = read.value;
889
+ if (project.schema !== PROJECT_SCHEMA) {
890
+ refusals.push({ code: 'SCHEMA_UNKNOWN', path: 'schema', message: `schema is ${JSON.stringify(project.schema ?? null)}; this check reads ${PROJECT_SCHEMA}` });
891
+ return fail();
892
+ }
893
+
894
+ const stage = asString(project.stage);
895
+ if (stage === null || !STAGE_VALUES.includes(stage)) {
896
+ refusals.push({ code: 'STAGE_UNKNOWN', path: 'stage', message: `stage is ${JSON.stringify(project.stage ?? null)}; the stages are ${STAGE_VALUES.join(', ')}` });
897
+ return fail();
898
+ }
899
+ const invariants = translateInvariants(project);
900
+ const expectedValues = hasStageOrder(project) ? STAGE_VALUES : LEGACY_STAGE_VALUES;
901
+ const recordValues = asArray(project.stage_values).filter((x): x is string => typeof x === 'string');
902
+ if (recordValues.join(',') !== expectedValues.join(',')) {
903
+ warnings.push({ code: 'STAGE_VALUES_STALE', path: 'stage_values', message: `stage_values is [${recordValues.join(', ')}]; the order in src/stages.ts is [${expectedValues.join(', ')}]` });
904
+ }
905
+
906
+ refusals.push(...packageUpgradeFindings(project));
907
+ checkCollectionBinding(dir, project, refusals);
908
+ const collection = loadCollection(dir, project);
909
+ const reopens = collectReopens(project, refusals);
910
+ const historySignoffs = collectSignoffs(project, refusals).filter(s => {
911
+ if (STAGE_VALUES.includes(s.stage)) return true;
912
+ const path = `signoffs[${s.index}]`;
913
+ refusals.push({ code: 'SIGNOFF_UNKNOWN_STAGE', path, message: `${path}: unknown stage ${s.stage}; use a known stage` });
914
+ return false;
915
+ });
916
+ for (const s of historySignoffs) checkSignoffTarget(dir, project, s.record, `signoffs[${s.index}]`, refusals, warnings);
917
+ const signoffs = historySignoffs.filter(s => liveSignoff(reopens, s.stage, s.at));
918
+ const steps = collectSteps(project, refusals);
919
+ collectCheckpointWithdrawals(project, refusals, options.now);
920
+ collectBarAmendments(project, refusals, options.now);
921
+ const replaced = collectPlanReplaced(project, refusals);
922
+ checkReplacedSignoffs(project, replaced, refusals);
923
+ const previousSteps = collectSteps({ plan: { steps: replaced.map(entry => entry.step) } }, []);
924
+ const items = collectItems(project, { collection });
925
+ const planApprovedAt = planApprovedBeforePartsReview(historySignoffs);
926
+ // Superseded signatures grant no authority, but malformed checkpoint history is still reported.
927
+ for (const s of historySignoffs) {
928
+ if (liveSignoff(reopens, s.stage, s.at) || s.checkpoint === null) continue;
929
+ const found = [...steps, ...previousSteps].some(step => step.id === s.checkpoint
930
+ && (s.stage === stageOfStep(step.phase, step.id) || (planApprovedAt !== null && s.stage === stageOfStep(step.phase, ''))));
931
+ if (!found) {
932
+ const path = `signoffs[${s.index}]`;
933
+ refusals.push({ code: 'SIGNOFF_UNKNOWN_CHECKPOINT', path, message: `${path}: historical checkpoint ${s.checkpoint} names no step in ${s.stage}; restore the original signature` });
934
+ }
935
+ }
936
+ const amendments = collectAmendments(project, []);
937
+ const postItems = items.filter(i => i.kind === 'post');
938
+ const noPost = postItems.length > 0 && postItems.every(i => i.state.state === 'not_applicable');
939
+ const amendedFrom = Math.min(...amendments.map(a => stageIndex(a.stage)));
940
+ const resumeStage = firstUnsignedStage(signoffs, steps, planApprovedAt !== null, hasStageOrder(project), amendedFrom, noPost);
941
+ const effectiveStage = stage === PAUSED_STAGE ? resumeStage
942
+ : amendments.length > 0 && stageIndex(resumeStage) < stageIndex(stage) ? resumeStage : stage;
943
+ // Only a project already past `parts-review` when the stage arrived is exempt; one still at or before it goes through it.
944
+ const predatesPartsReview = planApprovedAt !== null && stageIndex(effectiveStage) > stageIndex(PARTS_REVIEW) ? planApprovedAt : null;
945
+ const ctx: Ctx = { dir, project, refusals, warnings, items, signoffs, steps, stage, effectiveStage, collection, predatesPartsReview };
946
+
947
+ for (const s of signoffs) checkSignoffAnswers(ctx, s.record, `signoffs[${s.index}]`);
948
+
949
+ let parts: PartsSections | null = null;
950
+ let derived: DerivedSet | null = null;
951
+ try {
952
+ parts = readPartsSections(dir);
953
+ derived = deriveWaypoints(parts.sections);
954
+ checkDerived(ctx, parts, derived);
955
+ } catch (e) {
956
+ if (!(e instanceof PartsUnreadable)) throw e;
957
+ refusals.push({ code: e.code, path: '@rig-c/parts', message: e.message });
958
+ }
959
+ if (!options.recordPublication) checkDrafts(dir, project, refusals, warnings);
960
+ const ledger = options.recordPublication ? [] : checkFixLedger(dir, project, refusals, warnings, options.now);
961
+ if (!options.recordPublication) refusals.push(...draftVersionFindings(dir, project));
962
+ checkRiggingContext(dir, project, effectiveStage, refusals, warnings);
963
+ if (!options.recordPublication) checkPartsVerdictDrafts(ctx, historySignoffs);
964
+ checkAdvances(ctx);
965
+ checkAnswers(ctx);
966
+ checkOverrides(project, collection, refusals, warnings);
967
+ checkAskCoverage(ctx);
968
+ if (!options.recordPublication) checkPreparedInputs(ctx);
969
+ checkAgentBounces(ctx);
970
+ checkPlan(ctx);
971
+ checkCheckpointEvidence(dir, project, refusals);
972
+ warnings.push(...appearanceBarWarnings(project, options.now));
973
+ checkStages(ctx);
974
+ checkStageProgress(ctx, options.forwardWrite);
975
+ checkAdvanceBars(ctx);
976
+ checkStepStatusHistory(ctx);
977
+ checkProgressLinkage(ctx);
978
+ checkPauses(ctx);
979
+ if (!options.recordPublication) checkPartsChecks(ctx, options.forwardWrite);
980
+ if (!options.recordPublication) checkPreInterview(ctx);
981
+ if (!options.recordPublication) checkReviews(ctx);
982
+ if (!options.recordPublication) checkPublicationGenerations(dir, project, refusals);
983
+ if (!options.recordPublication) checkReviewPackages(ctx);
984
+ if (!options.recordPublication) refusals.push(...rigRegistrationFindings(dir, project, warnings));
985
+ if (!options.recordPublication) checkAppliedReview(ctx);
986
+ const provenance = options.recordPublication ? [] : checkProvenance(ctx, parts);
987
+ const feedback = options.recordPublication ? [] : checkFeedback(ctx);
988
+ refusals.push(...reviewActFindings(project), ...partsActFindings(dir, project), ...(options.recordPublication ? [] : partsReadFindings(dir, project)));
989
+ for (const [index, raw] of asArray(project.signoffs).entries()) {
990
+ if (!isObject(raw) || raw.stage !== 'parts-review' || typeof raw.draft_id !== 'string') continue;
991
+ const finding = latestPartsVerdictFinding(project, raw.draft_id, `signoffs[${index}].draft_id`);
992
+ if (finding) (typeof raw.at === 'string' && Date.parse(raw.at) < Date.parse(PARTS_VERDICT_LATEST_SINCE) ? warnings : refusals).push(finding);
993
+ }
994
+ checkCgGates(ctx, options.recordPublication);
995
+ if (!options.recordPublication) checkSetupPainting(ctx);
996
+ if (!options.recordPublication) checkFigureMask(ctx);
997
+ checkMeshesBust(ctx);
998
+ if (!hasStageOrder(project)) warnings.push(STAGE_ORDER_LEGACY_FINDING);
999
+ checkInputs(ctx);
1000
+ checkChoices(ctx);
1001
+ if (!options.recordPublication) checkCorrections(ctx, options.now);
1002
+ if (!options.recordPublication) checkScenes(ctx);
1003
+ if (!options.recordPublication) checkDirections(ctx, options.now);
1004
+ if (!options.recordPublication) warnings.push(...intakeWarnings(dir, project));
1005
+ warnings.push(...reopenLapseWarnings(project));
1006
+
1007
+ return {
1008
+ ok: refusals.length === 0 && holes.length === 0,
1009
+ project_dir: dir,
1010
+ stage,
1011
+ effective_stage: effectiveStage,
1012
+ parts: parts === null ? null : { version: parts.version, read_from: parts.read_from, dir: parts.dir },
1013
+ derived: derived === null ? null : { waypoints: derived.waypoints.map((w) => w.id), post_items: derived.post_items.map((p) => p.id) },
1014
+ invariants,
1015
+ provenance,
1016
+ feedback,
1017
+ ...(amendments.length === 0 ? {} : { amendments: amendmentSummaries(project, amendments, reopens) }),
1018
+ ...(ledger.length === 0 ? {} : { ledger }),
1019
+ refusals,
1020
+ warnings,
1021
+ ...(holes.length ? { holes } : {}),
1022
+ };
1023
+ }
1024
+
1025
+ /** The human-readable report: one line per finding, then a summary. */
1026
+ export function formatReport(report: CheckReport): string {
1027
+ const lines = [`interview --check: ${report.project_dir}`];
1028
+ if (report.parts !== null && report.derived !== null) {
1029
+ lines.push(
1030
+ ` @rig-c/parts ${report.parts.version} (${report.parts.read_from}): ${report.derived.waypoints.length} waypoints (${report.derived.waypoints.join(', ')}), ${report.derived.post_items.length} post items`,
1031
+ );
1032
+ }
1033
+ if (report.stage !== null) {
1034
+ lines.push(` stage ${report.stage}${report.effective_stage !== report.stage ? ` (checked as ${report.effective_stage})` : ''}`);
1035
+ }
1036
+ lines.push(...formatProvenance(report.provenance));
1037
+ lines.push(...formatFeedback(report.feedback));
1038
+ for (const amendment of report.amendments ?? []) {
1039
+ lines.push(` ${amendment.path} ${String(amendment.target.stage)} ${String(amendment.target.kind)} ${String(amendment.target.id)}`);
1040
+ lines.push(` lapsed (${amendment.lapsed.length})`);
1041
+ for (const a of amendment.lapsed) lines.push(` ${a.path} ${a.stage}${a.checkpoint === undefined ? '' : ` checkpoint ${a.checkpoint}`}: ${a.reason} (${a.gap})`);
1042
+ lines.push(` upstream live (${amendment.upstream.length})`);
1043
+ for (const a of amendment.upstream) lines.push(` ${a.path} ${a.stage}: ${a.reason}`);
1044
+ lines.push(` keep-by-binding (${amendment.keep_by_binding.length})`);
1045
+ }
1046
+ for (const entry of report.ledger ?? []) {
1047
+ lines.push(` drafts[${entry.draft}] fixes [${entry.fixes.join(', ')}]${entry.known_regressions.length ? `; known regressions [${entry.known_regressions.join(', ')}]` : ''}`);
1048
+ }
1049
+ for (const r of report.refusals) lines.push(` REFUSED ${r.code}: ${r.message}`);
1050
+ for (const w of report.warnings) lines.push(` warning ${w.code}: ${w.message}`);
1051
+ for (const hole of report.holes ?? []) lines.push(` HOLE ${hole.code}: ${hole.message}`);
1052
+ const n = (count: number, word: string): string => `${count} ${word}${count === 1 ? '' : 's'}`;
1053
+ lines.push(
1054
+ report.ok
1055
+ ? `clean: ${n(report.warnings.length, 'warning')}; the record is complete for stage ${report.stage}`
1056
+ : `not clear: ${n(report.refusals.length, 'refusal')}, ${n(report.warnings.length, 'warning')}${report.holes?.length ? `, ${n(report.holes.length, 'hole')}` : ''}`,
1057
+ );
1058
+ return lines.join('\n');
1059
+ }