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,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHAT A PDF'S TEXT AND FONTS SAY, once pdf.js has read them — PURE. No disk, no pdf.js.
|
|
3
|
+
*
|
|
4
|
+
* `pdf-facts.ts` is the shell: it opens the PDF with pdf.js (the `unpdf` package) and hands this
|
|
5
|
+
* module plain data — text runs with their metrics, and the fonts each page resolved. Everything
|
|
6
|
+
* that DECIDES something is here, so every branch is a row in a table test fed with hand-written
|
|
7
|
+
* words, including pages the fixture corpus does not contain.
|
|
8
|
+
*
|
|
9
|
+
* ── WHY pdf.js AND NOT POPPLER (issue #61) ───────────────────────────────────
|
|
10
|
+
* The facts used to come from three poppler programs (`pdfinfo`, `pdffonts`, `pdftotext -bbox`), a
|
|
11
|
+
* system install paperlint could not provide and every consumer had to add by hand. A measurement on 25
|
|
12
|
+
* PDFs (2026-09-24) found pdf.js equal on page count and on the Type 3 count of every PDF, and the
|
|
13
|
+
* last page's column heights within 0.2 pt except on an all-Type-3 page (7.2 pt; pdf.js has no
|
|
14
|
+
* ascent for Type 3). No verdict flipped at a 120 pt tolerance.
|
|
15
|
+
*
|
|
16
|
+
* ── THE LAST PAGE IS A MEASUREMENT, NOT A VERDICT ────────────────────────────
|
|
17
|
+
* `classifyLastPage` returns the heights of the two columns, or says why there is nothing to
|
|
18
|
+
* measure. It carries no threshold: whether a venue requires balance, and how much difference it
|
|
19
|
+
* allows, belongs to the lint rule `pdf/last-page-balance`, which a consumer turns on per venue.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** One word on a page, in points, top-down: `y0` is the top edge, `y1` the bottom. */
|
|
23
|
+
export interface Word {
|
|
24
|
+
readonly x0: number;
|
|
25
|
+
readonly y0: number;
|
|
26
|
+
readonly x1: number;
|
|
27
|
+
readonly y1: number;
|
|
28
|
+
readonly text: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A page's size and its words. */
|
|
32
|
+
export interface PageText {
|
|
33
|
+
readonly widthPt: number;
|
|
34
|
+
readonly heightPt: number;
|
|
35
|
+
readonly words: readonly Word[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The heights of the last page's two columns, in points: [left, right]. */
|
|
39
|
+
export type Columns = readonly [left: number, right: number];
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* What the last page is, as far as balance goes.
|
|
43
|
+
*
|
|
44
|
+
* - `measured` — the heights of both columns.
|
|
45
|
+
* - `stub` — fewer than `STUB_WORDS` words: a tail of a few lines is not a layout.
|
|
46
|
+
* - `review` — numbered lines in the outer margins. The numbers run down the whole page, so every
|
|
47
|
+
* column would measure full height; a review build is not judged at all.
|
|
48
|
+
*/
|
|
49
|
+
export type LastPage =
|
|
50
|
+
| { readonly kind: "measured"; readonly columns: Columns }
|
|
51
|
+
| { readonly kind: "stub"; readonly words: number }
|
|
52
|
+
| { readonly kind: "review"; readonly lineNumbers: number };
|
|
53
|
+
|
|
54
|
+
/** Below this many words the last page is a stub. */
|
|
55
|
+
export const STUB_WORDS = 60;
|
|
56
|
+
/** This many short integers in the outer margins make a review build. */
|
|
57
|
+
export const REVIEW_LINE_NUMBERS = 5;
|
|
58
|
+
/** The outer margins, as a fraction of the page width, where line numbers sit. */
|
|
59
|
+
export const MARGIN_FRACTION = 0.08;
|
|
60
|
+
|
|
61
|
+
const round1 = (n: number): number => Number(n.toFixed(1));
|
|
62
|
+
|
|
63
|
+
/** Top of the highest word to bottom of the lowest one; 0 for no words. */
|
|
64
|
+
function heightOf(words: readonly Word[]): number {
|
|
65
|
+
if (words.length === 0) return 0;
|
|
66
|
+
const top = Math.min(...words.map((w) => w.y0));
|
|
67
|
+
const bottom = Math.max(...words.map((w) => w.y1));
|
|
68
|
+
return round1(bottom - top);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** A short integer standing in the left or right margin: a line number of a review build. */
|
|
72
|
+
function isLineNumber(w: Word, widthPt: number): boolean {
|
|
73
|
+
const margin = widthPt * MARGIN_FRACTION;
|
|
74
|
+
return (
|
|
75
|
+
/^\d{1,4}$/.test(w.text.trim()) &&
|
|
76
|
+
(w.x1 < margin || w.x0 > widthPt - margin)
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Classify the last page and, when it has a layout, measure its two columns.
|
|
82
|
+
*
|
|
83
|
+
* 🔴 THE PAGE IS SPLIT AT ITS OWN MIDDLE, not between the outermost words: on a page where only the
|
|
84
|
+
* left column is filled, the words' midpoint falls inside that column and cuts it in two ("77 / 731"
|
|
85
|
+
* for six lines at the top left, measured 2026-08-29). A word belongs to the side its left edge is on.
|
|
86
|
+
*
|
|
87
|
+
* Throws on a page with no width — the caller built `PageText` from the PDF's own viewport, so a
|
|
88
|
+
* non-positive width is a defect in the caller, not a property of a paper.
|
|
89
|
+
*/
|
|
90
|
+
export function classifyLastPage(p: PageText): LastPage {
|
|
91
|
+
if (!(p.widthPt > 0))
|
|
92
|
+
throw new RangeError(`page width must be positive, got ${p.widthPt}`);
|
|
93
|
+
if (p.words.length < STUB_WORDS)
|
|
94
|
+
return { kind: "stub", words: p.words.length };
|
|
95
|
+
const lineNumbers = p.words.filter((w) => isLineNumber(w, p.widthPt)).length;
|
|
96
|
+
if (lineNumbers >= REVIEW_LINE_NUMBERS)
|
|
97
|
+
return { kind: "review", lineNumbers };
|
|
98
|
+
const mid = p.widthPt / 2;
|
|
99
|
+
return {
|
|
100
|
+
kind: "measured",
|
|
101
|
+
columns: [
|
|
102
|
+
heightOf(p.words.filter((w) => w.x0 < mid)),
|
|
103
|
+
heightOf(p.words.filter((w) => w.x0 >= mid)),
|
|
104
|
+
],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ── words from text runs ────────────────────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* One text run as pdf.js reports it, already in top-down page coordinates: where its baseline
|
|
112
|
+
* starts, how wide it is, its font size, and the font's ascent and descent as fractions of the size
|
|
113
|
+
* (descent negative, as fonts store it).
|
|
114
|
+
*/
|
|
115
|
+
export interface TextRun {
|
|
116
|
+
readonly text: string;
|
|
117
|
+
readonly x: number;
|
|
118
|
+
readonly baseline: number;
|
|
119
|
+
readonly width: number;
|
|
120
|
+
readonly size: number;
|
|
121
|
+
readonly ascent: number;
|
|
122
|
+
readonly descent: number;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** A font's vertical metrics as fractions of its size, when pdf.js supplies none. */
|
|
126
|
+
export const DEFAULT_ASCENT = 0.8;
|
|
127
|
+
export const DEFAULT_DESCENT = -0.2;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Split a run into words. pdf.js gives a run's total width, not each glyph's, so a word's horizontal
|
|
131
|
+
* extent is estimated in proportion to its characters. Only `x0` decides the column a word falls
|
|
132
|
+
* in, and a run never crosses the gutter, so the estimate cannot move a word to the other side.
|
|
133
|
+
*/
|
|
134
|
+
export function wordsOf(run: TextRun): Word[] {
|
|
135
|
+
const perChar = run.width / Math.max(1, run.text.length);
|
|
136
|
+
const y0 = run.baseline - run.ascent * run.size;
|
|
137
|
+
const y1 = run.baseline - run.descent * run.size;
|
|
138
|
+
return [...run.text.matchAll(/\S+/g)].map((m) => {
|
|
139
|
+
const x0 = run.x + (m.index ?? 0) * perChar;
|
|
140
|
+
return { x0, y0, x1: x0 + m[0].length * perChar, y1, text: m[0] };
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ── fonts ───────────────────────────────────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
/** The font programs a PDF can carry, in pdf.js's vocabulary. */
|
|
147
|
+
export type FontProgram =
|
|
148
|
+
| "Type1"
|
|
149
|
+
| "Type1C"
|
|
150
|
+
| "MMType1"
|
|
151
|
+
| "TrueType"
|
|
152
|
+
| "OpenType"
|
|
153
|
+
| "CIDType0"
|
|
154
|
+
| "CIDType0C"
|
|
155
|
+
| "CIDTrueType"
|
|
156
|
+
| "Type3";
|
|
157
|
+
|
|
158
|
+
/** A program pdf.js named but this module does not know — kept verbatim, never guessed. */
|
|
159
|
+
export type UnknownProgram = `unknown:${string}`;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* One font the PDF draws text with.
|
|
163
|
+
*
|
|
164
|
+
* - `embedded` — a usable font program is in the file.
|
|
165
|
+
* - `type3` — a Type 3 font: glyphs drawn as PDF procedures, usually bitmaps. pdf.js gives
|
|
166
|
+
* every Type 3 font the same name, `Type3`, so the name carries nothing.
|
|
167
|
+
* - `not-embedded` — the PDF names the font but carries no usable program for it: either none at
|
|
168
|
+
* all, or one pdf.js could not parse. pdf.js reports both as one flag
|
|
169
|
+
* (`missingFile`), so the two are not told apart here.
|
|
170
|
+
*/
|
|
171
|
+
export type FontFact =
|
|
172
|
+
| {
|
|
173
|
+
readonly kind: "embedded";
|
|
174
|
+
readonly name: string;
|
|
175
|
+
readonly program: Exclude<FontProgram, "Type3"> | UnknownProgram;
|
|
176
|
+
}
|
|
177
|
+
| { readonly kind: "type3"; readonly name: "Type3" }
|
|
178
|
+
| {
|
|
179
|
+
readonly kind: "not-embedded";
|
|
180
|
+
readonly name: string;
|
|
181
|
+
readonly program: Exclude<FontProgram, "Type3"> | UnknownProgram;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/** A font as pdf.js exports it with `fontExtraProperties`, reduced to what is read here. */
|
|
185
|
+
export interface RawFont {
|
|
186
|
+
/** pdf.js's id for the font within the document (`loadedName`). */
|
|
187
|
+
readonly id: string;
|
|
188
|
+
readonly name: string;
|
|
189
|
+
readonly type: string | undefined;
|
|
190
|
+
readonly subtype: string | undefined;
|
|
191
|
+
readonly isType3Font: boolean;
|
|
192
|
+
readonly missingFile: boolean;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** One page: whether it draws any text, and the fonts its text resolved to. */
|
|
196
|
+
export interface RawPage {
|
|
197
|
+
readonly hasText: boolean;
|
|
198
|
+
readonly fonts: readonly RawFont[];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* The fonts of a document. `drawn` is non-empty BY TYPE: a document whose pages draw text has at
|
|
203
|
+
* least one font, and "no fonts" is spelled only as `no-text`.
|
|
204
|
+
*/
|
|
205
|
+
export type Fonts =
|
|
206
|
+
| {
|
|
207
|
+
readonly kind: "drawn";
|
|
208
|
+
readonly list: readonly [FontFact, ...FontFact[]];
|
|
209
|
+
}
|
|
210
|
+
| { readonly kind: "no-text" };
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* 🔴 TEXT ON A PAGE WITH NO FONT BEHIND IT IS A FAILED READ, NOT AN EMPTY LIST. Measured on Node
|
|
214
|
+
* 20.20.2: pdf.js opens the same PDFs and returns ZERO fonts with no error. An empty list there
|
|
215
|
+
* would read downstream as "no Type 3, nothing unembedded" — a clean verdict over nothing.
|
|
216
|
+
*/
|
|
217
|
+
export interface ZeroFontsOnText {
|
|
218
|
+
readonly kind: "zero-fonts-on-text";
|
|
219
|
+
/** 1-based numbers of the pages that draw text with no font resolved. */
|
|
220
|
+
readonly pages: readonly number[];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* pdf.js's `type`/`subtype` pair → the program, keyed `type/subtype`. Composite fonts carry their
|
|
225
|
+
* descendant's type. A pair not listed is kept verbatim as `unknown:…`, never guessed.
|
|
226
|
+
*/
|
|
227
|
+
const PROGRAMS: Readonly<Record<string, Exclude<FontProgram, "Type3">>> = {
|
|
228
|
+
"Type1/undefined": "Type1",
|
|
229
|
+
"Type1/Type1C": "Type1C",
|
|
230
|
+
"MMType1/undefined": "MMType1",
|
|
231
|
+
"TrueType/undefined": "TrueType",
|
|
232
|
+
"CIDFontType0/undefined": "CIDType0",
|
|
233
|
+
"CIDFontType0/CIDFontType0C": "CIDType0C",
|
|
234
|
+
"CIDFontType2/undefined": "CIDTrueType",
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
export function programOf(
|
|
238
|
+
type: string | undefined,
|
|
239
|
+
subtype: string | undefined,
|
|
240
|
+
): Exclude<FontProgram, "Type3"> | UnknownProgram {
|
|
241
|
+
// pdf.js names an OpenType-wrapped program by the wrapper, on either field, whatever it wraps.
|
|
242
|
+
if (subtype === "OpenType" || type === "OpenType") return "OpenType";
|
|
243
|
+
const key = `${String(type)}/${String(subtype)}`;
|
|
244
|
+
return PROGRAMS[key] ?? `unknown:${key}`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Subset fonts carry a six-letter tag, `ABCDEF+Name`; the name is what follows it. */
|
|
248
|
+
const withoutSubsetTag = (name: string): string =>
|
|
249
|
+
name.replace(/^[A-Z]{6}\+/, "");
|
|
250
|
+
|
|
251
|
+
export function fontFact(f: RawFont): FontFact {
|
|
252
|
+
if (f.isType3Font) return { kind: "type3", name: "Type3" };
|
|
253
|
+
const name = withoutSubsetTag(f.name);
|
|
254
|
+
const program = programOf(f.type, f.subtype);
|
|
255
|
+
return f.missingFile
|
|
256
|
+
? { kind: "not-embedded", name, program }
|
|
257
|
+
: { kind: "embedded", name, program };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* The document's fonts, each once, in the order pages first drew them — or the pages that drew
|
|
262
|
+
* text with no font, which the caller turns into a failed read.
|
|
263
|
+
*/
|
|
264
|
+
export function fontsOf(pages: readonly RawPage[]): Fonts | ZeroFontsOnText {
|
|
265
|
+
const bad = pages.flatMap((p, i) =>
|
|
266
|
+
p.hasText && p.fonts.length === 0 ? [i + 1] : [],
|
|
267
|
+
);
|
|
268
|
+
if (bad.length > 0) return { kind: "zero-fonts-on-text", pages: bad };
|
|
269
|
+
const seen = new Map<string, FontFact>();
|
|
270
|
+
for (const p of pages)
|
|
271
|
+
for (const f of p.fonts) if (!seen.has(f.id)) seen.set(f.id, fontFact(f));
|
|
272
|
+
const [first, ...rest] = seen.values();
|
|
273
|
+
return first
|
|
274
|
+
? { kind: "drawn", list: [first, ...rest] }
|
|
275
|
+
: { kind: "no-text" };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* poppler's spelling of the font's type column (`pdffonts`), which the facts JSON has always used
|
|
280
|
+
* and consumers match on (`Type 3`).
|
|
281
|
+
*/
|
|
282
|
+
export const POPPLER_TYPE: Readonly<Record<FontProgram, string>> = {
|
|
283
|
+
Type1: "Type 1",
|
|
284
|
+
Type1C: "Type 1C",
|
|
285
|
+
MMType1: "Type 1",
|
|
286
|
+
TrueType: "TrueType",
|
|
287
|
+
OpenType: "OpenType",
|
|
288
|
+
CIDType0: "CID Type 0",
|
|
289
|
+
CIDType0C: "CID Type 0C",
|
|
290
|
+
CIDTrueType: "CID TrueType",
|
|
291
|
+
Type3: "Type 3",
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
/** The type column for any fact, the unknown ones verbatim. */
|
|
295
|
+
export function popplerType(f: FontFact): string {
|
|
296
|
+
if (f.kind === "type3") return POPPLER_TYPE.Type3;
|
|
297
|
+
return f.program.startsWith("unknown:")
|
|
298
|
+
? f.program
|
|
299
|
+
: POPPLER_TYPE[f.program as FontProgram];
|
|
300
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Fetching a URL — the generic port for the network. */
|
|
2
|
+
import type { Result } from "../domain/result.ts";
|
|
3
|
+
|
|
4
|
+
/** Fetch a URL's bytes. */
|
|
5
|
+
export interface Download {
|
|
6
|
+
fetch(
|
|
7
|
+
url: string,
|
|
8
|
+
timeoutMs: number,
|
|
9
|
+
): Result<Uint8Array, { readonly detail: string }>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Files by path — the generic port for the disk. */
|
|
2
|
+
import type { AbsolutePath } from "../domain/paths.ts";
|
|
3
|
+
|
|
4
|
+
/** Files by path. A read that fails for any reason but absence throws: that is an adapter bug. */
|
|
5
|
+
export interface Files {
|
|
6
|
+
isFile(p: AbsolutePath): boolean;
|
|
7
|
+
/** The file's bytes, or null when there is no such file. */
|
|
8
|
+
readBytes(p: AbsolutePath): Uint8Array | null;
|
|
9
|
+
/** Write through `<p>.part` and a rename, creating the directory: a reader never sees half a file. */
|
|
10
|
+
writeAtomic(p: AbsolutePath, bytes: Uint8Array): void;
|
|
11
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** The port `facts-file.ts` measures page geometry through — shaped by the need, not by a tool. */
|
|
2
|
+
import type { Geometry } from "../domain/geometry.ts";
|
|
3
|
+
import type { PageLayout } from "../domain/page-layout.ts";
|
|
4
|
+
|
|
5
|
+
/** Measure a paper's page geometry from its text boxes. Never throws: "not measured" is a value. */
|
|
6
|
+
export interface MeasureGeometry {
|
|
7
|
+
measure(pages: readonly PageLayout[]): Geometry;
|
|
8
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Running a program — the generic port an adapter that drives another program is built over (banal's,
|
|
3
|
+
* curl's). The app does not see it where a need-shaped port (`MeasureGeometry`) would do.
|
|
4
|
+
*/
|
|
5
|
+
import type { AbsolutePath } from "../domain/paths.ts";
|
|
6
|
+
|
|
7
|
+
/** A program to run, as data. `env` is the child's WHOLE environment, never merged in by the adapter. */
|
|
8
|
+
export interface Command {
|
|
9
|
+
readonly file: string;
|
|
10
|
+
readonly args: readonly string[];
|
|
11
|
+
readonly env: Readonly<Record<string, string>>;
|
|
12
|
+
readonly timeoutMs: number;
|
|
13
|
+
readonly maxOutputBytes?: number;
|
|
14
|
+
readonly cwd?: AbsolutePath;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* What running a program can come to. `not-found` is the EXECUTABLE itself (ENOENT on `file`) —
|
|
19
|
+
* perl, curl — never one of its arguments, and it names the program so a message can too.
|
|
20
|
+
*/
|
|
21
|
+
export type ProcessExit =
|
|
22
|
+
| {
|
|
23
|
+
readonly kind: "exited";
|
|
24
|
+
readonly status: number;
|
|
25
|
+
readonly stdout: string;
|
|
26
|
+
readonly stderr: string;
|
|
27
|
+
}
|
|
28
|
+
| {
|
|
29
|
+
readonly kind: "signalled";
|
|
30
|
+
readonly signal: string;
|
|
31
|
+
readonly stdout: string;
|
|
32
|
+
readonly stderr: string;
|
|
33
|
+
}
|
|
34
|
+
| {
|
|
35
|
+
readonly kind: "timed-out";
|
|
36
|
+
readonly afterMs: number;
|
|
37
|
+
readonly stdout: string;
|
|
38
|
+
readonly stderr: string;
|
|
39
|
+
}
|
|
40
|
+
| { readonly kind: "not-found"; readonly file: string }
|
|
41
|
+
| { readonly kind: "spawn-failed"; readonly message: string };
|
|
42
|
+
|
|
43
|
+
/** Run a program to completion. Synchronous, like the `spawnSync` it replaces. */
|
|
44
|
+
export interface RunProcess {
|
|
45
|
+
run(c: Command): ProcessExit;
|
|
46
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The port `paperlint toolchain` installs and checks an external program through. One port, many
|
|
3
|
+
* programs: banal implements it now, TeX Live in #76.
|
|
4
|
+
*/
|
|
5
|
+
import type { Opaque } from "ts-essentials";
|
|
6
|
+
import type { Result } from "../domain/result.ts";
|
|
7
|
+
import type { Lines } from "../domain/text.ts";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Present, verified, running. BRANDED: a literal `{ where, fresh, verified }` is not a `Ready`, so
|
|
11
|
+
* only an installer's adapter — one minting function each (banal's is `ready` in
|
|
12
|
+
* `adapters/banal/index.ts`) — can make one, and a ready line cannot be printed without it.
|
|
13
|
+
*/
|
|
14
|
+
export type Ready = Opaque<
|
|
15
|
+
{
|
|
16
|
+
/** Where it is: the program's path or its bin directory. */
|
|
17
|
+
readonly where: string;
|
|
18
|
+
/** This run installed it. */
|
|
19
|
+
readonly fresh: boolean;
|
|
20
|
+
/** What was checked before calling it ready, as a person reads it. */
|
|
21
|
+
readonly verified: string;
|
|
22
|
+
},
|
|
23
|
+
"Ready"
|
|
24
|
+
>;
|
|
25
|
+
|
|
26
|
+
export interface ToolInstaller {
|
|
27
|
+
/** The program as a person reads it: `banal 1.2 (HotCRP f3e4352)`. */
|
|
28
|
+
readonly label: string;
|
|
29
|
+
/** Make it present, verified and running; `report` is told before a slow step (a download). */
|
|
30
|
+
ensure(report: (line: string) => void): Result<Ready, Lines>;
|
|
31
|
+
/** Is it present, verified and running? The same predicate as `ensure`, changing nothing. */
|
|
32
|
+
check(): Result<Ready, Lines>;
|
|
33
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** A scratch directory with a lifetime — the generic port for temporary files. */
|
|
2
|
+
import type { AbsolutePath } from "../domain/paths.ts";
|
|
3
|
+
|
|
4
|
+
/** Rejects a Promise at the type level, so an `await` cannot escape a scope that ends synchronously. */
|
|
5
|
+
export type NotAPromise<T> = T extends PromiseLike<unknown> ? never : T;
|
|
6
|
+
|
|
7
|
+
/** A scratch directory that exists only inside `Workspace.within`. */
|
|
8
|
+
export interface Scratch {
|
|
9
|
+
readonly dir: AbsolutePath;
|
|
10
|
+
/** Write one file into `dir`; `exec` also sets its execute bit. Returns the file's path. */
|
|
11
|
+
write(name: string, content: string, mode: "read" | "exec"): AbsolutePath;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A scratch directory that cannot outlive the callback: it is created before `use` runs and removed
|
|
16
|
+
* after, whether `use` returns or throws. There is no other way to get one.
|
|
17
|
+
*/
|
|
18
|
+
export interface Workspace {
|
|
19
|
+
within<T>(prefix: string, use: (scratch: Scratch) => NotAPromise<T>): T;
|
|
20
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `rules-config.ts` — parsing the `rules` key into ESLint config blocks, and deriving which rules
|
|
3
|
+
* paperlint ships. The end-to-end path (a consumer's package.json → `paperlint lint`) is in `cli.harness.mjs`;
|
|
4
|
+
* this pins the parser's output shape and each refusal.
|
|
5
|
+
*/
|
|
6
|
+
import assert from "node:assert/strict";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
|
|
10
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const { parseRuleBlocks, shippedRuleIds, unknownKeys } = await import(
|
|
12
|
+
join(HERE, "rules-config.ts")
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
let n = 0;
|
|
16
|
+
const check = (label, cond, detail = "") => {
|
|
17
|
+
assert.ok(cond, detail ? `${label} — ${detail}` : label);
|
|
18
|
+
n++;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const SHIPPED = new Set(["pdf/last-page-balance", "paper/typography"]);
|
|
22
|
+
const parse = (raw) => parseRuleBlocks(raw, "S", SHIPPED, "/consumer");
|
|
23
|
+
|
|
24
|
+
check(
|
|
25
|
+
"no `rules` key: no blocks",
|
|
26
|
+
parse(undefined).ok && parse(undefined).value.length === 0,
|
|
27
|
+
);
|
|
28
|
+
{
|
|
29
|
+
const r = parse([
|
|
30
|
+
{
|
|
31
|
+
files: ["papers/a/**"],
|
|
32
|
+
ignores: ["papers/a/old/**"],
|
|
33
|
+
rules: { "pdf/last-page-balance": ["error", { tolerancePt: 90 }] },
|
|
34
|
+
},
|
|
35
|
+
]);
|
|
36
|
+
check(
|
|
37
|
+
"a block keeps files, ignores and rules, and gets the settings file's directory as basePath",
|
|
38
|
+
r.ok &&
|
|
39
|
+
JSON.stringify(r.value[0]) ===
|
|
40
|
+
JSON.stringify({
|
|
41
|
+
basePath: "/consumer",
|
|
42
|
+
files: ["papers/a/**"],
|
|
43
|
+
ignores: ["papers/a/old/**"],
|
|
44
|
+
rules: { "pdf/last-page-balance": ["error", { tolerancePt: 90 }] },
|
|
45
|
+
}),
|
|
46
|
+
JSON.stringify(r),
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
check(
|
|
50
|
+
"both severity spellings are accepted",
|
|
51
|
+
parse([{ rules: { "paper/typography": 0, "pdf/last-page-balance": "warn" } }])
|
|
52
|
+
.ok,
|
|
53
|
+
);
|
|
54
|
+
const refusals = [
|
|
55
|
+
[{ a: 1 }, "S.rules must be a list"],
|
|
56
|
+
[[42], "S.rules[0] must be an object"],
|
|
57
|
+
[[{ rules: [] }], "S.rules[0].rules must be an object"],
|
|
58
|
+
[[{ files: [], rules: {} }], "S.rules[0].files must be a non-empty list"],
|
|
59
|
+
[
|
|
60
|
+
[{ files: ["x"], ignores: [1], rules: {} }],
|
|
61
|
+
"S.rules[0].ignores must be a non-empty list",
|
|
62
|
+
],
|
|
63
|
+
[
|
|
64
|
+
[{ languageOptions: {}, rules: {} }],
|
|
65
|
+
'S.rules[0]: unknown key "languageOptions"',
|
|
66
|
+
],
|
|
67
|
+
[
|
|
68
|
+
[{ rules: { "markdown/no-html": "error" } }],
|
|
69
|
+
'"markdown/no-html" is not a rule paperlint ships',
|
|
70
|
+
],
|
|
71
|
+
[
|
|
72
|
+
[{ rules: { "paper/typography": ["loud"] } }],
|
|
73
|
+
'S.rules[0].rules["paper/typography"]: ["loud"] is not a severity',
|
|
74
|
+
],
|
|
75
|
+
];
|
|
76
|
+
for (const [raw, says] of refusals) {
|
|
77
|
+
const r = parse(raw);
|
|
78
|
+
check(
|
|
79
|
+
`refused, naming the key: ${says}`,
|
|
80
|
+
!r.ok && r.error.includes(says),
|
|
81
|
+
JSON.stringify(r),
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
check(
|
|
85
|
+
"unknown top-level keys are found by name; the known ones are not",
|
|
86
|
+
JSON.stringify(unknownKeys({ papersDir: "p", rules: [], papersDri: "x" })) ===
|
|
87
|
+
JSON.stringify(["papersDri"]),
|
|
88
|
+
);
|
|
89
|
+
{
|
|
90
|
+
const foreign = { rules: { "no-html": {} } };
|
|
91
|
+
const ids = shippedRuleIds(
|
|
92
|
+
[
|
|
93
|
+
{
|
|
94
|
+
plugins: {
|
|
95
|
+
pdf: { rules: { "last-page-balance": {} } },
|
|
96
|
+
markdown: foreign,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
{ plugins: { paper: { rules: { typography: {} } } } },
|
|
100
|
+
{ files: ["x"] },
|
|
101
|
+
],
|
|
102
|
+
[foreign],
|
|
103
|
+
);
|
|
104
|
+
check(
|
|
105
|
+
"shipped rule ids are read off the config's own plugins; a foreign plugin is skipped",
|
|
106
|
+
JSON.stringify([...ids].sort()) ===
|
|
107
|
+
JSON.stringify(["paper/typography", "pdf/last-page-balance"]),
|
|
108
|
+
JSON.stringify([...ids]),
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log(
|
|
113
|
+
`✓ ${String(n)} assertions passed — rules-config: blocks parsed with basePath, every refusal names its key`,
|
|
114
|
+
);
|