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,631 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Both halves for `build.ts` β the shell that compiles a paper with pdflatex and bibtex.
|
|
3
|
+
*
|
|
4
|
+
* The process runner is replaced by a fake that writes what pdflatex and bibtex would write, so
|
|
5
|
+
* these assertions check the SHELL's decisions: what is run, with which environment, in which
|
|
6
|
+
* order, and what is left on disk. Whether a real pdflatex produces a real PDF is the other half,
|
|
7
|
+
* `test/e2e/build.mjs`.
|
|
8
|
+
*
|
|
9
|
+
* π΄ THE TWO THINGS PINNED DOWN HERE THAT A RETURNED OBJECT CANNOT SHOW: a paper-supplied
|
|
10
|
+
* `build.sh` is never executed (checked by the trace it would leave on disk and by the list of
|
|
11
|
+
* processes started), and a failed build removes the stale `paper.pdf` (checked on disk).
|
|
12
|
+
*/
|
|
13
|
+
import assert from "node:assert/strict";
|
|
14
|
+
import {
|
|
15
|
+
mkdtempSync,
|
|
16
|
+
mkdirSync,
|
|
17
|
+
writeFileSync,
|
|
18
|
+
rmSync,
|
|
19
|
+
realpathSync,
|
|
20
|
+
existsSync,
|
|
21
|
+
readFileSync,
|
|
22
|
+
} from "node:fs";
|
|
23
|
+
import { tmpdir } from "node:os";
|
|
24
|
+
import { join, dirname, delimiter } from "node:path";
|
|
25
|
+
import { fileURLToPath } from "node:url";
|
|
26
|
+
|
|
27
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
28
|
+
const {
|
|
29
|
+
buildPaper,
|
|
30
|
+
buildPapers,
|
|
31
|
+
papersIn,
|
|
32
|
+
formatResult,
|
|
33
|
+
anyFailed,
|
|
34
|
+
remedyFor,
|
|
35
|
+
parseDocumentclass,
|
|
36
|
+
readFacts,
|
|
37
|
+
withTexInputs,
|
|
38
|
+
pdflatexArgs,
|
|
39
|
+
planFor,
|
|
40
|
+
STEPS,
|
|
41
|
+
IGNORED_SCRIPTS,
|
|
42
|
+
} = await import(join(HERE, "build.ts"));
|
|
43
|
+
const { packageVenuesDir } = await import(
|
|
44
|
+
join(HERE, "..", "skills", "paper-pipeline", "scripts", "consumer.mjs")
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
let n = 0;
|
|
48
|
+
const check = (label, cond) => {
|
|
49
|
+
assert.ok(cond, label);
|
|
50
|
+
n++;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const root = realpathSync(mkdtempSync(join(tmpdir(), "rpp-build-")));
|
|
54
|
+
// π΄ banal is looked for under the temp root, never in the developer's cache: with the real
|
|
55
|
+
// `~/.cache/rpp/banal` present, the measure step would run the real banal on the fake reader's pages.
|
|
56
|
+
const ENV = { RPP_BANAL_DIR: join(root, "no-banal") };
|
|
57
|
+
const paper = (name, files) => {
|
|
58
|
+
const dir = join(root, "papers", name);
|
|
59
|
+
mkdirSync(dir, { recursive: true });
|
|
60
|
+
for (const [rel, body] of Object.entries(files)) {
|
|
61
|
+
mkdirSync(dirname(join(dir, rel)), { recursive: true });
|
|
62
|
+
writeFileSync(join(dir, rel), body);
|
|
63
|
+
}
|
|
64
|
+
return dir;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* A fake TeX. `pdflatex` writes paper.aux / paper.log (and paper.pdf on success) in its cwd;
|
|
69
|
+
* `bibtex` writes paper.bbl. Every call is recorded with its argv, cwd and TEXINPUTS.
|
|
70
|
+
*/
|
|
71
|
+
function fakeTex({
|
|
72
|
+
aux = "\\relax\n",
|
|
73
|
+
exitCode = 0,
|
|
74
|
+
log = "",
|
|
75
|
+
missing = false,
|
|
76
|
+
noPdf = false,
|
|
77
|
+
} = {}) {
|
|
78
|
+
const calls = [];
|
|
79
|
+
const run = (bin, args, opts) => {
|
|
80
|
+
calls.push({ bin, args, cwd: opts?.cwd, texinputs: opts?.env?.TEXINPUTS });
|
|
81
|
+
if (missing)
|
|
82
|
+
return {
|
|
83
|
+
error: Object.assign(new Error("spawn ENOENT"), { code: "ENOENT" }),
|
|
84
|
+
status: null,
|
|
85
|
+
};
|
|
86
|
+
if (bin === "pdflatex") {
|
|
87
|
+
writeFileSync(join(opts.cwd, "paper.aux"), aux);
|
|
88
|
+
writeFileSync(join(opts.cwd, "paper.log"), log);
|
|
89
|
+
// `noPdf`: what real pdflatex does on a document with no pages β "No pages of output.", exit 0.
|
|
90
|
+
if (exitCode === 0 && !noPdf)
|
|
91
|
+
writeFileSync(join(opts.cwd, "paper.pdf"), "%PDF-fake");
|
|
92
|
+
return { status: exitCode, stdout: "", stderr: "" };
|
|
93
|
+
}
|
|
94
|
+
if (bin === "bibtex") {
|
|
95
|
+
writeFileSync(join(opts.cwd, "paper.bbl"), "\\begin{thebibliography}{1}");
|
|
96
|
+
return { status: 0, stdout: "This is BibTeX\n", stderr: "" };
|
|
97
|
+
}
|
|
98
|
+
return { status: 99 };
|
|
99
|
+
};
|
|
100
|
+
return { run, calls };
|
|
101
|
+
}
|
|
102
|
+
const quiet = () => {};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* pdf.js's port, faked: the build's measure step reads the PDF the fake TeX wrote (`%PDF-fake`),
|
|
106
|
+
* which a real reader would refuse. Every call is recorded; `fail` makes the read fail.
|
|
107
|
+
*/
|
|
108
|
+
const reads = [];
|
|
109
|
+
const lastPage = {
|
|
110
|
+
widthPt: 612,
|
|
111
|
+
heightPt: 792,
|
|
112
|
+
words: Array.from({ length: 80 }, (_, i) => ({
|
|
113
|
+
x0: i < 40 ? 54 : 320,
|
|
114
|
+
y0: 60 + (i % 40) * 10,
|
|
115
|
+
x1: i < 40 ? 74 : 340,
|
|
116
|
+
y1: 70 + (i % 40) * 10,
|
|
117
|
+
text: "w",
|
|
118
|
+
})),
|
|
119
|
+
};
|
|
120
|
+
const fakeRead = async (pdf) => {
|
|
121
|
+
reads.push(pdf);
|
|
122
|
+
// Like the real reader: a PDF that is not on disk is unreadable, not a clean read.
|
|
123
|
+
if (!existsSync(pdf))
|
|
124
|
+
return { ok: false, reason: "unreadable", detail: `ENOENT: ${pdf}` };
|
|
125
|
+
return {
|
|
126
|
+
ok: true,
|
|
127
|
+
facts: {
|
|
128
|
+
pages: 1,
|
|
129
|
+
fonts: {
|
|
130
|
+
kind: "drawn",
|
|
131
|
+
list: [{ kind: "embedded", name: "CMR10", program: "Type1" }],
|
|
132
|
+
},
|
|
133
|
+
last: lastPage,
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
const CLEAN_TEX = "\\documentclass{article}\\begin{document}x\\end{document}";
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
// ββ facts: parsed from the paper, not configured ββββββββββββββββββββββββββββββββββββββ
|
|
141
|
+
check(
|
|
142
|
+
"documentclass and options come from the parser",
|
|
143
|
+
JSON.stringify(
|
|
144
|
+
parseDocumentclass(
|
|
145
|
+
"\\documentclass[sigconf, review]{acmart}\n\\begin{document}x\\end{document}",
|
|
146
|
+
),
|
|
147
|
+
) === JSON.stringify({ name: "acmart", options: ["sigconf", "review"] }),
|
|
148
|
+
);
|
|
149
|
+
check(
|
|
150
|
+
"a \\documentclass inside a COMMENT is not the class",
|
|
151
|
+
parseDocumentclass("% \\documentclass{fake}\n\\documentclass{article}\n")
|
|
152
|
+
?.name === "article",
|
|
153
|
+
);
|
|
154
|
+
check(
|
|
155
|
+
"no \\documentclass β null, not a guess",
|
|
156
|
+
parseDocumentclass("hello") === null,
|
|
157
|
+
);
|
|
158
|
+
const venued = paper("venued", {
|
|
159
|
+
"paper.tex": CLEAN_TEX,
|
|
160
|
+
"venue.json": JSON.stringify({ venue: "agenticdev", kind: "short" }),
|
|
161
|
+
"build.sh": "exit 0\n",
|
|
162
|
+
});
|
|
163
|
+
const facts = readFacts(venued);
|
|
164
|
+
check("the venue comes from venue.json", facts.venue === "agenticdev");
|
|
165
|
+
check(
|
|
166
|
+
"a leftover build.sh is a FACT (reported), not a step",
|
|
167
|
+
JSON.stringify(facts.ignoredScripts) === JSON.stringify(["build.sh"]) &&
|
|
168
|
+
IGNORED_SCRIPTS.includes("repro/build-submission.sh"),
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
// ββ inputs: rpp's own venues directory, no configuration βββββββββββββββββββββββββββββββ
|
|
172
|
+
const venues = packageVenuesDir();
|
|
173
|
+
check(
|
|
174
|
+
"the venues directory is rpp's own and holds paper-guards.tex",
|
|
175
|
+
existsSync(join(venues, "paper-guards.tex")),
|
|
176
|
+
);
|
|
177
|
+
check(
|
|
178
|
+
"π΄ with no TEXINPUTS set, the value ENDS in the separator β otherwise the system tree stops resolving",
|
|
179
|
+
withTexInputs({}, [venues]).TEXINPUTS === `${venues}${delimiter}`,
|
|
180
|
+
);
|
|
181
|
+
check(
|
|
182
|
+
"an existing TEXINPUTS is kept, after ours",
|
|
183
|
+
withTexInputs({ TEXINPUTS: `/mine${delimiter}` }, [venues]).TEXINPUTS ===
|
|
184
|
+
`${venues}${delimiter}/mine${delimiter}`,
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
// ββ the plan ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
188
|
+
const plan = planFor(facts);
|
|
189
|
+
check(
|
|
190
|
+
"the plan has one line per step, in order, each with a why",
|
|
191
|
+
plan.map((p) => p.step).join(",") === STEPS.map((s) => s.name).join(",") &&
|
|
192
|
+
plan.every((p) => p.why.length > 0),
|
|
193
|
+
);
|
|
194
|
+
check(
|
|
195
|
+
"compile's why names the source, the class and the venue",
|
|
196
|
+
/paper\.tex/.test(plan[1].why) &&
|
|
197
|
+
/article/.test(plan[1].why) &&
|
|
198
|
+
/agenticdev/.test(plan[1].why),
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// ββ pdflatex arguments ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
202
|
+
check(
|
|
203
|
+
"every pass is nonstop, halts on error, and reports file:line",
|
|
204
|
+
["-interaction=nonstopmode", "-halt-on-error", "-file-line-error"].every(
|
|
205
|
+
(f) => pdflatexArgs(false).includes(f),
|
|
206
|
+
),
|
|
207
|
+
);
|
|
208
|
+
check(
|
|
209
|
+
"the FINAL pass defines \\finalpass (the paper-guards switch) and keeps the job name",
|
|
210
|
+
pdflatexArgs(true).includes("-jobname=paper") &&
|
|
211
|
+
pdflatexArgs(true).at(-1) === "\\def\\finalpass{}\\input{paper.tex}",
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
// ββ a clean build, with leftover scripts that must NOT run ββββββββββββββββββββββββββββ
|
|
215
|
+
const clean = paper("clean", {
|
|
216
|
+
"paper.tex": CLEAN_TEX,
|
|
217
|
+
"build.sh": '#!/usr/bin/env bash\ntouch "$(dirname "$0")/RAN"\nexit 1\n',
|
|
218
|
+
"repro/build-submission.sh":
|
|
219
|
+
'#!/usr/bin/env bash\ntouch "$(dirname "$0")/../RAN2"\n',
|
|
220
|
+
});
|
|
221
|
+
const events = [];
|
|
222
|
+
const tex = fakeTex();
|
|
223
|
+
const r = await buildPaper(clean, {
|
|
224
|
+
cwd: root,
|
|
225
|
+
readPdf: fakeRead,
|
|
226
|
+
run: (...a) => {
|
|
227
|
+
events.push("run");
|
|
228
|
+
return tex.run(...a);
|
|
229
|
+
},
|
|
230
|
+
env: ENV,
|
|
231
|
+
log: (l) => events.push(l),
|
|
232
|
+
});
|
|
233
|
+
check("a clean paper builds", r.status === "built");
|
|
234
|
+
check(
|
|
235
|
+
"π΄ the paper's build scripts were NOT executed β no trace on disk",
|
|
236
|
+
!existsSync(join(clean, "RAN")) && !existsSync(join(clean, "RAN2")),
|
|
237
|
+
);
|
|
238
|
+
check(
|
|
239
|
+
"π΄ and nothing but pdflatex/bibtex was started",
|
|
240
|
+
tex.calls.every((c) => c.bin === "pdflatex" || c.bin === "bibtex"),
|
|
241
|
+
);
|
|
242
|
+
check(
|
|
243
|
+
"one line per ignored script tells the author it is ignored",
|
|
244
|
+
events.filter(
|
|
245
|
+
(e) =>
|
|
246
|
+
e !== "run" && /is ignored β paperlint builds the paper itself/.test(e),
|
|
247
|
+
).length === 2,
|
|
248
|
+
);
|
|
249
|
+
check(
|
|
250
|
+
"the plan is printed BEFORE anything runs",
|
|
251
|
+
events.indexOf("run") >
|
|
252
|
+
events.findIndex((e) => e !== "run" && e.startsWith(" compile:")),
|
|
253
|
+
);
|
|
254
|
+
check(
|
|
255
|
+
"a stable aux: pass, pass, final pass β three pdflatex runs, the last one final",
|
|
256
|
+
tex.calls.length === 3 &&
|
|
257
|
+
tex.calls.at(-1).args.at(-1).includes("\\finalpass"),
|
|
258
|
+
);
|
|
259
|
+
check(
|
|
260
|
+
"pdflatex runs IN the paper directory",
|
|
261
|
+
tex.calls.every((c) => c.cwd === clean),
|
|
262
|
+
);
|
|
263
|
+
check(
|
|
264
|
+
"π΄ pdflatex got rpp's venues directory on TEXINPUTS, trailing separator kept",
|
|
265
|
+
tex.calls.every((c) => c.texinputs === `${venues}${delimiter}`),
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
// ββ the measure step: facts for the lint rules ββββββββββββββββββββββββββββββββββββββββ
|
|
269
|
+
const factsFile = join(clean, "_build", "paper.facts.json");
|
|
270
|
+
check(
|
|
271
|
+
"measure: the plan's last step writes the facts, and says it judges nothing",
|
|
272
|
+
r.plan.at(-1)?.step === "measure" &&
|
|
273
|
+
/pdf\.js β _build\/paper\.facts\.json/.test(r.plan.at(-1)?.why ?? "") &&
|
|
274
|
+
/nothing is judged/.test(r.plan.at(-1)?.why ?? ""),
|
|
275
|
+
);
|
|
276
|
+
check(
|
|
277
|
+
"π΄ measure: a green build wrote _build/paper.facts.json through the readPdf port, on paper.pdf",
|
|
278
|
+
existsSync(factsFile) && reads.includes(join(clean, "paper.pdf")),
|
|
279
|
+
);
|
|
280
|
+
if (existsSync(factsFile)) {
|
|
281
|
+
const f = JSON.parse(readFileSync(factsFile, "utf8"));
|
|
282
|
+
check(
|
|
283
|
+
"measure: the facts are schema 2, about paper.pdf, with the last page's heights",
|
|
284
|
+
f.schema === 2 &&
|
|
285
|
+
f.pdf === "paper.pdf" &&
|
|
286
|
+
JSON.stringify(f.last_page_cols_pt) === "[400,400]",
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
check(
|
|
290
|
+
"measure: the result line reports the facts file and the heights β a measurement, no verdict",
|
|
291
|
+
(r.notes ?? []).some(
|
|
292
|
+
(x) =>
|
|
293
|
+
x.includes("facts: _build/paper.facts.json") &&
|
|
294
|
+
x.includes("last page 400.0 / 400.0 pt"),
|
|
295
|
+
),
|
|
296
|
+
);
|
|
297
|
+
const unread = paper("unreadable-pdf", { "paper.tex": CLEAN_TEX });
|
|
298
|
+
const ur = await buildPaper(unread, {
|
|
299
|
+
cwd: root,
|
|
300
|
+
readPdf: async () => ({
|
|
301
|
+
ok: false,
|
|
302
|
+
reason: "unreadable",
|
|
303
|
+
detail: "InvalidPDFException: Invalid PDF structure.",
|
|
304
|
+
}),
|
|
305
|
+
run: fakeTex().run,
|
|
306
|
+
env: ENV,
|
|
307
|
+
log: quiet,
|
|
308
|
+
});
|
|
309
|
+
check(
|
|
310
|
+
"π΄ measure: a PDF pdf.js cannot read FAILS the build at `measure`, and no facts are written",
|
|
311
|
+
ur.status === "failed" &&
|
|
312
|
+
ur.failure?.step === "measure" &&
|
|
313
|
+
/could not read .*pdf\.js \(unreadable\)/.test(ur.failure.lines[0]) &&
|
|
314
|
+
!existsSync(join(unread, "_build", "paper.facts.json")),
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
// ββ the bibtex path βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
318
|
+
const cited = paper("cited", {
|
|
319
|
+
"paper.tex": CLEAN_TEX,
|
|
320
|
+
"refs.bib": "@misc{k, title={T}}",
|
|
321
|
+
});
|
|
322
|
+
const btex = fakeTex({
|
|
323
|
+
aux: "\\relax\n\\citation{k}\n\\bibstyle{plain}\n\\bibdata{refs}\n",
|
|
324
|
+
});
|
|
325
|
+
const rb = await buildPaper(cited, {
|
|
326
|
+
cwd: root,
|
|
327
|
+
readPdf: fakeRead,
|
|
328
|
+
run: btex.run,
|
|
329
|
+
env: ENV,
|
|
330
|
+
log: quiet,
|
|
331
|
+
});
|
|
332
|
+
check("a paper with a \\cite builds", rb.status === "built");
|
|
333
|
+
const bibCalls = btex.calls.filter((c) => c.bin === "bibtex");
|
|
334
|
+
check(
|
|
335
|
+
"bibtex ran exactly once, on the job name",
|
|
336
|
+
bibCalls.length === 1 && bibCalls[0].args.join(" ") === "paper",
|
|
337
|
+
);
|
|
338
|
+
check(
|
|
339
|
+
"β¦between two pdflatex passes, and the build still ends on the final pass",
|
|
340
|
+
btex.calls[0].bin === "pdflatex" &&
|
|
341
|
+
btex.calls[1].bin === "bibtex" &&
|
|
342
|
+
btex.calls.at(-1).args.at(-1).includes("\\finalpass"),
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
// ββ a failing build: the error, its context, and NO stale PDF βββββββββββββββββββββββββ
|
|
346
|
+
const broken = paper("broken", {
|
|
347
|
+
"paper.tex": CLEAN_TEX,
|
|
348
|
+
"paper.pdf": "%PDF-stale-from-yesterday",
|
|
349
|
+
});
|
|
350
|
+
const ftex = fakeTex({
|
|
351
|
+
exitCode: 1,
|
|
352
|
+
log: [
|
|
353
|
+
"(./paper.tex",
|
|
354
|
+
"./paper.tex:4: Undefined control sequence.",
|
|
355
|
+
"l.4 \\foo",
|
|
356
|
+
" bar baz ",
|
|
357
|
+
"Here is how much of TeX's memory you used:",
|
|
358
|
+
].join("\n"),
|
|
359
|
+
});
|
|
360
|
+
const rf = await buildPaper(broken, {
|
|
361
|
+
cwd: root,
|
|
362
|
+
readPdf: fakeRead,
|
|
363
|
+
run: ftex.run,
|
|
364
|
+
env: ENV,
|
|
365
|
+
log: quiet,
|
|
366
|
+
});
|
|
367
|
+
check(
|
|
368
|
+
"a pdflatex error fails the build",
|
|
369
|
+
rf.status === "failed" && anyFailed([rf]),
|
|
370
|
+
);
|
|
371
|
+
check(
|
|
372
|
+
"π΄ the stale paper.pdf is DELETED β a red build cannot leave a PDF that looks current",
|
|
373
|
+
!existsSync(join(broken, "paper.pdf")),
|
|
374
|
+
);
|
|
375
|
+
const shown = formatResult(rf);
|
|
376
|
+
check(
|
|
377
|
+
"the failure names the step and the program with its exit code",
|
|
378
|
+
shown.includes("β compile: pdflatex exited with 1"),
|
|
379
|
+
);
|
|
380
|
+
check(
|
|
381
|
+
"β¦quotes the first error line and the l.NNN context from the log",
|
|
382
|
+
shown.includes("./paper.tex:4: Undefined control sequence.") &&
|
|
383
|
+
shown.includes("l.4 \\foo") &&
|
|
384
|
+
shown.includes("bar baz"),
|
|
385
|
+
);
|
|
386
|
+
check("β¦and says the PDF was removed", /paper\.pdf removed/.test(shown));
|
|
387
|
+
check("a failed pass is not retried", ftex.calls.length === 1);
|
|
388
|
+
|
|
389
|
+
// ββ pdflatex not installed ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
390
|
+
const rm = await buildPaper(clean, {
|
|
391
|
+
cwd: root,
|
|
392
|
+
readPdf: fakeRead,
|
|
393
|
+
run: fakeTex({ missing: true }).run,
|
|
394
|
+
env: ENV,
|
|
395
|
+
log: quiet,
|
|
396
|
+
});
|
|
397
|
+
check(
|
|
398
|
+
"a missing pdflatex is a FAILURE that says what is missing, not a crash",
|
|
399
|
+
rm.status === "failed" &&
|
|
400
|
+
/pdflatex could not be started/.test(rm.failure.lines.join("\n")),
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
// ββ π΄ --dry-run runs NOTHING, checked by effect ββββββββββββββββββββββββββββββββββββββ
|
|
404
|
+
const dryDir = paper("dry", {
|
|
405
|
+
"paper.tex": CLEAN_TEX,
|
|
406
|
+
"paper.pdf": "%PDF-untouched",
|
|
407
|
+
});
|
|
408
|
+
const dtex = fakeTex();
|
|
409
|
+
const dryLog = [];
|
|
410
|
+
const dry = await buildPaper(dryDir, {
|
|
411
|
+
cwd: root,
|
|
412
|
+
readPdf: fakeRead,
|
|
413
|
+
run: dtex.run,
|
|
414
|
+
env: ENV,
|
|
415
|
+
dryRun: true,
|
|
416
|
+
log: (l) => dryLog.push(l),
|
|
417
|
+
});
|
|
418
|
+
check("--dry-run: nothing was started", dtex.calls.length === 0);
|
|
419
|
+
check(
|
|
420
|
+
"--dry-run: the PDF on disk is untouched",
|
|
421
|
+
readFileSync(join(dryDir, "paper.pdf"), "utf8") === "%PDF-untouched",
|
|
422
|
+
);
|
|
423
|
+
check(
|
|
424
|
+
"--dry-run: nothing was measured and no facts were written",
|
|
425
|
+
!reads.includes(join(dryDir, "paper.pdf")) &&
|
|
426
|
+
!existsSync(join(dryDir, "_build")),
|
|
427
|
+
);
|
|
428
|
+
check(
|
|
429
|
+
"--dry-run: the plan is printed, and the result says it did not run",
|
|
430
|
+
dryLog.some((l) => l.startsWith(" compile: paper.tex")) &&
|
|
431
|
+
dry.dry === true &&
|
|
432
|
+
/--dry-run/.test(formatResult(dry)),
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
// ββ no paper.tex: a REFUSAL, not a skip βββββββββββββββββββββββββββββββββββββββββββββββ
|
|
436
|
+
const bare = paper("bare", { "paper.md": "# x" });
|
|
437
|
+
const ntex = fakeTex();
|
|
438
|
+
const nr = await buildPaper(bare, {
|
|
439
|
+
cwd: root,
|
|
440
|
+
readPdf: fakeRead,
|
|
441
|
+
run: ntex.run,
|
|
442
|
+
env: ENV,
|
|
443
|
+
log: quiet,
|
|
444
|
+
});
|
|
445
|
+
check("no paper.tex β status no-source", nr.status === "no-source");
|
|
446
|
+
check("π΄ and this COUNTS AS A FAILURE", anyFailed([nr]) === true);
|
|
447
|
+
check("nothing was run for it", ntex.calls.length === 0);
|
|
448
|
+
const compileLine = nr.plan.find((p) => p.step === "compile");
|
|
449
|
+
check(
|
|
450
|
+
"the compile step is required and did not apply",
|
|
451
|
+
compileLine?.applies === false && compileLine?.required === true,
|
|
452
|
+
);
|
|
453
|
+
check(
|
|
454
|
+
'the remedy says this is NOT "nothing to build" and names paper.tex',
|
|
455
|
+
/NOT "nothing to build"/.test(remedyFor([nr])) &&
|
|
456
|
+
/paper\.tex/.test(remedyFor([nr])),
|
|
457
|
+
);
|
|
458
|
+
check("on full success there is no remedy", remedyFor([r]) === "");
|
|
459
|
+
|
|
460
|
+
// ββ π΄ A STALE PDF NEVER SURVIVES A RUN THAT DID NOT REPLACE IT β every path of buildPapers ββ
|
|
461
|
+
// The order matters for the battery: the up-front removal is killed by the no-engine case, the
|
|
462
|
+
// existence check by the empty document, and neither case depends on the other defence.
|
|
463
|
+
const REMOVED =
|
|
464
|
+
"paper.pdf removed β a stale PDF must not pass for this build";
|
|
465
|
+
|
|
466
|
+
// (b) no qualifying engine: the command stops before any paper is built.
|
|
467
|
+
const noEng = paper("no-engine", {
|
|
468
|
+
"paper.tex": CLEAN_TEX,
|
|
469
|
+
"paper.pdf": "%PDF-stale-from-yesterday",
|
|
470
|
+
});
|
|
471
|
+
const neTex = fakeTex();
|
|
472
|
+
const neLog = [];
|
|
473
|
+
let pdfWhenEngineAsked = null;
|
|
474
|
+
const ne = await buildPapers([noEng], {
|
|
475
|
+
cwd: root,
|
|
476
|
+
readPdf: fakeRead,
|
|
477
|
+
run: neTex.run,
|
|
478
|
+
env: ENV,
|
|
479
|
+
log: (l) => neLog.push(l),
|
|
480
|
+
engine: async () => {
|
|
481
|
+
pdfWhenEngineAsked = existsSync(join(noEng, "paper.pdf"));
|
|
482
|
+
return null;
|
|
483
|
+
},
|
|
484
|
+
});
|
|
485
|
+
check(
|
|
486
|
+
"π΄ no engine: the stale paper.pdf is removed BEFORE the engine is resolved",
|
|
487
|
+
pdfWhenEngineAsked === false && !existsSync(join(noEng, "paper.pdf")),
|
|
488
|
+
);
|
|
489
|
+
check(
|
|
490
|
+
"no engine: the run stops β nothing compiled, no results",
|
|
491
|
+
ne.kind === "no-engine" && neTex.calls.length === 0,
|
|
492
|
+
);
|
|
493
|
+
check(
|
|
494
|
+
"no engine: the run says so, with the line a failed build prints",
|
|
495
|
+
neLog.includes(`papers/no-engine: ${REMOVED}`),
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
// (c) no paper.tex: refused, and a PDF left from some earlier build goes too.
|
|
499
|
+
const bareStale = paper("bare-stale", {
|
|
500
|
+
"paper.md": "# x",
|
|
501
|
+
"paper.pdf": "%PDF-stale-from-yesterday",
|
|
502
|
+
});
|
|
503
|
+
const bsTex = fakeTex();
|
|
504
|
+
const bsLog = [];
|
|
505
|
+
const bs = await buildPapers([bareStale, bare], {
|
|
506
|
+
cwd: root,
|
|
507
|
+
readPdf: fakeRead,
|
|
508
|
+
run: bsTex.run,
|
|
509
|
+
env: ENV,
|
|
510
|
+
log: (l) => bsLog.push(l),
|
|
511
|
+
engine: async () => ENV,
|
|
512
|
+
});
|
|
513
|
+
check(
|
|
514
|
+
"π΄ no-source: the stale paper.pdf is GONE",
|
|
515
|
+
bs.kind === "ran" &&
|
|
516
|
+
bs.results[0].status === "no-source" &&
|
|
517
|
+
!existsSync(join(bareStale, "paper.pdf")),
|
|
518
|
+
);
|
|
519
|
+
check(
|
|
520
|
+
"no-source: the refusal says the PDF was removedβ¦",
|
|
521
|
+
bsLog.includes(` β nothing to compile: no paper.tex\n ${REMOVED}`),
|
|
522
|
+
);
|
|
523
|
+
check(
|
|
524
|
+
"β¦and a paper that had no PDF is not told one was removed",
|
|
525
|
+
bsLog.includes(" β nothing to compile: no paper.tex") &&
|
|
526
|
+
bs.results[1].staleRemoved === false,
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
// (d) --dry-run: no side effect, including this one.
|
|
530
|
+
const dryStale = paper("dry-stale", {
|
|
531
|
+
"paper.tex": CLEAN_TEX,
|
|
532
|
+
"paper.pdf": "%PDF-untouched",
|
|
533
|
+
});
|
|
534
|
+
const dsTex = fakeTex();
|
|
535
|
+
const ds = await buildPapers([dryStale], {
|
|
536
|
+
cwd: root,
|
|
537
|
+
readPdf: fakeRead,
|
|
538
|
+
run: dsTex.run,
|
|
539
|
+
env: ENV,
|
|
540
|
+
dryRun: true,
|
|
541
|
+
log: quiet,
|
|
542
|
+
engine: async () => ENV,
|
|
543
|
+
});
|
|
544
|
+
// Read only if present: a deleted file must fail THIS assertion, not throw ENOENT before it.
|
|
545
|
+
const bodyOf = (p) => (existsSync(p) ? readFileSync(p, "utf8") : null);
|
|
546
|
+
check(
|
|
547
|
+
"π΄ buildPapers --dry-run: the PDF on disk is untouched, and nothing ran",
|
|
548
|
+
bodyOf(join(dryStale, "paper.pdf")) === "%PDF-untouched" &&
|
|
549
|
+
dsTex.calls.length === 0 &&
|
|
550
|
+
ds.kind === "ran" &&
|
|
551
|
+
ds.results[0].dry === true,
|
|
552
|
+
);
|
|
553
|
+
|
|
554
|
+
// A success replaces the stale PDF and says nothing about removing it.
|
|
555
|
+
const fresh = paper("fresh", {
|
|
556
|
+
"paper.tex": CLEAN_TEX,
|
|
557
|
+
"paper.pdf": "%PDF-stale-from-yesterday",
|
|
558
|
+
});
|
|
559
|
+
const frLog = [];
|
|
560
|
+
const fr = await buildPapers([fresh], {
|
|
561
|
+
cwd: root,
|
|
562
|
+
readPdf: fakeRead,
|
|
563
|
+
run: fakeTex().run,
|
|
564
|
+
env: ENV,
|
|
565
|
+
log: (l) => frLog.push(l),
|
|
566
|
+
engine: async () => ENV,
|
|
567
|
+
});
|
|
568
|
+
check(
|
|
569
|
+
"a success: the PDF on disk is the one this run wrote, and nothing says 'removed'",
|
|
570
|
+
fr.kind === "ran" &&
|
|
571
|
+
fr.results[0].status === "built" &&
|
|
572
|
+
bodyOf(join(fresh, "paper.pdf")) === "%PDF-fake" &&
|
|
573
|
+
!frLog.some((l) => l.includes("removed")),
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
// (a) pdflatex exits 0 and writes no PDF β an empty document.
|
|
577
|
+
const empty = paper("empty", {
|
|
578
|
+
"paper.tex": "\\documentclass{article}\\begin{document}\\end{document}",
|
|
579
|
+
});
|
|
580
|
+
const emLog = [];
|
|
581
|
+
const em = await buildPapers([empty], {
|
|
582
|
+
cwd: root,
|
|
583
|
+
readPdf: fakeRead,
|
|
584
|
+
run: fakeTex({ noPdf: true }).run,
|
|
585
|
+
env: ENV,
|
|
586
|
+
log: (l) => emLog.push(l),
|
|
587
|
+
engine: async () => ENV,
|
|
588
|
+
});
|
|
589
|
+
check(
|
|
590
|
+
"π΄ empty document: pdflatex exit 0 with no PDF FAILS the build",
|
|
591
|
+
em.kind === "ran" &&
|
|
592
|
+
em.results[0].status === "failed" &&
|
|
593
|
+
em.results[0].failure.step === "compile" &&
|
|
594
|
+
anyFailed(em.results),
|
|
595
|
+
);
|
|
596
|
+
const emShown = emLog.join("\n");
|
|
597
|
+
check(
|
|
598
|
+
"empty document: no β, and the failure says what happened and asks the question",
|
|
599
|
+
!emShown.includes("β") &&
|
|
600
|
+
emShown.includes(
|
|
601
|
+
"β compile: pdflatex exited 0 but wrote no paper.pdf β does the document have any pages?",
|
|
602
|
+
),
|
|
603
|
+
);
|
|
604
|
+
|
|
605
|
+
// ββ walking the corpus ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
606
|
+
paper("not-a-paper", { "NOTES.md": "x" });
|
|
607
|
+
paper(".hidden-paper", { "PIPELINE-STATUS.md": "x" });
|
|
608
|
+
const found = papersIn(join(root, "papers")).map((d) => d.split("/").pop());
|
|
609
|
+
check(
|
|
610
|
+
"a directory with no markers is not a paper",
|
|
611
|
+
!found.includes("not-a-paper"),
|
|
612
|
+
);
|
|
613
|
+
check(
|
|
614
|
+
"the real ones are",
|
|
615
|
+
["clean", "broken", "bare"].every((x) => found.includes(x)),
|
|
616
|
+
);
|
|
617
|
+
check(
|
|
618
|
+
"a hidden directory is not a paper, even with a marker",
|
|
619
|
+
!found.includes(".hidden-paper"),
|
|
620
|
+
);
|
|
621
|
+
check(
|
|
622
|
+
"a nonexistent root does not crash",
|
|
623
|
+
papersIn(join(root, "nope")).length === 0,
|
|
624
|
+
);
|
|
625
|
+
} finally {
|
|
626
|
+
rmSync(root, { recursive: true, force: true });
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
console.log(
|
|
630
|
+
`β ${String(n)} assertions passed β build: paperlint compiles, build.sh never runs, a red build leaves no PDF`,
|
|
631
|
+
);
|