paperlint 7.2.0 → 9.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/CLAUDE.md +9 -25
- package/CONTRIBUTING.md +5 -4
- package/README.md +4 -3
- 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 -30
- package/dist/build.js.map +1 -1
- package/dist/claim-provenance.d.ts +2 -2
- package/dist/cli.d.ts +9 -5
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +43 -68
- 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/latex-language.d.ts.map +1 -1
- package/dist/eslint-rules/latex-language.js +10 -13
- package/dist/eslint-rules/latex-language.js.map +1 -1
- package/dist/eslint-rules/paper-context.d.ts.map +1 -1
- package/dist/eslint-rules/paper-context.js +6 -5
- 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 +34 -96
- package/dist/eslint-rules/paper-typography.js.map +1 -1
- package/dist/eslint-rules/papers.d.ts +1 -3
- package/dist/eslint-rules/papers.d.ts.map +1 -1
- package/dist/eslint-rules/papers.js +2 -4
- package/dist/eslint-rules/papers.js.map +1 -1
- package/dist/eslint-rules/tex-build.d.ts.map +1 -1
- package/dist/eslint-rules/tex-build.js +8 -25
- package/dist/eslint-rules/tex-build.js.map +1 -1
- package/dist/init.d.ts +1 -4
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +3 -9
- package/dist/init.js.map +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/markdown.d.ts +5 -81
- package/dist/lib/markdown.d.ts.map +1 -1
- package/dist/lib/markdown.js +5 -116
- package/dist/lib/markdown.js.map +1 -1
- package/dist/lib/paper-config.d.ts.map +1 -1
- package/dist/lib/paper-config.js +0 -2
- package/dist/lib/paper-config.js.map +1 -1
- package/dist/lib/skill-eval-fixture.d.ts +6 -4
- package/dist/lib/skill-eval-fixture.d.ts.map +1 -1
- package/dist/lib/skill-eval-fixture.js +25 -18
- package/dist/lib/skill-eval-fixture.js.map +1 -1
- package/dist/new-paper.d.ts +5 -11
- package/dist/new-paper.d.ts.map +1 -1
- package/dist/new-paper.js +12 -23
- package/dist/new-paper.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-files.d.ts +4 -4
- 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 +17 -0
- package/dist/paper-prose.d.ts.map +1 -0
- package/dist/paper-prose.js +46 -0
- package/dist/paper-prose.js.map +1 -0
- 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/structure.d.ts.map +1 -1
- package/dist/structure.js +4 -3
- package/dist/structure.js.map +1 -1
- 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-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/types.d.ts +0 -2
- package/dist/types.d.ts.map +1 -1
- package/docs/configuration.md +18 -12
- package/docs/design/paper-sources.md +197 -0
- package/docs/e2e.md +4 -6
- package/docs/package-shape-options.md +1 -1
- package/docs/prior-art/paper-folder-scaffolding.md +24 -29
- 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/tex/claim-provenance.md +0 -1
- package/docs/rules.md +62 -61
- package/eslint-rules/latex-language.harness.mjs +3 -3
- package/eslint-rules/latex-language.ts +10 -13
- package/eslint-rules/paper-context.ts +6 -5
- package/eslint-rules/paper-typography.test.mjs +29 -157
- package/eslint-rules/paper-typography.ts +35 -104
- package/eslint-rules/papers.harness.mjs +1 -1
- package/eslint-rules/papers.ts +2 -4
- package/eslint-rules/tex-build.harness.mjs +2 -2
- package/eslint-rules/tex-build.ts +8 -25
- package/eslint.config.mjs +22 -1
- 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/hooks.harness.ts +15 -15
- package/hooks/paper-edit-guard.hook.mjs +5 -8
- package/hooks/paper-edit-guard.test.ts +12 -3
- package/hooks/paper-skills-nudge.hook.mjs +2 -3
- package/hooks/paper-skills-nudge.test.ts +13 -3
- package/hooks/paper-status-gates.hook.mjs +2 -2
- package/hooks/paper-status-gates.test.ts +14 -2
- package/lib/markdown.test.mjs +5 -60
- package/lib/markdown.ts +9 -136
- package/lib/paper-config.ts +0 -2
- package/lib/skill-checks.test.mjs +4 -1
- package/lib/skill-eval-fixture.ts +25 -18
- package/package.json +2 -12
- package/presets/realm.jsonc +1 -1
- package/scripts/check.test.ts +23 -1
- package/scripts/check.ts +7 -3
- package/scripts/coverage-config.test.ts +19 -10
- package/scripts/harness-api.frozen.json +0 -5
- package/scripts/layer-legacy-frozen.test.ts +41 -2
- package/scripts/layer-legacy-frozen.ts +31 -2
- package/skills/cold-read-diff/SKILL.md +3 -3
- package/skills/cold-read-diff/SKILL.md.spec.ts +2 -2
- package/skills/grade-paper-writing/SKILL.md +15 -22
- package/skills/grade-paper-writing/SKILL.md.spec.ts +14 -21
- package/skills/grade-paper-writing/__snapshots__/prose-lint.test.ts.snap +40 -26
- package/skills/grade-paper-writing/prose-lint.mjs +117 -278
- package/skills/grade-paper-writing/prose-lint.test.ts +230 -79
- package/skills/paper-adversarial-review/SKILL.md +2 -2
- package/skills/paper-adversarial-review/SKILL.md.spec.ts +1 -1
- package/skills/paper-pipeline/SKILL.md +9 -12
- package/skills/paper-pipeline/SKILL.md.spec.ts +8 -11
- package/skills/paper-pipeline/description-language.eval.mjs +25 -15
- package/skills/paper-pipeline/framing-vs-vocabulary.eval.mjs +25 -15
- package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +25 -15
- package/skills/paper-pipeline/pipeline-firing.eval.mjs +27 -16
- package/skills/paper-pipeline/pipeline-language.eval.mjs +25 -15
- package/skills/paper-pipeline/references/acceptance-gate.md +16 -20
- package/skills/paper-pipeline/references/artifact-checklist.md +4 -5
- package/skills/paper-pipeline/references/pipeline-status-template.md +1 -1
- package/skills/paper-pipeline/scripts/README.md +0 -4
- package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +91 -105
- package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +85 -241
- package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +166 -153
- package/skills/paper-pipeline/scripts/generated-code.harness.mjs +3 -3
- package/skills/paper-status/SKILL.md +2 -2
- package/skills/paper-status/SKILL.md.spec.ts +1 -1
- package/skills/render-paper/SKILL.md +2 -2
- package/skills/render-paper/SKILL.md.spec.ts +1 -1
- package/skills/render-paper/extract-pdf-facts.mjs +2 -2
- package/skills/tighten-paper/SKILL.md +29 -42
- package/skills/tighten-paper/SKILL.md.spec.ts +28 -41
- 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 -32
- package/skills/verify-citations/scripts/bib-authors.cli.test.ts +138 -43
- package/skills/verify-citations/scripts/bib-authors.d.mts +22 -2
- package/skills/verify-citations/scripts/bib-authors.mjs +54 -193
- package/skills/verify-citations/scripts/bib-authors.test.mjs +1 -44
- 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/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.harness.mjs +2 -2
- package/src/build.test.ts +211 -1
- package/src/build.ts +101 -41
- package/src/claim-provenance.test.ts +33 -7
- package/src/claim-provenance.ts +2 -2
- package/src/cli.harness.mjs +46 -56
- package/src/cli.test.ts +44 -48
- package/src/cli.ts +51 -82
- 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/folder-venue-rule.test.ts +1 -1
- package/src/heading-case.test.ts +24 -10
- package/src/init.test.ts +6 -7
- package/src/init.ts +3 -22
- package/src/latex-log.ts +20 -0
- package/src/latex-loop.harness.ts +1 -0
- package/src/latex-loop.ts +2 -0
- package/src/lint-fix.test.ts +10 -4
- package/src/linted-set.test.ts +9 -1
- package/src/new-paper.harness.mjs +12 -30
- package/src/new-paper.test.ts +15 -11
- package/src/new-paper.ts +11 -30
- package/src/package-dirs.ts +3 -2
- package/src/paper-files.test.ts +5 -4
- package/src/paper-files.ts +4 -4
- package/src/paper-includes.test.ts +157 -38
- package/src/paper-includes.ts +39 -35
- package/src/paper-prose.test.ts +78 -0
- package/src/paper-prose.ts +67 -0
- 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/parse-args.test.ts +0 -1
- 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/structure.harness.ts +5 -11
- package/src/structure.ts +4 -3
- package/src/tex-paper.test.ts +148 -78
- package/src/tex-paper.ts +31 -61
- package/src/tex-venue-rules.test.ts +36 -8
- package/src/tex-venue-rules.ts +3 -5
- package/src/types.ts +0 -2
- 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
- package/fixtures/real-markdown-paper/baseline.json +0 -22
- package/fixtures/real-markdown-paper/baseline.ts +0 -70
- package/skills/paper-pipeline/PIPELINE-MAP.md +0 -381
- package/skills/paper-pipeline/pipeline-firing.baseline.json +0 -270
- package/skills/paper-pipeline/references/sweep-2026-08-09-ideal-pipeline.md +0 -585
- package/skills/paper-pipeline/scripts/artifact-coverage.harness.mjs +0 -492
- package/skills/paper-pipeline/scripts/artifact-coverage.mjs +0 -397
- package/skills/paper-pipeline/scripts/artifact-coverage.test.ts +0 -86
- package/skills/paper-pipeline/scripts/check-provenance.mjs +0 -184
- package/skills/paper-pipeline/scripts/population-map.harness.mjs +0 -135
- package/skills/paper-pipeline/scripts/population-map.mjs +0 -188
- package/skills/paper-pipeline/scripts/population-map.test.mjs +0 -154
- package/skills/paper-pipeline/scripts/provenance.harness.mjs +0 -266
- package/skills/paper-pipeline/scripts/round-diff.harness.ts +0 -907
- package/skills/paper-pipeline/scripts/round-diff.mjs +0 -583
- package/skills/paper-pipeline/scripts/round-diff.test.mjs +0 -159
- package/skills/tighten-paper/__snapshots__/structure.test.ts.snap +0 -133
- package/skills/tighten-paper/structure.mjs +0 -364
- package/skills/tighten-paper/structure.test.ts +0 -117
- package/templates/paper/paper.md +0 -4
|
@@ -43,200 +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 {
|
|
48
|
-
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";
|
|
49
53
|
import { isMain } from "../../paper-pipeline/scripts/consumer.mjs";
|
|
50
54
|
|
|
51
55
|
const DBLP = "https://dblp.org/search/publ/api";
|
|
52
56
|
|
|
53
|
-
/* ---------- input:
|
|
57
|
+
/* ---------- input: the bibliography TeX reads, or a .bib named ---------- */
|
|
58
|
+
|
|
59
|
+
const DEPS = {
|
|
60
|
+
files: nodeFiles,
|
|
61
|
+
codec: sourcesCodec,
|
|
62
|
+
latex: latexReader,
|
|
63
|
+
bib: bibReader,
|
|
64
|
+
};
|
|
54
65
|
|
|
55
66
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* 12. N. F. Liu, K. Lin, J. Hewitt. *Lost in the Middle: How Language Models Use Long
|
|
60
|
-
* Contexts.* TACL, 2024. arXiv:2307.03172.
|
|
61
|
-
*
|
|
62
|
-
* 🔴 Exempting such a paper would be the wrong call, and measurably so: a hand-written author
|
|
63
|
-
* list has NO machine-checkable source, so it is the MORE exposed of the two formats, not the
|
|
64
|
-
* less. The class this script hunts lives exactly where an entry names a published venue and an
|
|
65
|
-
* arXiv id in the same breath — which is most of this list.
|
|
66
|
-
*
|
|
67
|
-
* Authors are taken only from the text BEFORE the first `*`, on purpose: entries here sometimes
|
|
68
|
-
* append a second work ("— and the reanalysis: S. B. Hossain, ...") after the title, and folding
|
|
69
|
-
* those names in would invent co-authors that the first work does not have.
|
|
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
70
|
*/
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
// (`paper-pipeline/scripts/extract-ref-facts.harness.mjs`: "a heading inside a ``` block is not a
|
|
77
|
-
// heading"), that is, the class was known and was being reproduced here again.
|
|
78
|
-
// `requireMarkdown()` — so that a missing markdown-it fails LOUDLY: with an empty parser
|
|
79
|
-
// `headings()` returns [], and the gate would report "no references" instead of refusing. That is
|
|
80
|
-
// the same trade of one silent failure for another that the rule was written against.
|
|
81
|
-
requireMarkdown();
|
|
82
|
-
const h = mdHeadings(text).find((x) => /^References\s*$/u.test(x.text));
|
|
83
|
-
if (!h) return [];
|
|
84
|
-
const body = text.slice(h.offset);
|
|
85
|
-
const out = [];
|
|
86
|
-
// 🔴 `$(?![\s\S])`, NOT `\Z`. JavaScript has no `\Z` anchor — outside a unicode-mode pattern it
|
|
87
|
-
// is an identity escape meaning the LETTER Z, so the first version of this lookahead ended entry
|
|
88
|
-
// 1 in the middle of "J. Zhou", at the Z. Measured: 48 of 67 references parsed, and the missing
|
|
89
|
-
// 19 looked like ordinary gaps rather than a bug. Same family as the `\b`-over-Cyrillic defect
|
|
90
|
-
// already recorded in this repo: an escape that means one thing in Perl/Python and another here.
|
|
91
|
-
const re = /^(\d+)\.[ \t]+([\s\S]*?)(?=^\d+\.[ \t]|^#|$(?![\s\S]))/gm;
|
|
92
|
-
let m;
|
|
93
|
-
while ((m = re.exec(body))) {
|
|
94
|
-
const entry = m[2].replace(/\s*\n\s*/g, " ").trim();
|
|
95
|
-
const t = entry.match(/\*([^*]+)\*/);
|
|
96
|
-
if (!t) {
|
|
97
|
-
// No italic title and no author list — in this corpus these are SOFTWARE references
|
|
98
|
-
// (npm packages, repos). Nothing for DBLP to disagree with. They are surfaced as
|
|
99
|
-
// not-applicable rather than dropped: an entry that silently vanishes between the file
|
|
100
|
-
// and the report is indistinguishable from an entry that passed.
|
|
101
|
-
out.push({
|
|
102
|
-
type: "mdref",
|
|
103
|
-
key: `ref${m[1]}`,
|
|
104
|
-
unparsed: true,
|
|
105
|
-
author: "",
|
|
106
|
-
title: "",
|
|
107
|
-
booktitle: "",
|
|
108
|
-
journal: "",
|
|
109
|
-
});
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
const authorPart = entry
|
|
113
|
-
.slice(0, entry.indexOf("*"))
|
|
114
|
-
.trim()
|
|
115
|
-
.replace(/[.,;]\s*$/, "");
|
|
116
|
-
const rest = entry.slice(entry.indexOf(t[0]) + t[0].length);
|
|
117
|
-
out.push({
|
|
118
|
-
type: "mdref",
|
|
119
|
-
key: `ref${m[1]}`,
|
|
120
|
-
author: authorPart
|
|
121
|
-
.split(/,\s*|\s+and\s+/)
|
|
122
|
-
.filter(Boolean)
|
|
123
|
-
.join(" and "),
|
|
124
|
-
title: t[1].replace(/\.$/, "").trim(),
|
|
125
|
-
booktitle: rest.trim(),
|
|
126
|
-
journal: "",
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
return out;
|
|
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
|
-
let file = target;
|
|
134
|
-
if (!existsSync(file)) die(`no such path: ${file}`);
|
|
135
|
-
if (!extname(file)) {
|
|
136
|
-
const names = readdirSync(file);
|
|
137
|
-
// 🔴 THE CANONICAL NAME FIRST, then the only candidate, and never "whichever
|
|
138
|
-
// turns up first" (review #189). It used to be `names.find(f => f.endsWith(".tex"))`,
|
|
139
|
-
// that is, the first in directory order. Measured: one of the papers in the corpus
|
|
140
|
-
// has FIVE drafts lying around (paper-CONSTRUCTIVE-…, paper-FOLDED-…, paper-SAFE-…),
|
|
141
|
-
// while PIPELINE-STATUS.md names `paper.tex` as the submitted source. The author
|
|
142
|
-
// check went off into a stale draft and printed a verdict about the WRONG
|
|
143
|
-
// bibliography — a gate that checks the wrong file is worse than a missing one,
|
|
144
|
-
// because it says "checked".
|
|
145
|
-
const pick = (ext) => {
|
|
146
|
-
const canon = names.find((f) => f === `paper${ext}`);
|
|
147
|
-
if (canon) return canon;
|
|
148
|
-
const all = names.filter((f) => f.endsWith(ext)).sort();
|
|
149
|
-
if (all.length === 1) return all[0];
|
|
150
|
-
if (all.length > 1) {
|
|
151
|
-
// We do not fail: a new check here would set up one more surface, and its
|
|
152
|
-
// home is decided by the ladder in the `CLAUDE.md` next to the papers, not by
|
|
153
|
-
// this script (the `frozen-checks` ratchet is what guards that). It is enough
|
|
154
|
-
// to SAY it out loud and to choose DETERMINISTICALLY — the header below prints
|
|
155
|
-
// which file was checked anyway, so the reader sees the choice.
|
|
156
|
-
console.error(
|
|
157
|
-
`⚠️ ${file} holds several ${ext} files and no canonical paper${ext}: ` +
|
|
158
|
-
`${all.join(", ")} — took ${all[0]}. If that is the wrong one, name the file explicitly.`,
|
|
159
|
-
);
|
|
160
|
-
return all[0];
|
|
161
|
-
}
|
|
162
|
-
return undefined;
|
|
163
|
-
};
|
|
164
|
-
const found =
|
|
165
|
-
pick(".bib") ||
|
|
166
|
-
pick(".tex") ||
|
|
167
|
-
names.find((f) => f === "paper.md" || f === "draft.md");
|
|
168
|
-
if (!found) die(`no .bib, .tex or paper.md in ${file}`);
|
|
169
|
-
file = join(file, found);
|
|
170
|
-
}
|
|
171
|
-
const text = readFileSync(file, "utf-8");
|
|
172
|
-
if (file.endsWith(".md")) return { text, file, markdown: true };
|
|
173
|
-
if (file.endsWith(".bib")) return { text, file };
|
|
174
|
-
// A .tex may carry the bibliography inline via filecontents — that is how our papers do it.
|
|
175
|
-
const m = text.match(
|
|
176
|
-
/\\begin\{filecontents\*?\}(?:\[[^\]]*\])?\{[^}]*\.bib\}\r?\n([\s\S]*?)\\end\{filecontents\*?\}/,
|
|
177
|
-
);
|
|
178
|
-
if (m) return { text: m[1], file };
|
|
179
|
-
const sibling = join(file, "..", "refs.bib");
|
|
180
|
-
if (existsSync(sibling))
|
|
181
|
-
return { text: readFileSync(sibling, "utf-8"), file: sibling };
|
|
182
|
-
die(
|
|
183
|
-
`no bibliography found in ${file} (no filecontents block, no refs.bib beside it)`,
|
|
184
|
-
);
|
|
185
|
-
}
|
|
78
|
+
/* ---------- entries: as the bibtex reader read them ---------- */
|
|
186
79
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const re = /@(\w+)\s*\{\s*([^,\s]+)\s*,/g;
|
|
194
|
-
let m;
|
|
195
|
-
while ((m = re.exec(text))) {
|
|
196
|
-
const [, type, key] = m;
|
|
197
|
-
let i = re.lastIndex,
|
|
198
|
-
depth = 1;
|
|
199
|
-
while (i < text.length && depth > 0) {
|
|
200
|
-
if (text[i] === "{") depth++;
|
|
201
|
-
else if (text[i] === "}") depth--;
|
|
202
|
-
i++;
|
|
203
|
-
}
|
|
204
|
-
const body = text.slice(re.lastIndex, i - 1);
|
|
205
|
-
out.push({ type: type.toLowerCase(), key, ...fields(body) });
|
|
206
|
-
}
|
|
207
|
-
return out;
|
|
208
|
-
}
|
|
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(", ");
|
|
209
86
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
j++;
|
|
224
|
-
}
|
|
225
|
-
return body.slice(i + 1, j - 1);
|
|
226
|
-
}
|
|
227
|
-
if (body[i] === '"') {
|
|
228
|
-
const j = body.indexOf('"', i + 1);
|
|
229
|
-
return body.slice(i + 1, j);
|
|
230
|
-
}
|
|
231
|
-
const j = body.indexOf(",", i);
|
|
232
|
-
return body.slice(i, j === -1 ? undefined : j);
|
|
233
|
-
};
|
|
234
|
-
return {
|
|
235
|
-
author: get("author"),
|
|
236
|
-
title: get("title"),
|
|
237
|
-
booktitle: get("booktitle"),
|
|
238
|
-
journal: get("journal"),
|
|
239
|
-
};
|
|
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
|
+
}));
|
|
240
100
|
}
|
|
241
101
|
|
|
242
102
|
/* ---------- normalisation: surnames only, in order ---------- */
|
|
@@ -358,13 +218,6 @@ export async function checkAuthors(
|
|
|
358
218
|
const matched = []; // compared against the published record, and equal
|
|
359
219
|
const entries = parsed.filter((e) => e.title && e.author);
|
|
360
220
|
|
|
361
|
-
for (const e of parsed.filter((x) => x.unparsed)) {
|
|
362
|
-
skipped.push({
|
|
363
|
-
key: e.key,
|
|
364
|
-
why: "no author/title — a reference to software or a dataset, DBLP does not apply",
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
|
|
368
221
|
for (const e of entries) {
|
|
369
222
|
if (!claimsPublished(e)) continue; // a preprint entry is allowed to carry preprint metadata
|
|
370
223
|
if (truncated(e.author)) {
|
|
@@ -438,14 +291,22 @@ async function main() {
|
|
|
438
291
|
if (!args[0])
|
|
439
292
|
die("usage: bib-authors.mjs <paper-dir|file.bib|file.tex> [--json]");
|
|
440
293
|
|
|
441
|
-
const {
|
|
442
|
-
const
|
|
294
|
+
const { files, texts } = bibliographyFrom(args[0]);
|
|
295
|
+
const file = files.join(", ");
|
|
296
|
+
const parsed = texts.flatMap((t) => authorEntries(t.entries));
|
|
443
297
|
const entries = parsed.filter((e) => e.title && e.author);
|
|
444
298
|
const { findings, skipped, unchecked } = await checkAuthors(parsed);
|
|
445
299
|
if (asJson) {
|
|
446
300
|
console.log(
|
|
447
301
|
JSON.stringify(
|
|
448
|
-
{
|
|
302
|
+
{
|
|
303
|
+
file: files[0],
|
|
304
|
+
files,
|
|
305
|
+
entries: entries.length,
|
|
306
|
+
findings,
|
|
307
|
+
skipped,
|
|
308
|
+
unchecked,
|
|
309
|
+
},
|
|
449
310
|
null,
|
|
450
311
|
2,
|
|
451
312
|
),
|
|
@@ -14,12 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import assert from "node:assert/strict";
|
|
16
16
|
import { test } from "vitest";
|
|
17
|
-
import {
|
|
18
|
-
surnames,
|
|
19
|
-
compare,
|
|
20
|
-
claimsPublished,
|
|
21
|
-
parseMarkdownRefs,
|
|
22
|
-
} from "./bib-authors.mjs";
|
|
17
|
+
import { surnames, compare, claimsPublished } from "./bib-authors.mjs";
|
|
23
18
|
|
|
24
19
|
const t = test;
|
|
25
20
|
|
|
@@ -135,41 +130,3 @@ t("a proceedings entry IS in scope", () => {
|
|
|
135
130
|
true,
|
|
136
131
|
);
|
|
137
132
|
});
|
|
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
|
-
});
|
|
@@ -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
|
}
|