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,479 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* bib-authors.mjs — does our author list match the VERSION we claim to cite?
|
|
4
|
+
*
|
|
5
|
+
* The gap this fills, stated precisely so it does not drift into its neighbour:
|
|
6
|
+
* verify-cites.mjs asks "does this citation exist, and does the DOI point at it?"
|
|
7
|
+
* this script asks "we say @inproceedings{...NeurIPS 2023}; is our author list
|
|
8
|
+
* the NeurIPS one, or did we copy the arXiv preprint's?"
|
|
9
|
+
*
|
|
10
|
+
* 🔴 Why this is its own check (measured 2026-08-24, on a paper a `verify-citations`
|
|
11
|
+
* pass had already reported as "ALL 23 cites verified real"):
|
|
12
|
+
*
|
|
13
|
+
* schick2023toolformer — ours 8 authors. NeurIPS 2023 has NINE. Eric Hambro, a real
|
|
14
|
+
* person, was simply absent from a citation in an archival
|
|
15
|
+
* publication. The arXiv preprint has 8 — that is where ours
|
|
16
|
+
* came from.
|
|
17
|
+
* dehghani2022efficiency — ours: Dehghani, Arnab, Beyer, Vaswani, Tay.
|
|
18
|
+
* ICLR 2022: Dehghani, TAY, Arnab, Beyer, Vaswani.
|
|
19
|
+
* Again the preprint's list, under a proceedings entry.
|
|
20
|
+
*
|
|
21
|
+
* Both are invisible to an existence check: the paper is real, the id resolves, the title
|
|
22
|
+
* matches. The defect lives only in the gap between the venue we NAME and the metadata we
|
|
23
|
+
* CARRY, and nothing in the pipeline was looking there.
|
|
24
|
+
*
|
|
25
|
+
* DBLP is the source because it indexes both records separately and says so —
|
|
26
|
+
* "NeurIPS 2023" and "CoRR 2023" come back as two hits with different author lists. That
|
|
27
|
+
* side-by-side is the cleanest evidence available for this question.
|
|
28
|
+
*
|
|
29
|
+
* ⚠️ Discovery credit and a deliberate NON-dependency: this class was found by running
|
|
30
|
+
* `rebiber` (yuchenlin/rebiber), which rewrites bib entries to their DBLP records. We do
|
|
31
|
+
* NOT depend on it here. Two reasons, both practical: its install needs a Python toolchain
|
|
32
|
+
* that fights modern setuptools (bibtexparser wheel fails with `AttributeError:
|
|
33
|
+
* install_layout`; the workaround is vendoring a tarball), and its output REPLACES entries
|
|
34
|
+
* with DBLP's very long official booktitles, which is wrong for a page-limited paper. What
|
|
35
|
+
* we actually needed was the comparison, and that is one HTTPS call with no dependencies.
|
|
36
|
+
*
|
|
37
|
+
* 🔴 The normalisation below is load-bearing, not tidiness. A first cut of this comparison
|
|
38
|
+
* reported 13 mismatches on our bibliography, of which ELEVEN were "Last, First" vs
|
|
39
|
+
* "First Last" — pure formatting. A checker that cries 13 when 2 are real is read once and
|
|
40
|
+
* then ignored; that exact death is already recorded in this repo (a skill-pointer check
|
|
41
|
+
* that reported 33 findings of which 22 were live files). So: compare SURNAME SEQUENCES,
|
|
42
|
+
* and report nothing else.
|
|
43
|
+
*
|
|
44
|
+
* Usage: node bib-authors.mjs <paper-dir-or-.bib-or-.tex> [--json]
|
|
45
|
+
* Exit: 0 = no author-set/order differences 1 = differences found 2 = usage/IO error
|
|
46
|
+
*/
|
|
47
|
+
import { readFileSync, existsSync, readdirSync } from "node:fs";
|
|
48
|
+
import { join, extname } from "node:path";
|
|
49
|
+
import {
|
|
50
|
+
headings as mdHeadings,
|
|
51
|
+
requireMarkdown,
|
|
52
|
+
} from "../../../lib/markdown.mjs";
|
|
53
|
+
import { isMain } from "../../paper-pipeline/scripts/consumer.mjs";
|
|
54
|
+
|
|
55
|
+
const DBLP = "https://dblp.org/search/publ/api";
|
|
56
|
+
|
|
57
|
+
/* ---------- input: a .bib, a .tex with filecontents, or a paper dir ---------- */
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Some papers carry no BibTeX at all. `compile-rules-2026` writes its 67 references as a
|
|
61
|
+
* hand-numbered markdown list that `repro/md2acl.py` turns into ACL format at build time:
|
|
62
|
+
*
|
|
63
|
+
* 12. N. F. Liu, K. Lin, J. Hewitt. *Lost in the Middle: How Language Models Use Long
|
|
64
|
+
* Contexts.* TACL, 2024. arXiv:2307.03172.
|
|
65
|
+
*
|
|
66
|
+
* 🔴 Exempting such a paper would be the wrong call, and measurably so: a hand-written author
|
|
67
|
+
* list has NO machine-checkable source, so it is the MORE exposed of the two formats, not the
|
|
68
|
+
* less. The class this script hunts lives exactly where an entry names a published venue and an
|
|
69
|
+
* arXiv id in the same breath — which is most of this list.
|
|
70
|
+
*
|
|
71
|
+
* Authors are taken only from the text BEFORE the first `*`, on purpose: entries here sometimes
|
|
72
|
+
* append a second work ("— and the reanalysis: S. B. Hossain, ...") after the title, and folding
|
|
73
|
+
* those names in would invent co-authors that the first work does not have.
|
|
74
|
+
*/
|
|
75
|
+
export function parseMarkdownRefs(text) {
|
|
76
|
+
// 🔴 The heading comes FROM THE PARSER, not from a regex (the base's rule "parse markdown with a
|
|
77
|
+
// parser"). The previous `text.search(/^#+\s*References\s*$/m)` opened the bibliography on a
|
|
78
|
+
// `## References` line QUOTED inside a ``` fence — and the papers in this repo quote their own
|
|
79
|
+
// markup in chunks. Exactly this assert already stands at a neighbour
|
|
80
|
+
// (`paper-pipeline/scripts/extract-ref-facts.harness.mjs`: "a heading inside a ``` block is not a
|
|
81
|
+
// heading"), that is, the class was known and was being reproduced here again.
|
|
82
|
+
// `requireMarkdown()` — so that a missing markdown-it fails LOUDLY: with an empty parser
|
|
83
|
+
// `headings()` returns [], and the gate would report "no references" instead of refusing. That is
|
|
84
|
+
// the same trade of one silent failure for another that the rule was written against.
|
|
85
|
+
requireMarkdown();
|
|
86
|
+
const h = mdHeadings(text).find((x) => /^References\s*$/u.test(x.text));
|
|
87
|
+
if (!h) return [];
|
|
88
|
+
const body = text.slice(h.offset);
|
|
89
|
+
const out = [];
|
|
90
|
+
// 🔴 `$(?![\s\S])`, NOT `\Z`. JavaScript has no `\Z` anchor — outside a unicode-mode pattern it
|
|
91
|
+
// is an identity escape meaning the LETTER Z, so the first version of this lookahead ended entry
|
|
92
|
+
// 1 in the middle of "J. Zhou", at the Z. Measured: 48 of 67 references parsed, and the missing
|
|
93
|
+
// 19 looked like ordinary gaps rather than a bug. Same family as the `\b`-over-Cyrillic defect
|
|
94
|
+
// already recorded in this repo: an escape that means one thing in Perl/Python and another here.
|
|
95
|
+
const re = /^(\d+)\.[ \t]+([\s\S]*?)(?=^\d+\.[ \t]|^#|$(?![\s\S]))/gm;
|
|
96
|
+
let m;
|
|
97
|
+
while ((m = re.exec(body))) {
|
|
98
|
+
const entry = m[2].replace(/\s*\n\s*/g, " ").trim();
|
|
99
|
+
const t = entry.match(/\*([^*]+)\*/);
|
|
100
|
+
if (!t) {
|
|
101
|
+
// No italic title and no author list — in this corpus these are SOFTWARE references
|
|
102
|
+
// (npm packages, repos). Nothing for DBLP to disagree with. They are surfaced as
|
|
103
|
+
// not-applicable rather than dropped: an entry that silently vanishes between the file
|
|
104
|
+
// and the report is indistinguishable from an entry that passed.
|
|
105
|
+
out.push({
|
|
106
|
+
type: "mdref",
|
|
107
|
+
key: `ref${m[1]}`,
|
|
108
|
+
unparsed: true,
|
|
109
|
+
author: "",
|
|
110
|
+
title: "",
|
|
111
|
+
booktitle: "",
|
|
112
|
+
journal: "",
|
|
113
|
+
});
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const authorPart = entry
|
|
117
|
+
.slice(0, entry.indexOf("*"))
|
|
118
|
+
.trim()
|
|
119
|
+
.replace(/[.,;]\s*$/, "");
|
|
120
|
+
const rest = entry.slice(entry.indexOf(t[0]) + t[0].length);
|
|
121
|
+
out.push({
|
|
122
|
+
type: "mdref",
|
|
123
|
+
key: `ref${m[1]}`,
|
|
124
|
+
author: authorPart
|
|
125
|
+
.split(/,\s*|\s+and\s+/)
|
|
126
|
+
.filter(Boolean)
|
|
127
|
+
.join(" and "),
|
|
128
|
+
title: t[1].replace(/\.$/, "").trim(),
|
|
129
|
+
booktitle: rest.trim(),
|
|
130
|
+
journal: "",
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function bibTextFrom(target) {
|
|
137
|
+
let file = target;
|
|
138
|
+
if (!existsSync(file)) die(`no such path: ${file}`);
|
|
139
|
+
if (!extname(file)) {
|
|
140
|
+
const names = readdirSync(file);
|
|
141
|
+
// 🔴 THE CANONICAL NAME FIRST, then the only candidate, and never "whichever
|
|
142
|
+
// turns up first" (review #189). It used to be `names.find(f => f.endsWith(".tex"))`,
|
|
143
|
+
// that is, the first in directory order. Measured: one of the papers in the corpus
|
|
144
|
+
// has FIVE drafts lying around (paper-CONSTRUCTIVE-…, paper-FOLDED-…, paper-SAFE-…),
|
|
145
|
+
// while PIPELINE-STATUS.md names `paper.tex` as the submitted source. The author
|
|
146
|
+
// check went off into a stale draft and printed a verdict about the WRONG
|
|
147
|
+
// bibliography — a gate that checks the wrong file is worse than a missing one,
|
|
148
|
+
// because it says "checked".
|
|
149
|
+
const pick = (ext) => {
|
|
150
|
+
const canon = names.find((f) => f === `paper${ext}`);
|
|
151
|
+
if (canon) return canon;
|
|
152
|
+
const all = names.filter((f) => f.endsWith(ext)).sort();
|
|
153
|
+
if (all.length === 1) return all[0];
|
|
154
|
+
if (all.length > 1) {
|
|
155
|
+
// We do not fail: a new check here would set up one more surface, and its
|
|
156
|
+
// home is decided by the ladder in the `CLAUDE.md` next to the papers, not by
|
|
157
|
+
// this script (the `frozen-checks` ratchet is what guards that). It is enough
|
|
158
|
+
// to SAY it out loud and to choose DETERMINISTICALLY — the header below prints
|
|
159
|
+
// which file was checked anyway, so the reader sees the choice.
|
|
160
|
+
console.error(
|
|
161
|
+
`⚠️ ${file} holds several ${ext} files and no canonical paper${ext}: ` +
|
|
162
|
+
`${all.join(", ")} — took ${all[0]}. If that is the wrong one, name the file explicitly.`,
|
|
163
|
+
);
|
|
164
|
+
return all[0];
|
|
165
|
+
}
|
|
166
|
+
return undefined;
|
|
167
|
+
};
|
|
168
|
+
const found =
|
|
169
|
+
pick(".bib") ||
|
|
170
|
+
pick(".tex") ||
|
|
171
|
+
names.find((f) => f === "paper.md" || f === "draft.md");
|
|
172
|
+
if (!found) die(`no .bib, .tex or paper.md in ${file}`);
|
|
173
|
+
file = join(file, found);
|
|
174
|
+
}
|
|
175
|
+
const text = readFileSync(file, "utf-8");
|
|
176
|
+
if (file.endsWith(".md")) return { text, file, markdown: true };
|
|
177
|
+
if (file.endsWith(".bib")) return { text, file };
|
|
178
|
+
// A .tex may carry the bibliography inline via filecontents — that is how our papers do it.
|
|
179
|
+
const m = text.match(
|
|
180
|
+
/\\begin\{filecontents\*?\}(?:\[[^\]]*\])?\{[^}]*\.bib\}\r?\n([\s\S]*?)\\end\{filecontents\*?\}/,
|
|
181
|
+
);
|
|
182
|
+
if (m) return { text: m[1], file };
|
|
183
|
+
const sibling = join(file, "..", "refs.bib");
|
|
184
|
+
if (existsSync(sibling))
|
|
185
|
+
return { text: readFileSync(sibling, "utf-8"), file: sibling };
|
|
186
|
+
die(
|
|
187
|
+
`no bibliography found in ${file} (no filecontents block, no refs.bib beside it)`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/* ---------- a deliberately small bib reader ----------
|
|
192
|
+
* Only three fields are needed (type, key, author, title, booktitle/journal), and a full
|
|
193
|
+
* BibTeX grammar would be a second thing to maintain. Brace-depth counting is enough and
|
|
194
|
+
* is exercised by the colocated test. */
|
|
195
|
+
function parseBib(text) {
|
|
196
|
+
const out = [];
|
|
197
|
+
const re = /@(\w+)\s*\{\s*([^,\s]+)\s*,/g;
|
|
198
|
+
let m;
|
|
199
|
+
while ((m = re.exec(text))) {
|
|
200
|
+
const [, type, key] = m;
|
|
201
|
+
let i = re.lastIndex,
|
|
202
|
+
depth = 1;
|
|
203
|
+
while (i < text.length && depth > 0) {
|
|
204
|
+
if (text[i] === "{") depth++;
|
|
205
|
+
else if (text[i] === "}") depth--;
|
|
206
|
+
i++;
|
|
207
|
+
}
|
|
208
|
+
const body = text.slice(re.lastIndex, i - 1);
|
|
209
|
+
out.push({ type: type.toLowerCase(), key, ...fields(body) });
|
|
210
|
+
}
|
|
211
|
+
return out;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function fields(body) {
|
|
215
|
+
const get = (name) => {
|
|
216
|
+
const r = new RegExp(`(?:^|[,\\s])${name}\\s*=\\s*`, "i");
|
|
217
|
+
const at = body.search(r);
|
|
218
|
+
if (at === -1) return "";
|
|
219
|
+
let i = body.indexOf("=", at) + 1;
|
|
220
|
+
while (/\s/.test(body[i])) i++;
|
|
221
|
+
if (body[i] === "{") {
|
|
222
|
+
let depth = 1,
|
|
223
|
+
j = i + 1;
|
|
224
|
+
while (j < body.length && depth > 0) {
|
|
225
|
+
if (body[j] === "{") depth++;
|
|
226
|
+
else if (body[j] === "}") depth--;
|
|
227
|
+
j++;
|
|
228
|
+
}
|
|
229
|
+
return body.slice(i + 1, j - 1);
|
|
230
|
+
}
|
|
231
|
+
if (body[i] === '"') {
|
|
232
|
+
const j = body.indexOf('"', i + 1);
|
|
233
|
+
return body.slice(i + 1, j);
|
|
234
|
+
}
|
|
235
|
+
const j = body.indexOf(",", i);
|
|
236
|
+
return body.slice(i, j === -1 ? undefined : j);
|
|
237
|
+
};
|
|
238
|
+
return {
|
|
239
|
+
author: get("author"),
|
|
240
|
+
title: get("title"),
|
|
241
|
+
booktitle: get("booktitle"),
|
|
242
|
+
journal: get("journal"),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/* ---------- normalisation: surnames only, in order ---------- */
|
|
247
|
+
|
|
248
|
+
const DEACCENT = (s) =>
|
|
249
|
+
s
|
|
250
|
+
.normalize("NFD")
|
|
251
|
+
.replace(/[̀-ͯ]/g, "")
|
|
252
|
+
.replace(/[{}\\'`"^~]/g, "");
|
|
253
|
+
|
|
254
|
+
export function surnames(authorField) {
|
|
255
|
+
if (!authorField) return [];
|
|
256
|
+
return DEACCENT(authorField)
|
|
257
|
+
.split(/\s+and\s+/i)
|
|
258
|
+
.map((p) => p.trim())
|
|
259
|
+
.filter(Boolean)
|
|
260
|
+
.map((p) => {
|
|
261
|
+
// Reduce BOTH orders to the final token of the family name.
|
|
262
|
+
// "Berg, Jan van der" -> family part "Berg" -> "berg"
|
|
263
|
+
// "van der Berg, Jan" -> family part "van der Berg"-> "berg"
|
|
264
|
+
// "Jan van der Berg" -> last token -> "berg"
|
|
265
|
+
// 🔴 Taking the whole family part in the comma branch (the first version of this) made
|
|
266
|
+
// the two orders normalise DIFFERENTLY for any particle surname — "van der berg" vs
|
|
267
|
+
// "berg" — so every Dutch/German co-author would have read as a real disagreement
|
|
268
|
+
// against DBLP, which always writes "First Last". The colocated test caught it before
|
|
269
|
+
// this shipped; the assertion pinning it is "a multi-token surname keeps its last token,
|
|
270
|
+
// consistently in both orders".
|
|
271
|
+
const familyPart = p.includes(",") ? p.split(",")[0] : p;
|
|
272
|
+
const last = familyPart.trim().split(/\s+/).slice(-1)[0] || "";
|
|
273
|
+
return last
|
|
274
|
+
.trim()
|
|
275
|
+
.toLowerCase()
|
|
276
|
+
.replace(/[.\s-]+$/, "");
|
|
277
|
+
})
|
|
278
|
+
.filter((s) => s && s !== "others");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const truncated = (authorField) => /\band\s+others\b/i.test(authorField || "");
|
|
282
|
+
|
|
283
|
+
/** Is the entry claiming a PUBLISHED venue (as opposed to a preprint)? */
|
|
284
|
+
export function claimsPublished(e) {
|
|
285
|
+
const venue = `${e.booktitle} ${e.journal}`.trim();
|
|
286
|
+
if (!venue) return false;
|
|
287
|
+
return !/^\s*arxiv\b|arxiv preprint|\bcorr\b/i.test(venue);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/* ---------- DBLP ---------- */
|
|
291
|
+
|
|
292
|
+
async function dblpHits(title) {
|
|
293
|
+
const url = `${DBLP}/?q=${encodeURIComponent(title)}&format=json&h=6`;
|
|
294
|
+
// Per-request timeout: a check that hangs is indistinguishable from a check that is dead,
|
|
295
|
+
// and this one runs 25+ requests. Measured 2026-08-24: a single unbounded query stalled the
|
|
296
|
+
// whole run past two minutes while a healthy one answers in ~0.6 s.
|
|
297
|
+
const res = await fetch(url, {
|
|
298
|
+
headers: { "User-Agent": "bib-authors/1.0 (paper QA)" },
|
|
299
|
+
signal: AbortSignal.timeout(15_000),
|
|
300
|
+
});
|
|
301
|
+
if (res.status === 429)
|
|
302
|
+
throw Object.assign(new Error("DBLP 429"), { retryable: true });
|
|
303
|
+
if (!res.ok) throw new Error(`DBLP ${res.status}`);
|
|
304
|
+
const hits = (await res.json())?.result?.hits?.hit ?? [];
|
|
305
|
+
return hits.map((h) => {
|
|
306
|
+
const a = h.info?.authors?.author;
|
|
307
|
+
const list = a ? (Array.isArray(a) ? a : [a]) : [];
|
|
308
|
+
return {
|
|
309
|
+
venue: h.info?.venue ?? "",
|
|
310
|
+
year: h.info?.year ?? "",
|
|
311
|
+
type: h.info?.type ?? "",
|
|
312
|
+
title: h.info?.title ?? "",
|
|
313
|
+
// DBLP disambiguates people as "Mostafa Dehghani 0001" — the digits are not a name.
|
|
314
|
+
authors: list.map((p) => String(p.text).replace(/\s+\d{4}$/, "")),
|
|
315
|
+
};
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const isPreprintRecord = (h) =>
|
|
320
|
+
/^corr$/i.test(h.venue) || /informal/i.test(h.type);
|
|
321
|
+
const looseTitle = (s) =>
|
|
322
|
+
DEACCENT(s)
|
|
323
|
+
.toLowerCase()
|
|
324
|
+
.replace(/[^a-z0-9]+/g, "");
|
|
325
|
+
|
|
326
|
+
/* ---------- the comparison ---------- */
|
|
327
|
+
|
|
328
|
+
export function compare(ourSurnames, theirSurnames) {
|
|
329
|
+
const missing = theirSurnames.filter((x) => !ourSurnames.includes(x));
|
|
330
|
+
const extra = ourSurnames.filter((x) => !theirSurnames.includes(x));
|
|
331
|
+
const orderDiffers =
|
|
332
|
+
missing.length === 0 &&
|
|
333
|
+
extra.length === 0 &&
|
|
334
|
+
ourSurnames.join("|") !== theirSurnames.join("|");
|
|
335
|
+
return { missing, extra, orderDiffers };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function die(msg) {
|
|
339
|
+
console.error(`bib-authors: ${msg}`);
|
|
340
|
+
process.exit(2);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async function main() {
|
|
344
|
+
const args = process.argv.slice(2).filter((a) => a !== "--json");
|
|
345
|
+
const asJson = process.argv.includes("--json");
|
|
346
|
+
if (!args[0])
|
|
347
|
+
die("usage: bib-authors.mjs <paper-dir|file.bib|file.tex> [--json]");
|
|
348
|
+
|
|
349
|
+
const { text, file, markdown } = bibTextFrom(args[0]);
|
|
350
|
+
const parsed = markdown ? parseMarkdownRefs(text) : parseBib(text);
|
|
351
|
+
const entries = parsed.filter((e) => e.title && e.author);
|
|
352
|
+
const findings = [];
|
|
353
|
+
const skipped = []; // legitimately not applicable
|
|
354
|
+
const unchecked = []; // we FAILED to check — must never be reported as a pass
|
|
355
|
+
|
|
356
|
+
for (const e of parsed.filter((x) => x.unparsed)) {
|
|
357
|
+
skipped.push({
|
|
358
|
+
key: e.key,
|
|
359
|
+
why: "no author/title — a reference to software or a dataset, DBLP does not apply",
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
for (const e of entries) {
|
|
364
|
+
if (!claimsPublished(e)) continue; // a preprint entry is allowed to carry preprint metadata
|
|
365
|
+
if (truncated(e.author)) {
|
|
366
|
+
skipped.push({
|
|
367
|
+
key: e.key,
|
|
368
|
+
why: "author list ends in `and others` — completeness not checkable",
|
|
369
|
+
});
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
// 🔴 A failed lookup is NOT a skip. Measured 2026-08-24: the first run reported
|
|
373
|
+
// "PASS: no author-list disagreement" while NINE of 27 entries had never been examined —
|
|
374
|
+
// five of them because DBLP answered 429. A verdict printed over unexamined data is the
|
|
375
|
+
// "counter that counts what it ignores" failure this repo already has written down.
|
|
376
|
+
// So: retry transient failures, then record them in a SEPARATE bucket that suppresses PASS.
|
|
377
|
+
let hits = null;
|
|
378
|
+
for (let attempt = 0; attempt < 3 && hits === null; attempt++) {
|
|
379
|
+
try {
|
|
380
|
+
hits = await dblpHits(e.title.replace(/[{}]/g, ""));
|
|
381
|
+
} catch (err) {
|
|
382
|
+
const last = attempt === 2;
|
|
383
|
+
if (last)
|
|
384
|
+
unchecked.push({
|
|
385
|
+
key: e.key,
|
|
386
|
+
why: `DBLP lookup failed: ${err.message}`,
|
|
387
|
+
});
|
|
388
|
+
else
|
|
389
|
+
await new Promise((r) => {
|
|
390
|
+
setTimeout(r, 1500 * (attempt + 1));
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (hits === null) continue;
|
|
395
|
+
const want = looseTitle(e.title);
|
|
396
|
+
const same = hits.filter((h) => looseTitle(h.title) === want);
|
|
397
|
+
// We claim the published version, so compare against the published record, never CoRR.
|
|
398
|
+
const rec = same.find((h) => !isPreprintRecord(h));
|
|
399
|
+
if (!rec) {
|
|
400
|
+
skipped.push({
|
|
401
|
+
key: e.key,
|
|
402
|
+
why: same.length
|
|
403
|
+
? "only a preprint record on DBLP"
|
|
404
|
+
: "no DBLP title match",
|
|
405
|
+
});
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
const ours = surnames(e.author);
|
|
409
|
+
const theirs = surnames(rec.authors.join(" and "));
|
|
410
|
+
const d = compare(ours, theirs);
|
|
411
|
+
if (d.missing.length || d.extra.length || d.orderDiffers) {
|
|
412
|
+
findings.push({
|
|
413
|
+
key: e.key,
|
|
414
|
+
venue: `${rec.venue} ${rec.year}`.trim(),
|
|
415
|
+
ours,
|
|
416
|
+
theirs,
|
|
417
|
+
...d,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
// A braced body rather than a concise arrow — see `no-promise-executor-return` (2026-08-28).
|
|
421
|
+
await new Promise((r) => {
|
|
422
|
+
setTimeout(r, 900);
|
|
423
|
+
}); // DBLP asks for gentle clients; 350 ms drew 429s
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (asJson) {
|
|
427
|
+
console.log(
|
|
428
|
+
JSON.stringify(
|
|
429
|
+
{ file, entries: entries.length, findings, skipped, unchecked },
|
|
430
|
+
null,
|
|
431
|
+
2,
|
|
432
|
+
),
|
|
433
|
+
);
|
|
434
|
+
} else {
|
|
435
|
+
console.log(`== bib-authors: ${file} ==`);
|
|
436
|
+
for (const f of findings) {
|
|
437
|
+
console.log(`\n 🔴 ${f.key} (DBLP: ${f.venue})`);
|
|
438
|
+
if (f.missing.length)
|
|
439
|
+
console.log(` MISSING from ours : ${f.missing.join(", ")}`);
|
|
440
|
+
if (f.extra.length)
|
|
441
|
+
console.log(` EXTRA in ours : ${f.extra.join(", ")}`);
|
|
442
|
+
if (f.orderDiffers) {
|
|
443
|
+
console.log(` ORDER differs`);
|
|
444
|
+
console.log(` ours : ${f.ours.join(" > ")}`);
|
|
445
|
+
console.log(` DBLP : ${f.theirs.join(" > ")}`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
for (const s of skipped) console.log(` · n/a ${s.key} — ${s.why}`);
|
|
449
|
+
for (const u of unchecked)
|
|
450
|
+
console.log(` ⚠️ NOT CHECKED ${u.key} — ${u.why}`);
|
|
451
|
+
console.log(
|
|
452
|
+
`\n-- ${entries.length} entries · ${findings.length} difference(s) · ` +
|
|
453
|
+
`${skipped.length} not applicable · ${unchecked.length} NOT CHECKED`,
|
|
454
|
+
);
|
|
455
|
+
console.log(
|
|
456
|
+
findings.length
|
|
457
|
+
? "FAIL: our author list disagrees with the version we claim to cite."
|
|
458
|
+
: unchecked.length
|
|
459
|
+
? `PARTIAL: no disagreement among the entries reached, but ${unchecked.length} could not be checked — this is NOT a pass.`
|
|
460
|
+
: "PASS: no author-list disagreement.",
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
// 🔴 THE UNCHECKED IS NOT A PASS EITHER (review #189). The exit code depended only
|
|
464
|
+
// on findings, so a run where DBLP did not answer EVEN ONCE (a timeout, a series of
|
|
465
|
+
// 429s) exited 0 — and the caller read an author audit THAT NEVER RAN as passed.
|
|
466
|
+
// The text next to it already said "this is NOT a pass", but a human reads the text
|
|
467
|
+
// while CI reads the exit code. A divergence between what the script SAYS and what it
|
|
468
|
+
// REPORTS to its caller is the same class as "the gate checked the wrong file".
|
|
469
|
+
// Different codes so the caller can tell them apart: 1 — there are disagreements,
|
|
470
|
+
// 2 — the audit is incomplete.
|
|
471
|
+
process.exit(findings.length ? 1 : unchecked.length ? 2 : 0);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// 🔴 `isMain`, NOT `import.meta.url === `file://${process.argv[1]}``. Node resolves the entry point
|
|
475
|
+
// to its REAL path for `import.meta.url` but leaves `process.argv[1]` as typed, so through a symlink
|
|
476
|
+
// the two are not equal and the CLI silently does not execute — the process exits 0 having done
|
|
477
|
+
// nothing. The consumer reaches these scripts precisely through a symlink. Observed 14.09 on run
|
|
478
|
+
// 34784079821: `extract-pdf-facts.mjs --strict` returned RC=0 and created no facts file.
|
|
479
|
+
if (isMain(import.meta.url)) await main();
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Unit tests for bib-authors.mjs — the pure half, no network.
|
|
4
|
+
*
|
|
5
|
+
* The network half (DBLP) is deliberately NOT mocked: a mock of DBLP would assert that our
|
|
6
|
+
* mock behaves, which is the "checker that never failed" trap. What is worth pinning here is
|
|
7
|
+
* the NORMALISATION, because that is the part whose failure is silent and expensive:
|
|
8
|
+
*
|
|
9
|
+
* too loose -> real differences vanish (a dropped author reads as a formatting quirk)
|
|
10
|
+
* too strict -> 13 findings of which 11 are "Last, First" vs "First Last", the checker is
|
|
11
|
+
* read once and never again. Measured on our own bibliography 2026-08-24.
|
|
12
|
+
*
|
|
13
|
+
* Run: npx vitest run skills/verify-citations/scripts/bib-authors.test.mjs
|
|
14
|
+
*/
|
|
15
|
+
import assert from "node:assert/strict";
|
|
16
|
+
import { test } from "vitest";
|
|
17
|
+
import {
|
|
18
|
+
surnames,
|
|
19
|
+
compare,
|
|
20
|
+
claimsPublished,
|
|
21
|
+
parseMarkdownRefs,
|
|
22
|
+
} from "./bib-authors.mjs";
|
|
23
|
+
|
|
24
|
+
const t = test;
|
|
25
|
+
|
|
26
|
+
console.log("bib-authors — normalisation");
|
|
27
|
+
|
|
28
|
+
t("the two BibTeX name orders normalise to the SAME sequence", () => {
|
|
29
|
+
const a = surnames("Schick, Timo and Dwivedi-Yu, Jane and Scialom, Thomas");
|
|
30
|
+
const b = surnames("Timo Schick and Jane Dwivedi-Yu and Thomas Scialom");
|
|
31
|
+
assert.deepEqual(a, b);
|
|
32
|
+
assert.deepEqual(a, ["schick", "dwivedi-yu", "scialom"]);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
t("accents and TeX braces do not create a difference", () => {
|
|
36
|
+
assert.deepEqual(surnames("Dess{\\`i}, Roberto"), surnames("Roberto Dessì"));
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
t("`and others` is dropped, not treated as a surname", () => {
|
|
40
|
+
assert.deepEqual(surnames("Zheng, Lianmin and Yin, Liangsheng and others"), [
|
|
41
|
+
"zheng",
|
|
42
|
+
"yin",
|
|
43
|
+
]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
t(
|
|
47
|
+
"a multi-token surname keeps its last token, consistently in both orders",
|
|
48
|
+
() => {
|
|
49
|
+
assert.deepEqual(
|
|
50
|
+
surnames("van der Berg, Jan"),
|
|
51
|
+
surnames("Jan van der Berg"),
|
|
52
|
+
);
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
console.log("bib-authors — comparison");
|
|
57
|
+
|
|
58
|
+
t("identical sequences produce nothing", () => {
|
|
59
|
+
const d = compare(["raji", "denton"], ["raji", "denton"]);
|
|
60
|
+
assert.deepEqual(d, { missing: [], extra: [], orderDiffers: false });
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
t("REGRESSION — Toolformer: a dropped author is reported as MISSING", () => {
|
|
64
|
+
// Ours (arXiv preprint) had 8; NeurIPS 2023 has 9. Eric Hambro was absent from a citation
|
|
65
|
+
// in an archival publication until 2026-08-24. This assert is that exact paper.
|
|
66
|
+
const ours = surnames(
|
|
67
|
+
"Schick, Timo and Dwivedi-Yu, Jane and Dess{\\`i}, Roberto and Raileanu, Roberta and " +
|
|
68
|
+
"Lomeli, Maria and Zettlemoyer, Luke and Cancedda, Nicola and Scialom, Thomas",
|
|
69
|
+
);
|
|
70
|
+
const neurips = surnames(
|
|
71
|
+
"Timo Schick and Jane Dwivedi-Yu and Roberto Dessì and Roberta Raileanu and Maria Lomeli and " +
|
|
72
|
+
"Eric Hambro and Luke Zettlemoyer and Nicola Cancedda and Thomas Scialom",
|
|
73
|
+
);
|
|
74
|
+
const d = compare(ours, neurips);
|
|
75
|
+
assert.deepEqual(d.missing, ["hambro"]);
|
|
76
|
+
assert.deepEqual(d.extra, []);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
t(
|
|
80
|
+
"REGRESSION — Efficiency Misnomer: same people, preprint ORDER, reported as order",
|
|
81
|
+
() => {
|
|
82
|
+
const ours = surnames(
|
|
83
|
+
"Dehghani, Mostafa and Arnab, Anurag and Beyer, Lucas and Vaswani, Ashish and Tay, Yi",
|
|
84
|
+
);
|
|
85
|
+
const iclr = surnames(
|
|
86
|
+
"Mostafa Dehghani and Yi Tay and Anurag Arnab and Lucas Beyer and Ashish Vaswani",
|
|
87
|
+
);
|
|
88
|
+
const d = compare(ours, iclr);
|
|
89
|
+
assert.equal(
|
|
90
|
+
d.orderDiffers,
|
|
91
|
+
true,
|
|
92
|
+
"an order-only difference must still be a finding",
|
|
93
|
+
);
|
|
94
|
+
assert.deepEqual(d.missing, []);
|
|
95
|
+
assert.deepEqual(d.extra, []);
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
t(
|
|
100
|
+
"REGRESSION — Raji: order-only, found by the script itself after the other two were fixed",
|
|
101
|
+
() => {
|
|
102
|
+
const ours = surnames(
|
|
103
|
+
"Raji, Inioluwa Deborah and Bender, Emily M. and Paullada, Amandalynne and Denton, Emily and Hanna, Alex",
|
|
104
|
+
);
|
|
105
|
+
const neurips = surnames(
|
|
106
|
+
"Inioluwa Deborah Raji and Emily Denton and Emily M. Bender and Alex Hanna and Amandalynne Paullada",
|
|
107
|
+
);
|
|
108
|
+
assert.equal(compare(ours, neurips).orderDiffers, true);
|
|
109
|
+
},
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
t("an author we invented would be reported as EXTRA", () => {
|
|
113
|
+
assert.deepEqual(compare(["raji", "ghost"], ["raji"]).extra, ["ghost"]);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
console.log("bib-authors — scope");
|
|
117
|
+
|
|
118
|
+
t("a preprint entry is NOT held to a published author list", () => {
|
|
119
|
+
assert.equal(
|
|
120
|
+
claimsPublished({
|
|
121
|
+
booktitle: "",
|
|
122
|
+
journal: "arXiv preprint arXiv:2604.22750",
|
|
123
|
+
}),
|
|
124
|
+
false,
|
|
125
|
+
);
|
|
126
|
+
assert.equal(claimsPublished({ booktitle: "", journal: "CoRR" }), false);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
t("a proceedings entry IS in scope", () => {
|
|
130
|
+
assert.equal(
|
|
131
|
+
claimsPublished({
|
|
132
|
+
booktitle: "NeurIPS Datasets and Benchmarks Track",
|
|
133
|
+
journal: "",
|
|
134
|
+
}),
|
|
135
|
+
true,
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
console.log("bib-authors — where the bibliography begins");
|
|
140
|
+
|
|
141
|
+
// Both halves. "Silent on a fence" without "finds the real one" is indistinguishable from a broken parse.
|
|
142
|
+
t("a real ## References is parsed", () => {
|
|
143
|
+
const md = [
|
|
144
|
+
"# Paper",
|
|
145
|
+
"text",
|
|
146
|
+
"",
|
|
147
|
+
"## References",
|
|
148
|
+
"",
|
|
149
|
+
"1. A. Author. *A title*. In NeurIPS, 2023.",
|
|
150
|
+
].join("\n");
|
|
151
|
+
const refs = parseMarkdownRefs(md);
|
|
152
|
+
assert.equal(refs.length, 1);
|
|
153
|
+
assert.match(refs[0].title, /A title/);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
t("a ## References inside a ``` fence does NOT open the bibliography", () => {
|
|
157
|
+
const md = [
|
|
158
|
+
"# Paper",
|
|
159
|
+
"",
|
|
160
|
+
"The section is written like this:",
|
|
161
|
+
"",
|
|
162
|
+
"```markdown",
|
|
163
|
+
"## References",
|
|
164
|
+
"",
|
|
165
|
+
"1. Z. Ghost. *Not a reference, but a markup example*. In Nowhere, 2020.",
|
|
166
|
+
"```",
|
|
167
|
+
"",
|
|
168
|
+
"The end.",
|
|
169
|
+
].join("\n");
|
|
170
|
+
assert.deepEqual(
|
|
171
|
+
parseMarkdownRefs(md),
|
|
172
|
+
[],
|
|
173
|
+
"quoted markup is not a bibliography — the ^#+ regex counted it as a heading",
|
|
174
|
+
);
|
|
175
|
+
});
|