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
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* paper/section-word · paper/leading-zero · paper/figure-ref-style
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* paper/section-word · paper/leading-zero · paper/figure-ref-style — each FIRES on a bad input,
|
|
3
|
+
* stays SILENT on a good one, reports every occurrence at its own place, and fixes to an exact
|
|
4
|
+
* output. Plus the escape hatch: a disable directive in a `%` comment silences exactly one finding,
|
|
5
|
+
* and a directive that silences nothing is itself reported. (`bib/reachable-entry`, the fourth check
|
|
6
|
+
* of the old `paper/typography`, is tested beside it in src/bib-rules.test.ts.)
|
|
7
7
|
*
|
|
8
8
|
* The leading-zero cases are the ones paperlint#44 measured: a regex over the raw source found 22
|
|
9
9
|
* decimals on a real corpus and none was a defect, so the SILENT list is as load-bearing as the
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { describe, expect, it } from "vitest";
|
|
13
13
|
import { ESLint } from "eslint";
|
|
14
|
-
import markdown from "@eslint/markdown";
|
|
15
14
|
import { readFileSync } from "node:fs";
|
|
16
15
|
import { dirname, join } from "node:path";
|
|
17
16
|
import { fileURLToPath } from "node:url";
|
|
@@ -20,7 +19,6 @@ import typography, {
|
|
|
20
19
|
crefDefinitionRanges,
|
|
21
20
|
texVisibleRuns,
|
|
22
21
|
} from "./paper-typography.ts";
|
|
23
|
-
import bib from "./bib-reachable-entry.ts";
|
|
24
22
|
|
|
25
23
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
26
24
|
const FIX = join(HERE, "..", "fixtures", "paper-typography");
|
|
@@ -29,7 +27,6 @@ const RULES = {
|
|
|
29
27
|
"paper/section-word": "warn",
|
|
30
28
|
"paper/leading-zero": "warn",
|
|
31
29
|
"paper/figure-ref-style": "warn",
|
|
32
|
-
"bib/reachable-entry": "warn",
|
|
33
30
|
};
|
|
34
31
|
|
|
35
32
|
const eslint = (fix = false) =>
|
|
@@ -43,22 +40,11 @@ const eslint = (fix = false) =>
|
|
|
43
40
|
plugins: {
|
|
44
41
|
tex: { languages: { latex: texLanguage } },
|
|
45
42
|
paper: typography,
|
|
46
|
-
bib,
|
|
47
43
|
},
|
|
48
44
|
language: "tex/latex",
|
|
49
45
|
linterOptions: { reportUnusedDisableDirectives: "error" },
|
|
50
46
|
rules: RULES,
|
|
51
47
|
},
|
|
52
|
-
{
|
|
53
|
-
files: ["**/*.md"],
|
|
54
|
-
plugins: { markdown, paper: typography },
|
|
55
|
-
language: "markdown/gfm",
|
|
56
|
-
languageOptions: { frontmatter: "yaml" },
|
|
57
|
-
rules: {
|
|
58
|
-
"paper/section-word": "warn",
|
|
59
|
-
"paper/leading-zero": "warn",
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
48
|
],
|
|
63
49
|
});
|
|
64
50
|
|
|
@@ -85,8 +71,6 @@ describe("the fixtures: messy fires on each rule, clean is silent", () => {
|
|
|
85
71
|
expect(count("paper/section-word")).toBe(3);
|
|
86
72
|
expect(count("paper/leading-zero")).toBe(2);
|
|
87
73
|
expect(count("paper/figure-ref-style")).toBe(1);
|
|
88
|
-
expect(count("bib/reachable-entry")).toBe(2);
|
|
89
|
-
expect(ids(res, "bib/reachable-entry").map((m) => m.line)).toEqual([3, 8]);
|
|
90
74
|
});
|
|
91
75
|
|
|
92
76
|
it("clean-paper — nothing", async () => {
|
|
@@ -121,6 +105,13 @@ describe("paper/section-word", () => {
|
|
|
121
105
|
expect(ids(await lint(src), "paper/section-word")).toEqual([]);
|
|
122
106
|
});
|
|
123
107
|
|
|
108
|
+
it("🔴 silent on a bibliography whose file name stands on the line after \\begin{filecontents*}", async () => {
|
|
109
|
+
const src =
|
|
110
|
+
"\\documentclass{acmart}\n\\begin{filecontents*}\n{refs.bib}\n@misc{k, note = {see §5}, url = {https://x.org}}\n\\end{filecontents*}\n" +
|
|
111
|
+
"\\begin{document}\nSection~\\ref{a}.\n\\end{document}\n";
|
|
112
|
+
expect(ids(await lint(src), "paper/section-word")).toEqual([]);
|
|
113
|
+
});
|
|
114
|
+
|
|
124
115
|
it("🔴 a cleveref name definition is not prose: silent, and --fix leaves it as written", async () => {
|
|
125
116
|
const src =
|
|
126
117
|
"\\documentclass{article}\n\\usepackage{cleveref}\n\\crefname{section}{§}{§§}\n\\Crefname{section}{§}{§§}\n" +
|
|
@@ -164,14 +155,6 @@ describe("paper/section-word", () => {
|
|
|
164
155
|
"\\documentclass{article}\n\\crefname{section}{§}{§§}\n\\begin{document}\nSee §5.\n\\end{document}\n";
|
|
165
156
|
expect(ids(await lint(src), "paper/section-word")).toHaveLength(1);
|
|
166
157
|
});
|
|
167
|
-
|
|
168
|
-
it("markdown: `§5` → `Section 5`, code untouched", async () => {
|
|
169
|
-
const src = "See §5 and `§3` here.\n";
|
|
170
|
-
expect(ids(await lint(src, "paper.md"), "paper/section-word")).toHaveLength(
|
|
171
|
-
1,
|
|
172
|
-
);
|
|
173
|
-
expect(await fixed(src, "paper.md")).toBe("See Section 5 and `§3` here.\n");
|
|
174
|
-
});
|
|
175
158
|
});
|
|
176
159
|
|
|
177
160
|
describe("paper/leading-zero", () => {
|
|
@@ -237,17 +220,6 @@ describe("paper/leading-zero", () => {
|
|
|
237
220
|
doc("At $p < 0.05$ and $d=0.21$; cell & 0.037 \\\\ and .5 or 0.25."),
|
|
238
221
|
);
|
|
239
222
|
});
|
|
240
|
-
|
|
241
|
-
it("markdown: prose and a table cell are fixed, code is not", async () => {
|
|
242
|
-
const src =
|
|
243
|
-
"Significant at p<.05.\n\n| task | p |\n| --- | --- |\n| bugfix | .002 |\n\nRun `.25`.\n";
|
|
244
|
-
expect(ids(await lint(src, "paper.md"), "paper/leading-zero")).toHaveLength(
|
|
245
|
-
2,
|
|
246
|
-
);
|
|
247
|
-
expect(await fixed(src, "paper.md")).toBe(
|
|
248
|
-
"Significant at p<0.05.\n\n| task | p |\n| --- | --- |\n| bugfix | 0.002 |\n\nRun `.25`.\n",
|
|
249
|
-
);
|
|
250
|
-
});
|
|
251
223
|
});
|
|
252
224
|
|
|
253
225
|
describe("a finding after a heading written over several lines", () => {
|
|
@@ -290,15 +262,11 @@ describe("paper/leading-zero — a number after ¶ or § is not a decimal (#102)
|
|
|
290
262
|
expect(await fixed(doc(CITE))).toBe(doc(CITE));
|
|
291
263
|
});
|
|
292
264
|
|
|
293
|
-
it("…while a real decimal still fixes,
|
|
265
|
+
it("…while a real decimal still fixes, even beside a cited paragraph", async () => {
|
|
294
266
|
const body = `${CITE}\n\nWe use a threshold of .05 throughout.`;
|
|
295
267
|
expect(await fixed(doc(body))).toBe(
|
|
296
268
|
doc(`${CITE}\n\nWe use a threshold of 0.05 throughout.`),
|
|
297
269
|
);
|
|
298
|
-
const md = "See ¶¶.42 and .44.\n\nSignificant at p<.05.\n";
|
|
299
|
-
expect(await fixed(md, "paper.md")).toBe(
|
|
300
|
-
"See ¶¶.42 and .44.\n\nSignificant at p<0.05.\n",
|
|
301
|
-
);
|
|
302
270
|
});
|
|
303
271
|
});
|
|
304
272
|
|
|
@@ -330,69 +298,7 @@ describe("paper/figure-ref-style", () => {
|
|
|
330
298
|
});
|
|
331
299
|
});
|
|
332
300
|
|
|
333
|
-
describe("bib/reachable-entry", () => {
|
|
334
|
-
const bibDoc = (entries) =>
|
|
335
|
-
`\\documentclass{acmart}\n\\begin{filecontents*}{refs.bib}\n${entries}\n\\end{filecontents*}\n\\begin{document}\nx\n\\end{document}\n`;
|
|
336
|
-
|
|
337
|
-
it("a doi, a url or an arXiv id each make an entry reachable", async () => {
|
|
338
|
-
const res = await lint(
|
|
339
|
-
bibDoc(
|
|
340
|
-
"@article{a, doi = {10.1/x}}\n@misc{b, url = {https://x.org}}\n@misc{c, note = {arXiv:2310.05736}}\n@string{v = {x}}",
|
|
341
|
-
),
|
|
342
|
-
);
|
|
343
|
-
expect(ids(res, "bib/reachable-entry")).toEqual([]);
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
it("reports on the entry itself, naming its key", async () => {
|
|
347
|
-
const res = await lint(
|
|
348
|
-
bibDoc("@misc{ok, url = {https://x.org}}\n@article{lost, title = {x}}"),
|
|
349
|
-
);
|
|
350
|
-
const [m] = ids(res, "bib/reachable-entry");
|
|
351
|
-
expect(m.line).toBe(4);
|
|
352
|
-
expect(m.column).toBe(1);
|
|
353
|
-
expect(m.message).toMatch(/`lost`/);
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
it("an entry with no key is reported as `?`, not skipped", async () => {
|
|
357
|
-
const [m] = ids(
|
|
358
|
-
await lint(bibDoc("@article{\n title = {x}}")),
|
|
359
|
-
"bib/reachable-entry",
|
|
360
|
-
);
|
|
361
|
-
expect(m.message).toMatch(/`\?`/);
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
it("a source code with only `text`, or with neither text nor raw", () => {
|
|
365
|
-
const run = (sourceCode) => {
|
|
366
|
-
const out = [];
|
|
367
|
-
bib.rules["reachable-entry"]
|
|
368
|
-
.create({
|
|
369
|
-
sourceCode: {
|
|
370
|
-
getLocFromIndex: (i) => ({ line: 1, column: i }),
|
|
371
|
-
...sourceCode,
|
|
372
|
-
},
|
|
373
|
-
report: (r) => out.push(r.data.key),
|
|
374
|
-
})
|
|
375
|
-
["root:exit"]();
|
|
376
|
-
return out;
|
|
377
|
-
};
|
|
378
|
-
expect(run({ text: bibDoc("@article{t, title = {x}}") })).toEqual(["t"]);
|
|
379
|
-
expect(run({})).toEqual([]);
|
|
380
|
-
});
|
|
381
|
-
});
|
|
382
|
-
|
|
383
301
|
describe("the escape hatch — a disable directive in a `%` comment", () => {
|
|
384
|
-
it("🔴 inside the filecontents bibliography, it silences exactly the next entry", async () => {
|
|
385
|
-
const res = await lint(
|
|
386
|
-
`\\documentclass{acmart}\n\\begin{filecontents*}{refs.bib}\n` +
|
|
387
|
-
"% eslint-disable-next-line bib/reachable-entry -- an invited talk, no recording exists\n" +
|
|
388
|
-
"@misc{talk, title = {x}}\n@misc{lost, title = {y}}\n" +
|
|
389
|
-
"\\end{filecontents*}\n\\begin{document}\nx\n\\end{document}\n",
|
|
390
|
-
);
|
|
391
|
-
const found = ids(res, "bib/reachable-entry");
|
|
392
|
-
expect(found).toHaveLength(1);
|
|
393
|
-
expect(found[0].message).toMatch(/`lost`/);
|
|
394
|
-
});
|
|
395
|
-
|
|
396
302
|
it("in the body: next-line, same-line, and a disable/enable region", async () => {
|
|
397
303
|
const src = doc(
|
|
398
304
|
[
|
|
@@ -467,48 +373,6 @@ describe("where visible text comes from — the walk's other paths", () => {
|
|
|
467
373
|
});
|
|
468
374
|
});
|
|
469
375
|
|
|
470
|
-
describe("markdown: offsets that cannot be followed, and rules that are LaTeX-only", () => {
|
|
471
|
-
const mdLint = async (text, rules) => {
|
|
472
|
-
const [res] = await new ESLint({
|
|
473
|
-
cwd: FIX,
|
|
474
|
-
overrideConfigFile: true,
|
|
475
|
-
overrideConfig: [
|
|
476
|
-
{
|
|
477
|
-
files: ["**/*.md"],
|
|
478
|
-
plugins: { markdown, paper: typography },
|
|
479
|
-
language: "markdown/gfm",
|
|
480
|
-
rules,
|
|
481
|
-
},
|
|
482
|
-
],
|
|
483
|
-
}).lintText(text, { filePath: join(FIX, "inline", "paper.md") });
|
|
484
|
-
expect(res.messages.filter((m) => m.fatal)).toEqual([]);
|
|
485
|
-
return res;
|
|
486
|
-
};
|
|
487
|
-
|
|
488
|
-
it("`§` before prose is reported without a fix", async () => {
|
|
489
|
-
const src = "As § above.\n";
|
|
490
|
-
const res = await mdLint(src, { "paper/section-word": "warn" });
|
|
491
|
-
expect(res.messages.map((m) => [m.ruleId, m.column, m.fix])).toEqual([
|
|
492
|
-
["paper/section-word", 4, undefined],
|
|
493
|
-
]);
|
|
494
|
-
});
|
|
495
|
-
|
|
496
|
-
it("a sign or a dot written as a character reference has no source offset: nothing is reported", async () => {
|
|
497
|
-
const res = await mdLint("See § 5 and .25.\n", {
|
|
498
|
-
"paper/section-word": "warn",
|
|
499
|
-
"paper/leading-zero": "warn",
|
|
500
|
-
});
|
|
501
|
-
expect(res.messages).toEqual([]);
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
it("figure-ref-style is LaTeX-only: silent on markdown", async () => {
|
|
505
|
-
const res = await mdLint("Figure~\\ref{a} and Fig.~\\ref{b}.\n", {
|
|
506
|
-
"paper/figure-ref-style": "warn",
|
|
507
|
-
});
|
|
508
|
-
expect(res.messages).toEqual([]);
|
|
509
|
-
});
|
|
510
|
-
});
|
|
511
|
-
|
|
512
376
|
describe("a source code the rules did not expect — the defaults hold", () => {
|
|
513
377
|
const reports = (ruleId, sourceCode) => {
|
|
514
378
|
const out = [];
|
|
@@ -530,12 +394,20 @@ describe("a source code the rules did not expect — the defaults hold", () => {
|
|
|
530
394
|
]);
|
|
531
395
|
});
|
|
532
396
|
|
|
533
|
-
it("
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
397
|
+
it.each(["section-word", "leading-zero", "figure-ref-style"])(
|
|
398
|
+
"%s reads LaTeX only: a source without `raw` (any other language) is not reported",
|
|
399
|
+
(ruleId) => {
|
|
400
|
+
const ast = {
|
|
401
|
+
type: "root",
|
|
402
|
+
children: [
|
|
403
|
+
{
|
|
404
|
+
type: "text",
|
|
405
|
+
value: "§5 and .25",
|
|
406
|
+
position: { start: { offset: 0 }, end: { offset: 10 } },
|
|
407
|
+
},
|
|
408
|
+
],
|
|
409
|
+
};
|
|
410
|
+
expect(reports(ruleId, { text: "§5 and .25", ast })).toEqual([]);
|
|
411
|
+
},
|
|
412
|
+
);
|
|
541
413
|
});
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* paper/figure-ref-style `Fig.~\ref` beside `Figure~\ref` in one document → the majority form
|
|
8
8
|
*
|
|
9
9
|
* The fourth check of the old `paper/typography`, an unreachable bibliography entry, is
|
|
10
|
-
* `bib/reachable-entry` (bib-
|
|
11
|
-
*
|
|
10
|
+
* `bib/reachable-entry` (src/bib-rules.ts): it is about the bibliography, not the prose, and it has
|
|
11
|
+
* no fix.
|
|
12
12
|
*
|
|
13
13
|
* ── PROVENANCE: every one is a real review finding, not an invented style ───────────────
|
|
14
14
|
* From HotCRP #20 (2026-08-23). Reviewer B listed two as literal to-dos:
|
|
@@ -41,11 +41,9 @@
|
|
|
41
41
|
* IEEE Editorial Style Manual ("0.25, not .25") require the zero. A regex over the raw source
|
|
42
42
|
* found 22 decimals on a real corpus and none was a defect: every one sat in markup (an option,
|
|
43
43
|
* a column spec, a comment, a listing, a tikz coordinate). So this walks the TREE and keeps only
|
|
44
|
-
* what the reader sees as a number:
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* comments, verbatim/listings/`\lstinline`, tikz, the preamble, the bibliography.
|
|
48
|
-
* Markdown text nodes, table cells included. Not: code, inline code, html, front matter.
|
|
44
|
+
* what the reader sees as a number: prose, inline and display math, table cells, and the text
|
|
45
|
+
* arguments of a known set of text macros. Not: any other macro's arguments, environment
|
|
46
|
+
* arguments, comments, verbatim/listings/`\lstinline`, tikz, the preamble, the bibliography.
|
|
49
47
|
* Each character of a run keeps its source offset, so the finding points at the dot and the fix
|
|
50
48
|
* inserts the zero there. The arXiv lookbehind stays: in `2310.05736` the dot follows a digit.
|
|
51
49
|
* A text macro outside the known set (`\hl{.05}`) is read as a parameter and NOT reported — a
|
|
@@ -62,7 +60,6 @@
|
|
|
62
60
|
import assert from "node:assert/strict";
|
|
63
61
|
import { getParser } from "@unified-latex/unified-latex-util-parse";
|
|
64
62
|
import { takeWhile } from "remeda";
|
|
65
|
-
import type { Nodes } from "mdast";
|
|
66
63
|
import {
|
|
67
64
|
texToMdast,
|
|
68
65
|
type TexArg,
|
|
@@ -78,7 +75,7 @@ import type {
|
|
|
78
75
|
} from "./rule-context.ts";
|
|
79
76
|
|
|
80
77
|
/** Where a `.bib` embedded in a `filecontents` environment sits in the file. */
|
|
81
|
-
|
|
78
|
+
interface BibRange {
|
|
82
79
|
readonly start: number;
|
|
83
80
|
readonly end: number;
|
|
84
81
|
readonly bodyStart: number;
|
|
@@ -94,10 +91,17 @@ interface Run {
|
|
|
94
91
|
offs: (number | null)[];
|
|
95
92
|
}
|
|
96
93
|
|
|
97
|
-
/**
|
|
98
|
-
|
|
94
|
+
/**
|
|
95
|
+
* The inline bibliography — a `filecontents` block writing a `.bib`, its name on the `\begin` line or
|
|
96
|
+
* after white space on a later one, as TeX takes it — with its offsets: the span the markup rules
|
|
97
|
+
* skip. Which bibliography TeX reads is the build's record (`_build/sources.json`,
|
|
98
|
+
* docs/design/paper-sources.md §1);
|
|
99
|
+
* this range only says which bytes are not prose, and moves onto the one projection of live text
|
|
100
|
+
* with the rest of `skippedRanges` (Q3, docs/design/paper-sources.md §10).
|
|
101
|
+
*/
|
|
102
|
+
function bibRange(text: string): BibRange | null {
|
|
99
103
|
const m =
|
|
100
|
-
/\\begin\{filecontents\*?\}(?:\[[^\]]*\])?\{[^}]*\.bib\}
|
|
104
|
+
/\\begin\{filecontents\*?\}\s*(?:\[[^\]]*\]\s*)?\{[^}]*\.bib\}[^\n]*\n([\s\S]*?)\\end\{filecontents\*?\}/d.exec(
|
|
101
105
|
text,
|
|
102
106
|
);
|
|
103
107
|
if (!m) return null;
|
|
@@ -108,7 +112,7 @@ export function bibRange(text: string): BibRange | null {
|
|
|
108
112
|
return { start: m.index, end: m.index + whole.length, bodyStart, body };
|
|
109
113
|
}
|
|
110
114
|
|
|
111
|
-
/** A node of
|
|
115
|
+
/** A node of the `.tex` language's projection, as far as its place goes. */
|
|
112
116
|
interface ProjectedNode {
|
|
113
117
|
readonly type: string;
|
|
114
118
|
readonly position?:
|
|
@@ -372,66 +376,6 @@ export function texVisibleRuns(
|
|
|
372
376
|
return texRuns(document ? document.content : root, false, raw, []);
|
|
373
377
|
}
|
|
374
378
|
|
|
375
|
-
// Markdown node types that are not prose.
|
|
376
|
-
const MD_HIDDEN = new Set([
|
|
377
|
-
"code",
|
|
378
|
-
"inlineCode",
|
|
379
|
-
"html",
|
|
380
|
-
"yaml",
|
|
381
|
-
"toml",
|
|
382
|
-
"math",
|
|
383
|
-
"inlineMath",
|
|
384
|
-
]);
|
|
385
|
-
/**
|
|
386
|
-
* The source offset of each character of a text node's value. The value is the source with
|
|
387
|
-
* markdown's escapes (`\*`) and character references (`<`) resolved, so the two are walked
|
|
388
|
-
* side by side; a character whose source cannot be followed gets null (reported, not fixed).
|
|
389
|
-
*/
|
|
390
|
-
function mdOffsets(value: string, src: string, start: number) {
|
|
391
|
-
const offs: (number | null)[] = [];
|
|
392
|
-
let j = start;
|
|
393
|
-
for (const ch of value) {
|
|
394
|
-
if (src[j] === ch) offs.push(j++);
|
|
395
|
-
else if (src[j] === "\\" && src[j + 1] === ch) {
|
|
396
|
-
offs.push(j + 1);
|
|
397
|
-
j += 2;
|
|
398
|
-
} else if (src[j] === "&" && /^&#?\w{1,8};/.test(src.slice(j, j + 10))) {
|
|
399
|
-
offs.push(null);
|
|
400
|
-
j = src.indexOf(";", j) + 1;
|
|
401
|
-
} else {
|
|
402
|
-
offs.push(null);
|
|
403
|
-
j++;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
return offs;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
function mdVisibleRuns(
|
|
410
|
-
node: Nodes | undefined,
|
|
411
|
-
src: string,
|
|
412
|
-
out: Run[] = [],
|
|
413
|
-
): Run[] {
|
|
414
|
-
if (!node || MD_HIDDEN.has(node.type)) return out;
|
|
415
|
-
if (node.type === "text") {
|
|
416
|
-
const start = node.position?.start.offset;
|
|
417
|
-
out.push({
|
|
418
|
-
text: node.value,
|
|
419
|
-
offs:
|
|
420
|
-
typeof start === "number"
|
|
421
|
-
? mdOffsets(node.value, src, start)
|
|
422
|
-
: Array.from(node.value, () => null),
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
|
-
for (const c of "children" in node ? node.children : [])
|
|
426
|
-
mdVisibleRuns(c, src, out);
|
|
427
|
-
return out;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
const visibleRuns = (sourceCode: RuleSourceCode): Run[] =>
|
|
431
|
-
isTex(sourceCode)
|
|
432
|
-
? texVisibleRuns(sourceCode.raw)
|
|
433
|
-
: mdVisibleRuns(sourceCode.ast, sourceCode.text);
|
|
434
|
-
|
|
435
379
|
/** A report at `[from, to)` of the file. */
|
|
436
380
|
const at = (
|
|
437
381
|
context: RuleContext,
|
|
@@ -455,36 +399,22 @@ const SECTION_GLYPH = /§/g;
|
|
|
455
399
|
|
|
456
400
|
function sectionWord(context: RuleContext) {
|
|
457
401
|
const sc = context.sourceCode;
|
|
458
|
-
if (isTex(sc))
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
}
|
|
469
|
-
// A glyph before neither a reference nor a number: reported, not rewritten — there is no
|
|
470
|
-
// single right word for a doubled sign or a sign before prose.
|
|
471
|
-
for (const m of markupMatches(sc, SECTION_GLYPH))
|
|
472
|
-
if (!seen.has(m.index))
|
|
473
|
-
at(context, m.index, m.index + 1, { messageId: "sign" });
|
|
474
|
-
return;
|
|
402
|
+
if (!isTex(sc)) return;
|
|
403
|
+
const seen = new Set();
|
|
404
|
+
for (const m of markupMatches(sc, SECTION_TEX)) {
|
|
405
|
+
seen.add(m.index);
|
|
406
|
+
const word = m[3] ? "Section~" : "Section ";
|
|
407
|
+
const range: [number, number] = [m.index, m.index + m[0].length];
|
|
408
|
+
at(context, range[0], range[1], {
|
|
409
|
+
messageId: "sign",
|
|
410
|
+
fix: (f) => f.replaceTextRange(range, word),
|
|
411
|
+
});
|
|
475
412
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
const [, gap = ""] = m;
|
|
482
|
-
const range: [number, number] = [from, from + 1 + gap.length];
|
|
483
|
-
at(context, range[0], range[1], {
|
|
484
|
-
messageId: "sign",
|
|
485
|
-
...(m[2] ? { fix: (f) => f.replaceTextRange(range, "Section ") } : {}),
|
|
486
|
-
});
|
|
487
|
-
}
|
|
413
|
+
// A glyph before neither a reference nor a number: reported, not rewritten — there is no
|
|
414
|
+
// single right word for a doubled sign or a sign before prose.
|
|
415
|
+
for (const m of markupMatches(sc, SECTION_GLYPH))
|
|
416
|
+
if (!seen.has(m.index))
|
|
417
|
+
at(context, m.index, m.index + 1, { messageId: "sign" });
|
|
488
418
|
}
|
|
489
419
|
|
|
490
420
|
// ── paper/leading-zero ─────────────────────────────────────────────────────────────────
|
|
@@ -508,8 +438,9 @@ const paragraphBefore = (src: string, i: number) => {
|
|
|
508
438
|
|
|
509
439
|
function leadingZero(context: RuleContext) {
|
|
510
440
|
const sc = context.sourceCode;
|
|
511
|
-
|
|
512
|
-
|
|
441
|
+
if (!isTex(sc)) return;
|
|
442
|
+
const src = sc.raw;
|
|
443
|
+
for (const r of texVisibleRuns(src))
|
|
513
444
|
for (const m of r.text.matchAll(BARE_DECIMAL)) {
|
|
514
445
|
const dot = r.offs[m.index];
|
|
515
446
|
if (dot === null || dot === undefined) continue;
|
|
@@ -170,7 +170,7 @@ for (const bad of ["", null, 0, false, [], {}, 42])
|
|
|
170
170
|
const names = Object.keys(sets).sort();
|
|
171
171
|
assert.deepEqual(
|
|
172
172
|
names,
|
|
173
|
-
["all", "
|
|
173
|
+
["all", "pdfFacts", "refFacts", "status", "tex", "venue"],
|
|
174
174
|
"the glob set changed — a consumer's config enumerates these by name, and a renamed or " +
|
|
175
175
|
"dropped key is a block that silently lints nothing",
|
|
176
176
|
);
|
package/eslint-rules/papers.ts
CHANGED
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
*
|
|
42
42
|
* A FILENAME MARKER, no root at all (`**\/paper.tex`).
|
|
43
43
|
* Measured on the first consumer: `paper.tex` ×4 with one outside the corpus,
|
|
44
|
-
* `PIPELINE-STATUS.md` ×6 with two of them test fixtures
|
|
44
|
+
* `PIPELINE-STATUS.md` ×6 with two of them test fixtures.
|
|
45
45
|
* The guard that would consume this is a `deny` at `error` severity; a denial on writing
|
|
46
46
|
* a fixture gets the hook switched off the same day.
|
|
47
47
|
*
|
|
@@ -134,15 +134,13 @@ export function papersRoot(
|
|
|
134
134
|
* The glob sets a consumer declares its blocks on, derived from one root.
|
|
135
135
|
*
|
|
136
136
|
* 🔴 THE SHAPES LIVE HERE, NOT IN THE CONSUMER, and that is the half that keeps a rule from
|
|
137
|
-
* going blind later. `paper.
|
|
137
|
+
* going blind later. `paper.tex`, `_build/*.facts.json`, `PIPELINE-STATUS.md` are
|
|
138
138
|
* this pipeline's CONVENTION; if a rule starts reading a new file, the glob for it must arrive
|
|
139
139
|
* with the rule, in the same package, rather than as a line every consumer has to add by hand
|
|
140
140
|
* to keep up. A hand-maintained list of paths is the thing that rots, and rots silently.
|
|
141
141
|
*/
|
|
142
142
|
export function paperFiles(root: string) {
|
|
143
143
|
return {
|
|
144
|
-
/** Markdown drafts — the two names a paper's prose is allowed to have. */
|
|
145
|
-
md: [`${root}/*/paper.md`, `${root}/*/draft.md`],
|
|
146
144
|
/** The LaTeX source of a paper (not its figures, not its vendored drafts). */
|
|
147
145
|
tex: [`${root}/*/paper.tex`],
|
|
148
146
|
/** The per-paper stage ledger. */
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
* (`284:will be released`, `284:Upon acceptance` + `285:will be made publicly available`).
|
|
36
36
|
*
|
|
37
37
|
* 🔴 AND ONE DISAGREEMENT, WHICH IS NOT IN THE LOGIC BUT IN THE INPUT — and it is the whole
|
|
38
|
-
* gain. The old implementation picked its file by
|
|
39
|
-
*
|
|
38
|
+
* gain. The old implementation picked its file by taking the FIRST `.tex` in `readdirSync`
|
|
39
|
+
* order. Measured 2026-09-07:
|
|
40
40
|
* paper A → a 536-BYTE file of nine `\def`s with venue numbers. The check, added
|
|
41
41
|
* specifically BECAUSE of paper A, was not reading paper A;
|
|
42
42
|
* paper B → a four-month-old draft, one of seven `.tex` files in that directory;
|
|
@@ -3,27 +3,11 @@
|
|
|
3
3
|
* how the prose is written.
|
|
4
4
|
*
|
|
5
5
|
* ── WHY ITS OWN NAMESPACE, AND NOT `paper/*` ────────────────────────────────
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* `paper/future-promise: "off"` on markdown would be an exemption justified by "there is no
|
|
12
|
-
* subject", and that same parity assertion requires the rule to be ALIVE on the other side —
|
|
13
|
-
* i.e. the machinery exists but is built for the opposite direction (off on `.tex`, alive on
|
|
14
|
-
* `.md`).
|
|
15
|
-
*
|
|
16
|
-
* Hence a namespace of its own, the same way an external word-counter gets one: it has a word
|
|
17
|
-
* count markdown does not have, we have a preamble markdown does not have. Where the config
|
|
18
|
-
* enumerates non-`paper/*` rules on `.tex`, the list is CLOSED and `tex/future-promise` is
|
|
19
|
-
* named explicitly, so that a new namespace arrives by decision rather than as a side effect
|
|
20
|
-
* of editing a config.
|
|
21
|
-
*
|
|
22
|
-
* ⚠️ THE MEASUREMENT THIS DECISION RESTS ON (source corpus, 2026-09-06 → 2026-09-07): had the
|
|
23
|
-
* rule been declared on `paper.md` it would produce ZERO there TODAY — not one match of
|
|
24
|
-
* `FUTURE_PROMISE_RE` in the markdown draft of the one paper that has one. So the choice is
|
|
25
|
-
* about semantics, not noise: markdown has no input distinguishing a draft from a shipped
|
|
26
|
-
* build, and the rule would fire on a draft's honest promise.
|
|
6
|
+
* The subject here is the BUILD MODE (`\documentclass[review]`, `printacmref=false`), not the
|
|
7
|
+
* prose the `paper/*` rules read. Hence a namespace of its own, the same way an external
|
|
8
|
+
* word-counter gets one. Where the config enumerates non-`paper/*` rules on `.tex`, the list is
|
|
9
|
+
* CLOSED and `tex/future-promise` is named explicitly, so that a new namespace arrives by
|
|
10
|
+
* decision rather than as a side effect of editing a config.
|
|
27
11
|
*
|
|
28
12
|
* ── THE RULE READS THE FILE FROM DISK, NOT THE PROJECTION. This is load-bearing ──
|
|
29
13
|
* The LaTeX language in `latex-language.mjs` hands rules a PROJECTION of `.tex` into a
|
|
@@ -37,9 +21,8 @@
|
|
|
37
21
|
* run (and in CI) there is no such difference.
|
|
38
22
|
*
|
|
39
23
|
* ── WHAT THE MOVE TO A LINT RULE FIXED BESIDES THE FORM (measured 2026-09-07) ──
|
|
40
|
-
* 🔴 The previous implementation, a script, picked its input like this:
|
|
41
|
-
* `
|
|
42
|
-
* FIRST `.tex` in directory order. Measured across the three papers of the source corpus:
|
|
24
|
+
* 🔴 The previous implementation, a script, picked its input like this:
|
|
25
|
+
* `readdirSync(dir).find((f) => f.endsWith(".tex"))` — the FIRST `.tex` in directory order. Measured across the three papers of the source corpus:
|
|
43
26
|
* paper A → a 536-BYTE file holding nine `\def`s with venue numbers. The check, added
|
|
44
27
|
* specifically BECAUSE of paper A, was not reading paper A;
|
|
45
28
|
* paper B → a four-month-old draft, one of SEVEN `.tex` files in that directory (the real
|
|
@@ -121,7 +104,7 @@ const FRONTMATTER_OVERRIDES = [
|
|
|
121
104
|
// is looking.
|
|
122
105
|
//
|
|
123
106
|
// The scope is DELIBERATELY NARROW, because a noisy gate is a silenced gate:
|
|
124
|
-
// - `.tex` only
|
|
107
|
+
// - `.tex` only: the build mode is read from the preamble;
|
|
125
108
|
// - a review-mode build is EXEMPT, and that is the whole point: during peer review
|
|
126
109
|
// "at camera-ready" is a true and ordinary promise. The defect is only a promise that
|
|
127
110
|
// outlived its own delivery;
|
package/eslint.config.mjs
CHANGED
|
@@ -520,7 +520,6 @@ const TYPESCRIPT_ONLY = [
|
|
|
520
520
|
"test/e2e/**/*.{js,mjs,cjs}",
|
|
521
521
|
"test/fixtures/layers/src/**/*.{js,mjs,cjs}",
|
|
522
522
|
"fixtures/accepted-papers/**/*.{js,mjs,cjs}",
|
|
523
|
-
"fixtures/real-markdown-paper/**/*.{js,mjs,cjs}",
|
|
524
523
|
"skills/paper-pipeline/scripts/fixtures/**/*.{js,mjs,cjs}",
|
|
525
524
|
"skills/plan-paper-timeline/fixtures/**/*.{js,mjs,cjs}",
|
|
526
525
|
];
|
|
@@ -799,6 +798,28 @@ export default [
|
|
|
799
798
|
"no-restricted-imports": ["error", { paths: ONE_COLLECTION_LIBRARY }],
|
|
800
799
|
},
|
|
801
800
|
},
|
|
801
|
+
// 🔴 ONE PLACE READS BIBLIOGRAPHIES. `@retorquere/bibtex-parser` is known to its adapter only;
|
|
802
|
+
// every other file — the app, the domain, another adapter, a skill's script — reads a `.bib` through
|
|
803
|
+
// the `BibReader` port, and a path a person gave through `bibliographyAt` (src/paper-sources.ts).
|
|
804
|
+
// Three readers of `.bib` text grew here before there was one. Its own rule id, so no other
|
|
805
|
+
// `no-restricted-imports` block can replace it (test/eslint-bib-parser.test.ts).
|
|
806
|
+
{
|
|
807
|
+
files: ["**/*.{ts,mts,mjs,js,cjs}"],
|
|
808
|
+
ignores: ["src/adapters/bibtex/**"],
|
|
809
|
+
plugins: { n },
|
|
810
|
+
rules: {
|
|
811
|
+
"n/no-restricted-import": [
|
|
812
|
+
"error",
|
|
813
|
+
[
|
|
814
|
+
{
|
|
815
|
+
name: ["@retorquere/bibtex-parser", "@retorquere/bibtex-parser/**"],
|
|
816
|
+
message:
|
|
817
|
+
"The bibtex parser is src/adapters/bibtex/'s: read a .bib through the BibReader port (src/ports/bib-reader.ts), or a path through bibliographyAt (src/paper-sources.ts).",
|
|
818
|
+
},
|
|
819
|
+
],
|
|
820
|
+
],
|
|
821
|
+
},
|
|
822
|
+
},
|
|
802
823
|
// 🔴 THE LINTER DOES NOT READ A SKILL. Skills are consumers of the package, not its storage: the
|
|
803
824
|
// venue presets once lived in a skill and were located through a skill's script, so renaming the
|
|
804
825
|
// skill broke `paperlint lint` (#133). The package's own code — src/, eslint-rules/, lib/ — may
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
% rule is judged against what a real build of the class measures. It conforms: the LLM Usage
|
|
4
4
|
% Statement closes it, and not one rule has anything to say.
|
|
5
5
|
\documentclass[conference,compsoc]{IEEEtran}
|
|
6
|
-
\begin{filecontents*}{refs.bib}
|
|
6
|
+
\begin{filecontents*}[overwrite]{refs.bib}
|
|
7
7
|
@article{knuth84,
|
|
8
8
|
title = {Literate Programming},
|
|
9
9
|
author = {Knuth, Donald E.},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
% B: the name in an acknowledgments paragraph.
|
|
2
2
|
% The declared identity is invented: "Ada Example", handle "adaexample" (paperlint.json).
|
|
3
3
|
\documentclass[conference,compsoc]{IEEEtran}
|
|
4
|
-
\begin{filecontents*}{refs.bib}
|
|
4
|
+
\begin{filecontents*}[overwrite]{refs.bib}
|
|
5
5
|
@article{knuth84,
|
|
6
6
|
title = {Literate Programming},
|
|
7
7
|
author = {Knuth, Donald E.},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
% A: the name in the author block.
|
|
2
2
|
% The declared identity is invented: "Ada Example", handle "adaexample" (paperlint.json).
|
|
3
3
|
\documentclass[conference,compsoc]{IEEEtran}
|
|
4
|
-
\begin{filecontents*}{refs.bib}
|
|
4
|
+
\begin{filecontents*}[overwrite]{refs.bib}
|
|
5
5
|
@article{knuth84,
|
|
6
6
|
title = {Literate Programming},
|
|
7
7
|
author = {Knuth, Donald E.},
|