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
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { RuleBlock } from "./rules-config.ts";
|
|
2
|
+
/**
|
|
3
|
+
* The shapes the CLI commands exchange. Before the move to TypeScript this was all `{}` and lived
|
|
4
|
+
* in someone's head: a typo in a config key read as "field not set", not as an error.
|
|
5
|
+
*/
|
|
6
|
+
/** The contents of the consumer's `rpp.json`. */
|
|
7
|
+
export interface RppConfig {
|
|
8
|
+
/** 🔴 REQUIRED. The paper director(ies), relative to the config file ITSELF. The field name is
|
|
9
|
+
* `PAPERS_DIR_FIELD` in lib/paper-config.mjs; code reads it through `papersDirOf()` in cli.ts. */
|
|
10
|
+
papersDir?: string | string[];
|
|
11
|
+
/** The command that prints the author list from .bib — each corpus has its own. */
|
|
12
|
+
authorListCommand?: string;
|
|
13
|
+
/** Per-paper typography debt: how many findings already exist; the number may only go down. */
|
|
14
|
+
typographyDebt?: Record<string, Record<string, number>>;
|
|
15
|
+
/** Frontmatter fields required for review notes, and the permitted values of each. */
|
|
16
|
+
docFields?: Record<string, {
|
|
17
|
+
values: string[];
|
|
18
|
+
}>;
|
|
19
|
+
/** Ignore review findings older than this date. */
|
|
20
|
+
reviewSince?: string;
|
|
21
|
+
/** How many findings a cold read must produce to count as a cold read. */
|
|
22
|
+
minFindings?: number;
|
|
23
|
+
/** The word with which review notes introduce the cause. */
|
|
24
|
+
causeMarker?: string;
|
|
25
|
+
/** Which files a paper directory must carry; `false` turns the check off entirely. */
|
|
26
|
+
structure?: StructureConfig | false;
|
|
27
|
+
/** REMOVED — `paperlint build` compiles the paper itself. Still typed so a leftover key can be named. */
|
|
28
|
+
buildScripts?: unknown;
|
|
29
|
+
/** ESLint config blocks appended after rpp's own — PARSED by `parseSettings` in cli.ts. */
|
|
30
|
+
rules?: readonly RuleBlock[];
|
|
31
|
+
}
|
|
32
|
+
export interface StructureConfig {
|
|
33
|
+
/** These files are what IDENTIFY a directory as a paper. Detection is generous. */
|
|
34
|
+
markers?: string[];
|
|
35
|
+
/** These files must exist. The requirements are strict. */
|
|
36
|
+
require?: string[];
|
|
37
|
+
/** At least one from each group. */
|
|
38
|
+
requireOneOf?: string[][];
|
|
39
|
+
/** Directories exempted from the check by name. */
|
|
40
|
+
ignore?: string[];
|
|
41
|
+
}
|
|
42
|
+
/** The parsed command line. */
|
|
43
|
+
export interface Args {
|
|
44
|
+
cmd: string | null;
|
|
45
|
+
paths: string[];
|
|
46
|
+
config: string | null;
|
|
47
|
+
json: boolean;
|
|
48
|
+
all: boolean;
|
|
49
|
+
dryRun: boolean;
|
|
50
|
+
/** `--check`: `toolchain` reports what is missing and changes nothing. */
|
|
51
|
+
check: boolean;
|
|
52
|
+
maxWarnings: number;
|
|
53
|
+
/** `--yes` / `-y`: take every default, ask nothing. */
|
|
54
|
+
yes: boolean;
|
|
55
|
+
/** `--no-hooks`: `init` does not touch `.claude/settings.json`. */
|
|
56
|
+
noHooks: boolean;
|
|
57
|
+
/** `--paper <name>`: `init` creates this paper. */
|
|
58
|
+
paper: string | null;
|
|
59
|
+
/** `--format tex|md` for `new` and `init --paper`. Validated by the command, not here. */
|
|
60
|
+
format: string | null;
|
|
61
|
+
/** `--hooks=<mode>` — parsed only so it can be REFUSED by name rather than read as a path. */
|
|
62
|
+
hooksMode: string | null;
|
|
63
|
+
help?: boolean;
|
|
64
|
+
/** A flag that turned out to have no value. A non-empty field is a REFUSAL, not a default. */
|
|
65
|
+
missingValue?: string;
|
|
66
|
+
}
|
|
67
|
+
/** A finding about the PRESENCE of a file — what an ESLint rule cannot express. */
|
|
68
|
+
export interface StructureFinding {
|
|
69
|
+
file: string;
|
|
70
|
+
message: string;
|
|
71
|
+
}
|
|
72
|
+
/** One line of a build plan: a step, whether it applies to this paper, and why. */
|
|
73
|
+
export interface PlanLine {
|
|
74
|
+
step: string;
|
|
75
|
+
applies: boolean;
|
|
76
|
+
/** A required step that does not apply refuses the build instead of being skipped. */
|
|
77
|
+
required: boolean;
|
|
78
|
+
why: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The build outcome for one paper. `no-source` — there is no `paper.tex` to compile — is a
|
|
82
|
+
* REFUSAL, not a skip: "nothing to build" and "built" must never look alike.
|
|
83
|
+
*/
|
|
84
|
+
export interface BuildResult {
|
|
85
|
+
dir: string;
|
|
86
|
+
status: "built" | "failed" | "no-source";
|
|
87
|
+
plan: PlanLine[];
|
|
88
|
+
/** `--dry-run`: the plan was printed and nothing ran. */
|
|
89
|
+
dry?: boolean;
|
|
90
|
+
/** The step that failed and what to show for it. */
|
|
91
|
+
failure?: {
|
|
92
|
+
step: string;
|
|
93
|
+
lines: string[];
|
|
94
|
+
};
|
|
95
|
+
/** What the steps reported on success: pass counts, undefined-reference warnings. */
|
|
96
|
+
notes?: string[];
|
|
97
|
+
/** A paper.pdf from an earlier run was on disk and `buildPapers` removed it before building. */
|
|
98
|
+
staleRemoved?: boolean;
|
|
99
|
+
}
|
|
100
|
+
/** The result of reading the config: either data, or the exit code the caller exits with. */
|
|
101
|
+
export type ConfigRead = {
|
|
102
|
+
opts: RppConfig;
|
|
103
|
+
configPath: string | null;
|
|
104
|
+
code?: undefined;
|
|
105
|
+
} | {
|
|
106
|
+
code: number;
|
|
107
|
+
opts?: undefined;
|
|
108
|
+
configPath?: undefined;
|
|
109
|
+
};
|
|
110
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD;;;GAGG;AAEH,iDAAiD;AACjD,MAAM,WAAW,SAAS;IACxB;sGACkG;IAClG,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC9B,mFAAmF;IACnF,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,+FAA+F;IAC/F,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACxD,sFAAsF;IACtF,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACjD,mDAAmD;IACnD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sFAAsF;IACtF,SAAS,CAAC,EAAE,eAAe,GAAG,KAAK,CAAC;IACpC,yGAAyG;IACzG,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,2FAA2F;IAC3F,KAAK,CAAC,EAAE,SAAS,SAAS,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,eAAe;IAC9B,mFAAmF;IACnF,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,oCAAoC;IACpC,YAAY,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC;IAC1B,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,+BAA+B;AAC/B,MAAM,WAAW,IAAI;IACnB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC;IACd,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,0EAA0E;IAC1E,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,uDAAuD;IACvD,GAAG,EAAE,OAAO,CAAC;IACb,mEAAmE;IACnE,OAAO,EAAE,OAAO,CAAC;IACjB,mDAAmD;IACnD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,0FAA0F;IAC1F,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,8FAA8F;IAC9F,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,8FAA8F;IAC9F,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,mFAAmF;AACnF,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,mFAAmF;AACnF,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,sFAAsF;IACtF,QAAQ,EAAE,OAAO,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,WAAW,CAAC;IACzC,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB,yDAAyD;IACzD,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,oDAAoD;IACpD,OAAO,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAC5C,qFAAqF;IACrF,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,gGAAgG;IAChG,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,6FAA6F;AAC7F,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,CAAA;CAAE,GAChE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,CAAC;IAAC,UAAU,CAAC,EAAE,SAAS,CAAA;CAAE,CAAC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# Configuration reference
|
|
2
|
+
|
|
3
|
+
One key in your `package.json`, written by `paperlint init`. It holds the facts only your repository can
|
|
4
|
+
supply — nothing in it is guessable by a package that has never seen your corpus.
|
|
5
|
+
|
|
6
|
+
The README carries the minimal version of this. Everything below is the full surface, moved out on
|
|
7
|
+
2026-09-19.
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"paperlint": {
|
|
12
|
+
"papersDir": "papers",
|
|
13
|
+
"authorListCommand": "node scripts/bib-authors.mjs",
|
|
14
|
+
"typographyDebt": { "papers/my-paper": { "sectionSign": 12 } },
|
|
15
|
+
"docFields": { "read": { "values": ["full", "abstract", "none"] } },
|
|
16
|
+
"reviewSince": "2026-08-23",
|
|
17
|
+
"minFindings": 3,
|
|
18
|
+
"causeMarker": "Cause:",
|
|
19
|
+
"rules": [
|
|
20
|
+
{
|
|
21
|
+
"files": ["papers/agenticdev-2026/**"],
|
|
22
|
+
"rules": { "pdf/last-page-balance": ["error", { "tolerancePt": 120 }] }
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
| key | required | what it is |
|
|
30
|
+
| ------------------- | -------- | ----------------------------------------------------------------------------------------- |
|
|
31
|
+
| `papersDir` | **yes** | the directory your papers live in, relative to the file holding it. One string or a list. |
|
|
32
|
+
| `structure` | no | which files every paper directory must contain — see below. `false` turns it off. |
|
|
33
|
+
| `authorListCommand` | no | the command `paper/author-list` tells you to run when the check is missing |
|
|
34
|
+
| `typographyDebt` | no | per-paper allowance of existing typography findings, so the count can only go down |
|
|
35
|
+
| `docFields` | no | required front-matter fields in review files, and the values each may hold |
|
|
36
|
+
| `reviewSince` | no | only review files created on or after this date are checked |
|
|
37
|
+
| `minFindings` | no | a review with fewer findings than this is not required to name causes |
|
|
38
|
+
| `causeMarker` | no | the phrase a review uses to introduce a cause (default `Cause:`), in any language |
|
|
39
|
+
| `rules` | no | extra ESLint config blocks: turn optional rules on, change a rule's severity — see below |
|
|
40
|
+
|
|
41
|
+
The skill scripts read a few more keys of the same object — `ledger`, `scripts`, `timezone`,
|
|
42
|
+
`contactEmail`, `citeChecks`, `triggerCases` — documented with the skills that use them.
|
|
43
|
+
|
|
44
|
+
**Any other key is an error**, named in the message: `package.json → "paperlint":
|
|
45
|
+
unknown key "typographyDept"`. A misspelt key would otherwise read as "not set", and the setting
|
|
46
|
+
you meant would silently do nothing. The list of known keys is `SETTINGS_KEYS` in
|
|
47
|
+
`lib/paper-config.mjs`.
|
|
48
|
+
|
|
49
|
+
`papersDir` is required because the scope is the one thing that must not default: a default of `"."`
|
|
50
|
+
turns every run into a green report over the whole checkout. `paperlint init` fills it by measuring —
|
|
51
|
+
and when nothing on disk looks like a papers directory, it writes the documented default and says
|
|
52
|
+
in the same breath that it is a guess.
|
|
53
|
+
|
|
54
|
+
Until 2026-09-24 this field was called `papers`. The old name is not read as a fallback: `paperlint lint`,
|
|
55
|
+
`paperlint init`, `paperlint doctor`, the ESLint helper and the edit guard all stop with
|
|
56
|
+
`"papers" was renamed to "papersDir" in package.json → "paperlint"`. The two advisory
|
|
57
|
+
hooks stay silent instead. The name is defined once, as `PAPERS_DIR_FIELD` in
|
|
58
|
+
`lib/paper-config.mjs`.
|
|
59
|
+
|
|
60
|
+
## Why the key lives in `package.json`
|
|
61
|
+
|
|
62
|
+
Because of a count: the `package.json` key has **five** readers — the three editor hooks, the
|
|
63
|
+
ESLint helper, the skill scripts — and a separate config file had **one**, the CLI. A hook cannot
|
|
64
|
+
import code and cannot walk up a tree looking for a config; it can read a path it is able to name,
|
|
65
|
+
and the one path it can always name is the project's `package.json`.
|
|
66
|
+
|
|
67
|
+
`paperlint lint` looks for it in the current directory and then upwards, the way eslint and tsc find
|
|
68
|
+
theirs, and prints which file it found. `--config <file>` overrides the search.
|
|
69
|
+
|
|
70
|
+
⚠️ **`rpp.json` is deprecated and still read.** Earlier versions of `init` created it; `init` no
|
|
71
|
+
longer does, and a run that reads one says so on its first line. The hooks never read it, so
|
|
72
|
+
leaving settings there is how the linter and the guard end up watching different directories —
|
|
73
|
+
`paperlint init` copies the value across for you.
|
|
74
|
+
|
|
75
|
+
## The `rules` key: turning rules on and off
|
|
76
|
+
|
|
77
|
+
`rules` is a list of blocks in ESLint's own
|
|
78
|
+
[flat-config shape](https://eslint.org/docs/latest/use/configure/configuration-files), limited to
|
|
79
|
+
the three keys that make sense in JSON — `files`, `ignores` and `rules`. paperlint appends the blocks
|
|
80
|
+
**after** its own configuration, so, as in ESLint, a later block wins: a block can turn on a rule
|
|
81
|
+
that is off by default, or change the severity of one that is on.
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
"rules": [
|
|
85
|
+
{
|
|
86
|
+
"files": ["papers/agenticdev-2026/**"],
|
|
87
|
+
"rules": { "pdf/last-page-balance": ["error", { "tolerancePt": 120 }] }
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"files": ["papers/old-draft/**"],
|
|
91
|
+
"rules": { "paper/typography": "off" }
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
- **`files` and `ignores` are globs relative to the file that holds the settings** — the
|
|
97
|
+
directory of your `package.json` — exactly as ESLint resolves them relative to its config file,
|
|
98
|
+
whatever directory you run `paperlint lint` from. A block without `files` applies to every linted file.
|
|
99
|
+
A pattern ending in `/**` is the usual way to name one paper.
|
|
100
|
+
- **A rule entry** is a severity (`"off"`, `"warn"`, `"error"`, or `0`/`1`/`2`), or a list whose
|
|
101
|
+
first element is a severity and the rest are the rule's options.
|
|
102
|
+
- **Only rules paperlint ships can be named** — the ones in [`docs/rules.md`](rules.md) and
|
|
103
|
+
[`docs/optional-rules.md`](optional-rules.md). A rule id paperlint does not ship, a bad severity, a
|
|
104
|
+
`rules` that is not a list, or a block key other than `files`, `ignores` and `rules` stops the run
|
|
105
|
+
with a message naming the exact key, before anything is linted.
|
|
106
|
+
- **An optional rule you turned on must reach a paper.** If no linted `paper.tex` gets the rule —
|
|
107
|
+
usually a `files` glob with a typo — `paperlint lint` fails and says so: a rule that never runs
|
|
108
|
+
reports exactly like one that passed.
|
|
109
|
+
|
|
110
|
+
## Required files
|
|
111
|
+
|
|
112
|
+
A rule runs on a file it was handed. A file that is missing is never handed to anything — so no
|
|
113
|
+
rule can report it, and a paper directory without `PIPELINE-STATUS.md` gets **zero** rules and a
|
|
114
|
+
clean report. `paperlint lint` therefore checks presence itself, before ESLint runs.
|
|
115
|
+
|
|
116
|
+
Detection is generous and requirements are strict, on purpose. A directory counts as a paper only
|
|
117
|
+
once it already holds one of the marker files, so `research/`, `plans/` and other neighbours in
|
|
118
|
+
the corpus are left alone; an error-level check that fires on a correct tree gets switched off,
|
|
119
|
+
and the real findings leave with it.
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
"structure": {
|
|
123
|
+
"markers": ["PIPELINE-STATUS.md", "paper.tex", "paper.md", "venue.json"],
|
|
124
|
+
"require": ["PIPELINE-STATUS.md"],
|
|
125
|
+
"requireOneOf": [["paper.tex", "paper.md"]],
|
|
126
|
+
"ignore": []
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Those are the defaults; you only write the block to change them. `paper.md` is still in them
|
|
131
|
+
because Markdown papers are deprecated but not yet removed
|
|
132
|
+
([#57](https://github.com/zernie/research-paper-pipeline/issues/57)). They were measured against a
|
|
133
|
+
real five-paper corpus rather than chosen — it passes with zero findings, while adding
|
|
134
|
+
`paper.pdf` to `require` produces two findings on papers that are perfectly fine, which is why it
|
|
135
|
+
is not there.
|
|
136
|
+
|
|
137
|
+
This is the half [ls-lint](https://ls-lint.org/) cannot do. ls-lint judges the **names** of files
|
|
138
|
+
that exist; it has nothing to compare against for a file that does not. Use both: ls-lint for
|
|
139
|
+
"what is there is named right", this for "what must be there is there".
|
|
140
|
+
|
|
141
|
+
## How `paperlint build` compiles a paper
|
|
142
|
+
|
|
143
|
+
`paperlint build <paper>` compiles `paper.tex` to `paper.pdf` itself, with TeX Live's `pdflatex` and
|
|
144
|
+
`bibtex` ([`docs/toolchain.md`](toolchain.md)). There is nothing to configure and no script to
|
|
145
|
+
write. It prints its plan first, one line per step, then runs it:
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
papers/my-paper
|
|
149
|
+
inputs: TEXINPUTS += <paperlint>/skills/submit-paper/references/venues
|
|
150
|
+
compile: paper.tex (\documentclass[sigconf,screen]{acmart}, venue agenticdev)
|
|
151
|
+
measure: pdf.js → _build/paper.facts.json (facts for the lint rules; nothing is judged here)
|
|
152
|
+
✓ paper.pdf — 4 pdflatex passes, 1 bibtex run; facts: _build/paper.facts.json, last page 621.5 / 264.8 pt
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
- **inputs** — paperlint's own venue files (`paper-guards.tex`, `<venue>.tex`) are put on `TEXINPUTS`,
|
|
156
|
+
so `\input{paper-guards}` in a preamble resolves with no setup. The system tree still resolves
|
|
157
|
+
after them.
|
|
158
|
+
- **compile** — `pdflatex -interaction=nonstopmode -halt-on-error -file-line-error`, then `bibtex`
|
|
159
|
+
when the `.aux` names a bibliography, then pdflatex again until the `.aux`, `.toc`, `.out` and
|
|
160
|
+
`.bbl` stop changing and the log stops asking for a rerun. bibtex runs again only when the cited
|
|
161
|
+
keys or a `.bib` file changed. After that, one **final** pass defines `\finalpass`, which arms
|
|
162
|
+
the reference guards in `paper-guards.tex`: an undefined `\ref` or `\cite` fails the build
|
|
163
|
+
there instead of printing `??`. A document that still changes after five passes fails, naming
|
|
164
|
+
the file that kept changing.
|
|
165
|
+
- **measure** — after a green compile, the PDF is read with pdf.js and what it measures is written
|
|
166
|
+
to `_build/paper.facts.json`: the page count, every font the pages draw text with (and whether
|
|
167
|
+
its program is embedded, and whether it is Type 3), and the heights of the last page's two
|
|
168
|
+
columns — or why they were not measured (a stub page of a few lines, or a review build with
|
|
169
|
+
numbered lines). [banal](https://github.com/kohler/hotcrp/blob/master/src/banal) — installed by
|
|
170
|
+
`paperlint toolchain`, or a project's own `vendor/banal` or `$BANAL` — adds the page size, column count
|
|
171
|
+
and font sizes, measured from the same pdf.js read (no poppler; see
|
|
172
|
+
[`toolchain.md`](toolchain.md#page-geometry-banal-without-poppler)); without banal those fields
|
|
173
|
+
are `null` and the build says so. A PDF pdf.js cannot read fails the build. The file is
|
|
174
|
+
written by one function, which `skills/render-paper/extract-pdf-facts.mjs` also calls for PDFs
|
|
175
|
+
paperlint did not build. Keep `_build/` out of git: the facts carry the PDF's SHA-256, and a rule
|
|
176
|
+
refuses facts about a different PDF than the one on disk.
|
|
177
|
+
|
|
178
|
+
The build does **not** judge the layout. A balanced last page, a page limit, the fonts a venue
|
|
179
|
+
wants — those are verdicts about the finished PDF, and they belong to lint rules that can be
|
|
180
|
+
turned on per venue, given a severity and suppressed with a reason. paperlint once searched for a
|
|
181
|
+
`\balance` position itself and failed the build when none worked; that was removed on
|
|
182
|
+
2026-09-24.
|
|
183
|
+
|
|
184
|
+
The class and its options and the venue in `venue.json` are read from the paper and shown in the
|
|
185
|
+
plan; later steps decide from them whether they apply.
|
|
186
|
+
|
|
187
|
+
**`paper.pdf` is deleted before anything runs**, for every targeted paper — before the TeX Live is
|
|
188
|
+
chosen and before the first step. So no outcome leaves an old PDF looking current: not a failed
|
|
189
|
+
build, not a run that stops because there is no TeX Live with the packages the papers need, not a
|
|
190
|
+
paper with no `paper.tex`. A green `✓ paper.pdf` therefore always means this run wrote it, and a
|
|
191
|
+
pdflatex that exits 0 without writing one (a document with no pages) is a failure:
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
✗ compile: pdflatex exited 0 but wrote no paper.pdf — does the document have any pages?
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**On failure** the command names the program that failed, quotes the first error line from the log
|
|
198
|
+
with its `l.NNN` source context, and says the PDF is gone — the same line on every path that ends
|
|
199
|
+
without a new one:
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
✗ compile: pdflatex exited with 1
|
|
203
|
+
./paper.tex:6: Undefined control sequence.
|
|
204
|
+
l.6 Text before, then \undefinedmacro
|
|
205
|
+
{} after.
|
|
206
|
+
full log: papers/my-paper/paper.log
|
|
207
|
+
paper.pdf removed — a stale PDF must not pass for this build
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**A paper with no `paper.tex` is a FAILURE, not a skip.** "Nothing to build" and "built" must never
|
|
211
|
+
look alike: the corpus this came from once let a paper reach its venue without a single paper job
|
|
212
|
+
having run on it, because a missing build read as nothing to do.
|
|
213
|
+
|
|
214
|
+
`--dry-run` prints the plan and runs nothing — and deletes nothing, `paper.pdf` included.
|
|
215
|
+
|
|
216
|
+
⚠️ **A `build.sh` or `repro/build-submission.sh` in the paper directory is IGNORED.** Earlier
|
|
217
|
+
versions ran it; `paperlint build` now says one line — `build.sh is ignored — paperlint builds the paper
|
|
218
|
+
itself` — and builds the paper itself. The `buildScripts` key is ignored the same way.
|
|
219
|
+
Why: [#59](https://github.com/zernie/research-paper-pipeline/issues/59).
|
|
220
|
+
|
|
221
|
+
## Using the rules from an existing ESLint config
|
|
222
|
+
|
|
223
|
+
Under the hood `paperlint lint` builds an ESLint flat config and runs it. If your repository already
|
|
224
|
+
lints with ESLint, you can import the rule modules from `paperlint/eslint-rules/`
|
|
225
|
+
and wire them yourself; `bin/rpp.mjs` exports `buildConfig(options, texLanguage)` that returns
|
|
226
|
+
the exact config the CLI uses, so the shortest path is:
|
|
227
|
+
|
|
228
|
+
```js
|
|
229
|
+
// eslint.config.mjs
|
|
230
|
+
import { buildConfig } from "paperlint/bin/rpp.mjs";
|
|
231
|
+
import { texLanguage } from "paperlint/eslint-rules/latex-language.mjs";
|
|
232
|
+
export default buildConfig({ minFindings: 3 }, texLanguage);
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Then point the CI action's `config` input at that file.
|
package/docs/e2e.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# End-to-end tests
|
|
2
|
+
|
|
3
|
+
They are `test/e2e/install.mjs` and `test/e2e/build.mjs`. Both are
|
|
4
|
+
part of `npm run check`, so nobody has to remember to call them. `npm run test:e2e` runs just
|
|
5
|
+
these, install first; if the install e2e fails or is skipped, the build e2e does not run.
|
|
6
|
+
|
|
7
|
+
This page says what they prove, what they deliberately do not, and when a change owes a new one.
|
|
8
|
+
|
|
9
|
+
## The line between a harness and an end-to-end run
|
|
10
|
+
|
|
11
|
+
Most of this package is tested by harnesses, described in [`CONTRIBUTING.md`](../CONTRIBUTING.md):
|
|
12
|
+
a rule is handed a file with a defect and must find it, then a clean file and must stay silent.
|
|
13
|
+
That is the right shape for a rule, and it cannot answer a different kind of question.
|
|
14
|
+
|
|
15
|
+
A harness runs **in this repository's process, against this repository's working tree**. It
|
|
16
|
+
imports the module directly. Every path resolves, every dependency is already installed, and the
|
|
17
|
+
file it reads is the file in `git`. An end-to-end run exists because all three of those are
|
|
18
|
+
assumptions that stop holding the moment somebody else installs the package.
|
|
19
|
+
|
|
20
|
+
| question | answered by |
|
|
21
|
+
| ------------------------------------------------------------------------------------ | --------------------------------------------------------- |
|
|
22
|
+
| does this rule find this defect, and stay quiet otherwise | harness |
|
|
23
|
+
| does the CLI pick the right script, with the right interpreter | harness (`src/build.harness.mjs` substitutes `spawnSync`) |
|
|
24
|
+
| does the tarball, installed into a tree that is not this one, contain what it claims | **e2e** |
|
|
25
|
+
| do the paths written inside a skill resolve where the package actually lands | **e2e** |
|
|
26
|
+
| did a PDF come out, and is it typeset in the fonts the venue requires | **e2e** |
|
|
27
|
+
|
|
28
|
+
The third and fourth rows are the ones a harness cannot reach even in principle: the defect only
|
|
29
|
+
exists once the code is somewhere else.
|
|
30
|
+
|
|
31
|
+
## `test/e2e/install.mjs` — the package, installed
|
|
32
|
+
|
|
33
|
+
`test/e2e/install.mjs`. It runs `npm pack`, then installs the resulting tarball into a fresh
|
|
34
|
+
temporary tree, **under npm and under pnpm separately**, and drives the installed binary:
|
|
35
|
+
|
|
36
|
+
- the install itself finishes
|
|
37
|
+
- `paperlint --help` answers with zero
|
|
38
|
+
- `paperlint init` declares the papers directory in `package.json`, and does _not_ leave a second
|
|
39
|
+
carrier `rpp.json` behind
|
|
40
|
+
- `paperlint init` finishes with zero — its doctor found no discrepancy
|
|
41
|
+
- `paperlint init` wires the hooks into `.claude/settings.json` — the same commands `hooks.json`
|
|
42
|
+
publishes, once each — and says they need `npm install` in a fresh clone; a second `init`
|
|
43
|
+
leaves that file byte-identical
|
|
44
|
+
- `paperlint lint` passes the staged corpus
|
|
45
|
+
- `paperlint new demo` scaffolds a paper from the templates that shipped in the tarball, its own lint is
|
|
46
|
+
clean, and `paperlint lint` stays clean with it in the corpus
|
|
47
|
+
- `hooks.json` arrived and parses, and every hook command named in it resolves to a file
|
|
48
|
+
- every skill arrived, and every script path named inside a skill resolves **in the consumer**
|
|
49
|
+
|
|
50
|
+
The last two rows are the reason this file exists. A skill is a markdown document with paths in
|
|
51
|
+
it; nothing in this repository's own test run would notice that those paths only work here.
|
|
52
|
+
|
|
53
|
+
🔴 **Two package managers, not one, and it is not belt-and-braces.** The wiring addresses the
|
|
54
|
+
runtime from the project root, and pnpm does not put transitive dependencies at the root. A layout
|
|
55
|
+
that works under npm can be dead under pnpm with no error anywhere. The same run also launches the
|
|
56
|
+
binary **directly** rather than through `node <path>`: under npm `.bin` holds a symlink, under
|
|
57
|
+
pnpm a shell wrapper, and calling `node bin` measures the caller's habit instead of the package.
|
|
58
|
+
|
|
59
|
+
## `test/e2e/build.mjs` — a real `pdflatex`
|
|
60
|
+
|
|
61
|
+
`test/e2e/build.mjs`. It copies `fixtures/build-e2e/` — eight papers, none with a build script paperlint
|
|
62
|
+
would run — into a temporary tree, points a config at it, and runs `paperlint build --all`. paperlint compiles
|
|
63
|
+
each paper itself with the real `pdflatex` and `bibtex`; the artifacts are then measured with
|
|
64
|
+
paperlint's own pdf.js reader from `dist/`, and the fonts are cross-checked against the list of
|
|
65
|
+
programs pdfTeX writes into `paper.log`:
|
|
66
|
+
|
|
67
|
+
| fixture | what it is there to prove |
|
|
68
|
+
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
69
|
+
| `acmart` | a real `\documentclass{acmart}` source builds, and the PDF carries the class's **own** font families — not one family of the silent substitution |
|
|
70
|
+
| `fallback` | the same document with the fonts missing still **builds green**, and is **rejected** by the font check. The failure is in the artifact, not in the exit code |
|
|
71
|
+
| `cite` | the bibtex path: the PDF shows `[1]`, not `[?]`, and no `??`. Its `build.sh` would leave a trace if run — it must not run, and the run must say it is ignored |
|
|
72
|
+
| `guards` | `\input{paper-guards}` resolves with no configuration, because paperlint puts its own venues directory on `TEXINPUTS` |
|
|
73
|
+
| `unbalanced` | a two-column acmart paper whose last page comes out 621.5 / 264.8 pt builds **green** — the build does not judge the layout and rewrites nothing — while `_build/paper.facts.json` records both heights, and `paperlint lint` with `pdf/last-page-balance` turned on in `rules` reports the page |
|
|
74
|
+
| `broken` | a failing build names pdflatex and its exit code, quotes the error line and its `l.NNN` context, and deletes the stale `paper.pdf` planted before the run |
|
|
75
|
+
| `no-source` | a paper with no `paper.tex` is named separately, and the run as a whole is a failure |
|
|
76
|
+
|
|
77
|
+
The `fallback` row is the point of the whole file. `acmart.cls` checks for `libertine.sty`,
|
|
78
|
+
`zi4.sty` and `newtxmath.sty`, and failing to find any of them sets `\@ACM@newfontsfalse` and
|
|
79
|
+
typesets the paper in Computer Modern. The build is green, the PDF looks fine, the metrics differ,
|
|
80
|
+
and therefore so does the pagination. A submitted paper went out that way. **An exit code cannot
|
|
81
|
+
see it, so the content of the artifact is what gets measured.**
|
|
82
|
+
|
|
83
|
+
Before the fixtures, the run proves the refusal with no TeX at all: PATH holds `node` alone, the
|
|
84
|
+
cache directory is empty and `CI` is set, and `paperlint build` must exit 1 with one line naming
|
|
85
|
+
`npx paperlint toolchain` and the venue's packages, print no plan and create no PDF and no cache. It then
|
|
86
|
+
asks `paperlint build --dry-run` which TeX Live the real run will use; under `--strict` (CI) that must be
|
|
87
|
+
paperlint's own cache, because the runner has no other.
|
|
88
|
+
|
|
89
|
+
## `test/e2e/toolchain.mjs` — real TeX Live, and only it
|
|
90
|
+
|
|
91
|
+
`paperlint toolchain` into `$RPP_TEXLIVE_DIR` against real CTAN; a second run must say "nothing to do"
|
|
92
|
+
within seconds; `--check` must exit 0; then the `acmart` fixture is built with PATH holding `node`
|
|
93
|
+
only, so no other TeX Live and no PDF tool can stand in, and the PDF must carry Libertine and Biolinum
|
|
94
|
+
and no Computer Modern face. Without `RPP_TEXLIVE_DIR` it is a declared skip: installing ~270 MB
|
|
95
|
+
into a home directory as a side effect of `npm run check` is the unasked install rule 11 forbids.
|
|
96
|
+
`src/toolchain.harness.mjs` covers the installer's logic (mirror fallback, archive check, time
|
|
97
|
+
limit, verification, idempotence) against a fake mirror on disk, without the network.
|
|
98
|
+
|
|
99
|
+
## Skips are declared, never silent
|
|
100
|
+
|
|
101
|
+
A clone without TeX Live genuinely cannot run the build e2e; a machine without pnpm cannot run
|
|
102
|
+
half of the install e2e. Each says so and exits **77** — the skip code vigiles' own runner uses —
|
|
103
|
+
not zero, and `npm run check` reports it as `SKIPPED — not run, not passed`. In CI the same absence
|
|
104
|
+
means a broken environment, so `--strict` turns the skip into a failure.
|
|
105
|
+
|
|
106
|
+
A skipped step and a passed one look identical in a CI interface. That is the class this whole
|
|
107
|
+
package is written against, so it is not allowed to happen inside it either.
|
|
108
|
+
|
|
109
|
+
## When a change owes an e2e
|
|
110
|
+
|
|
111
|
+
Not every change does. The trigger is countable — ask whether the change touches something that is
|
|
112
|
+
**only true after installation**:
|
|
113
|
+
|
|
114
|
+
- a new file or directory that has to arrive on the consumer's disk (a skill, a hook, a template)
|
|
115
|
+
- a path written inside a document rather than resolved by a module loader
|
|
116
|
+
- anything read through `package.json` fields — `bin`, `files`, `exports`
|
|
117
|
+
- a new external program the pipeline shells out to
|
|
118
|
+
- a new artifact the pipeline produces whose **content** can be wrong while the exit code is zero
|
|
119
|
+
|
|
120
|
+
If none of those apply, a harness beside the changed module is the right test, and an e2e run
|
|
121
|
+
would only make the suite slower without making it stricter.
|
|
122
|
+
|
|
123
|
+
## What these two do NOT cover
|
|
124
|
+
|
|
125
|
+
Named here on purpose: a test suite that does not say where it stops is read as covering
|
|
126
|
+
everything.
|
|
127
|
+
|
|
128
|
+
- **macOS and Windows.** Locally these run on whatever machine you have. In CI `build-e2e` is a
|
|
129
|
+
matrix over `ubuntu-latest` and `macos-latest`; Windows is not run anywhere, and `paperlint toolchain`
|
|
130
|
+
refuses it.
|
|
131
|
+
- **The rules, on a real document.** `fixtures/real-markdown-paper/` holds a published article and
|
|
132
|
+
a recorded baseline of what the rules say about it. That is a **lint** fixture, driven by a
|
|
133
|
+
harness — it does not go through the installed package. Wiring it into the install corpus is
|
|
134
|
+
open work, not something already done.
|
|
135
|
+
- **The skills as behaviour.** The install run proves a skill arrived and that its paths resolve.
|
|
136
|
+
Whether the agent then does the right thing with it is a different measurement, and it belongs
|
|
137
|
+
to the harness tier of `vigiles`, not here.
|
|
138
|
+
- **Registry publication.** The package is on npm: `research-paper-pipeline@1.0.0` was published
|
|
139
|
+
on 2026-09-25 by `.github/workflows/release.yml`; from 2.0.0 it is published as `paperlint`, the
|
|
140
|
+
same way. The
|
|
141
|
+
e2e still covers the packlist through `npm pack`, which produces the same tarball `npm publish`
|
|
142
|
+
uploads; the registry round-trip — install from npm, then run — is still not run by any test.
|
|
143
|
+
|
|
144
|
+
## The corpus
|
|
145
|
+
|
|
146
|
+
`stageCorpus()` in `test/e2e/install.mjs` writes a small paper by hand — a declared stage, its
|
|
147
|
+
PDF, its byte counts, the cross-check between them — and copies `fixtures/build-e2e/acmart` beside
|
|
148
|
+
it so the LaTeX rules see LaTeX rather than a placeholder.
|
|
149
|
+
|
|
150
|
+
⚠️ **Every line of that corpus was written by somebody who already knew which rule would read it.**
|
|
151
|
+
It keeps the stage machinery honest and it cannot tell you anything about false positives. That is
|
|
152
|
+
what the real-article fixture is for, and why the two tiers are not substitutes.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Incidents — the measurements the rules stand on
|
|
2
|
+
|
|
3
|
+
`CLAUDE.md` is loaded on every turn, so it carries **the claim and the remedy** and nothing
|
|
4
|
+
else. The measurement that produced a rule lives here, and is read only when someone argues
|
|
5
|
+
with the rule or wants to know how it was found.
|
|
6
|
+
|
|
7
|
+
Two things never move into this file: a measurement that **is** the rule (rule 5's table of
|
|
8
|
+
claims that fell to one command — without it the rule reads "measure things", which nobody
|
|
9
|
+
obeys), and evidence that already lives in a code comment beside the code it explains. Those
|
|
10
|
+
get a pointer, never a copy.
|
|
11
|
+
|
|
12
|
+
Entries are append-only and dated. A superseded entry is marked, not deleted.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 2026-09-17 — four fixes proposed and withdrawn in one session (rule 9)
|
|
17
|
+
|
|
18
|
+
Each was proposed before the defect it claimed to fix had been exhibited, and each fell to a
|
|
19
|
+
single command:
|
|
20
|
+
|
|
21
|
+
| proposed | what one command showed instead |
|
|
22
|
+
| ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
23
|
+
| a new `skip` API in the test runner | the existing classification had never been shown to be wrong |
|
|
24
|
+
| a `link` subcommand as the single install path | this package is consumed three ways, and a plugin cannot serve two of them |
|
|
25
|
+
| "one delivery channel instead of two" | the channels carry different things: eight ESLint imports and a composite action on one side, skills and hooks on the other |
|
|
26
|
+
| "symlinked skills are our own invention" | the documentation describes them, and third-party repositories install exactly that way |
|
|
27
|
+
|
|
28
|
+
Two of the four were demolished by an adversarial second pass **before** either was built. A
|
|
29
|
+
third pass over six claims about the plugin channel corrected four of them — including a count
|
|
30
|
+
attributed to the wrong file (110 was one script's occurrences; the prefix has 208 across 190
|
|
31
|
+
lines) and a "nothing is installed" claim that was false in the consumer, where 54 symlinks
|
|
32
|
+
make the path resolve.
|
|
33
|
+
|
|
34
|
+
Cost of the pattern: the four proposals took more of the session than the work they proposed.
|
|
35
|
+
|
|
36
|
+
## 2026-09-17 — the install-location class, four silent breakages (rule 10)
|
|
37
|
+
|
|
38
|
+
One class — _code that knows where it is on disk_ — has broken this repository four times, and
|
|
39
|
+
not one of the four announced itself:
|
|
40
|
+
|
|
41
|
+
| when | what was assumed | how it failed |
|
|
42
|
+
| ---------------- | ----------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
43
|
+
| until 2026-08-15 | `resolve(HERE, "..", "skills")`, correct while the code lived beside the skills | a hash over a missing directory is stable, so every verdict read FRESH forever, with no error |
|
|
44
|
+
| 2026-09-12 | the same walk, after the move into a package | broke the same way, in the same silent direction |
|
|
45
|
+
| 2026-09-12 | the ledger defaults to a file beside its own module | inside `node_modules`, which `npm ci` deletes: appending SUCCEEDS, so rows look recorded until the next install removes them |
|
|
46
|
+
| 2026-09-17 | skills name their scripts by an install-specific path — 208 literals across 190 lines, in bodies and in `allowed-tools` | resolves through a symlink for one consumer and does not exist for another |
|
|
47
|
+
|
|
48
|
+
The first three are documented in the code they broke, and that is where the detail stays:
|
|
49
|
+
`skills/paper-pipeline/scripts/ledger.mjs` (the three rungs) and
|
|
50
|
+
`skills/paper-pipeline/scripts/consumer.mjs` (`ledgerPath`, three steps).
|
|
51
|
+
|
|
52
|
+
🔴 The load-bearing observation is not the count. All four repetitions happened **while
|
|
53
|
+
comments explaining the exact hazard sat directly above the code** — which is why rule 10 owes
|
|
54
|
+
a lint rule and not another paragraph.
|
|
55
|
+
|
|
56
|
+
A fifth instance of the same class was measured the same day and is not yet fixed: the guard
|
|
57
|
+
in `ledgerPath` asks `insideNodeModules(hereDir)`, and a plugin cache is not `node_modules`, so
|
|
58
|
+
a plugin-only consumer without a `package.json` gets its ledger written into the cache — which
|
|
59
|
+
the documentation says changes when the plugin updates.
|