paperlint 8.0.0 → 9.0.1
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/CLAUDE.md +22 -0
- package/.github/CLAUDE.md.spec.ts +20 -0
- package/CLAUDE.md +59 -531
- package/CONTRIBUTING.md +7 -6
- package/README.md +4 -3
- package/dist/CLAUDE.md.spec.d.ts +3 -0
- package/dist/CLAUDE.md.spec.d.ts.map +1 -0
- package/dist/CLAUDE.md.spec.js +62 -0
- package/dist/CLAUDE.md.spec.js.map +1 -0
- package/dist/adapters/bibtex/index.d.ts +3 -0
- package/dist/adapters/bibtex/index.d.ts.map +1 -0
- package/dist/adapters/bibtex/index.js +103 -0
- package/dist/adapters/bibtex/index.js.map +1 -0
- package/dist/adapters/latex/filecontents.d.ts +33 -0
- package/dist/adapters/latex/filecontents.d.ts.map +1 -0
- package/dist/adapters/latex/filecontents.js +65 -0
- package/dist/adapters/latex/filecontents.js.map +1 -0
- package/dist/adapters/latex/includes.d.ts +3 -3
- package/dist/adapters/latex/includes.d.ts.map +1 -1
- package/dist/adapters/latex/includes.js +18 -10
- package/dist/adapters/latex/includes.js.map +1 -1
- package/dist/adapters/latex/index.d.ts.map +1 -1
- package/dist/adapters/latex/index.js +2 -0
- package/dist/adapters/latex/index.js.map +1 -1
- package/dist/adapters/latex/nodes.d.ts +12 -0
- package/dist/adapters/latex/nodes.d.ts.map +1 -1
- package/dist/adapters/latex/nodes.js +39 -0
- package/dist/adapters/latex/nodes.js.map +1 -1
- package/dist/adapters/latex/parse.d.ts +7 -1
- package/dist/adapters/latex/parse.d.ts.map +1 -1
- package/dist/adapters/latex/parse.js +55 -6
- package/dist/adapters/latex/parse.js.map +1 -1
- package/dist/adapters/memory/index.d.ts +3 -0
- package/dist/adapters/memory/index.d.ts.map +1 -1
- package/dist/adapters/memory/index.js +9 -0
- package/dist/adapters/memory/index.js.map +1 -1
- package/dist/adapters/references/index.d.ts.map +1 -1
- package/dist/adapters/references/index.js +27 -24
- package/dist/adapters/references/index.js.map +1 -1
- package/dist/adapters/sources-record/index.d.ts +9 -0
- package/dist/adapters/sources-record/index.d.ts.map +1 -0
- package/dist/adapters/sources-record/index.js +68 -0
- package/dist/adapters/sources-record/index.js.map +1 -0
- package/dist/adapters/tex-output/blg.d.ts +9 -0
- package/dist/adapters/tex-output/blg.d.ts.map +1 -0
- package/dist/adapters/tex-output/blg.js +20 -0
- package/dist/adapters/tex-output/blg.js.map +1 -0
- package/dist/adapters/tex-output/fls.d.ts +8 -0
- package/dist/adapters/tex-output/fls.d.ts.map +1 -0
- package/dist/adapters/tex-output/fls.js +16 -0
- package/dist/adapters/tex-output/fls.js.map +1 -0
- package/dist/adapters/tex-output/index.d.ts +7 -0
- package/dist/adapters/tex-output/index.d.ts.map +1 -0
- package/dist/adapters/tex-output/index.js +7 -0
- package/dist/adapters/tex-output/index.js.map +1 -0
- package/dist/bib-rules.d.ts +47 -0
- package/dist/bib-rules.d.ts.map +1 -0
- package/dist/bib-rules.js +127 -0
- package/dist/bib-rules.js.map +1 -0
- package/dist/build.d.ts +29 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +41 -25
- package/dist/build.js.map +1 -1
- package/dist/claim-provenance.d.ts +2 -2
- package/dist/cli.d.ts +6 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +30 -26
- package/dist/cli.js.map +1 -1
- package/dist/domain/paper-source.d.ts +1 -10
- package/dist/domain/paper-source.d.ts.map +1 -1
- package/dist/domain/paper-source.js +34 -24
- package/dist/domain/paper-source.js.map +1 -1
- package/dist/domain/paper-sources.d.ts +72 -0
- package/dist/domain/paper-sources.d.ts.map +1 -0
- package/dist/domain/paper-sources.js +18 -0
- package/dist/domain/paper-sources.js.map +1 -0
- package/dist/domain/sources-record.d.ts +102 -0
- package/dist/domain/sources-record.d.ts.map +1 -0
- package/dist/domain/sources-record.js +38 -0
- package/dist/domain/sources-record.js.map +1 -0
- package/dist/domain/tex-run.d.ts +25 -0
- package/dist/domain/tex-run.d.ts.map +1 -0
- package/dist/domain/tex-run.js +79 -0
- package/dist/domain/tex-run.js.map +1 -0
- package/dist/eslint-rules/CLAUDE.md.spec.d.ts +3 -0
- package/dist/eslint-rules/CLAUDE.md.spec.d.ts.map +1 -0
- package/dist/eslint-rules/CLAUDE.md.spec.js +39 -0
- package/dist/eslint-rules/CLAUDE.md.spec.js.map +1 -0
- package/dist/eslint-rules/install-path-literals.d.ts +1 -1
- package/dist/eslint-rules/install-path-literals.js +4 -4
- package/dist/eslint-rules/install-path-literals.js.map +1 -1
- package/dist/eslint-rules/paper-context.d.ts.map +1 -1
- package/dist/eslint-rules/paper-context.js +4 -3
- package/dist/eslint-rules/paper-context.js.map +1 -1
- package/dist/eslint-rules/paper-typography.d.ts +0 -9
- package/dist/eslint-rules/paper-typography.d.ts.map +1 -1
- package/dist/eslint-rules/paper-typography.js +12 -5
- package/dist/eslint-rules/paper-typography.js.map +1 -1
- package/dist/eslint-rules/pdf-last-page-balance.js +1 -1
- package/dist/latex-log.d.ts +10 -0
- package/dist/latex-log.d.ts.map +1 -1
- package/dist/latex-log.js +10 -0
- package/dist/latex-log.js.map +1 -1
- package/dist/latex-loop.d.ts +2 -0
- package/dist/latex-loop.d.ts.map +1 -1
- package/dist/latex-loop.js.map +1 -1
- package/dist/lib/agent-cli-version.js +1 -1
- package/dist/lib/agent-cli-version.js.map +1 -1
- package/dist/package-dirs.d.ts.map +1 -1
- package/dist/package-dirs.js +3 -2
- package/dist/package-dirs.js.map +1 -1
- package/dist/paper-includes.d.ts +4 -7
- package/dist/paper-includes.d.ts.map +1 -1
- package/dist/paper-includes.js +29 -21
- package/dist/paper-includes.js.map +1 -1
- package/dist/paper-prose.d.ts +2 -2
- package/dist/paper-record.d.ts +25 -0
- package/dist/paper-record.d.ts.map +1 -0
- package/dist/paper-record.js +39 -0
- package/dist/paper-record.js.map +1 -0
- package/dist/paper-sources.d.ts +66 -0
- package/dist/paper-sources.d.ts.map +1 -0
- package/dist/paper-sources.js +92 -0
- package/dist/paper-sources.js.map +1 -0
- package/dist/ports/bib-reader.d.ts +18 -0
- package/dist/ports/bib-reader.d.ts.map +1 -0
- package/dist/ports/bib-reader.js +2 -0
- package/dist/ports/bib-reader.js.map +1 -0
- package/dist/ports/check-references.d.ts +9 -4
- package/dist/ports/check-references.d.ts.map +1 -1
- package/dist/ports/latex.d.ts +15 -0
- package/dist/ports/latex.d.ts.map +1 -1
- package/dist/ports/sources-codec.d.ts +11 -0
- package/dist/ports/sources-codec.d.ts.map +1 -0
- package/dist/ports/sources-codec.js +2 -0
- package/dist/ports/sources-codec.js.map +1 -0
- package/dist/ports/tex-output.d.ts +17 -0
- package/dist/ports/tex-output.d.ts.map +1 -0
- package/dist/ports/tex-output.js +2 -0
- package/dist/ports/tex-output.js.map +1 -0
- package/dist/recorded-bibliography.d.ts +89 -0
- package/dist/recorded-bibliography.d.ts.map +1 -0
- package/dist/recorded-bibliography.js +136 -0
- package/dist/recorded-bibliography.js.map +1 -0
- package/dist/reference-rules.d.ts +5 -5
- package/dist/reference-rules.d.ts.map +1 -1
- package/dist/reference-rules.js +150 -77
- package/dist/reference-rules.js.map +1 -1
- package/dist/references.d.ts +71 -16
- package/dist/references.d.ts.map +1 -1
- package/dist/references.js +110 -26
- package/dist/references.js.map +1 -1
- package/dist/register-bands.d.ts +3 -3
- package/dist/register-bands.d.ts.map +1 -1
- package/dist/register-bands.js.map +1 -1
- package/dist/register.d.ts +2 -2
- package/dist/sources-record.d.ts +38 -0
- package/dist/sources-record.d.ts.map +1 -0
- package/dist/sources-record.js +123 -0
- package/dist/sources-record.js.map +1 -0
- package/dist/sources-rules.d.ts +44 -0
- package/dist/sources-rules.d.ts.map +1 -0
- package/dist/sources-rules.js +76 -0
- package/dist/sources-rules.js.map +1 -0
- package/dist/tex-paper.d.ts +10 -15
- package/dist/tex-paper.d.ts.map +1 -1
- package/dist/tex-paper.js +24 -40
- package/dist/tex-paper.js.map +1 -1
- package/dist/tex-requirements.js +1 -1
- package/dist/tex-venue-rules.d.ts +3 -3
- package/dist/tex-venue-rules.d.ts.map +1 -1
- package/dist/tex-venue-rules.js.map +1 -1
- package/dist/toolchain.js +1 -1
- package/docs/configuration.md +12 -4
- package/docs/design/paper-sources.md +197 -0
- package/docs/e2e.md +1 -1
- package/docs/incidents.md +5 -5
- package/docs/package-shape-options.md +1 -1
- package/docs/references.md +34 -8
- package/docs/rules/bib/reachable-entry.md +64 -0
- package/docs/rules/paper/sources-fresh.md +81 -0
- package/docs/rules.md +62 -57
- package/eslint-rules/CLAUDE.md +44 -0
- package/eslint-rules/CLAUDE.md.spec.ts +43 -0
- package/eslint-rules/install-path-literals.harness.mjs +1 -1
- package/eslint-rules/install-path-literals.ts +4 -4
- package/eslint-rules/paper-context.ts +4 -3
- package/eslint-rules/paper-typography.test.mjs +12 -72
- package/eslint-rules/paper-typography.ts +13 -6
- package/eslint-rules/pdf-last-page-balance.ts +1 -1
- package/eslint.config.mjs +28 -3
- package/fixtures/build-e2e/aidc/paper.tex +1 -1
- package/fixtures/build-e2e/anon-ack/paper.tex +1 -1
- package/fixtures/build-e2e/anon-author/paper.tex +1 -1
- package/fixtures/build-e2e/anon-bib/paper.tex +1 -1
- package/fixtures/build-e2e/anon-href/paper.tex +1 -1
- package/fixtures/build-e2e/anon-macro/paper.tex +1 -1
- package/fixtures/build-e2e/anon-pdfauthor/paper.tex +1 -1
- package/fixtures/paper-sources/p1/paper.tex +30 -0
- package/fixtures/paper-sources/p1/refs.bib +6 -0
- package/fixtures/paper-sources/p1/sections/dead.tex +6 -0
- package/fixtures/paper-sources/p1/sections/intro.tex +7 -0
- package/fixtures/paper-sources/p1/sections/parked.tex +6 -0
- package/fixtures/paper-sources/p1/tex-truth.json +37 -0
- package/fixtures/paper-sources/p1/versions/2026-01-01-review.tex +4 -0
- package/fixtures/paper-sources/v1-stale/paper.tex +14 -0
- package/fixtures/paper-sources/v1-stale/refs.bib +6 -0
- package/fixtures/paper-sources/v1-stale/tex-truth.json +32 -0
- package/fixtures/paper-sources/v10-ifthenelse/anon.bib +1 -0
- package/fixtures/paper-sources/v10-ifthenelse/paper.tex +9 -0
- package/fixtures/paper-sources/v10-ifthenelse/real.bib +1 -0
- package/fixtures/paper-sources/v10-ifthenelse/tex-truth.json +28 -0
- package/fixtures/paper-sources/v11-two-blocks/paper.tex +12 -0
- package/fixtures/paper-sources/v11-two-blocks/tex-truth.json +30 -0
- package/fixtures/paper-sources/v13-percent-text/paper.tex +6 -0
- package/fixtures/paper-sources/v13-percent-text/refs.bib +3 -0
- package/fixtures/paper-sources/v13-percent-text/tex-truth.json +30 -0
- package/fixtures/paper-sources/v15-after-end/paper.tex +6 -0
- package/fixtures/paper-sources/v15-after-end/refs.bib +1 -0
- package/fixtures/paper-sources/v15-after-end/tex-truth.json +17 -0
- package/fixtures/paper-sources/v16-included-block/bibblock.tex +3 -0
- package/fixtures/paper-sources/v16-included-block/paper.tex +7 -0
- package/fixtures/paper-sources/v16-included-block/refs.bib +1 -0
- package/fixtures/paper-sources/v16-included-block/tex-truth.json +35 -0
- package/fixtures/paper-sources/v17-include-order/bibsetup.tex +1 -0
- package/fixtures/paper-sources/v17-include-order/first.bib +1 -0
- package/fixtures/paper-sources/v17-include-order/later.bib +1 -0
- package/fixtures/paper-sources/v17-include-order/paper.tex +7 -0
- package/fixtures/paper-sources/v17-include-order/tex-truth.json +40 -0
- package/fixtures/paper-sources/v2-overwrite/paper.tex +14 -0
- package/fixtures/paper-sources/v2-overwrite/refs.bib +6 -0
- package/fixtures/paper-sources/v2-overwrite/tex-truth.json +30 -0
- package/fixtures/paper-sources/v22-parked-thebibliography/paper.tex +7 -0
- package/fixtures/paper-sources/v22-parked-thebibliography/tex-truth.json +17 -0
- package/fixtures/paper-sources/v23-parked-include/old.bib +1 -0
- package/fixtures/paper-sources/v23-parked-include/paper.tex +7 -0
- package/fixtures/paper-sources/v23-parked-include/parked.tex +1 -0
- package/fixtures/paper-sources/v23-parked-include/refs.bib +1 -0
- package/fixtures/paper-sources/v23-parked-include/tex-truth.json +28 -0
- package/fixtures/paper-sources/v24-quoted-fields/paper.tex +6 -0
- package/fixtures/paper-sources/v24-quoted-fields/refs.bib +5 -0
- package/fixtures/paper-sources/v24-quoted-fields/tex-truth.json +32 -0
- package/fixtures/paper-sources/v25-string-differs/paper.tex +10 -0
- package/fixtures/paper-sources/v25-string-differs/refs.bib +2 -0
- package/fixtures/paper-sources/v25-string-differs/tex-truth.json +32 -0
- package/fixtures/paper-sources/v26-comment-in-names/one.bib +1 -0
- package/fixtures/paper-sources/v26-comment-in-names/paper.tex +7 -0
- package/fixtures/paper-sources/v26-comment-in-names/tex-truth.json +32 -0
- package/fixtures/paper-sources/v26-comment-in-names/two.bib +1 -0
- package/fixtures/paper-sources/v27-shared-string/abbreviations.bib +2 -0
- package/fixtures/paper-sources/v27-shared-string/paper.tex +6 -0
- package/fixtures/paper-sources/v27-shared-string/references.bib +6 -0
- package/fixtures/paper-sources/v27-shared-string/tex-truth.json +31 -0
- package/fixtures/paper-sources/v28-shared-string-block/abbreviations.bib +2 -0
- package/fixtures/paper-sources/v28-shared-string-block/paper.tex +14 -0
- package/fixtures/paper-sources/v28-shared-string-block/tex-truth.json +33 -0
- package/fixtures/paper-sources/v29-generated-input/paper.tex +9 -0
- package/fixtures/paper-sources/v29-generated-input/tex-truth.json +24 -0
- package/fixtures/paper-sources/v3-declared/paper.bib +6 -0
- package/fixtures/paper-sources/v3-declared/paper.tex +6 -0
- package/fixtures/paper-sources/v3-declared/tex-truth.json +28 -0
- package/fixtures/paper-sources/v30-name-next-line/paper.tex +14 -0
- package/fixtures/paper-sources/v30-name-next-line/tex-truth.json +30 -0
- package/fixtures/paper-sources/v4-commented/paper.tex +14 -0
- package/fixtures/paper-sources/v4-commented/refs.bib +6 -0
- package/fixtures/paper-sources/v4-commented/tex-truth.json +28 -0
- package/fixtures/paper-sources/v5-percent-entry/paper.tex +20 -0
- package/fixtures/paper-sources/v5-percent-entry/tex-truth.json +37 -0
- package/fixtures/paper-sources/v6-unclosed/paper.tex +6 -0
- package/fixtures/paper-sources/v6-unclosed/refs.bib +5 -0
- package/fixtures/paper-sources/v6-unclosed/tex-truth.json +37 -0
- package/fixtures/paper-sources/v8-jobname/paper.tex +9 -0
- package/fixtures/paper-sources/v8-jobname/tex-truth.json +30 -0
- package/fixtures/paper-sources/v9-two-bibliographies/one.bib +1 -0
- package/fixtures/paper-sources/v9-two-bibliographies/paper.tex +7 -0
- package/fixtures/paper-sources/v9-two-bibliographies/tex-truth.json +35 -0
- package/fixtures/paper-sources/v9-two-bibliographies/two.bib +1 -0
- package/fixtures/paper-typography/clean-paper/paper.tex +1 -1
- package/fixtures/paper-typography/messy-paper/paper.tex +1 -1
- package/hooks/CLAUDE.md +34 -0
- package/hooks/CLAUDE.md.spec.ts +30 -0
- package/lib/agent-cli-version.ts +1 -1
- package/package.json +3 -11
- package/plugin/CLAUDE.md +22 -0
- package/plugin/CLAUDE.md.spec.ts +22 -0
- package/scripts/check.test.ts +23 -1
- package/scripts/check.ts +7 -3
- package/scripts/coverage-config.test.ts +21 -11
- package/scripts/layer-legacy-frozen.test.ts +41 -2
- package/scripts/layer-legacy-frozen.ts +31 -2
- package/skills/CLAUDE.md +19 -0
- package/skills/CLAUDE.md.spec.ts +18 -0
- package/skills/grade-paper-writing/SKILL.md +4 -3
- package/skills/grade-paper-writing/SKILL.md.spec.ts +3 -2
- package/skills/grade-paper-writing/prose-lint.mjs +11 -2
- package/skills/grade-paper-writing/prose-lint.test.ts +52 -4
- package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +83 -33
- package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +67 -115
- package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +128 -121
- package/skills/plan-paper-timeline/plan-paper-timeline.effects.harness.mjs +21 -102
- package/skills/verify-citations/SKILL.md +7 -4
- package/skills/verify-citations/SKILL.md.spec.ts +6 -3
- package/skills/verify-citations/scripts/bib-authors.check.test.mjs +17 -19
- package/skills/verify-citations/scripts/bib-authors.cli.test.ts +129 -46
- package/skills/verify-citations/scripts/bib-authors.d.mts +22 -2
- package/skills/verify-citations/scripts/bib-authors.mjs +57 -111
- package/skills/verify-citations/scripts/verify-cites.cli.test.ts +16 -0
- package/skills/verify-citations/scripts/verify-cites.d.mts +19 -3
- package/skills/verify-citations/scripts/verify-cites.mjs +42 -108
- package/skills/verify-citations/scripts/verify-cites.test.mjs +23 -36
- package/src/CLAUDE.md +23 -1
- package/src/CLAUDE.md.spec.ts +72 -0
- package/src/adapters/bibtex/index.test.ts +163 -0
- package/src/adapters/bibtex/index.ts +155 -0
- package/src/adapters/latex/filecontents.ts +114 -0
- package/src/adapters/latex/includes.ts +18 -10
- package/src/adapters/latex/index.test.ts +73 -0
- package/src/adapters/latex/index.ts +2 -0
- package/src/adapters/latex/nodes.ts +64 -0
- package/src/adapters/latex/parse.test.ts +38 -0
- package/src/adapters/latex/parse.ts +67 -6
- package/src/adapters/memory/index.ts +17 -0
- package/src/adapters/references/index.test.ts +75 -21
- package/src/adapters/references/index.ts +37 -32
- package/src/adapters/sources-record/index.test.ts +87 -0
- package/src/adapters/sources-record/index.ts +82 -0
- package/src/adapters/tex-output/blg.ts +27 -0
- package/src/adapters/tex-output/fls.ts +24 -0
- package/src/adapters/tex-output/index.test.ts +171 -0
- package/src/adapters/tex-output/index.ts +12 -0
- package/src/bib-rules.test.ts +356 -0
- package/src/bib-rules.ts +228 -0
- package/src/bibliography-at.test.ts +286 -0
- package/src/build.test.ts +210 -0
- package/src/build.ts +101 -36
- package/src/claim-provenance.test.ts +33 -7
- package/src/claim-provenance.ts +2 -2
- package/src/cli.harness.mjs +20 -10
- package/src/cli.test.ts +14 -10
- package/src/cli.ts +37 -28
- package/src/domain/paper-source.test.ts +58 -20
- package/src/domain/paper-source.ts +38 -42
- package/src/domain/paper-sources.ts +94 -0
- package/src/domain/sources-record.test.ts +71 -0
- package/src/domain/sources-record.ts +136 -0
- package/src/domain/tex-run.test.ts +228 -0
- package/src/domain/tex-run.ts +139 -0
- package/src/heading-case.test.ts +24 -10
- package/src/latex-log.ts +20 -0
- package/src/latex-loop.harness.ts +1 -0
- package/src/latex-loop.ts +2 -0
- package/src/linted-set.test.ts +9 -1
- package/src/new-paper.harness.mjs +3 -2
- package/src/new-paper.test.ts +9 -5
- package/src/package-dirs.ts +3 -2
- package/src/paper-includes.test.ts +157 -38
- package/src/paper-includes.ts +39 -35
- package/src/paper-prose.test.ts +34 -5
- package/src/paper-prose.ts +2 -2
- package/src/paper-record.test.ts +173 -0
- package/src/paper-record.ts +66 -0
- package/src/paper-settings-commands.test.ts +5 -2
- package/src/paper-sources.ts +151 -0
- package/src/ports/bib-reader.ts +27 -0
- package/src/ports/check-references.ts +7 -3
- package/src/ports/latex.ts +19 -0
- package/src/ports/sources-codec.ts +11 -0
- package/src/ports/tex-output.ts +17 -0
- package/src/recorded-bibliography.ts +297 -0
- package/src/reference-rules.ts +217 -104
- package/src/references.test.ts +520 -177
- package/src/references.ts +185 -37
- package/src/register-bands.test.ts +5 -1
- package/src/register-bands.ts +3 -5
- package/src/register.test.ts +6 -1
- package/src/register.ts +2 -2
- package/src/rule-cases.test.ts +68 -12
- package/src/rule-docs.test.ts +0 -1
- package/src/sources-record.test.ts +308 -0
- package/src/sources-record.ts +231 -0
- package/src/sources-rules.test.ts +152 -0
- package/src/sources-rules.ts +117 -0
- package/src/tex-paper.test.ts +148 -78
- package/src/tex-paper.ts +31 -61
- package/src/tex-requirements.ts +1 -1
- package/src/tex-venue-rules.test.ts +36 -8
- package/src/tex-venue-rules.ts +3 -5
- package/src/toolchain.ts +1 -1
- package/dist/eslint-rules/bib-reachable-entry.d.ts +0 -22
- package/dist/eslint-rules/bib-reachable-entry.d.ts.map +0 -1
- package/dist/eslint-rules/bib-reachable-entry.js +0 -75
- package/dist/eslint-rules/bib-reachable-entry.js.map +0 -1
- package/eslint-rules/bib-reachable-entry.ts +0 -76
|
@@ -43,122 +43,60 @@
|
|
|
43
43
|
* Usage: node bib-authors.mjs <paper-dir-or-.bib-or-.tex> [--json]
|
|
44
44
|
* Exit: 0 = no author-set/order differences 1 = differences found 2 = usage/IO error
|
|
45
45
|
*/
|
|
46
|
-
import {
|
|
47
|
-
import {
|
|
46
|
+
import { resolve } from "node:path";
|
|
47
|
+
import { latexReader } from "#src/adapters/latex/index";
|
|
48
|
+
import { nodeFiles } from "#src/adapters/node/index";
|
|
49
|
+
import { sourcesCodec } from "#src/adapters/sources-record/index";
|
|
50
|
+
import { absolutePath } from "#src/domain/paths";
|
|
51
|
+
import { bibReader } from "#src/adapters/bibtex/index";
|
|
52
|
+
import { bibliographyAt, bibliographyUnreadWhy } from "#src/paper-sources";
|
|
48
53
|
import { isMain } from "../../paper-pipeline/scripts/consumer.mjs";
|
|
49
54
|
|
|
50
55
|
const DBLP = "https://dblp.org/search/publ/api";
|
|
51
56
|
|
|
52
|
-
/* ---------- input:
|
|
57
|
+
/* ---------- input: the bibliography TeX reads, or a .bib named ---------- */
|
|
53
58
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
// turns up first" (review #189). It used to be `names.find(f => f.endsWith(".tex"))`,
|
|
61
|
-
// that is, the first in directory order. Measured: one of the papers in the corpus
|
|
62
|
-
// has FIVE drafts lying around (paper-CONSTRUCTIVE-…, paper-FOLDED-…, paper-SAFE-…),
|
|
63
|
-
// while PIPELINE-STATUS.md names `paper.tex` as the submitted source. The author
|
|
64
|
-
// check went off into a stale draft and printed a verdict about the WRONG
|
|
65
|
-
// bibliography — a gate that checks the wrong file is worse than a missing one,
|
|
66
|
-
// because it says "checked".
|
|
67
|
-
const pick = (ext) => {
|
|
68
|
-
const canon = names.find((f) => f === `paper${ext}`);
|
|
69
|
-
if (canon) return canon;
|
|
70
|
-
const all = names.filter((f) => f.endsWith(ext)).sort();
|
|
71
|
-
if (all.length === 1) return all[0];
|
|
72
|
-
if (all.length > 1) {
|
|
73
|
-
// We do not fail: a new check here would set up one more surface, and its
|
|
74
|
-
// home is decided by the ladder in the `CLAUDE.md` next to the papers, not by
|
|
75
|
-
// this script (the `frozen-checks` ratchet is what guards that). It is enough
|
|
76
|
-
// to SAY it out loud and to choose DETERMINISTICALLY — the header below prints
|
|
77
|
-
// which file was checked anyway, so the reader sees the choice.
|
|
78
|
-
console.error(
|
|
79
|
-
`⚠️ ${file} holds several ${ext} files and no canonical paper${ext}: ` +
|
|
80
|
-
`${all.join(", ")} — took ${all[0]}. If that is the wrong one, name the file explicitly.`,
|
|
81
|
-
);
|
|
82
|
-
return all[0];
|
|
83
|
-
}
|
|
84
|
-
return undefined;
|
|
85
|
-
};
|
|
86
|
-
const found = pick(".bib") || pick(".tex");
|
|
87
|
-
if (!found) die(`no .bib or .tex in ${file}`);
|
|
88
|
-
file = join(file, found);
|
|
89
|
-
}
|
|
90
|
-
const text = readFileSync(file, "utf-8");
|
|
91
|
-
if (file.endsWith(".bib")) return { text, file };
|
|
92
|
-
// Only a .bib and a .tex are read: any other file would fall through to the refs.bib beside it,
|
|
93
|
-
// and the report would name a file nobody asked about.
|
|
94
|
-
if (!file.endsWith(".tex"))
|
|
95
|
-
die(`${file} is not a .bib or a .tex — give the paper's .bib or paper.tex`);
|
|
96
|
-
// A .tex may carry the bibliography inline via filecontents — that is how our papers do it.
|
|
97
|
-
const m = text.match(
|
|
98
|
-
/\\begin\{filecontents\*?\}(?:\[[^\]]*\])?\{[^}]*\.bib\}\r?\n([\s\S]*?)\\end\{filecontents\*?\}/,
|
|
99
|
-
);
|
|
100
|
-
if (m) return { text: m[1], file };
|
|
101
|
-
const sibling = join(file, "..", "refs.bib");
|
|
102
|
-
if (existsSync(sibling))
|
|
103
|
-
return { text: readFileSync(sibling, "utf-8"), file: sibling };
|
|
104
|
-
die(
|
|
105
|
-
`no bibliography found in ${file} (no filecontents block, no refs.bib beside it)`,
|
|
106
|
-
);
|
|
107
|
-
}
|
|
59
|
+
const DEPS = {
|
|
60
|
+
files: nodeFiles,
|
|
61
|
+
codec: sourcesCodec,
|
|
62
|
+
latex: latexReader,
|
|
63
|
+
bib: bibReader,
|
|
64
|
+
};
|
|
108
65
|
|
|
109
|
-
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
let i = re.lastIndex,
|
|
120
|
-
depth = 1;
|
|
121
|
-
while (i < text.length && depth > 0) {
|
|
122
|
-
if (text[i] === "{") depth++;
|
|
123
|
-
else if (text[i] === "}") depth--;
|
|
124
|
-
i++;
|
|
125
|
-
}
|
|
126
|
-
const body = text.slice(re.lastIndex, i - 1);
|
|
127
|
-
out.push({ type: type.toLowerCase(), key, ...fields(body) });
|
|
128
|
-
}
|
|
129
|
-
return out;
|
|
66
|
+
/**
|
|
67
|
+
* The texts to read: a `.bib` as named; for a paper directory or a `.tex`, the databases the last
|
|
68
|
+
* build's bibtex opened (`_build/sources.json`) — `bibliographyAt` (src/paper-sources.ts) answers, and
|
|
69
|
+
* refuses in its words, "run `npx paperlint build` first" among them.
|
|
70
|
+
*/
|
|
71
|
+
function bibliographyFrom(target) {
|
|
72
|
+
const r = bibliographyAt(absolutePath(resolve(target)), DEPS);
|
|
73
|
+
if (!r.ok) die(bibliographyUnreadWhy(r.error));
|
|
74
|
+
const { texts } = r.value;
|
|
75
|
+
return { files: [...new Set(texts.map((x) => x.path))], texts };
|
|
130
76
|
}
|
|
131
77
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
return body.slice(i, j === -1 ? undefined : j);
|
|
155
|
-
};
|
|
156
|
-
return {
|
|
157
|
-
author: get("author"),
|
|
158
|
-
title: get("title"),
|
|
159
|
-
booktitle: get("booktitle"),
|
|
160
|
-
journal: get("journal"),
|
|
161
|
-
};
|
|
78
|
+
/* ---------- entries: as the bibtex reader read them ---------- */
|
|
79
|
+
|
|
80
|
+
/** A name of a name list as BibTeX writes it, family part first: `von Last, First`, or the literal. */
|
|
81
|
+
const writtenName = (n) =>
|
|
82
|
+
n.name ??
|
|
83
|
+
[[n.prefix, n.lastName].filter(Boolean).join(" "), n.firstName]
|
|
84
|
+
.filter(Boolean)
|
|
85
|
+
.join(", ");
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The entries this check reads, from the bibtex reader's (src/ports/bib-reader.ts): type, key, the
|
|
89
|
+
* author list written out (`and others` kept), title, booktitle and journal; "" for a field it lacks.
|
|
90
|
+
*/
|
|
91
|
+
export function authorEntries(entries) {
|
|
92
|
+
return entries.map((e) => ({
|
|
93
|
+
type: e.type,
|
|
94
|
+
key: e.key,
|
|
95
|
+
author: (e.names.author ?? []).map(writtenName).join(" and "),
|
|
96
|
+
title: e.fields.title ?? "",
|
|
97
|
+
booktitle: e.fields.booktitle ?? "",
|
|
98
|
+
journal: e.fields.journal ?? "",
|
|
99
|
+
}));
|
|
162
100
|
}
|
|
163
101
|
|
|
164
102
|
/* ---------- normalisation: surnames only, in order ---------- */
|
|
@@ -353,14 +291,22 @@ async function main() {
|
|
|
353
291
|
if (!args[0])
|
|
354
292
|
die("usage: bib-authors.mjs <paper-dir|file.bib|file.tex> [--json]");
|
|
355
293
|
|
|
356
|
-
const {
|
|
357
|
-
const
|
|
294
|
+
const { files, texts } = bibliographyFrom(args[0]);
|
|
295
|
+
const file = files.join(", ");
|
|
296
|
+
const parsed = texts.flatMap((t) => authorEntries(t.entries));
|
|
358
297
|
const entries = parsed.filter((e) => e.title && e.author);
|
|
359
298
|
const { findings, skipped, unchecked } = await checkAuthors(parsed);
|
|
360
299
|
if (asJson) {
|
|
361
300
|
console.log(
|
|
362
301
|
JSON.stringify(
|
|
363
|
-
{
|
|
302
|
+
{
|
|
303
|
+
file: files[0],
|
|
304
|
+
files,
|
|
305
|
+
entries: entries.length,
|
|
306
|
+
findings,
|
|
307
|
+
skipped,
|
|
308
|
+
unchecked,
|
|
309
|
+
},
|
|
364
310
|
null,
|
|
365
311
|
2,
|
|
366
312
|
),
|
|
@@ -10,6 +10,8 @@ import { dirname, join } from "node:path";
|
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
11
|
import { test } from "vitest";
|
|
12
12
|
import { z } from "zod";
|
|
13
|
+
import { absolutePath } from "#src/domain/paths";
|
|
14
|
+
import { bibliographyUnreadWhy } from "#src/paper-sources";
|
|
13
15
|
import {
|
|
14
16
|
runNode,
|
|
15
17
|
useTempDir,
|
|
@@ -92,6 +94,20 @@ test("an input that cannot be read: exit 2, named", () => {
|
|
|
92
94
|
);
|
|
93
95
|
});
|
|
94
96
|
|
|
97
|
+
test("a .bib that is not there: exit 2, in bibliographyAt's words", () => {
|
|
98
|
+
// The path → bibliography vocabulary is tested once, beside bibliographyAt (src/paper-sources.ts).
|
|
99
|
+
const gone = join(root, "gone.bib");
|
|
100
|
+
const r = offline([gone]);
|
|
101
|
+
assert.deepEqual(
|
|
102
|
+
{ status: r.status, stdout: r.stdout, stderr: r.stderr },
|
|
103
|
+
{
|
|
104
|
+
status: 2,
|
|
105
|
+
stdout: "",
|
|
106
|
+
stderr: `error: could not read input — ${bibliographyUnreadWhy({ kind: "missing", path: absolutePath(gone) })}\n`,
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
|
|
95
111
|
test("offline over a JSON list: a citation with no id is unresolvable by name, a short SHA is flagged", () => {
|
|
96
112
|
const r = parsed(offline([join(root, "cites.json")]));
|
|
97
113
|
assert.deepEqual(
|
|
@@ -5,14 +5,30 @@
|
|
|
5
5
|
// Only what `src/` imports is declared. The `.mjs` is not typechecked, so a declaration nobody
|
|
6
6
|
// compiles against would drift without anyone noticing.
|
|
7
7
|
|
|
8
|
-
/** One
|
|
8
|
+
/** One citation as `citationsOf` returns it: the BibTeX key as `id`, plus the fields it recognised. */
|
|
9
9
|
export interface Citation {
|
|
10
10
|
readonly id: string;
|
|
11
11
|
readonly [field: string]: unknown;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
export
|
|
14
|
+
/** A name of a name list, in its parts (`BibName`). */
|
|
15
|
+
export interface ReadName {
|
|
16
|
+
readonly lastName?: string;
|
|
17
|
+
readonly firstName?: string;
|
|
18
|
+
readonly prefix?: string;
|
|
19
|
+
readonly suffix?: string;
|
|
20
|
+
readonly name?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** An entry as the bibtex reader reads it (`BibEntry`, src/domain/paper-sources.ts), as far as citations use it. */
|
|
24
|
+
export interface ReadEntry {
|
|
25
|
+
readonly key: string;
|
|
26
|
+
readonly fields: Readonly<Record<string, string>>;
|
|
27
|
+
readonly names: Readonly<Record<string, readonly ReadName[]>>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The citations of a bibliography's entries, as the bibtex reader read them. */
|
|
31
|
+
export declare function citationsOf(entries: readonly ReadEntry[]): Citation[];
|
|
16
32
|
|
|
17
33
|
/** The title reduced to what identifies it: case, braces and punctuation removed. */
|
|
18
34
|
export declare function titleIdentity(title: string): string;
|
|
@@ -47,7 +47,13 @@
|
|
|
47
47
|
|
|
48
48
|
import { mkdirSync, readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
49
49
|
import { homedir } from "node:os";
|
|
50
|
-
import { dirname, join, extname } from "node:path";
|
|
50
|
+
import { dirname, join, extname, resolve } from "node:path";
|
|
51
|
+
import { bibReader } from "#src/adapters/bibtex/index";
|
|
52
|
+
import { latexReader } from "#src/adapters/latex/index";
|
|
53
|
+
import { nodeFiles } from "#src/adapters/node/index";
|
|
54
|
+
import { sourcesCodec } from "#src/adapters/sources-record/index";
|
|
55
|
+
import { absolutePath } from "#src/domain/paths";
|
|
56
|
+
import { bibliographyAt, bibliographyUnreadWhy } from "#src/paper-sources";
|
|
51
57
|
import { createHash } from "node:crypto";
|
|
52
58
|
import {
|
|
53
59
|
consumerContactEmail,
|
|
@@ -588,116 +594,32 @@ export function reduceVerdict(citation, evidence, commitFlags = []) {
|
|
|
588
594
|
}
|
|
589
595
|
|
|
590
596
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
591
|
-
//
|
|
592
|
-
// and (loosely) \bibitem blocks. Not a full BibTeX grammar; enough to seed cites.
|
|
597
|
+
// CITATIONS — from the entries the bibtex reader read (src/ports/bib-reader.ts).
|
|
593
598
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
594
599
|
|
|
600
|
+
/** A name of a name list, written out: `First von Last, Jr.`, or the literal it was. */
|
|
601
|
+
const writtenName = (n) =>
|
|
602
|
+
n.name ??
|
|
603
|
+
[n.firstName, n.prefix, n.lastName, n.suffix].filter(Boolean).join(" ");
|
|
604
|
+
|
|
595
605
|
/**
|
|
596
|
-
*
|
|
597
|
-
*
|
|
598
|
-
* (b) reads BRACE-delimited values with balanced-brace scanning, so a protected
|
|
599
|
-
* acronym `{Evaluating {LLM}-based ...}` is captured whole, not truncated at
|
|
600
|
-
* the inner `}` (S4b);
|
|
601
|
-
* also handles "quoted" and bare (numeric) values.
|
|
606
|
+
* The citations of a bibliography's entries, as the bibtex reader read them: the key as `id`, and
|
|
607
|
+
* the doi, title, year, authors and arXiv eprint each entry has.
|
|
602
608
|
*/
|
|
603
|
-
function
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
return body
|
|
620
|
-
.slice(i + 1, j)
|
|
621
|
-
.replace(/\s+/g, " ")
|
|
622
|
-
.trim();
|
|
623
|
-
}
|
|
624
|
-
if (ch === '"') {
|
|
625
|
-
let j = i + 1;
|
|
626
|
-
while (j < body.length && body[j] !== '"') j++;
|
|
627
|
-
return body
|
|
628
|
-
.slice(i + 1, j)
|
|
629
|
-
.replace(/\s+/g, " ")
|
|
630
|
-
.trim();
|
|
631
|
-
}
|
|
632
|
-
// bare value (e.g. year = 2017,) — up to the next comma / newline / close brace
|
|
633
|
-
let j = i;
|
|
634
|
-
while (j < body.length && !/[,\n}]/.test(body[j])) j++;
|
|
635
|
-
const v = body.slice(i, j).replace(/\s+/g, " ").trim();
|
|
636
|
-
return v || undefined;
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
export function parseBib(text) {
|
|
640
|
-
const cites = [];
|
|
641
|
-
// @type{key, — then read the entry body with BALANCED-BRACE scanning so that
|
|
642
|
-
// one-line entries and indented / same-line closing braces (`\n }` or `...}`)
|
|
643
|
-
// are captured, not dropped by a `\n}`-at-column-0 requirement (S4c).
|
|
644
|
-
const headRe = /@(\w+)\s*\{\s*([^,\s}]+)\s*,/g;
|
|
645
|
-
let m;
|
|
646
|
-
while ((m = headRe.exec(text)) !== null) {
|
|
647
|
-
const type = m[1].toLowerCase();
|
|
648
|
-
if (type === "comment" || type === "string" || type === "preamble")
|
|
649
|
-
continue;
|
|
650
|
-
const key = m[2].trim();
|
|
651
|
-
// Balanced scan from just after the key's comma to the entry's closing brace.
|
|
652
|
-
let depth = 1; // the entry's opening `{` was already consumed by headRe
|
|
653
|
-
let j = headRe.lastIndex;
|
|
654
|
-
for (; j < text.length && depth > 0; j++) {
|
|
655
|
-
if (text[j] === "{") depth++;
|
|
656
|
-
else if (text[j] === "}") depth--;
|
|
657
|
-
}
|
|
658
|
-
const body = text.slice(headRe.lastIndex, j - 1);
|
|
659
|
-
headRe.lastIndex = j; // resume scanning AFTER this entry
|
|
660
|
-
|
|
661
|
-
const c = { id: key };
|
|
662
|
-
const doi = bibField(body, "doi");
|
|
663
|
-
const title = bibField(body, "title");
|
|
664
|
-
const year = bibField(body, "year");
|
|
665
|
-
const author = bibField(body, "author");
|
|
666
|
-
const eprint = bibField(body, "eprint");
|
|
667
|
-
const archive =
|
|
668
|
-
bibField(body, "archiveprefix") || bibField(body, "eprinttype");
|
|
669
|
-
if (doi) c.doi = normalizeDoi(doi);
|
|
670
|
-
if (title) c.title = title.replace(/[{}]/g, "");
|
|
671
|
-
if (year) c.year = year;
|
|
672
|
-
if (author) c.authors = author;
|
|
673
|
-
if (eprint && (!archive || /arxiv/i.test(archive)))
|
|
674
|
-
c.arxiv = normalizeArxiv(eprint);
|
|
675
|
-
cites.push(c);
|
|
676
|
-
}
|
|
677
|
-
if (cites.length > 0) return cites;
|
|
678
|
-
|
|
679
|
-
// Fallback: \bibitem — grab arXiv ids / DOIs out of each block, best-effort.
|
|
680
|
-
const itemRe =
|
|
681
|
-
/\\bibitem(?:\[[^\]]*\])?\{([^}]+)\}([\s\S]*?)(?=\\bibitem|\\end\{thebibliography\}|$)/g;
|
|
682
|
-
while ((m = itemRe.exec(text)) !== null) {
|
|
683
|
-
const key = m[1].trim();
|
|
684
|
-
const block = m[2];
|
|
685
|
-
const c = { id: key };
|
|
686
|
-
const doiM = block.match(/10\.\d{4,9}\/[-._;()/:A-Z0-9]+/i);
|
|
687
|
-
const arxivM = block.match(/arXiv:\s*(\d{4}\.\d{4,5})/i);
|
|
688
|
-
const yearM = block.match(/\b(19|20)\d{2}\b/);
|
|
689
|
-
if (doiM) c.doi = normalizeDoi(doiM[0]);
|
|
690
|
-
if (arxivM) c.arxiv = normalizeArxiv(arxivM[1]);
|
|
691
|
-
if (yearM) c.year = yearM[0];
|
|
692
|
-
// crude title: longest quoted or {\em ...} run
|
|
693
|
-
const titleM =
|
|
694
|
-
block.match(/``([^']{6,}?)''/) ||
|
|
695
|
-
block.match(/\\emph\{([^}]{6,}?)\}/) ||
|
|
696
|
-
block.match(/\{\\em\s+([^}]{6,}?)\}/);
|
|
697
|
-
if (titleM) c.title = titleM[1].replace(/\s+/g, " ").trim();
|
|
698
|
-
cites.push(c);
|
|
699
|
-
}
|
|
700
|
-
return cites;
|
|
609
|
+
export function citationsOf(entries) {
|
|
610
|
+
return entries.map((e) => {
|
|
611
|
+
const f = e.fields;
|
|
612
|
+
const c = { id: e.key };
|
|
613
|
+
if (f.doi) c.doi = normalizeDoi(f.doi);
|
|
614
|
+
if (f.title) c.title = f.title.replace(/\s+/g, " ").trim();
|
|
615
|
+
if (f.year) c.year = f.year;
|
|
616
|
+
const authors = (e.names.author ?? []).map(writtenName).join(" and ");
|
|
617
|
+
if (authors) c.authors = authors;
|
|
618
|
+
const archive = f.archiveprefix || f.eprinttype;
|
|
619
|
+
if (f.eprint && (!archive || /arxiv/i.test(archive)))
|
|
620
|
+
c.arxiv = normalizeArxiv(f.eprint);
|
|
621
|
+
return c;
|
|
622
|
+
});
|
|
701
623
|
}
|
|
702
624
|
|
|
703
625
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -1212,11 +1134,23 @@ export async function verifyCitationLive(
|
|
|
1212
1134
|
// CLI
|
|
1213
1135
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
1214
1136
|
|
|
1137
|
+
/** What `bibliographyAt` needs to read a `.bib` (src/paper-sources.ts, the one answer for a path). */
|
|
1138
|
+
const DEPS = {
|
|
1139
|
+
files: nodeFiles,
|
|
1140
|
+
codec: sourcesCodec,
|
|
1141
|
+
latex: latexReader,
|
|
1142
|
+
bib: bibReader,
|
|
1143
|
+
};
|
|
1144
|
+
|
|
1215
1145
|
function loadInput(argv) {
|
|
1216
1146
|
const path = argv.find((a) => !a.startsWith("-"));
|
|
1147
|
+
if (path && extname(path).toLowerCase() === ".bib") {
|
|
1148
|
+
const r = bibliographyAt(absolutePath(resolve(path)), DEPS);
|
|
1149
|
+
if (!r.ok) throw new Error(bibliographyUnreadWhy(r.error));
|
|
1150
|
+
return citationsOf(r.value.texts.flatMap((t) => t.entries));
|
|
1151
|
+
}
|
|
1217
1152
|
const raw =
|
|
1218
1153
|
path && path !== "-" ? readFileSync(path, "utf8") : readFileSync(0, "utf8"); // stdin
|
|
1219
|
-
if (path && extname(path).toLowerCase() === ".bib") return parseBib(raw);
|
|
1220
1154
|
const parsed = JSON.parse(raw);
|
|
1221
1155
|
return Array.isArray(parsed) ? parsed : [parsed];
|
|
1222
1156
|
}
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
checkNvd,
|
|
25
25
|
checkCommit,
|
|
26
26
|
reduceVerdict,
|
|
27
|
-
|
|
27
|
+
citationsOf,
|
|
28
28
|
parseArxivFeed,
|
|
29
29
|
normalizeDoi,
|
|
30
30
|
normalizeArxiv,
|
|
@@ -33,6 +33,12 @@ import {
|
|
|
33
33
|
titleIdentity,
|
|
34
34
|
isValidCveId,
|
|
35
35
|
} from "./verify-cites.mjs";
|
|
36
|
+
import { bibReader } from "#src/adapters/bibtex/index";
|
|
37
|
+
import { absolutePath } from "#src/domain/paths";
|
|
38
|
+
|
|
39
|
+
/** The citations of a BibTeX text, read by the one bibtex reader. */
|
|
40
|
+
const citationsIn = (bib) =>
|
|
41
|
+
citationsOf(bibReader.readFile(absolutePath("/p/refs.bib"), bib).entries);
|
|
36
42
|
|
|
37
43
|
/** One vitest case per check, named by it. */
|
|
38
44
|
function ok(cond, name) {
|
|
@@ -377,7 +383,7 @@ console.log("\n(extra) .bib parser best-effort extraction:");
|
|
|
377
383
|
eprint={1512.03385}
|
|
378
384
|
}
|
|
379
385
|
`;
|
|
380
|
-
const cites =
|
|
386
|
+
const cites = citationsIn(bib);
|
|
381
387
|
eq(cites.length, 2, "parsed 2 entries");
|
|
382
388
|
eq(cites[0].doi, "10.5555/3295222.3295349", "entry 1 doi");
|
|
383
389
|
eq(cites[1].arxiv, "1512.03385", "entry 2 arxiv eprint");
|
|
@@ -724,7 +730,7 @@ console.log("\n[S4] .bib parser edge cases:");
|
|
|
724
730
|
title = {The Real Paper Title},
|
|
725
731
|
year = {2021}
|
|
726
732
|
}`;
|
|
727
|
-
const a =
|
|
733
|
+
const a = citationsIn(bibA)[0];
|
|
728
734
|
eq(a.title, "The Real Paper Title", "title anchored — not booktitle");
|
|
729
735
|
|
|
730
736
|
// (b) brace-protected acronym must not truncate at the inner brace.
|
|
@@ -733,14 +739,14 @@ console.log("\n[S4] .bib parser edge cases:");
|
|
|
733
739
|
year = {2026}
|
|
734
740
|
}`;
|
|
735
741
|
eq(
|
|
736
|
-
|
|
742
|
+
citationsIn(bibB)[0].title,
|
|
737
743
|
"Evaluating LLM-based Systems for Code Review",
|
|
738
744
|
"brace-protected acronym kept whole",
|
|
739
745
|
);
|
|
740
746
|
|
|
741
747
|
// (c1) one-line entry (no \n} at column 0).
|
|
742
748
|
const bibC1 = `@article{k3, title = {A One Line Entry}, year = {2020}, doi = {10.1/abc}}`;
|
|
743
|
-
const c1 =
|
|
749
|
+
const c1 = citationsIn(bibC1)[0];
|
|
744
750
|
eq(c1.title, "A One Line Entry", "one-line entry title parsed");
|
|
745
751
|
eq(c1.doi, "10.1/abc", "one-line entry doi parsed");
|
|
746
752
|
|
|
@@ -750,14 +756,14 @@ console.log("\n[S4] .bib parser edge cases:");
|
|
|
750
756
|
year = {2019}
|
|
751
757
|
}`;
|
|
752
758
|
eq(
|
|
753
|
-
|
|
759
|
+
citationsIn(bibC2)[0].title,
|
|
754
760
|
"Indented Closing Brace",
|
|
755
761
|
"indented-closing-brace entry parsed",
|
|
756
762
|
);
|
|
757
763
|
|
|
758
764
|
// all four parse together, in sequence (balanced-brace scan resumes correctly).
|
|
759
765
|
eq(
|
|
760
|
-
|
|
766
|
+
citationsIn(bibA + "\n" + bibB + "\n" + bibC1 + "\n" + bibC2).length,
|
|
761
767
|
4,
|
|
762
768
|
"all 4 entries parsed in sequence",
|
|
763
769
|
);
|
|
@@ -796,15 +802,6 @@ console.log("\n[S4] .bib parser edge cases:");
|
|
|
796
802
|
});
|
|
797
803
|
eq(n.doi, "10.1/y", "normalizeIdentifiers cleans doi");
|
|
798
804
|
eq(n.arxiv, "1234.5678", "normalizeIdentifiers cleans arxiv");
|
|
799
|
-
// the exact live-reproduced break: a \bibitem DOI with a swallowed sentence period.
|
|
800
|
-
const bib = `\\begin{thebibliography}{9}
|
|
801
|
-
\\bibitem{ccs23} A. Author. Title. In Proc. CCS. doi:10.1145/3576915.3623218. 2023.
|
|
802
|
-
\\end{thebibliography}`;
|
|
803
|
-
eq(
|
|
804
|
-
parseBib(bib)[0].doi,
|
|
805
|
-
"10.1145/3576915.3623218",
|
|
806
|
-
"bibitem DOI trailing period not swallowed",
|
|
807
|
-
);
|
|
808
805
|
}
|
|
809
806
|
|
|
810
807
|
// ── Edges of the pure layer: the defensive arms, each with the input that reaches it ──────────
|
|
@@ -916,32 +913,22 @@ test("reduceVerdict: an arXiv id missed only by a non-authoritative source is un
|
|
|
916
913
|
});
|
|
917
914
|
});
|
|
918
915
|
|
|
919
|
-
test("
|
|
916
|
+
test("citationsOf: @comment/@string/@preamble are no citations; a quoted and a bare value are read; authors written out", () => {
|
|
920
917
|
const bib = [
|
|
921
918
|
"@comment{c, not an entry}",
|
|
922
|
-
"@string{s
|
|
923
|
-
'@article{k1, title = "Quoted Title", year = 2017, eprint = {1706.03762}, archiveprefix = {arXiv}}',
|
|
924
|
-
"@misc{k2, title = {T},
|
|
925
|
-
].join("\n");
|
|
926
|
-
assert.deepEqual(parseBib(bib), [
|
|
927
|
-
{ id: "k1", title: "Quoted Title", year: "2017", arxiv: "1706.03762" },
|
|
928
|
-
{ id: "k2", title: "T" },
|
|
929
|
-
]);
|
|
930
|
-
});
|
|
931
|
-
|
|
932
|
-
test("parseBib's \\bibitem fallback reads an arXiv id and an \\emph title", () => {
|
|
933
|
-
const text = [
|
|
934
|
-
"\\begin{thebibliography}{9}",
|
|
935
|
-
"\\bibitem{a} A. Author. \\emph{A Long Enough Title}. arXiv:1706.03762, 2017.",
|
|
936
|
-
"\\end{thebibliography}",
|
|
919
|
+
"@string{s = {y}}",
|
|
920
|
+
'@article{k1, title = "Quoted Title", year = 2017, eprint = {1706.03762}, archiveprefix = {arXiv}, author = {Vaswani, Ashish and von Neumann, John}}',
|
|
921
|
+
"@misc{k2, title = {T}, eprint = {x}, archiveprefix = {HAL}}",
|
|
937
922
|
].join("\n");
|
|
938
|
-
assert.deepEqual(
|
|
923
|
+
assert.deepEqual(citationsIn(bib), [
|
|
939
924
|
{
|
|
940
|
-
id: "
|
|
941
|
-
|
|
925
|
+
id: "k1",
|
|
926
|
+
title: "Quoted Title",
|
|
942
927
|
year: "2017",
|
|
943
|
-
|
|
928
|
+
authors: "Ashish Vaswani and John von Neumann",
|
|
929
|
+
arxiv: "1706.03762",
|
|
944
930
|
},
|
|
931
|
+
{ id: "k2", title: "T" },
|
|
945
932
|
]);
|
|
946
933
|
});
|
|
947
934
|
|
package/src/CLAUDE.md
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
<!-- vigiles:sha256:f51051aae2ef09f8 compiled from src/CLAUDE.md.spec.ts -->
|
|
2
|
+
|
|
3
|
+
# CLAUDE.md
|
|
4
|
+
|
|
5
|
+
## `src/`
|
|
2
6
|
|
|
3
7
|
The package's TypeScript: the CLI, the build, the toolchain installer, and the measuring that feeds
|
|
4
8
|
the facts file. Loaded when working under `src/`; the repository-wide rules are in the root
|
|
5
9
|
`CLAUDE.md`.
|
|
6
10
|
|
|
11
|
+
## The two layer axes
|
|
12
|
+
|
|
7
13
|
**`src/` is hexagonal on two axes, and the linter enforces both** (`eslint.config.mjs`; fixtures in
|
|
8
14
|
`test/fixtures/layers/`, run by `test/eslint-layers.test.ts`; issue #76).
|
|
9
15
|
|
|
@@ -49,3 +55,19 @@ against `process.cwd()`, so lint started from any other directory classifies not
|
|
|
49
55
|
`checkAllOrigins: true`: without it no `module:` policy is evaluated, so axis B and the library bans
|
|
50
56
|
report nothing — dropping it turns the fixtures `src/domain/reads-disk.ts` and
|
|
51
57
|
`src/adapters/one/disk.ts` clean (measured).
|
|
58
|
+
|
|
59
|
+
## Readers and commands
|
|
60
|
+
|
|
61
|
+
`@retorquere/bibtex-parser` is the one `.bib` reader, behind `src/ports/bib-reader.ts`.
|
|
62
|
+
Rejected on 2026-09-27, so not proposed again: a separate `paperlint refs` command; the slowness it
|
|
63
|
+
would have moved was a cache thrown away after every build (`docs/references.md`).
|
|
64
|
+
|
|
65
|
+
## Rules
|
|
66
|
+
|
|
67
|
+
### Layers
|
|
68
|
+
|
|
69
|
+
**Enforced by:** `eslint/boundaries/dependencies`
|
|
70
|
+
|
|
71
|
+
### Effect Globals
|
|
72
|
+
|
|
73
|
+
**Enforced by:** `eslint/no-restricted-globals`
|