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,455 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `paper/stages` — the stage a paper has reached is an author's CLAIM, so it is a FIELD, and
|
|
3
|
+
* the bytes on disk are the evidence that claim is checked against.
|
|
4
|
+
*
|
|
5
|
+
* ── WHAT THIS REPLACES, and why the replacement is not cosmetic ─────────────────────────
|
|
6
|
+
* The predecessor kept a vocabulary of stage names in a JS const and applied its regexes to
|
|
7
|
+
* the WHOLE of `PIPELINE-STATUS.md`:
|
|
8
|
+
*
|
|
9
|
+
* { stage: "submitted", declaredBy: /(?<!\bas\s)(?<!\bbe\s)\bsubmitted\b/i }
|
|
10
|
+
*
|
|
11
|
+
* The lookbehinds are not decoration — they were added after the only match in one paper's
|
|
12
|
+
* file turned out to be a reviewer's idiom, "Weak Accept as submitted", which says nothing
|
|
13
|
+
* about submission. The check then demanded a frozen pdf and was RIGHT BY ACCIDENT.
|
|
14
|
+
*
|
|
15
|
+
* 🔴 And it stayed wrong in the other direction, measured 2026-09-16 on the live corpus:
|
|
16
|
+
* `versions/2026-08-29-camera-ready.pdf` had been on disk for 16 days at 616 175 bytes while
|
|
17
|
+
* the `camera-ready` pattern matched ZERO times anywhere in the file. The artefact preceded
|
|
18
|
+
* the declaration, so all three checks gated on that declaration were blind by construction.
|
|
19
|
+
*
|
|
20
|
+
* ── TWO DIRECTIONS, and neither alone is the check ──────────────────────────────────────
|
|
21
|
+
* declared -> bytes a record without its file, or with the wrong size, is a false claim
|
|
22
|
+
* bytes -> declared a frozen version nobody declared is the case above, and it is also
|
|
23
|
+
* what stops the whole rule being switched off by deleting a line
|
|
24
|
+
*
|
|
25
|
+
* Absence of the field is therefore LEGITIMATE — a paper that has shipped nothing owes
|
|
26
|
+
* nothing — without being an escape hatch: the second direction still speaks if bytes exist.
|
|
27
|
+
* That asymmetry is deliberate and is the reason this rule does not copy `doc/fields`, where
|
|
28
|
+
* a missing frontmatter IS the finding.
|
|
29
|
+
*
|
|
30
|
+
* ── WHY A FIELD CANNOT BECOME A TICKED BOX HERE ─────────────────────────────────────────
|
|
31
|
+
* The usual objection to turning a check into a field is that a field gets ticked instead of
|
|
32
|
+
* the work being done. It does not apply, and the reason is the direction of the incentive:
|
|
33
|
+
* a tick normally REMOVES an obligation, while `stages` CREATES them — declare a stage and
|
|
34
|
+
* you owe a pdf of the right size. Nobody writes a line in order to receive findings. The
|
|
35
|
+
* real-world pressure is to UNDER-declare, which is exactly what direction two catches.
|
|
36
|
+
*
|
|
37
|
+
* ── THE FIELD IS THE ONLY MACHINE-READABLE CARRIER ──────────────────────────────────────
|
|
38
|
+
* ⚠️ A paper's status file also carries a `State:` line in its header and one or two
|
|
39
|
+
* scorecard tables. Those are DISPLAY. No consistency check between them is written here on
|
|
40
|
+
* purpose: one paper in the source corpus carries two scorecards in two different column
|
|
41
|
+
* formats, and a rule reconciling them would drown in noise within a day.
|
|
42
|
+
*
|
|
43
|
+
* ── A LIST, NOT A MAP, and that is load-bearing ─────────────────────────────────────────
|
|
44
|
+
* A paper can reach the same stage twice: one in the source corpus was submitted to a venue
|
|
45
|
+
* in August, rejected in September, and is being resubmitted elsewhere in October. A map
|
|
46
|
+
* keyed by stage name holds one. The `versions/<date>-<stage>.pdf` convention already holds
|
|
47
|
+
* many, and the field must not be weaker than the filenames it describes.
|
|
48
|
+
*/
|
|
49
|
+
import { existsSync, statSync, readdirSync } from "node:fs";
|
|
50
|
+
import { join, dirname } from "node:path";
|
|
51
|
+
import { load } from "js-yaml";
|
|
52
|
+
|
|
53
|
+
const STAGES = ["submitted", "camera-ready", "arxiv"];
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A YAML date without quotes parses to a `Date`, not a string — js-yaml honours YAML 1.1
|
|
57
|
+
* timestamps. Comparing `Date === "2026-08-29"` is silently false, so every date is
|
|
58
|
+
* normalised before it is compared with one taken from a filename.
|
|
59
|
+
*/
|
|
60
|
+
function isoDate(v) {
|
|
61
|
+
if (v instanceof Date) return v.toISOString().slice(0, 10);
|
|
62
|
+
if (typeof v === "string") return /^\d{4}-\d{2}-\d{2}/.exec(v)?.[0] ?? "";
|
|
63
|
+
return "";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Frozen versions on disk, as `{name, date, stage}`. */
|
|
67
|
+
function frozenPdfs(versionsDir) {
|
|
68
|
+
let names;
|
|
69
|
+
try {
|
|
70
|
+
names = readdirSync(versionsDir);
|
|
71
|
+
} catch {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
const out = [];
|
|
75
|
+
for (const name of names) {
|
|
76
|
+
if (!name.endsWith(".pdf")) continue;
|
|
77
|
+
// ⚠️ A name carrying `STALE` is a version WITHDRAWN on purpose — one such file records
|
|
78
|
+
// that the wrong pdf sat in the folder for a month. Demanding a declaration for it would
|
|
79
|
+
// turn a deliberate record of a mistake into a finding.
|
|
80
|
+
if (name.includes("STALE")) continue;
|
|
81
|
+
const m = /^(\d{4}-\d{2}-\d{2})-(.+)\.pdf$/.exec(name);
|
|
82
|
+
if (!m) continue;
|
|
83
|
+
if (!STAGES.includes(m[2])) continue;
|
|
84
|
+
out.push({ name, date: m[1], stage: m[2] });
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export default {
|
|
90
|
+
rules: {
|
|
91
|
+
stages: {
|
|
92
|
+
meta: {
|
|
93
|
+
type: "problem",
|
|
94
|
+
docs: {
|
|
95
|
+
description:
|
|
96
|
+
"a paper's stage is declared as a FIELD, and every declaration is checked against the bytes on disk in both directions",
|
|
97
|
+
},
|
|
98
|
+
schema: [],
|
|
99
|
+
messages: {
|
|
100
|
+
badYaml: "the frontmatter does not parse as YAML: {{reason}}",
|
|
101
|
+
notAList:
|
|
102
|
+
"`stages` must be a LIST of entries, not {{got}} — a paper can reach the same stage twice",
|
|
103
|
+
badStage: "unknown stage «{{stage}}» — the vocabulary is: {{known}}",
|
|
104
|
+
missingKey: "the «{{stage}}» entry has no `{{key}}` field",
|
|
105
|
+
badDate:
|
|
106
|
+
"the date «{{date}}» in the «{{stage}}» entry is not YYYY-MM-DD",
|
|
107
|
+
declaredNoFile:
|
|
108
|
+
"stage «{{stage}}» ({{date}}) is declared, but `{{pdf}}` is not on disk",
|
|
109
|
+
bytesDiffer:
|
|
110
|
+
"«{{stage}}» ({{date}}): {{want}} bytes declared, {{got}} on disk — this is NOT that file",
|
|
111
|
+
fileNotDeclared:
|
|
112
|
+
"`versions/{{file}}` is frozen, but no «{{stage}}» stage on {{date}} is declared in `stages` — the artefact ran ahead of the declaration",
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
create(context) {
|
|
116
|
+
const dir = dirname(context.filename);
|
|
117
|
+
let declared = null; // null = there was no frontmatter at all
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
yaml(node) {
|
|
121
|
+
let data;
|
|
122
|
+
try {
|
|
123
|
+
data = load(node.value ?? "");
|
|
124
|
+
} catch (e) {
|
|
125
|
+
context.report({
|
|
126
|
+
node,
|
|
127
|
+
messageId: "badYaml",
|
|
128
|
+
data: { reason: String(e.message) },
|
|
129
|
+
});
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const raw = data?.stages;
|
|
133
|
+
if (raw === undefined) return;
|
|
134
|
+
if (!Array.isArray(raw)) {
|
|
135
|
+
context.report({
|
|
136
|
+
node,
|
|
137
|
+
messageId: "notAList",
|
|
138
|
+
data: { got: raw === null ? "empty" : typeof raw },
|
|
139
|
+
});
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
declared = [];
|
|
143
|
+
for (const rec of raw) {
|
|
144
|
+
const stage = String(rec?.stage ?? "");
|
|
145
|
+
if (!STAGES.includes(stage)) {
|
|
146
|
+
context.report({
|
|
147
|
+
node,
|
|
148
|
+
messageId: "badStage",
|
|
149
|
+
data: { stage, known: STAGES.join(" · ") },
|
|
150
|
+
});
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
let complete = true;
|
|
154
|
+
for (const key of ["date", "pdf", "bytes"]) {
|
|
155
|
+
if (rec[key] === undefined) {
|
|
156
|
+
context.report({
|
|
157
|
+
node,
|
|
158
|
+
messageId: "missingKey",
|
|
159
|
+
data: { stage, key },
|
|
160
|
+
});
|
|
161
|
+
complete = false;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (!complete) continue;
|
|
165
|
+
const date = isoDate(rec.date);
|
|
166
|
+
if (date === "") {
|
|
167
|
+
context.report({
|
|
168
|
+
node,
|
|
169
|
+
messageId: "badDate",
|
|
170
|
+
data: { stage, date: String(rec.date) },
|
|
171
|
+
});
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
declared.push({
|
|
175
|
+
stage,
|
|
176
|
+
date,
|
|
177
|
+
pdf: String(rec.pdf),
|
|
178
|
+
bytes: Number(rec.bytes),
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// ── direction one: a claim owes its bytes ────────────────────────────────
|
|
182
|
+
const abs = join(dir, String(rec.pdf));
|
|
183
|
+
if (!existsSync(abs)) {
|
|
184
|
+
context.report({
|
|
185
|
+
node,
|
|
186
|
+
messageId: "declaredNoFile",
|
|
187
|
+
data: { stage, date, pdf: String(rec.pdf) },
|
|
188
|
+
});
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const got = statSync(abs).size;
|
|
192
|
+
if (got !== Number(rec.bytes)) {
|
|
193
|
+
context.report({
|
|
194
|
+
node,
|
|
195
|
+
messageId: "bytesDiffer",
|
|
196
|
+
data: {
|
|
197
|
+
stage,
|
|
198
|
+
date,
|
|
199
|
+
want: String(rec.bytes),
|
|
200
|
+
got: String(got),
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
// ── direction two: bytes owe their declaration ────────────────────────────────
|
|
208
|
+
// 🔴 On `root:exit` rather than inside `yaml`, because the case worth catching is
|
|
209
|
+
// precisely the one with NO frontmatter at all — where the `yaml` visitor never runs.
|
|
210
|
+
"root:exit"(node) {
|
|
211
|
+
const records = declared ?? [];
|
|
212
|
+
for (const f of frozenPdfs(join(dir, "versions"))) {
|
|
213
|
+
if (records.some((r) => r.stage === f.stage && r.date === f.date))
|
|
214
|
+
continue;
|
|
215
|
+
context.report({
|
|
216
|
+
node,
|
|
217
|
+
messageId: "fileNotDeclared",
|
|
218
|
+
data: { file: f.name, stage: f.stage, date: f.date },
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* `paper/source` — a declared stage must have its SOURCE frozen on disk, beside the pdf,
|
|
228
|
+
* and the bytes must match. Not a commit reference. Not a hash of one.
|
|
229
|
+
*
|
|
230
|
+
* ── WHY NOT A COMMIT, measured 2026-09-16 and it is not a close call ──────────────────
|
|
231
|
+
* The predecessor recorded provenance as `commit <sha>` in prose and checked that the sha
|
|
232
|
+
* RESOLVED. Its stated premise was "git holds those bytes immutably, materialising a copy
|
|
233
|
+
* duplicates a guarantee we already have". That premise is false wherever branches are
|
|
234
|
+
* SQUASH-merged: the squash destroys the branch commits, and the next `git gc` removes
|
|
235
|
+
* the objects.
|
|
236
|
+
*
|
|
237
|
+
* 🔴 Measured on the live corpus, inside ninety minutes of ONE session: two recorded shas
|
|
238
|
+
* resolved, then stopped resolving after a routine `gc` following a branch reset. Of the
|
|
239
|
+
* four declared stages in that corpus, THREE had lost their source entirely — including
|
|
240
|
+
* papers already submitted to a venue, which is exactly the case the check existed for
|
|
241
|
+
* (a reviewer cites a line number and there is no layout left to resolve it against).
|
|
242
|
+
*
|
|
243
|
+
* So a sha is not a pointer to bytes; it is a pointer to a pointer, and the outer one
|
|
244
|
+
* evaporates. Checking it verifies that the REFERENCE is alive, which is a different
|
|
245
|
+
* claim from the one anybody wants.
|
|
246
|
+
*
|
|
247
|
+
* ── COST, because "duplicating" was the objection ─────────────────────────────────────
|
|
248
|
+
* A paper source is 57-68 KB of LaTeX beside a pdf of 305-382 KB that is already
|
|
249
|
+
* committed. Freezing it adds under a fifth to what the folder holds anyway, and turns a
|
|
250
|
+
* claim about the world into bytes this rule can actually compare.
|
|
251
|
+
*
|
|
252
|
+
* ── SEVERITY: nudge, and the reason CHANGED ──────────────────────────────────────────
|
|
253
|
+
* Still `warn` at the consumer, but no longer because "only the author knows". Now it is
|
|
254
|
+
* because a stage frozen BEFORE this convention existed cannot be fixed at all — those
|
|
255
|
+
* bytes are gone. Failing a build over unrecoverable history is a gate nobody can clear.
|
|
256
|
+
*/
|
|
257
|
+
/**
|
|
258
|
+
* `paper/author-list` — a shipped paper OWES ITSELF a run of the author-list cross-check.
|
|
259
|
+
*
|
|
260
|
+
* The class this cross-check catches is invisible to an existence check on citations: the
|
|
261
|
+
* citation exists, the identifier resolves, and the authors are taken from the PREPRINT
|
|
262
|
+
* while the entry declares a conference. On the live corpus this turned up seven records
|
|
263
|
+
* across three papers, including a DROPPED LIVING PERSON (`schick2023toolformer` — Eric
|
|
264
|
+
* Hambro is missing; the NeurIPS version has nine authors, the preprint has eight). Two of
|
|
265
|
+
* the seven were found on a paper ALREADY SUBMITTED.
|
|
266
|
+
*
|
|
267
|
+
* 🔴 WHAT THE MOVE FIXED, and this is a measurement, not taste. The predecessor derived the
|
|
268
|
+
* declared stage with a REGEX OVER THE PROSE of that same file. Remeasured 2026-09-17 on the
|
|
269
|
+
* live corpus: for `agenticdev-2026` the prose reads `submitted`, the frontmatter declares
|
|
270
|
+
* `submitted, camera-ready` — the pattern `/camera-ready (?:uploaded|submitted|shipped)/i`
|
|
271
|
+
* does not catch the form the stage is actually recorded in. Today's set of findings did not
|
|
272
|
+
* change from this (both checks only asked "is there ANY stage at all"), but the MESSAGE
|
|
273
|
+
* printed the wrong list of stages. The subject here is the same `stages` field that
|
|
274
|
+
* `paper/stages` already checks against the bytes in both directions.
|
|
275
|
+
*
|
|
276
|
+
* 🔴 THE MARKER IS LOOKED FOR IN TABLE CELLS, NOT GREPPED OVER THE FILE. The first draft did
|
|
277
|
+
* `context.sourceCode.text.includes(marker)` and justified it with a comment saying "a
|
|
278
|
+
* footnote cell has no node of its own". That turned out to be SIMPLY WRONG — a 2026-09-17
|
|
279
|
+
* measurement showed the markdown parser hands back `table`, `tableRow` and `tableCell`
|
|
280
|
+
* (twelve cells on a three-row table). The base rule says, word for word, "parse markdown
|
|
281
|
+
* with a parser".
|
|
282
|
+
*
|
|
283
|
+
* Parsing is also STRICTER than grep, and the difference is real: a marker mentioned in
|
|
284
|
+
* prose outside the scorecard — in a heading, in a paragraph saying "still need to run
|
|
285
|
+
* bib-authors", in a quote from someone else — no longer counts as a record of a run. To
|
|
286
|
+
* grep, those three cases are indistinguishable from a genuine record.
|
|
287
|
+
*
|
|
288
|
+
* ⚠️ WHAT THIS STILL DOES NOT FIX: inside the cell the evidence is still PROSE, and a run
|
|
289
|
+
* phrased in different words will not be seen by the rule. The real fix is a frontmatter
|
|
290
|
+
* field (`gates.cites.ran`), not a smarter text search; that's separate work, touching four
|
|
291
|
+
* live scorecards.
|
|
292
|
+
*
|
|
293
|
+
* So severity is set by the CONSUMER, and the default is not `error`: the proof of a run is
|
|
294
|
+
* prose, and a false positive at a blocking level costs more than a miss.
|
|
295
|
+
*/
|
|
296
|
+
"author-list": {
|
|
297
|
+
meta: {
|
|
298
|
+
type: "suggestion",
|
|
299
|
+
docs: {
|
|
300
|
+
description:
|
|
301
|
+
"a paper that declares a stage records that the author-list check ran — a class the existence check cannot see",
|
|
302
|
+
},
|
|
303
|
+
schema: [
|
|
304
|
+
{
|
|
305
|
+
type: "object",
|
|
306
|
+
properties: {
|
|
307
|
+
// The run marker in the scorecard. This is consumer data: the package cannot
|
|
308
|
+
// know EXACTLY how a given consumer records that the cross-check happened.
|
|
309
|
+
marker: { type: "string" },
|
|
310
|
+
// What to run. This is a CONSUMER-SPECIFIC PATH, and it has no place in a public
|
|
311
|
+
// package: the predecessor hardcoded
|
|
312
|
+
// `.claude/skills/verify-citations/scripts/bib-authors.mjs` right into the message text.
|
|
313
|
+
command: { type: "string" },
|
|
314
|
+
},
|
|
315
|
+
additionalProperties: false,
|
|
316
|
+
},
|
|
317
|
+
],
|
|
318
|
+
messages: {
|
|
319
|
+
neverRan:
|
|
320
|
+
"stage «{{stages}}» is declared, but the scorecard records no author-list run (looked for «{{marker}}» in its table). It catches what an existence check cannot see: the citation resolves, the id resolves, and the authors are the PREPRINT's while the entry declares a conference{{how}}",
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
create(context) {
|
|
324
|
+
const opts = context.options?.[0] ?? {};
|
|
325
|
+
const marker = opts.marker ?? "bib-authors";
|
|
326
|
+
const command = opts.command ?? "";
|
|
327
|
+
|
|
328
|
+
// The verdict is deferred to the end of the file: the frontmatter node arrives FIRST,
|
|
329
|
+
// and the table comes after it. Reporting on `yaml` would mean a verdict rendered
|
|
330
|
+
// without ever seeing the scorecard.
|
|
331
|
+
let declaredAt = null;
|
|
332
|
+
let stages = [];
|
|
333
|
+
let recorded = false;
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
yaml(node) {
|
|
337
|
+
let data;
|
|
338
|
+
try {
|
|
339
|
+
data = load(node.value ?? "");
|
|
340
|
+
} catch {
|
|
341
|
+
return; // `paper/stages` has already reported the unreadable YAML
|
|
342
|
+
}
|
|
343
|
+
const raw = data?.stages;
|
|
344
|
+
if (!Array.isArray(raw)) return;
|
|
345
|
+
stages = raw.map((r) => r?.stage).filter(Boolean);
|
|
346
|
+
// One guard, not two: `raw.length === 0` would be a SPECIAL CASE of this same
|
|
347
|
+
// condition, and a mutation against it would turn out unkillable — the second guard
|
|
348
|
+
// mutes it. This also covers a non-empty list made of records with no `stage` field.
|
|
349
|
+
if (stages.length === 0) return; // nothing shipped — nothing is owed
|
|
350
|
+
declaredAt = node;
|
|
351
|
+
},
|
|
352
|
+
|
|
353
|
+
// The evidence is a SCORECARD CELL, not any occurrence of the string in the file. It
|
|
354
|
+
// has a node of its own; the first draft claimed otherwise and grepped the whole text.
|
|
355
|
+
tableCell(node) {
|
|
356
|
+
if (recorded) return;
|
|
357
|
+
if (context.sourceCode.getText(node).includes(marker))
|
|
358
|
+
recorded = true;
|
|
359
|
+
},
|
|
360
|
+
|
|
361
|
+
"root:exit"() {
|
|
362
|
+
if (!declaredAt || recorded) return;
|
|
363
|
+
context.report({
|
|
364
|
+
node: declaredAt,
|
|
365
|
+
messageId: "neverRan",
|
|
366
|
+
data: {
|
|
367
|
+
stages: stages.join("/"),
|
|
368
|
+
marker,
|
|
369
|
+
how: command ? `. Run: ${command}` : "",
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
|
|
377
|
+
source: {
|
|
378
|
+
meta: {
|
|
379
|
+
type: "problem",
|
|
380
|
+
docs: {
|
|
381
|
+
description:
|
|
382
|
+
"a declared stage freezes its source beside the pdf and is checked by bytes — not by a commit reference",
|
|
383
|
+
},
|
|
384
|
+
schema: [],
|
|
385
|
+
messages: {
|
|
386
|
+
noSource:
|
|
387
|
+
"stage «{{stage}}» ({{date}}) carries no frozen source. A commit reference will not do: squash and gc destroy it — three of four sources were lost that way in this corpus",
|
|
388
|
+
sourceMissing:
|
|
389
|
+
"«{{stage}}» ({{date}}): source `{{src}}` is declared, but the file is not on disk",
|
|
390
|
+
sourceBytes:
|
|
391
|
+
"«{{stage}}» ({{date}}): the source is declared as {{want}} bytes, {{got}} on disk — this is NOT that file",
|
|
392
|
+
lostAcknowledged:
|
|
393
|
+
"stage «{{stage}}» ({{date}}): the source is declared LOST. There is nothing left to match a build against a reviewer's line — if a copy turns up, put it in versions/ and clear the flag",
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
create(context) {
|
|
397
|
+
const dir = dirname(context.filename);
|
|
398
|
+
return {
|
|
399
|
+
yaml(node) {
|
|
400
|
+
let data;
|
|
401
|
+
try {
|
|
402
|
+
data = load(node.value ?? "");
|
|
403
|
+
} catch {
|
|
404
|
+
return; // `paper/stages` has already reported the unreadable YAML
|
|
405
|
+
}
|
|
406
|
+
const raw = data?.stages;
|
|
407
|
+
if (!Array.isArray(raw)) return;
|
|
408
|
+
for (const rec of raw) {
|
|
409
|
+
const stage = String(rec?.stage ?? "");
|
|
410
|
+
if (!STAGES.includes(stage)) continue;
|
|
411
|
+
const date = isoDate(rec?.date);
|
|
412
|
+
|
|
413
|
+
// Acknowledging the loss is a RECORD, not an exemption: the rule keeps speaking,
|
|
414
|
+
// because the state stays defective, just unfixable today.
|
|
415
|
+
if (rec?.sourceLost === true) {
|
|
416
|
+
context.report({
|
|
417
|
+
node,
|
|
418
|
+
messageId: "lostAcknowledged",
|
|
419
|
+
data: { stage, date },
|
|
420
|
+
});
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
const src = rec?.source === undefined ? "" : String(rec.source);
|
|
424
|
+
if (src === "") {
|
|
425
|
+
context.report({
|
|
426
|
+
node,
|
|
427
|
+
messageId: "noSource",
|
|
428
|
+
data: { stage, date },
|
|
429
|
+
});
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
const abs = join(dir, src);
|
|
433
|
+
if (!existsSync(abs)) {
|
|
434
|
+
context.report({
|
|
435
|
+
node,
|
|
436
|
+
messageId: "sourceMissing",
|
|
437
|
+
data: { stage, date, src },
|
|
438
|
+
});
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
const got = statSync(abs).size;
|
|
442
|
+
const want = Number(rec?.sourceBytes);
|
|
443
|
+
if (Number.isFinite(want) && got !== want)
|
|
444
|
+
context.report({
|
|
445
|
+
node,
|
|
446
|
+
messageId: "sourceBytes",
|
|
447
|
+
data: { stage, date, want: String(want), got: String(got) },
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
},
|
|
451
|
+
};
|
|
452
|
+
},
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Battery for `paper/stages` and `paper/source` — four mutations, chosen so that each one
|
|
3
|
+
* removes ITS OWN load-bearing property, rather than just breaking the file.
|
|
4
|
+
*
|
|
5
|
+
* 🔴 Why the battery matters here specifically. For both rules the success state is SILENCE, and
|
|
6
|
+
* the harness catches them on fixtures that it lays out itself. "It passed" and "it cannot fail"
|
|
7
|
+
* look identical from the outside; the battery is the only thing that tells them apart.
|
|
8
|
+
*
|
|
9
|
+
* The first two mutations hit the TWO DIRECTIONS of `paper/stages`, and this is not symmetry for
|
|
10
|
+
* its own sake: without the second direction the rule turns itself off by deleting the
|
|
11
|
+
* frontmatter — no declarations, so no mismatches, so green. The `declared-without-bytes`
|
|
12
|
+
* mutation and the `bytes-without-declaration` mutation must die on DIFFERENT asserts, or only
|
|
13
|
+
* one half is proven.
|
|
14
|
+
*/
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
import { dirname, join, resolve } from "node:path";
|
|
17
|
+
import { runMutations } from "../lib/mutation-driver.mjs";
|
|
18
|
+
|
|
19
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
const ROOT = resolve(HERE, "..");
|
|
21
|
+
const RULE = join(HERE, "paper-stages.mjs");
|
|
22
|
+
const HARNESS = join(HERE, "paper-stages.harness.mjs");
|
|
23
|
+
|
|
24
|
+
process.exit(
|
|
25
|
+
runMutations({
|
|
26
|
+
root: ROOT,
|
|
27
|
+
runner: "node",
|
|
28
|
+
cases: [
|
|
29
|
+
{
|
|
30
|
+
name: "the DECLARED→BYTES direction stops checking",
|
|
31
|
+
harness: HARNESS,
|
|
32
|
+
expect: "wrong byte count is reported",
|
|
33
|
+
disables:
|
|
34
|
+
"the check of declared `bytes` against the file on disk — a stage could declare any " +
|
|
35
|
+
"number, and the pdf underneath it could be anything",
|
|
36
|
+
edits: [[RULE, "if (got !== Number(rec.bytes)) {", "if (false) {"]],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "the BYTES→DECLARED direction stops checking",
|
|
40
|
+
harness: HARNESS,
|
|
41
|
+
expect: "a frozen version nobody declared is reported",
|
|
42
|
+
disables:
|
|
43
|
+
"the second half — the one without which the rule is muted by deleting the " +
|
|
44
|
+
"frontmatter: no declarations ⇒ no mismatches ⇒ green",
|
|
45
|
+
edits: [
|
|
46
|
+
[
|
|
47
|
+
RULE,
|
|
48
|
+
"if (records.some((r) => r.stage === f.stage && r.date === f.date))\n continue;",
|
|
49
|
+
"if (true)\n continue;",
|
|
50
|
+
],
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "`paper/source` stops checking the source's size",
|
|
55
|
+
harness: HARNESS,
|
|
56
|
+
expect: "a byte mismatch on the source is reported",
|
|
57
|
+
disables:
|
|
58
|
+
"the IDENTITY check on a frozen source: `existsSync` answers 'a file exists', " +
|
|
59
|
+
"and only the size answers 'this is that exact file'",
|
|
60
|
+
edits: [
|
|
61
|
+
[RULE, "if (Number.isFinite(want) && got !== want)", "if (false)"],
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "acknowledging a loss starts EXEMPTING instead of recording",
|
|
66
|
+
harness: HARNESS,
|
|
67
|
+
expect: "an acknowledged loss is still reported, not silenced",
|
|
68
|
+
disables:
|
|
69
|
+
"the decision that `sourceLost` is a RECORD, not an indulgence: the rule must keep " +
|
|
70
|
+
"speaking, because the state is still defective, merely unfixable today",
|
|
71
|
+
edits: [
|
|
72
|
+
[
|
|
73
|
+
RULE,
|
|
74
|
+
"if (rec?.sourceLost === true) {",
|
|
75
|
+
"if (rec?.sourceLost === true && false) {",
|
|
76
|
+
],
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
// 🔴 A REGRESSION BACK TO GREP. The rule's first draft did exactly this and justified it
|
|
81
|
+
// by saying "a footnote cell has no node of its own" — a measurement showed the
|
|
82
|
+
// opposite: the parser hands back `tableCell`. The mutation is caught by ONE fixture out
|
|
83
|
+
// of six: in the rest the marker already sits in a cell, so grep and parsing are
|
|
84
|
+
// indistinguishable. Without `marker-in-prose` this regression would have passed
|
|
85
|
+
// silently, and the rule would again read an intention as a fact.
|
|
86
|
+
name: "the evidence is grepped over the WHOLE FILE again",
|
|
87
|
+
harness: HARNESS,
|
|
88
|
+
expect: "a marker in prose OUTSIDE the table is not a record of a run",
|
|
89
|
+
disables:
|
|
90
|
+
"parsing in favor of a string search: 'still need to run bib-authors' — an intention, " +
|
|
91
|
+
"not a record — counts as a run again, and the paper silently stops being a debtor",
|
|
92
|
+
edits: [
|
|
93
|
+
[
|
|
94
|
+
RULE,
|
|
95
|
+
"if (context.sourceCode.getText(node).includes(marker))\n recorded = true;",
|
|
96
|
+
"if (context.sourceCode.text.includes(marker))\n recorded = true;",
|
|
97
|
+
],
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: "the rule stops requiring an author cross-check run",
|
|
102
|
+
harness: HARNESS,
|
|
103
|
+
expect: "a stage is declared, no run — a finding",
|
|
104
|
+
disables:
|
|
105
|
+
"the debt itself: a shipped paper no longer owes anything, and the rule stays silent " +
|
|
106
|
+
"across the whole corpus — silence is its success state, so from the outside this is " +
|
|
107
|
+
"indistinguishable",
|
|
108
|
+
// Target retargeted 09-17: the old line disappeared when the rule moved to parsing, and
|
|
109
|
+
// the driver honestly said UNUSABLE — "the patch did not land", not "the test is weak".
|
|
110
|
+
// Now the mutation declares the run recorded before the file is even read.
|
|
111
|
+
edits: [[RULE, "let recorded = false;", "let recorded = true;"]],
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: "the exemption for an unshipped paper becomes WIDER than it should",
|
|
115
|
+
harness: HARNESS,
|
|
116
|
+
expect: "an empty stage list — silent",
|
|
117
|
+
disables:
|
|
118
|
+
"the distinction between 'no stages' and 'there are stages': a draft starts getting a " +
|
|
119
|
+
"finding, and a rule that scolds drafts gets turned off within a week",
|
|
120
|
+
edits: [
|
|
121
|
+
[
|
|
122
|
+
RULE,
|
|
123
|
+
"if (stages.length === 0) return; // nothing shipped — nothing is owed",
|
|
124
|
+
"",
|
|
125
|
+
],
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: "the stage list is taken from somewhere OTHER than the field again",
|
|
130
|
+
harness: HARNESS,
|
|
131
|
+
expect:
|
|
132
|
+
"the stage list in the message comes from the FIELD and carries all of them",
|
|
133
|
+
disables:
|
|
134
|
+
"the exact reason the move was made. The predecessor derived the stage with a regex " +
|
|
135
|
+
"over the prose, and on agenticdev printed `submitted` where `submitted, " +
|
|
136
|
+
"camera-ready` was declared. The mutation brings back a hardcoded list — the set of " +
|
|
137
|
+
"findings does not change, only the TEXT lies, and without this assert the regression " +
|
|
138
|
+
"would have passed silently",
|
|
139
|
+
edits: [[RULE, 'stages: stages.join("/"),', 'stages: "submitted",']],
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: "a consumer-specific path comes back into the package's text",
|
|
143
|
+
harness: HARNESS,
|
|
144
|
+
expect:
|
|
145
|
+
"the run command comes in as an option and lands in the message",
|
|
146
|
+
disables:
|
|
147
|
+
"the boundary 'the mechanism goes in the package, the data stays with the consumer': " +
|
|
148
|
+
"the command stops arriving as an option. The predecessor hardcoded " +
|
|
149
|
+
"`.claude/skills/verify-citations/...` — the path of one private repository — right " +
|
|
150
|
+
"into a public rule's message",
|
|
151
|
+
edits: [
|
|
152
|
+
[RULE, 'const command = opts.command ?? "";', 'const command = "";'],
|
|
153
|
+
],
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
}),
|
|
157
|
+
);
|