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,397 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* artifact-coverage.mjs — does the released bundle actually contain what the paper points at?
|
|
4
|
+
*
|
|
5
|
+
* node artifact-coverage.mjs <paper-dir> report
|
|
6
|
+
* node artifact-coverage.mjs <paper-dir> --flags-only for hooks and pre-commit
|
|
7
|
+
*
|
|
8
|
+
* 🔴 WHY THIS EXISTS. On 2026-08-06, hours before a submission, the author asked a plain question —
|
|
9
|
+
* "did we ever actually run the artifact?" — and the answer exposed that the released bundle held NO
|
|
10
|
+
* DATA for the paper's §5.2: the agent experiment behind "stopped and asked a human in 34 of 48
|
|
11
|
+
* gated runs". That result is in the ABSTRACT, it is the single reason an agent workshop is the
|
|
12
|
+
* right venue for the paper, and the artifact reference promises "every data file behind the
|
|
13
|
+
* numbers in this paper".
|
|
14
|
+
*
|
|
15
|
+
* Six `harden-paper` passes and five review panels had not caught it, and none of them was broken.
|
|
16
|
+
* They were all asking a different question:
|
|
17
|
+
*
|
|
18
|
+
* the anonymity gate asks — does anything in the bundle leak an identity?
|
|
19
|
+
* the numbers gate asks — does a printed number match its data file?
|
|
20
|
+
* the artifact reviewer asks— does the code in the bundle run?
|
|
21
|
+
* NOBODY asked — is the data for this experiment IN THE BUNDLE AT ALL?
|
|
22
|
+
*
|
|
23
|
+
* A missing directory passes every one of those: nothing leaks, nothing mismatches, and everything
|
|
24
|
+
* present still runs. Absence is invisible to a checker that only inspects what is there.
|
|
25
|
+
*
|
|
26
|
+
* The two checks below are deliberately dumb, because dumb is what survives:
|
|
27
|
+
* 1. every body section that REPORTS A NUMBER is named somewhere in the artifact's index;
|
|
28
|
+
* 2. every path the index names EXISTS in the bundle.
|
|
29
|
+
*
|
|
30
|
+
* ── 2026-08-10: THE THIRD CHECK RUNS THE OTHER WAY ────────────────────────────────────────────
|
|
31
|
+
*
|
|
32
|
+
* 🔴 Both checks above run FORWARD, from the paper outward, and neither can see the opposite
|
|
33
|
+
* failure: a result that EXISTS ON DISK and that the paper never mentions. Ported from
|
|
34
|
+
* `check_artifact_coverage.py` in MedSci Skills (Nam, Jeong & Kim, arXiv:2606.09500v4, tag
|
|
35
|
+
* v3.8.0), where it is a Major verdict named `DISK_UNREPORTED` and defined as
|
|
36
|
+
*
|
|
37
|
+
* "an analysis output that exists on disk … is never mentioned in the manuscript. The work was
|
|
38
|
+
* done and run but its result, which may contradict the headline, is silently absent."
|
|
39
|
+
*
|
|
40
|
+
* That is a CHERRY-PICKING DETECTOR, and it is the one direction no other gate in this pipeline
|
|
41
|
+
* covers. `check-anon` reads the bundle. `paper_numbers` reads printed quantities. `arm_permutation`
|
|
42
|
+
* reads a quantity against its arm label. Every one of them starts from something the paper says.
|
|
43
|
+
* An experiment that was run and quietly dropped says nothing, so nothing looks at it.
|
|
44
|
+
*
|
|
45
|
+
* ⚠️ WHAT WE PORTED IS THE MECHANISM, NOT THE PAPER'S HEADLINE. That paper's 27/27-versus-11/27
|
|
46
|
+
* result does not survive its own artifact: seven of the 27 defects were in files never shown to
|
|
47
|
+
* the model, and one of its eleven "hits" is a substring match on the word `random` in a sentence
|
|
48
|
+
* about random forests. The honest number is 10/27, and 10/20 on what the model could see. Read
|
|
49
|
+
* `the author's private research notes` §2.4 before citing
|
|
50
|
+
* anything from it. The DETECTOR is a good idea regardless of how its evaluation was scored.
|
|
51
|
+
*
|
|
52
|
+
* 🔴 FALSE POSITIVES ARE THE WHOLE DESIGN PROBLEM, AND THE ANSWER IS AN IGNORE SET YOU CAN COUNT.
|
|
53
|
+
* `repro/` is mostly things nobody should ever report: fixtures, fetch caches, working clones,
|
|
54
|
+
* harness scratch, per-repository dumps. So this check needs an ignore set — and an ignore set that
|
|
55
|
+
* is applied silently would turn a cherry-picking detector into the very thing it exists to catch.
|
|
56
|
+
* So it is PRINTED IN FULL ON EVERY RUN, findings or none, exactly as `check_grandfather` in
|
|
57
|
+
* `repro/paper_numbers.py` prints the quantities the numbers gate does not cover. Two halves:
|
|
58
|
+
* — structural rules, in the code below, each one printed with the names it swallowed;
|
|
59
|
+
* — `repro/unreported-grandfathered.txt`, one row per knowingly-unreported analysis, and rows of
|
|
60
|
+
* the form `ships-as:<path>` are RE-VERIFIED against the bundle so that reason cannot rot.
|
|
61
|
+
*/
|
|
62
|
+
import { readFileSync, existsSync, readdirSync, statSync } from "node:fs";
|
|
63
|
+
import { join } from "node:path";
|
|
64
|
+
import {
|
|
65
|
+
headings as mdHeadings,
|
|
66
|
+
splitSections,
|
|
67
|
+
requireMarkdown,
|
|
68
|
+
} from "../../../lib/markdown.mjs";
|
|
69
|
+
|
|
70
|
+
// Markup is parsed with a parser (`CLAUDE.md`, 2026-08-11). We fail rather than degrade: without
|
|
71
|
+
// the parser NOT A SINGLE section would be found in the paper, and the check "every section with a
|
|
72
|
+
// number is named in the artifact index" would pass clean — that is exactly the failure this file
|
|
73
|
+
// was written for (absence is invisible to a checker that looks only at what is present).
|
|
74
|
+
requireMarkdown();
|
|
75
|
+
|
|
76
|
+
const args = process.argv.slice(2);
|
|
77
|
+
const dir = args.find((a) => !a.startsWith("--"));
|
|
78
|
+
const flagsOnly = args.includes("--flags-only");
|
|
79
|
+
if (!dir) {
|
|
80
|
+
console.error("usage: node artifact-coverage.mjs <paper-dir> [--flags-only]");
|
|
81
|
+
process.exit(0);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const paperPath = ["paper.md", "draft.md"]
|
|
85
|
+
.map((f) => join(dir, f))
|
|
86
|
+
.find(existsSync);
|
|
87
|
+
// 🔴 A SILENT exit(0) here read as «clean» for four of five papers. Measured 2026-08-25: only
|
|
88
|
+
// <paper-a> has a markdown source; agenticdev/aisec/<paper-e>/scored ship
|
|
89
|
+
// `paper.tex`, so this checker printed NOTHING and returned 0 on the camera-ready — which has
|
|
90
|
+
// an artifact (reproduce.py, data/) it was built to inspect. Nothing anywhere said so, and the
|
|
91
|
+
// CI step mirrored the same condition, so both layers agreed to say nothing. A skip that cannot
|
|
92
|
+
// be told apart from a pass is the failure this whole file exists to prevent.
|
|
93
|
+
if (!paperPath) {
|
|
94
|
+
console.error(
|
|
95
|
+
`⏭️ artifact coverage SKIPPED for ${dir} — no paper.md/draft.md. This checker\n reads markdown only; a .tex paper is NOT covered by it. Absence of findings here is\n absence of checking, not a clean bill.`,
|
|
96
|
+
);
|
|
97
|
+
process.exit(0);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// The bundle is whatever directory the paper releases. `artifact-anon` is the anonymised one that
|
|
101
|
+
// actually ships; a working `repro/` is NOT the release and must never be treated as one.
|
|
102
|
+
const bundle = [join(dir, "repro/artifact-anon"), join(dir, "artifact")].find(
|
|
103
|
+
existsSync,
|
|
104
|
+
);
|
|
105
|
+
if (!bundle) {
|
|
106
|
+
console.error(
|
|
107
|
+
`⏭️ artifact coverage SKIPPED for ${dir} — no repro/artifact-anon and no artifact/ directory.`,
|
|
108
|
+
);
|
|
109
|
+
process.exit(0);
|
|
110
|
+
}
|
|
111
|
+
const indexPath = ["NUMBERS.md", "README.md"]
|
|
112
|
+
.map((f) => join(bundle, f))
|
|
113
|
+
.find(existsSync);
|
|
114
|
+
if (!indexPath) {
|
|
115
|
+
const msg = `${bundle} has no NUMBERS.md and no README.md — nothing maps the paper's numbers to its data`;
|
|
116
|
+
console.error(`📦 artifact — ${msg}`);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const paper = readFileSync(paperPath, "utf8").replace(/<!--[\s\S]*?-->/g, "");
|
|
121
|
+
const index = readFileSync(indexPath, "utf8");
|
|
122
|
+
const findings = [];
|
|
123
|
+
|
|
124
|
+
// ---- 1. every number-reporting section is represented in the index --------------------------
|
|
125
|
+
// "Reports a number" means a BOLD span containing digits — the paper's own convention for a figure
|
|
126
|
+
// it is asserting, as opposed to a section number or a citation. A section that merely mentions
|
|
127
|
+
// §4.3 in passing does not trip this.
|
|
128
|
+
// 2026-08-11: both the body boundary and the split into sections come from the parser. The former
|
|
129
|
+
// `^## ` / `^### ` counted any line with hashes as a section, including hashes inside a ```-block:
|
|
130
|
+
// a paper QUOTING somebody else's `## 4.2 …` in an example got an extra "section", and its bold
|
|
131
|
+
// numbers got a requirement to be named in the artifact index. The `-1 / >>> 0` convention is kept:
|
|
132
|
+
// the former expression did not treat a heading on the very first line of the file as a boundary
|
|
133
|
+
// either.
|
|
134
|
+
const freeH = mdHeadings(paper).find(
|
|
135
|
+
(h) => h.depth === 2 && /^(Limitations|References|Ethical)/u.test(h.text),
|
|
136
|
+
);
|
|
137
|
+
const body = paper.slice(0, (freeH ? freeH.offset : -1) >>> 0 || paper.length);
|
|
138
|
+
const SECTION_NUM = /^(\d+(?:\.\d+)?)\.?\s+(.+)$/u;
|
|
139
|
+
const SUBSECTION_NUM = /^(\d+\.\d+)\s+(.+)$/u;
|
|
140
|
+
for (const chunk of splitSections(body, { min: 2, max: 2 })) {
|
|
141
|
+
const h = chunk.heading && SECTION_NUM.exec(chunk.heading.text);
|
|
142
|
+
if (!h) continue;
|
|
143
|
+
const subs = splitSections(chunk.raw, { min: 3, max: 3 });
|
|
144
|
+
for (const subSec of subs) {
|
|
145
|
+
// `subs.length === 1` means "there are no subsections at all", exactly what used to be checked
|
|
146
|
+
// as `sub === chunk`: the numbers are then counted against the section itself. If subsections DO
|
|
147
|
+
// exist, the section's leading chunk (before the first `###`) is not checked — as it was before.
|
|
148
|
+
const sh = subSec.heading
|
|
149
|
+
? SUBSECTION_NUM.exec(subSec.heading.text)
|
|
150
|
+
: subs.length === 1
|
|
151
|
+
? [null, h[1], h[2]]
|
|
152
|
+
: null;
|
|
153
|
+
if (!sh) continue;
|
|
154
|
+
const sub = subSec.raw;
|
|
155
|
+
const num = sh[1];
|
|
156
|
+
// Bold spans with digits that are not section pointers or bracketed citations.
|
|
157
|
+
const reported = (
|
|
158
|
+
sub
|
|
159
|
+
.replace(/§\s*\d+(\.\d+)*/g, "")
|
|
160
|
+
.replace(/\[[\d,\s–-]+\]/g, "")
|
|
161
|
+
.match(/\*\*[^*]*\d[^*]*\*\*/g) || []
|
|
162
|
+
).length;
|
|
163
|
+
if (!reported) continue;
|
|
164
|
+
const named = new RegExp(
|
|
165
|
+
`§\\s*${num.replace(".", "\\.")}\\b|\\b${num.replace(".", "\\.")}\\b`,
|
|
166
|
+
).test(index);
|
|
167
|
+
if (!named)
|
|
168
|
+
findings.push(
|
|
169
|
+
`§${num} reports ${reported} bolded figure(s) and is named NOWHERE in ` +
|
|
170
|
+
`${indexPath.replace(dir + "/", "")} — the released bundle may hold no data for it`,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ---- 2. every path the index names actually exists -------------------------------------------
|
|
176
|
+
// Backtick-quoted things that look like a path into the bundle. A promise to a file that is not
|
|
177
|
+
// there is worse than no promise: it reads as released.
|
|
178
|
+
const seen = new Set();
|
|
179
|
+
for (const m of index.matchAll(/`([A-Za-z0-9_./-]+\/[A-Za-z0-9_.*/-]+)`/g)) {
|
|
180
|
+
let p = m[1];
|
|
181
|
+
if (seen.has(p) || p.includes("*") || p.startsWith("http")) continue;
|
|
182
|
+
seen.add(p);
|
|
183
|
+
// A paper about repositories names a LOT of `owner/repo`, and those are not paths. Require a file
|
|
184
|
+
// extension: it is the one signal that separates `census/rows.tsv` from `saleor/apps`. Caught on
|
|
185
|
+
// the first run, when three GitHub repositories were reported as missing directories.
|
|
186
|
+
if (!/\.[a-z0-9]{1,5}$/i.test(p)) continue;
|
|
187
|
+
// `repro/` is the WORKING tree, deliberately not in the anonymised bundle — the whole reason the
|
|
188
|
+
// bundle exists separately is that repro holds real names in fixtures.
|
|
189
|
+
if (p.startsWith("repro/")) continue;
|
|
190
|
+
// A directory prefix is enough: the index names files inside runs that may be regenerated.
|
|
191
|
+
const head = p.split("/")[0];
|
|
192
|
+
if (!existsSync(join(bundle, head)) && !readdirSync(bundle).includes(head))
|
|
193
|
+
findings.push(
|
|
194
|
+
`the index names \`${p}\` and \`${head}\` is not in the bundle`,
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ---- 3. reverse: a result on disk that neither the paper nor the index mentions ---------------
|
|
199
|
+
// The unit is an EXPERIMENT DIRECTORY under `repro/` that holds a headline write-up — this
|
|
200
|
+
// project's own convention, stated in `repro/README.md`: "you go into the folder, and there is a
|
|
201
|
+
// RESULTS.md with the full write-up". A directory with a RESULTS.md is a run somebody finished and
|
|
202
|
+
// wrote up.
|
|
203
|
+
//
|
|
204
|
+
// "Mentioned" is: the directory's name, or its name with a trailing -YYYY-MM-DD stripped, appears
|
|
205
|
+
// in the paper OR in the released index. Matching is LITERAL on purpose. Prefix or fuzzy matching
|
|
206
|
+
// would quietly absolve `gate-effect-spike-2026-08-05` on the strength of the shipped
|
|
207
|
+
// `gate-effect/`, and a false NEGATIVE in a cherry-picking detector is the failure that costs
|
|
208
|
+
// something. A rename is therefore a row in the ignore file, where it is visible and re-verified,
|
|
209
|
+
// rather than an inference nobody can see.
|
|
210
|
+
//
|
|
211
|
+
// 🔴 THIS LEG READS EVERY INDEX FILE, NOT JUST THE FIRST. Check 1 above resolves `indexPath` to
|
|
212
|
+
// NUMBERS.md *or* README.md and stops, which is right for it: it asks whether the map from numbers
|
|
213
|
+
// to data exists. Reused here it produced three false positives on the first run —
|
|
214
|
+
// `enforcement-surface`, `ladder-prototype` and `tier-a-prime` are all named in the bundle's
|
|
215
|
+
// README.md and in no other file, so a reverse check reading only NUMBERS.md accused the bundle of
|
|
216
|
+
// hiding three analyses it ships and documents. A cherry-picking detector that cries wolf gets
|
|
217
|
+
// muted, and a muted detector is how the failure happens twice.
|
|
218
|
+
const ledger = [];
|
|
219
|
+
const HEADLINE = /^(RESULTS|SUMMARY|FINDINGS|METRICS)\.(md|json|tsv|csv|txt)$/i;
|
|
220
|
+
const repro = join(dir, "repro");
|
|
221
|
+
const bundleName = bundle
|
|
222
|
+
.slice(dir.length + 1)
|
|
223
|
+
.split("/")
|
|
224
|
+
.pop();
|
|
225
|
+
const indexAll = ["NUMBERS.md", "README.md"]
|
|
226
|
+
.map((f) => join(bundle, f))
|
|
227
|
+
.filter(existsSync)
|
|
228
|
+
.map((f) => readFileSync(f, "utf8"))
|
|
229
|
+
.join("\n");
|
|
230
|
+
|
|
231
|
+
if (!existsSync(repro)) {
|
|
232
|
+
ledger.push(
|
|
233
|
+
" reverse check (results on disk nobody reports): SKIPPED — no repro/ directory",
|
|
234
|
+
);
|
|
235
|
+
} else {
|
|
236
|
+
// Structural ignores. Each is a RULE, and each prints what it swallowed.
|
|
237
|
+
const swallowed = new Map([
|
|
238
|
+
[
|
|
239
|
+
`the released bundle itself (${bundleName}/) — it is the shipped copy of these same analyses, ` +
|
|
240
|
+
"so counting it would report every one of them a second time",
|
|
241
|
+
[],
|
|
242
|
+
],
|
|
243
|
+
[
|
|
244
|
+
"dot-directories and build/dependency trees (node_modules, __pycache__)",
|
|
245
|
+
[],
|
|
246
|
+
],
|
|
247
|
+
]);
|
|
248
|
+
const allowPath = join(repro, "unreported-grandfathered.txt");
|
|
249
|
+
const allowed = new Map();
|
|
250
|
+
if (existsSync(allowPath)) {
|
|
251
|
+
for (const line of readFileSync(allowPath, "utf8").split("\n")) {
|
|
252
|
+
const row = line.split("#")[0].trimEnd();
|
|
253
|
+
if (!row.trim()) continue;
|
|
254
|
+
const [name, reason, ...rest] = row.split("\t").map((c) => c.trim());
|
|
255
|
+
if (name)
|
|
256
|
+
allowed.set(
|
|
257
|
+
name,
|
|
258
|
+
[reason, ...rest].filter(Boolean).join(" — ") || "(NO REASON GIVEN)",
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const candidates = [];
|
|
264
|
+
for (const name of readdirSync(repro)) {
|
|
265
|
+
const p = join(repro, name);
|
|
266
|
+
if (!statSync(p).isDirectory()) continue;
|
|
267
|
+
if (name === bundleName) {
|
|
268
|
+
swallowed.get([...swallowed.keys()][0]).push(name);
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (
|
|
272
|
+
name.startsWith(".") ||
|
|
273
|
+
name === "node_modules" ||
|
|
274
|
+
name === "__pycache__"
|
|
275
|
+
) {
|
|
276
|
+
swallowed.get([...swallowed.keys()][1]).push(name);
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
const heads = readdirSync(p).filter((f) => HEADLINE.test(f));
|
|
280
|
+
if (heads.length) candidates.push({ name, heads });
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// 🔴 WHOLE-TOKEN, AND NEVER ON A SHORT SLUG. `includes()` was the first version and it acquitted
|
|
284
|
+
// a directory called `a-2026-01-01` on the strength of the letter "a" appearing in the prose —
|
|
285
|
+
// caught by the harness case that asserts the stated finding count. Substring matching in a
|
|
286
|
+
// detector like this fails in the dangerous direction: it invents evidence that a result was
|
|
287
|
+
// mentioned. Hyphens count as part of the token, so `gate-effect` does not match inside
|
|
288
|
+
// `gate-effect-spike`, and a slug shorter than four characters is not used for matching at all.
|
|
289
|
+
const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
290
|
+
const mentions = (t, s) =>
|
|
291
|
+
s.length >= 4 && new RegExp(`(?<![\\w-])${esc(s)}(?![\\w-])`).test(t);
|
|
292
|
+
|
|
293
|
+
const reported = [],
|
|
294
|
+
ignored = [],
|
|
295
|
+
unreported = [];
|
|
296
|
+
for (const c of candidates) {
|
|
297
|
+
const slug = c.name.replace(/-\d{4}-\d{2}-\d{2}$/, "");
|
|
298
|
+
const named = (t) => mentions(t, c.name) || mentions(t, slug);
|
|
299
|
+
if (named(paper)) {
|
|
300
|
+
reported.push(`${c.name} (named in the paper)`);
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
if (named(indexAll)) {
|
|
304
|
+
reported.push(`${c.name} (named in the index)`);
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
if (allowed.has(c.name)) {
|
|
308
|
+
ignored.push([c.name, allowed.get(c.name)]);
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
unreported.push(c);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// A `ships-as:` reason carries a witness, so it is checked rather than believed. Everything else
|
|
315
|
+
// is prose and is reprinted verbatim for a human to disbelieve.
|
|
316
|
+
const stale = [];
|
|
317
|
+
for (const [name, reason] of ignored) {
|
|
318
|
+
const m = /^ships-as:(\S+)/.exec(reason);
|
|
319
|
+
if (m && !existsSync(join(bundle, m[1])))
|
|
320
|
+
stale.push(
|
|
321
|
+
`the ignore row for \`${name}\` claims it ships as \`${m[1]}\`, and that path is ` +
|
|
322
|
+
"not in the bundle — the allowance has rotted and is now hiding a real absence",
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
for (const name of allowed.keys())
|
|
326
|
+
if (!candidates.some((c) => c.name === name))
|
|
327
|
+
stale.push(
|
|
328
|
+
`the ignore row for \`${name}\` names no directory under repro/ that holds a ` +
|
|
329
|
+
"headline result — retire the row rather than leaving it to grow stale",
|
|
330
|
+
);
|
|
331
|
+
findings.push(...stale);
|
|
332
|
+
|
|
333
|
+
for (const c of unreported)
|
|
334
|
+
findings.push(
|
|
335
|
+
`DISK_UNREPORTED repro/${c.name}/ holds ${c.heads.join(", ")} and is named neither in the ` +
|
|
336
|
+
"paper nor in the released index — an analysis that ran and whose result a reader cannot reach",
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
ledger.push(
|
|
340
|
+
"",
|
|
341
|
+
` ── what the reverse check ignored, in full (${candidates.length} candidate analyses under repro/) ──`,
|
|
342
|
+
" An ignore set applied in silence would make this a cherry-picking detector that cherry-picks,",
|
|
343
|
+
" so it is printed whether or not anything was found. Same discipline as check_grandfather in",
|
|
344
|
+
" repro/paper_numbers.py: the uncovered set is a list you can count, not an absence of warnings.",
|
|
345
|
+
);
|
|
346
|
+
for (const [rule, names] of swallowed)
|
|
347
|
+
ledger.push(
|
|
348
|
+
` • ${names.length} before they were counted — ${rule}`,
|
|
349
|
+
...(names.length ? [` ${names.join(", ")}`] : []),
|
|
350
|
+
);
|
|
351
|
+
ledger.push(
|
|
352
|
+
` • ${reported.length} reported: ${reported.join(" · ") || "(none)"}`,
|
|
353
|
+
);
|
|
354
|
+
ledger.push(
|
|
355
|
+
` • ${ignored.length} in repro/unreported-grandfathered.txt — every row, every reason:`,
|
|
356
|
+
);
|
|
357
|
+
for (const [name, reason] of ignored)
|
|
358
|
+
ledger.push(` ${name} → ${reason}`);
|
|
359
|
+
if (!ignored.length)
|
|
360
|
+
ledger.push(" (the file is empty — nothing is being waved through)");
|
|
361
|
+
ledger.push(
|
|
362
|
+
` • ${unreported.length} left over, and those are the findings above.`,
|
|
363
|
+
` Ratio: ${unreported.length} reported as unreported, ${ignored.length} deliberately ignored, ` +
|
|
364
|
+
`${reported.length} reachable. If the ignored column ever dwarfs the other two, this check has`,
|
|
365
|
+
" become a formality and the honest move is to say so rather than to keep running it.",
|
|
366
|
+
"",
|
|
367
|
+
" 🔴 WHAT THIS LEG IS SILENT ABOUT, stated so nobody reads its silence as coverage:",
|
|
368
|
+
" — an analysis SHIPPED in the bundle but never discussed in prose is counted as reported here,",
|
|
369
|
+
" because the index is treated as the paper's delegate. MedSci's DISK_UNREPORTED is scoped to",
|
|
370
|
+
" the manuscript alone and would flag those; ours would not.",
|
|
371
|
+
" — a result that was never written up (no RESULTS.md) is invisible. Deleting the write-up is a",
|
|
372
|
+
" one-command way past this check, and nothing here can tell that from an experiment nobody",
|
|
373
|
+
" finished.",
|
|
374
|
+
' — nothing reads the CONTENT of a result, so "mentioned" and "reported honestly" are different',
|
|
375
|
+
" things and only the first is checked.",
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const header = `📦 artifact coverage — ${findings.length} finding(s) in ${dir}`;
|
|
380
|
+
if (findings.length) {
|
|
381
|
+
const out = flagsOnly ? console.error : console.log;
|
|
382
|
+
out(header);
|
|
383
|
+
findings.forEach((f) => out(" " + f));
|
|
384
|
+
out(
|
|
385
|
+
" absence is invisible to the leak gate, the numbers gate and the artifact runner:",
|
|
386
|
+
);
|
|
387
|
+
out(" nothing leaks, nothing mismatches, and what IS there still runs.");
|
|
388
|
+
ledger.forEach((l) => out(l));
|
|
389
|
+
process.exit(1);
|
|
390
|
+
}
|
|
391
|
+
if (!flagsOnly) {
|
|
392
|
+
console.log(header);
|
|
393
|
+
console.log(
|
|
394
|
+
" every number-reporting section is indexed, every named path exists",
|
|
395
|
+
);
|
|
396
|
+
ledger.forEach((l) => console.log(l));
|
|
397
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* artifact-coverage.mutations.mjs — proof that every check in `artifact-coverage.mjs` is watched
|
|
3
|
+
* FAILING, with the reverse (DISK_UNREPORTED) leg and its ignore set as the reason this file exists.
|
|
4
|
+
* Run: `node .claude/skills/paper-pipeline/scripts/artifact-coverage.mutations.mjs`
|
|
5
|
+
*
|
|
6
|
+
* This is not a test. It is the evidence behind the claim that the tests test something.
|
|
7
|
+
*
|
|
8
|
+
* WHY THIS LEG IN PARTICULAR. The reverse leg is the only thing in the pipeline that looks for a
|
|
9
|
+
* result nobody reported, and it is guarded by an ignore set. Both halves fail silently in the same
|
|
10
|
+
* direction: a broken detector finds nothing, and an over-wide ignore set also finds nothing, and
|
|
11
|
+
* from outside those are the same output. A green harness proves the CHECKER fires on planted
|
|
12
|
+
* defects; it does not prove the HARNESS would notice if a check were deleted. Every mutations file
|
|
13
|
+
* written in this repository has found something on its first run, and this one is no exception —
|
|
14
|
+
* see the notes in the table.
|
|
15
|
+
*
|
|
16
|
+
* WHAT IT DOES. For each check, it neuters exactly that check in the source, runs the harness, and
|
|
17
|
+
* requires the harness to go RED with the named assertion. Then it restores the file. A mutation
|
|
18
|
+
* the harness survives is a check nobody is watching, and it is reported as SURVIVED.
|
|
19
|
+
*
|
|
20
|
+
* The source is restored in a `finally`, and the run refuses to start on a dirty working tree, so
|
|
21
|
+
* an interrupted run cannot leave a neutered checker behind.
|
|
22
|
+
*/
|
|
23
|
+
import { runMutations } from "../../../lib/mutation-driver.mjs";
|
|
24
|
+
import { dirname, join } from "node:path";
|
|
25
|
+
import { fileURLToPath } from "node:url";
|
|
26
|
+
import { consumerRoot } from "./consumer.mjs";
|
|
27
|
+
|
|
28
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
29
|
+
const ROOT = consumerRoot();
|
|
30
|
+
const SRC = join(HERE, "artifact-coverage.mjs");
|
|
31
|
+
const HARNESS = join(HERE, "artifact-coverage.harness.mjs");
|
|
32
|
+
|
|
33
|
+
/** [name, what it disables, exact source substring, replacement, harness case expected to go red] */
|
|
34
|
+
const MUTATIONS = [
|
|
35
|
+
// ── the two forward legs, which had a harness and no mutation proof until today ──
|
|
36
|
+
[
|
|
37
|
+
"fwd/section-indexed",
|
|
38
|
+
"the rule that a number-reporting section must be named in the index",
|
|
39
|
+
" if (!named)\n",
|
|
40
|
+
" if (false)\n",
|
|
41
|
+
"is named NOWHERE",
|
|
42
|
+
],
|
|
43
|
+
[
|
|
44
|
+
"fwd/index-path-exists",
|
|
45
|
+
"the rule that a path the index names must exist",
|
|
46
|
+
" if (!existsSync(join(bundle, head)) && !readdirSync(bundle).includes(head))",
|
|
47
|
+
" if (false)",
|
|
48
|
+
"is not in the bundle",
|
|
49
|
+
],
|
|
50
|
+
[
|
|
51
|
+
"fwd/no-index",
|
|
52
|
+
"the refusal to accept a bundle that maps nothing to anything",
|
|
53
|
+
"if (!indexPath) {",
|
|
54
|
+
"if (false) {",
|
|
55
|
+
"a bundle that maps nothing to anything was accepted",
|
|
56
|
+
],
|
|
57
|
+
[
|
|
58
|
+
"fwd/repo-not-path",
|
|
59
|
+
"the guard that `owner/repo` is not a path (extension required)",
|
|
60
|
+
" if (!/\\.[a-z0-9]{1,5}$/i.test(p)) continue;",
|
|
61
|
+
" if (false) continue;",
|
|
62
|
+
"a complete bundle was failed",
|
|
63
|
+
],
|
|
64
|
+
|
|
65
|
+
// ── the reverse leg: the detector itself ──
|
|
66
|
+
[
|
|
67
|
+
"rev/finding",
|
|
68
|
+
"the DISK_UNREPORTED finding",
|
|
69
|
+
" for (const c of unreported)\n findings.push(",
|
|
70
|
+
" for (const c of [])\n findings.push(",
|
|
71
|
+
"cherry-picking case this leg was ported for",
|
|
72
|
+
],
|
|
73
|
+
[
|
|
74
|
+
"rev/headline-results",
|
|
75
|
+
"RESULTS.md as a headline write-up",
|
|
76
|
+
"/^(RESULTS|SUMMARY|FINDINGS|METRICS)\\.",
|
|
77
|
+
"/^(SUMMARY|FINDINGS|METRICS)\\.",
|
|
78
|
+
"cherry-picking case this leg was ported for",
|
|
79
|
+
],
|
|
80
|
+
[
|
|
81
|
+
"rev/paper-mention",
|
|
82
|
+
"crediting a mention in the paper",
|
|
83
|
+
"if (named(paper)) {",
|
|
84
|
+
"if (false) {",
|
|
85
|
+
"an experiment the paper discusses by name was reported as unmentioned",
|
|
86
|
+
],
|
|
87
|
+
[
|
|
88
|
+
"rev/index-mention",
|
|
89
|
+
"crediting a mention in the index",
|
|
90
|
+
"if (named(indexAll)) {",
|
|
91
|
+
"if (false) {",
|
|
92
|
+
"named in the bundle's README.md and nowhere else",
|
|
93
|
+
],
|
|
94
|
+
[
|
|
95
|
+
"rev/all-index-files",
|
|
96
|
+
"reading BOTH index files (reverts to check 1's single indexPath)",
|
|
97
|
+
" if (named(indexAll)) {",
|
|
98
|
+
" if (named(index)) {",
|
|
99
|
+
"named in the bundle's README.md and nowhere else",
|
|
100
|
+
],
|
|
101
|
+
[
|
|
102
|
+
"rev/date-slug",
|
|
103
|
+
"stripping the -YYYY-MM-DD suffix before matching",
|
|
104
|
+
'const slug = c.name.replace(/-\\d{4}-\\d{2}-\\d{2}$/, "");',
|
|
105
|
+
"const slug = c.name;",
|
|
106
|
+
"names without its date suffix",
|
|
107
|
+
],
|
|
108
|
+
[
|
|
109
|
+
"rev/whole-token",
|
|
110
|
+
"whole-token matching (reverts to substring `includes`)",
|
|
111
|
+
"const mentions = (t, s) =>\n s.length >= 4 && new RegExp(`(?<![\\\\w-])${esc(s)}(?![\\\\w-])`).test(t);",
|
|
112
|
+
"const mentions = (t, s) => t.includes(s);",
|
|
113
|
+
"the header does not state the real finding count",
|
|
114
|
+
],
|
|
115
|
+
[
|
|
116
|
+
"rev/bundle-skipped",
|
|
117
|
+
"the structural ignore that skips the released bundle",
|
|
118
|
+
" if (name === bundleName) {\n swallowed.get([...swallowed.keys()][0]).push(name);\n continue;\n }",
|
|
119
|
+
" if (false) {\n swallowed.get([...swallowed.keys()][0]).push(name);\n continue;\n }",
|
|
120
|
+
"the bundle's own RESULTS.md was counted",
|
|
121
|
+
],
|
|
122
|
+
|
|
123
|
+
// ── the ignore set, which can neuter the detector without changing a line of it ──
|
|
124
|
+
[
|
|
125
|
+
"ign/applied",
|
|
126
|
+
"applying an allow row at all",
|
|
127
|
+
"if (allowed.has(c.name)) {",
|
|
128
|
+
"if (false) {",
|
|
129
|
+
"an allowed row still counted as a finding",
|
|
130
|
+
],
|
|
131
|
+
[
|
|
132
|
+
"ign/rows-parsed",
|
|
133
|
+
"parsing the allow file",
|
|
134
|
+
" if (name)\n allowed.set(",
|
|
135
|
+
" if (false)\n allowed.set(",
|
|
136
|
+
"an allowed row still counted as a finding",
|
|
137
|
+
],
|
|
138
|
+
[
|
|
139
|
+
"ign/ships-as-verified",
|
|
140
|
+
"re-verifying a `ships-as:` claim against the bundle",
|
|
141
|
+
"if (m && !existsSync(join(bundle, m[1])))",
|
|
142
|
+
"if (false)",
|
|
143
|
+
"the allowance has rotted",
|
|
144
|
+
],
|
|
145
|
+
[
|
|
146
|
+
"ign/stale-rows",
|
|
147
|
+
"reporting a row that names no directory",
|
|
148
|
+
" if (!candidates.some((c) => c.name === name))",
|
|
149
|
+
" if (false)",
|
|
150
|
+
"sat in the ignore file unreported",
|
|
151
|
+
],
|
|
152
|
+
[
|
|
153
|
+
"ign/printed-on-clean",
|
|
154
|
+
"printing the ledger when there are no findings",
|
|
155
|
+
' console.log(\n " every number-reporting section is indexed, every named path exists",\n );\n ledger.forEach((l) => console.log(l));',
|
|
156
|
+
' console.log(\n " every number-reporting section is indexed, every named path exists",\n );',
|
|
157
|
+
// Killed by case 9 rather than case 12: case 9 is also a 0-finding report-mode run and asserts
|
|
158
|
+
// the same line, and it runs first. Both cases assert the property; the row records WHICH one
|
|
159
|
+
// catches it so a future reader does not mistake this for an off-target kill.
|
|
160
|
+
"was applied and NOT printed",
|
|
161
|
+
],
|
|
162
|
+
[
|
|
163
|
+
"ign/reasons-printed",
|
|
164
|
+
"printing each allow row's reason",
|
|
165
|
+
" for (const [name, reason] of ignored)\n ledger.push(` ${name} → ${reason}`);",
|
|
166
|
+
" for (const [name, reason] of ignored)\n ledger.push(` ${name}`);",
|
|
167
|
+
"was applied and NOT printed",
|
|
168
|
+
],
|
|
169
|
+
[
|
|
170
|
+
"ign/empty-file-said",
|
|
171
|
+
"saying so when nothing is being waved through",
|
|
172
|
+
' if (!ignored.length)\n ledger.push(" (the file is empty — nothing is being waved through)");',
|
|
173
|
+
' if (false)\n ledger.push(" (the file is empty — nothing is being waved through)");',
|
|
174
|
+
"an empty allow file printed nothing at all",
|
|
175
|
+
],
|
|
176
|
+
[
|
|
177
|
+
"ign/ratio",
|
|
178
|
+
"the ratio line that says whether this check still does anything",
|
|
179
|
+
" ` Ratio: ${unreported.length} reported as unreported, ${ignored.length} deliberately ignored, ` +",
|
|
180
|
+
" ` Ratio: ` +",
|
|
181
|
+
"printed no ratio",
|
|
182
|
+
],
|
|
183
|
+
|
|
184
|
+
// ── the machine-readable contract with run-mechanical.mjs ──
|
|
185
|
+
[
|
|
186
|
+
"out/stated-count",
|
|
187
|
+
"the stated finding count in the header",
|
|
188
|
+
"const header = `📦 artifact coverage — ${findings.length} finding(s) in ${dir}`;",
|
|
189
|
+
"const header = `📦 artifact coverage — ${dir}`;",
|
|
190
|
+
"does not state `— 0 finding(s)`",
|
|
191
|
+
],
|
|
192
|
+
[
|
|
193
|
+
"out/flags-quiet",
|
|
194
|
+
"the silence of --flags-only on a clean tree",
|
|
195
|
+
"if (!flagsOnly) {\n console.log(header);",
|
|
196
|
+
"if (true) {\n console.log(header);",
|
|
197
|
+
"--flags-only printed something about a complete bundle",
|
|
198
|
+
],
|
|
199
|
+
];
|
|
200
|
+
|
|
201
|
+
// The table above is DATA about this checker; the engine below is shared with the other mutation
|
|
202
|
+
// files (`mutation-driver.mjs`). The split gave this file two protections it never had: the no-op
|
|
203
|
+
// guard (a replacement equal to the original leaves a green harness proving nothing) and one retry
|
|
204
|
+
// on a non-kill (a flake reported as a survivor sends the next reader to rewrite a working
|
|
205
|
+
// assertion). It also refuses to start when a named harness does not exist — the defect that made
|
|
206
|
+
// three files report SURVIVED for every case after the 2026-08-11 colocation.
|
|
207
|
+
process.exit(
|
|
208
|
+
runMutations({
|
|
209
|
+
root: ROOT,
|
|
210
|
+
cases: MUTATIONS.map(([name, disables, find, replace, expect]) => ({
|
|
211
|
+
name,
|
|
212
|
+
disables,
|
|
213
|
+
edits: [[SRC, find, replace]],
|
|
214
|
+
harness: HARNESS,
|
|
215
|
+
expect,
|
|
216
|
+
})),
|
|
217
|
+
}),
|
|
218
|
+
);
|