paperlint 2.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/.github/dependabot.yml +72 -0
- package/.github/workflows/ci.yml +297 -0
- package/.github/workflows/dependabot-automerge.yml +70 -0
- package/.github/workflows/pr-title.yml +59 -0
- package/.github/workflows/release.yml +54 -0
- package/CLAUDE.md +598 -0
- package/CONTRIBUTING.md +159 -0
- package/LICENSE +21 -0
- package/README.md +240 -0
- package/action.harness.mjs +287 -0
- package/action.mutations.mjs +162 -0
- package/action.yml +138 -0
- package/bin/rpp.mjs +43 -0
- package/dist/action-ref.d.ts +12 -0
- package/dist/action-ref.d.ts.map +1 -0
- package/dist/action-ref.js +16 -0
- package/dist/action-ref.js.map +1 -0
- package/dist/adapters/banal/failure.d.ts +73 -0
- package/dist/adapters/banal/failure.d.ts.map +1 -0
- package/dist/adapters/banal/failure.js +58 -0
- package/dist/adapters/banal/failure.js.map +1 -0
- package/dist/adapters/banal/index.d.ts +17 -0
- package/dist/adapters/banal/index.d.ts.map +1 -0
- package/dist/adapters/banal/index.js +56 -0
- package/dist/adapters/banal/index.js.map +1 -0
- package/dist/adapters/banal/install.d.ts +26 -0
- package/dist/adapters/banal/install.d.ts.map +1 -0
- package/dist/adapters/banal/install.js +15 -0
- package/dist/adapters/banal/install.js.map +1 -0
- package/dist/adapters/banal/invocation.d.ts +48 -0
- package/dist/adapters/banal/invocation.d.ts.map +1 -0
- package/dist/adapters/banal/invocation.js +43 -0
- package/dist/adapters/banal/invocation.js.map +1 -0
- package/dist/adapters/banal/locate.d.ts +50 -0
- package/dist/adapters/banal/locate.d.ts.map +1 -0
- package/dist/adapters/banal/locate.js +34 -0
- package/dist/adapters/banal/locate.js.map +1 -0
- package/dist/adapters/banal/output.d.ts +27 -0
- package/dist/adapters/banal/output.d.ts.map +1 -0
- package/dist/adapters/banal/output.js +112 -0
- package/dist/adapters/banal/output.js.map +1 -0
- package/dist/adapters/banal/pin.d.ts +19 -0
- package/dist/adapters/banal/pin.d.ts.map +1 -0
- package/dist/adapters/banal/pin.js +15 -0
- package/dist/adapters/banal/pin.js.map +1 -0
- package/dist/adapters/banal/probe.d.ts +12 -0
- package/dist/adapters/banal/probe.d.ts.map +1 -0
- package/dist/adapters/banal/probe.js +27 -0
- package/dist/adapters/banal/probe.js.map +1 -0
- package/dist/adapters/banal/run.d.ts +89 -0
- package/dist/adapters/banal/run.d.ts.map +1 -0
- package/dist/adapters/banal/run.js +104 -0
- package/dist/adapters/banal/run.js.map +1 -0
- package/dist/adapters/banal/settings.d.ts +18 -0
- package/dist/adapters/banal/settings.d.ts.map +1 -0
- package/dist/adapters/banal/settings.js +29 -0
- package/dist/adapters/banal/settings.js.map +1 -0
- package/dist/adapters/banal/xml.d.ts +48 -0
- package/dist/adapters/banal/xml.d.ts.map +1 -0
- package/dist/adapters/banal/xml.js +67 -0
- package/dist/adapters/banal/xml.js.map +1 -0
- package/dist/adapters/curl/download.io.d.ts +14 -0
- package/dist/adapters/curl/download.io.d.ts.map +1 -0
- package/dist/adapters/curl/download.io.js +69 -0
- package/dist/adapters/curl/download.io.js.map +1 -0
- package/dist/adapters/curl/index.d.ts +6 -0
- package/dist/adapters/curl/index.d.ts.map +1 -0
- package/dist/adapters/curl/index.js +6 -0
- package/dist/adapters/curl/index.js.map +1 -0
- package/dist/adapters/memory/index.d.ts +43 -0
- package/dist/adapters/memory/index.d.ts.map +1 -0
- package/dist/adapters/memory/index.js +79 -0
- package/dist/adapters/memory/index.js.map +1 -0
- package/dist/adapters/node/files.io.d.ts +3 -0
- package/dist/adapters/node/files.io.d.ts.map +1 -0
- package/dist/adapters/node/files.io.js +31 -0
- package/dist/adapters/node/files.io.js.map +1 -0
- package/dist/adapters/node/host.io.d.ts +3 -0
- package/dist/adapters/node/host.io.d.ts.map +1 -0
- package/dist/adapters/node/host.io.js +14 -0
- package/dist/adapters/node/host.io.js.map +1 -0
- package/dist/adapters/node/index.d.ts +25 -0
- package/dist/adapters/node/index.d.ts.map +1 -0
- package/dist/adapters/node/index.js +14 -0
- package/dist/adapters/node/index.js.map +1 -0
- package/dist/adapters/node/process.io.d.ts +14 -0
- package/dist/adapters/node/process.io.d.ts.map +1 -0
- package/dist/adapters/node/process.io.js +41 -0
- package/dist/adapters/node/process.io.js.map +1 -0
- package/dist/adapters/node/workspace.io.d.ts +4 -0
- package/dist/adapters/node/workspace.io.d.ts.map +1 -0
- package/dist/adapters/node/workspace.io.js +33 -0
- package/dist/adapters/node/workspace.io.js.map +1 -0
- package/dist/adapters/pdfjs/fill.d.ts +42 -0
- package/dist/adapters/pdfjs/fill.d.ts.map +1 -0
- package/dist/adapters/pdfjs/fill.js +91 -0
- package/dist/adapters/pdfjs/fill.js.map +1 -0
- package/dist/build-engine.d.ts +48 -0
- package/dist/build-engine.d.ts.map +1 -0
- package/dist/build-engine.js +148 -0
- package/dist/build-engine.js.map +1 -0
- package/dist/build.d.ts +163 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +575 -0
- package/dist/build.js.map +1 -0
- package/dist/cli.d.ts +151 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +951 -0
- package/dist/cli.js.map +1 -0
- package/dist/doctor.d.ts +42 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +280 -0
- package/dist/doctor.js.map +1 -0
- package/dist/domain/geometry.d.ts +71 -0
- package/dist/domain/geometry.d.ts.map +1 -0
- package/dist/domain/geometry.js +35 -0
- package/dist/domain/geometry.js.map +1 -0
- package/dist/domain/host.d.ts +16 -0
- package/dist/domain/host.d.ts.map +1 -0
- package/dist/domain/host.js +8 -0
- package/dist/domain/host.js.map +1 -0
- package/dist/domain/page-layout.d.ts +34 -0
- package/dist/domain/page-layout.d.ts.map +1 -0
- package/dist/domain/page-layout.js +8 -0
- package/dist/domain/page-layout.js.map +1 -0
- package/dist/domain/paths.d.ts +5 -0
- package/dist/domain/paths.d.ts.map +1 -0
- package/dist/domain/paths.js +2 -0
- package/dist/domain/paths.js.map +1 -0
- package/dist/domain/result.d.ts +23 -0
- package/dist/domain/result.d.ts.map +1 -0
- package/dist/domain/result.js +10 -0
- package/dist/domain/result.js.map +1 -0
- package/dist/domain/sha256.d.ts +7 -0
- package/dist/domain/sha256.d.ts.map +1 -0
- package/dist/domain/sha256.js +14 -0
- package/dist/domain/sha256.js.map +1 -0
- package/dist/domain/text.d.ts +6 -0
- package/dist/domain/text.d.ts.map +1 -0
- package/dist/domain/text.js +7 -0
- package/dist/domain/text.js.map +1 -0
- package/dist/engine.d.ts +93 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +119 -0
- package/dist/engine.js.map +1 -0
- package/dist/exit-code.d.ts +22 -0
- package/dist/exit-code.d.ts.map +1 -0
- package/dist/exit-code.js +10 -0
- package/dist/exit-code.js.map +1 -0
- package/dist/facts-file.d.ts +96 -0
- package/dist/facts-file.d.ts.map +1 -0
- package/dist/facts-file.js +134 -0
- package/dist/facts-file.js.map +1 -0
- package/dist/hooks-settings.d.ts +141 -0
- package/dist/hooks-settings.d.ts.map +1 -0
- package/dist/hooks-settings.js +306 -0
- package/dist/hooks-settings.js.map +1 -0
- package/dist/init.d.ts +201 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +579 -0
- package/dist/init.js.map +1 -0
- package/dist/latex-log.d.ts +80 -0
- package/dist/latex-log.d.ts.map +1 -0
- package/dist/latex-log.js +187 -0
- package/dist/latex-log.js.map +1 -0
- package/dist/latex-loop.d.ts +129 -0
- package/dist/latex-loop.d.ts.map +1 -0
- package/dist/latex-loop.js +113 -0
- package/dist/latex-loop.js.map +1 -0
- package/dist/link-skills.d.ts +51 -0
- package/dist/link-skills.d.ts.map +1 -0
- package/dist/link-skills.js +199 -0
- package/dist/link-skills.js.map +1 -0
- package/dist/new-paper.d.ts +48 -0
- package/dist/new-paper.d.ts.map +1 -0
- package/dist/new-paper.js +110 -0
- package/dist/new-paper.js.map +1 -0
- package/dist/pdf-facts.d.ts +44 -0
- package/dist/pdf-facts.d.ts.map +1 -0
- package/dist/pdf-facts.js +239 -0
- package/dist/pdf-facts.js.map +1 -0
- package/dist/pdf-geometry.d.ts +170 -0
- package/dist/pdf-geometry.d.ts.map +1 -0
- package/dist/pdf-geometry.js +158 -0
- package/dist/pdf-geometry.js.map +1 -0
- package/dist/ports/download.d.ts +9 -0
- package/dist/ports/download.d.ts.map +1 -0
- package/dist/ports/download.js +2 -0
- package/dist/ports/download.js.map +1 -0
- package/dist/ports/files.d.ts +11 -0
- package/dist/ports/files.d.ts.map +1 -0
- package/dist/ports/files.js +2 -0
- package/dist/ports/files.js.map +1 -0
- package/dist/ports/measure-geometry.d.ts +8 -0
- package/dist/ports/measure-geometry.d.ts.map +1 -0
- package/dist/ports/measure-geometry.js +2 -0
- package/dist/ports/measure-geometry.js.map +1 -0
- package/dist/ports/process.d.ts +45 -0
- package/dist/ports/process.d.ts.map +1 -0
- package/dist/ports/process.js +2 -0
- package/dist/ports/process.js.map +1 -0
- package/dist/ports/tool-installer.d.ts +29 -0
- package/dist/ports/tool-installer.d.ts.map +1 -0
- package/dist/ports/tool-installer.js +2 -0
- package/dist/ports/tool-installer.js.map +1 -0
- package/dist/ports/workspace.d.ts +18 -0
- package/dist/ports/workspace.d.ts.map +1 -0
- package/dist/ports/workspace.js +2 -0
- package/dist/ports/workspace.js.map +1 -0
- package/dist/rules-config.d.ts +34 -0
- package/dist/rules-config.d.ts.map +1 -0
- package/dist/rules-config.js +132 -0
- package/dist/rules-config.js.map +1 -0
- package/dist/structure.d.ts +34 -0
- package/dist/structure.d.ts.map +1 -0
- package/dist/structure.js +149 -0
- package/dist/structure.js.map +1 -0
- package/dist/tex-requirements.d.ts +43 -0
- package/dist/tex-requirements.d.ts.map +1 -0
- package/dist/tex-requirements.js +127 -0
- package/dist/tex-requirements.js.map +1 -0
- package/dist/toolchain.d.ts +159 -0
- package/dist/toolchain.d.ts.map +1 -0
- package/dist/toolchain.js +542 -0
- package/dist/toolchain.js.map +1 -0
- package/dist/types.d.ts +110 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/docs/configuration.md +235 -0
- package/docs/e2e.md +152 -0
- package/docs/incidents.md +59 -0
- package/docs/install.md +170 -0
- package/docs/optional-rules.md +107 -0
- package/docs/package-shape-options.md +262 -0
- package/docs/prior-art/README.md +76 -0
- package/docs/prior-art/blocking-vs-advisory.md +83 -0
- package/docs/prior-art/content-delivery.md +124 -0
- package/docs/prior-art/multi-mode-tools.md +106 -0
- package/docs/prior-art/nondeterministic-checks.md +99 -0
- package/docs/prior-art/package-location.md +422 -0
- package/docs/prior-art/paper-folder-scaffolding.md +538 -0
- package/docs/prior-art/readme-structure.md +69 -0
- package/docs/prior-art/repro/README.md +92 -0
- package/docs/prior-art/repro/claim1-allowedtools.mjs +66 -0
- package/docs/prior-art/repro/claim1-at2.mjs +40 -0
- package/docs/prior-art/repro/claim1-crosschannel.mjs +54 -0
- package/docs/prior-art/repro/claim1-frontmatter.mjs +76 -0
- package/docs/prior-art/repro/claim1-hook-payload-reporter.mjs +10 -0
- package/docs/prior-art/repro/claim1-plugin-frontmatter.mjs +27 -0
- package/docs/prior-art/repro/claim1-plugin-skill.mjs +52 -0
- package/docs/prior-art/repro/claim1-project-skill.mjs +81 -0
- package/docs/prior-art/repro/claim2-marketplace-flat-asclaimed.json +1 -0
- package/docs/prior-art/repro/claim2-marketplace-negative-control.json +1 -0
- package/docs/prior-art/repro/claim2-marketplace-nested-exact.json +9 -0
- package/docs/prior-art/repro/claim2-marketplace-nested-noversion.json +9 -0
- package/docs/prior-art/repro/claim2-marketplace-nested-range.json +1 -0
- package/docs/prior-art/repro/claim3-imports.mjs +50 -0
- package/docs/prior-art/repro/claim4-find-package-json.mjs +8 -0
- package/docs/prior-art/repro/claim4-package-dir.mjs +39 -0
- package/docs/prior-art/repro/claim4-parent-arg.mjs +17 -0
- package/docs/prior-art/repro/claim4-resolve-apis.mjs +21 -0
- package/docs/prior-art/repro/claim4-setup-consumers.mjs +45 -0
- package/docs/prior-art/repro/claim4-yarn-pnp.mjs +70 -0
- package/docs/prior-art/repro/claim5-bin-launch.mjs +39 -0
- package/docs/prior-art/repro/claim5-exports-mutation.mjs +57 -0
- package/docs/prior-art/repro/claim5-resolved-location-and-bin.mjs +33 -0
- package/docs/prior-art/repro/claim6-candidate-ambiguity.mjs +17 -0
- package/docs/prior-art/repro/claim6-doc-path-candidates.mjs +27 -0
- package/docs/prior-art/test-tooling.md +131 -0
- package/docs/rules.md +58 -0
- package/docs/texlive-install-decision.md +230 -0
- package/docs/toolchain.md +152 -0
- package/eslint-rules/doc-fields.harness.mjs +336 -0
- package/eslint-rules/doc-fields.mjs +186 -0
- package/eslint-rules/doc-fields.mutations.mjs +96 -0
- package/eslint-rules/install-path-literals.harness.mjs +121 -0
- package/eslint-rules/install-path-literals.mjs +108 -0
- package/eslint-rules/install-path-literals.mutations.mjs +62 -0
- package/eslint-rules/latex-language.harness.mjs +599 -0
- package/eslint-rules/latex-language.mjs +591 -0
- package/eslint-rules/latex-language.mutations.mjs +196 -0
- package/eslint-rules/paper-research-question.harness.mjs +146 -0
- package/eslint-rules/paper-research-question.mjs +180 -0
- package/eslint-rules/paper-research-question.mutations.mjs +127 -0
- package/eslint-rules/paper-stages.harness.mjs +356 -0
- package/eslint-rules/paper-stages.mjs +455 -0
- package/eslint-rules/paper-stages.mutations.mjs +157 -0
- package/eslint-rules/paper-typography.harness.mjs +291 -0
- package/eslint-rules/paper-typography.mjs +313 -0
- package/eslint-rules/paper-typography.mutations.mjs +131 -0
- package/eslint-rules/papers.harness.mjs +259 -0
- package/eslint-rules/papers.mjs +166 -0
- package/eslint-rules/papers.mutations.mjs +186 -0
- package/eslint-rules/pdf-last-page-balance.harness.mjs +206 -0
- package/eslint-rules/pdf-last-page-balance.mjs +208 -0
- package/eslint-rules/review-findings-cause.harness.mjs +228 -0
- package/eslint-rules/review-findings-cause.mjs +135 -0
- package/eslint-rules/review-findings-cause.mutations.mjs +72 -0
- package/eslint-rules/temp-root-realpath.harness.mjs +176 -0
- package/eslint-rules/temp-root-realpath.mjs +129 -0
- package/eslint-rules/temp-root-realpath.mutations.mjs +99 -0
- package/eslint-rules/tex-build.harness.mjs +753 -0
- package/eslint-rules/tex-build.mjs +322 -0
- package/eslint-rules/tex-build.mutations.mjs +258 -0
- package/eslint.config.mjs +521 -0
- package/fixtures/build-e2e/acmart/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/acmart/paper.tex +11 -0
- package/fixtures/build-e2e/acmart/venue.json +1 -0
- package/fixtures/build-e2e/broken/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/broken/paper.tex +7 -0
- package/fixtures/build-e2e/cite/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/cite/build.sh +5 -0
- package/fixtures/build-e2e/cite/paper.tex +10 -0
- package/fixtures/build-e2e/cite/refs.bib +9 -0
- package/fixtures/build-e2e/empty/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/empty/paper.tex +6 -0
- package/fixtures/build-e2e/fallback/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/fallback/paper.tex +11 -0
- package/fixtures/build-e2e/guards/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/guards/paper.tex +10 -0
- package/fixtures/build-e2e/no-source/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/unbalanced/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/unbalanced/paper.tex +28 -0
- package/fixtures/build-e2e/unbalanced/refs.bib +269 -0
- package/fixtures/install-path-literals/clean.fixture.mjs +3 -0
- package/fixtures/install-path-literals/clean.md +15 -0
- package/fixtures/install-path-literals/defect.fixture.mjs +3 -0
- package/fixtures/install-path-literals/defect.md +14 -0
- package/fixtures/latex-language/clean.tex +50 -0
- package/fixtures/latex-language/defect.tex +52 -0
- package/fixtures/paper-research-question/comment-only/PIPELINE-STATUS.md +9 -0
- package/fixtures/paper-research-question/comment-only/paper.tex +7 -0
- package/fixtures/paper-research-question/declared-not-in-paper/PIPELINE-STATUS.md +10 -0
- package/fixtures/paper-research-question/declared-not-in-paper/paper.tex +6 -0
- package/fixtures/paper-research-question/draft/PIPELINE-STATUS.md +6 -0
- package/fixtures/paper-research-question/draft/paper.tex +2 -0
- package/fixtures/paper-research-question/markdown-no-rq/PIPELINE-STATUS.md +9 -0
- package/fixtures/paper-research-question/markdown-no-rq/paper.md +4 -0
- package/fixtures/paper-research-question/shipped-no-rq/PIPELINE-STATUS.md +12 -0
- package/fixtures/paper-research-question/shipped-no-rq/paper.tex +3 -0
- package/fixtures/paper-research-question/shipped-with-rq/PIPELINE-STATUS.md +10 -0
- package/fixtures/paper-research-question/shipped-with-rq/paper.tex +2 -0
- package/fixtures/paper-stages/authors-ran/PIPELINE-STATUS.md +16 -0
- package/fixtures/paper-stages/marker-in-prose/PIPELINE-STATUS.md +17 -0
- package/fixtures/paper-stages/nofile/PIPELINE-STATUS.md +8 -0
- package/fixtures/paper-stages/noheader/PIPELINE-STATUS.md +1 -0
- package/fixtures/paper-stages/noheader/versions/2026-07-22-submitted.pdf +0 -0
- package/fixtures/paper-stages/nothing/PIPELINE-STATUS.md +3 -0
- package/fixtures/paper-stages/ok/PIPELINE-STATUS.md +9 -0
- package/fixtures/paper-stages/ok/versions/2026-07-22-submitted.pdf +0 -0
- package/fixtures/paper-stages/stale/PIPELINE-STATUS.md +1 -0
- package/fixtures/paper-stages/stale/versions/2026-07-22-submitted.STALE-WRONG-FILE.pdf +0 -0
- package/fixtures/paper-stages/twice/PIPELINE-STATUS.md +14 -0
- package/fixtures/paper-stages/twice/versions/2026-08-06-submitted.pdf +0 -0
- package/fixtures/paper-stages/twice/versions/2026-10-24-submitted.pdf +0 -0
- package/fixtures/paper-stages/undeclared/PIPELINE-STATUS.md +8 -0
- package/fixtures/paper-stages/undeclared/versions/2026-07-22-submitted.pdf +0 -0
- package/fixtures/paper-stages/undeclared/versions/2026-08-29-camera-ready.pdf +0 -0
- package/fixtures/paper-stages/wrongsize/PIPELINE-STATUS.md +8 -0
- package/fixtures/paper-stages/wrongsize/versions/2026-07-22-submitted.pdf +0 -0
- package/fixtures/paper-typography/clean-paper/paper.tex +29 -0
- package/fixtures/paper-typography/messy-paper/paper.tex +27 -0
- package/fixtures/pdf-facts/README.md +22 -0
- package/fixtures/pdf-facts/corrupt-font.pdf +0 -0
- package/fixtures/pdf-facts/encrypted.pdf +0 -0
- package/fixtures/pdf-facts/hidden-text.pdf +0 -0
- package/fixtures/pdf-facts/hidden-text.tex +28 -0
- package/fixtures/pdf-facts/t3-all.pdf +0 -0
- package/fixtures/pdf-facts/t3-all.tex +8 -0
- package/fixtures/pdf-facts/t3-mixed.pdf +0 -0
- package/fixtures/pdf-facts/t3-mixed.tex +9 -0
- package/fixtures/pdf-facts/ttf.pdf +2240 -1
- package/fixtures/pdf-facts/ttf.tex +6 -0
- package/fixtures/real-markdown-paper/baseline.json +24 -0
- package/fixtures/real-markdown-paper/baseline.mjs +48 -0
- package/fixtures/render-paper/build-clean.sh +25 -0
- package/fixtures/render-paper/build-defect.sh +15 -0
- package/fixtures/review-findings-cause/clean.md +17 -0
- package/fixtures/review-findings-cause/defect.md +14 -0
- package/fixtures/review-findings-cause/old-debt.md +14 -0
- package/fixtures/review-findings-cause/quiet-in-fence.md +16 -0
- package/fixtures/tex-build/clean.tex +21 -0
- package/fixtures/tex-build/defect.tex +24 -0
- package/fixtures/tex-build/frontmatter-clean.tex +25 -0
- package/fixtures/tex-build/frontmatter-defect.tex +23 -0
- package/fixtures/toolchain-mirror/catalog.txt +5 -0
- package/fixtures/toolchain-mirror/install-tl +27 -0
- package/fixtures/toolchain-mirror/release-texlive.txt +3 -0
- package/fixtures/toolchain-mirror/release-year +1 -0
- package/fixtures/toolchain-mirror/stub-kpsewhich +8 -0
- package/fixtures/toolchain-mirror/stub-pdflatex +3 -0
- package/fixtures/toolchain-mirror/stub-tlmgr +44 -0
- package/hooks/hooks.harness.mjs +713 -0
- package/hooks/hooks.mutations.mjs +337 -0
- package/hooks/paper-edit-guard.hook.d.mts +13 -0
- package/hooks/paper-edit-guard.hook.mjs +457 -0
- package/hooks/paper-skills-nudge.hook.mjs +136 -0
- package/hooks/paper-status-gates.hook.mjs +156 -0
- package/hooks/paper-status-gates.sh +91 -0
- package/lib/agent-cli-version.harness.mjs +165 -0
- package/lib/agent-cli-version.mjs +106 -0
- package/lib/agent-cli-version.mutations.mjs +109 -0
- package/lib/markdown.mjs +386 -0
- package/lib/mutation-driver.harness.mjs +227 -0
- package/lib/mutation-driver.mjs +397 -0
- package/lib/mutation-driver.mutations.mjs +68 -0
- package/lib/paper-config.d.mts +34 -0
- package/lib/paper-config.harness.mjs +286 -0
- package/lib/paper-config.mjs +142 -0
- package/lib/paper-config.mutations.mjs +143 -0
- package/lib/skill-checks.mjs +701 -0
- package/lib/skill-corpus.mjs +403 -0
- package/lib/skill-eval-fixture.mjs +63 -0
- package/lib/skill-eval-kit.mjs +257 -0
- package/lib/skill-trigger-cases.harness.mjs +170 -0
- package/lib/skill-trigger-cases.mjs +446 -0
- package/lib/skill-trigger-cases.mutations.mjs +65 -0
- package/lib/trigger-ledger.mjs +215 -0
- package/package.json +97 -0
- package/plugin/.claude-plugin/plugin.json +8 -0
- package/plugin/hooks/hooks.json +30 -0
- package/scripts/check.harness.mjs +177 -0
- package/scripts/check.mjs +239 -0
- package/scripts/check.mutations.mjs +110 -0
- package/scripts/eslint-report-guard.mjs +82 -0
- package/scripts/exclusive.mjs +138 -0
- package/scripts/harness-api.frozen.json +76 -0
- package/scripts/harness-api.test.ts +175 -0
- package/scripts/layer-legacy-frozen.d.mts +28 -0
- package/scripts/layer-legacy-frozen.mjs +152 -0
- package/scripts/layer-legacy-frozen.test.ts +115 -0
- package/scripts/layer-legacy.frozen.json +50 -0
- package/scripts/mutation-batteries-frozen.harness.mjs +204 -0
- package/scripts/mutation-batteries-frozen.mjs +238 -0
- package/scripts/mutation-batteries.frozen.json +117 -0
- package/scripts/release-config.test.ts +90 -0
- package/scripts/rules-are-content-only.harness.mjs +113 -0
- package/scripts/rules-are-content-only.mjs +138 -0
- package/scripts/rules-are-content-only.mutations.mjs +81 -0
- package/scripts/rules-see-files.harness.mjs +115 -0
- package/scripts/rules-see-files.mjs +99 -0
- package/scripts/rules-see-files.mutations.mjs +131 -0
- package/scripts/run-mutations.mjs +100 -0
- package/scripts/semantic-release-plugins.d.ts +16 -0
- package/skills/README.md +15 -0
- package/skills/analyze-sibling-paper/SKILL.md +170 -0
- package/skills/analyze-sibling-paper/SKILL.md.spec.ts +186 -0
- package/skills/analyze-sibling-paper/analyze-sibling-paper.eval.mjs +19 -0
- package/skills/analyze-sibling-paper/analyze-sibling-paper.harness.mjs +23 -0
- package/skills/argument-arc/SKILL.md +177 -0
- package/skills/argument-arc/SKILL.md.spec.ts +192 -0
- package/skills/argument-arc/argument-arc.eval.mjs +19 -0
- package/skills/argument-arc/argument-arc.harness.mjs +23 -0
- package/skills/build-benchmark/SKILL.md +213 -0
- package/skills/build-benchmark/SKILL.md.spec.ts +220 -0
- package/skills/build-benchmark/build-benchmark.eval.mjs +19 -0
- package/skills/build-benchmark/build-benchmark.harness.mjs +23 -0
- package/skills/build-benchmark/references/adversarial-cold-repro.md +68 -0
- package/skills/camera-ready/SKILL.md +148 -0
- package/skills/camera-ready/SKILL.md.spec.ts +164 -0
- package/skills/camera-ready/camera-ready.eval.mjs +19 -0
- package/skills/camera-ready/camera-ready.harness.mjs +23 -0
- package/skills/cold-read-diff/SKILL.md +160 -0
- package/skills/cold-read-diff/SKILL.md.spec.ts +166 -0
- package/skills/cold-read-diff/cold-read-diff.eval.mjs +19 -0
- package/skills/cold-read-diff/cold-read-diff.harness.mjs +23 -0
- package/skills/draft-paper/SKILL.md +152 -0
- package/skills/draft-paper/SKILL.md.spec.ts +169 -0
- package/skills/draft-paper/draft-paper.eval.mjs +19 -0
- package/skills/draft-paper/draft-paper.harness.mjs +23 -0
- package/skills/extend-paper/SKILL.md +99 -0
- package/skills/extend-paper/SKILL.md.spec.ts +116 -0
- package/skills/extend-paper/extend-paper.eval.mjs +19 -0
- package/skills/extend-paper/extend-paper.harness.mjs +23 -0
- package/skills/find-venue/SKILL.md +128 -0
- package/skills/find-venue/SKILL.md.spec.ts +145 -0
- package/skills/find-venue/find-venue.eval.mjs +19 -0
- package/skills/find-venue/find-venue.harness.mjs +23 -0
- package/skills/grade-paper-writing/SKILL.md +436 -0
- package/skills/grade-paper-writing/SKILL.md.spec.ts +453 -0
- package/skills/grade-paper-writing/fixtures/control_gopen.txt +1 -0
- package/skills/grade-paper-writing/fixtures/control_human_paper.txt +1 -0
- package/skills/grade-paper-writing/fixtures/rewrite.txt +1 -0
- package/skills/grade-paper-writing/fixtures/specimen.txt +1 -0
- package/skills/grade-paper-writing/fixtures/structure-checks.md +22 -0
- package/skills/grade-paper-writing/grade-paper-writing.eval.mjs +19 -0
- package/skills/grade-paper-writing/grade-paper-writing.harness.mjs +23 -0
- package/skills/grade-paper-writing/prose-lint.mjs +713 -0
- package/skills/harden-paper/SKILL.md +318 -0
- package/skills/harden-paper/SKILL.md.spec.ts +336 -0
- package/skills/harden-paper/check-numbers.sh +33 -0
- package/skills/harden-paper/check-release-claims.sh +35 -0
- package/skills/harden-paper/fixtures/uncited-assertions-sample.md +43 -0
- package/skills/harden-paper/fixtures/uncited-assertions-sample.tex +77 -0
- package/skills/harden-paper/harden-paper.eval.mjs +19 -0
- package/skills/harden-paper/harden-paper.harness.mjs +23 -0
- package/skills/map-prior-work/SKILL.md +211 -0
- package/skills/map-prior-work/SKILL.md.spec.ts +227 -0
- package/skills/map-prior-work/map-prior-work.eval.mjs +19 -0
- package/skills/map-prior-work/map-prior-work.harness.mjs +23 -0
- package/skills/osf-artifact-upload/SKILL.md +52 -0
- package/skills/osf-artifact-upload/SKILL.md.spec.ts +59 -0
- package/skills/osf-artifact-upload/osf-artifact-upload.eval.mjs +22 -0
- package/skills/osf-artifact-upload/osf-artifact-upload.harness.mjs +103 -0
- package/skills/paper-adversarial-review/SKILL.md +126 -0
- package/skills/paper-adversarial-review/SKILL.md.spec.ts +142 -0
- package/skills/paper-adversarial-review/paper-adversarial-review.eval.mjs +19 -0
- package/skills/paper-adversarial-review/paper-adversarial-review.harness.mjs +23 -0
- package/skills/paper-pipeline/PIPELINE-MAP.md +371 -0
- package/skills/paper-pipeline/SKILL.md +499 -0
- package/skills/paper-pipeline/SKILL.md.spec.ts +517 -0
- package/skills/paper-pipeline/description-language.eval.mjs +347 -0
- package/skills/paper-pipeline/framing-vs-vocabulary.eval.mjs +891 -0
- package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +1254 -0
- package/skills/paper-pipeline/paper-pipeline.eval.mjs +22 -0
- package/skills/paper-pipeline/paper-pipeline.harness.mjs +143 -0
- package/skills/paper-pipeline/pipeline-firing.baseline.json +270 -0
- package/skills/paper-pipeline/pipeline-firing.eval.mjs +664 -0
- package/skills/paper-pipeline/pipeline-language.eval.mjs +672 -0
- package/skills/paper-pipeline/references/acceptance-gate.md +329 -0
- package/skills/paper-pipeline/references/acl-venue-rules.md +142 -0
- package/skills/paper-pipeline/references/anonymization.md +68 -0
- package/skills/paper-pipeline/references/artifact-checklist.md +93 -0
- package/skills/paper-pipeline/references/body-vs-appendix.md +97 -0
- package/skills/paper-pipeline/references/credit-criteria.md +69 -0
- package/skills/paper-pipeline/references/occupancy-2026-08-06-probe/README.md +35 -0
- package/skills/paper-pipeline/references/occupancy-2026-08-06-probe/run_retext.mjs +24 -0
- package/skills/paper-pipeline/references/occupancy-2026-08-06-probe/sentences.txt +11 -0
- package/skills/paper-pipeline/references/occupancy-2026-08-06-probe/test_sentences.py +25 -0
- package/skills/paper-pipeline/references/occupancy-2026-08-06-prose-checkers.md +538 -0
- package/skills/paper-pipeline/references/occupancy-2026-08-06-reproducible-tooling.md +431 -0
- package/skills/paper-pipeline/references/occupancy-2026-08-06-staleness-and-orchestration.md +592 -0
- package/skills/paper-pipeline/references/pipeline-status-template.md +162 -0
- package/skills/paper-pipeline/references/review-ratchet.md +36 -0
- package/skills/paper-pipeline/references/sweep-2026-08-09-ideal-pipeline.md +585 -0
- package/skills/paper-pipeline/references/writing-craft.md +448 -0
- package/skills/paper-pipeline/repro/2026-08-07-description-language-control.log +63 -0
- package/skills/paper-pipeline/repro/2026-08-07-fork-check.log +52 -0
- package/skills/paper-pipeline/repro/2026-08-07-fork-check2.log +33 -0
- package/skills/paper-pipeline/repro/2026-08-07-language-eval-pilot.log +33 -0
- package/skills/paper-pipeline/repro/2026-08-07-language-eval-raw.log +166 -0
- package/skills/paper-pipeline/repro/2026-08-08-framing-vs-vocabulary-oracle.json +338 -0
- package/skills/paper-pipeline/repro/2026-08-08-framing-vs-vocabulary-oracle.log +118 -0
- package/skills/paper-pipeline/repro/2026-08-08-framing-vs-vocabulary-raw.log +245 -0
- package/skills/paper-pipeline/repro/2026-08-08-framing-vs-vocabulary.json +776 -0
- package/skills/paper-pipeline/repro/2026-08-08-grade-paper-writing-ablation-A6-oracle.log +53 -0
- package/skills/paper-pipeline/repro/2026-08-08-grade-paper-writing-ablation-A6-raw.log +89 -0
- package/skills/paper-pipeline/repro/2026-08-08-grade-paper-writing-ablation-oracle.json +450 -0
- package/skills/paper-pipeline/repro/2026-08-08-grade-paper-writing-ablation-oracle.log +136 -0
- package/skills/paper-pipeline/repro/2026-08-08-grade-paper-writing-ablation-raw.log +242 -0
- package/skills/paper-pipeline/repro/2026-08-08-grade-paper-writing-ablation-setupdiff.log +59 -0
- package/skills/paper-pipeline/repro/2026-08-08-grade-paper-writing-ablation.json +1032 -0
- package/skills/paper-pipeline/repro/2026-08-08-parent-replication-gpw.json +139 -0
- package/skills/paper-pipeline/repro/2026-08-08-parent-replication-gpw.log +98 -0
- package/skills/paper-pipeline/repro/2026-08-08-parent-replication.mjs +92 -0
- package/skills/paper-pipeline/repro/README.md +129 -0
- package/skills/paper-pipeline/repro/analyze-language-eval.py +116 -0
- package/skills/paper-pipeline/scripts/README.md +344 -0
- package/skills/paper-pipeline/scripts/announce.mjs +67 -0
- package/skills/paper-pipeline/scripts/artifact-coverage.harness.mjs +496 -0
- package/skills/paper-pipeline/scripts/artifact-coverage.mjs +397 -0
- package/skills/paper-pipeline/scripts/artifact-coverage.mutations.mjs +218 -0
- package/skills/paper-pipeline/scripts/check-provenance.mjs +184 -0
- package/skills/paper-pipeline/scripts/consumer.d.mts +32 -0
- package/skills/paper-pipeline/scripts/consumer.harness.mjs +562 -0
- package/skills/paper-pipeline/scripts/consumer.mjs +535 -0
- package/skills/paper-pipeline/scripts/consumer.mutations.mjs +190 -0
- package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +457 -0
- package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +656 -0
- package/skills/paper-pipeline/scripts/extract-ref-facts.mutations.mjs +54 -0
- package/skills/paper-pipeline/scripts/fixtures/clean/PIPELINE-STATUS.md +51 -0
- package/skills/paper-pipeline/scripts/fixtures/dirty/PIPELINE-STATUS.md +52 -0
- package/skills/paper-pipeline/scripts/fixtures/dirty/paper.md +6 -0
- package/skills/paper-pipeline/scripts/fixtures/real-bib/refs.bib +153 -0
- package/skills/paper-pipeline/scripts/generated-code.harness.mjs +466 -0
- package/skills/paper-pipeline/scripts/generated-code.mjs +338 -0
- package/skills/paper-pipeline/scripts/generated-code.mutations.mjs +254 -0
- package/skills/paper-pipeline/scripts/ledger.mjs +623 -0
- package/skills/paper-pipeline/scripts/ledger.selftest.mjs +286 -0
- package/skills/paper-pipeline/scripts/pipeline-check.harness.mjs +389 -0
- package/skills/paper-pipeline/scripts/pipeline-check.mjs +737 -0
- package/skills/paper-pipeline/scripts/pipeline-check.mutations.mjs +54 -0
- package/skills/paper-pipeline/scripts/pipeline-edges.mjs +169 -0
- package/skills/paper-pipeline/scripts/population-map.harness.mjs +178 -0
- package/skills/paper-pipeline/scripts/population-map.mjs +181 -0
- package/skills/paper-pipeline/scripts/population-map.mutations.mjs +65 -0
- package/skills/paper-pipeline/scripts/population-map.selftest.mjs +122 -0
- package/skills/paper-pipeline/scripts/provenance.harness.mjs +240 -0
- package/skills/paper-pipeline/scripts/provenance.mutations.mjs +59 -0
- package/skills/paper-pipeline/scripts/round-diff.harness.mjs +881 -0
- package/skills/paper-pipeline/scripts/round-diff.mjs +576 -0
- package/skills/paper-pipeline/scripts/round-diff.mutations.mjs +276 -0
- package/skills/paper-pipeline/scripts/run-mechanical.mjs +633 -0
- package/skills/paper-pipeline/scripts/status.mjs +295 -0
- package/skills/paper-status/SKILL.md +183 -0
- package/skills/paper-status/SKILL.md.spec.ts +190 -0
- package/skills/paper-status/paper-status.eval.mjs +22 -0
- package/skills/paper-status/paper-status.harness.mjs +25 -0
- package/skills/pc-panel-review/SKILL.md +263 -0
- package/skills/pc-panel-review/SKILL.md.spec.ts +280 -0
- package/skills/pc-panel-review/pc-panel-review.eval.mjs +19 -0
- package/skills/pc-panel-review/pc-panel-review.harness.mjs +23 -0
- package/skills/plan-paper-timeline/SKILL.md +182 -0
- package/skills/plan-paper-timeline/SKILL.md.spec.ts +200 -0
- package/skills/plan-paper-timeline/fixtures/fake-google-calendar.mjs +239 -0
- package/skills/plan-paper-timeline/plan-paper-timeline.effects.harness.mjs +431 -0
- package/skills/plan-paper-timeline/plan-paper-timeline.effects.mutations.mjs +65 -0
- package/skills/plan-paper-timeline/plan-paper-timeline.eval.mjs +19 -0
- package/skills/plan-paper-timeline/plan-paper-timeline.harness.mjs +23 -0
- package/skills/render-paper/SKILL.md +159 -0
- package/skills/render-paper/SKILL.md.spec.ts +166 -0
- package/skills/render-paper/check-render.sh +419 -0
- package/skills/render-paper/checkers-requirements.txt +55 -0
- package/skills/render-paper/ensure-checkers.sh +69 -0
- package/skills/render-paper/extract-pdf-facts.harness.mjs +166 -0
- package/skills/render-paper/extract-pdf-facts.mjs +144 -0
- package/skills/render-paper/render-paper.eval.mjs +19 -0
- package/skills/render-paper/render-paper.harness.mjs +339 -0
- package/skills/research-ideate/SKILL.md +136 -0
- package/skills/research-ideate/SKILL.md.spec.ts +152 -0
- package/skills/research-ideate/research-ideate.eval.mjs +19 -0
- package/skills/research-ideate/research-ideate.harness.mjs +23 -0
- package/skills/skill-contract.mutations.mjs +179 -0
- package/skills/study-accepted-papers/SKILL.md +206 -0
- package/skills/study-accepted-papers/SKILL.md.spec.ts +223 -0
- package/skills/study-accepted-papers/study-accepted-papers.eval.mjs +19 -0
- package/skills/study-accepted-papers/study-accepted-papers.harness.mjs +23 -0
- package/skills/submit-paper/SKILL.md +182 -0
- package/skills/submit-paper/SKILL.md.spec.ts +199 -0
- package/skills/submit-paper/check-deanon.sh +149 -0
- package/skills/submit-paper/references/publishers/acm.md +92 -0
- package/skills/submit-paper/references/venues/agenticdev.jsonc +108 -0
- package/skills/submit-paper/references/venues/agenticdev.md +139 -0
- package/skills/submit-paper/references/venues/agenticdev.tex +19 -0
- package/skills/submit-paper/references/venues/aisec.jsonc +101 -0
- package/skills/submit-paper/references/venues/aisec.md +105 -0
- package/skills/submit-paper/references/venues/paper-guards.tex +41 -0
- package/skills/submit-paper/references/venues/realm.jsonc +81 -0
- package/skills/submit-paper/references/venues/realm.md +155 -0
- package/skills/submit-paper/references/venues/tex-base.jsonc +50 -0
- package/skills/submit-paper/references/venues/venue-profile.schema.json +74 -0
- package/skills/submit-paper/submit-paper.eval.mjs +19 -0
- package/skills/submit-paper/submit-paper.harness.mjs +23 -0
- package/skills/sweep-design-space/SKILL.md +269 -0
- package/skills/sweep-design-space/SKILL.md.spec.ts +285 -0
- package/skills/sweep-design-space/sweep-design-space.eval.mjs +19 -0
- package/skills/sweep-design-space/sweep-design-space.harness.mjs +23 -0
- package/skills/tighten-paper/SKILL.md +368 -0
- package/skills/tighten-paper/SKILL.md.spec.ts +384 -0
- package/skills/tighten-paper/structure.mjs +371 -0
- package/skills/tighten-paper/tighten-paper.eval.mjs +19 -0
- package/skills/tighten-paper/tighten-paper.harness.mjs +23 -0
- package/skills/verify-citations/SKILL.md +328 -0
- package/skills/verify-citations/SKILL.md.spec.ts +345 -0
- package/skills/verify-citations/scripts/bib-authors.mjs +479 -0
- package/skills/verify-citations/scripts/bib-authors.test.mjs +175 -0
- package/skills/verify-citations/scripts/verify-cites.mjs +1108 -0
- package/skills/verify-citations/scripts/verify-cites.test.mjs +735 -0
- package/skills/verify-citations/verify-citations.eval.mjs +19 -0
- package/skills/verify-citations/verify-citations.harness.mjs +23 -0
- package/src/CLAUDE.md +51 -0
- package/src/action-ref.test.ts +26 -0
- package/src/action-ref.ts +15 -0
- package/src/adapters/banal/failure.test.ts +63 -0
- package/src/adapters/banal/failure.ts +118 -0
- package/src/adapters/banal/index.test.ts +119 -0
- package/src/adapters/banal/index.ts +100 -0
- package/src/adapters/banal/install.test.ts +20 -0
- package/src/adapters/banal/install.ts +41 -0
- package/src/adapters/banal/invocation.test.ts +74 -0
- package/src/adapters/banal/invocation.ts +95 -0
- package/src/adapters/banal/locate.test.ts +52 -0
- package/src/adapters/banal/locate.ts +84 -0
- package/src/adapters/banal/output.test.ts +140 -0
- package/src/adapters/banal/output.ts +141 -0
- package/src/adapters/banal/pin.ts +30 -0
- package/src/adapters/banal/probe.ts +35 -0
- package/src/adapters/banal/run.test.ts +191 -0
- package/src/adapters/banal/run.ts +244 -0
- package/src/adapters/banal/settings.test.ts +31 -0
- package/src/adapters/banal/settings.ts +55 -0
- package/src/adapters/banal/xml.test.ts +111 -0
- package/src/adapters/banal/xml.ts +112 -0
- package/src/adapters/curl/download.io.ts +73 -0
- package/src/adapters/curl/download.test.ts +55 -0
- package/src/adapters/curl/index.ts +5 -0
- package/src/adapters/memory/index.ts +131 -0
- package/src/adapters/node/files.io.ts +39 -0
- package/src/adapters/node/files.test.ts +28 -0
- package/src/adapters/node/host.io.ts +15 -0
- package/src/adapters/node/index.ts +36 -0
- package/src/adapters/node/process.io.ts +49 -0
- package/src/adapters/node/process.test.ts +46 -0
- package/src/adapters/node/workspace.io.ts +40 -0
- package/src/adapters/node/workspace.test.ts +58 -0
- package/src/adapters/pdfjs/fill.test.ts +111 -0
- package/src/adapters/pdfjs/fill.ts +141 -0
- package/src/build-engine.harness.mjs +314 -0
- package/src/build-engine.ts +219 -0
- package/src/build.harness.mjs +631 -0
- package/src/build.mutations.mjs +195 -0
- package/src/build.ts +793 -0
- package/src/cli.harness.mjs +2007 -0
- package/src/cli.mutations.mjs +448 -0
- package/src/cli.ts +1189 -0
- package/src/doctor.harness.mjs +396 -0
- package/src/doctor.mutations.mjs +175 -0
- package/src/doctor.ts +356 -0
- package/src/domain/geometry.ts +108 -0
- package/src/domain/host.ts +23 -0
- package/src/domain/page-layout.ts +32 -0
- package/src/domain/paths.ts +5 -0
- package/src/domain/result.test.ts +26 -0
- package/src/domain/result.ts +29 -0
- package/src/domain/sha256.test.ts +12 -0
- package/src/domain/sha256.ts +21 -0
- package/src/domain/text.ts +11 -0
- package/src/engine.harness.mjs +252 -0
- package/src/engine.ts +176 -0
- package/src/exit-code.test.ts +21 -0
- package/src/exit-code.ts +38 -0
- package/src/facts-file.test.ts +240 -0
- package/src/facts-file.ts +241 -0
- package/src/hooks-settings.harness.mjs +386 -0
- package/src/hooks-settings.mutations.mjs +116 -0
- package/src/hooks-settings.ts +434 -0
- package/src/init.ts +900 -0
- package/src/latex-log.harness.mjs +226 -0
- package/src/latex-log.ts +234 -0
- package/src/latex-loop.harness.mjs +449 -0
- package/src/latex-loop.ts +211 -0
- package/src/link-skills.harness.mjs +273 -0
- package/src/link-skills.mutations.mjs +136 -0
- package/src/link-skills.ts +258 -0
- package/src/new-paper.harness.mjs +216 -0
- package/src/new-paper.mutations.mjs +79 -0
- package/src/new-paper.ts +158 -0
- package/src/pdf-facts.harness.mjs +188 -0
- package/src/pdf-facts.ts +327 -0
- package/src/pdf-geometry.harness.mjs +254 -0
- package/src/pdf-geometry.ts +300 -0
- package/src/ports/download.ts +10 -0
- package/src/ports/files.ts +11 -0
- package/src/ports/measure-geometry.ts +8 -0
- package/src/ports/process.ts +46 -0
- package/src/ports/tool-installer.ts +33 -0
- package/src/ports/workspace.ts +20 -0
- package/src/rules-config.harness.mjs +114 -0
- package/src/rules-config.ts +178 -0
- package/src/structure.harness.mjs +179 -0
- package/src/structure.mutations.mjs +83 -0
- package/src/structure.ts +166 -0
- package/src/tex-requirements.harness.mjs +238 -0
- package/src/tex-requirements.ts +181 -0
- package/src/toolchain.harness.mjs +651 -0
- package/src/toolchain.ts +755 -0
- package/src/types.ts +106 -0
- package/templates/paper/PIPELINE-STATUS.md +72 -0
- package/templates/paper/paper.md +4 -0
- package/templates/paper/paper.tex +8 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Occupancy research — staleness tracking / build-graph machinery for the paper QA pipeline"
|
|
3
|
+
created: 2026-08-06
|
|
4
|
+
tags:
|
|
5
|
+
[
|
|
6
|
+
occupancy-research,
|
|
7
|
+
paper-pipeline,
|
|
8
|
+
build-systems,
|
|
9
|
+
staleness,
|
|
10
|
+
provenance,
|
|
11
|
+
mutation-testing,
|
|
12
|
+
]
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Occupancy: "what ran against what, and what is stale now"
|
|
16
|
+
|
|
17
|
+
**The problem restated.** A hand-maintained `PIPELINE-STATUS.md` marks ~20 quality checks (some
|
|
18
|
+
cheap scripts, some expensive LLM agent runs) as ✅/❌ against a document. It lies: a check stays ✅
|
|
19
|
+
after the text it checked has been rewritten, because nothing recomputes the mark. Requirement in
|
|
20
|
+
his own words: _"I want stuff to be visible immediately... timestamps, and/or hashes, marks for when
|
|
21
|
+
was what skill ran last time inline... preference should be to deterministic checks."_
|
|
22
|
+
|
|
23
|
+
**Hypothesis under test:** this is a solved problem — a content-addressed build graph — and the
|
|
24
|
+
status table is a hand-rolled, lying build cache. Below is what was actually fetched and quoted, not
|
|
25
|
+
recalled from memory. Scope is narrowly "what ran against what, is it stale" — not workflow design.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 1. `make` — the baseline, and exactly where it breaks
|
|
30
|
+
|
|
31
|
+
**What it is.** The original dependency-graph build tool: targets, prerequisites, recipes,
|
|
32
|
+
timestamp comparison.
|
|
33
|
+
|
|
34
|
+
**Alive?** Yes (GNU make, still maintained), but irrelevant as a mechanism question — it's the
|
|
35
|
+
_reference failure mode_, not a candidate.
|
|
36
|
+
|
|
37
|
+
**Exactly what it gives, and what it doesn't.** Fetched `bug-make` mailing list discussion and two
|
|
38
|
+
independent blog write-ups (John Graham-Cumming; Oli Pratt) confirming the same architectural fact:
|
|
39
|
+
|
|
40
|
+
> "the only tool in Make's toolbox is the timestamp of files ... GNU make decides that a target
|
|
41
|
+
> needs rebuilding if a dependency is 'newer' than the target file by comparing the modification
|
|
42
|
+
> timestamp of the target and dependency files."
|
|
43
|
+
|
|
44
|
+
Consequence documented in the same sources: a `git checkout` that restores byte-identical content
|
|
45
|
+
still bumps mtimes, so make reruns things that didn't change (false positive staleness) — and,
|
|
46
|
+
worse for this use case, **make has no notion of the recipe itself changing**. If you edit the
|
|
47
|
+
_checker's own logic_ (its shell command) but don't touch any file listed as a prerequisite, make
|
|
48
|
+
will not know the recipe changed and will report the target up to date. That is precisely the
|
|
49
|
+
failure mode in `PIPELINE-STATUS.md`: a check's own definition (prompt, script, model version)
|
|
50
|
+
changes and nothing notices.
|
|
51
|
+
|
|
52
|
+
**Verdict: this is the mechanism the status table currently is, manually, minus even the timestamp
|
|
53
|
+
comparison.** Not adoptable as-is; useful only as the "what NOT to build" baseline.
|
|
54
|
+
|
|
55
|
+
Sources: [Rebuilding when the hash has changed, not the timestamp](https://blog.jgc.org/2006/04/rebuilding-when-hash-has-changed-not.html), [bug-make thread on hash vs timestamp](https://lists.gnu.org/archive/html/bug-make/2015-04/msg00000.html), [Rebuilding Makefile Targets Only When Dependency Content Changes](http://olipratt.co.uk/rebuilding-makefile-targets-only-when-dependency-content-changes.html)
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 2. Content-addressed build systems: Bazel, Buck2, Nix, Please
|
|
60
|
+
|
|
61
|
+
### Bazel
|
|
62
|
+
|
|
63
|
+
**Alive.** Actively developed (bazel.build current docs, 2026).
|
|
64
|
+
|
|
65
|
+
**Exactly what it gives.** Fetched `bazel.build/reference/glossary` directly. Quote:
|
|
66
|
+
|
|
67
|
+
> **Action Cache:** "An on-disk cache that stores a mapping of executed actions to the outputs they
|
|
68
|
+
> created. The cache key is known as the action key."
|
|
69
|
+
> **Action Key:** "The cache key of an action. Computed based on action metadata, which might
|
|
70
|
+
> include the command to be executed in the action, compiler flags, library locations, or system
|
|
71
|
+
> headers, depending on the action."
|
|
72
|
+
|
|
73
|
+
This directly answers the test question: **yes, changing the checker's own definition (the command
|
|
74
|
+
line, compiler flags — i.e. the recipe) changes the action key and invalidates the cached result**,
|
|
75
|
+
because the command itself is hashed into the key, not just the input file contents. This is exactly
|
|
76
|
+
the property `make` lacks.
|
|
77
|
+
|
|
78
|
+
Cost to adopt: real. Bazel requires a `BUILD`/`WORKSPACE` file model, a Starlark rule per check type,
|
|
79
|
+
and — for anything beyond a toy example — understanding of its dependency/action graph. This is a
|
|
80
|
+
build-system migration, not a drop-in script.
|
|
81
|
+
|
|
82
|
+
Sources: [Bazel Reference Glossary](https://bazel.build/reference/glossary), [Remote Caching | Bazel](https://bazel.build/remote/caching)
|
|
83
|
+
|
|
84
|
+
### Buck2
|
|
85
|
+
|
|
86
|
+
**Alive.** Meta's active successor to Buck1 (buck2.build current docs).
|
|
87
|
+
|
|
88
|
+
**What it gives.** Same family as Bazel: action cache addressed by a content-addressable-storage
|
|
89
|
+
(CAS) digest of inputs. Docs describe the flow: "When Buck2 decides to run an action remotely, it
|
|
90
|
+
will first upload all of the action's inputs that are missing from the Remote Execution service's
|
|
91
|
+
content addressable storage." The action-cache-key mechanics (whether the command itself is
|
|
92
|
+
included) are described at the same level as Bazel's — Buck2 is explicitly modeled as a rewrite of
|
|
93
|
+
the same idea with a different execution engine.
|
|
94
|
+
|
|
95
|
+
Cost to adopt: same order as Bazel — a real build-system migration (`BUCK` files, Starlark), heavier
|
|
96
|
+
tooling than a solo document-QA pipeline needs.
|
|
97
|
+
|
|
98
|
+
Source: [Architectural Model | Buck2](https://buck2.build/docs/developers/architecture/buck2/)
|
|
99
|
+
|
|
100
|
+
### Nix
|
|
101
|
+
|
|
102
|
+
**Alive.** Actively developed; current manual at nix.dev/manual.
|
|
103
|
+
|
|
104
|
+
**What it gives — verified precisely, this is the cleanest confirmation of the hypothesis.**
|
|
105
|
+
Fetched a technical blog (fzakaria.com) explaining `.drv` hash construction, cross-checked against
|
|
106
|
+
Nix Pills and the official manual summary:
|
|
107
|
+
|
|
108
|
+
> "The hash is constructed from the contents of the derivation file rather than the bytes of the
|
|
109
|
+
> output, which is the input-addressed approach. This means that even the teeniest change, such as
|
|
110
|
+
> a comment, that might have no consequential change to the output artifact causes a whole new store
|
|
111
|
+
> path."
|
|
112
|
+
>
|
|
113
|
+
> "The builder script is in the input sources, and the store path is computed based on the filename
|
|
114
|
+
> and on the hash of its contents. Since the builder script (and its contents) are part of the
|
|
115
|
+
> inputs to the derivation, changes to the builder script directly affect the derivation hash."
|
|
116
|
+
|
|
117
|
+
This is a **direct, unambiguous yes** to "does changing the checker's own source invalidate the
|
|
118
|
+
cached result?" — a Nix derivation's identity _is_ the hash of its build script plus its declared
|
|
119
|
+
inputs; edit the script by one character (even a comment) and the store path changes, so the cached
|
|
120
|
+
result at the old path is simply never looked up again. There's no separate bookkeeping step to
|
|
121
|
+
forget to run — it's structurally impossible to get a stale hit.
|
|
122
|
+
|
|
123
|
+
Nix also has an experimental content-addressed mode (CA derivations) layered on top, where the
|
|
124
|
+
_output's_ store path is additionally keyed by output content rather than only by input — orthogonal
|
|
125
|
+
improvement, not needed here.
|
|
126
|
+
|
|
127
|
+
Cost to adopt: high for what it's for. Nix is a whole packaging/build language with a steep learning
|
|
128
|
+
curve, a daemon, and a store (`/nix/store`) model. Using it _only_ to hash "did the checker's prompt
|
|
129
|
+
or the target text change" is using a freight train to cross a room — the property is real, the
|
|
130
|
+
vehicle is oversized.
|
|
131
|
+
|
|
132
|
+
Sources: [What's in a Nix store path](https://fzakaria.com/2025/03/28/what-s-in-a-nix-store-path), [Working Derivation — Nix Pills](https://nixos.org/guides/nix-pills/07-working-derivation.html), [Content-addressing derivation outputs — Nix manual](https://releases.nixos.org/nix/nix-2.31.0/manual/store/derivation/outputs/content-address.html)
|
|
133
|
+
|
|
134
|
+
### Please
|
|
135
|
+
|
|
136
|
+
**Alive** (please.build, active docs).
|
|
137
|
+
|
|
138
|
+
**What it gives.** Same content-addressed-build family as Bazel/Buck2 (it explicitly targets
|
|
139
|
+
Bazel-like ergonomics with a simpler config). Docs page found describes incrementality as a build
|
|
140
|
+
metric and notes an escape hatch — "An arbitrary string can be added to the hash of every build
|
|
141
|
+
target to force a rebuild of everything when it's changed" — confirming the hash does fold in more
|
|
142
|
+
than just file content, and is designed to be deliberately perturbable. Full documentation of what
|
|
143
|
+
exactly composes the default hash wasn't reachable in the fetched pages (would need `please.build`
|
|
144
|
+
internals doc, not fetched).
|
|
145
|
+
|
|
146
|
+
Cost to adopt: same class as Bazel/Buck2 — new build-file DSL, new mental model. No decisive
|
|
147
|
+
advantage over Bazel found that would justify picking it here.
|
|
148
|
+
|
|
149
|
+
Source: [Please FAQ](https://please.build/faq.html)
|
|
150
|
+
|
|
151
|
+
**Bazel/Buck2/Nix/Please verdict, jointly:** all three fully deliver the property "recipe change ⇒
|
|
152
|
+
cache invalidation." All four cost a build-system migration. None is proportionate to "~20 checks
|
|
153
|
+
over a handful of markdown files, run by one person."
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## 3. Task runners with hashing: Turborepo, Nx, moon, `just`, `task`
|
|
158
|
+
|
|
159
|
+
### Turborepo
|
|
160
|
+
|
|
161
|
+
**Alive.** turborepo.dev, active (Vercel).
|
|
162
|
+
|
|
163
|
+
**What it gives.** Docs + GitHub issues confirm: "Turborepo creates two hashes: a global hash and a
|
|
164
|
+
task hash, and if either changes, the task will miss cache." `turbo run --dry-run=json` shows the
|
|
165
|
+
computed hash and predicted hit/miss _without executing_, and `--summarize` produces a JSON diff of
|
|
166
|
+
"what changed" when a miss happens — this is close to the "immediately visible" status view he
|
|
167
|
+
wants, and it's inspectable per-task rather than a hand-written table. Caveat found directly in
|
|
168
|
+
GitHub issues (#9044, #2004): dry-run's predicted cache state and the real run's cache state have
|
|
169
|
+
been reported to disagree — the dry-run preview is not 100% authoritative in practice.
|
|
170
|
+
|
|
171
|
+
Cost to adopt: requires a JS/pnpm-monorepo project shape (`turbo.json`, `package.json` per
|
|
172
|
+
"package"). Wrong tool family for a markdown/Python-script pipeline unless one is willing to wrap
|
|
173
|
+
every check as an npm-workspace package purely to get the scheduler — real friction, not free.
|
|
174
|
+
|
|
175
|
+
Sources: [Caching | Turborepo](https://turborepo.dev/docs/crafting-your-repository/caching), [turbo run --dry=json cache.local inconsistency](https://github.com/vercel/turborepo/discussions/9038)
|
|
176
|
+
|
|
177
|
+
### Nx
|
|
178
|
+
|
|
179
|
+
**Alive.** nx.dev, active (Nrwl).
|
|
180
|
+
|
|
181
|
+
**What it gives.** Docs: "Nx hashes all configured inputs for the task to produce a single
|
|
182
|
+
computation hash string... If the hash matches a previous run, Nx skips execution and replays the
|
|
183
|
+
cached result. If not, Nx runs the task and stores the result for next time." `nx affected` computes
|
|
184
|
+
which projects/tasks are touched by a change and is explicitly designed as the "what needs to rerun"
|
|
185
|
+
view — closer to a dashboard than Turborepo's raw dry-run JSON. Same caveat class found in issue
|
|
186
|
+
#16153: hash computed by `nx print-affected` has been reported to diverge from the hash computed at
|
|
187
|
+
actual run time (dynamic inputs like a version field breaking reproducibility) — a reminder that
|
|
188
|
+
_inputs must be fully declared_ for any of these systems, not a flaw unique to Nx.
|
|
189
|
+
|
|
190
|
+
Cost to adopt: same as Turborepo — an Nx workspace (`nx.json`, project graph) is a JS/TS monorepo
|
|
191
|
+
tool. Not a natural fit for markdown+script checks unless the whole repo is restructured around it.
|
|
192
|
+
|
|
193
|
+
Sources: [How Caching Works | Nx](https://nx.dev/docs/concepts/how-caching-works), [Run Only Tasks Affected by a PR | Nx](https://nx.dev/docs/features/ci-features/affected)
|
|
194
|
+
|
|
195
|
+
### moon (moonrepo)
|
|
196
|
+
|
|
197
|
+
**Alive, actively releasing** — found four 2026 releases in sequence (v2.2 Apr, v2.3 Jun, v2.4 Jul 2026) via moonrepo.dev/blog, including a notable v2.2 feature: "a new **debug-task** AI skill to help
|
|
198
|
+
diagnose cache and hashing issues" — moon's own team recognized "why is this stale/not stale" is
|
|
199
|
+
hard enough to need a dedicated debug tool, which is telling. v2.3 added "native file hashing and a
|
|
200
|
+
local CAS" as _experimental_ layers, meaning full content-addressed caching is still stabilizing
|
|
201
|
+
even in a purpose-built 2026 tool.
|
|
202
|
+
|
|
203
|
+
**What it gives.** Rust-based, framework-agnostic (less JS-locked than Nx/Turborepo), same hash→
|
|
204
|
+
cache-hit/miss model.
|
|
205
|
+
|
|
206
|
+
Cost to adopt: still a monorepo task-runner install + `moon.yml` config per project; younger/smaller
|
|
207
|
+
ecosystem than Nx/Turborepo, mitigated by not being JS-specific.
|
|
208
|
+
|
|
209
|
+
Source: [moon v2.2](https://moonrepo.dev/blog/moon-v2.2), [moon v2.3](https://moonrepo.dev/blog/moon-v2.3), [moon v2.4](https://moonrepo.dev/blog/moon-v2.4)
|
|
210
|
+
|
|
211
|
+
### `just`
|
|
212
|
+
|
|
213
|
+
**Alive** (casey/just, active). It is a **command runner, not a build system** — it has no built-in
|
|
214
|
+
staleness/hash tracking at all. Confirmed by search: no file-hash change-detection feature exists in
|
|
215
|
+
just's core; users bolt on external tools (e.g. `hash-runner`) for that. Rules out `just` as a
|
|
216
|
+
candidate for the actual property needed — it gives ergonomic command aliasing, nothing about "did
|
|
217
|
+
the input change."
|
|
218
|
+
|
|
219
|
+
Source: [casey/just — GitHub](https://github.com/casey/just)
|
|
220
|
+
|
|
221
|
+
### `task` (go-task / Taskfile.dev)
|
|
222
|
+
|
|
223
|
+
**Alive** (taskfile.dev, active changelog).
|
|
224
|
+
|
|
225
|
+
**What it gives — the closest lightweight match found in this whole category.** Docs confirm a
|
|
226
|
+
first-class `method: checksum` per task: "Task provides a 'checksum' method for checking if a task
|
|
227
|
+
is up-to-date by monitoring source files for changes... by default task stores checksums on a local
|
|
228
|
+
`.task` directory." This is genuinely a content hash (not mtime) of declared `sources:`, computed
|
|
229
|
+
per task, persisted locally, with no daemon and no monorepo restructuring — just a `Taskfile.yml`.
|
|
230
|
+
Caveat found in GitHub issue #2294: a reported bug where the checksum was stuck at the same value in
|
|
231
|
+
one release (v3.44.0) — a live tool, so verify current behavior before relying on it, but the design
|
|
232
|
+
is sound and the closest "make, but hashed" tool surfaced in this research.
|
|
233
|
+
|
|
234
|
+
**This is the best-fit adoption candidate of the entire task-runner category**: single YAML file,
|
|
235
|
+
per-task `sources:` list (which could include the checker script _and_ the document, so editing
|
|
236
|
+
either invalidates the checksum), no new language, no daemon.
|
|
237
|
+
|
|
238
|
+
Sources: [Taskfile Guide](https://taskfile.dev/docs/guide), [Task checksum bug report #2294](https://github.com/go-task/task/issues/2294)
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## 4. Scientific workflow engines: Snakemake, Nextflow, CWL, Dagster, Airflow
|
|
243
|
+
|
|
244
|
+
### Snakemake
|
|
245
|
+
|
|
246
|
+
**Alive**, current docs at v9.23.1/v9.25.1 (snakemake.readthedocs.io).
|
|
247
|
+
|
|
248
|
+
**What it gives — exactly the "why is this stale" surface he asked for.** Fetched the FAQ directly:
|
|
249
|
+
|
|
250
|
+
> "Snakemake tries to ensure consistency between input and output files. This is based on file
|
|
251
|
+
> modification dates (input files may not be newer than output files of the same job), as well as
|
|
252
|
+
> execution metadata like the used software stack (e.g. conda env or container image), the non-file
|
|
253
|
+
> parameters, the set of input files, **and the code of the rule**."
|
|
254
|
+
|
|
255
|
+
That last clause is the load-bearing one: Snakemake explicitly tracks the _rule's own code_ as a
|
|
256
|
+
rerun trigger, separate from mtimes — this is the same property Bazel/Nix give, but in a tool built
|
|
257
|
+
for exactly this shape of problem (a DAG of heterogeneous checks over files, cheap and expensive
|
|
258
|
+
mixed, run by a single researcher — Snakemake's actual target audience, it underlies `showyourwork`
|
|
259
|
+
for reproducible papers).
|
|
260
|
+
|
|
261
|
+
`--dry-run --reason` (confirmed in docs) prints, per rule, the specific reason it would run — "detailed
|
|
262
|
+
information about the reasoning is given in the job description of Snakemake's output as well as in
|
|
263
|
+
the final summary at the end of a dry-run." This is close to literally what was asked for:
|
|
264
|
+
"marks for when was what skill ran last time inline."
|
|
265
|
+
|
|
266
|
+
Escape hatches also documented: `--touch` to fake a rule as run without running it (useful, also a
|
|
267
|
+
foot-gun — the exact thing `PIPELINE-STATUS.md`'s ✅ currently is, done deliberately instead of by
|
|
268
|
+
neglect); `--rerun-triggers mtimes` to fall back to make-style behavior if the finer-grained
|
|
269
|
+
triggers are too twitchy.
|
|
270
|
+
|
|
271
|
+
Cost to adopt: moderate, not zero. A `Snakefile` with one `rule` per check, declaring the document(s)
|
|
272
|
+
as `input` and a `.done`/report file as `output`; the check's own script goes in `input` too (or
|
|
273
|
+
Snakemake tracks "code of the rule" automatically per the FAQ quote — needs one more read of the
|
|
274
|
+
rule-versioning docs to confirm whether that's automatic or requires opting into `--rerun-triggers`
|
|
275
|
+
including `code`, but the FAQ states it as default behavior). No daemon, runs from CLI, has a
|
|
276
|
+
`--dag`/`--rulegraph` visual graph.
|
|
277
|
+
|
|
278
|
+
**Strongest single candidate in the entire survey** for the actual problem statement — purpose-built
|
|
279
|
+
for "heterogeneous checks (cheap script + expensive job) over files, for one researcher, needs to
|
|
280
|
+
show what's stale and why."
|
|
281
|
+
|
|
282
|
+
Sources: [Snakemake FAQ](https://snakemake.readthedocs.io/en/stable/project_info/faq.html), [Snakemake CLI docs](https://snakemake.readthedocs.io/en/stable/executing/cli.html)
|
|
283
|
+
|
|
284
|
+
### Nextflow
|
|
285
|
+
|
|
286
|
+
**Alive** (nextflow.io, active docs + GitHub).
|
|
287
|
+
|
|
288
|
+
**What it gives.** `-resume` + task-hash cache, backed by LevelDB in `.nextflow/cache/<session-id>`.
|
|
289
|
+
Docs github page: "The cache key is the task hash... If a cached directory exists but validation
|
|
290
|
+
fails, the system increments tries and recomputes the hash." A `-dump-hashes` flag exists for
|
|
291
|
+
debugging why a cached task reran — but GitHub issue #4367 is an open feature request precisely
|
|
292
|
+
because that debugging is currently manual/painful: "users have to do a lot of manual work to
|
|
293
|
+
extract the relevant information from the logs" to figure out why a hash changed. So Nextflow gives
|
|
294
|
+
the hashing mechanism but **not yet** the "immediately visible" glanceable reason — worse on that
|
|
295
|
+
specific axis than Snakemake's `--reason`.
|
|
296
|
+
|
|
297
|
+
Cost to adopt: Nextflow's DSL and execution model (channels, processes) is heavier than Snakemake's
|
|
298
|
+
rule files for a non-genomics pipeline; ecosystem is bioinformatics-specific in convention even
|
|
299
|
+
though generic in principle.
|
|
300
|
+
|
|
301
|
+
Source: [nextflow.cache.md dev docs](https://github.com/nextflow-io/nextflow/blob/master/docs/developer/nextflow.cache.md), [Improve cache debugging with -dump-hashes #4367](https://github.com/nextflow-io/nextflow/issues/4367)
|
|
302
|
+
|
|
303
|
+
### CWL (Common Workflow Language)
|
|
304
|
+
|
|
305
|
+
**Alive** as a spec (v1.1/v1.2 docs at commonwl.org), `cwltool` reference runner actively maintained.
|
|
306
|
+
|
|
307
|
+
**What it gives.** `cwltool --cachedir` + per-step `${HASH}.status` files; v1.1 added `WorkReuse` to
|
|
308
|
+
opt individual tools/steps in or out of reuse. Confirmed via docs + GitHub issue #493 (cwl-runner
|
|
309
|
+
re-executing cached jobs unexpectedly) that cache correctness has known rough edges, particularly
|
|
310
|
+
around tools whose outputs embed absolute file paths.
|
|
311
|
+
|
|
312
|
+
Cost to adopt: CWL is a JSON/YAML IR designed for portability across execution engines
|
|
313
|
+
(Toil, Arvados, etc.) — heavier abstraction than needed for a single-machine, single-user pipeline;
|
|
314
|
+
picking it only for the hash-cache would be adopting an interchange format for no interchange need.
|
|
315
|
+
|
|
316
|
+
Source: [CWL v1.1 Workflow spec](https://www.commonwl.org/v1.1/Workflow.html), [cwl-runner re-executes cached jobs #493](https://github.com/common-workflow-language/cwltool/issues/493)
|
|
317
|
+
|
|
318
|
+
### Dagster
|
|
319
|
+
|
|
320
|
+
**Alive** (docs.dagster.io, active — freshness-policy docs current).
|
|
321
|
+
|
|
322
|
+
**What it gives.** Explicit "staleness" as a first-class UI concept, not just a cache hit/miss:
|
|
323
|
+
|
|
324
|
+
> "A Dagster asset is considered stale if Dagster knows that its code or upstream data has changed,
|
|
325
|
+
> but the asset hasn't been materialized since then to incorporate those changes. Any asset that
|
|
326
|
+
> depends on a stale asset is also stale."
|
|
327
|
+
|
|
328
|
+
`op_versions`/`observation_fns` let you version an asset's _code_, and staleness is computed from
|
|
329
|
+
whether upstream versions changed — again, exactly the "did the checker's own logic change" property
|
|
330
|
+
— surfaced in a UI (Dagit) with states PASS/WARN/FAIL/UNKNOWN per a `FreshnessPolicy`. This is the
|
|
331
|
+
best _dashboard_ answer found (better visual "immediately visible" story than Snakemake's CLI text),
|
|
332
|
+
at real cost: Dagster is a full orchestration platform (daemon/webserver, Python-decorator asset
|
|
333
|
+
graph, optional Dagster Cloud) — adopting it to track 20 document checks is materially heavier than
|
|
334
|
+
Snakemake for the same property.
|
|
335
|
+
|
|
336
|
+
Source: [Asset freshness policies | Dagster Docs](https://docs.dagster.io/guides/observe/asset-freshness-policies), [Just-computed SDAs show up as stale #11442](https://github.com/dagster-io/dagster/issues/11442) (a live bug report — confirms the feature is real and non-trivial to get exactly right even in Dagster itself)
|
|
337
|
+
|
|
338
|
+
### Airflow
|
|
339
|
+
|
|
340
|
+
Not independently fetched in depth — Airflow's scheduling model is time/trigger based (DAG runs on
|
|
341
|
+
a schedule or external trigger), not content-hash-based staleness detection by design; it is the
|
|
342
|
+
wrong shape of tool for "is this artifact stale relative to its input's content" and was correctly
|
|
343
|
+
deprioritized rather than researched further. **Documented negative result**, not an oversight: this
|
|
344
|
+
category is orchestration-of-schedules, not content-addressed caching, so it does not provide the
|
|
345
|
+
needed property natively (would require bolting on something like the above tools anyway).
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## 5. Data/experiment versioning: DVC, MLflow, W&B
|
|
350
|
+
|
|
351
|
+
### DVC
|
|
352
|
+
|
|
353
|
+
**Alive** (dvc.org, active docs).
|
|
354
|
+
|
|
355
|
+
**What it gives — very close, as hypothesized, but with a real gap.** Fetched `dvc status` docs
|
|
356
|
+
directly:
|
|
357
|
+
|
|
358
|
+
> "`_changed deps_` or `_changed outs_` means that there are changes in dependencies or outputs
|
|
359
|
+
> tracked by the stage." Example output:
|
|
360
|
+
>
|
|
361
|
+
> ```
|
|
362
|
+
> matrix-train.p:
|
|
363
|
+
> changed deps:
|
|
364
|
+
> modified: code/featurization.py
|
|
365
|
+
> ```
|
|
366
|
+
|
|
367
|
+
**This is the single clearest piece of evidence in the whole survey that a check's own script
|
|
368
|
+
counts as a "dep."** `code/featurization.py` — the _code_, not just data — is listed as a tracked
|
|
369
|
+
dependency in DVC's own official example, and its modification is what `dvc status` reports. That is
|
|
370
|
+
exactly "the checker's source changed, therefore stale," reported by name, with zero custom
|
|
371
|
+
tooling — DVC computes it from `dvc.yaml` stage definitions (`deps:`/`outs:`) plus content hashing
|
|
372
|
+
(MD5 by default) of everything listed.
|
|
373
|
+
|
|
374
|
+
`dvc dag` gives the visual dependency graph (structure only — confirmed by fetch: it does **not**
|
|
375
|
+
itself show live/stale status, that's `dvc status`'s job, they're complementary not overlapping).
|
|
376
|
+
|
|
377
|
+
Cost to adopt: low-to-moderate. One `dvc.yaml` with a `stage:` per check (`cmd:`, `deps:` = [document
|
|
378
|
+
|
|
379
|
+
- checker script], `outs:` = [check's result file]). No daemon. `dvc status` becomes the single
|
|
380
|
+
command that answers "what's stale" instead of a human-maintained table. The DVC-specific overhead
|
|
381
|
+
(designed for large binary data + Git-LFS-style remote storage, `.dvc` files, `dvc add`) is mostly
|
|
382
|
+
avoidable if only the pipeline/stage feature (not the data-versioning/remote-storage feature) is
|
|
383
|
+
used — worth confirming stage-only usage doesn't force the storage layer on.
|
|
384
|
+
|
|
385
|
+
**Verdict: DVC's `dvc status` is confirmed, by direct fetch, to be extremely close to what he wants** —
|
|
386
|
+
tied with Snakemake as the two strongest candidates, and DVC's output format (a literal
|
|
387
|
+
`changed deps: modified: <path>` list) is arguably even more directly "immediately visible" than
|
|
388
|
+
Snakemake's prose reasons.
|
|
389
|
+
|
|
390
|
+
Sources: [dvc status docs](https://doc.dvc.org/command-reference/status), [dvc dag docs](https://doc.dvc.org/command-reference/dag)
|
|
391
|
+
|
|
392
|
+
### MLflow / Weights & Biases
|
|
393
|
+
|
|
394
|
+
**Alive**, both actively developed, both extremely popular in 2026 ML tooling comparisons.
|
|
395
|
+
|
|
396
|
+
**What they give: nothing relevant.** These are **experiment trackers** (log metrics/params/
|
|
397
|
+
artifacts per run, compare runs in a UI, model registry) — not build systems and not staleness
|
|
398
|
+
detectors. Neither computes "is this result stale relative to its current input," both assume the
|
|
399
|
+
human decides when to log a new run. Search results (multiple 2026 comparison articles) describe
|
|
400
|
+
their differentiators purely as UI/collaboration/model-registry features, never staleness or
|
|
401
|
+
cache-invalidation. **This is a documented negative result**, not a gap in research: wrong tool
|
|
402
|
+
category entirely, ruled out correctly rather than by omission.
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
## 6. `pre-commit` (the framework)
|
|
407
|
+
|
|
408
|
+
**Alive** (pre-commit.com, active).
|
|
409
|
+
|
|
410
|
+
**What it gives.** Confirmed via fetch of pre-commit.com root docs:
|
|
411
|
+
|
|
412
|
+
- `files:` — regex filter for which files a hook applies to.
|
|
413
|
+
- `always_run: true` — hook runs even with no matching files (needed for hooks that don't operate
|
|
414
|
+
file-by-file, e.g. post-rewrite hooks).
|
|
415
|
+
- `stages:` — restricts a hook to specific git hook points (`pre-commit`, `pre-push`,
|
|
416
|
+
`commit-msg`, plus a `manual` stage for explicit-only invocation).
|
|
417
|
+
- `pre-commit run --all-files` — force every hook against the whole tree regardless of what's
|
|
418
|
+
staged; the manual "run everything now" escape hatch.
|
|
419
|
+
|
|
420
|
+
**What it does NOT give — confirmed negative result.** Fetch explicitly found no history/log
|
|
421
|
+
mechanism: "The documentation provided does **not** mention pre-commit maintaining a history or log
|
|
422
|
+
of which files each hook ran against or when hooks executed... no persistent audit trail of all hook
|
|
423
|
+
executions is documented." And no built-in detection of a hook that never fails: "No feature exists
|
|
424
|
+
for identifying hooks that may have become ineffective or redundant over time."
|
|
425
|
+
|
|
426
|
+
**Verdict:** pre-commit gives filtering/scoping (which hooks apply to which files) but is
|
|
427
|
+
**stateless between invocations** — it has no cache, no "ran against version X of this file,"
|
|
428
|
+
nothing that answers "is check N stale." It solves "which hooks should run on this file" (routing),
|
|
429
|
+
not "did this file change since the hook last ran" (staleness). Genuinely does not provide the core
|
|
430
|
+
property, contrary to a plausible-sounding assumption that a hook framework would.
|
|
431
|
+
|
|
432
|
+
Source: [pre-commit.com](https://pre-commit.com/)
|
|
433
|
+
|
|
434
|
+
---
|
|
435
|
+
|
|
436
|
+
## 7. Mutation testing as "a check on the checks"
|
|
437
|
+
|
|
438
|
+
**The concept he needs, stated precisely:** a check that has never produced a negative verdict on
|
|
439
|
+
real drift is not a check — it's decoration. Mutation testing tools exist for exactly this question,
|
|
440
|
+
but only for **code test suites**, not document/prose checks — an important scope gap to be honest
|
|
441
|
+
about.
|
|
442
|
+
|
|
443
|
+
### mutmut (Python)
|
|
444
|
+
|
|
445
|
+
**Alive** — GitHub shows 680 commits, active CI. Mutates production code, reruns the test suite per
|
|
446
|
+
mutation, reports "survivors" (mutations no test caught) — the code-level version of "check that
|
|
447
|
+
never fires."
|
|
448
|
+
|
|
449
|
+
Source: [boxed/mutmut](https://github.com/boxed/mutmut)
|
|
450
|
+
|
|
451
|
+
### cosmic-ray (Python)
|
|
452
|
+
|
|
453
|
+
**Alive** — PyPI + readthedocs show a release dated **2026-04-02**, actively maintained. Same
|
|
454
|
+
survivor-mutant model as mutmut.
|
|
455
|
+
|
|
456
|
+
Source: [cosmic-ray docs](https://cosmic-ray.readthedocs.io/)
|
|
457
|
+
|
|
458
|
+
### Stryker (JS/.NET/Scala family)
|
|
459
|
+
|
|
460
|
+
**Alive, actively releasing across sub-projects in 2026** — StrykerJS v9.6.1 (April 2026),
|
|
461
|
+
Stryker.NET 4.13 added Microsoft Testing Platform support (March 2026), VS Code plugin (Nov 2025).
|
|
462
|
+
|
|
463
|
+
### PIT / Pitest (Java)
|
|
464
|
+
|
|
465
|
+
**Alive** — v4.16.0, dated 2026-07-03 per a fetched comparison article; multiple 2026 tutorial/guide
|
|
466
|
+
articles confirm ongoing relevance as "the gold standard test coverage" tool for JVM mutation
|
|
467
|
+
testing.
|
|
468
|
+
|
|
469
|
+
### pytest-gremlins — the closest thing to "vacuous check detector," still code-scoped
|
|
470
|
+
|
|
471
|
+
**Alive** (291 commits, Python 3.11+, active). Fetched README:
|
|
472
|
+
|
|
473
|
+
> "Fast-first mutation testing for pytest. Speed that makes mutation testing practical for everyday
|
|
474
|
+
> TDD."
|
|
475
|
+
|
|
476
|
+
Injects bugs ("gremlins"), reports which mutations tests eliminate vs. which slip through.
|
|
477
|
+
|
|
478
|
+
### falsegreen — the one STATIC-analysis hit for "vacuous test," not mutation-based
|
|
479
|
+
|
|
480
|
+
**Alive** (149 commits, tagged release v0.9.2, on PyPI, GitHub Actions CI). Fetched README: AST-scans
|
|
481
|
+
pytest files for assertion-free/always-true/self-referential/mocked-unit-under-test patterns, 47
|
|
482
|
+
active codes (C1–C59 family). Explicitly confirmed: **C2 = "test with no assertion at all," HIGH
|
|
483
|
+
confidence, described as blocking**. This is a real, shipped, cheap **static pre-filter** for "this
|
|
484
|
+
test cannot possibly fail" — the nearest thing found in this whole survey to "audits a check suite
|
|
485
|
+
for checks that never fire," and it's cheaper than mutation testing (no test execution needed).
|
|
486
|
+
|
|
487
|
+
**Honest scope gap:** none of the five tools above operate on anything but a code test suite (a
|
|
488
|
+
`pytest`/JUnit/xUnit run with assertions). **No shipped tool was found that mutation-tests an LLM
|
|
489
|
+
agent-run prose check** (e.g., "does the citation-verification skill actually catch a fabricated
|
|
490
|
+
citation, or does it rubber-stamp everything?"). That would have to be hand-built: deliberately
|
|
491
|
+
corrupt the document (inject a fake citation, break a citation's page number, flip a claim) and
|
|
492
|
+
confirm the specific check flags it — the same _idea_ as mutation testing, applied manually because
|
|
493
|
+
no tool ships this for prose/LLM checks. This is the one property in the whole survey that is
|
|
494
|
+
**genuinely empty** as a shipped tool, not merely heavy to adopt.
|
|
495
|
+
|
|
496
|
+
Sources: [boxed/mutmut](https://github.com/boxed/mutmut), [cosmic-ray docs](https://cosmic-ray.readthedocs.io/), [vinicq/falsegreen](https://github.com/vinicq/falsegreen), [mikelane/pytest-gremlins](https://github.com/mikelane/pytest-gremlins)
|
|
497
|
+
|
|
498
|
+
---
|
|
499
|
+
|
|
500
|
+
## 8. Registration/strictness patterns ("unregistered = ERROR, not silent skip")
|
|
501
|
+
|
|
502
|
+
**pytest `--strict-markers` / `--strict-config`.** Confirmed via docs + a live GitHub issue: "When
|
|
503
|
+
the `--strict-markers` command-line flag is passed, any unknown marks applied with
|
|
504
|
+
`@pytest.mark.name_of_the_mark` will trigger an error"; registering markers in config is required to
|
|
505
|
+
avoid the error. Also confirmed a **currently-open regression** (issue #14442, pytest 9): "The
|
|
506
|
+
`OverrideIniAction` compatibility code added in pytest 9 does not play nicely with `addopts`, and
|
|
507
|
+
strictness options configured that way now get silently ignored" — a sharp, on-topic irony: the
|
|
508
|
+
_strictness-enforcement flag itself_ was found silently not enforcing, in the exact "check that
|
|
509
|
+
looks like it's protecting you but isn't" shape this whole research is about.
|
|
510
|
+
|
|
511
|
+
**mypy `--strict`, ESLint `--max-warnings 0`, `cargo deny`** — not independently re-fetched (well
|
|
512
|
+
known, stable, unambiguous flags); the general shape confirmed by the pytest case transfers directly:
|
|
513
|
+
these all convert "thing not declared/allowed" from a silent no-op into a nonzero exit code. The
|
|
514
|
+
generalizable pattern for `PIPELINE-STATUS.md`: **a check with no matching hash-verified entry for
|
|
515
|
+
the current document version should be a hard error at pipeline-run time, not a stale ✅ left in
|
|
516
|
+
place** — this is a policy shape, not a specific tool to adopt; every task runner/build tool above
|
|
517
|
+
(Snakemake, DVC, Task) already enforces this by construction (no stale cache entry is ever silently
|
|
518
|
+
reported as fresh — it's either a hash match or it reruns).
|
|
519
|
+
|
|
520
|
+
---
|
|
521
|
+
|
|
522
|
+
## 9. Provenance/attestation: in-toto, SLSA, sigstore
|
|
523
|
+
|
|
524
|
+
**Alive**, both frameworks under active CNCF-adjacent development in 2026.
|
|
525
|
+
|
|
526
|
+
**What they give.** in-toto: signed attestations per pipeline step, recording "who did what, when,"
|
|
527
|
+
chained so a consumer can verify the full step sequence occurred as declared. SLSA: provenance levels
|
|
528
|
+
(SLSA 1–4) for build integrity; sigstore: keyless signing + a public transparency log for attestations.
|
|
529
|
+
|
|
530
|
+
**Verdict on fit — confirmed by direct search, not assumed: enterprise/supply-chain scale, not
|
|
531
|
+
single-user-document scale.** Two independent points found:
|
|
532
|
+
|
|
533
|
+
1. Adoption-barrier research (a qualitative study of 1,523 GitHub issues across 233 repos, cited by
|
|
534
|
+
a fetched search result) found practitioners cite "complex implementation" and "unclear
|
|
535
|
+
communication" as the dominant SLSA adoption barriers — i.e., real projects with CI teams find it
|
|
536
|
+
heavy, which is a strong signal it's disproportionate for one person and twenty markdown checks.
|
|
537
|
+
2. A direct critique surfaced: "SigStore creates a transparency log that makes build information
|
|
538
|
+
public, but enterprise software is closed-source and built for self-use, and disclosing its build
|
|
539
|
+
information to the general public cannot be tolerated" — the mismatch runs in _both_ directions
|
|
540
|
+
(too heavy for solo use; also structurally wrong for private documents, since sigstore's default
|
|
541
|
+
posture publishes attestations to a public log — the opposite of what a private paper pipeline
|
|
542
|
+
wants).
|
|
543
|
+
|
|
544
|
+
**This is a genuinely empty fit** for this problem, correctly ruled out rather than under-researched:
|
|
545
|
+
the property "step X ran on input hash H, producing output hash H′, signed" is real and exists, but
|
|
546
|
+
every shipped implementation is scaled and hardened for adversarial multi-party software supply
|
|
547
|
+
chains (verifying a _stranger's_ build didn't get tampered with), not for one person tracking their
|
|
548
|
+
own document-check pipeline where the trust model is "do I, myself, trust my own hash computation."
|
|
549
|
+
Borrowing only the _idea_ (a signed/hashed record chaining input→check→output) is sound; borrowing
|
|
550
|
+
the _tooling_ is not.
|
|
551
|
+
|
|
552
|
+
Sources: [SLSA Provenance Part 3: Adoption Challenges](https://www.legitsecurity.com/blog/slsa-provenance-blog-series-part3-challenges-of-adopting-slsa-provenance), [in-toto/attestation README](https://github.com/in-toto/attestation/blob/main/README.md)
|
|
553
|
+
|
|
554
|
+
---
|
|
555
|
+
|
|
556
|
+
## Ranked table
|
|
557
|
+
|
|
558
|
+
| Property we need | Shipped thing that provides it | Adoption cost | Verdict |
|
|
559
|
+
| ---------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
560
|
+
| Content hash (not timestamp) of the **document** as a dependency | DVC (`deps:`), Snakemake (`input:`), Task (`sources:`, `method: checksum`), Bazel/Nix/Buck2 (action/derivation inputs), Nx/Turborepo/moon (task inputs) | Low (Task) → very high (Nix) | **ADOPT** — trivially available in the low-cost tier |
|
|
561
|
+
| Hash of the **checker's own script/prompt itself**, so editing the check invalidates its own cached result | Confirmed explicitly: Nix (`.drv` hash includes builder script — even a comment changes the store path), Bazel (action key includes "the command to be executed"), Snakemake (FAQ: rerun triggers include "the code of the rule"), DVC (own docs example literally lists `code/featurization.py` as a `changed dep`) | Low (DVC, Snakemake) → very high (Nix, Bazel) | **ADOPT** — DVC or Snakemake gives this without a build-system migration |
|
|
562
|
+
| At-a-glance "what's stale and why," readable by a human, no digging | Snakemake `--dry-run --reason` (prose reasons per rule); DVC `dvc status` (`changed deps: modified: <path>` — arguably the single closest match to "marks... inline"); Dagster UI (PASS/WARN/FAIL/UNKNOWN per asset, richest visual, heaviest platform) | Low (DVC/Snakemake CLI text) → high (Dagster full platform) | **ADOPT** — DVC's output format is close to literally what was asked for |
|
|
563
|
+
| Fast preview without running anything (`--dry-run`-equivalent) | Snakemake `--dry-run`; Turborepo `--dry=json` (confirmed to sometimes disagree with real run — issue #9038/#9044); Nx `print-affected` (confirmed to sometimes diverge from run-time hash — issue #16153); Task's checksum-based skip is itself near-instant | Low–moderate | **ADOPT-PARTIAL** — the preview exists everywhere but two of three JS tools have open bug reports about the preview lying, ironically the same disease being solved for |
|
|
564
|
+
| Routing: which checks apply to which files, cheap vs. expensive tiering | `pre-commit` `files:`/`stages:`/`always_run` | Low (already YAML-shaped, easy to add) | **ADOPT-PARTIAL** — good for routing, contributes nothing to staleness itself (confirmed no run history/log exists) |
|
|
565
|
+
| "Declared-but-never-fires" audit of the checks themselves | falsegreen (static, code-only), mutmut/cosmic-ray/Stryker/PIT/pytest-gremlins (mutation, code-only) | Low (falsegreen, if checks were pytest) | **ADOPT-PARTIAL** — real and cheap, but only reaches the subset of checks that are code with assertions; the LLM-agent prose checks are out of scope for every one of these tools |
|
|
566
|
+
| Mutation-style "does this specific prose/LLM check actually catch a real defect" audit | — | — (would be hand-built: seed a known defect into the doc, confirm the check flags it) | **GENUINELY EMPTY** — no shipped tool found; this is the one place the hypothesis "solved problem" does not fully hold |
|
|
567
|
+
| Unregistered-thing-is-an-error strictness pattern | `pytest --strict-markers`, `mypy --strict`, `eslint --max-warnings 0` (pattern only; also caught pytest 9 silently _breaking_ this exact flag — issue #14442) | N/A — a policy shape, not a separate tool | **ADOPT** as a design principle inside whichever runner is chosen, not as a separate dependency |
|
|
568
|
+
| Cryptographic step-provenance (input hash → step → output hash, signed) | in-toto / SLSA / sigstore | Very high, and wrong trust model (publishes to a public log) for a private paper pipeline | **GENUINELY EMPTY** for this scale — correctly ruled out, not under-researched |
|
|
569
|
+
| ML-experiment "which run used which data/params" | MLflow / W&B | N/A | **GENUINELY EMPTY** — wrong tool category, confirmed by direct research (no cache-invalidation or staleness feature exists in either) |
|
|
570
|
+
| Time/schedule-based DAG orchestration | Airflow | N/A | **GENUINELY EMPTY** for this property — Airflow schedules; it does not compute content-hash staleness by design |
|
|
571
|
+
|
|
572
|
+
---
|
|
573
|
+
|
|
574
|
+
## The one paragraph: smallest adoption that kills the lying-status-table problem
|
|
575
|
+
|
|
576
|
+
**DVC's pipeline feature (`dvc.yaml` + `dvc status`), used for its stage-hashing alone, is the
|
|
577
|
+
smallest adoption that fully replaces `PIPELINE-STATUS.md` with a computed answer.** Define one
|
|
578
|
+
`dvc.yaml` stage per check: `cmd:` is the check's actual command (script or LLM-agent invocation),
|
|
579
|
+
`deps:` lists both the target document(s) _and_ the checker's own script/prompt file, `outs:` is
|
|
580
|
+
wherever the check writes its verdict. From that point, "is check N stale" is never declared by a
|
|
581
|
+
human again — `dvc status` computes it from content hashes of everything in `deps:`, and its own
|
|
582
|
+
docs example already demonstrates exactly the wanted case (editing the checker's code shows up as
|
|
583
|
+
`changed deps: modified: <checker-script>`, indistinguishable in the tool's eyes from editing the
|
|
584
|
+
document itself). No daemon, no new language, no monorepo restructuring, and DVC's heavier
|
|
585
|
+
data-versioning/remote-storage machinery can be ignored entirely if only the pipeline/stage feature
|
|
586
|
+
is used. Snakemake is the close second choice and arguably the better long-term fit _if_ the pipeline
|
|
587
|
+
grows real DAG structure between checks (some checks depending on others' outputs) or needs the
|
|
588
|
+
prose-level `--reason` explanation text rather than DVC's terser diff-list — but for the immediate
|
|
589
|
+
goal of "kill the lying table with the least new surface area," DVC's stage-and-status pair is the
|
|
590
|
+
smaller, faster adoption. Note explicitly what neither tool solves: whether an LLM-agent-run check is
|
|
591
|
+
actually _testing_ anything (the vacuous-check problem) is not addressed by hashing — that remains
|
|
592
|
+
the one genuinely unsolved piece, requiring a hand-built mutation-style probe per check type.
|