rig-create 0.0.0-stage → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (555) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +169 -2
  3. package/docs/CHECKS.md +1682 -0
  4. package/docs/COLLECTIONS.md +337 -0
  5. package/docs/CRAFT.md +277 -0
  6. package/docs/INTERVIEW.md +886 -0
  7. package/docs/LESSONS.md +172 -0
  8. package/docs/PROJECT.md +2657 -0
  9. package/docs/SOURCE.md +225 -0
  10. package/package.json +44 -4
  11. package/src/agent-bounce.ts +27 -0
  12. package/src/agent-guide.ts +237 -0
  13. package/src/amend.ts +193 -0
  14. package/src/applied-review.ts +104 -0
  15. package/src/ask-coverage.ts +69 -0
  16. package/src/assemble-answers.ts +53 -0
  17. package/src/cg-gates.ts +211 -0
  18. package/src/check-reads.ts +106 -0
  19. package/src/check.ts +1059 -0
  20. package/src/checkpoint-evidence-writer.ts +125 -0
  21. package/src/checkpoint-evidence.ts +152 -0
  22. package/src/choice-writers.ts +271 -0
  23. package/src/choices.ts +378 -0
  24. package/src/cli.ts +371 -0
  25. package/src/collection-answer.ts +136 -0
  26. package/src/collection-binding-reader.ts +496 -0
  27. package/src/collection-binding.ts +478 -0
  28. package/src/collection.ts +348 -0
  29. package/src/config-inputs.ts +155 -0
  30. package/src/config-lost.ts +135 -0
  31. package/src/correction-writers.ts +207 -0
  32. package/src/corrections.ts +685 -0
  33. package/src/diff-classes.ts +219 -0
  34. package/src/diff-map.ts +248 -0
  35. package/src/direction-citation.ts +13 -0
  36. package/src/direction-writers.ts +119 -0
  37. package/src/direction.ts +266 -0
  38. package/src/drafts.ts +363 -0
  39. package/src/feedback.ts +744 -0
  40. package/src/figure-mask.ts +143 -0
  41. package/src/fix-ledger.ts +355 -0
  42. package/src/init.ts +265 -0
  43. package/src/inputs.ts +160 -0
  44. package/src/intake.ts +158 -0
  45. package/src/interview.ts +53 -0
  46. package/src/invariants.ts +132 -0
  47. package/src/job-acts.ts +80 -0
  48. package/src/job-advance.ts +65 -0
  49. package/src/job-close.ts +226 -0
  50. package/src/job-host.ts +529 -0
  51. package/src/job-outbox.ts +383 -0
  52. package/src/job-publication.ts +701 -0
  53. package/src/job-runtime.ts +99 -0
  54. package/src/job-writer.ts +123 -0
  55. package/src/meshes-bust.ts +63 -0
  56. package/src/next.ts +1506 -0
  57. package/src/overrides.ts +42 -0
  58. package/src/package-upgrades.ts +32 -0
  59. package/src/packages-upgrade-check.ts +6 -0
  60. package/src/packages-upgrade.ts +168 -0
  61. package/src/parts-act.ts +96 -0
  62. package/src/parts-check.ts +242 -0
  63. package/src/parts-review.ts +196 -0
  64. package/src/parts-sections.ts +98 -0
  65. package/src/parts-verdict-record.ts +265 -0
  66. package/src/parts-verdict.ts +33 -0
  67. package/src/pause.ts +118 -0
  68. package/src/pins-check.ts +619 -0
  69. package/src/pins.ts +17 -0
  70. package/src/plan-bar-amendments.ts +54 -0
  71. package/src/plan-history.ts +147 -0
  72. package/src/plan-writers.ts +202 -0
  73. package/src/plate-receipt.ts +110 -0
  74. package/src/pre-interview.ts +302 -0
  75. package/src/prepared-inputs.ts +113 -0
  76. package/src/progress-writers.ts +181 -0
  77. package/src/provenance.ts +301 -0
  78. package/src/record-direction-target.ts +26 -0
  79. package/src/record-input.ts +18 -0
  80. package/src/refusal-baseline.ts +55 -0
  81. package/src/refusal.ts +30 -0
  82. package/src/reopen-lapses.ts +80 -0
  83. package/src/reopen.ts +144 -0
  84. package/src/republish.ts +93 -0
  85. package/src/restart.ts +141 -0
  86. package/src/review-act-runtime.ts +16 -0
  87. package/src/review-act.ts +592 -0
  88. package/src/review-package.ts +524 -0
  89. package/src/review-read.ts +238 -0
  90. package/src/review.ts +432 -0
  91. package/src/rig-version-date.ts +22 -0
  92. package/src/rig-version.ts +1047 -0
  93. package/src/run-record.ts +232 -0
  94. package/src/scene-authority.ts +410 -0
  95. package/src/scene-writers.ts +144 -0
  96. package/src/scene.ts +284 -0
  97. package/src/setup-painting.ts +46 -0
  98. package/src/signable.ts +65 -0
  99. package/src/signoff-answers.ts +76 -0
  100. package/src/signoff-checkpoint-act.ts +95 -0
  101. package/src/signoff-target.ts +127 -0
  102. package/src/signoff.ts +331 -0
  103. package/src/split-captions.ts +154 -0
  104. package/src/stage-progress.ts +379 -0
  105. package/src/stages.ts +148 -0
  106. package/src/step-hash.ts +19 -0
  107. package/src/structure-snapshot.ts +109 -0
  108. package/src/structure.ts +166 -0
  109. package/src/templates.ts +16 -0
  110. package/src/waypoints.ts +163 -0
  111. package/templates/agent-guide.md +108 -0
  112. package/templates/collection-agent-guide.md +72 -0
  113. package/templates/collection.json +252 -0
  114. package/templates/example-chain/AGENTS.md +134 -0
  115. package/templates/example-chain/CLAUDE.md +3 -0
  116. package/templates/example-chain/README.md +13 -0
  117. package/templates/example-chain/chain.json +2869 -0
  118. package/templates/example-chain/checkpoints/0a-interview-options/AGENTS.md +134 -0
  119. package/templates/example-chain/checkpoints/0a-interview-options/CLAUDE.md +3 -0
  120. package/templates/example-chain/checkpoints/0a-interview-options/evidence/.gitkeep +0 -0
  121. package/templates/example-chain/checkpoints/0a-interview-options/evidence/approval.txt +1 -0
  122. package/templates/example-chain/checkpoints/0a-interview-options/evidence/interview/rig_type.json +59 -0
  123. package/templates/example-chain/checkpoints/0a-interview-options/evidence/split-config.json +1 -0
  124. package/templates/example-chain/checkpoints/0a-interview-options/inputs/painting.png +0 -0
  125. package/templates/example-chain/checkpoints/0a-interview-options/inputs/references/.gitkeep +0 -0
  126. package/templates/example-chain/checkpoints/0a-interview-options/package.json +9 -0
  127. package/templates/example-chain/checkpoints/0a-interview-options/project.json +295 -0
  128. package/templates/example-chain/checkpoints/0a-interview-options.json +127 -0
  129. package/templates/example-chain/checkpoints/0b-plan-pending/AGENTS.md +134 -0
  130. package/templates/example-chain/checkpoints/0b-plan-pending/CLAUDE.md +3 -0
  131. package/templates/example-chain/checkpoints/0b-plan-pending/evidence/.gitkeep +0 -0
  132. package/templates/example-chain/checkpoints/0b-plan-pending/evidence/approval.txt +1 -0
  133. package/templates/example-chain/checkpoints/0b-plan-pending/evidence/interview/assemble.json +309 -0
  134. package/templates/example-chain/checkpoints/0b-plan-pending/evidence/interview/reference.json +59 -0
  135. package/templates/example-chain/checkpoints/0b-plan-pending/evidence/interview/rig_type.json +59 -0
  136. package/templates/example-chain/checkpoints/0b-plan-pending/evidence/interview/seethrough.json +186 -0
  137. package/templates/example-chain/checkpoints/0b-plan-pending/evidence/split-config.json +1 -0
  138. package/templates/example-chain/checkpoints/0b-plan-pending/inputs/painting.png +0 -0
  139. package/templates/example-chain/checkpoints/0b-plan-pending/inputs/references/.gitkeep +0 -0
  140. package/templates/example-chain/checkpoints/0b-plan-pending/package.json +9 -0
  141. package/templates/example-chain/checkpoints/0b-plan-pending/project.json +685 -0
  142. package/templates/example-chain/checkpoints/0b-plan-pending.json +81 -0
  143. package/templates/example-chain/checkpoints/1-parts-verdict/AGENTS.md +134 -0
  144. package/templates/example-chain/checkpoints/1-parts-verdict/CLAUDE.md +3 -0
  145. package/templates/example-chain/checkpoints/1-parts-verdict/evidence/.gitkeep +0 -0
  146. package/templates/example-chain/checkpoints/1-parts-verdict/evidence/approval.txt +1 -0
  147. package/templates/example-chain/checkpoints/1-parts-verdict/evidence/interview/assemble.json +309 -0
  148. package/templates/example-chain/checkpoints/1-parts-verdict/evidence/interview/reference.json +59 -0
  149. package/templates/example-chain/checkpoints/1-parts-verdict/evidence/interview/rig_type.json +59 -0
  150. package/templates/example-chain/checkpoints/1-parts-verdict/evidence/interview/seethrough.json +186 -0
  151. package/templates/example-chain/checkpoints/1-parts-verdict/evidence/parts-check.json +127 -0
  152. package/templates/example-chain/checkpoints/1-parts-verdict/evidence/split-config.json +1 -0
  153. package/templates/example-chain/checkpoints/1-parts-verdict/inputs/painting.png +0 -0
  154. package/templates/example-chain/checkpoints/1-parts-verdict/inputs/references/.gitkeep +0 -0
  155. package/templates/example-chain/checkpoints/1-parts-verdict/package.json +9 -0
  156. package/templates/example-chain/checkpoints/1-parts-verdict/project.json +955 -0
  157. package/templates/example-chain/checkpoints/1-parts-verdict.json +204 -0
  158. package/templates/example-chain/checkpoints/2-rigging-answers/AGENTS.md +134 -0
  159. package/templates/example-chain/checkpoints/2-rigging-answers/CLAUDE.md +3 -0
  160. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/.gitkeep +0 -0
  161. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/approval.txt +1 -0
  162. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/diff-components.json +34 -0
  163. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/interview/assemble.json +309 -0
  164. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/interview/bones.json +190 -0
  165. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/interview/reference.json +59 -0
  166. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/interview/rig_type.json +59 -0
  167. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/interview/seethrough.json +186 -0
  168. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/parts-check.json +127 -0
  169. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/parts-review.json +88 -0
  170. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/review-reads/a12dea02d32508acc99386f894af76a14f1c85f45f804db2aa6b4dea5297973f.json +59 -0
  171. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/split-config.json +1 -0
  172. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/structure.json +30 -0
  173. package/templates/example-chain/checkpoints/2-rigging-answers/inputs/painting.png +0 -0
  174. package/templates/example-chain/checkpoints/2-rigging-answers/inputs/references/.gitkeep +0 -0
  175. package/templates/example-chain/checkpoints/2-rigging-answers/package.json +9 -0
  176. package/templates/example-chain/checkpoints/2-rigging-answers/project.json +1198 -0
  177. package/templates/example-chain/checkpoints/2-rigging-answers.json +254 -0
  178. package/templates/example-chain/checkpoints/3-rig-review/AGENTS.md +134 -0
  179. package/templates/example-chain/checkpoints/3-rig-review/CLAUDE.md +3 -0
  180. package/templates/example-chain/checkpoints/3-rig-review/evidence/.gitkeep +0 -0
  181. package/templates/example-chain/checkpoints/3-rig-review/evidence/approval.txt +1 -0
  182. package/templates/example-chain/checkpoints/3-rig-review/evidence/diff-components.json +34 -0
  183. package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/assemble.json +309 -0
  184. package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/bones.json +190 -0
  185. package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/constraints.json +272 -0
  186. package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/meshes.json +190 -0
  187. package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/motion.json +272 -0
  188. package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/reference.json +59 -0
  189. package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/rig_type.json +59 -0
  190. package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/seethrough.json +186 -0
  191. package/templates/example-chain/checkpoints/3-rig-review/evidence/parts-check.json +127 -0
  192. package/templates/example-chain/checkpoints/3-rig-review/evidence/parts-review.json +88 -0
  193. package/templates/example-chain/checkpoints/3-rig-review/evidence/review-reads/a12dea02d32508acc99386f894af76a14f1c85f45f804db2aa6b4dea5297973f.json +59 -0
  194. package/templates/example-chain/checkpoints/3-rig-review/evidence/setup.png +0 -0
  195. package/templates/example-chain/checkpoints/3-rig-review/evidence/split-config.json +1 -0
  196. package/templates/example-chain/checkpoints/3-rig-review/evidence/structure.json +30 -0
  197. package/templates/example-chain/checkpoints/3-rig-review/evidence/v1/config.json +88 -0
  198. package/templates/example-chain/checkpoints/3-rig-review/evidence/v1/interview-requirements.json +8 -0
  199. package/templates/example-chain/checkpoints/3-rig-review/inputs/painting.png +0 -0
  200. package/templates/example-chain/checkpoints/3-rig-review/inputs/references/.gitkeep +0 -0
  201. package/templates/example-chain/checkpoints/3-rig-review/package.json +9 -0
  202. package/templates/example-chain/checkpoints/3-rig-review/project.json +1909 -0
  203. package/templates/example-chain/checkpoints/3-rig-review.json +424 -0
  204. package/templates/example-chain/checkpoints/4-direction-post/AGENTS.md +134 -0
  205. package/templates/example-chain/checkpoints/4-direction-post/CLAUDE.md +3 -0
  206. package/templates/example-chain/checkpoints/4-direction-post/evidence/.gitkeep +0 -0
  207. package/templates/example-chain/checkpoints/4-direction-post/evidence/approval.txt +1 -0
  208. package/templates/example-chain/checkpoints/4-direction-post/evidence/diff-components.json +34 -0
  209. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/assemble.json +309 -0
  210. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/bones.json +190 -0
  211. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/camera_depth.json +102 -0
  212. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/constraints.json +272 -0
  213. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/meshes.json +190 -0
  214. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/motion.json +272 -0
  215. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/post_composition.json +122 -0
  216. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/post_timing.json +102 -0
  217. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/reference.json +59 -0
  218. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/rig_type.json +59 -0
  219. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/seethrough.json +186 -0
  220. package/templates/example-chain/checkpoints/4-direction-post/evidence/parts-check.json +127 -0
  221. package/templates/example-chain/checkpoints/4-direction-post/evidence/parts-review.json +88 -0
  222. package/templates/example-chain/checkpoints/4-direction-post/evidence/review-reads/a12dea02d32508acc99386f894af76a14f1c85f45f804db2aa6b4dea5297973f.json +59 -0
  223. package/templates/example-chain/checkpoints/4-direction-post/evidence/setup.png +0 -0
  224. package/templates/example-chain/checkpoints/4-direction-post/evidence/split-config.json +1 -0
  225. package/templates/example-chain/checkpoints/4-direction-post/evidence/structure.json +30 -0
  226. package/templates/example-chain/checkpoints/4-direction-post/evidence/v1/config.json +88 -0
  227. package/templates/example-chain/checkpoints/4-direction-post/evidence/v1/interview-requirements.json +8 -0
  228. package/templates/example-chain/checkpoints/4-direction-post/inputs/painting.png +0 -0
  229. package/templates/example-chain/checkpoints/4-direction-post/inputs/references/.gitkeep +0 -0
  230. package/templates/example-chain/checkpoints/4-direction-post/package.json +9 -0
  231. package/templates/example-chain/checkpoints/4-direction-post/project.json +2302 -0
  232. package/templates/example-chain/checkpoints/4-direction-post.json +456 -0
  233. package/templates/example-chain/evidence/.gitkeep +0 -0
  234. package/templates/example-chain/evidence/approval.txt +1 -0
  235. package/templates/example-chain/evidence/captions.json +101 -0
  236. package/templates/example-chain/evidence/diff-components.json +34 -0
  237. package/templates/example-chain/evidence/direction/v2.json +74 -0
  238. package/templates/example-chain/evidence/direction-input.json +74 -0
  239. package/templates/example-chain/evidence/interview/assemble.json +309 -0
  240. package/templates/example-chain/evidence/interview/bones.json +190 -0
  241. package/templates/example-chain/evidence/interview/camera_depth.json +102 -0
  242. package/templates/example-chain/evidence/interview/constraints.json +272 -0
  243. package/templates/example-chain/evidence/interview/meshes.json +190 -0
  244. package/templates/example-chain/evidence/interview/motion.json +272 -0
  245. package/templates/example-chain/evidence/interview/post_composition.json +122 -0
  246. package/templates/example-chain/evidence/interview/post_timing.json +102 -0
  247. package/templates/example-chain/evidence/interview/reference.json +59 -0
  248. package/templates/example-chain/evidence/interview/rig_type.json +59 -0
  249. package/templates/example-chain/evidence/interview/seethrough.json +186 -0
  250. package/templates/example-chain/evidence/parts-check.json +127 -0
  251. package/templates/example-chain/evidence/parts-review.json +88 -0
  252. package/templates/example-chain/evidence/review-reads/a12dea02d32508acc99386f894af76a14f1c85f45f804db2aa6b4dea5297973f.json +59 -0
  253. package/templates/example-chain/evidence/scene/s1.json +65 -0
  254. package/templates/example-chain/evidence/scene-citations.json +9 -0
  255. package/templates/example-chain/evidence/setup.png +0 -0
  256. package/templates/example-chain/evidence/split-config.json +1 -0
  257. package/templates/example-chain/evidence/structure.json +30 -0
  258. package/templates/example-chain/evidence/v1/config.json +88 -0
  259. package/templates/example-chain/evidence/v1/interview-requirements.json +8 -0
  260. package/templates/example-chain/inputs/background.png +0 -0
  261. package/templates/example-chain/inputs/painting.png +0 -0
  262. package/templates/example-chain/inputs/references/.gitkeep +0 -0
  263. package/templates/example-chain/package.json +9 -0
  264. package/templates/example-chain/project.json +2593 -0
  265. package/templates/example-diff/diff-components.json +71 -0
  266. package/templates/example-diff/recomposite_rig.png +0 -0
  267. package/templates/example-direction/approval.txt +1 -0
  268. package/templates/example-direction/derived/review/v1/out/check/build/rectangle.png +0 -0
  269. package/templates/example-direction/derived/review/v1/out/check/build/run.json +40 -0
  270. package/templates/example-direction/derived/review/v1/out/check/build/skeleton.atlas +8 -0
  271. package/templates/example-direction/derived/review/v1/out/check/build/skeleton.json +73 -0
  272. package/templates/example-direction/derived/review/v1/out/check/build/skeleton.model.json +195 -0
  273. package/templates/example-direction/evidence/direction/v2.json +69 -0
  274. package/templates/example-direction/evidence/direction/v3.json +59 -0
  275. package/templates/example-direction/evidence/images/rectangle.png +0 -0
  276. package/templates/example-direction/evidence/input.json +69 -0
  277. package/templates/example-direction/evidence/motion.json +33 -0
  278. package/templates/example-direction/evidence/patch.json +23 -0
  279. package/templates/example-direction/evidence/rig-config.json +55 -0
  280. package/templates/example-direction/evidence/still.png +0 -0
  281. package/templates/example-direction/project.json +372 -0
  282. package/templates/example-direction/reasoning.txt +1 -0
  283. package/templates/example-direction/words.txt +1 -0
  284. package/templates/example-draft/README.md +28 -0
  285. package/templates/example-draft/config.json +71 -0
  286. package/templates/example-draft/derived/drafts/lantern/config.json +71 -0
  287. package/templates/example-draft/derived/drafts/lantern/input/painting_in_rig.png +0 -0
  288. package/templates/example-draft/derived/drafts/lantern/input/parts/background.png +0 -0
  289. package/templates/example-draft/derived/drafts/lantern/input/parts/eye_l.png +0 -0
  290. package/templates/example-draft/derived/drafts/lantern/input/parts/eye_r.png +0 -0
  291. package/templates/example-draft/derived/drafts/lantern/input/parts/face.png +0 -0
  292. package/templates/example-draft/derived/drafts/lantern/input/parts/lantern.png +0 -0
  293. package/templates/example-draft/derived/drafts/lantern/input/parts/torso.png +0 -0
  294. package/templates/example-draft/derived/drafts/lantern/input/parts.json +188 -0
  295. package/templates/example-draft/derived/drafts/lantern/input/recomposite_rig.png +0 -0
  296. package/templates/example-draft/derived/drafts/lantern/interview-requirements.json +8 -0
  297. package/templates/example-draft/derived/drafts/lantern/out/check/build/run.json +40 -0
  298. package/templates/example-draft/derived/drafts/lantern/out/check/build/skeleton.atlas +28 -0
  299. package/templates/example-draft/derived/drafts/lantern/out/check/build/skeleton.json +193 -0
  300. package/templates/example-draft/derived/drafts/lantern/out/check/build/skeleton.model.json +483 -0
  301. package/templates/example-draft/derived/drafts/lantern/out/check/build/skeleton.png +0 -0
  302. package/templates/example-draft/derived/drafts/lantern/out/images/background.png +0 -0
  303. package/templates/example-draft/derived/drafts/lantern/out/images/eye_l.png +0 -0
  304. package/templates/example-draft/derived/drafts/lantern/out/images/eye_r.png +0 -0
  305. package/templates/example-draft/derived/drafts/lantern/out/images/face.png +0 -0
  306. package/templates/example-draft/derived/drafts/lantern/out/images/lantern.png +0 -0
  307. package/templates/example-draft/derived/drafts/lantern/out/images/torso.png +0 -0
  308. package/templates/example-draft/derived/drafts/lantern/out/mesh_report.json +1 -0
  309. package/templates/example-draft/derived/drafts/lantern/out/motion.json +137 -0
  310. package/templates/example-draft/derived/drafts/lantern/out/rig.json +102 -0
  311. package/templates/example-draft/derived/drafts/lantern/out/run.json +52 -0
  312. package/templates/example-draft/derived/parts/rig/painting_in_rig.png +0 -0
  313. package/templates/example-draft/derived/parts/rig/parts/background.png +0 -0
  314. package/templates/example-draft/derived/parts/rig/parts/eye_l.png +0 -0
  315. package/templates/example-draft/derived/parts/rig/parts/eye_r.png +0 -0
  316. package/templates/example-draft/derived/parts/rig/parts/face.png +0 -0
  317. package/templates/example-draft/derived/parts/rig/parts/lantern.png +0 -0
  318. package/templates/example-draft/derived/parts/rig/parts/torso.png +0 -0
  319. package/templates/example-draft/derived/parts/rig/parts.json +188 -0
  320. package/templates/example-draft/derived/parts/rig/recomposite_rig.png +0 -0
  321. package/templates/example-draft/derived/review/v1/config.json +71 -0
  322. package/templates/example-draft/derived/review/v1/interview-requirements.json +8 -0
  323. package/templates/example-draft/derived/review/v1/out/check/build/run.json +40 -0
  324. package/templates/example-draft/derived/review/v1/out/check/build/skeleton.atlas +28 -0
  325. package/templates/example-draft/derived/review/v1/out/check/build/skeleton.json +193 -0
  326. package/templates/example-draft/derived/review/v1/out/check/build/skeleton.model.json +483 -0
  327. package/templates/example-draft/derived/review/v1/out/check/build/skeleton.png +0 -0
  328. package/templates/example-draft/derived/review/v1/out/parts/background.png +0 -0
  329. package/templates/example-draft/derived/review/v1/out/parts/eye_l.png +0 -0
  330. package/templates/example-draft/derived/review/v1/out/parts/eye_r.png +0 -0
  331. package/templates/example-draft/derived/review/v1/out/parts/face.png +0 -0
  332. package/templates/example-draft/derived/review/v1/out/parts/lantern.png +0 -0
  333. package/templates/example-draft/derived/review/v1/out/parts/torso.png +0 -0
  334. package/templates/example-draft/derived/review/v1/out/parts.json +188 -0
  335. package/templates/example-draft/derived/review/v1/out/rig/images/background.png +0 -0
  336. package/templates/example-draft/derived/review/v1/out/rig/images/eye_l.png +0 -0
  337. package/templates/example-draft/derived/review/v1/out/rig/images/eye_r.png +0 -0
  338. package/templates/example-draft/derived/review/v1/out/rig/images/face.png +0 -0
  339. package/templates/example-draft/derived/review/v1/out/rig/images/lantern.png +0 -0
  340. package/templates/example-draft/derived/review/v1/out/rig/images/torso.png +0 -0
  341. package/templates/example-draft/derived/review/v1/out/rig/mesh_report.json +1 -0
  342. package/templates/example-draft/derived/review/v1/out/rig/motion.json +137 -0
  343. package/templates/example-draft/derived/review/v1/out/rig/rig.json +102 -0
  344. package/templates/example-draft/derived/review/v1/out/rig/run.json +52 -0
  345. package/templates/example-draft/derived/review/v2/config.json +71 -0
  346. package/templates/example-draft/derived/review/v2/interview-requirements.json +8 -0
  347. package/templates/example-draft/derived/review/v2/out/check/build/run.json +40 -0
  348. package/templates/example-draft/derived/review/v2/out/check/build/skeleton.atlas +28 -0
  349. package/templates/example-draft/derived/review/v2/out/check/build/skeleton.json +193 -0
  350. package/templates/example-draft/derived/review/v2/out/check/build/skeleton.model.json +483 -0
  351. package/templates/example-draft/derived/review/v2/out/check/build/skeleton.png +0 -0
  352. package/templates/example-draft/derived/review/v2/out/parts/background.png +0 -0
  353. package/templates/example-draft/derived/review/v2/out/parts/eye_l.png +0 -0
  354. package/templates/example-draft/derived/review/v2/out/parts/eye_r.png +0 -0
  355. package/templates/example-draft/derived/review/v2/out/parts/face.png +0 -0
  356. package/templates/example-draft/derived/review/v2/out/parts/lantern.png +0 -0
  357. package/templates/example-draft/derived/review/v2/out/parts/torso.png +0 -0
  358. package/templates/example-draft/derived/review/v2/out/parts.json +188 -0
  359. package/templates/example-draft/derived/review/v2/out/rig/images/background.png +0 -0
  360. package/templates/example-draft/derived/review/v2/out/rig/images/eye_l.png +0 -0
  361. package/templates/example-draft/derived/review/v2/out/rig/images/eye_r.png +0 -0
  362. package/templates/example-draft/derived/review/v2/out/rig/images/face.png +0 -0
  363. package/templates/example-draft/derived/review/v2/out/rig/images/lantern.png +0 -0
  364. package/templates/example-draft/derived/review/v2/out/rig/images/torso.png +0 -0
  365. package/templates/example-draft/derived/review/v2/out/rig/mesh_report.json +1 -0
  366. package/templates/example-draft/derived/review/v2/out/rig/motion.json +137 -0
  367. package/templates/example-draft/derived/review/v2/out/rig/rig.json +102 -0
  368. package/templates/example-draft/derived/review/v2/out/rig/run.json +52 -0
  369. package/templates/example-draft/evidence/diff-components.json +71 -0
  370. package/templates/example-draft/evidence/direction/v4.json +64 -0
  371. package/templates/example-draft/evidence/direction-input-v4.json +64 -0
  372. package/templates/example-draft/evidence/fix-ledger/lantern.json +17 -0
  373. package/templates/example-draft/evidence/parts-check.json +127 -0
  374. package/templates/example-draft/evidence/parts-review.json +88 -0
  375. package/templates/example-draft/evidence/review-reads/4ed0507381809746443021554e012032be5b5f8417da0f2e525e9b6df16e9f05.json +59 -0
  376. package/templates/example-draft/evidence/scene/s1.json +65 -0
  377. package/templates/example-draft/evidence/scene-background.png +0 -0
  378. package/templates/example-draft/evidence/scene-citations.json +9 -0
  379. package/templates/example-draft/evidence/structure-decisions.json +9 -0
  380. package/templates/example-draft/evidence/v1/interview-config.json +71 -0
  381. package/templates/example-draft/evidence/v1/interview-requirements.json +8 -0
  382. package/templates/example-draft/evidence/v1/review-config.json +71 -0
  383. package/templates/example-draft/evidence/v2/interview-config.json +71 -0
  384. package/templates/example-draft/evidence/v2/interview-requirements.json +8 -0
  385. package/templates/example-draft/evidence/v2/review-config.json +71 -0
  386. package/templates/example-draft/input/painting_in_rig.png +0 -0
  387. package/templates/example-draft/input/parts/background.png +0 -0
  388. package/templates/example-draft/input/parts/eye_l.png +0 -0
  389. package/templates/example-draft/input/parts/eye_r.png +0 -0
  390. package/templates/example-draft/input/parts/face.png +0 -0
  391. package/templates/example-draft/input/parts/lantern.png +0 -0
  392. package/templates/example-draft/input/parts/torso.png +0 -0
  393. package/templates/example-draft/input/parts.json +188 -0
  394. package/templates/example-draft/input/recomposite_rig.png +0 -0
  395. package/templates/example-draft/inputs/painting.png +0 -0
  396. package/templates/example-draft/interview-requirements.json +8 -0
  397. package/templates/example-draft/out/check/build/run.json +40 -0
  398. package/templates/example-draft/out/check/build/skeleton.atlas +28 -0
  399. package/templates/example-draft/out/check/build/skeleton.json +193 -0
  400. package/templates/example-draft/out/check/build/skeleton.model.json +483 -0
  401. package/templates/example-draft/out/check/build/skeleton.png +0 -0
  402. package/templates/example-draft/out/images/background.png +0 -0
  403. package/templates/example-draft/out/images/eye_l.png +0 -0
  404. package/templates/example-draft/out/images/eye_r.png +0 -0
  405. package/templates/example-draft/out/images/face.png +0 -0
  406. package/templates/example-draft/out/images/lantern.png +0 -0
  407. package/templates/example-draft/out/images/torso.png +0 -0
  408. package/templates/example-draft/out/mesh_report.json +1 -0
  409. package/templates/example-draft/out/motion.json +137 -0
  410. package/templates/example-draft/out/rig.json +102 -0
  411. package/templates/example-draft/out/run.json +52 -0
  412. package/templates/example-draft/outputs/v1/out/check/build/run.json +40 -0
  413. package/templates/example-draft/outputs/v1/out/check/build/skeleton.atlas +28 -0
  414. package/templates/example-draft/outputs/v1/out/check/build/skeleton.json +193 -0
  415. package/templates/example-draft/outputs/v1/out/check/build/skeleton.model.json +483 -0
  416. package/templates/example-draft/outputs/v1/out/check/build/skeleton.png +0 -0
  417. package/templates/example-draft/outputs/v1/out/painting_in_rig.png +0 -0
  418. package/templates/example-draft/outputs/v1/out/parts/background.png +0 -0
  419. package/templates/example-draft/outputs/v1/out/parts/eye_l.png +0 -0
  420. package/templates/example-draft/outputs/v1/out/parts/eye_r.png +0 -0
  421. package/templates/example-draft/outputs/v1/out/parts/face.png +0 -0
  422. package/templates/example-draft/outputs/v1/out/parts/lantern.png +0 -0
  423. package/templates/example-draft/outputs/v1/out/parts/torso.png +0 -0
  424. package/templates/example-draft/outputs/v1/out/parts.json +188 -0
  425. package/templates/example-draft/outputs/v1/out/rig/images/background.png +0 -0
  426. package/templates/example-draft/outputs/v1/out/rig/images/eye_l.png +0 -0
  427. package/templates/example-draft/outputs/v1/out/rig/images/eye_r.png +0 -0
  428. package/templates/example-draft/outputs/v1/out/rig/images/face.png +0 -0
  429. package/templates/example-draft/outputs/v1/out/rig/images/lantern.png +0 -0
  430. package/templates/example-draft/outputs/v1/out/rig/images/torso.png +0 -0
  431. package/templates/example-draft/outputs/v1/out/rig/mesh_report.json +1 -0
  432. package/templates/example-draft/outputs/v1/out/rig/motion.json +137 -0
  433. package/templates/example-draft/outputs/v1/out/rig/rig.json +102 -0
  434. package/templates/example-draft/outputs/v1/out/rig/run.json +52 -0
  435. package/templates/example-draft/outputs/v2/out/check/build/run.json +40 -0
  436. package/templates/example-draft/outputs/v2/out/check/build/skeleton.atlas +28 -0
  437. package/templates/example-draft/outputs/v2/out/check/build/skeleton.json +193 -0
  438. package/templates/example-draft/outputs/v2/out/check/build/skeleton.model.json +483 -0
  439. package/templates/example-draft/outputs/v2/out/check/build/skeleton.png +0 -0
  440. package/templates/example-draft/outputs/v2/out/painting_in_rig.png +0 -0
  441. package/templates/example-draft/outputs/v2/out/parts/background.png +0 -0
  442. package/templates/example-draft/outputs/v2/out/parts/eye_l.png +0 -0
  443. package/templates/example-draft/outputs/v2/out/parts/eye_r.png +0 -0
  444. package/templates/example-draft/outputs/v2/out/parts/face.png +0 -0
  445. package/templates/example-draft/outputs/v2/out/parts/lantern.png +0 -0
  446. package/templates/example-draft/outputs/v2/out/parts/torso.png +0 -0
  447. package/templates/example-draft/outputs/v2/out/parts.json +188 -0
  448. package/templates/example-draft/outputs/v2/out/rig/images/background.png +0 -0
  449. package/templates/example-draft/outputs/v2/out/rig/images/eye_l.png +0 -0
  450. package/templates/example-draft/outputs/v2/out/rig/images/eye_r.png +0 -0
  451. package/templates/example-draft/outputs/v2/out/rig/images/face.png +0 -0
  452. package/templates/example-draft/outputs/v2/out/rig/images/lantern.png +0 -0
  453. package/templates/example-draft/outputs/v2/out/rig/images/torso.png +0 -0
  454. package/templates/example-draft/outputs/v2/out/rig/mesh_report.json +1 -0
  455. package/templates/example-draft/outputs/v2/out/rig/motion.json +137 -0
  456. package/templates/example-draft/outputs/v2/out/rig/rig.json +102 -0
  457. package/templates/example-draft/outputs/v2/out/rig/run.json +52 -0
  458. package/templates/example-interview-choice.json +1068 -0
  459. package/templates/example-interview-options/constraint.json +376 -0
  460. package/templates/example-interview-options/example-assemble.json +376 -0
  461. package/templates/example-interview-options/example-bones.json +190 -0
  462. package/templates/example-interview-options/example-camera-depth.json +99 -0
  463. package/templates/example-interview-options/example-constraints.json +272 -0
  464. package/templates/example-interview-options/example-meshes.json +190 -0
  465. package/templates/example-interview-options/example-motion.json +272 -0
  466. package/templates/example-interview-options/example-picks.json +15 -0
  467. package/templates/example-interview-options/example-post-composition.json +110 -0
  468. package/templates/example-interview-options/example-post-timing.json +105 -0
  469. package/templates/example-interview-options/example-reference.json +63 -0
  470. package/templates/example-interview-options/example-rig-type.json +63 -0
  471. package/templates/example-interview-options/example-seethrough.json +190 -0
  472. package/templates/example-interview-options/fullscreen.json +63 -0
  473. package/templates/example-interview-options/other-explicit.json +63 -0
  474. package/templates/example-interview-options/other-pending.json +63 -0
  475. package/templates/example-interview-options/other-picks.json +16 -0
  476. package/templates/example-interview.md +213 -0
  477. package/templates/example-invariants/invariants.json +1339 -0
  478. package/templates/example-prepared/prepared.svg +1 -0
  479. package/templates/example-prepared/source.svg +1 -0
  480. package/templates/example-project.json +4259 -0
  481. package/templates/example-remesh/AGENTS.md +134 -0
  482. package/templates/example-remesh/CLAUDE.md +3 -0
  483. package/templates/example-remesh/evidence/.gitkeep +0 -0
  484. package/templates/example-remesh/evidence/approval.txt +1 -0
  485. package/templates/example-remesh/evidence/captions.json +101 -0
  486. package/templates/example-remesh/evidence/diff-components.json +34 -0
  487. package/templates/example-remesh/evidence/direction/v4.json +74 -0
  488. package/templates/example-remesh/evidence/direction/v5.json +74 -0
  489. package/templates/example-remesh/evidence/direction-input-v4.json +74 -0
  490. package/templates/example-remesh/evidence/direction-input-v5.json +74 -0
  491. package/templates/example-remesh/evidence/interview/assemble.json +309 -0
  492. package/templates/example-remesh/evidence/interview/bones.json +190 -0
  493. package/templates/example-remesh/evidence/interview/camera_depth.json +103 -0
  494. package/templates/example-remesh/evidence/interview/constraints.json +272 -0
  495. package/templates/example-remesh/evidence/interview/meshes.json +190 -0
  496. package/templates/example-remesh/evidence/interview/motion.json +272 -0
  497. package/templates/example-remesh/evidence/interview/post_composition.json +123 -0
  498. package/templates/example-remesh/evidence/interview/post_timing.json +103 -0
  499. package/templates/example-remesh/evidence/interview/reference.json +59 -0
  500. package/templates/example-remesh/evidence/interview/rig_type.json +59 -0
  501. package/templates/example-remesh/evidence/interview/seethrough.json +186 -0
  502. package/templates/example-remesh/evidence/parts-check.json +127 -0
  503. package/templates/example-remesh/evidence/parts-review.json +88 -0
  504. package/templates/example-remesh/evidence/reopen.txt +1 -0
  505. package/templates/example-remesh/evidence/review-reads/6a17db694db96f1b6049a61a017e0c8e66757b982b8851e0187e561cfcf9af68.json +59 -0
  506. package/templates/example-remesh/evidence/scene/s1.json +69 -0
  507. package/templates/example-remesh/evidence/scene-citations.json +9 -0
  508. package/templates/example-remesh/evidence/setup.png +0 -0
  509. package/templates/example-remesh/evidence/split-config.json +1 -0
  510. package/templates/example-remesh/evidence/structure.json +30 -0
  511. package/templates/example-remesh/evidence/v1/registration/at-source.json +44 -0
  512. package/templates/example-remesh/evidence/v1/registration/citations.json +68 -0
  513. package/templates/example-remesh/evidence/v1/registration/compile-report.json +368 -0
  514. package/templates/example-remesh/evidence/v1/registration/config-source.json +88 -0
  515. package/templates/example-remesh/evidence/v1/registration/config.json +88 -0
  516. package/templates/example-remesh/evidence/v1/registration/interview-requirements.json +8 -0
  517. package/templates/example-remesh/evidence/v1/registration/package-lock.json +11 -0
  518. package/templates/example-remesh/evidence/v1/registration/path-adapter.json +1 -0
  519. package/templates/example-remesh/evidence/v1/registration/rigc-run-1.json +44 -0
  520. package/templates/example-remesh/evidence/v2/registration/at-source.json +44 -0
  521. package/templates/example-remesh/evidence/v2/registration/citations.json +68 -0
  522. package/templates/example-remesh/evidence/v2/registration/compile-report.json +368 -0
  523. package/templates/example-remesh/evidence/v2/registration/config-source.json +88 -0
  524. package/templates/example-remesh/evidence/v2/registration/config.json +88 -0
  525. package/templates/example-remesh/evidence/v2/registration/interview-requirements.json +8 -0
  526. package/templates/example-remesh/evidence/v2/registration/package-lock.json +11 -0
  527. package/templates/example-remesh/evidence/v2/registration/path-adapter.json +1 -0
  528. package/templates/example-remesh/evidence/v2/registration/rigc-run-1.json +44 -0
  529. package/templates/example-remesh/evidence/v3/registration/at-source.json +44 -0
  530. package/templates/example-remesh/evidence/v3/registration/citations.json +68 -0
  531. package/templates/example-remesh/evidence/v3/registration/compile-report.json +368 -0
  532. package/templates/example-remesh/evidence/v3/registration/config-source.json +88 -0
  533. package/templates/example-remesh/evidence/v3/registration/config.json +88 -0
  534. package/templates/example-remesh/evidence/v3/registration/interview-requirements.json +8 -0
  535. package/templates/example-remesh/evidence/v3/registration/package-lock.json +11 -0
  536. package/templates/example-remesh/evidence/v3/registration/path-adapter.json +1 -0
  537. package/templates/example-remesh/evidence/v3/registration/rigc-run-1.json +44 -0
  538. package/templates/example-remesh/inputs/background.png +0 -0
  539. package/templates/example-remesh/inputs/painting.png +0 -0
  540. package/templates/example-remesh/inputs/references/.gitkeep +0 -0
  541. package/templates/example-remesh/package.json +9 -0
  542. package/templates/example-remesh/project.json +3916 -0
  543. package/templates/example-reopened/project.json +4568 -0
  544. package/templates/example-review/config.json +1 -0
  545. package/templates/example-review/out/check/build/skeleton.model.json +1 -0
  546. package/templates/example-review/out/parts/piece.svg +1 -0
  547. package/templates/example-review/out/parts.json +1 -0
  548. package/templates/example-review/out/rig/mesh_report.json +1 -0
  549. package/templates/example-review/out/rig/motion.json +1 -0
  550. package/templates/example-review/out/rig/rig.json +1 -0
  551. package/templates/example-split/captions.json +357 -0
  552. package/templates/example-split/config.json +215 -0
  553. package/templates/legacy-example-project.json +1071 -0
  554. package/templates/project.json +199 -0
  555. package/templates/shared/conventions.json +23 -0
@@ -0,0 +1,348 @@
1
+ /**
2
+ * `rig-create init --collection <dir>` and `rig-create init --member <collection dir> <name>`
3
+ * (docs/COLLECTIONS.md § Layout).
4
+ *
5
+ * A collection is one git repository. Its members are ordinary project folders
6
+ * under `members/<name>/` inside that repository, not repositories of their
7
+ * own: the folder path is the membership record (COLLECTIONS.md § Members live
8
+ * inside the collection), and a collection version pins member builds, so the
9
+ * collection's history has to hold the member records it pins. A nested
10
+ * repository would make the collection's git see a member as an opaque gitlink.
11
+ *
12
+ * For the same reason a member has no `package.json` of its own: the
13
+ * collection pins the rig-c packages once, so every member is built with the
14
+ * same versions and a cross bar measures one toolchain. `interview --check` in
15
+ * a member reads the collection's install (src/parts-sections.ts).
16
+ */
17
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
18
+ import { basename, join, resolve } from 'node:path';
19
+
20
+ import { CREATE_DOCS, DERIVED_LINK, PACKAGE_DOCS, READ_FIRST, createDocUrl, packageDocPath, writeAgentGuide } from './agent-guide.ts';
21
+ import { COLLECTION_SCHEMA, collectionAnswer, readJsonObject } from './check.ts';
22
+ import {
23
+ checkTarget,
24
+ FALLBACK_IDENTITY,
25
+ GITIGNORE,
26
+ hasGitIdentity,
27
+ IGNORED_DIRS,
28
+ INTERVIEW_DOC_URL,
29
+ run,
30
+ TRACKED_EMPTY_DIRS,
31
+ writePackageJson,
32
+ } from './init.ts';
33
+ import { PINNED_PACKAGES } from './pins.ts';
34
+ import { refuse, USAGE_EXIT } from './refusal.ts';
35
+ import { CHOICE_ORDER_SPEC } from './choices.ts';
36
+ import { readTemplate, TEMPLATES_DIR, type Json, type JsonObject } from './templates.ts';
37
+
38
+ /** The collection's stages, in order (COLLECTIONS.md § Collection stages); `closed_by` as in src/stages.ts. */
39
+ export const COLLECTION_STAGES = [
40
+ { id: 'interview', closed_by: 'signoff' },
41
+ { id: 'plan-approved', closed_by: 'signoff' },
42
+ { id: 'reference', closed_by: 'signoff' },
43
+ { id: 'members', closed_by: 'bar' },
44
+ { id: 'post-interview', closed_by: 'signoff' },
45
+ { id: 'post', closed_by: 'bar' },
46
+ { id: 'direction-review', closed_by: 'signoff' },
47
+ { id: 'accepted', closed_by: 'none' },
48
+ ] as const satisfies readonly { id: string; closed_by: 'signoff' | 'bar' | 'none' }[];
49
+
50
+ export const COLLECTION_STAGE_VALUES: readonly string[] = [...COLLECTION_STAGES.map((s) => s.id), 'paused'];
51
+
52
+ /** A collection folder holds members/ besides a project's tracked folders. */
53
+ export const COLLECTION_TRACKED_EMPTY_DIRS = ['members', ...TRACKED_EMPTY_DIRS] as const;
54
+
55
+ export const COLLECTION_GUIDE_TEMPLATE = 'collection-agent-guide.md';
56
+
57
+ /** A member name is one folder name: it becomes `members/<name>/` and the `from.collection` key nobody retypes. */
58
+ const MEMBER_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
59
+
60
+ export interface CollectionInitResult {
61
+ dir: string;
62
+ name: string;
63
+ installed: boolean;
64
+ installError: string | null;
65
+ committed: string[];
66
+ commit: string;
67
+ fallbackIdentity: boolean;
68
+ }
69
+
70
+ export interface MemberInitResult {
71
+ collectionDir: string;
72
+ collection: string;
73
+ dir: string;
74
+ name: string;
75
+ inherited: string[];
76
+ own: string[];
77
+ committed: string[];
78
+ /** `null` when the collection folder is not a git work tree: files are written, nothing is committed. */
79
+ commit: string | null;
80
+ fallbackIdentity: boolean;
81
+ }
82
+
83
+ function writeJson(file: string, value: JsonObject): void {
84
+ writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
85
+ }
86
+
87
+ function isObject(v: Json | undefined): v is JsonObject {
88
+ return v !== null && v !== undefined && typeof v === 'object' && !Array.isArray(v);
89
+ }
90
+
91
+ function identityArgs(dir: string): { args: string[]; fallback: boolean } {
92
+ const fallback = !hasGitIdentity(dir);
93
+ return {
94
+ fallback,
95
+ args: fallback ? ['-c', `user.name=${FALLBACK_IDENTITY.name}`, '-c', `user.email=${FALLBACK_IDENTITY.email}`] : [],
96
+ };
97
+ }
98
+
99
+ function commitPaths(dir: string, paths: string[], message: string): { commit: string; fallback: boolean } {
100
+ const add = run(['git', 'add', '--', ...paths], dir);
101
+ if (!add.ok) refuse('INIT_GIT_FAILED', `git add failed in ${dir}\n${add.out}`);
102
+ const identity = identityArgs(dir);
103
+ const commit = run(['git', ...identity.args, 'commit', '-q', '-m', message, '--', ...paths], dir);
104
+ if (!commit.ok) refuse('INIT_GIT_FAILED', `git commit failed in ${dir}\n${commit.out}`);
105
+ return { commit: run(['git', 'rev-parse', '--short', 'HEAD'], dir).out, fallback: identity.fallback };
106
+ }
107
+
108
+ /** The collection's AGENTS.md, from templates/collection-agent-guide.md. */
109
+ export function renderCollectionGuide(): string {
110
+ const fills: Record<string, string> = {
111
+ COLLECTIONS_LINK: `[COLLECTIONS.md](${createDocUrl('docs/COLLECTIONS.md')})`,
112
+ DERIVED_LINK: `[INTERVIEW.md § Derived answers](${createDocUrl(DERIVED_LINK.path, DERIVED_LINK.anchor)})`,
113
+ READ_FIRST,
114
+ PACKAGE_DOCS: PACKAGE_DOCS.map((doc) => `- [${doc.package} ${doc.path}](${packageDocPath(doc)}): ${doc.why}.`).join('\n'),
115
+ CREATE_DOCS: CREATE_DOCS.map((doc) => `- [${doc.path}](${createDocUrl(doc.path)}): ${doc.why}.`).join('\n'),
116
+ };
117
+ const template = readFileSync(join(TEMPLATES_DIR, COLLECTION_GUIDE_TEMPLATE), 'utf8');
118
+ return template.replace(/\{\{([A-Z_]+)\}\}/g, (marker, key: string) => {
119
+ const fill = fills[key];
120
+ if (fill === undefined) throw new Error(`${COLLECTION_GUIDE_TEMPLATE}: unknown marker ${marker}`);
121
+ return fill;
122
+ });
123
+ }
124
+
125
+ /**
126
+ * Creates a collection in `target`: collection.json and shared/conventions.json
127
+ * from the templates (both named after the folder), members/, the project
128
+ * folders a collection shares (inputs/references, evidence, and the ignored
129
+ * derived, outputs, scratch), .gitignore, package.json pinning the rig-c
130
+ * packages once for every member, the install (unless skipped), the agent
131
+ * guide, and a first commit of exactly those files.
132
+ */
133
+ export function initCollection(target: string, options: { install: boolean }): CollectionInitResult {
134
+ const dir = resolve(target);
135
+ checkTarget(dir);
136
+ const name = basename(dir);
137
+ mkdirSync(join(dir, 'shared'), { recursive: true });
138
+ const written: string[] = [];
139
+
140
+ const collection = readTemplate('collection.json');
141
+ collection.name = name;
142
+ writeJson(join(dir, 'collection.json'), collection);
143
+ written.push('collection.json');
144
+ const conventions = readTemplate('shared/conventions.json');
145
+ conventions.collection = name;
146
+ writeJson(join(dir, 'shared', 'conventions.json'), conventions);
147
+ written.push('shared/conventions.json');
148
+ writePackageJson(dir, name);
149
+ written.push('package.json');
150
+ writeFileSync(join(dir, '.gitignore'), GITIGNORE);
151
+ written.push('.gitignore');
152
+ for (const sub of COLLECTION_TRACKED_EMPTY_DIRS) {
153
+ mkdirSync(join(dir, sub), { recursive: true });
154
+ writeFileSync(join(dir, sub, '.gitkeep'), '');
155
+ written.push(`${sub}/.gitkeep`);
156
+ }
157
+ for (const sub of IGNORED_DIRS) mkdirSync(join(dir, sub), { recursive: true });
158
+ writeFileSync(join(dir, 'AGENTS.md'), renderCollectionGuide());
159
+ writeFileSync(join(dir, 'CLAUDE.md'), ['@AGENTS.md', '', 'The agent guide for this collection is AGENTS.md, imported above. It is the only copy: change it there, not here.', ''].join('\n'));
160
+ written.push('AGENTS.md', 'CLAUDE.md');
161
+
162
+ let installError: string | null = null;
163
+ if (options.install) {
164
+ const install = run([process.execPath, 'install'], dir);
165
+ if (install.ok) written.push('bun.lock');
166
+ else installError = install.out;
167
+ }
168
+ const gitInit = run(['git', 'init', '-q'], dir);
169
+ if (!gitInit.ok) refuse('INIT_GIT_FAILED', `git init failed in ${dir}\n${gitInit.out}`);
170
+ const { commit, fallback } = commitPaths(dir, written, `Create collection ${name}`);
171
+ return { dir, name, installed: options.install && installError === null, installError, committed: written, commit, fallbackIdentity: fallback };
172
+ }
173
+
174
+ /** Reads `<dir>/collection.json`, refusing by name when the folder is not a collection. */
175
+ export function readCollection(dir: string): JsonObject {
176
+ const file = join(dir, 'collection.json');
177
+ if (!existsSync(file)) refuse('INIT_NOT_A_COLLECTION', `${dir} is not a collection: it has no collection.json (make one with rig-create init --collection <dir>)`);
178
+ const read = readJsonObject(file);
179
+ if (!read.ok) refuse('INIT_NOT_A_COLLECTION', `${file}: ${read.error}`);
180
+ if (read.value.schema !== COLLECTION_SCHEMA) {
181
+ refuse('INIT_NOT_A_COLLECTION', `${file}: schema is ${JSON.stringify(read.value.schema ?? null)}, not ${COLLECTION_SCHEMA}`);
182
+ }
183
+ return read.value;
184
+ }
185
+
186
+ /**
187
+ * Where the member's answer starts: a reference to the collection's answer
188
+ * when the collection gave one (anything but `null` or `"per-member"`), and
189
+ * `null` — the member's own question — otherwise. The value is never copied.
190
+ */
191
+ function startingAnswer(collection: JsonObject, collectionName: string, id: string, at: string): { answer: Json; inherited: boolean } {
192
+ const answer = collectionAnswer(collection, id);
193
+ if (answer === undefined || answer === null) return { answer: null, inherited: false };
194
+ if (isObject(answer) && ('deferred' in answer || answer.value === 'per-member')) return { answer: null, inherited: false };
195
+ return { answer: { basis: 'inherited', from: { collection: collectionName, question: id }, by: 'rig-create', at }, inherited: true };
196
+ }
197
+
198
+ /**
199
+ * Creates `<collection>/members/<name>/` as an ordinary project (PROJECT.md §
200
+ * Layout, without package.json and without a repository of its own), its
201
+ * answers inherited from the collection where the collection answered, and
202
+ * registers it in collection.json `members[]`. Commits exactly the files
203
+ * written and collection.json in the collection's repository; refuses when
204
+ * collection.json has uncommitted changes, so that commit never carries
205
+ * someone else's edit.
206
+ */
207
+ export function initMember(collectionArg: string, name: string, now: Date): MemberInitResult {
208
+ const collectionDir = resolve(collectionArg);
209
+ if (!MEMBER_NAME_RE.test(name)) {
210
+ refuse('INIT_MEMBER_NAME_INVALID', `member name ${JSON.stringify(name)} is not one folder name (letters, digits, '.', '_', '-'; not starting with '.', '_' or '-')`);
211
+ }
212
+ const collection = readCollection(collectionDir);
213
+ const collectionName = typeof collection.name === 'string' && collection.name !== '' ? collection.name : null;
214
+ if (collectionName === null) refuse('INIT_COLLECTION_UNNAMED', `${join(collectionDir, 'collection.json')} has no name; an inherited answer names its collection`);
215
+ const members = Array.isArray(collection.members) ? collection.members : [];
216
+ const dir = join(collectionDir, 'members', name);
217
+ if (members.some((m) => isObject(m) && m.name === name)) {
218
+ refuse('INIT_MEMBER_EXISTS', `collection '${collectionName}' already has a member '${name}' in members[]`);
219
+ }
220
+ if (existsSync(dir)) refuse('INIT_MEMBER_EXISTS', `${dir} already exists; collection '${collectionName}' has a folder for '${name}'`);
221
+
222
+ const inGit = run(['git', 'rev-parse', '--is-inside-work-tree'], collectionDir).ok;
223
+ if (inGit) {
224
+ const dirty = run(['git', 'status', '--porcelain', '--', 'collection.json'], collectionDir).out;
225
+ if (dirty !== '') {
226
+ refuse('INIT_COLLECTION_DIRTY', `${join(collectionDir, 'collection.json')} has uncommitted changes; commit them first, so registering '${name}' commits only that`);
227
+ }
228
+ }
229
+
230
+ const at = now.toISOString();
231
+ const project = readTemplate('project.json');
232
+ project.name = name;
233
+ project.corrections_contract = { schema: 'rig-create/corrections@1', adopted_at: at };
234
+ project.corrections = [];
235
+ const inherited: string[] = [];
236
+ const own: string[] = [];
237
+ const interview = isObject(project.interview) ? project.interview : {};
238
+ interview.order = { spec: CHOICE_ORDER_SPEC, adopted_at: at, choices_adopted_at: at };
239
+ const post = isObject(interview.post) ? interview.post : {};
240
+ const lists: [Json | undefined, 'answer' | 'guide'][] = [
241
+ [interview.framing, 'answer'],
242
+ [interview.waypoints, 'guide'],
243
+ [post.items, 'guide'],
244
+ ];
245
+ for (const [list, field] of lists) {
246
+ for (const item of Array.isArray(list) ? list : []) {
247
+ if (!isObject(item) || typeof item.id !== 'string') continue;
248
+ const start = startingAnswer(collection, collectionName, item.id, at);
249
+ item[field] = start.answer;
250
+ (start.inherited ? inherited : own).push(item.id);
251
+ }
252
+ }
253
+
254
+ mkdirSync(dir, { recursive: true });
255
+ const rel = (p: string): string => `members/${name}/${p}`;
256
+ const written: string[] = [];
257
+ writeJson(join(dir, 'project.json'), project);
258
+ written.push(rel('project.json'));
259
+ writeFileSync(join(dir, '.gitignore'), GITIGNORE);
260
+ written.push(rel('.gitignore'));
261
+ for (const sub of TRACKED_EMPTY_DIRS) {
262
+ mkdirSync(join(dir, sub), { recursive: true });
263
+ writeFileSync(join(dir, sub, '.gitkeep'), '');
264
+ written.push(rel(`${sub}/.gitkeep`));
265
+ }
266
+ for (const sub of IGNORED_DIRS) mkdirSync(join(dir, sub), { recursive: true });
267
+ // The member's guide links the installed docs in the collection's node_modules, two folders up.
268
+ written.push(...writeAgentGuide(dir).map(rel));
269
+ const guide = join(dir, 'AGENTS.md');
270
+ writeFileSync(guide, readFileSync(guide, 'utf8').replaceAll('](node_modules/', '](../../node_modules/'));
271
+
272
+ collection.members = [...members, { name, project: rel('project.json'), input: null, note: null }];
273
+ writeJson(join(collectionDir, 'collection.json'), collection);
274
+ written.push('collection.json');
275
+
276
+ let commit: string | null = null;
277
+ let fallbackIdentity = false;
278
+ if (inGit) {
279
+ const made = commitPaths(collectionDir, written, `Add member ${name} to collection ${collectionName}`);
280
+ commit = made.commit;
281
+ fallbackIdentity = made.fallback;
282
+ }
283
+ return { collectionDir, collection: collectionName, dir, name, inherited, own, committed: written, commit, fallbackIdentity };
284
+ }
285
+
286
+ export function describeCollectionInit(result: CollectionInitResult): string {
287
+ const pins = Object.entries(PINNED_PACKAGES).map(([pkg, version]) => `${pkg} ${version}`).join(', ');
288
+ const lines = [
289
+ `Created collection ${result.name} at ${result.dir}`,
290
+ result.installed
291
+ ? ` installed ${pins} for every member (bun.lock written)`
292
+ : result.installError === null
293
+ ? ` pinned ${pins} for every member; install skipped (--no-install)`
294
+ : ` pinned ${pins}; bun install FAILED, so bun.lock is not in the first commit`,
295
+ ` first commit ${result.commit}: ${result.committed.join(', ')}`,
296
+ ];
297
+ if (result.fallbackIdentity) lines.push(` no git identity is configured; the first commit is authored as "${FALLBACK_IDENTITY.name} <${FALLBACK_IDENTITY.email}>"`);
298
+ lines.push('', 'Next:');
299
+ let step = 1;
300
+ if (!result.installed) lines.push(` ${step++}. cd ${result.dir} && bun install, then commit bun.lock`);
301
+ lines.push(` ${step++}. put the group painting in inputs/, then hold the collection interview: ${INTERVIEW_DOC_URL}`);
302
+ lines.push(` ${step++}. add members once the shared answers are given: rig-create init --member ${result.dir} <name>`);
303
+ return lines.join('\n');
304
+ }
305
+
306
+ export function describeMemberInit(result: MemberInitResult): string {
307
+ const lines = [
308
+ `Created member ${result.name} of collection ${result.collection} at ${result.dir}`,
309
+ ` inherited from the collection (${result.inherited.length}): ${result.inherited.join(', ') || 'none'}`,
310
+ ` the member's own to answer (${result.own.length}): ${result.own.join(', ') || 'none'}`,
311
+ result.commit === null
312
+ ? ` ${result.collectionDir} is not a git work tree; nothing committed (written: ${result.committed.join(', ')})`
313
+ : ` commit ${result.commit}: ${result.committed.join(', ')}`,
314
+ ];
315
+ if (result.fallbackIdentity) lines.push(` no git identity is configured; the commit is authored as "${FALLBACK_IDENTITY.name} <${FALLBACK_IDENTITY.email}>"`);
316
+ lines.push('', 'Next:', ` 1. interview the member on what it does not inherit: rig-create interview --check ${result.dir}`);
317
+ return lines.join('\n');
318
+ }
319
+
320
+ /**
321
+ * `init --collection <dir> [--no-install]` and `init --member <collection dir> <name>`.
322
+ * The member is named, not pathed: the command derives `members/<name>/`, so a
323
+ * member cannot be made outside `members/`, and "not a collection" is refused
324
+ * on the folder the user named, before anything is written.
325
+ */
326
+ export function collectionInitCommand(args: string[], now: number): { stdout: string; installError: string | null; dir: string } {
327
+ let collection = false;
328
+ let member = false;
329
+ let install = true;
330
+ const positional: string[] = [];
331
+ for (const arg of args) {
332
+ if (arg === '--collection') collection = true;
333
+ else if (arg === '--member') member = true;
334
+ else if (arg === '--no-install') install = false;
335
+ else if (arg.startsWith('-')) refuse('UNKNOWN_OPTION', `init does not take ${arg}`, USAGE_EXIT);
336
+ else positional.push(arg);
337
+ }
338
+ if (collection && member) refuse('INIT_TOO_MANY_ARGUMENTS', 'init takes --collection or --member, not both', USAGE_EXIT);
339
+ if (collection) {
340
+ if (positional.length !== 1) refuse('INIT_DIR_MISSING', 'init --collection takes one folder: rig-create init --collection <dir>', USAGE_EXIT);
341
+ const result = initCollection(positional[0] as string, { install });
342
+ return { stdout: describeCollectionInit(result), installError: result.installError, dir: result.dir };
343
+ }
344
+ if (!install) refuse('UNKNOWN_OPTION', 'init --member does not take --no-install: a member installs nothing (the collection pins the packages)', USAGE_EXIT);
345
+ if (positional.length !== 2) refuse('INIT_DIR_MISSING', 'init --member takes the collection folder and a member name: rig-create init --member <collection dir> <name>', USAGE_EXIT);
346
+ const result = initMember(positional[0] as string, positional[1] as string, new Date(now));
347
+ return { stdout: describeMemberInit(result), installError: null, dir: result.dir };
348
+ }
@@ -0,0 +1,155 @@
1
+ /** Config input identities come from the installed loader's file declarations. */
2
+ import { createHash } from 'node:crypto';
3
+ import { existsSync, lstatSync, readFileSync, realpathSync } from './check-reads.ts';
4
+ import { createRequire } from 'node:module';
5
+ import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
6
+ import { locateParts, OWN_NODE_MODULES } from './parts-sections.ts';
7
+ import type { JsonObject } from './templates.ts';
8
+
9
+ export const CONFIG_INPUTS_SINCE = '2026-11-06T00:00:00Z';
10
+ const load = createRequire(import.meta.url);
11
+ export class ConfigInputsUnavailable extends Error {
12
+ constructor(version: string) {
13
+ super(`@rig-c/parts ${version} has no configFileReferences export; install a version providing the config file declarations before recording new named inputs`);
14
+ }
15
+ }
16
+ export type ConfigInputsWarning = (error: ConfigInputsUnavailable) => void;
17
+ export function configInputReferences(dir: string, config: unknown, unavailable?: ConfigInputsWarning): { at: string; value: string }[] {
18
+ const parts = locateParts(dir);
19
+ if (parts === null) throw new Error('install @rig-c/parts to read config-named inputs');
20
+ const mod = load(join(parts, 'src/config.ts')) as { configFileReferences?: (config: unknown) => { at: string; value: string }[] };
21
+ if (typeof mod.configFileReferences !== 'function') {
22
+ // The pinned declaration only detects whether the unavailable capability matters.
23
+ // It supplies no binding on behalf of the project's older loader.
24
+ const own = load(join(OWN_NODE_MODULES, '@rig-c/parts/src/config.ts')) as typeof mod;
25
+ if (typeof own.configFileReferences !== 'function' || !own.configFileReferences(config).length) return [];
26
+ const pkg = JSON.parse(readFileSync(join(parts, 'package.json'), 'utf8')) as { version: string };
27
+ const error = new ConfigInputsUnavailable(pkg.version);
28
+ if (!unavailable) throw error;
29
+ unavailable(error);
30
+ return [];
31
+ }
32
+ return mod.configFileReferences(config);
33
+ }
34
+ /**
35
+ * The config-named inputs the pinned parts declares for `config` that the
36
+ * project's install does not, with the install's version. The pinned
37
+ * declaration stands in for a newer producer's, which create cannot load; it
38
+ * is used only to refuse a writer, never to bind on the install's behalf.
39
+ */
40
+ export function inputsUndeclaredByInstall(dir: string, config: unknown): { version: string; refs: { at: string; value: string }[] } {
41
+ const parts = locateParts(dir);
42
+ if (parts === null) throw new Error('install @rig-c/parts to read config-named inputs');
43
+ type Loader = { configFileReferences?: (config: unknown) => { at: string; value: string }[] };
44
+ const mod = load(join(parts, 'src/config.ts')) as Loader;
45
+ const own = load(join(OWN_NODE_MODULES, '@rig-c/parts/src/config.ts')) as Loader;
46
+ const version = (JSON.parse(readFileSync(join(parts, 'package.json'), 'utf8')) as { version: string }).version;
47
+ if (typeof own.configFileReferences !== 'function') return { version, refs: [] };
48
+ const declared = typeof mod.configFileReferences === 'function' ? new Set(mod.configFileReferences(config).map(ref => ref.at)) : new Set<string>();
49
+ return { version, refs: own.configFileReferences(config).filter(ref => !declared.has(ref.at)) };
50
+ }
51
+ /** Whether release `a` is later than release `b`; a version that is not plain major.minor.patch is never later. */
52
+ export function releaseAfter(a: string, b: string): boolean {
53
+ const x = /^(\d+)\.(\d+)\.(\d+)$/.exec(a);
54
+ const y = /^(\d+)\.(\d+)\.(\d+)$/.exec(b);
55
+ if (x === null || y === null) return false;
56
+ for (let i = 1; i <= 3; i++) if (Number(x[i]) !== Number(y[i])) return Number(x[i]) > Number(y[i]);
57
+ return false;
58
+ }
59
+ /**
60
+ * The file a directory reference binds: a See-through occluder plate directory
61
+ * (`comfy plate --mode occluder`) is bound by its producer's receipt, which
62
+ * itself binds the plate's sibling files; create never hashes those siblings.
63
+ */
64
+ export const DIRECTORY_INPUT_RECEIPT = 'receipt.json';
65
+ /**
66
+ * The plate-receipt spec the project's installed parts declares
67
+ * (`PLATE_RECEIPT_SPEC` in `src/plate.ts`), read from the producer and never
68
+ * copied into create.
69
+ */
70
+ export function plateReceiptSpec(dir: string): string {
71
+ const parts = locateParts(dir);
72
+ if (parts === null) throw new Error('install @rig-c/parts to read config-named inputs');
73
+ let spec: unknown;
74
+ try { spec = (load(join(parts, 'src/plate.ts')) as { PLATE_RECEIPT_SPEC?: unknown }).PLATE_RECEIPT_SPEC; } catch { spec = undefined; }
75
+ if (typeof spec !== 'string' || !spec) {
76
+ const pkg = JSON.parse(readFileSync(join(parts, 'package.json'), 'utf8')) as { version: string };
77
+ throw new Error(`@rig-c/parts ${pkg.version} has no PLATE_RECEIPT_SPEC export in src/plate.ts; install a version declaring the plate receipt spec before binding a directory input`);
78
+ }
79
+ return spec;
80
+ }
81
+ /** The `spec` a receipt's bytes declare, or null when they are not a JSON object with a string spec. */
82
+ export function receiptSpec(bytes: Uint8Array): string | null {
83
+ let parsed: unknown;
84
+ try { parsed = JSON.parse(Buffer.from(bytes).toString('utf8')); } catch { return null; }
85
+ return parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed) && typeof (parsed as { spec?: unknown }).spec === 'string' ? (parsed as { spec: string }).spec : null;
86
+ }
87
+ /**
88
+ * One binding per config-named input. A file binds `{path, sha256}`; a
89
+ * directory binds `{path: <dir>/receipt.json, sha256, directory: <dir>, spec}`,
90
+ * so every reader that hashes `path` reads a regular file either way. The
91
+ * receipt must declare the installed parts' plate-receipt spec; its siblings
92
+ * and measurements are parts' to verify, not create's.
93
+ */
94
+ export function configInputBindings(dir: string, configPath: string, allowMissing = false, unavailable?: ConfigInputsWarning): JsonObject[] {
95
+ const config = JSON.parse(readFileSync(resolve(dir, configPath), 'utf8'));
96
+ const root = realpathSync(dir);
97
+ const contained = (at: string, path: string): string => {
98
+ if (!path || isAbsolute(path) || path === '..' || path.startsWith(`..${sep}`)) throw new Error(`${at}: named input leaves the project`);
99
+ return path.split(sep).join('/');
100
+ };
101
+ const canonical = (target: string): string => {
102
+ let cur = target;
103
+ while (!existsSync(cur)) {
104
+ try { if (lstatSync(cur).isSymbolicLink()) break; } catch { /* Missing segment; try parent. */ }
105
+ const parent = dirname(cur);
106
+ if (parent === cur) return target;
107
+ cur = parent;
108
+ }
109
+ return resolve(realpathSync(cur), relative(cur, target));
110
+ };
111
+ return configInputReferences(dir, config, unavailable).map((ref): JsonObject => {
112
+ const file = resolve(dir, dirname(configPath), ref.value);
113
+ const present = existsSync(file);
114
+ const real = present ? realpathSync(file) : canonical(file);
115
+ const path = contained(ref.at, relative(root, real));
116
+ if (!present) {
117
+ if (allowMissing) return { path, sha256: null };
118
+ realpathSync(file);
119
+ }
120
+ if (lstatSync(real).isDirectory()) {
121
+ const receipt = join(real, DIRECTORY_INPUT_RECEIPT);
122
+ const bound = `${path}/${DIRECTORY_INPUT_RECEIPT}`;
123
+ if (allowMissing && !existsSync(receipt)) return { path: bound, sha256: null, directory: path };
124
+ if (!existsSync(receipt) || !lstatSync(receipt).isFile()) throw new Error(`${ref.at}: named input directory ${path} holds no regular-file ${DIRECTORY_INPUT_RECEIPT}; a directory input binds its producer's receipt, such as a comfy plate --mode occluder output`);
125
+ const bytes = readFileSync(receipt);
126
+ const spec = plateReceiptSpec(dir);
127
+ const declared = receiptSpec(bytes);
128
+ if (declared !== spec) throw new Error(`${ref.at}: ${bound} declares spec ${JSON.stringify(declared)}; a directory input binds a ${spec} receipt, such as a comfy plate --mode occluder output`);
129
+ return { path: bound, sha256: createHash('sha256').update(bytes).digest('hex'), directory: path, spec };
130
+ }
131
+ if (!lstatSync(real).isFile()) throw new Error(`${ref.at}: named input ${path} is not a regular file`);
132
+ return { path, sha256: createHash('sha256').update(readFileSync(file)).digest('hex') };
133
+ });
134
+ }
135
+ export function configInputsLegacy(at: unknown): boolean {
136
+ return typeof at !== 'string' || !Number.isFinite(Date.parse(at)) || Date.parse(at) < Date.parse(CONFIG_INPUTS_SINCE);
137
+ }
138
+ /**
139
+ * Drafts recorded before this date with no `config_inputs` key, on a config
140
+ * that names inputs, WARN DRAFT_CONFIG_INPUTS_UNRECORDED: the config is bound
141
+ * by sha256, so the absent key came from an install that could not declare the
142
+ * inputs, which draft-rig refuses from this date (DRAFT_PARTS_INSTALL_BEHIND).
143
+ */
144
+ export const DRAFT_CONFIG_INPUTS_SINCE = '2026-11-09T00:00:00Z';
145
+ /**
146
+ * How a draft's unbound config-named input is reported, the one rule for check
147
+ * and every reader that runs it (the job record's inventory phase included):
148
+ * `legacy` WARNs DRAFT_RIG_FILE before CONFIG_INPUTS_SINCE, `unrecorded` WARNs
149
+ * DRAFT_CONFIG_INPUTS_UNRECORDED for an absent key before
150
+ * DRAFT_CONFIG_INPUTS_SINCE, and `refused` is a DRAFT_RIG_FILE refusal.
151
+ */
152
+ export function draftUnboundInputs(draft: { at?: unknown; config_inputs?: unknown }): 'legacy' | 'unrecorded' | 'refused' {
153
+ if (configInputsLegacy(draft.at)) return 'legacy';
154
+ return draft.config_inputs === undefined && Date.parse(String(draft.at)) < Date.parse(DRAFT_CONFIG_INPUTS_SINCE) ? 'unrecorded' : 'refused';
155
+ }
@@ -0,0 +1,135 @@
1
+ /** Record a pre-interview version's lost build config or frozen outputs (#171, #344, docs/PROJECT.md § Migrating work done before the interview). */
2
+ import { resolve } from 'node:path';
3
+
4
+ import { asArray, isObject, PROJECT_SCHEMA } from './check.ts';
5
+ import { save } from './choice-writers.ts';
6
+ import { readProject } from './feedback.ts';
7
+ import { inspectPreInterviewOutputs, OUTPUTS_LOST_MALFORMED_MESSAGE, validConfigLoss, validOutputsLoss } from './pre-interview.ts';
8
+ import { refuse, USAGE_EXIT } from './refusal.ts';
9
+ import type { JsonObject } from './templates.ts';
10
+
11
+ export const VERSION_HELP = `version config-lost <n> --note <text> --source <text> [<dir>] [--json]
12
+ Record config_lost: { note, source } on a pre-interview version whose build config did not survive.
13
+ Refuses when the version is not pre_interview: true, carries provenance.config, already declares config_lost, or adds a check refusal.
14
+ version outputs-lost <n> --note <text> --source <text> [<dir>] [--json]
15
+ Record outputs_lost: { note, source, recorded } on a pre-interview version whose frozen outputs did not survive, keeping any readable outputs in outputs[].
16
+ Refuses when the version is not pre_interview: true, no output is lost, already declares outputs_lost, or adds a check refusal.`;
17
+
18
+ type VersionSubcommand = 'config-lost' | 'outputs-lost';
19
+
20
+ function usage(sub: VersionSubcommand, why: string): never {
21
+ const code = sub === 'outputs-lost' ? 'OUTPUTS_LOST_USAGE' : 'CONFIG_LOST_USAGE';
22
+ refuse(code, `version ${sub}: ${why}; ${VERSION_HELP}`, USAGE_EXIT);
23
+ }
24
+
25
+ export function versionCommand(args: string[], now: number): { stdout: string; exit: number } {
26
+ const rawSub = args[0];
27
+ if (rawSub !== 'config-lost' && rawSub !== 'outputs-lost') {
28
+ usage('config-lost', 'expected subcommand config-lost or outputs-lost');
29
+ }
30
+ const sub: VersionSubcommand = rawSub;
31
+ const positional: string[] = [];
32
+ const opts: Record<string, string> = {};
33
+ let json = false;
34
+ for (let i = 1; i < args.length; i++) {
35
+ const arg = args[i] as string;
36
+ if (arg === '--json') {
37
+ if (json) usage(sub, 'duplicate --json');
38
+ json = true;
39
+ } else if (arg === '--note' || arg === '--source') {
40
+ const value = args[++i];
41
+ if (value === undefined || value.startsWith('--') || arg in opts) usage(sub, `${arg} needs one value`);
42
+ opts[arg] = value;
43
+ } else if (arg.startsWith('-')) {
44
+ usage(sub, `unsupported flag ${arg}`);
45
+ } else {
46
+ positional.push(arg);
47
+ }
48
+ }
49
+ const rawN = positional[0];
50
+ const n = rawN === undefined || !/^v?[1-9]\d*$/.test(rawN) ? Number.NaN : Number(rawN.replace(/^v/, ''));
51
+ if (!Number.isSafeInteger(n) || n < 1 || positional.length > 2 || !('--note' in opts) || !('--source' in opts)) {
52
+ usage(sub, 'supply version <n>, --note <text>, --source <text> and at most one project folder');
53
+ }
54
+ const note = opts['--note'] as string;
55
+ const source = opts['--source'] as string;
56
+ const dir = resolve(positional[1] ?? process.cwd());
57
+ const { file, project } = readProject(dir);
58
+ if (project.schema !== PROJECT_SCHEMA) {
59
+ refuse('SCHEMA_UNKNOWN', `schema is ${JSON.stringify(project.schema ?? null)}; version ${sub} reads ${PROJECT_SCHEMA}`);
60
+ }
61
+ const label = `versions[v${n}]`;
62
+ if (sub === 'outputs-lost') {
63
+ if (note.trim() === '' || source.trim() === '') {
64
+ refuse('OUTPUTS_LOST_EMPTY', `${label}.outputs_lost: --note and --source must be non-empty strings; explain the output loss and name its source record`);
65
+ }
66
+ const current = asArray(project.versions).find((v): v is JsonObject => isObject(v) && v.n === n);
67
+ if (current === undefined) {
68
+ refuse('OUTPUTS_LOST_VERSION_UNKNOWN', `${label}: versions[] has no version ${n}; name an existing pre-interview version`);
69
+ }
70
+ if (current.pre_interview !== true) {
71
+ refuse('OUTPUTS_LOST_NOT_PRE_INTERVIEW', `${label}.pre_interview: version ${n} is not pre_interview: true; record outputs_lost only on a migrated pre-interview version, or keep outputs[] on a create-built version`);
72
+ }
73
+ if (Object.hasOwn(current, 'outputs_lost')) {
74
+ refuse('OUTPUTS_LOST_ALREADY_SET', `${label}.outputs_lost: outputs_lost is already recorded on version ${n}; preserve the existing loss declaration`);
75
+ }
76
+ const inspected = inspectPreInterviewOutputs(dir, current, label);
77
+ const malformed = inspected.malformed[0];
78
+ if (malformed !== undefined) {
79
+ refuse(malformed.code, malformed.message);
80
+ }
81
+ const other = inspected.otherRefusals[0];
82
+ if (other !== undefined) {
83
+ refuse(other.code, other.message);
84
+ }
85
+ if (inspected.missing.length === 0) {
86
+ refuse('OUTPUTS_LOST_HAS_OUTPUTS', `${label}.outputs: no output is lost on version ${n} (${inspected.readable.join(', ')}); keep surviving outputs instead of declaring outputs_lost`);
87
+ }
88
+ const next = structuredClone(project);
89
+ const target = asArray(next.versions).find((v): v is JsonObject => isObject(v) && v.n === n)!;
90
+ const missingPaths = new Set(inspected.missing.map((m) => m.path));
91
+ const loss: JsonObject = {
92
+ note,
93
+ source,
94
+ recorded: inspected.missing.map((b) => ({ path: b.path, sha256: b.sha256 })),
95
+ };
96
+ target.outputs = asArray(target.outputs).filter((o) => isObject(o) && typeof o.path === 'string' && !missingPaths.has(o.path));
97
+ target.outputs_lost = loss;
98
+ if (!validOutputsLoss(target, dir)) {
99
+ refuse('PRE_INTERVIEW_OUTPUTS_LOST_MALFORMED', `${label}.outputs_lost: ${OUTPUTS_LOST_MALFORMED_MESSAGE}`);
100
+ }
101
+ save(file, dir, project, next, now);
102
+ return {
103
+ stdout: json ? JSON.stringify(loss, null, 2) : `version outputs-lost: recorded ${label}.outputs_lost (${source}); stage unchanged`,
104
+ exit: 0,
105
+ };
106
+ }
107
+ if (note.trim() === '' || source.trim() === '') {
108
+ refuse('CONFIG_LOST_EMPTY', `${label}.config_lost: --note and --source must be non-empty strings; explain the config loss and name its source record`);
109
+ }
110
+ const current = asArray(project.versions).find((v): v is JsonObject => isObject(v) && v.n === n);
111
+ if (current === undefined) {
112
+ refuse('CONFIG_LOST_VERSION_UNKNOWN', `${label}: versions[] has no version ${n}; name an existing pre-interview version`);
113
+ }
114
+ if (current.pre_interview !== true) {
115
+ refuse('CONFIG_LOST_NOT_PRE_INTERVIEW', `${label}.pre_interview: version ${n} is not pre_interview: true; record config_lost only on a migrated pre-interview version, or keep provenance.config on a create-built version`);
116
+ }
117
+ if (Object.hasOwn(current, 'config_lost')) {
118
+ refuse('CONFIG_LOST_ALREADY_SET', `${label}.config_lost: config_lost is already recorded on version ${n}; preserve the existing loss declaration`);
119
+ }
120
+ if (isObject(current.provenance) && current.provenance.config !== null && current.provenance.config !== undefined) {
121
+ refuse('CONFIG_LOST_HAS_CONFIG', `${label}.provenance.config: version ${n} has provenance.config; keep the surviving build config instead of declaring config_lost`);
122
+ }
123
+ const next = structuredClone(project);
124
+ const target = asArray(next.versions).find((v): v is JsonObject => isObject(v) && v.n === n)!;
125
+ const loss: JsonObject = { note, source };
126
+ target.config_lost = loss;
127
+ if (!validConfigLoss(target)) {
128
+ refuse('PRE_INTERVIEW_CONFIG_LOST_MALFORMED', `${label}.config_lost: record { note, source } with nonempty strings only on a pre-interview version without provenance.config; restore a surviving config instead`);
129
+ }
130
+ save(file, dir, project, next, now);
131
+ return {
132
+ stdout: json ? JSON.stringify(loss, null, 2) : `version config-lost: recorded ${label}.config_lost (${source}); stage unchanged`,
133
+ exit: 0,
134
+ };
135
+ }