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,166 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `extract-pdf-facts.mjs` — the command-line shim over `writeFacts`, run as a process the way CI
|
|
4
|
+
* runs it, on a real committed PDF.
|
|
5
|
+
*
|
|
6
|
+
* What is pinned here is the CONTRACT callers branch on — the exit codes and where the file lands —
|
|
7
|
+
* plus the one defect of the poppler reader this shim used to carry: it read `yes` anywhere in a
|
|
8
|
+
* `pdffonts` row, so a font printed `no no yes` (not embedded, not subset, has a ToUnicode map)
|
|
9
|
+
* came out embedded. `fixtures/pdf-facts/t3-mixed.pdf` carries exactly such a font.
|
|
10
|
+
*
|
|
11
|
+
* The shim imports the compiled package (`dist/`), so `npm run build` runs before this.
|
|
12
|
+
*/
|
|
13
|
+
import assert from "node:assert/strict";
|
|
14
|
+
import { spawnSync } from "node:child_process";
|
|
15
|
+
import {
|
|
16
|
+
cpSync,
|
|
17
|
+
existsSync,
|
|
18
|
+
mkdirSync,
|
|
19
|
+
mkdtempSync,
|
|
20
|
+
readFileSync,
|
|
21
|
+
realpathSync,
|
|
22
|
+
rmSync,
|
|
23
|
+
writeFileSync,
|
|
24
|
+
} from "node:fs";
|
|
25
|
+
import { tmpdir } from "node:os";
|
|
26
|
+
import { dirname, join, resolve } from "node:path";
|
|
27
|
+
import { fileURLToPath } from "node:url";
|
|
28
|
+
|
|
29
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
30
|
+
const SHIM = join(HERE, "extract-pdf-facts.mjs");
|
|
31
|
+
const FIX = resolve(HERE, "..", "..", "fixtures", "pdf-facts");
|
|
32
|
+
|
|
33
|
+
let n = 0;
|
|
34
|
+
const check = (label, cond, detail = "") => {
|
|
35
|
+
assert.ok(cond, detail ? `${label} — ${detail}` : label);
|
|
36
|
+
n++;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const root = realpathSync(mkdtempSync(join(tmpdir(), "rpp-extract-")));
|
|
40
|
+
/**
|
|
41
|
+
* Run the shim from `root`, with no banal anywhere unless `env` names one. `HOME` is the temp root:
|
|
42
|
+
* without it Node falls back to the account's home, where `paperlint toolchain` may have installed banal.
|
|
43
|
+
*/
|
|
44
|
+
const shim = (args, env = {}) =>
|
|
45
|
+
spawnSync(process.execPath, [SHIM, ...args], {
|
|
46
|
+
cwd: root,
|
|
47
|
+
encoding: "utf8",
|
|
48
|
+
env: {
|
|
49
|
+
PATH: process.env.PATH,
|
|
50
|
+
HOME: root,
|
|
51
|
+
CLAUDE_PROJECT_DIR: root,
|
|
52
|
+
...env,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
const said = (r) => `${r.stdout}${r.stderr}`;
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const paper = join(root, "papers", "mixed");
|
|
59
|
+
mkdirSync(paper, { recursive: true });
|
|
60
|
+
cpSync(join(FIX, "t3-mixed.pdf"), join(paper, "paper.pdf"));
|
|
61
|
+
writeFileSync(
|
|
62
|
+
join(paper, "venue.json"),
|
|
63
|
+
JSON.stringify({ venue: "agenticdev", kind: "short" }),
|
|
64
|
+
);
|
|
65
|
+
const factsFile = join(paper, "_build", "paper.facts.json");
|
|
66
|
+
|
|
67
|
+
check("usage: no target is exit 2", shim([]).status === 2);
|
|
68
|
+
check("no such path is exit 1", shim([join(root, "nope")]).status === 1);
|
|
69
|
+
|
|
70
|
+
const strict = shim([paper, "--strict"]);
|
|
71
|
+
check(
|
|
72
|
+
"🔴 --strict with no banal: exit 1, named as an environment error, and nothing written",
|
|
73
|
+
strict.status === 1 &&
|
|
74
|
+
/banal not found/.test(said(strict)) &&
|
|
75
|
+
/`npx paperlint toolchain` installs it/.test(said(strict)) &&
|
|
76
|
+
/environment error/.test(said(strict)) &&
|
|
77
|
+
!existsSync(factsFile),
|
|
78
|
+
said(strict),
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const local = shim([paper]);
|
|
82
|
+
check(
|
|
83
|
+
"without --strict and no banal: exit 0, facts written, the missing geometry said out loud",
|
|
84
|
+
local.status === 0 &&
|
|
85
|
+
existsSync(factsFile) &&
|
|
86
|
+
/banal not found/.test(local.stderr),
|
|
87
|
+
said(local),
|
|
88
|
+
);
|
|
89
|
+
const facts = JSON.parse(readFileSync(factsFile, "utf8"));
|
|
90
|
+
check(
|
|
91
|
+
"the facts are schema 2, about paper.pdf, for the venue venue.json declares",
|
|
92
|
+
facts.schema === 2 &&
|
|
93
|
+
facts.pdf === "paper.pdf" &&
|
|
94
|
+
facts.venue === "agenticdev" &&
|
|
95
|
+
facts.kind === "short",
|
|
96
|
+
JSON.stringify(facts).slice(0, 200),
|
|
97
|
+
);
|
|
98
|
+
check(
|
|
99
|
+
"a text PDF has fonts",
|
|
100
|
+
Array.isArray(facts.fonts) && facts.fonts.length === 4,
|
|
101
|
+
JSON.stringify(facts.fonts),
|
|
102
|
+
);
|
|
103
|
+
const times = facts.fonts.find((f) => f.name === "Times-Roman");
|
|
104
|
+
check(
|
|
105
|
+
"🔴 Times-Roman — pdffonts `Type 1 Custom no no yes` — is embedded: false (the old reader said true)",
|
|
106
|
+
times?.embedded === false && times.type === "Type 1",
|
|
107
|
+
JSON.stringify(times),
|
|
108
|
+
);
|
|
109
|
+
check(
|
|
110
|
+
"the Type 3 font keeps poppler's type spelling, which consumers match on",
|
|
111
|
+
facts.fonts.some((f) => f.type === "Type 3"),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const fake = join(root, "banal.pl");
|
|
115
|
+
writeFileSync(
|
|
116
|
+
fake,
|
|
117
|
+
`print '{"papersize":[792,612],"columns":2,"bodyfontsize":9,"pages":[{}]}';\n`,
|
|
118
|
+
);
|
|
119
|
+
const withBanal = shim([paper, "--strict"], { BANAL: fake });
|
|
120
|
+
const g = JSON.parse(readFileSync(factsFile, "utf8"));
|
|
121
|
+
check(
|
|
122
|
+
"--strict with banal ($BANAL): exit 0, and the geometry is banal's",
|
|
123
|
+
withBanal.status === 0 && g.geometry_source === "banal" && g.columns === 2,
|
|
124
|
+
said(withBanal),
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// Guards: missing perl is its own diagnosis, with the fix — not a generic "banal failed".
|
|
128
|
+
const noPerl = shim([paper, "--strict"], {
|
|
129
|
+
BANAL: fake,
|
|
130
|
+
PATH: mkdtempSync(join(root, "empty-path-")),
|
|
131
|
+
});
|
|
132
|
+
check(
|
|
133
|
+
"🔴 --strict with banal but no perl: exit 1, and the message names perl and how to get it",
|
|
134
|
+
noPerl.status === 1 &&
|
|
135
|
+
/perl is not installed/.test(said(noPerl)) &&
|
|
136
|
+
/apt-get install perl/.test(said(noPerl)),
|
|
137
|
+
said(noPerl),
|
|
138
|
+
);
|
|
139
|
+
const noPerlLocal = shim([paper], {
|
|
140
|
+
BANAL: fake,
|
|
141
|
+
PATH: mkdtempSync(join(root, "empty-path-")),
|
|
142
|
+
});
|
|
143
|
+
check(
|
|
144
|
+
"without --strict and no perl: exit 0, and the missing geometry is said, naming perl",
|
|
145
|
+
noPerlLocal.status === 0 &&
|
|
146
|
+
/perl is not installed/.test(noPerlLocal.stderr),
|
|
147
|
+
said(noPerlLocal),
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
writeFileSync(
|
|
151
|
+
join(paper, "venue.json"),
|
|
152
|
+
JSON.stringify({ venue: "x", pdf: "build/other.pdf" }),
|
|
153
|
+
);
|
|
154
|
+
const missing = shim([paper, "--strict"], { BANAL: fake });
|
|
155
|
+
check(
|
|
156
|
+
"a declared artifact that is not built is exit 3, not 1",
|
|
157
|
+
missing.status === 3 && /not built/.test(said(missing)),
|
|
158
|
+
said(missing),
|
|
159
|
+
);
|
|
160
|
+
} finally {
|
|
161
|
+
rmSync(root, { recursive: true, force: true });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
console.log(
|
|
165
|
+
`✓ ${String(n)} assertions passed — extract-pdf-facts: the exit-code contract, schema 2, and the no-no-yes font`,
|
|
166
|
+
);
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* extract-pdf-facts.mjs — measure a finished PDF and write its FACTS into
|
|
4
|
+
* `<paper>/_build/paper.facts.json`. It judges nothing; lint rules judge the file.
|
|
5
|
+
*
|
|
6
|
+
* 🔴 A THIN SHIM. The measuring and the writing are `measurePaper` and `writeFactsFile` in
|
|
7
|
+
* `src/facts-file.ts`, the one writer of that file; `paperlint build` calls the same functions right after
|
|
8
|
+
* it compiles a paper. This file is a composition root: it reads the environment and builds the real
|
|
9
|
+
* adapters (`dist/adapters/node/`), and it owns the `--strict` policy. This
|
|
10
|
+
* script exists for two callers the build does not serve: a PDF paperlint did not build (a paper that
|
|
11
|
+
* declares its artifact elsewhere in `venue.json`), and CI steps that name this script by path.
|
|
12
|
+
* It reaches the package's compiled code through `../../dist/`, resolved from this file's real
|
|
13
|
+
* location, so it works the same from a checkout, from `node_modules` and through a symlink.
|
|
14
|
+
*
|
|
15
|
+
* WHAT IT MEASURES WITH. pdf.js (the `unpdf` package, installed with paperlint) for the page count, the
|
|
16
|
+
* fonts and the last page — nothing to install. `banal` (the page-geometry script HotCRP's format
|
|
17
|
+
* checker runs) for paper size, columns and font sizes — run by perl on XML paperlint writes from the same
|
|
18
|
+
* pdf.js read, no poppler. `npx paperlint toolchain` installs it; `$BANAL` or a project's `vendor/banal`
|
|
19
|
+
* take precedence (`src/adapters/banal/`).
|
|
20
|
+
*
|
|
21
|
+
* ── THE EXIT-CODE CONTRACT (callers branch on it; the table is `src/exit-code.ts`) ──
|
|
22
|
+
* 0 facts written — or, without `--strict`, not written and said so (a local run may lack banal)
|
|
23
|
+
* 1 no such path; or, with `--strict`, the facts could not be taken (no banal, no perl, banal
|
|
24
|
+
* failed, unreadable PDF).
|
|
25
|
+
* In CI a missing tool is an ENVIRONMENT error, and a skipped step looks like a passed one
|
|
26
|
+
* 2 usage
|
|
27
|
+
* 3 the paper declares an artifact that is not built — a different event from "extraction
|
|
28
|
+
* crashed", and the caller must be able to tell them apart (external review, 2026-08-31)
|
|
29
|
+
*/
|
|
30
|
+
import { existsSync, statSync } from "node:fs";
|
|
31
|
+
import { join, dirname, basename, relative } from "node:path";
|
|
32
|
+
import { isMain } from "../paper-pipeline/scripts/consumer.mjs";
|
|
33
|
+
import {
|
|
34
|
+
declaredVenue as declaredIn,
|
|
35
|
+
measurePaper,
|
|
36
|
+
writeFactsFile,
|
|
37
|
+
} from "../../dist/facts-file.js";
|
|
38
|
+
import { readPdf } from "../../dist/pdf-facts.js";
|
|
39
|
+
import {
|
|
40
|
+
banalMeasurer,
|
|
41
|
+
parseBanalSettings,
|
|
42
|
+
} from "../../dist/adapters/banal/index.js";
|
|
43
|
+
import {
|
|
44
|
+
hostDirs,
|
|
45
|
+
nodeAdapters,
|
|
46
|
+
nodeFiles,
|
|
47
|
+
} from "../../dist/adapters/node/index.js";
|
|
48
|
+
import { whyNoGeometry } from "../../dist/domain/geometry.js";
|
|
49
|
+
import { exitCodeFor } from "../../dist/exit-code.js";
|
|
50
|
+
|
|
51
|
+
/** The paper's `venue.json`, read from disk. */
|
|
52
|
+
export const declaredVenue = (paperDir) => declaredIn(nodeFiles, paperDir);
|
|
53
|
+
|
|
54
|
+
const ROOT = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Accept either a path to a PDF or a PAPER DIRECTORY — and in the second case find the artifact on
|
|
58
|
+
* its own: the path the paper declares in the `pdf` field of `venue.json`, else `paper.pdf` beside
|
|
59
|
+
* it. A paper written in markdown and built by its own script into `build/acl_latex.pdf` would
|
|
60
|
+
* otherwise be skipped silently. The facts ALWAYS go into `<paper directory>/_build/`, not next to
|
|
61
|
+
* the PDF, where neither a rule's glob nor a human looks.
|
|
62
|
+
*/
|
|
63
|
+
export function resolveTarget(arg) {
|
|
64
|
+
const isDir = existsSync(arg) && statSync(arg).isDirectory();
|
|
65
|
+
const paperDir = isDir ? arg : dirname(arg);
|
|
66
|
+
const decl = declaredVenue(paperDir);
|
|
67
|
+
const pdf = isDir ? join(paperDir, decl?.pdf || "paper.pdf") : arg;
|
|
68
|
+
return { paperDir, pdf, decl: decl || {} };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The ONE `process.exit`: print what happened, then exit with the contract's code for this outcome
|
|
73
|
+
* in this mode.
|
|
74
|
+
*/
|
|
75
|
+
function finish(outcome, strict, say = null) {
|
|
76
|
+
if (say) console.error(say);
|
|
77
|
+
process.exit(exitCodeFor(outcome, { strict }));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Parse the command line, or finish with the contract's code. */
|
|
81
|
+
function parseCommandLine(argv) {
|
|
82
|
+
const strict = argv.includes("--strict");
|
|
83
|
+
const [target, venue, kind] = argv.filter((a) => a !== "--strict");
|
|
84
|
+
if (!target)
|
|
85
|
+
finish(
|
|
86
|
+
"usage",
|
|
87
|
+
strict,
|
|
88
|
+
"usage: extract-pdf-facts.mjs <paper.pdf | paper directory> [venue] [kind] [--strict]",
|
|
89
|
+
);
|
|
90
|
+
if (!existsSync(target))
|
|
91
|
+
finish("no-such-path", strict, `🛑 no such path ${target}`);
|
|
92
|
+
return { strict, target, venue, kind };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Why no facts were taken, in the words for this mode. */
|
|
96
|
+
const refusal = (msg, strict) =>
|
|
97
|
+
strict
|
|
98
|
+
? `🛑 facts not taken: ${msg} — in CI this is an environment error, not a skip`
|
|
99
|
+
: `⏭️ facts not taken: ${msg} (a local run, without --strict). THE PDF IS NOT CHECKED`;
|
|
100
|
+
|
|
101
|
+
// 🔴 `isMain`, NOT a comparison of `import.meta.url` with `process.argv[1]`. Node resolves the entry
|
|
102
|
+
// point to its REAL path for `import.meta.url` but leaves `process.argv[1]` as typed, so through a
|
|
103
|
+
// symlink the two differ and the CLI silently does nothing, exiting 0. Observed 2026-09-14 on run
|
|
104
|
+
// 34784079821: `extract-pdf-facts.mjs --strict` returned RC=0 and created no facts file.
|
|
105
|
+
if (isMain(import.meta.url)) {
|
|
106
|
+
const { strict, target, venue, kind } = parseCommandLine(
|
|
107
|
+
process.argv.slice(2),
|
|
108
|
+
);
|
|
109
|
+
const { paperDir, pdf } = resolveTarget(target);
|
|
110
|
+
if (!existsSync(pdf))
|
|
111
|
+
finish(
|
|
112
|
+
"no-artifact",
|
|
113
|
+
strict,
|
|
114
|
+
`🛑 no artifact ${pdf} — the paper declared it in venue.json, but it is not built`,
|
|
115
|
+
);
|
|
116
|
+
// The composition root: banal as the measurer, wired from this process's environment.
|
|
117
|
+
const settings = parseBanalSettings(process.env, hostDirs());
|
|
118
|
+
const measure = banalMeasurer(
|
|
119
|
+
nodeAdapters({ tmpDir: settings.tmpDir }),
|
|
120
|
+
settings,
|
|
121
|
+
ROOT,
|
|
122
|
+
);
|
|
123
|
+
// Command-line arguments override the declaration — for a one-off check of someone else's PDF.
|
|
124
|
+
const m = await measurePaper(paperDir, pdf, {
|
|
125
|
+
readPdf,
|
|
126
|
+
venue: venue ?? null,
|
|
127
|
+
kind: kind ?? null,
|
|
128
|
+
measure,
|
|
129
|
+
files: nodeFiles,
|
|
130
|
+
});
|
|
131
|
+
if (!m.ok) finish("unreadable", strict, refusal(m.error, strict));
|
|
132
|
+
const { facts, geometry } = m.value;
|
|
133
|
+
const why = geometry.kind === "unmeasured" ? whyNoGeometry(geometry) : null;
|
|
134
|
+
if (why && strict) finish("no-geometry", strict, refusal(why, strict));
|
|
135
|
+
const out = writeFactsFile(nodeFiles, paperDir, facts);
|
|
136
|
+
if (why)
|
|
137
|
+
console.error(
|
|
138
|
+
`⚠️ ${why} — page size, columns and font sizes are null in the facts (run with --strict to require them)`,
|
|
139
|
+
);
|
|
140
|
+
console.log(
|
|
141
|
+
`✅ ${basename(pdf)} → ${relative(ROOT, out) || out} (${facts.fonts.length} faces, ${facts.npages} pp.)`,
|
|
142
|
+
);
|
|
143
|
+
finish("written", strict);
|
|
144
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* render-paper — the PAID tier: does this skill's description actually fire?
|
|
3
|
+
*
|
|
4
|
+
* COLOCATED ON PURPOSE (vigiles decides coverage by placement as of 2026-08-11).
|
|
5
|
+
* The prompts live in `.claude/lib/skill-trigger-cases.mjs` so all 21 cases
|
|
6
|
+
* are reviewed as one table where collisions between siblings are visible;
|
|
7
|
+
* copying them here would recreate the drift that rule exists to prevent.
|
|
8
|
+
*
|
|
9
|
+
* Measures recall (fires on its own territory) AND precision (stays quiet on a
|
|
10
|
+
* colliding sibling's territory), against the REAL `.claude` harness so the skill
|
|
11
|
+
* competes with every other installed description — an isolated run overstates
|
|
12
|
+
* recall and understates false positives.
|
|
13
|
+
*
|
|
14
|
+
* Costs money; not CI.
|
|
15
|
+
* node .claude/skills/render-paper/render-paper.eval.mjs [trials]
|
|
16
|
+
*/
|
|
17
|
+
import { runSkillTriggerEval } from "../../lib/skill-eval-kit.mjs";
|
|
18
|
+
|
|
19
|
+
await runSkillTriggerEval("render-paper");
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* render-paper — the free, deterministic tier. No model, no network.
|
|
3
|
+
*
|
|
4
|
+
* COLOCATED ON PURPOSE. vigiles decides coverage by PLACEMENT as of 2026-08-11:
|
|
5
|
+
* a test that merely names a surface no longer counts, because that tier was
|
|
6
|
+
* crediting surfaces nothing touched. So each skill needs a file inside its own
|
|
7
|
+
* directory — this one.
|
|
8
|
+
*
|
|
9
|
+
* The assertions live in `.claude/lib/skill-checks.mjs` and are CALLED here with
|
|
10
|
+
* this skill's name. They are not copied: 22 copies of the same checks is the drift that
|
|
11
|
+
* module exists to avoid. (Until 2026-08-11 this was an env-var side channel into a
|
|
12
|
+
* 614-line file named after no surface; it is a function call now.)
|
|
13
|
+
*
|
|
14
|
+
* What this proves: this skill's frontmatter parses as strict YAML, its declared
|
|
15
|
+
* tool contract is sane, its pipeline wiring points at scripts that exist, and it
|
|
16
|
+
* announces/records under ITS OWN identity rather than a sibling's.
|
|
17
|
+
*
|
|
18
|
+
* What it does NOT prove: that the skill fires, or that its guidance produces a
|
|
19
|
+
* good result. Those need a real model — see `render-paper.eval.mjs`.
|
|
20
|
+
*/
|
|
21
|
+
import assert from "node:assert/strict";
|
|
22
|
+
import {
|
|
23
|
+
copyFileSync,
|
|
24
|
+
existsSync,
|
|
25
|
+
mkdirSync,
|
|
26
|
+
mkdtempSync,
|
|
27
|
+
readFileSync,
|
|
28
|
+
readdirSync,
|
|
29
|
+
writeFileSync,
|
|
30
|
+
realpathSync,
|
|
31
|
+
} from "node:fs";
|
|
32
|
+
import { tmpdir } from "node:os";
|
|
33
|
+
import { spawnSync } from "node:child_process";
|
|
34
|
+
import { fileURLToPath } from "node:url";
|
|
35
|
+
import { dirname, join } from "node:path";
|
|
36
|
+
import { checkSkill } from "../../lib/skill-checks.mjs";
|
|
37
|
+
import { papersRoot } from "../../eslint-rules/papers.mjs";
|
|
38
|
+
import {
|
|
39
|
+
consumerPkg,
|
|
40
|
+
consumerRoot,
|
|
41
|
+
} from "../paper-pipeline/scripts/consumer.mjs";
|
|
42
|
+
|
|
43
|
+
await checkSkill("render-paper");
|
|
44
|
+
|
|
45
|
+
// The TeX package pins that stood here (texlive-fonts-extra, texlive-plain-generic in
|
|
46
|
+
// ensure-toolchain.sh) moved with the list itself: the venue profiles declare the packages, and
|
|
47
|
+
// src/tex-requirements.harness.mjs asserts that every acmart venue declares libertine, inconsolata,
|
|
48
|
+
// newtx, kastrup (binhex.tex) and fancyhdr — each proved by the file whose absence broke a build.
|
|
49
|
+
|
|
50
|
+
// ── TEXINPUTS: WHERE THE SCRIPTS LOOK FOR `paper-guards.tex` ─────────────────
|
|
51
|
+
//
|
|
52
|
+
// Why this is guarded by a test and not by proofreading. The reference guard is included via
|
|
53
|
+
// `\input{paper-guards}` WITHOUT a path — the file is found through `TEXINPUTS`, which the caller
|
|
54
|
+
// sets. An unresolved `\input` does NOT crash pdflatex: it writes a line to the log and continues,
|
|
55
|
+
// meaning the PDF builds GREEN and simply stops checking for dangling `\ref`/`\cite`. So a miss in
|
|
56
|
+
// this wiring is indistinguishable from success right up until the day someone finds a `??` in the
|
|
57
|
+
// submitted paper.
|
|
58
|
+
//
|
|
59
|
+
// What's checked is the RESOLUTION, not the build: that's what `check-render.sh`'s
|
|
60
|
+
// `--print-venues` mode is for — it prints the chosen directory and compiles nothing.
|
|
61
|
+
//
|
|
62
|
+
// What these assertions do NOT prove: that the found file is correct, that pdflatex read it, and
|
|
63
|
+
// that the guard fired. That's caught by `paper-guards.tex` itself (it fails with
|
|
64
|
+
// `\PackageError`) — here it's only "a directory was found, and found in the right order."
|
|
65
|
+
{
|
|
66
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
67
|
+
const CHECK = join(HERE, "check-render.sh");
|
|
68
|
+
const run = (args, opts = {}) =>
|
|
69
|
+
spawnSync("bash", [CHECK, ...args], { encoding: "utf8", ...opts });
|
|
70
|
+
|
|
71
|
+
// First half — on TODAY's tree, resolution must succeed IF THERE IS ANYTHING TO
|
|
72
|
+
// resolve.
|
|
73
|
+
//
|
|
74
|
+
// 🔴 THERE USED TO BE AN UNCONDITIONAL `assert.equal(status, 0)` HERE, AND THE MOVE BROKE IT —
|
|
75
|
+
// for a real reason, not by accident. It relied on the SURROUNDING tree: while the skill lived
|
|
76
|
+
// at the consumer, the second rung of the ladder won
|
|
77
|
+
// (`.claude/skills/submit-paper/references/venues/`). The package checkout has neither that nor
|
|
78
|
+
// the first rung — `venues/` arrives together with `submit-paper`, which hasn't moved yet. So
|
|
79
|
+
// the assertion wasn't checking resolution, it was checking for the presence of an unrelated
|
|
80
|
+
// directory nearby.
|
|
81
|
+
//
|
|
82
|
+
// The form now is: resolution must succeed IF AND ONLY IF at least one candidate exists on
|
|
83
|
+
// disk. This is still a real assertion — it fails if a candidate exists and the script didn't
|
|
84
|
+
// find it — but it stops requiring the existence of something this repository doesn't have yet.
|
|
85
|
+
// The absence of both candidates gets PRINTED, not swallowed: "the check found nothing to
|
|
86
|
+
// check" and "the check passed" have to look different.
|
|
87
|
+
const candidates = [
|
|
88
|
+
// rung 1 — the package
|
|
89
|
+
join(HERE, "..", "..", "venues", "paper-guards.tex"),
|
|
90
|
+
// rung 2 — the skill directory at the consumer
|
|
91
|
+
join(
|
|
92
|
+
HERE,
|
|
93
|
+
"..",
|
|
94
|
+
"submit-paper",
|
|
95
|
+
"references",
|
|
96
|
+
"venues",
|
|
97
|
+
"paper-guards.tex",
|
|
98
|
+
),
|
|
99
|
+
].filter((c) => existsSync(c));
|
|
100
|
+
|
|
101
|
+
const ok = run(["--print-venues"]);
|
|
102
|
+
if (candidates.length === 0) {
|
|
103
|
+
assert.notEqual(
|
|
104
|
+
ok.status,
|
|
105
|
+
0,
|
|
106
|
+
`there is NO paper-guards.tex on disk at all, but --print-venues returned 0 and printed ` +
|
|
107
|
+
`${JSON.stringify(ok.stdout.trim())}. Resolution succeeding out of nothing is exactly the ` +
|
|
108
|
+
`silent success this whole block was written to catch.`,
|
|
109
|
+
);
|
|
110
|
+
console.log(
|
|
111
|
+
` --print-venues: NOT VERIFIED on the live tree — paper-guards.tex is absent from both ` +
|
|
112
|
+
`the package (venues/) and next to it (submit-paper/references/venues/). It arrives with ` +
|
|
113
|
+
`\`submit-paper\`; until then only the fixtures below hold this half.`,
|
|
114
|
+
);
|
|
115
|
+
} else {
|
|
116
|
+
assert.equal(
|
|
117
|
+
ok.status,
|
|
118
|
+
0,
|
|
119
|
+
`a candidate exists on disk (${candidates[0]}), so --print-venues must resolve:\n${ok.stderr}`,
|
|
120
|
+
);
|
|
121
|
+
assert.ok(
|
|
122
|
+
existsSync(join(ok.stdout.trim(), "paper-guards.tex")),
|
|
123
|
+
`--print-venues printed ${ok.stdout.trim()}, but paper-guards.tex isn't there`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// First half, part two — RESOLUTION DOES NOT DEPEND ON THE CURRENT DIRECTORY, and this case is
|
|
128
|
+
// written against a real regression from 09-12, not invented. `check-render.sh` itself `cd`s
|
|
129
|
+
// into the paper's directory BEFORE it looks for venues, and `gates.harness.mjs` calls it
|
|
130
|
+
// against a temporary fixture with a trimmed environment. So `git rev-parse --show-toplevel`
|
|
131
|
+
// runs in a temp directory, and `CLAUDE_PROJECT_DIR` isn't set — so the ladder found nothing,
|
|
132
|
+
// stopping a build on a test that had passed the day before. The anchor became the SCRIPT'S OWN
|
|
133
|
+
// directory: it depends on neither the cwd nor whether the caller sits inside a git tree.
|
|
134
|
+
//
|
|
135
|
+
// ⚠️ Like the first half, this case requires that at least one `paper-guards.tex` exists on
|
|
136
|
+
// disk: it checks that resolution doesn't depend on cwd, not that the file exists. With no
|
|
137
|
+
// candidate there's nothing to check, and that gets printed, not swallowed.
|
|
138
|
+
if (candidates.length > 0) {
|
|
139
|
+
const outside = realpathSync(mkdtempSync(join(tmpdir(), "venues-cwd-")));
|
|
140
|
+
const env = { ...process.env };
|
|
141
|
+
delete env.CLAUDE_PROJECT_DIR;
|
|
142
|
+
const r = run(["--print-venues"], { cwd: outside, env });
|
|
143
|
+
assert.equal(
|
|
144
|
+
r.status,
|
|
145
|
+
0,
|
|
146
|
+
`resolution must work outside a git tree and without CLAUDE_PROJECT_DIR:\n${r.stderr}`,
|
|
147
|
+
);
|
|
148
|
+
assert.ok(
|
|
149
|
+
existsSync(join(r.stdout.trim(), "paper-guards.tex")),
|
|
150
|
+
`outside the repository, printed ${r.stdout.trim()}, but paper-guards.tex isn't there`,
|
|
151
|
+
);
|
|
152
|
+
} else {
|
|
153
|
+
console.log(
|
|
154
|
+
` --print-venues (outside a git tree): NOT VERIFIED — no candidate on disk, see above.`,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Second half — and it's the load-bearing one, because the silent failure lives right here.
|
|
159
|
+
// When the file is NOWHERE, the script must exit with code 2 and say why. A silent success here
|
|
160
|
+
// is exactly the defect the whole ladder was written for.
|
|
161
|
+
//
|
|
162
|
+
// 🔴 RUN AGAINST A COPY OF THE SCRIPT, AND THIS IS NOT A TEST WORKAROUND, IT'S A CONSEQUENCE OF
|
|
163
|
+
// THE MOVE (09-12, wave ③). Before `submit-paper` moved, "nowhere" was reproduced by an empty
|
|
164
|
+
// consumer root: both former rungs looked outward (the package name from cwd · `.claude/` under
|
|
165
|
+
// the root), and an empty directory found nothing at either. Now the script has a rung anchored
|
|
166
|
+
// to ITS OWN directory (`$SELF_DIR/../submit-paper/references/venues`) — added for
|
|
167
|
+
// cwd-independence — so as long as the script sits inside the package, venues next to it exist
|
|
168
|
+
// ALWAYS, and "nowhere" can no longer be reached through the environment. The only honest way to
|
|
169
|
+
// demonstrate that state is to move the script itself out of the package. The property being
|
|
170
|
+
// checked is otherwise identical, word for word: nothing resolved ⇒ exit 2 + the name of the
|
|
171
|
+
// missing file. ⚠️ What this form no longer checks: that an empty consumer root won't make the
|
|
172
|
+
// script lie — and it shouldn't, because such a root is now legitimately shadowed by the copy in
|
|
173
|
+
// the package.
|
|
174
|
+
const empty = realpathSync(mkdtempSync(join(tmpdir(), "venues-none-")));
|
|
175
|
+
const lonely = join(empty, "check-render.sh");
|
|
176
|
+
copyFileSync(CHECK, lonely);
|
|
177
|
+
const none = spawnSync("bash", [lonely, "--print-venues"], {
|
|
178
|
+
encoding: "utf8",
|
|
179
|
+
cwd: empty, // outside a git tree ⇒ the package doesn't resolve
|
|
180
|
+
env: { ...process.env, CLAUDE_PROJECT_DIR: empty },
|
|
181
|
+
});
|
|
182
|
+
assert.equal(
|
|
183
|
+
none.status,
|
|
184
|
+
2,
|
|
185
|
+
"no venues anywhere ⇒ exit 2, not a silent success",
|
|
186
|
+
);
|
|
187
|
+
assert.match(
|
|
188
|
+
none.stderr,
|
|
189
|
+
/paper-guards\.tex/,
|
|
190
|
+
"the failure message must name the missing file",
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
// Third half — the ORDER of the rungs. Not cosmetic: on the day of the move, both rungs will be
|
|
194
|
+
// true at once, and the package has to win, otherwise the consumer silently keeps building with
|
|
195
|
+
// their old copy. Plant BOTH candidates and see which one is chosen.
|
|
196
|
+
const both = realpathSync(mkdtempSync(join(tmpdir(), "venues-both-")));
|
|
197
|
+
const pkgVenues = join(both, "node_modules", "paperlint", "venues");
|
|
198
|
+
const skillVenues = join(
|
|
199
|
+
both,
|
|
200
|
+
".claude",
|
|
201
|
+
"skills",
|
|
202
|
+
"submit-paper",
|
|
203
|
+
"references",
|
|
204
|
+
"venues",
|
|
205
|
+
);
|
|
206
|
+
for (const d of [pkgVenues, skillVenues]) mkdirSync(d, { recursive: true });
|
|
207
|
+
writeFileSync(
|
|
208
|
+
join(both, "node_modules", "paperlint", "package.json"),
|
|
209
|
+
JSON.stringify({
|
|
210
|
+
name: "paperlint",
|
|
211
|
+
version: "0.0.0",
|
|
212
|
+
exports: { "./venues/*": "./venues/*" },
|
|
213
|
+
}),
|
|
214
|
+
);
|
|
215
|
+
writeFileSync(join(pkgVenues, "paper-guards.tex"), "% from package\n");
|
|
216
|
+
writeFileSync(join(skillVenues, "paper-guards.tex"), "% from skill dir\n");
|
|
217
|
+
const race = run(["--print-venues"], {
|
|
218
|
+
cwd: both,
|
|
219
|
+
env: { ...process.env, CLAUDE_PROJECT_DIR: both },
|
|
220
|
+
});
|
|
221
|
+
assert.equal(
|
|
222
|
+
race.status,
|
|
223
|
+
0,
|
|
224
|
+
`both candidates are in place ⇒ resolution must succeed:\n${race.stderr}`,
|
|
225
|
+
);
|
|
226
|
+
assert.equal(
|
|
227
|
+
race.stdout.trim(),
|
|
228
|
+
pkgVenues,
|
|
229
|
+
"with both candidates present, the PACKAGE wins, not the skill directory",
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ── THE SAME LADDER IN THE PAPER's build.sh, AND WHY THE ASSERTION DIFFERS ───
|
|
234
|
+
//
|
|
235
|
+
// Before 2026-09-12 the paper's build script held a HARDCODED
|
|
236
|
+
// `export TEXINPUTS="$ROOT/.claude/skills/submit-paper/references/venues:"` — an address inside
|
|
237
|
+
// the skills directory that, after their move into the package, points into a void, and again
|
|
238
|
+
// SILENTLY: LaTeX's reaction to a missing \input is silence, not an error.
|
|
239
|
+
//
|
|
240
|
+
// The `--print-venues` trick can't be repeated here: build.sh has no mode that prints the
|
|
241
|
+
// decision without running pdflatex, and adding one just for the test means reshaping the
|
|
242
|
+
// paper's build script around the test. So the assertion is STRUCTURAL, and it deliberately
|
|
243
|
+
// anchors on the EXECUTABLE line, not on a substring anywhere: the file quotes the old path in a
|
|
244
|
+
// comment explaining the fix, and a search for "is this string present" would have caught the
|
|
245
|
+
// explanation instead of the defect.
|
|
246
|
+
//
|
|
247
|
+
// 🔴 THE SUBJECT OF THIS CHECK IS SOMEONE ELSE'S FILE, AND THAT SHAPES ITS WHOLE FORM. Before the
|
|
248
|
+
// move into the package, this block read ONE hardcoded path into the first consumer's private
|
|
249
|
+
// corpus. In the package no such path exists or can exist, so the property was pulled out into a
|
|
250
|
+
// pure function and is proven TWICE, per the "both halves" rule:
|
|
251
|
+
//
|
|
252
|
+
// 1. on FIXTURES — `build-clean.sh` must pass, `build-defect.sh` must fail. This is the only
|
|
253
|
+
// half that works in a checkout of the package itself, where there are no papers at all.
|
|
254
|
+
// Without it, the check in the package would be vacuous and would print a green zero;
|
|
255
|
+
// 2. on the consumer's REAL papers — via the `papers` carrier, not a path in code. There can be
|
|
256
|
+
// many consumers, each names the directory differently, and that carrier exists exactly for
|
|
257
|
+
// this.
|
|
258
|
+
//
|
|
259
|
+
// The counter below is printed ALWAYS and grows together with the verdict, not before it: "0
|
|
260
|
+
// consumer scripts" is an honest result (in a checkout of the package itself it should be zero),
|
|
261
|
+
// whereas a silently-produced "0" would be indistinguishable from a verified corpus.
|
|
262
|
+
{
|
|
263
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
264
|
+
|
|
265
|
+
/** The property of a paper's build script. A pure function — so it can also be BROKEN. */
|
|
266
|
+
const assertLadder = (build, where) => {
|
|
267
|
+
assert.doesNotMatch(
|
|
268
|
+
build,
|
|
269
|
+
/^\s*export\s+TEXINPUTS="\$ROOT\/\.claude\//m,
|
|
270
|
+
`${where}: is assigning TEXINPUTS the hardcoded skills-directory path again`,
|
|
271
|
+
);
|
|
272
|
+
assert.match(
|
|
273
|
+
build,
|
|
274
|
+
/^\s*export\s+TEXINPUTS="\$VENUES_DIR:"/m,
|
|
275
|
+
`${where}: must set TEXINPUTS from the resolved VENUES_DIR`,
|
|
276
|
+
);
|
|
277
|
+
assert.match(
|
|
278
|
+
build,
|
|
279
|
+
/require\.resolve\("paperlint\/venues\/paper-guards\.tex"\)/,
|
|
280
|
+
`${where}: the ladder's first rung (the package) must remain`,
|
|
281
|
+
);
|
|
282
|
+
assert.match(
|
|
283
|
+
build,
|
|
284
|
+
/^\s*kpsewhich paper-guards\.tex/m,
|
|
285
|
+
`${where}: after setting TEXINPUTS, must CONFIRM that LaTeX can see the file`,
|
|
286
|
+
);
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
// ── First half: fixtures. Both sides, otherwise "silent" is indistinguishable from "dead."
|
|
290
|
+
const fx = join(HERE, "..", "..", "fixtures", "render-paper");
|
|
291
|
+
assertLadder(
|
|
292
|
+
readFileSync(join(fx, "build-clean.sh"), "utf8"),
|
|
293
|
+
"fixture build-clean.sh",
|
|
294
|
+
);
|
|
295
|
+
assert.throws(
|
|
296
|
+
() =>
|
|
297
|
+
assertLadder(
|
|
298
|
+
readFileSync(join(fx, "build-defect.sh"), "utf8"),
|
|
299
|
+
"fixture build-defect.sh",
|
|
300
|
+
),
|
|
301
|
+
/TEXINPUTS/,
|
|
302
|
+
"the defective fixture must fail the check — otherwise the check isn't checking anything",
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
// ── Second half: the consumer's real papers, addressed via the `papers` carrier.
|
|
306
|
+
// `papersRoot()` THROWS when the directory doesn't exist; in a checkout of the package itself
|
|
307
|
+
// it doesn't, and that's not an error, it's the absence of a consumer. Catch exactly that case
|
|
308
|
+
// and say so out loud.
|
|
309
|
+
const root = (() => {
|
|
310
|
+
try {
|
|
311
|
+
return papersRoot(consumerPkg(), consumerRoot());
|
|
312
|
+
} catch {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
})();
|
|
316
|
+
|
|
317
|
+
let checked = 0;
|
|
318
|
+
if (root) {
|
|
319
|
+
const base = join(consumerRoot(), root);
|
|
320
|
+
for (const entry of readdirSync(base, { withFileTypes: true })) {
|
|
321
|
+
if (!entry.isDirectory()) continue;
|
|
322
|
+
const script = join(base, entry.name, "build.sh");
|
|
323
|
+
if (!existsSync(script)) continue;
|
|
324
|
+
// The count grows TOGETHER WITH the verdict, not ahead of it.
|
|
325
|
+
assertLadder(
|
|
326
|
+
readFileSync(script, "utf8"),
|
|
327
|
+
`${root}/${entry.name}/build.sh`,
|
|
328
|
+
);
|
|
329
|
+
checked += 1;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
console.log(
|
|
334
|
+
` build.sh ladder: 2 fixture(s) + ${checked} consumer script(s)` +
|
|
335
|
+
(root === null
|
|
336
|
+
? " (no papers root on disk — package checkout)"
|
|
337
|
+
: ` under ${root}/`),
|
|
338
|
+
);
|
|
339
|
+
}
|