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,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `facts-file.ts` — the one writer of `_build/paper.facts.json`.
|
|
3
|
+
*
|
|
4
|
+
* The document is assembled from hand-made measurements (pure), and `measurePaper` runs on
|
|
5
|
+
* in-memory files with a FAKE pdf.js reader and a FAKE measurer, so each outcome — read failed, not
|
|
6
|
+
* measured, measured — is chosen rather than hoped for. Reading real PDFs is
|
|
7
|
+
* `pdf-facts.harness.mjs`'s job; banal as the measurer is `adapters/banal/index.test.ts`, and the
|
|
8
|
+
* real banal is `test/e2e/banal.mjs`.
|
|
9
|
+
*/
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import { test } from "vitest";
|
|
12
|
+
import { memoryFiles } from "./adapters/memory/index.ts";
|
|
13
|
+
import {
|
|
14
|
+
declaredVenue,
|
|
15
|
+
factsDocument,
|
|
16
|
+
factsPath,
|
|
17
|
+
measurePaper,
|
|
18
|
+
writeFactsFile,
|
|
19
|
+
type MeasureOptions,
|
|
20
|
+
} from "./facts-file.ts";
|
|
21
|
+
import { whyNoGeometry, type Geometry } from "./domain/geometry.ts";
|
|
22
|
+
import { sha256Hex } from "./domain/sha256.ts";
|
|
23
|
+
import type { Fonts, PageText } from "./pdf-geometry.ts";
|
|
24
|
+
import type { PdfReader } from "./pdf-facts.ts";
|
|
25
|
+
|
|
26
|
+
/** A last page of `left` and `right` words in two columns, plus `extra`. */
|
|
27
|
+
const lastPage = (
|
|
28
|
+
left: number,
|
|
29
|
+
right: number,
|
|
30
|
+
extra: PageText["words"] = [],
|
|
31
|
+
): PageText => {
|
|
32
|
+
const col = (x: number, n: number) =>
|
|
33
|
+
Array.from({ length: n }, (_, i) => ({
|
|
34
|
+
x0: x,
|
|
35
|
+
y0: 60 + i * 10,
|
|
36
|
+
x1: x + 20,
|
|
37
|
+
y1: 70 + i * 10,
|
|
38
|
+
text: "w",
|
|
39
|
+
}));
|
|
40
|
+
return {
|
|
41
|
+
widthPt: 612,
|
|
42
|
+
heightPt: 792,
|
|
43
|
+
words: [...col(54, left), ...col(320, right), ...extra],
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
const FONTS: Fonts = {
|
|
47
|
+
kind: "drawn",
|
|
48
|
+
list: [
|
|
49
|
+
{ kind: "embedded", name: "LinLibertineT", program: "Type1C" },
|
|
50
|
+
{ kind: "type3", name: "Type3" },
|
|
51
|
+
{ kind: "not-embedded", name: "Times-Roman", program: "Type1" },
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
const NONE: Geometry = {
|
|
55
|
+
kind: "unmeasured",
|
|
56
|
+
why: ["perl is not installed"],
|
|
57
|
+
tried: null,
|
|
58
|
+
};
|
|
59
|
+
/** A measurement, in the domain's names, by a measurer called `banal`. */
|
|
60
|
+
const MEASURED: Geometry = {
|
|
61
|
+
kind: "measured",
|
|
62
|
+
by: { tool: "banal", path: "/b", how: "$BANAL" },
|
|
63
|
+
geometry: {
|
|
64
|
+
pageWidthIn: 8.5,
|
|
65
|
+
pageHeightIn: 11,
|
|
66
|
+
columns: 2,
|
|
67
|
+
bodyPt: 9,
|
|
68
|
+
refPt: 7,
|
|
69
|
+
bodyPages: 3,
|
|
70
|
+
refPages: 1,
|
|
71
|
+
appendixPages: 1,
|
|
72
|
+
pagesByType: { body: 3, bib: 1, appendix: 1 },
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
const doc = (last: PageText, geometry: Geometry = NONE) =>
|
|
76
|
+
factsDocument({
|
|
77
|
+
pdf: "paper.pdf",
|
|
78
|
+
sha: "a".repeat(64),
|
|
79
|
+
venue: "agenticdev",
|
|
80
|
+
kind: "short",
|
|
81
|
+
read: { pages: 4, fonts: FONTS, last, layout: [] },
|
|
82
|
+
geometry,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// ── the document ────────────────────────────────────────────────────────────────────────
|
|
86
|
+
test("schema 2; a measured last page gives both heights, in both fields", () => {
|
|
87
|
+
const d = doc(lastPage(60, 30));
|
|
88
|
+
assert.equal(d.schema, 2);
|
|
89
|
+
assert.deepEqual(d.last_page, { kind: "measured", columns_pt: [600, 300] });
|
|
90
|
+
assert.deepEqual(d.last_page_cols_pt, [600, 300]);
|
|
91
|
+
assert.equal(d.npages, 4);
|
|
92
|
+
assert.equal(d.fonts_source, "pdfjs-drawn");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("fonts keep poppler's spelling; Type 3 is embedded; no usable program is not", () => {
|
|
96
|
+
const d = doc(lastPage(60, 30));
|
|
97
|
+
assert.deepEqual(d.fonts[1], {
|
|
98
|
+
name: "Type3",
|
|
99
|
+
type: "Type 3",
|
|
100
|
+
embedded: true,
|
|
101
|
+
program: "Type3",
|
|
102
|
+
});
|
|
103
|
+
// Guards: a font with no usable program is `embedded: false`.
|
|
104
|
+
assert.equal(d.fonts[2]?.embedded, false);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("🔴 a review build: last_page says review, and last_page_cols_pt is null", () => {
|
|
108
|
+
const numbers = Array.from({ length: 60 }, (_, i) => ({
|
|
109
|
+
x0: 20,
|
|
110
|
+
y0: 60 + i * 10,
|
|
111
|
+
x1: 30,
|
|
112
|
+
y1: 70 + i * 10,
|
|
113
|
+
text: String(i + 1),
|
|
114
|
+
}));
|
|
115
|
+
const d = doc(lastPage(60, 10, numbers));
|
|
116
|
+
// Guards: no fudged heights on a review build.
|
|
117
|
+
assert.deepEqual(d.last_page, { kind: "review", line_numbers: 60 });
|
|
118
|
+
assert.equal(d.last_page_cols_pt, null);
|
|
119
|
+
const stub = doc(lastPage(3, 0));
|
|
120
|
+
assert.deepEqual(stub.last_page, { kind: "stub", words: 3 });
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("🔴 geometry_source and the nine columns come from ONE branch", () => {
|
|
124
|
+
const none = doc(lastPage(60, 30));
|
|
125
|
+
assert.equal(none.geometry_source, null);
|
|
126
|
+
assert.equal(none.page_w_in, null);
|
|
127
|
+
assert.equal(none.body_pages, null);
|
|
128
|
+
const measured = doc(lastPage(60, 30), MEASURED);
|
|
129
|
+
// Guards: the file names the measurer that measured, from the provenance only an adapter builds.
|
|
130
|
+
assert.equal(measured.geometry_source, "banal");
|
|
131
|
+
assert.deepEqual(
|
|
132
|
+
[
|
|
133
|
+
measured.body_pages,
|
|
134
|
+
measured.ref_pages,
|
|
135
|
+
measured.page_w_in,
|
|
136
|
+
measured.ref_pt,
|
|
137
|
+
],
|
|
138
|
+
[3, 1, 8.5, 7],
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// ── measuring and writing ───────────────────────────────────────────────────────────────
|
|
143
|
+
const PAPER = "/r/papers/p";
|
|
144
|
+
const PDF = `${PAPER}/paper.pdf`;
|
|
145
|
+
const PDF_BYTES = "%PDF-pretend";
|
|
146
|
+
const good: PdfReader = async () => ({
|
|
147
|
+
ok: true,
|
|
148
|
+
facts: { pages: 2, fonts: FONTS, last: lastPage(60, 60), layout: [] },
|
|
149
|
+
});
|
|
150
|
+
/** Options over in-memory files: the PDF and venue.json on "disk", a measurer answering `geometry`. */
|
|
151
|
+
function setup(geometry: Geometry = NONE) {
|
|
152
|
+
const files = memoryFiles({
|
|
153
|
+
[PDF]: PDF_BYTES,
|
|
154
|
+
[`${PAPER}/venue.json`]: JSON.stringify({
|
|
155
|
+
venue: "agenticdev",
|
|
156
|
+
kind: "short",
|
|
157
|
+
}),
|
|
158
|
+
});
|
|
159
|
+
const measured: unknown[] = [];
|
|
160
|
+
const o: MeasureOptions = {
|
|
161
|
+
readPdf: good,
|
|
162
|
+
files,
|
|
163
|
+
measure: {
|
|
164
|
+
measure(pages) {
|
|
165
|
+
measured.push(pages);
|
|
166
|
+
return geometry;
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
return { files, measured, o };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
test("measure: venue and kind from venue.json, pdf relative to the paper, the file's sha256", async () => {
|
|
174
|
+
const { o } = setup();
|
|
175
|
+
const m = await measurePaper(PAPER, PDF, o);
|
|
176
|
+
assert.ok(m.ok);
|
|
177
|
+
assert.equal(m.value.facts.pdf, "paper.pdf");
|
|
178
|
+
assert.equal(
|
|
179
|
+
m.value.facts.pdf_sha256,
|
|
180
|
+
sha256Hex(new TextEncoder().encode(PDF_BYTES)),
|
|
181
|
+
);
|
|
182
|
+
assert.deepEqual(
|
|
183
|
+
[m.value.facts.venue, m.value.facts.kind],
|
|
184
|
+
["agenticdev", "short"],
|
|
185
|
+
);
|
|
186
|
+
const over = await measurePaper(PAPER, PDF, { ...o, venue: "aisec" });
|
|
187
|
+
assert.equal(over.ok && over.value.facts.venue, "aisec");
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("measure, not measured: the facts are there with null geometry, and the measurer's reason is kept", async () => {
|
|
191
|
+
const { o, measured } = setup(NONE);
|
|
192
|
+
const m = await measurePaper(PAPER, PDF, o);
|
|
193
|
+
assert.ok(m.ok);
|
|
194
|
+
assert.equal(m.value.facts.geometry_source, null);
|
|
195
|
+
const g = m.value.geometry;
|
|
196
|
+
assert.equal(
|
|
197
|
+
g.kind === "unmeasured" ? whyNoGeometry(g) : "",
|
|
198
|
+
"perl is not installed",
|
|
199
|
+
);
|
|
200
|
+
// Guards: the measurer is handed the pages pdf.js read — the one read feeds both halves.
|
|
201
|
+
assert.deepEqual(measured, [[]]);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("measure, measured: the geometry is in the file under its measurer's name", async () => {
|
|
205
|
+
const { o } = setup(MEASURED);
|
|
206
|
+
const m = await measurePaper(PAPER, PDF, o);
|
|
207
|
+
assert.ok(m.ok);
|
|
208
|
+
assert.equal(m.value.facts.geometry_source, "banal");
|
|
209
|
+
assert.deepEqual(
|
|
210
|
+
[m.value.facts.columns, m.value.facts.body_pages, m.value.facts.page_w_in],
|
|
211
|
+
[2, 3, 8.5],
|
|
212
|
+
);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("a failed read is the one error line, and nothing is written", async () => {
|
|
216
|
+
const { o, files } = setup();
|
|
217
|
+
const before = files.map.size;
|
|
218
|
+
const m = await measurePaper(PAPER, PDF, {
|
|
219
|
+
...o,
|
|
220
|
+
readPdf: async () => ({ ok: false, reason: "unreadable", detail: "boom" }),
|
|
221
|
+
});
|
|
222
|
+
assert.match(!m.ok ? m.error : "", /pdf\.js \(unreadable\): boom/);
|
|
223
|
+
assert.equal(files.map.size, before);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("writeFactsFile: <paper>/_build/paper.facts.json holds exactly the document", async () => {
|
|
227
|
+
const { o, files } = setup();
|
|
228
|
+
const m = await measurePaper(PAPER, PDF, o);
|
|
229
|
+
assert.ok(m.ok);
|
|
230
|
+
const out = writeFactsFile(files, PAPER, m.value.facts);
|
|
231
|
+
assert.equal(out, factsPath(PAPER));
|
|
232
|
+
const text = new TextDecoder().decode(files.map.get(out));
|
|
233
|
+
assert.deepEqual(JSON.parse(text), m.value.facts);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test("declaredVenue: none without venue.json, none when it names no venue", () => {
|
|
237
|
+
assert.equal(declaredVenue(memoryFiles(), PAPER), null);
|
|
238
|
+
const files = memoryFiles({ [`${PAPER}/venue.json`]: '{"kind":"x"}' });
|
|
239
|
+
assert.equal(declaredVenue(files, PAPER), null);
|
|
240
|
+
});
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE ONE WRITER of `<paper>/_build/paper.facts.json` — what a finished PDF measures, as data a lint
|
|
3
|
+
* rule can judge. It judges nothing itself.
|
|
4
|
+
*
|
|
5
|
+
* Two callers, one function: `paperlint build` writes the facts right after a successful compile, and
|
|
6
|
+
* `skills/render-paper/extract-pdf-facts.mjs` is a thin command-line shim over the same
|
|
7
|
+
* `measurePaper` + `writeFactsFile` for PDFs paperlint did not build and for CI steps that name that script by path. Two
|
|
8
|
+
* writers would drift — one would learn a field the other does not — and the rules reading the file
|
|
9
|
+
* cannot tell which wrote it.
|
|
10
|
+
*
|
|
11
|
+
* ── WHERE THE FACTS COME FROM ──────────────────────────────────────────────────
|
|
12
|
+
* - pdf.js (`pdf-facts.ts`) — page count, the fonts the pages draw text with, the last page. Always.
|
|
13
|
+
* - a page-geometry measurer, through the `MeasureGeometry` port (`ports/measure-geometry.ts`) —
|
|
14
|
+
* today banal (Eddie Kohler's page-geometry tool, the one HotCRP's format checker runs) — paper size,
|
|
15
|
+
* columns, body and reference font sizes, page types. It runs on pdftohtml-style XML that paperlint
|
|
16
|
+
* writes from the same pdf.js read (`adapters/banal/xml.ts`), so poppler is not needed (`adapters/banal/`).
|
|
17
|
+
* OPTIONAL: banal is GPL and paperlint does not ship it — `paperlint toolchain` fetches it. Found ⇒ its fields
|
|
18
|
+
* are filled and `geometry_source` says `banal`; not found ⇒ they are null and `geometry_source`
|
|
19
|
+
* is null, so a rule can tell "not measured" from "measured as zero". Whether that is a failure is
|
|
20
|
+
* the caller's policy: `extract-pdf-facts.mjs --strict` refuses to write such facts.
|
|
21
|
+
*
|
|
22
|
+
* ── SCHEMA 2 (2026-09-24) ───────────────────────────────────────────────────────
|
|
23
|
+
* Every schema-1 field keeps its name. The number changed because three meanings did, and a
|
|
24
|
+
* consumer that pins the schema must hear about it rather than read new meanings under the old
|
|
25
|
+
* number: fonts are the ones pages DRAW (pdf.js), not every font in a resource dictionary; Type 3
|
|
26
|
+
* fonts are all named `Type3`; `embedded` means a usable program is present, so a corrupted one is
|
|
27
|
+
* false. `pdf` is now relative to the PAPER directory, not to wherever the command ran.
|
|
28
|
+
*
|
|
29
|
+
* 🔴 STALENESS. The PDF is not committed, so neither are these facts: they live in `_build/` and
|
|
30
|
+
* carry `pdf_sha256`. A rule compares it with the PDF on disk and refuses facts about another build.
|
|
31
|
+
*/
|
|
32
|
+
import { join, relative, sep } from "node:path";
|
|
33
|
+
import {
|
|
34
|
+
classifyLastPage,
|
|
35
|
+
popplerType,
|
|
36
|
+
type FontFact,
|
|
37
|
+
type LastPage,
|
|
38
|
+
} from "./pdf-geometry.ts";
|
|
39
|
+
import { describeFailure, type PdfFacts, type PdfReader } from "./pdf-facts.ts";
|
|
40
|
+
import {
|
|
41
|
+
flatGeometry,
|
|
42
|
+
type FactsGeometryFields,
|
|
43
|
+
type Geometry,
|
|
44
|
+
} from "./domain/geometry.ts";
|
|
45
|
+
import { sha256Hex } from "./domain/sha256.ts";
|
|
46
|
+
import type { MeasureGeometry } from "./ports/measure-geometry.ts";
|
|
47
|
+
import type { AbsolutePath } from "./domain/paths.ts";
|
|
48
|
+
import type { Files } from "./ports/files.ts";
|
|
49
|
+
import { err, ok, type Result } from "./domain/result.ts";
|
|
50
|
+
|
|
51
|
+
export const FACTS_SCHEMA = 2;
|
|
52
|
+
export const FACTS_DIR = "_build";
|
|
53
|
+
export const FACTS_FILE = "paper.facts.json";
|
|
54
|
+
|
|
55
|
+
/** Where a paper's facts live. */
|
|
56
|
+
export const factsPath = (paperDir: string): string =>
|
|
57
|
+
join(paperDir, FACTS_DIR, FACTS_FILE);
|
|
58
|
+
|
|
59
|
+
/** The `venue.json` a paper declares: where it is submitted, as which kind, and where its PDF is. */
|
|
60
|
+
export interface VenueDecl {
|
|
61
|
+
readonly venue: string;
|
|
62
|
+
readonly kind: string | null;
|
|
63
|
+
readonly pdf: string | null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* A path as the caller gave it. The `Files` adapter resolves a relative one against the process's
|
|
68
|
+
* cwd, which is what these callers have always meant; the brand is not a claim this code checked it.
|
|
69
|
+
*/
|
|
70
|
+
const at = (p: string): AbsolutePath => p as AbsolutePath;
|
|
71
|
+
|
|
72
|
+
const str = (v: unknown): string | null => (typeof v === "string" ? v : null);
|
|
73
|
+
|
|
74
|
+
/** A parsed `venue.json`, or null when it names no venue. */
|
|
75
|
+
export function parseVenueDecl(json: unknown): VenueDecl | null {
|
|
76
|
+
const d = typeof json === "object" && json !== null ? json : {};
|
|
77
|
+
const field = (k: string) => str((d as Record<string, unknown>)[k]);
|
|
78
|
+
const venue = field("venue");
|
|
79
|
+
return venue ? { venue, kind: field("kind"), pdf: field("pdf") } : null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The paper's `venue.json`, or null when there is none or it names no venue. The one reader of
|
|
84
|
+
* that file: the build, the facts writer and the shim all read it here.
|
|
85
|
+
*/
|
|
86
|
+
export function declaredVenue(
|
|
87
|
+
files: Files,
|
|
88
|
+
paperDir: string,
|
|
89
|
+
): VenueDecl | null {
|
|
90
|
+
const bytes = files.readBytes(at(join(paperDir, "venue.json")));
|
|
91
|
+
return bytes === null
|
|
92
|
+
? null
|
|
93
|
+
: parseVenueDecl(JSON.parse(new TextDecoder().decode(bytes)));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ── the document ────────────────────────────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
/** One font as the facts file spells it. `type` keeps poppler's spelling (`Type 3`). */
|
|
99
|
+
export interface FontEntry {
|
|
100
|
+
readonly name: string;
|
|
101
|
+
readonly type: string;
|
|
102
|
+
readonly embedded: boolean;
|
|
103
|
+
readonly program: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export type LastPageEntry =
|
|
107
|
+
| {
|
|
108
|
+
readonly kind: "measured";
|
|
109
|
+
readonly columns_pt: readonly [number, number];
|
|
110
|
+
}
|
|
111
|
+
| { readonly kind: "stub"; readonly words: number }
|
|
112
|
+
| { readonly kind: "review"; readonly line_numbers: number };
|
|
113
|
+
|
|
114
|
+
/** The facts file's content. */
|
|
115
|
+
export type FactsDocument = {
|
|
116
|
+
readonly schema: typeof FACTS_SCHEMA;
|
|
117
|
+
readonly pdf: string;
|
|
118
|
+
readonly pdf_sha256: string;
|
|
119
|
+
readonly venue: string | null;
|
|
120
|
+
readonly kind: string | null;
|
|
121
|
+
readonly npages: number;
|
|
122
|
+
readonly fonts_source: "pdfjs-drawn";
|
|
123
|
+
readonly fonts: readonly FontEntry[];
|
|
124
|
+
readonly last_page: LastPageEntry;
|
|
125
|
+
/** Schema 1's field, kept: the two heights, or null when the last page is a stub or a review build. */
|
|
126
|
+
readonly last_page_cols_pt: readonly [number, number] | null;
|
|
127
|
+
} & FactsGeometryFields;
|
|
128
|
+
|
|
129
|
+
export function fontEntry(f: FontFact): FontEntry {
|
|
130
|
+
return {
|
|
131
|
+
name: f.name,
|
|
132
|
+
type: popplerType(f),
|
|
133
|
+
// A Type 3 font's glyphs are procedures inside the PDF: nothing is substituted, so it IS
|
|
134
|
+
// embedded (poppler says so too). Its defect is being Type 3, which `type` already names.
|
|
135
|
+
embedded: f.kind !== "not-embedded",
|
|
136
|
+
program: f.kind === "type3" ? "Type3" : f.program,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function lastPageEntry(p: LastPage): LastPageEntry {
|
|
141
|
+
if (p.kind === "measured")
|
|
142
|
+
return { kind: "measured", columns_pt: [p.columns[0], p.columns[1]] };
|
|
143
|
+
if (p.kind === "review")
|
|
144
|
+
return { kind: "review", line_numbers: p.lineNumbers };
|
|
145
|
+
return { kind: "stub", words: p.words };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Everything the document is assembled from. */
|
|
149
|
+
export interface FactsInput {
|
|
150
|
+
/** The PDF's path relative to the paper directory, with forward slashes. */
|
|
151
|
+
readonly pdf: string;
|
|
152
|
+
readonly sha: string;
|
|
153
|
+
readonly venue: string | null;
|
|
154
|
+
readonly kind: string | null;
|
|
155
|
+
readonly read: PdfFacts;
|
|
156
|
+
readonly geometry: Geometry;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Assemble the document. Pure. */
|
|
160
|
+
export function factsDocument(i: FactsInput): FactsDocument {
|
|
161
|
+
const last = lastPageEntry(classifyLastPage(i.read.last));
|
|
162
|
+
return {
|
|
163
|
+
schema: FACTS_SCHEMA,
|
|
164
|
+
pdf: i.pdf,
|
|
165
|
+
pdf_sha256: i.sha,
|
|
166
|
+
venue: i.venue,
|
|
167
|
+
kind: i.kind,
|
|
168
|
+
npages: i.read.pages,
|
|
169
|
+
fonts_source: "pdfjs-drawn",
|
|
170
|
+
fonts:
|
|
171
|
+
i.read.fonts.kind === "drawn" ? i.read.fonts.list.map(fontEntry) : [],
|
|
172
|
+
last_page: last,
|
|
173
|
+
last_page_cols_pt: last.kind === "measured" ? last.columns_pt : null,
|
|
174
|
+
...flatGeometry(i.geometry),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ── measuring and writing ───────────────────────────────────────────────────────────────
|
|
179
|
+
|
|
180
|
+
/** What `measurePaper` needs besides the paper and its PDF. */
|
|
181
|
+
export interface MeasureOptions {
|
|
182
|
+
readonly readPdf: PdfReader;
|
|
183
|
+
readonly venue?: string | null;
|
|
184
|
+
readonly kind?: string | null;
|
|
185
|
+
/** The page-geometry measurer (banal, as the composition root wired it). */
|
|
186
|
+
readonly measure: MeasureGeometry;
|
|
187
|
+
/** Where the PDF and `venue.json` are read from. */
|
|
188
|
+
readonly files: Files;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** The document, and the geometry it was projected from — a caller decides what "no geometry" means. */
|
|
192
|
+
export interface Measured {
|
|
193
|
+
readonly facts: FactsDocument;
|
|
194
|
+
readonly geometry: Geometry;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const posix = (p: string): string => p.split(sep).join("/");
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Measure `pdf`: pdf.js always, banal when one is found. Writes nothing. The error is one line: the
|
|
201
|
+
* PDF could not be read. Whether a missing geometry is a failure is the CALLER's policy — the build
|
|
202
|
+
* writes the facts anyway, the shim's `--strict` refuses them.
|
|
203
|
+
*/
|
|
204
|
+
export async function measurePaper(
|
|
205
|
+
paperDir: string,
|
|
206
|
+
pdf: string,
|
|
207
|
+
o: MeasureOptions,
|
|
208
|
+
): Promise<Result<Measured, string>> {
|
|
209
|
+
const r = await o.readPdf(pdf);
|
|
210
|
+
if (!r.ok) return err(describeFailure(r, pdf));
|
|
211
|
+
const bytes = o.files.readBytes(at(pdf));
|
|
212
|
+
if (bytes === null) return err(`${pdf}: gone after pdf.js read it`);
|
|
213
|
+
const geometry = o.measure.measure(r.facts.layout);
|
|
214
|
+
const decl = declaredVenue(o.files, paperDir);
|
|
215
|
+
const facts = factsDocument({
|
|
216
|
+
pdf: posix(relative(paperDir, pdf)),
|
|
217
|
+
sha: sha256Hex(bytes),
|
|
218
|
+
venue: o.venue ?? decl?.venue ?? null,
|
|
219
|
+
kind: o.kind ?? decl?.kind ?? null,
|
|
220
|
+
read: r.facts,
|
|
221
|
+
geometry,
|
|
222
|
+
});
|
|
223
|
+
return ok({ facts, geometry });
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Write `<paperDir>/_build/paper.facts.json`, atomically. A caller that decides not to write leaves
|
|
228
|
+
* an earlier file as it was — its `pdf_sha256` no longer matches the PDF, so a rule refuses it.
|
|
229
|
+
*/
|
|
230
|
+
export function writeFactsFile(
|
|
231
|
+
files: Files,
|
|
232
|
+
paperDir: string,
|
|
233
|
+
facts: FactsDocument,
|
|
234
|
+
): string {
|
|
235
|
+
const out = factsPath(paperDir);
|
|
236
|
+
files.writeAtomic(
|
|
237
|
+
at(out),
|
|
238
|
+
new TextEncoder().encode(`${JSON.stringify(facts, null, 2)}\n`),
|
|
239
|
+
);
|
|
240
|
+
return out;
|
|
241
|
+
}
|