@rig-c/create 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +163 -2
- package/docs/CHECKS.md +1665 -0
- package/docs/COLLECTIONS.md +337 -0
- package/docs/CRAFT.md +277 -0
- package/docs/INTERVIEW.md +886 -0
- package/docs/LESSONS.md +172 -0
- package/docs/PROJECT.md +2657 -0
- package/docs/SOURCE.md +225 -0
- package/package.json +44 -4
- package/src/agent-bounce.ts +27 -0
- package/src/agent-guide.ts +237 -0
- package/src/amend.ts +193 -0
- package/src/applied-review.ts +104 -0
- package/src/ask-coverage.ts +69 -0
- package/src/assemble-answers.ts +53 -0
- package/src/cg-gates.ts +211 -0
- package/src/check-reads.ts +106 -0
- package/src/check.ts +1059 -0
- package/src/checkpoint-evidence-writer.ts +125 -0
- package/src/checkpoint-evidence.ts +152 -0
- package/src/choice-writers.ts +271 -0
- package/src/choices.ts +378 -0
- package/src/cli.ts +371 -0
- package/src/collection-answer.ts +136 -0
- package/src/collection-binding-reader.ts +496 -0
- package/src/collection-binding.ts +478 -0
- package/src/collection.ts +348 -0
- package/src/config-inputs.ts +155 -0
- package/src/config-lost.ts +135 -0
- package/src/correction-writers.ts +207 -0
- package/src/corrections.ts +685 -0
- package/src/diff-classes.ts +219 -0
- package/src/diff-map.ts +248 -0
- package/src/direction-citation.ts +13 -0
- package/src/direction-writers.ts +119 -0
- package/src/direction.ts +266 -0
- package/src/drafts.ts +363 -0
- package/src/feedback.ts +744 -0
- package/src/figure-mask.ts +143 -0
- package/src/fix-ledger.ts +355 -0
- package/src/init.ts +265 -0
- package/src/inputs.ts +160 -0
- package/src/intake.ts +158 -0
- package/src/interview.ts +53 -0
- package/src/invariants.ts +132 -0
- package/src/job-acts.ts +80 -0
- package/src/job-advance.ts +65 -0
- package/src/job-close.ts +226 -0
- package/src/job-host.ts +529 -0
- package/src/job-outbox.ts +383 -0
- package/src/job-publication.ts +701 -0
- package/src/job-runtime.ts +99 -0
- package/src/job-writer.ts +123 -0
- package/src/meshes-bust.ts +63 -0
- package/src/next.ts +1506 -0
- package/src/overrides.ts +42 -0
- package/src/package-upgrades.ts +32 -0
- package/src/packages-upgrade-check.ts +6 -0
- package/src/packages-upgrade.ts +168 -0
- package/src/parts-act.ts +96 -0
- package/src/parts-check.ts +242 -0
- package/src/parts-review.ts +196 -0
- package/src/parts-sections.ts +98 -0
- package/src/parts-verdict-record.ts +265 -0
- package/src/parts-verdict.ts +33 -0
- package/src/pause.ts +118 -0
- package/src/pins-check.ts +619 -0
- package/src/pins.ts +17 -0
- package/src/plan-bar-amendments.ts +54 -0
- package/src/plan-history.ts +147 -0
- package/src/plan-writers.ts +202 -0
- package/src/plate-receipt.ts +110 -0
- package/src/pre-interview.ts +302 -0
- package/src/prepared-inputs.ts +113 -0
- package/src/progress-writers.ts +181 -0
- package/src/provenance.ts +301 -0
- package/src/record-direction-target.ts +26 -0
- package/src/record-input.ts +18 -0
- package/src/refusal-baseline.ts +55 -0
- package/src/refusal.ts +30 -0
- package/src/reopen-lapses.ts +80 -0
- package/src/reopen.ts +144 -0
- package/src/republish.ts +93 -0
- package/src/restart.ts +141 -0
- package/src/review-act-runtime.ts +16 -0
- package/src/review-act.ts +592 -0
- package/src/review-package.ts +524 -0
- package/src/review-read.ts +238 -0
- package/src/review.ts +432 -0
- package/src/rig-version-date.ts +22 -0
- package/src/rig-version.ts +1047 -0
- package/src/run-record.ts +232 -0
- package/src/scene-authority.ts +410 -0
- package/src/scene-writers.ts +144 -0
- package/src/scene.ts +284 -0
- package/src/setup-painting.ts +46 -0
- package/src/signable.ts +65 -0
- package/src/signoff-answers.ts +76 -0
- package/src/signoff-checkpoint-act.ts +95 -0
- package/src/signoff-target.ts +127 -0
- package/src/signoff.ts +331 -0
- package/src/split-captions.ts +154 -0
- package/src/stage-progress.ts +379 -0
- package/src/stages.ts +148 -0
- package/src/step-hash.ts +19 -0
- package/src/structure-snapshot.ts +109 -0
- package/src/structure.ts +166 -0
- package/src/templates.ts +16 -0
- package/src/waypoints.ts +163 -0
- package/templates/agent-guide.md +108 -0
- package/templates/collection-agent-guide.md +72 -0
- package/templates/collection.json +252 -0
- package/templates/example-chain/AGENTS.md +134 -0
- package/templates/example-chain/CLAUDE.md +3 -0
- package/templates/example-chain/README.md +13 -0
- package/templates/example-chain/chain.json +2869 -0
- package/templates/example-chain/checkpoints/0a-interview-options/AGENTS.md +134 -0
- package/templates/example-chain/checkpoints/0a-interview-options/CLAUDE.md +3 -0
- package/templates/example-chain/checkpoints/0a-interview-options/evidence/.gitkeep +0 -0
- package/templates/example-chain/checkpoints/0a-interview-options/evidence/approval.txt +1 -0
- package/templates/example-chain/checkpoints/0a-interview-options/evidence/interview/rig_type.json +59 -0
- package/templates/example-chain/checkpoints/0a-interview-options/evidence/split-config.json +1 -0
- package/templates/example-chain/checkpoints/0a-interview-options/inputs/painting.png +0 -0
- package/templates/example-chain/checkpoints/0a-interview-options/inputs/references/.gitkeep +0 -0
- package/templates/example-chain/checkpoints/0a-interview-options/package.json +9 -0
- package/templates/example-chain/checkpoints/0a-interview-options/project.json +295 -0
- package/templates/example-chain/checkpoints/0a-interview-options.json +127 -0
- package/templates/example-chain/checkpoints/0b-plan-pending/AGENTS.md +134 -0
- package/templates/example-chain/checkpoints/0b-plan-pending/CLAUDE.md +3 -0
- package/templates/example-chain/checkpoints/0b-plan-pending/evidence/.gitkeep +0 -0
- package/templates/example-chain/checkpoints/0b-plan-pending/evidence/approval.txt +1 -0
- package/templates/example-chain/checkpoints/0b-plan-pending/evidence/interview/assemble.json +309 -0
- package/templates/example-chain/checkpoints/0b-plan-pending/evidence/interview/reference.json +59 -0
- package/templates/example-chain/checkpoints/0b-plan-pending/evidence/interview/rig_type.json +59 -0
- package/templates/example-chain/checkpoints/0b-plan-pending/evidence/interview/seethrough.json +186 -0
- package/templates/example-chain/checkpoints/0b-plan-pending/evidence/split-config.json +1 -0
- package/templates/example-chain/checkpoints/0b-plan-pending/inputs/painting.png +0 -0
- package/templates/example-chain/checkpoints/0b-plan-pending/inputs/references/.gitkeep +0 -0
- package/templates/example-chain/checkpoints/0b-plan-pending/package.json +9 -0
- package/templates/example-chain/checkpoints/0b-plan-pending/project.json +685 -0
- package/templates/example-chain/checkpoints/0b-plan-pending.json +81 -0
- package/templates/example-chain/checkpoints/1-parts-verdict/AGENTS.md +134 -0
- package/templates/example-chain/checkpoints/1-parts-verdict/CLAUDE.md +3 -0
- package/templates/example-chain/checkpoints/1-parts-verdict/evidence/.gitkeep +0 -0
- package/templates/example-chain/checkpoints/1-parts-verdict/evidence/approval.txt +1 -0
- package/templates/example-chain/checkpoints/1-parts-verdict/evidence/interview/assemble.json +309 -0
- package/templates/example-chain/checkpoints/1-parts-verdict/evidence/interview/reference.json +59 -0
- package/templates/example-chain/checkpoints/1-parts-verdict/evidence/interview/rig_type.json +59 -0
- package/templates/example-chain/checkpoints/1-parts-verdict/evidence/interview/seethrough.json +186 -0
- package/templates/example-chain/checkpoints/1-parts-verdict/evidence/parts-check.json +127 -0
- package/templates/example-chain/checkpoints/1-parts-verdict/evidence/split-config.json +1 -0
- package/templates/example-chain/checkpoints/1-parts-verdict/inputs/painting.png +0 -0
- package/templates/example-chain/checkpoints/1-parts-verdict/inputs/references/.gitkeep +0 -0
- package/templates/example-chain/checkpoints/1-parts-verdict/package.json +9 -0
- package/templates/example-chain/checkpoints/1-parts-verdict/project.json +955 -0
- package/templates/example-chain/checkpoints/1-parts-verdict.json +204 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/AGENTS.md +134 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/CLAUDE.md +3 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/evidence/.gitkeep +0 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/evidence/approval.txt +1 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/evidence/diff-components.json +34 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/evidence/interview/assemble.json +309 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/evidence/interview/bones.json +190 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/evidence/interview/reference.json +59 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/evidence/interview/rig_type.json +59 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/evidence/interview/seethrough.json +186 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/evidence/parts-check.json +127 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/evidence/parts-review.json +88 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/evidence/review-reads/a12dea02d32508acc99386f894af76a14f1c85f45f804db2aa6b4dea5297973f.json +59 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/evidence/split-config.json +1 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/evidence/structure.json +30 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/inputs/painting.png +0 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/inputs/references/.gitkeep +0 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/package.json +9 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/project.json +1198 -0
- package/templates/example-chain/checkpoints/2-rigging-answers.json +254 -0
- package/templates/example-chain/checkpoints/3-rig-review/AGENTS.md +134 -0
- package/templates/example-chain/checkpoints/3-rig-review/CLAUDE.md +3 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/.gitkeep +0 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/approval.txt +1 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/diff-components.json +34 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/assemble.json +309 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/bones.json +190 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/constraints.json +272 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/meshes.json +190 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/motion.json +272 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/reference.json +59 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/rig_type.json +59 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/seethrough.json +186 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/parts-check.json +127 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/parts-review.json +88 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/review-reads/a12dea02d32508acc99386f894af76a14f1c85f45f804db2aa6b4dea5297973f.json +59 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/setup.png +0 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/split-config.json +1 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/structure.json +30 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/v1/config.json +88 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/v1/interview-requirements.json +8 -0
- package/templates/example-chain/checkpoints/3-rig-review/inputs/painting.png +0 -0
- package/templates/example-chain/checkpoints/3-rig-review/inputs/references/.gitkeep +0 -0
- package/templates/example-chain/checkpoints/3-rig-review/package.json +9 -0
- package/templates/example-chain/checkpoints/3-rig-review/project.json +1909 -0
- package/templates/example-chain/checkpoints/3-rig-review.json +424 -0
- package/templates/example-chain/checkpoints/4-direction-post/AGENTS.md +134 -0
- package/templates/example-chain/checkpoints/4-direction-post/CLAUDE.md +3 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/.gitkeep +0 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/approval.txt +1 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/diff-components.json +34 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/assemble.json +309 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/bones.json +190 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/camera_depth.json +102 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/constraints.json +272 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/meshes.json +190 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/motion.json +272 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/post_composition.json +122 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/post_timing.json +102 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/reference.json +59 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/rig_type.json +59 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/seethrough.json +186 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/parts-check.json +127 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/parts-review.json +88 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/review-reads/a12dea02d32508acc99386f894af76a14f1c85f45f804db2aa6b4dea5297973f.json +59 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/setup.png +0 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/split-config.json +1 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/structure.json +30 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/v1/config.json +88 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/v1/interview-requirements.json +8 -0
- package/templates/example-chain/checkpoints/4-direction-post/inputs/painting.png +0 -0
- package/templates/example-chain/checkpoints/4-direction-post/inputs/references/.gitkeep +0 -0
- package/templates/example-chain/checkpoints/4-direction-post/package.json +9 -0
- package/templates/example-chain/checkpoints/4-direction-post/project.json +2302 -0
- package/templates/example-chain/checkpoints/4-direction-post.json +456 -0
- package/templates/example-chain/evidence/.gitkeep +0 -0
- package/templates/example-chain/evidence/approval.txt +1 -0
- package/templates/example-chain/evidence/captions.json +101 -0
- package/templates/example-chain/evidence/diff-components.json +34 -0
- package/templates/example-chain/evidence/direction/v2.json +74 -0
- package/templates/example-chain/evidence/direction-input.json +74 -0
- package/templates/example-chain/evidence/interview/assemble.json +309 -0
- package/templates/example-chain/evidence/interview/bones.json +190 -0
- package/templates/example-chain/evidence/interview/camera_depth.json +102 -0
- package/templates/example-chain/evidence/interview/constraints.json +272 -0
- package/templates/example-chain/evidence/interview/meshes.json +190 -0
- package/templates/example-chain/evidence/interview/motion.json +272 -0
- package/templates/example-chain/evidence/interview/post_composition.json +122 -0
- package/templates/example-chain/evidence/interview/post_timing.json +102 -0
- package/templates/example-chain/evidence/interview/reference.json +59 -0
- package/templates/example-chain/evidence/interview/rig_type.json +59 -0
- package/templates/example-chain/evidence/interview/seethrough.json +186 -0
- package/templates/example-chain/evidence/parts-check.json +127 -0
- package/templates/example-chain/evidence/parts-review.json +88 -0
- package/templates/example-chain/evidence/review-reads/a12dea02d32508acc99386f894af76a14f1c85f45f804db2aa6b4dea5297973f.json +59 -0
- package/templates/example-chain/evidence/scene/s1.json +65 -0
- package/templates/example-chain/evidence/scene-citations.json +9 -0
- package/templates/example-chain/evidence/setup.png +0 -0
- package/templates/example-chain/evidence/split-config.json +1 -0
- package/templates/example-chain/evidence/structure.json +30 -0
- package/templates/example-chain/evidence/v1/config.json +88 -0
- package/templates/example-chain/evidence/v1/interview-requirements.json +8 -0
- package/templates/example-chain/inputs/background.png +0 -0
- package/templates/example-chain/inputs/painting.png +0 -0
- package/templates/example-chain/inputs/references/.gitkeep +0 -0
- package/templates/example-chain/package.json +9 -0
- package/templates/example-chain/project.json +2593 -0
- package/templates/example-diff/diff-components.json +71 -0
- package/templates/example-diff/recomposite_rig.png +0 -0
- package/templates/example-direction/approval.txt +1 -0
- package/templates/example-direction/derived/review/v1/out/check/build/rectangle.png +0 -0
- package/templates/example-direction/derived/review/v1/out/check/build/run.json +40 -0
- package/templates/example-direction/derived/review/v1/out/check/build/skeleton.atlas +8 -0
- package/templates/example-direction/derived/review/v1/out/check/build/skeleton.json +73 -0
- package/templates/example-direction/derived/review/v1/out/check/build/skeleton.model.json +195 -0
- package/templates/example-direction/evidence/direction/v2.json +69 -0
- package/templates/example-direction/evidence/direction/v3.json +59 -0
- package/templates/example-direction/evidence/images/rectangle.png +0 -0
- package/templates/example-direction/evidence/input.json +69 -0
- package/templates/example-direction/evidence/motion.json +33 -0
- package/templates/example-direction/evidence/patch.json +23 -0
- package/templates/example-direction/evidence/rig-config.json +55 -0
- package/templates/example-direction/evidence/still.png +0 -0
- package/templates/example-direction/project.json +372 -0
- package/templates/example-direction/reasoning.txt +1 -0
- package/templates/example-direction/words.txt +1 -0
- package/templates/example-draft/README.md +28 -0
- package/templates/example-draft/config.json +71 -0
- package/templates/example-draft/derived/drafts/lantern/config.json +71 -0
- package/templates/example-draft/derived/drafts/lantern/input/painting_in_rig.png +0 -0
- package/templates/example-draft/derived/drafts/lantern/input/parts/background.png +0 -0
- package/templates/example-draft/derived/drafts/lantern/input/parts/eye_l.png +0 -0
- package/templates/example-draft/derived/drafts/lantern/input/parts/eye_r.png +0 -0
- package/templates/example-draft/derived/drafts/lantern/input/parts/face.png +0 -0
- package/templates/example-draft/derived/drafts/lantern/input/parts/lantern.png +0 -0
- package/templates/example-draft/derived/drafts/lantern/input/parts/torso.png +0 -0
- package/templates/example-draft/derived/drafts/lantern/input/parts.json +188 -0
- package/templates/example-draft/derived/drafts/lantern/input/recomposite_rig.png +0 -0
- package/templates/example-draft/derived/drafts/lantern/interview-requirements.json +8 -0
- package/templates/example-draft/derived/drafts/lantern/out/check/build/run.json +40 -0
- package/templates/example-draft/derived/drafts/lantern/out/check/build/skeleton.atlas +28 -0
- package/templates/example-draft/derived/drafts/lantern/out/check/build/skeleton.json +193 -0
- package/templates/example-draft/derived/drafts/lantern/out/check/build/skeleton.model.json +483 -0
- package/templates/example-draft/derived/drafts/lantern/out/check/build/skeleton.png +0 -0
- package/templates/example-draft/derived/drafts/lantern/out/images/background.png +0 -0
- package/templates/example-draft/derived/drafts/lantern/out/images/eye_l.png +0 -0
- package/templates/example-draft/derived/drafts/lantern/out/images/eye_r.png +0 -0
- package/templates/example-draft/derived/drafts/lantern/out/images/face.png +0 -0
- package/templates/example-draft/derived/drafts/lantern/out/images/lantern.png +0 -0
- package/templates/example-draft/derived/drafts/lantern/out/images/torso.png +0 -0
- package/templates/example-draft/derived/drafts/lantern/out/mesh_report.json +1 -0
- package/templates/example-draft/derived/drafts/lantern/out/motion.json +137 -0
- package/templates/example-draft/derived/drafts/lantern/out/rig.json +102 -0
- package/templates/example-draft/derived/drafts/lantern/out/run.json +52 -0
- package/templates/example-draft/derived/parts/rig/painting_in_rig.png +0 -0
- package/templates/example-draft/derived/parts/rig/parts/background.png +0 -0
- package/templates/example-draft/derived/parts/rig/parts/eye_l.png +0 -0
- package/templates/example-draft/derived/parts/rig/parts/eye_r.png +0 -0
- package/templates/example-draft/derived/parts/rig/parts/face.png +0 -0
- package/templates/example-draft/derived/parts/rig/parts/lantern.png +0 -0
- package/templates/example-draft/derived/parts/rig/parts/torso.png +0 -0
- package/templates/example-draft/derived/parts/rig/parts.json +188 -0
- package/templates/example-draft/derived/parts/rig/recomposite_rig.png +0 -0
- package/templates/example-draft/derived/review/v1/config.json +71 -0
- package/templates/example-draft/derived/review/v1/interview-requirements.json +8 -0
- package/templates/example-draft/derived/review/v1/out/check/build/run.json +40 -0
- package/templates/example-draft/derived/review/v1/out/check/build/skeleton.atlas +28 -0
- package/templates/example-draft/derived/review/v1/out/check/build/skeleton.json +193 -0
- package/templates/example-draft/derived/review/v1/out/check/build/skeleton.model.json +483 -0
- package/templates/example-draft/derived/review/v1/out/check/build/skeleton.png +0 -0
- package/templates/example-draft/derived/review/v1/out/parts/background.png +0 -0
- package/templates/example-draft/derived/review/v1/out/parts/eye_l.png +0 -0
- package/templates/example-draft/derived/review/v1/out/parts/eye_r.png +0 -0
- package/templates/example-draft/derived/review/v1/out/parts/face.png +0 -0
- package/templates/example-draft/derived/review/v1/out/parts/lantern.png +0 -0
- package/templates/example-draft/derived/review/v1/out/parts/torso.png +0 -0
- package/templates/example-draft/derived/review/v1/out/parts.json +188 -0
- package/templates/example-draft/derived/review/v1/out/rig/images/background.png +0 -0
- package/templates/example-draft/derived/review/v1/out/rig/images/eye_l.png +0 -0
- package/templates/example-draft/derived/review/v1/out/rig/images/eye_r.png +0 -0
- package/templates/example-draft/derived/review/v1/out/rig/images/face.png +0 -0
- package/templates/example-draft/derived/review/v1/out/rig/images/lantern.png +0 -0
- package/templates/example-draft/derived/review/v1/out/rig/images/torso.png +0 -0
- package/templates/example-draft/derived/review/v1/out/rig/mesh_report.json +1 -0
- package/templates/example-draft/derived/review/v1/out/rig/motion.json +137 -0
- package/templates/example-draft/derived/review/v1/out/rig/rig.json +102 -0
- package/templates/example-draft/derived/review/v1/out/rig/run.json +52 -0
- package/templates/example-draft/derived/review/v2/config.json +71 -0
- package/templates/example-draft/derived/review/v2/interview-requirements.json +8 -0
- package/templates/example-draft/derived/review/v2/out/check/build/run.json +40 -0
- package/templates/example-draft/derived/review/v2/out/check/build/skeleton.atlas +28 -0
- package/templates/example-draft/derived/review/v2/out/check/build/skeleton.json +193 -0
- package/templates/example-draft/derived/review/v2/out/check/build/skeleton.model.json +483 -0
- package/templates/example-draft/derived/review/v2/out/check/build/skeleton.png +0 -0
- package/templates/example-draft/derived/review/v2/out/parts/background.png +0 -0
- package/templates/example-draft/derived/review/v2/out/parts/eye_l.png +0 -0
- package/templates/example-draft/derived/review/v2/out/parts/eye_r.png +0 -0
- package/templates/example-draft/derived/review/v2/out/parts/face.png +0 -0
- package/templates/example-draft/derived/review/v2/out/parts/lantern.png +0 -0
- package/templates/example-draft/derived/review/v2/out/parts/torso.png +0 -0
- package/templates/example-draft/derived/review/v2/out/parts.json +188 -0
- package/templates/example-draft/derived/review/v2/out/rig/images/background.png +0 -0
- package/templates/example-draft/derived/review/v2/out/rig/images/eye_l.png +0 -0
- package/templates/example-draft/derived/review/v2/out/rig/images/eye_r.png +0 -0
- package/templates/example-draft/derived/review/v2/out/rig/images/face.png +0 -0
- package/templates/example-draft/derived/review/v2/out/rig/images/lantern.png +0 -0
- package/templates/example-draft/derived/review/v2/out/rig/images/torso.png +0 -0
- package/templates/example-draft/derived/review/v2/out/rig/mesh_report.json +1 -0
- package/templates/example-draft/derived/review/v2/out/rig/motion.json +137 -0
- package/templates/example-draft/derived/review/v2/out/rig/rig.json +102 -0
- package/templates/example-draft/derived/review/v2/out/rig/run.json +52 -0
- package/templates/example-draft/evidence/diff-components.json +71 -0
- package/templates/example-draft/evidence/direction/v4.json +64 -0
- package/templates/example-draft/evidence/direction-input-v4.json +64 -0
- package/templates/example-draft/evidence/fix-ledger/lantern.json +17 -0
- package/templates/example-draft/evidence/parts-check.json +127 -0
- package/templates/example-draft/evidence/parts-review.json +88 -0
- package/templates/example-draft/evidence/review-reads/4ed0507381809746443021554e012032be5b5f8417da0f2e525e9b6df16e9f05.json +59 -0
- package/templates/example-draft/evidence/scene/s1.json +65 -0
- package/templates/example-draft/evidence/scene-background.png +0 -0
- package/templates/example-draft/evidence/scene-citations.json +9 -0
- package/templates/example-draft/evidence/structure-decisions.json +9 -0
- package/templates/example-draft/evidence/v1/interview-config.json +71 -0
- package/templates/example-draft/evidence/v1/interview-requirements.json +8 -0
- package/templates/example-draft/evidence/v1/review-config.json +71 -0
- package/templates/example-draft/evidence/v2/interview-config.json +71 -0
- package/templates/example-draft/evidence/v2/interview-requirements.json +8 -0
- package/templates/example-draft/evidence/v2/review-config.json +71 -0
- package/templates/example-draft/input/painting_in_rig.png +0 -0
- package/templates/example-draft/input/parts/background.png +0 -0
- package/templates/example-draft/input/parts/eye_l.png +0 -0
- package/templates/example-draft/input/parts/eye_r.png +0 -0
- package/templates/example-draft/input/parts/face.png +0 -0
- package/templates/example-draft/input/parts/lantern.png +0 -0
- package/templates/example-draft/input/parts/torso.png +0 -0
- package/templates/example-draft/input/parts.json +188 -0
- package/templates/example-draft/input/recomposite_rig.png +0 -0
- package/templates/example-draft/inputs/painting.png +0 -0
- package/templates/example-draft/interview-requirements.json +8 -0
- package/templates/example-draft/out/check/build/run.json +40 -0
- package/templates/example-draft/out/check/build/skeleton.atlas +28 -0
- package/templates/example-draft/out/check/build/skeleton.json +193 -0
- package/templates/example-draft/out/check/build/skeleton.model.json +483 -0
- package/templates/example-draft/out/check/build/skeleton.png +0 -0
- package/templates/example-draft/out/images/background.png +0 -0
- package/templates/example-draft/out/images/eye_l.png +0 -0
- package/templates/example-draft/out/images/eye_r.png +0 -0
- package/templates/example-draft/out/images/face.png +0 -0
- package/templates/example-draft/out/images/lantern.png +0 -0
- package/templates/example-draft/out/images/torso.png +0 -0
- package/templates/example-draft/out/mesh_report.json +1 -0
- package/templates/example-draft/out/motion.json +137 -0
- package/templates/example-draft/out/rig.json +102 -0
- package/templates/example-draft/out/run.json +52 -0
- package/templates/example-draft/outputs/v1/out/check/build/run.json +40 -0
- package/templates/example-draft/outputs/v1/out/check/build/skeleton.atlas +28 -0
- package/templates/example-draft/outputs/v1/out/check/build/skeleton.json +193 -0
- package/templates/example-draft/outputs/v1/out/check/build/skeleton.model.json +483 -0
- package/templates/example-draft/outputs/v1/out/check/build/skeleton.png +0 -0
- package/templates/example-draft/outputs/v1/out/painting_in_rig.png +0 -0
- package/templates/example-draft/outputs/v1/out/parts/background.png +0 -0
- package/templates/example-draft/outputs/v1/out/parts/eye_l.png +0 -0
- package/templates/example-draft/outputs/v1/out/parts/eye_r.png +0 -0
- package/templates/example-draft/outputs/v1/out/parts/face.png +0 -0
- package/templates/example-draft/outputs/v1/out/parts/lantern.png +0 -0
- package/templates/example-draft/outputs/v1/out/parts/torso.png +0 -0
- package/templates/example-draft/outputs/v1/out/parts.json +188 -0
- package/templates/example-draft/outputs/v1/out/rig/images/background.png +0 -0
- package/templates/example-draft/outputs/v1/out/rig/images/eye_l.png +0 -0
- package/templates/example-draft/outputs/v1/out/rig/images/eye_r.png +0 -0
- package/templates/example-draft/outputs/v1/out/rig/images/face.png +0 -0
- package/templates/example-draft/outputs/v1/out/rig/images/lantern.png +0 -0
- package/templates/example-draft/outputs/v1/out/rig/images/torso.png +0 -0
- package/templates/example-draft/outputs/v1/out/rig/mesh_report.json +1 -0
- package/templates/example-draft/outputs/v1/out/rig/motion.json +137 -0
- package/templates/example-draft/outputs/v1/out/rig/rig.json +102 -0
- package/templates/example-draft/outputs/v1/out/rig/run.json +52 -0
- package/templates/example-draft/outputs/v2/out/check/build/run.json +40 -0
- package/templates/example-draft/outputs/v2/out/check/build/skeleton.atlas +28 -0
- package/templates/example-draft/outputs/v2/out/check/build/skeleton.json +193 -0
- package/templates/example-draft/outputs/v2/out/check/build/skeleton.model.json +483 -0
- package/templates/example-draft/outputs/v2/out/check/build/skeleton.png +0 -0
- package/templates/example-draft/outputs/v2/out/painting_in_rig.png +0 -0
- package/templates/example-draft/outputs/v2/out/parts/background.png +0 -0
- package/templates/example-draft/outputs/v2/out/parts/eye_l.png +0 -0
- package/templates/example-draft/outputs/v2/out/parts/eye_r.png +0 -0
- package/templates/example-draft/outputs/v2/out/parts/face.png +0 -0
- package/templates/example-draft/outputs/v2/out/parts/lantern.png +0 -0
- package/templates/example-draft/outputs/v2/out/parts/torso.png +0 -0
- package/templates/example-draft/outputs/v2/out/parts.json +188 -0
- package/templates/example-draft/outputs/v2/out/rig/images/background.png +0 -0
- package/templates/example-draft/outputs/v2/out/rig/images/eye_l.png +0 -0
- package/templates/example-draft/outputs/v2/out/rig/images/eye_r.png +0 -0
- package/templates/example-draft/outputs/v2/out/rig/images/face.png +0 -0
- package/templates/example-draft/outputs/v2/out/rig/images/lantern.png +0 -0
- package/templates/example-draft/outputs/v2/out/rig/images/torso.png +0 -0
- package/templates/example-draft/outputs/v2/out/rig/mesh_report.json +1 -0
- package/templates/example-draft/outputs/v2/out/rig/motion.json +137 -0
- package/templates/example-draft/outputs/v2/out/rig/rig.json +102 -0
- package/templates/example-draft/outputs/v2/out/rig/run.json +52 -0
- package/templates/example-interview-choice.json +1068 -0
- package/templates/example-interview-options/constraint.json +376 -0
- package/templates/example-interview-options/example-assemble.json +376 -0
- package/templates/example-interview-options/example-bones.json +190 -0
- package/templates/example-interview-options/example-camera-depth.json +99 -0
- package/templates/example-interview-options/example-constraints.json +272 -0
- package/templates/example-interview-options/example-meshes.json +190 -0
- package/templates/example-interview-options/example-motion.json +272 -0
- package/templates/example-interview-options/example-picks.json +15 -0
- package/templates/example-interview-options/example-post-composition.json +110 -0
- package/templates/example-interview-options/example-post-timing.json +105 -0
- package/templates/example-interview-options/example-reference.json +63 -0
- package/templates/example-interview-options/example-rig-type.json +63 -0
- package/templates/example-interview-options/example-seethrough.json +190 -0
- package/templates/example-interview-options/fullscreen.json +63 -0
- package/templates/example-interview-options/other-explicit.json +63 -0
- package/templates/example-interview-options/other-pending.json +63 -0
- package/templates/example-interview-options/other-picks.json +16 -0
- package/templates/example-interview.md +213 -0
- package/templates/example-invariants/invariants.json +1339 -0
- package/templates/example-prepared/prepared.svg +1 -0
- package/templates/example-prepared/source.svg +1 -0
- package/templates/example-project.json +4259 -0
- package/templates/example-remesh/AGENTS.md +134 -0
- package/templates/example-remesh/CLAUDE.md +3 -0
- package/templates/example-remesh/evidence/.gitkeep +0 -0
- package/templates/example-remesh/evidence/approval.txt +1 -0
- package/templates/example-remesh/evidence/captions.json +101 -0
- package/templates/example-remesh/evidence/diff-components.json +34 -0
- package/templates/example-remesh/evidence/direction/v4.json +74 -0
- package/templates/example-remesh/evidence/direction/v5.json +74 -0
- package/templates/example-remesh/evidence/direction-input-v4.json +74 -0
- package/templates/example-remesh/evidence/direction-input-v5.json +74 -0
- package/templates/example-remesh/evidence/interview/assemble.json +309 -0
- package/templates/example-remesh/evidence/interview/bones.json +190 -0
- package/templates/example-remesh/evidence/interview/camera_depth.json +103 -0
- package/templates/example-remesh/evidence/interview/constraints.json +272 -0
- package/templates/example-remesh/evidence/interview/meshes.json +190 -0
- package/templates/example-remesh/evidence/interview/motion.json +272 -0
- package/templates/example-remesh/evidence/interview/post_composition.json +123 -0
- package/templates/example-remesh/evidence/interview/post_timing.json +103 -0
- package/templates/example-remesh/evidence/interview/reference.json +59 -0
- package/templates/example-remesh/evidence/interview/rig_type.json +59 -0
- package/templates/example-remesh/evidence/interview/seethrough.json +186 -0
- package/templates/example-remesh/evidence/parts-check.json +127 -0
- package/templates/example-remesh/evidence/parts-review.json +88 -0
- package/templates/example-remesh/evidence/reopen.txt +1 -0
- package/templates/example-remesh/evidence/review-reads/6a17db694db96f1b6049a61a017e0c8e66757b982b8851e0187e561cfcf9af68.json +59 -0
- package/templates/example-remesh/evidence/scene/s1.json +69 -0
- package/templates/example-remesh/evidence/scene-citations.json +9 -0
- package/templates/example-remesh/evidence/setup.png +0 -0
- package/templates/example-remesh/evidence/split-config.json +1 -0
- package/templates/example-remesh/evidence/structure.json +30 -0
- package/templates/example-remesh/evidence/v1/registration/at-source.json +44 -0
- package/templates/example-remesh/evidence/v1/registration/citations.json +68 -0
- package/templates/example-remesh/evidence/v1/registration/compile-report.json +368 -0
- package/templates/example-remesh/evidence/v1/registration/config-source.json +88 -0
- package/templates/example-remesh/evidence/v1/registration/config.json +88 -0
- package/templates/example-remesh/evidence/v1/registration/interview-requirements.json +8 -0
- package/templates/example-remesh/evidence/v1/registration/package-lock.json +11 -0
- package/templates/example-remesh/evidence/v1/registration/path-adapter.json +1 -0
- package/templates/example-remesh/evidence/v1/registration/rigc-run-1.json +44 -0
- package/templates/example-remesh/evidence/v2/registration/at-source.json +44 -0
- package/templates/example-remesh/evidence/v2/registration/citations.json +68 -0
- package/templates/example-remesh/evidence/v2/registration/compile-report.json +368 -0
- package/templates/example-remesh/evidence/v2/registration/config-source.json +88 -0
- package/templates/example-remesh/evidence/v2/registration/config.json +88 -0
- package/templates/example-remesh/evidence/v2/registration/interview-requirements.json +8 -0
- package/templates/example-remesh/evidence/v2/registration/package-lock.json +11 -0
- package/templates/example-remesh/evidence/v2/registration/path-adapter.json +1 -0
- package/templates/example-remesh/evidence/v2/registration/rigc-run-1.json +44 -0
- package/templates/example-remesh/evidence/v3/registration/at-source.json +44 -0
- package/templates/example-remesh/evidence/v3/registration/citations.json +68 -0
- package/templates/example-remesh/evidence/v3/registration/compile-report.json +368 -0
- package/templates/example-remesh/evidence/v3/registration/config-source.json +88 -0
- package/templates/example-remesh/evidence/v3/registration/config.json +88 -0
- package/templates/example-remesh/evidence/v3/registration/interview-requirements.json +8 -0
- package/templates/example-remesh/evidence/v3/registration/package-lock.json +11 -0
- package/templates/example-remesh/evidence/v3/registration/path-adapter.json +1 -0
- package/templates/example-remesh/evidence/v3/registration/rigc-run-1.json +44 -0
- package/templates/example-remesh/inputs/background.png +0 -0
- package/templates/example-remesh/inputs/painting.png +0 -0
- package/templates/example-remesh/inputs/references/.gitkeep +0 -0
- package/templates/example-remesh/package.json +9 -0
- package/templates/example-remesh/project.json +3916 -0
- package/templates/example-reopened/project.json +4568 -0
- package/templates/example-review/config.json +1 -0
- package/templates/example-review/out/check/build/skeleton.model.json +1 -0
- package/templates/example-review/out/parts/piece.svg +1 -0
- package/templates/example-review/out/parts.json +1 -0
- package/templates/example-review/out/rig/mesh_report.json +1 -0
- package/templates/example-review/out/rig/motion.json +1 -0
- package/templates/example-review/out/rig/rig.json +1 -0
- package/templates/example-split/captions.json +357 -0
- package/templates/example-split/config.json +215 -0
- package/templates/legacy-example-project.json +1071 -0
- package/templates/project.json +199 -0
- package/templates/shared/conventions.json +23 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/** Group only producer fields; no image measurement takes place here. */
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { writeFileSync } from 'node:fs';
|
|
4
|
+
import { existsSync, readFileSync } from './check-reads.ts';
|
|
5
|
+
import { join, resolve } from 'node:path';
|
|
6
|
+
|
|
7
|
+
import { checkProject, readJsonObject, type Finding } from './check.ts';
|
|
8
|
+
import { diffReportBytes, reportShape, resolveDiffMapPartsFile, validateDiffMapRecord } from './diff-map.ts';
|
|
9
|
+
import { acquireBoundJobWrite } from './job-writer.ts';
|
|
10
|
+
import { refuse, USAGE_EXIT } from './refusal.ts';
|
|
11
|
+
import type { JsonObject } from './templates.ts';
|
|
12
|
+
|
|
13
|
+
type Component = { id: string; px: number; uncovered_px: number; coverage: string; borders: { part: string }[] };
|
|
14
|
+
type Key = { coverage_band: string; border_parts: string[] };
|
|
15
|
+
export type DiffClass = { id: string; key: Key; members: string[]; px_range: number[]; uncovered_fraction_range: number[] };
|
|
16
|
+
const object = (v: unknown): v is Record<string, unknown> => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
17
|
+
const status = (v: unknown): boolean => typeof v === 'string' && ['in-painting', 'fixed', 'left'].includes(v);
|
|
18
|
+
const nonempty = (v: unknown): v is string => typeof v === 'string' && v.trim().length > 0;
|
|
19
|
+
|
|
20
|
+
/** Quarter-width mixed bands limit the within-class spread to less than 0.25. */
|
|
21
|
+
export function deriveDiffClasses(report: unknown): DiffClass[] {
|
|
22
|
+
if (!object(report) || !reportShape(report)) refuse('DIFF_MAP_REPORT', 'diff_map.report: needs a well-shaped rig-parts-diff-components/1 report; regenerate the report');
|
|
23
|
+
const groups = new Map<string, { key: Key; components: Component[] }>();
|
|
24
|
+
const ids = new Set<string>();
|
|
25
|
+
for (const c of report.components as Component[]) {
|
|
26
|
+
const fraction = c.uncovered_px / c.px;
|
|
27
|
+
if (ids.has(c.id) || fraction > 1 || c.coverage === 'covered' && fraction !== 0 || c.coverage === 'uncovered' && fraction !== 1 || c.coverage === 'mixed' && (fraction <= 0 || fraction >= 1)) {
|
|
28
|
+
refuse('DIFF_MAP_REPORT', `diff_map.report.components ${c.id}: duplicate id or inconsistent coverage counts; regenerate the report`);
|
|
29
|
+
}
|
|
30
|
+
ids.add(c.id);
|
|
31
|
+
const band = Math.floor(fraction * 4);
|
|
32
|
+
const key = { coverage_band: c.coverage === 'mixed' ? `mixed:${(band / 4).toFixed(2)}-${((band + 1) / 4).toFixed(2)}` : c.coverage,
|
|
33
|
+
border_parts: [...new Set(c.borders.map(b => b.part))].sort() };
|
|
34
|
+
const encoded = JSON.stringify(key);
|
|
35
|
+
const group = groups.get(encoded) ?? { key, components: [] };
|
|
36
|
+
group.components.push(c);
|
|
37
|
+
groups.set(encoded, group);
|
|
38
|
+
}
|
|
39
|
+
return [...groups.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([encoded, { key, components }]) => ({
|
|
40
|
+
id: `class-${createHash('sha256').update(encoded).digest('hex')}`, key,
|
|
41
|
+
members: components.map(c => c.id).sort(),
|
|
42
|
+
px_range: [Math.min(...components.map(c => c.px)), Math.max(...components.map(c => c.px))],
|
|
43
|
+
uncovered_fraction_range: [Math.min(...components.map(c => c.uncovered_px / c.px)), Math.max(...components.map(c => c.uncovered_px / c.px))],
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function specific(why: string, key: Key): boolean {
|
|
48
|
+
const tokens = [key.coverage_band, ...key.border_parts];
|
|
49
|
+
return tokens.some(token => {
|
|
50
|
+
const escaped = token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
51
|
+
return new RegExp(`(^|[^\\p{L}\\p{N}_-])${escaped}($|[^\\p{L}\\p{N}_-])`, 'iu').test(why);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Strict decision-only input: callers cannot supply keys or member lists. */
|
|
56
|
+
export function expandDiffTriage(report: unknown, triage: unknown, path: string): JsonObject {
|
|
57
|
+
const fail = (detail: string): never => refuse('DIFF_MAP_TRIAGE', `${path}: ${detail}; use diff-classes and provide only status and why for each class or component id`);
|
|
58
|
+
if (!object(triage) || Object.keys(triage).some(k => !['classes', 'components'].includes(k))) fail('needs classes and/or components decision maps');
|
|
59
|
+
const input = triage as Record<string, unknown>;
|
|
60
|
+
const derived = new Map(deriveDiffClasses(report).map(c => [c.id, c]));
|
|
61
|
+
const ids = new Set((report as { components: Component[] }).components.map(c => c.id));
|
|
62
|
+
const classes: JsonObject[] = [], components: JsonObject[] = [];
|
|
63
|
+
for (const kind of ['classes', 'components']) {
|
|
64
|
+
const entries = input[kind] ?? {};
|
|
65
|
+
if (!object(entries)) fail(`${kind} must be an id-to-decision object`);
|
|
66
|
+
for (const [id, value] of Object.entries(entries as Record<string, unknown>)) {
|
|
67
|
+
if (!object(value) || Object.keys(value).length !== 2 || !status(value.status) || !nonempty(value.why)) fail(`${id} needs only status and nonempty why`);
|
|
68
|
+
const decision = value as { status: string; why: string };
|
|
69
|
+
if (kind === 'classes') {
|
|
70
|
+
const c = derived.get(id);
|
|
71
|
+
if (!c) fail(`unknown class ${id}`);
|
|
72
|
+
const group = c!;
|
|
73
|
+
if (!specific(decision.why, group.key)) refuse('DIFF_MAP_CLASS_WHY', `${path}.${id}.why: name a border part or ${group.key.coverage_band}; inspect this class and state its measured property`);
|
|
74
|
+
classes.push({ id, key: group.key, ...decision, members: group.members });
|
|
75
|
+
components.push(...group.members.map(member => ({ id: member, ...decision, class_id: id })));
|
|
76
|
+
} else {
|
|
77
|
+
if (!ids.has(id)) fail(`unknown component ${id}`);
|
|
78
|
+
components.push({ id, ...decision });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return { classes, components };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Optional class records are audited by both the writer and check. */
|
|
86
|
+
export function checkDiffClasses(report: unknown, map: Record<string, unknown>, path: string, push: (code: string, detail: string) => void): void {
|
|
87
|
+
const components = map.components as Record<string, unknown>[];
|
|
88
|
+
if (map.classes === undefined && !components.some(c => object(c) && c.class_id !== undefined)) return;
|
|
89
|
+
let derived: DiffClass[];
|
|
90
|
+
try { derived = deriveDiffClasses(report); }
|
|
91
|
+
catch (error) { push('DIFF_MAP_CLASS', `${path}: ${error instanceof Error ? error.message : String(error)}`); return; }
|
|
92
|
+
if (!Array.isArray(map.classes)) { push('DIFF_MAP_CLASS', `${path}.classes must be a list; regenerate classes with --triage-file`); return; }
|
|
93
|
+
const expected = new Map(derived.map(c => [c.id, c]));
|
|
94
|
+
const seen = new Set<string>(), owners = new Map<string, Record<string, unknown>>();
|
|
95
|
+
for (const c of map.classes) {
|
|
96
|
+
if (!object(c) || !nonempty(c.id)) { push('DIFF_MAP_CLASS', `${path}.classes: class needs an id`); continue; }
|
|
97
|
+
const group = expected.get(c.id);
|
|
98
|
+
if (!group || seen.has(c.id) || !object(c.key) || Object.keys(c.key).length !== 2
|
|
99
|
+
|| c.key.coverage_band !== group.key.coverage_band || JSON.stringify(c.key.border_parts) !== JSON.stringify(group.key.border_parts)
|
|
100
|
+
|| !Array.isArray(c.members) || c.members.length !== group.members.length || new Set(c.members).size !== c.members.length
|
|
101
|
+
|| c.members.some(id => typeof id !== 'string' || !group.members.includes(id))) {
|
|
102
|
+
push('DIFF_MAP_CLASS', `${path}.classes ${c.id}: key or full member set differs from the derived class, or id repeats`);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
seen.add(c.id);
|
|
106
|
+
if (!status(c.status) || !nonempty(c.why)) push('DIFF_MAP_CLASS', `${path}.classes ${c.id}: needs a valid status and nonempty why`);
|
|
107
|
+
else if (!specific(c.why, group.key)) push('DIFF_MAP_CLASS_WHY', `${path}.classes ${c.id}: why must name a border part or ${group.key.coverage_band}`);
|
|
108
|
+
for (const member of c.members as string[]) {
|
|
109
|
+
if (owners.has(member)) push('DIFF_MAP_CLASS', `${path}.classes ${c.id}: ${member} belongs to more than one class`);
|
|
110
|
+
owners.set(member, c);
|
|
111
|
+
const expanded = components.filter(v => object(v) && v.id === member);
|
|
112
|
+
if (expanded.length !== 1 || expanded[0].class_id !== c.id || expanded[0].status !== c.status || expanded[0].why !== c.why) {
|
|
113
|
+
push('DIFF_MAP_CLASS', `${path}.components ${member}: class id, status and why must equal its class`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
for (const c of components) if (object(c) && c.class_id !== undefined && (!nonempty(c.id) || owners.get(c.id)?.id !== c.class_id)) {
|
|
118
|
+
push('DIFF_MAP_CLASS', `${path}.components ${String(c.id)}: class_id has no matching class membership`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export const DIFF_CLASSES_HELP = `diff-classes [<project dir>] --diff-report <path> [--triage-file <file> | --component '<id>=<status>:<why>'] [--by agent] [--json]
|
|
123
|
+
Read derived classes as JSON: id, key, members, member_count, px_range and uncovered_fraction_range.
|
|
124
|
+
With --triage-file, --component or --by: validate and record the agent's component classification in project.json.diff_map.
|
|
125
|
+
Triage JSON: {"classes":{"<class id>":{"status":"left","why":"<property and observation>"}},"components":{}}.
|
|
126
|
+
Mixed bands have width 0.25; the lower bound is inclusive and upper bound exclusive.`;
|
|
127
|
+
|
|
128
|
+
export function diffClassesCommand(args: string[], now: number = Date.now()): { stdout: string; exit: number } {
|
|
129
|
+
let dir: string | undefined;
|
|
130
|
+
let report: string | undefined;
|
|
131
|
+
let triageFile: string | undefined;
|
|
132
|
+
let by: string | undefined;
|
|
133
|
+
const components: JsonObject[] = [];
|
|
134
|
+
for (let i = 0; i < args.length; i++) {
|
|
135
|
+
const arg = args[i]!;
|
|
136
|
+
if (arg === '--json') continue;
|
|
137
|
+
if (arg === '--diff-report' && report === undefined) report = args[++i];
|
|
138
|
+
else if (arg === '--triage-file' && triageFile === undefined) triageFile = args[++i];
|
|
139
|
+
else if (arg === '--by' && by === undefined) by = args[++i];
|
|
140
|
+
else if (arg === '--component') {
|
|
141
|
+
const value = args[++i] ?? '';
|
|
142
|
+
const eq = value.indexOf('=');
|
|
143
|
+
const colon = value.indexOf(':', eq + 1);
|
|
144
|
+
if (eq < 1 || colon < eq + 2) refuse('DIFF_MAP_STATUS', 'diff_map: --component needs <id>=<status>:<why>', USAGE_EXIT);
|
|
145
|
+
const id = value.slice(0, eq);
|
|
146
|
+
if (components.some((c) => c.id === id)) refuse('DIFF_MAP_STATUS', `diff_map: duplicate component ${id}`, USAGE_EXIT);
|
|
147
|
+
components.push({ id, status: value.slice(eq + 1, colon), why: value.slice(colon + 1) });
|
|
148
|
+
} else if (arg.startsWith('-') || dir !== undefined) {
|
|
149
|
+
refuse('DIFF_MAP_TRIAGE', `diff_map.report: ${DIFF_CLASSES_HELP}`, USAGE_EXIT);
|
|
150
|
+
} else {
|
|
151
|
+
dir = arg;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (!report || triageFile === '' || by === '' || (triageFile !== undefined && components.length > 0)) {
|
|
155
|
+
refuse('DIFF_MAP_TRIAGE', `diff_map.report: ${DIFF_CLASSES_HELP}`, USAGE_EXIT);
|
|
156
|
+
}
|
|
157
|
+
const root = resolve(dir ?? process.cwd());
|
|
158
|
+
const isWrite = triageFile !== undefined || components.length > 0 || by !== undefined;
|
|
159
|
+
if (!isWrite) {
|
|
160
|
+
try {
|
|
161
|
+
const classes = deriveDiffClasses(JSON.parse(diffReportBytes(root, report).toString('utf8')));
|
|
162
|
+
return { stdout: JSON.stringify(classes.map(c => ({ ...c, member_count: c.members.length })), null, 2), exit: 0 };
|
|
163
|
+
} catch (error) {
|
|
164
|
+
refuse('DIFF_MAP_REPORT', `diff_map.report ${report}: ${error instanceof Error ? error.message : String(error)}; generate a report under the project and read it again`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const author = (by ?? 'agent').trim();
|
|
168
|
+
if (author.toLowerCase() !== 'agent') {
|
|
169
|
+
refuse('DIFF_MAP_NOT_AGENT', `diff_map.by: component classification is agent-authored ('--by agent', never a person); received ${JSON.stringify(by)}`);
|
|
170
|
+
}
|
|
171
|
+
const projectFile = join(root, 'project.json');
|
|
172
|
+
if (!existsSync(projectFile)) {
|
|
173
|
+
refuse('PROJECT_JSON_MISSING', `${projectFile} does not exist; run diff-classes in a project folder or name one`);
|
|
174
|
+
}
|
|
175
|
+
const release = acquireBoundJobWrite(root);
|
|
176
|
+
try {
|
|
177
|
+
const read = readJsonObject(projectFile);
|
|
178
|
+
if (!read.ok) refuse('PROJECT_JSON_INVALID', `${projectFile}: ${read.error}`);
|
|
179
|
+
const project = read.value;
|
|
180
|
+
let reportBytes: Buffer;
|
|
181
|
+
let parsedReport: unknown;
|
|
182
|
+
try {
|
|
183
|
+
reportBytes = diffReportBytes(root, report);
|
|
184
|
+
parsedReport = JSON.parse(reportBytes.toString('utf8'));
|
|
185
|
+
} catch (error) {
|
|
186
|
+
refuse('DIFF_MAP_REPORT', `diff_map.report ${report}: ${error instanceof Error ? error.message : String(error)}; generate a report under the project`);
|
|
187
|
+
}
|
|
188
|
+
const expanded = triageFile !== undefined
|
|
189
|
+
? expandDiffTriage(parsedReport, readDiffTriage(resolve(root, triageFile)), 'diff_map')
|
|
190
|
+
: { components };
|
|
191
|
+
const diffMap: JsonObject = {
|
|
192
|
+
by: 'agent',
|
|
193
|
+
at: new Date(now).toISOString(),
|
|
194
|
+
report: { path: report, sha256: createHash('sha256').update(reportBytes).digest('hex') },
|
|
195
|
+
...expanded,
|
|
196
|
+
};
|
|
197
|
+
const partsFile = resolveDiffMapPartsFile(root, project);
|
|
198
|
+
const errors: Finding[] = [];
|
|
199
|
+
validateDiffMapRecord(root, diffMap, 'diff_map', partsFile, errors, {
|
|
200
|
+
requireAgent: true,
|
|
201
|
+
label: 'signoffs[parts-review] diff_map',
|
|
202
|
+
});
|
|
203
|
+
if (errors[0] !== undefined) refuse(errors[0].code, errors[0].message);
|
|
204
|
+
const asRead = structuredClone(project);
|
|
205
|
+
project.diff_map = diffMap;
|
|
206
|
+
const baseline = new Set(checkProject(root, asRead, { now, readOnly: true }).refusals.map(f => `${f.code}\0${f.path}\0${f.message}`));
|
|
207
|
+
const fresh = checkProject(root, project, { now, readOnly: true }).refusals.find(f => !baseline.has(`${f.code}\0${f.path}\0${f.message}`));
|
|
208
|
+
if (fresh !== undefined) refuse(fresh.code, fresh.message);
|
|
209
|
+
writeFileSync(projectFile, `${JSON.stringify(project, null, 2)}\n`);
|
|
210
|
+
return { stdout: JSON.stringify({ diff_map: diffMap }, null, 2), exit: 0 };
|
|
211
|
+
} finally {
|
|
212
|
+
release();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function readDiffTriage(file: string): unknown {
|
|
217
|
+
try { return JSON.parse(readFileSync(file, 'utf8')); }
|
|
218
|
+
catch (error) { refuse('DIFF_MAP_TRIAGE', `diff_map --triage-file ${file}: ${error instanceof Error ? error.message : String(error)}; supply a readable JSON decision file`); }
|
|
219
|
+
}
|
package/src/diff-map.ts
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/** Hash-bound inventory on the latest parts-review sign-off. create never measures differences. */
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { existsSync, readFileSync, realpathSync } from './check-reads.ts';
|
|
4
|
+
import { basename, dirname, isAbsolute, relative, resolve, sep } from 'node:path';
|
|
5
|
+
|
|
6
|
+
import { checkDiffClasses } from './diff-classes.ts';
|
|
7
|
+
import { recordedPartsFinding, recordedPartsVerdict } from './parts-verdict-record.ts';
|
|
8
|
+
import { collectReopens, liveSignoff } from './reopen.ts';
|
|
9
|
+
|
|
10
|
+
import type { Finding } from './check.ts';
|
|
11
|
+
import type { JsonObject } from './templates.ts';
|
|
12
|
+
|
|
13
|
+
export const DIFF_MAP_SINCE = '2026-11-06T00:00:00Z';
|
|
14
|
+
export const DIFF_MAP_AGENT_SINCE = '2026-11-09T00:00:00Z';
|
|
15
|
+
export const DIFF_MAP_MAX_FLOOR_PX = 2;
|
|
16
|
+
export const DIFF_MAP_MAX_THRESHOLD = 40;
|
|
17
|
+
export const DIFF_MAP_REMEDY =
|
|
18
|
+
'generate rig-parts diff-components on the reviewed assembly and record agent component classification with rig-create diff-classes --diff-report <path> --triage-file <file> --by agent (or --component <id>=<status>:<why> --by agent)';
|
|
19
|
+
const SPEC = 'rig-parts-diff-components/1';
|
|
20
|
+
const SHA256 = /^[0-9a-f]{64}$/;
|
|
21
|
+
const object = (v: unknown): v is Record<string, unknown> => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
22
|
+
const nonempty = (v: unknown): v is string => typeof v === 'string' && v.trim().length > 0;
|
|
23
|
+
const hash = (v: unknown): v is string => typeof v === 'string' && SHA256.test(v);
|
|
24
|
+
const integer = (v: unknown): v is number => typeof v === 'number' && Number.isSafeInteger(v) && v >= 0;
|
|
25
|
+
const binding = (v: unknown): boolean => object(v) && nonempty(v.path) && hash(v.sha256);
|
|
26
|
+
|
|
27
|
+
export function reportShape(v: Record<string, unknown>): boolean {
|
|
28
|
+
const d = v.definition;
|
|
29
|
+
const b = v.below_floor;
|
|
30
|
+
return v.spec === SPEC && binding(v.stack) && binding(v.painting) && object(d)
|
|
31
|
+
&& d.metric === 'max-absolute-rgb-channel' && d.comparison === '>' && d.connectivity === 8
|
|
32
|
+
&& d.coordinates === 'native' && d.y === 'down' && d.origin === 'top-left' && d.resampling === 'none'
|
|
33
|
+
&& d.covered_alpha_above === 128 && integer(d.width) && d.width > 0 && integer(d.height) && d.height > 0
|
|
34
|
+
&& integer(d.floor_px) && d.floor_px > 0 && typeof d.threshold === 'number' && Number.isFinite(d.threshold) && d.threshold >= 0 && d.threshold <= 255
|
|
35
|
+
&& (d.inside_sha256 === null || hash(d.inside_sha256))
|
|
36
|
+
&& integer(v.error_px) && integer(v.uncovered_error_px)
|
|
37
|
+
&& object(b) && integer(b.count) && integer(b.px) && integer(b.uncovered_px)
|
|
38
|
+
&& Array.isArray(v.components) && v.components.every((c) => object(c) && nonempty(c.id) && integer(c.px) && c.px > 0
|
|
39
|
+
&& object(c.box) && integer(c.box.x) && integer(c.box.y) && integer(c.box.w) && c.box.w > 0 && integer(c.box.h) && c.box.h > 0
|
|
40
|
+
&& typeof c.coverage === 'string' && ['covered', 'uncovered', 'mixed'].includes(c.coverage) && integer(c.uncovered_px)
|
|
41
|
+
&& Array.isArray(c.borders) && c.borders.every((p) => object(p) && nonempty(p.part) && integer(p.px)));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** A project-relative regular file, contained as written and with links followed. */
|
|
45
|
+
export function diffReportBytes(dir: string, path: string): Buffer {
|
|
46
|
+
if (isAbsolute(path)) throw new Error('report path must be project-relative');
|
|
47
|
+
const file = resolve(dir, path);
|
|
48
|
+
const contained = (root: string, target: string): boolean => {
|
|
49
|
+
const rel = relative(root, target);
|
|
50
|
+
return rel !== '' && rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
|
|
51
|
+
};
|
|
52
|
+
if (!contained(resolve(dir), file) || !contained(realpathSync(dir), realpathSync(file))) throw new Error('report must be under the project');
|
|
53
|
+
return readFileSync(file);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** parts cli.ts assemble --out uses rig/ and render/; src/build.ts writes both beside one another. */
|
|
57
|
+
export function diffStackCandidates(partsFile: string): string[] {
|
|
58
|
+
const dir = dirname(partsFile);
|
|
59
|
+
const candidates = [resolve(dir, 'recomposite_rig.png')];
|
|
60
|
+
if (basename(dir) === 'rig') candidates.push(resolve(dir, '..', 'render', 'recomposite_rig.png'));
|
|
61
|
+
return candidates;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Resolve the reviewed parts.json for stack checking from the latest live parts-review sign-off or latest draft. */
|
|
65
|
+
export function resolveDiffMapPartsFile(dir: string, project: JsonObject): string | undefined {
|
|
66
|
+
const reopens = collectReopens(project, []);
|
|
67
|
+
const signoffs = Array.isArray(project.signoffs)
|
|
68
|
+
? project.signoffs.filter((v): v is JsonObject => object(v))
|
|
69
|
+
: [];
|
|
70
|
+
let latest: JsonObject | undefined;
|
|
71
|
+
let bestAt = -Infinity;
|
|
72
|
+
for (const s of signoffs) {
|
|
73
|
+
if (s.stage !== 'parts-review' || (s.checkpoint ?? null) !== null) continue;
|
|
74
|
+
const at = typeof s.at === 'string' ? Date.parse(s.at) : NaN;
|
|
75
|
+
if (!liveSignoff(reopens, 'parts-review', at)) continue;
|
|
76
|
+
const score = Number.isNaN(at) ? -Infinity : at;
|
|
77
|
+
if (latest === undefined || score >= bestAt) {
|
|
78
|
+
latest = s;
|
|
79
|
+
bestAt = score;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (latest !== undefined && !recordedPartsFinding(dir, project, latest, 'signoffs')) {
|
|
83
|
+
try {
|
|
84
|
+
return recordedPartsVerdict(dir, project, latest.draft_id, latest.evidence, { requireAvailable: false }).partsFile;
|
|
85
|
+
} catch {
|
|
86
|
+
// Fall through to draft lookup when receipt bytes are unavailable.
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const drafts = Array.isArray(project.drafts)
|
|
90
|
+
? project.drafts.filter((v): v is JsonObject => object(v))
|
|
91
|
+
: [];
|
|
92
|
+
const latestDraft = drafts.at(-1);
|
|
93
|
+
if (latestDraft !== undefined && typeof latestDraft.id === 'string') {
|
|
94
|
+
try {
|
|
95
|
+
return recordedPartsVerdict(dir, project, latestDraft.id, undefined, { requireAvailable: false }).partsFile;
|
|
96
|
+
} catch {
|
|
97
|
+
if (object(latestDraft.parts) && typeof latestDraft.parts.path === 'string') {
|
|
98
|
+
const candidate = resolve(dir, latestDraft.parts.path, 'parts.json');
|
|
99
|
+
if (existsSync(candidate) || diffStackCandidates(candidate).some((file) => existsSync(file))) return candidate;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function validateDiffMapRecord(
|
|
107
|
+
dir: string,
|
|
108
|
+
map: unknown,
|
|
109
|
+
path: string,
|
|
110
|
+
partsFile: string | undefined,
|
|
111
|
+
refusals: Finding[],
|
|
112
|
+
options: { requireAgent?: boolean; label?: string } = {},
|
|
113
|
+
): void {
|
|
114
|
+
const label = options.label ?? path;
|
|
115
|
+
const push = (code: string, detail: string): void => {
|
|
116
|
+
refusals.push({ code, path, message: `${label}: ${detail}; ${DIFF_MAP_REMEDY}` });
|
|
117
|
+
};
|
|
118
|
+
if (!object(map)) {
|
|
119
|
+
push('DIFF_MAP_REPORT', 'report needs a project-relative path and lowercase sha256');
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (options.requireAgent) {
|
|
123
|
+
if (map.by !== 'agent') {
|
|
124
|
+
push('DIFF_MAP_NOT_AGENT', `by is ${JSON.stringify(map.by ?? null)}, but component classification is agent-authored ('by': 'agent', never a person)`);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (map.at !== undefined && (typeof map.at !== 'string' || !Number.isFinite(Date.parse(map.at)))) {
|
|
128
|
+
push('DIFF_MAP_NOT_AGENT', 'at must be a readable ISO timestamp');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (!object(map.report) || !nonempty(map.report.path) || !hash(map.report.sha256)) {
|
|
133
|
+
push('DIFF_MAP_REPORT', 'report needs a project-relative path and lowercase sha256');
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
let report: unknown;
|
|
137
|
+
try {
|
|
138
|
+
const bytes = diffReportBytes(dir, map.report.path);
|
|
139
|
+
if (createHash('sha256').update(bytes).digest('hex') !== map.report.sha256) throw new Error('sha256 differs from diff_map.report.sha256');
|
|
140
|
+
report = JSON.parse(bytes.toString('utf8'));
|
|
141
|
+
} catch (error) {
|
|
142
|
+
push('DIFF_MAP_REPORT', `${map.report.path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (!object(report) || !reportShape(report)) {
|
|
146
|
+
push('DIFF_MAP_REPORT', `${map.report.path}: needs a well-shaped ${SPEC} report`);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const components = report.components as { id: string }[];
|
|
150
|
+
const ids = new Set(components.map((c) => c.id));
|
|
151
|
+
if (ids.size !== components.length) {
|
|
152
|
+
push('DIFF_MAP_REPORT', `${map.report.path}: duplicate report component ids`);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const definition = report.definition as { floor_px: number; threshold: number };
|
|
156
|
+
if (definition.floor_px > DIFF_MAP_MAX_FLOOR_PX || definition.threshold > DIFF_MAP_MAX_THRESHOLD) {
|
|
157
|
+
push('DIFF_MAP_POLICY', `${map.report.path}: floor_px ${definition.floor_px} must be <= ${DIFF_MAP_MAX_FLOOR_PX} and threshold ${definition.threshold} must be <= ${DIFF_MAP_MAX_THRESHOLD}`);
|
|
158
|
+
}
|
|
159
|
+
if (partsFile !== undefined) {
|
|
160
|
+
const candidates = diffStackCandidates(partsFile);
|
|
161
|
+
const present = candidates.filter((file) => existsSync(file));
|
|
162
|
+
try {
|
|
163
|
+
if (present.length !== 1) throw new Error(present.length === 0 ? 'neither candidate is on disk' : 'both candidates are on disk, ambiguous');
|
|
164
|
+
const bytes = diffReportBytes(dir, relative(dir, present[0]));
|
|
165
|
+
if (createHash('sha256').update(bytes).digest('hex') !== (report.stack as { sha256: string }).sha256) throw new Error('sha256 differs from report.stack.sha256');
|
|
166
|
+
} catch (error) {
|
|
167
|
+
push('DIFF_MAP_STACK_MISMATCH', `${candidates.map((file) => relative(dir, file)).join(', ')} for ${map.report.path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (!Array.isArray(map.components)) {
|
|
171
|
+
push('DIFF_MAP_STATUS', `${map.report.path}: diff_map.components must be a list of {id, status, why}`);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const listed = new Set<string>();
|
|
175
|
+
for (const c of map.components) {
|
|
176
|
+
if (!object(c) || !nonempty(c.id)) {
|
|
177
|
+
push('DIFF_MAP_STATUS', `${map.report.path}: component needs an id, status and why`);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (!ids.has(c.id)) push('DIFF_MAP_COMPONENT_UNKNOWN', `${map.report.path}: unknown component ${c.id}`);
|
|
181
|
+
if (listed.has(c.id)) push('DIFF_MAP_STATUS', `${map.report.path}: component ${c.id} is listed more than once`);
|
|
182
|
+
listed.add(c.id);
|
|
183
|
+
if (typeof c.status !== 'string' || !['in-painting', 'fixed', 'left'].includes(c.status) || !nonempty(c.why)) {
|
|
184
|
+
push('DIFF_MAP_STATUS', `${map.report.path}: component ${c.id} needs status in-painting, fixed or left and nonempty why`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
for (const id of ids) if (!listed.has(id)) push('DIFF_MAP_COMPONENT_UNLISTED', `${map.report.path}: component ${id} is unlisted`);
|
|
188
|
+
checkDiffClasses(report, map, path, push);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** `partsFile` is returned only by the successful parts-verdict reader. */
|
|
192
|
+
export function checkDiffMap(
|
|
193
|
+
dir: string,
|
|
194
|
+
signoff: JsonObject,
|
|
195
|
+
at: string,
|
|
196
|
+
partsFile: string | undefined,
|
|
197
|
+
refusals: Finding[],
|
|
198
|
+
warnings: Finding[],
|
|
199
|
+
project?: JsonObject,
|
|
200
|
+
): void {
|
|
201
|
+
const signoffPath = `${at}.diff_map`;
|
|
202
|
+
const date = typeof signoff.at === 'string' ? Date.parse(signoff.at) : NaN;
|
|
203
|
+
const predatesAgent = Number.isFinite(date) && date < Date.parse(DIFF_MAP_AGENT_SINCE);
|
|
204
|
+
if (signoff.diff_map !== undefined) {
|
|
205
|
+
if (predatesAgent) {
|
|
206
|
+
warnings.push({
|
|
207
|
+
code: 'DIFF_MAP_ON_SIGNOFF_LEGACY',
|
|
208
|
+
path: signoffPath,
|
|
209
|
+
message: `${at} ${signoffPath}: parts-review sign-off predates ${DIFF_MAP_AGENT_SINCE} and carries component classifications on the human sign-off; preserve this legacy record and record future classifications with rig-create diff-classes --diff-report <path> --triage-file <file> --by agent`,
|
|
210
|
+
});
|
|
211
|
+
if (project?.diff_map === undefined) {
|
|
212
|
+
validateDiffMapRecord(dir, signoff.diff_map, signoffPath, partsFile, refusals, {
|
|
213
|
+
requireAgent: false,
|
|
214
|
+
label: `${at} ${signoffPath}`,
|
|
215
|
+
});
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
} else {
|
|
219
|
+
refusals.push({
|
|
220
|
+
code: 'SIGNOFF_DIFF_MAP_REMOVED',
|
|
221
|
+
path: signoffPath,
|
|
222
|
+
message: `${at} ${signoffPath}: component classification is agent-authored since ${DIFF_MAP_AGENT_SINCE} and cannot sit on a person's parts-review sign-off; remove diff_map from ${at} and record classification with rig-create diff-classes --diff-report <path> --triage-file <file> --by agent`,
|
|
223
|
+
});
|
|
224
|
+
if (project?.diff_map === undefined) return;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const map = project?.diff_map;
|
|
228
|
+
if (map === undefined) {
|
|
229
|
+
if (Number.isFinite(date) && date < Date.parse(DIFF_MAP_SINCE)) {
|
|
230
|
+
warnings.push({
|
|
231
|
+
code: 'DIFF_MAP_PREDATES',
|
|
232
|
+
path: 'diff_map',
|
|
233
|
+
message: `${at} diff_map: no diff_map, sign-off predates ${DIFF_MAP_SINCE}; preserve this record and record classification with rig-create diff-classes --diff-report <path> --triage-file <file> --by agent when parts are reviewed again`,
|
|
234
|
+
});
|
|
235
|
+
} else {
|
|
236
|
+
refusals.push({
|
|
237
|
+
code: 'DIFF_MAP_MISSING',
|
|
238
|
+
path: 'diff_map',
|
|
239
|
+
message: `${at} diff_map: no agent component classification recorded in project.json; ${DIFF_MAP_REMEDY}`,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
validateDiffMapRecord(dir, map, 'diff_map', partsFile, refusals, {
|
|
245
|
+
requireAgent: true,
|
|
246
|
+
label: `${at} diff_map`,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** The fixed-camera N/A exception is identical for direction and provenance readers. */
|
|
2
|
+
import { isObject, type Item } from './check.ts';
|
|
3
|
+
import type { Json, JsonObject } from './templates.ts';
|
|
4
|
+
|
|
5
|
+
export function fixedNotApplicableCamera(section: string, item: Item | undefined, citation: Json | undefined, document: JsonObject | undefined): boolean {
|
|
6
|
+
const camera = document?.camera;
|
|
7
|
+
return section === 'camera'
|
|
8
|
+
&& item?.state.state === 'not_applicable'
|
|
9
|
+
&& isObject(citation) && typeof citation.quote === 'string'
|
|
10
|
+
&& typeof item.state.answer.verbatim === 'string'
|
|
11
|
+
&& item.state.answer.verbatim.includes(citation.quote)
|
|
12
|
+
&& isObject(camera) && Array.isArray(camera.keys) && camera.keys.length === 1;
|
|
13
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/** Immutable direction publication; project.json is the single commit point. */
|
|
2
|
+
import { closeSync, existsSync, lstatSync, mkdirSync, openSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
4
|
+
import { asArray, checkProject, collectItems, collectSignoffs, loadCollection, isObject, type Finding } from './check.ts';
|
|
5
|
+
import { DIRECTION_CONTRACT, DirectionError, checkDirections, directionBinding, directionBytes, directionContract, directionFail, directionHash, directionIdleBytes, directionIdlePath, directionJson, directionPath, directionVersion, objects, patchDirection, readDirection, requireDirectionScene, validateDirectionCitations, validateDirectionRig, validateDirectionScene, validateDirectionShape } from './direction.ts';
|
|
6
|
+
import { collectReopens, liveSignoff } from './reopen.ts';
|
|
7
|
+
import { REVIEW_ACCEPTS } from './stages.ts';
|
|
8
|
+
import { refuse, USAGE_EXIT } from './refusal.ts';
|
|
9
|
+
import { baselineOf, newFindings } from './refusal-baseline.ts';
|
|
10
|
+
import { parseFeedbackAnswers } from './feedback.ts';
|
|
11
|
+
import { handoff } from './review.ts';
|
|
12
|
+
import { acquireBoundJobWrite, prepareVersionIntent, publishCommittedVersion } from './job-writer.ts';
|
|
13
|
+
import { cancelUncommittedIntent, type PendingPublication } from './job-outbox.ts';
|
|
14
|
+
import type { JsonObject } from './templates.ts';
|
|
15
|
+
|
|
16
|
+
export const DIRECTION_HELP = `direction --adopt [<dir>]
|
|
17
|
+
direction write <N> [<dir>] --file <retained JSON> --rig <R> [--answers <version>:<verdict>]
|
|
18
|
+
direction patch <N> [<dir>] --patch-file <retained direction-patch/1> [--answers <version>:<verdict>]
|
|
19
|
+
direction check <N> [<dir>] [--json]
|
|
20
|
+
Documents are immutable evidence/direction/v<N>.json. No rendering or producer builds.
|
|
21
|
+
Capture a made correction with correction record --direction-patch-file before patch ingestion;
|
|
22
|
+
resolve it against the result post. Review exports patches, never invokes this writer.`;
|
|
23
|
+
function usage(why:string):never {refuse('DIRECTION_USAGE',`direction: ${why}; ${DIRECTION_HELP}`,USAGE_EXIT);}
|
|
24
|
+
function openReview(project:JsonObject):void {
|
|
25
|
+
const reopens=collectReopens(project,[]);
|
|
26
|
+
if(collectSignoffs(project,[]).some(s=>s.stage==='direction-review'&&s.checkpoint===null&&liveSignoff(reopens,s.stage,s.at)) || objects(project.versions).some(v=>v.kind===REVIEW_ACCEPTS['direction-review']&&objects(v.verdicts).some(d=>d.review==='direction-review'&&d.verdict==='accepted'&&liveSignoff(reopens,'direction-review',Date.parse(String(d.at))))))directionFail('DIRECTION_REVIEW_CLOSED','signoffs','reopen direction-review before authoring');
|
|
27
|
+
}
|
|
28
|
+
function ownValidation(dir:string,project:JsonObject,now:number):void {const refusals:Finding[]=[];checkDirections({dir,project,refusals},now);if(refusals[0]){const f=refusals[0];directionFail(f.code,f.path,f.message);}}
|
|
29
|
+
function prospective(dir:string,before:JsonObject,next:JsonObject,now:number):void {
|
|
30
|
+
const f=newFindings(baselineOf(checkProject(dir,before,{now})),checkProject(dir,next,{now}).refusals)[0];if(f)directionFail(f.code,f.path,f.message);
|
|
31
|
+
}
|
|
32
|
+
/** A narrow seam for fault-injected transaction tests; CLI never supplies a hook. */
|
|
33
|
+
export interface DirectionWriteHooks { beforeCommit?:()=>void; }
|
|
34
|
+
export function directionCommand(args:string[],now:number,hooks:DirectionWriteHooks={}):{stdout:string;exit:number} {
|
|
35
|
+
try{return command(args,now,hooks);}catch(e){if(e instanceof DirectionError)refuse(e.code,e.message,e.code==='DIRECTION_USAGE'?USAGE_EXIT:1);throw e;}
|
|
36
|
+
}
|
|
37
|
+
function command(args:string[],now:number,hooks:DirectionWriteHooks):{stdout:string;exit:number} {
|
|
38
|
+
const action=args[0];const positional:string[]=[];const opts:Record<string,string>={};const answerValues:string[]=[];let json=false;
|
|
39
|
+
for(let i=1;i<args.length;i++){const arg=args[i] as string;if(arg==='--answers'){const value=args[++i];if(!value||value.startsWith('--'))usage('--answers needs version:verdict');answerValues.push(value);}else if(arg==='--json'){if(json)usage('duplicate json');json=true;}else if(['--file','--rig','--patch-file'].includes(arg)){const value=args[++i];if(!value||value.startsWith('--')||opts[arg]!==undefined)usage(`invalid ${arg}`);opts[arg]=value;}else if(arg.startsWith('-'))usage(`unknown ${arg}`);else positional.push(arg);}
|
|
40
|
+
if(!['--adopt','write','patch','check'].includes(action??'')||positional.length>(action==='--adopt'?1:2))usage('unknown action/directory count');
|
|
41
|
+
const n=Number(positional[0]);if(action!=='--adopt'&&(!Number.isSafeInteger(n)||n<1))usage('N must be positive integer');
|
|
42
|
+
if(action==='write' ? !opts['--file']||!opts['--rig']||Boolean(opts['--patch-file']) : action==='patch' ? !opts['--patch-file']||Boolean(opts['--file']||opts['--rig']) : Object.keys(opts).length>0)usage('flags do not match action');
|
|
43
|
+
if(answerValues.length && action!=='write' && action!=='patch')usage('--answers is write/patch-only');
|
|
44
|
+
if(json&&action!=='check')usage('json is check-only');
|
|
45
|
+
const dir=resolve(positional[action==='--adopt'?0:1]??process.cwd());
|
|
46
|
+
// Root selected by the caller; record itself is still a literal regular file.
|
|
47
|
+
const file=join(dir,'project.json');const original=directionBytes(dir,'project.json');const project=directionJson(original,'project.json');
|
|
48
|
+
if(project.schema!=='rig-create/project@0')directionFail('DIRECTION_SCHEMA','project.json.schema','expected rig-create/project@0');
|
|
49
|
+
const collectionRoot=basename(dirname(dir))==='members'?dirname(dirname(dir)):null;
|
|
50
|
+
const collectionBytes=collectionRoot&&existsSync(join(collectionRoot,'collection.json'))?directionBytes(collectionRoot,'collection.json'):null;
|
|
51
|
+
if(action==='check'){
|
|
52
|
+
const v=directionVersion(project,n);if(v.kind!=='post')directionFail('DIRECTION_VERSION','versions','direction check needs a post');
|
|
53
|
+
const errors:Finding[]=[];checkDirections({dir,project,refusals:errors},now,n);if(errors[0])directionFail(errors[0].code,errors[0].path,errors[0].message);
|
|
54
|
+
const doc=readDirection(dir,project,n,false);
|
|
55
|
+
return{stdout:json?JSON.stringify({schema:'rig-create/direction-check@1',post:n,direction:(v.provenance as JsonObject).direction,document:doc}):`direction: v${n} valid`,exit:0};
|
|
56
|
+
}
|
|
57
|
+
const lock=join(dir,'.direction.lock');let fd:number;
|
|
58
|
+
try{fd=openSync(lock,'wx');}catch{directionFail('DIRECTION_SOURCE_CHANGED','project.json','another direction writer holds the lock');}
|
|
59
|
+
let published:string|null=null;const staged:string[]=[];
|
|
60
|
+
let releasePublication = () => {};
|
|
61
|
+
let publicationIntent: PendingPublication | null = null;
|
|
62
|
+
try{
|
|
63
|
+
releasePublication = acquireBoundJobWrite(dir);
|
|
64
|
+
const next=structuredClone(project);const contract=directionContract(project,now);
|
|
65
|
+
let bytes:Buffer|undefined;let doc:JsonObject|undefined;const snapshots=new Map<string,Buffer>([['project.json',original]]);
|
|
66
|
+
const snapshot=(path:string):Buffer=>{const value=directionBytes(dir,path);snapshots.set(path,value);return value;};
|
|
67
|
+
if(action==='--adopt'){
|
|
68
|
+
if(contract)return{stdout:'direction: already adopted; original adoption preserved',exit:0};
|
|
69
|
+
next.direction_contract={schema:DIRECTION_CONTRACT,adopted_at:new Date(now).toISOString(),grandfathered_posts:objects(project.versions).filter(v=>v.kind==='post').map(v=>v.n as number).sort((a,b)=>a-b)};
|
|
70
|
+
}else{
|
|
71
|
+
if(!contract)directionFail('DIRECTION_CONTRACT_INVALID','direction_contract','adopt direction explicitly first');
|
|
72
|
+
openReview(project);
|
|
73
|
+
if(objects(project.versions).some(v=>Number(v.n)>=n))directionFail('DIRECTION_VERSION','versions','new post number must be unique and later than all versions');
|
|
74
|
+
const answersFeedback=parseFeedbackAnswers(project,n,answerValues);
|
|
75
|
+
if(action==='write'){
|
|
76
|
+
bytes=snapshot(opts['--file'] as string);doc=directionJson(bytes,opts['--file'] as string);validateDirectionShape(doc);
|
|
77
|
+
if(!Number.isSafeInteger(Number(opts['--rig']))||Number(opts['--rig'])!==(doc.rig as JsonObject).version)directionFail('DIRECTION_RIG_UNKNOWN','direction.rig','--rig must name the document rig');
|
|
78
|
+
}else{
|
|
79
|
+
const patchBytes=snapshot(opts['--patch-file'] as string);const patch=directionJson(patchBytes,opts['--patch-file'] as string);
|
|
80
|
+
const source=readDirection(dir,project,patch.source_post,true);const sourceVersion=directionVersion(project,patch.source_post);snapshot(String(((sourceVersion.provenance as JsonObject).direction as JsonObject).path));
|
|
81
|
+
const latest=Math.max(0,...objects(project.versions).filter(v=>v.kind==='post').map(v=>Number(v.n)));
|
|
82
|
+
if(patch.source_post!==latest)directionFail('DIRECTION_PATCH','direction.patch.source_post','patch must target current post');
|
|
83
|
+
doc=patchDirection(project,source,patch);bytes=Buffer.from(`${JSON.stringify(doc,null,2)}\n`);
|
|
84
|
+
}
|
|
85
|
+
// Retain the option evidence read by the citation validator, not only the project bytes.
|
|
86
|
+
for(const item of collectItems(project,{collection:loadCollection(dir)}).filter(i=>['camera_depth','post_timing'].includes(i.id))){
|
|
87
|
+
const answers=[item.raw,...asArray(item.record.history)];
|
|
88
|
+
if(item.state.state==='user'||item.state.state==='delegated')answers.push(item.state.answer);
|
|
89
|
+
for(const answer of answers)if(isObject(answer)&&isObject(answer.choice)&&isObject(answer.choice.set)&&typeof answer.choice.set.path==='string')snapshot(answer.choice.set.path);
|
|
90
|
+
}
|
|
91
|
+
requireDirectionScene(doc, now, 'direction.schema');
|
|
92
|
+
validateDirectionScene(dir, project, doc, true, snapshots);
|
|
93
|
+
validateDirectionRig(dir,project,doc,true);validateDirectionCitations(dir,project,doc,true);
|
|
94
|
+
handoff(dir,Number((doc.rig as JsonObject).version),now);
|
|
95
|
+
snapshot(String(((doc.rig as JsonObject).model as JsonObject).path));
|
|
96
|
+
|
|
97
|
+
const binding={path:directionPath(n),sha256:directionHash(bytes)};
|
|
98
|
+
const version:JsonObject={n,kind:'post',rig_version:(doc.rig as JsonObject).version as number,at:new Date(now).toISOString(),built_by:'agent',built_with:{create:String(doc.schema).slice('rig-create/'.length)},changed:'Authored camera and timing direction',outputs:[binding],evidence:[],verdicts:[],answers_feedback:answersFeedback,review_origin:null,provenance:{config:binding,direction:binding,sections:{camera:[{entry:'camera',...((doc.citations as JsonObject).camera as JsonObject)}],timing:[{entry:'timing',...((doc.citations as JsonObject).timing as JsonObject)}]}}};
|
|
99
|
+
if(action==='patch'){const patch=directionJson(snapshots.get(opts['--patch-file'] as string) as Buffer,'direction.patch');version.direction_origin={source_post:patch.source_post as number,patch:directionBinding(dir,opts['--patch-file'] as string),result:binding};}
|
|
100
|
+
next.versions=[...asArray(next.versions),version];
|
|
101
|
+
// Validate using the exact staged bytes at their final immutable path, before committing the record.
|
|
102
|
+
const parent=dirname(join(dir,binding.path));let current=dir;
|
|
103
|
+
for(const part of ['evidence','direction']){current=join(current,part);if(existsSync(current)){if(!lstatSync(current).isDirectory()||lstatSync(current).isSymbolicLink())directionFail('DIRECTION_FILE',binding.path,'document parent must be a real directory');}else mkdirSync(current);}
|
|
104
|
+
if(existsSync(join(dir,binding.path)))directionFail('DIRECTION_SOURCE_CHANGED',binding.path,'immutable document already exists');
|
|
105
|
+
const temp=join(parent,`.direction-${crypto.randomUUID()}.tmp`);staged.push(temp);writeFileSync(temp,bytes,{flag:'wx'});renameSync(temp,join(dir,binding.path));published=join(dir,binding.path);
|
|
106
|
+
}
|
|
107
|
+
ownValidation(dir,next,now);prospective(dir,project,next,now);
|
|
108
|
+
hooks.beforeCommit?.();
|
|
109
|
+
if(collectionRoot && collectionBytes && !directionBytes(collectionRoot,'collection.json').equals(collectionBytes))directionFail('DIRECTION_SOURCE_CHANGED','collection.json','inherited answer context changed during publication');
|
|
110
|
+
// The scene's idle frames are re-read through the record binding that admitted them, not as a retained path.
|
|
111
|
+
for(const[path,expected]of snapshots)if(!(doc&&directionIdlePath(path)?directionIdleBytes(dir,project,Number((doc.rig as JsonObject).version),{path,sha256:directionHash(expected)}):directionBytes(dir,path)).equals(expected))directionFail('DIRECTION_SOURCE_CHANGED',path,'source bytes changed during publication');
|
|
112
|
+
if(bytes&&doc){const target=directionPath(n);if(!directionBytes(dir,target).equals(bytes))directionFail('DIRECTION_SOURCE_CHANGED',target,'published document changed before commit');}
|
|
113
|
+
const recordBytes = Buffer.from(`${JSON.stringify(next,null,2)}\n`);
|
|
114
|
+
if (action !== '--adopt') publicationIntent = prepareVersionIntent(dir, recordBytes, n);
|
|
115
|
+
const temp=join(dir,`.direction-${crypto.randomUUID()}.tmp`);staged.push(temp);writeFileSync(temp,recordBytes,{flag:'wx'});renameSync(temp,file);published=null;
|
|
116
|
+
if (action !== '--adopt') publishCommittedVersion(dir, n);
|
|
117
|
+
return{stdout:action==='--adopt'?'direction: adopted; historical posts preserved':`direction: v${n} recorded; stage unchanged`,exit:0};
|
|
118
|
+
}finally{cancelUncommittedIntent(dir,publicationIntent);if(published)rmSync(published,{force:true});for(const path of staged)rmSync(path,{force:true});closeSync(fd);rmSync(lock);releasePublication();}
|
|
119
|
+
}
|