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
|
@@ -36,7 +36,6 @@ const ROOT = dirname(HERE);
|
|
|
36
36
|
const {
|
|
37
37
|
newPaper,
|
|
38
38
|
nameProblem,
|
|
39
|
-
wantedFiles,
|
|
40
39
|
PACKAGE_TEMPLATES,
|
|
41
40
|
OVERRIDE_DIR,
|
|
42
41
|
STATUS_FILE,
|
|
@@ -63,7 +62,7 @@ async function lint(dir) {
|
|
|
63
62
|
|
|
64
63
|
try {
|
|
65
64
|
// ── the templates are FILES in the package, one per required file ──────────────────────
|
|
66
|
-
for (const f of [STATUS_FILE, SOURCE_FILE
|
|
65
|
+
for (const f of [STATUS_FILE, SOURCE_FILE])
|
|
67
66
|
check(
|
|
68
67
|
`the package ships templates/paper/${f}`,
|
|
69
68
|
existsSync(join(PACKAGE_TEMPLATES, f)),
|
|
@@ -85,13 +84,13 @@ try {
|
|
|
85
84
|
);
|
|
86
85
|
check(
|
|
87
86
|
"a refused name creates nothing",
|
|
88
|
-
newPaper(work, "Bad",
|
|
87
|
+
newPaper(work, "Bad", { venues: [] }).ok === false &&
|
|
89
88
|
!existsSync(join(work, "Bad")),
|
|
90
89
|
);
|
|
91
90
|
|
|
92
91
|
// ── a fresh LaTeX paper ─────────────────────────────────────────────────────────────
|
|
93
92
|
const papers = join(work, "papers");
|
|
94
|
-
const r = newPaper(papers, "demo",
|
|
93
|
+
const r = newPaper(papers, "demo", { venues: [] });
|
|
95
94
|
check(
|
|
96
95
|
"a fresh folder gets the scorecard, a .tex source and paperlint.json, all from the package",
|
|
97
96
|
r.ok &&
|
|
@@ -113,10 +112,11 @@ try {
|
|
|
113
112
|
);
|
|
114
113
|
const l = await lint(join(papers, "demo"));
|
|
115
114
|
check(
|
|
116
|
-
"🔴 `paperlint lint` passes the scaffold — exit 0, not a missing-file error — with
|
|
115
|
+
"🔴 `paperlint lint` passes the scaffold — exit 0, not a missing-file error — with two warnings: no venue is chosen yet, and the paper is not built",
|
|
117
116
|
l.code === 0 &&
|
|
118
117
|
/names no venue preset yet/.test(l.text) &&
|
|
119
|
-
/
|
|
118
|
+
/paper\/sources-fresh/.test(l.text) &&
|
|
119
|
+
/2 problems \(0 errors, 2 warnings\)/.test(l.text),
|
|
120
120
|
);
|
|
121
121
|
|
|
122
122
|
// What the status hook reads on every paper edit: the verdict line.
|
|
@@ -129,36 +129,18 @@ try {
|
|
|
129
129
|
|
|
130
130
|
// ── never overwrite; on an existing folder only the missing files ─────────────────────
|
|
131
131
|
writeFileSync(join(papers, "demo", STATUS_FILE), "mine\n");
|
|
132
|
-
const again = newPaper(papers, "demo",
|
|
132
|
+
const again = newPaper(papers, "demo", { venues: [] });
|
|
133
133
|
check(
|
|
134
|
-
"🔴 a second run overwrites nothing
|
|
134
|
+
"🔴 a second run overwrites nothing",
|
|
135
135
|
again.ok &&
|
|
136
136
|
!again.fresh &&
|
|
137
137
|
read(papers, "demo", STATUS_FILE) === "mine\n" &&
|
|
138
|
-
!existsSync(join(papers, "demo", "paper.md")) &&
|
|
139
138
|
again.files.every((f) => f.status === "kept"),
|
|
140
139
|
);
|
|
141
|
-
mkdirSync(join(papers, "old"), { recursive: true });
|
|
142
|
-
writeFileSync(join(papers, "old", "paper.md"), "# Old\n");
|
|
143
|
-
const migrated = newPaper(papers, "old", "tex", { venues: [] });
|
|
144
|
-
check(
|
|
145
|
-
"an old folder with only paper.md gets ONLY the scorecard — that is the migration",
|
|
146
|
-
migrated.ok &&
|
|
147
|
-
existsSync(join(papers, "old", STATUS_FILE)) &&
|
|
148
|
-
!existsSync(join(papers, "old", "paper.tex")) &&
|
|
149
|
-
read(papers, "old", "paper.md") === "# Old\n",
|
|
150
|
-
);
|
|
151
|
-
check(
|
|
152
|
-
"wantedFiles asks for a source only when neither format is there",
|
|
153
|
-
wantedFiles(join(papers, "old"), "tex").join() ===
|
|
154
|
-
`${STATUS_FILE},paperlint.json` &&
|
|
155
|
-
wantedFiles(join(papers, "nowhere"), "md").join() ===
|
|
156
|
-
`${STATUS_FILE},paper.md,paperlint.json`,
|
|
157
|
-
);
|
|
158
140
|
writeFileSync(join(papers, "afile"), "x");
|
|
159
141
|
check(
|
|
160
142
|
"a name taken by a FILE is refused, not written through",
|
|
161
|
-
newPaper(papers, "afile",
|
|
143
|
+
newPaper(papers, "afile", { venues: [] }).ok === false,
|
|
162
144
|
);
|
|
163
145
|
|
|
164
146
|
// ── the project's template wins, file by file ─────────────────────────────────────────
|
|
@@ -167,7 +149,7 @@ try {
|
|
|
167
149
|
join(papers, OVERRIDE_DIR, STATUS_FILE),
|
|
168
150
|
"---\n---\n# House scorecard for {{name}}\n",
|
|
169
151
|
);
|
|
170
|
-
const own = newPaper(papers, "house",
|
|
152
|
+
const own = newPaper(papers, "house", { venues: [] });
|
|
171
153
|
// Guards: the override slot — `fromTemplate` in new-paper.ts reads `<papers>/.template/<file>`
|
|
172
154
|
// before the package's copy. Replace that lookup with the package's alone and this goes red.
|
|
173
155
|
check(
|
|
@@ -176,11 +158,11 @@ try {
|
|
|
176
158
|
read(papers, "house", STATUS_FILE) ===
|
|
177
159
|
"---\n---\n# House scorecard for house\n" &&
|
|
178
160
|
own.files.find((f) => f.file === STATUS_FILE)?.from === "project" &&
|
|
179
|
-
own.files.find((f) => f.file ===
|
|
161
|
+
own.files.find((f) => f.file === SOURCE_FILE)?.from === "package",
|
|
180
162
|
);
|
|
181
163
|
|
|
182
164
|
// ── a missing template is a broken install, not an empty file ─────────────────────────
|
|
183
|
-
const broken = newPaper(join(work, "b"), "x",
|
|
165
|
+
const broken = newPaper(join(work, "b"), "x", {
|
|
184
166
|
venues: [],
|
|
185
167
|
packageTemplates: join(work, "no-such-dir"),
|
|
186
168
|
});
|
package/src/new-paper.test.ts
CHANGED
|
@@ -41,7 +41,7 @@ const settingsOf = (dir: string) =>
|
|
|
41
41
|
describe("paperlint new — paperlint.json", () => {
|
|
42
42
|
it("is always written, from the package template: no venue chosen yet, and valid", () => {
|
|
43
43
|
const papers = join(tmp(), "papers");
|
|
44
|
-
const r = newPaper(papers, "demo",
|
|
44
|
+
const r = newPaper(papers, "demo", { venues: [] });
|
|
45
45
|
expect(r.ok && r.files.find((f) => f.file === "paperlint.json")).toEqual({
|
|
46
46
|
file: "paperlint.json",
|
|
47
47
|
status: "created",
|
|
@@ -72,7 +72,7 @@ describe("paperlint new — paperlint.json", () => {
|
|
|
72
72
|
join(papers, OVERRIDE_DIR, "paperlint.json"),
|
|
73
73
|
'{ "extends": "paperlint:aisec", "kind": "research" }\n',
|
|
74
74
|
);
|
|
75
|
-
const r = newPaper(papers, "house",
|
|
75
|
+
const r = newPaper(papers, "house", { venues: [] });
|
|
76
76
|
expect(r.ok && r.files.find((f) => f.file === "paperlint.json")?.from).toBe(
|
|
77
77
|
"project",
|
|
78
78
|
);
|
|
@@ -89,7 +89,7 @@ describe("paperlint new — paperlint.json", () => {
|
|
|
89
89
|
join(papers, "p", "paperlint.json"),
|
|
90
90
|
'{"extends":"paperlint:aisec"}',
|
|
91
91
|
);
|
|
92
|
-
const r = newPaper(papers, "p",
|
|
92
|
+
const r = newPaper(papers, "p", { venues: [] });
|
|
93
93
|
expect(
|
|
94
94
|
r.ok && r.files.find((f) => f.file === "paperlint.json")?.status,
|
|
95
95
|
).toBe("kept");
|
|
@@ -106,7 +106,7 @@ describe("paperlint lint — a paper with no venue preset chosen", () => {
|
|
|
106
106
|
join(root, "package.json"),
|
|
107
107
|
JSON.stringify({ name: "c", private: true }),
|
|
108
108
|
);
|
|
109
|
-
newPaper(join(root, "papers"), "demo",
|
|
109
|
+
newPaper(join(root, "papers"), "demo", { venues: [] });
|
|
110
110
|
if (extendsValue !== null)
|
|
111
111
|
writeFileSync(
|
|
112
112
|
join(root, "papers", "demo", "paperlint.json"),
|
|
@@ -122,10 +122,13 @@ describe("paperlint lint — a paper with no venue preset chosen", () => {
|
|
|
122
122
|
return { code, messages };
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
it("gets
|
|
125
|
+
it("gets the warning naming the file to set, besides the one that it is not built, and exits 0", async () => {
|
|
126
126
|
const { code, messages } = await lintNew(null);
|
|
127
127
|
expect(code).toBe(0);
|
|
128
|
-
expect(messages).
|
|
128
|
+
expect(messages.map((m) => m.ruleId)).toEqual([
|
|
129
|
+
"pdf/measured",
|
|
130
|
+
"paper/sources-fresh",
|
|
131
|
+
]);
|
|
129
132
|
expect(messages[0]?.ruleId).toBe("pdf/measured");
|
|
130
133
|
expect(messages[0]?.severity).toBe(1);
|
|
131
134
|
expect(messages[0]?.message).toMatch(/names no venue preset yet/);
|
|
@@ -254,10 +257,11 @@ describe("paperlint new --venue msr", () => {
|
|
|
254
257
|
log: (s: string) => out.push(s),
|
|
255
258
|
err: () => {},
|
|
256
259
|
});
|
|
257
|
-
// Guards: what remains is the reminder that the
|
|
258
|
-
// about the paper.
|
|
259
|
-
expect(out.join("\n")).toMatch(/\(0 errors,
|
|
260
|
+
// Guards: what remains is the reminder that the paper is not built yet — two warnings, not a
|
|
261
|
+
// finding about the paper: no PDF measured, no record of what TeX read.
|
|
262
|
+
expect(out.join("\n")).toMatch(/\(0 errors, 2 warnings\)/);
|
|
260
263
|
expect(out.join("\n")).toMatch(/pdf\/measured/);
|
|
264
|
+
expect(out.join("\n")).toMatch(/paper\/sources-fresh/);
|
|
261
265
|
expect(code).toBe(0);
|
|
262
266
|
});
|
|
263
267
|
});
|
|
@@ -488,7 +492,7 @@ describe("a project's own template", () => {
|
|
|
488
492
|
mkdirSync(join(papers, OVERRIDE_DIR), { recursive: true });
|
|
489
493
|
const src = join(papers, OVERRIDE_DIR, "paperlint.json");
|
|
490
494
|
writeFileSync(src, '{\n // our house venue\n "extends": null\n}\n');
|
|
491
|
-
const r = newPaper(papers, "p",
|
|
495
|
+
const r = newPaper(papers, "p", {
|
|
492
496
|
venues: [],
|
|
493
497
|
venue: { extends: "paperlint:aisec", kind: null },
|
|
494
498
|
});
|
|
@@ -504,7 +508,7 @@ describe("a project's own template", () => {
|
|
|
504
508
|
mkdirSync(join(papers, OVERRIDE_DIR), { recursive: true });
|
|
505
509
|
writeFileSync(join(papers, OVERRIDE_DIR, "paperlint.json"), "{}\n");
|
|
506
510
|
const lines = reportNewPaper(
|
|
507
|
-
newPaper(papers, "p",
|
|
511
|
+
newPaper(papers, "p", { venues: [] }),
|
|
508
512
|
(p) => p,
|
|
509
513
|
);
|
|
510
514
|
expect(lines).toContain(
|
package/src/new-paper.ts
CHANGED
|
@@ -8,8 +8,7 @@
|
|
|
8
8
|
* Every rule below is taken from a tool that already solved it (docs/prior-art/paper-folder-scaffolding.md):
|
|
9
9
|
* - the template is a FILE with a lookup order — the project's `<papers>/.template/<file>` first,
|
|
10
10
|
* the package's `templates/paper/<file>` second (Hugo's archetypes);
|
|
11
|
-
* - it NEVER overwrites: on an existing folder it adds only the missing required files
|
|
12
|
-
* also the migration for an old folder that has only `paper.md` (`cargo init`);
|
|
11
|
+
* - it NEVER overwrites: on an existing folder it adds only the missing required files (`cargo init`);
|
|
13
12
|
* - the name is validated, because it becomes a path and, through the hooks, a shell argument.
|
|
14
13
|
*
|
|
15
14
|
* The only substitution is `{{name}}` → the folder name. Nothing else in a template is touched.
|
|
@@ -33,10 +32,6 @@ import {
|
|
|
33
32
|
type NamedVenue,
|
|
34
33
|
} from "./domain/venue-name.ts";
|
|
35
34
|
|
|
36
|
-
export type PaperFormat = "tex" | "md";
|
|
37
|
-
export const FORMATS: readonly PaperFormat[] = ["tex", "md"];
|
|
38
|
-
export const DEFAULT_FORMAT: PaperFormat = "tex";
|
|
39
|
-
|
|
40
35
|
/** The package's default templates. Read from disk, so they ship in the tarball (`files`). */
|
|
41
36
|
export const PACKAGE_TEMPLATES = fileURLToPath(
|
|
42
37
|
new URL("../templates/paper/", import.meta.url),
|
|
@@ -50,17 +45,12 @@ export const PACKAGE_TEMPLATES = fileURLToPath(
|
|
|
50
45
|
export const OVERRIDE_DIR = ".template";
|
|
51
46
|
|
|
52
47
|
export const STATUS_FILE = "PIPELINE-STATUS.md";
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
md: "paper.md",
|
|
56
|
-
};
|
|
48
|
+
/** A paper's source: LaTeX, one file at the paper's root. */
|
|
49
|
+
export const SOURCE_FILE = "paper.tex";
|
|
57
50
|
|
|
58
51
|
/** The flag that lets `paperlint new` create a folder whose name names a venue. */
|
|
59
52
|
export const ALLOW_VENUE_NAME = "--allow-venue-name";
|
|
60
53
|
|
|
61
|
-
export const isFormat = (v: unknown): v is PaperFormat =>
|
|
62
|
-
FORMATS.some((f) => f === v);
|
|
63
|
-
|
|
64
54
|
/**
|
|
65
55
|
* Why a name is refused, or null. `[a-z0-9._-]+` is the charset; a LEADING dot is refused on top
|
|
66
56
|
* of it, because discovery skips dot-directories — a paper named `.x` would be created and then
|
|
@@ -109,20 +99,12 @@ export type NewPaperResult =
|
|
|
109
99
|
}
|
|
110
100
|
| { readonly ok: false; readonly reason: string };
|
|
111
101
|
|
|
112
|
-
/**
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function scorecardAndSource(dir: string, format: PaperFormat): string[] {
|
|
121
|
-
const hasSource = Object.values(SOURCE_FILE).some((f) =>
|
|
122
|
-
existsSync(join(dir, f)),
|
|
123
|
-
);
|
|
124
|
-
return hasSource ? [STATUS_FILE] : [STATUS_FILE, SOURCE_FILE[format]];
|
|
125
|
-
}
|
|
102
|
+
/** The files a paper folder must end up with: the scorecard, the source and `paperlint.json`. */
|
|
103
|
+
export const WANTED_FILES: readonly string[] = [
|
|
104
|
+
STATUS_FILE,
|
|
105
|
+
SOURCE_FILE,
|
|
106
|
+
CONFIG_FILE,
|
|
107
|
+
];
|
|
126
108
|
|
|
127
109
|
/** The venue `paperlint new --venue` chose: what goes into the new paper's `paperlint.json`. */
|
|
128
110
|
export interface VenueSetting {
|
|
@@ -195,7 +177,7 @@ function fromTemplate(
|
|
|
195
177
|
return { ok: false, reason: `no template for ${file}: ${src} is missing` };
|
|
196
178
|
const text = readFileSync(src, "utf8").split("{{name}}").join(name);
|
|
197
179
|
const setClass = venue?.setClass ?? null;
|
|
198
|
-
if (file === SOURCE_FILE
|
|
180
|
+
if (file === SOURCE_FILE && setClass !== null)
|
|
199
181
|
return { ok: true, value: { file, text: setClass(text), from } };
|
|
200
182
|
if (file !== CONFIG_FILE || venue === null)
|
|
201
183
|
return { ok: true, value: { file, text, from } };
|
|
@@ -217,7 +199,6 @@ export interface NewPaperOptions {
|
|
|
217
199
|
export function newPaper(
|
|
218
200
|
papersRoot: string,
|
|
219
201
|
name: string,
|
|
220
|
-
format: PaperFormat,
|
|
221
202
|
opts: NewPaperOptions,
|
|
222
203
|
): NewPaperResult {
|
|
223
204
|
const { packageTemplates = PACKAGE_TEMPLATES, venue = null } = opts;
|
|
@@ -230,7 +211,7 @@ export function newPaper(
|
|
|
230
211
|
|
|
231
212
|
const files: FileOutcome[] = [];
|
|
232
213
|
const toWrite: Planned[] = [];
|
|
233
|
-
for (const file of
|
|
214
|
+
for (const file of WANTED_FILES) {
|
|
234
215
|
if (existsSync(join(dir, file))) {
|
|
235
216
|
files.push({ file, status: "kept" });
|
|
236
217
|
continue;
|
package/src/package-dirs.ts
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* presets/ the venue presets (`paperlint:<name>`) and their JSON Schema
|
|
7
7
|
* presets/tex/ the LaTeX inputs `paperlint build` puts on TEXINPUTS
|
|
8
8
|
*
|
|
9
|
-
* `texSearchPath` is where an `\input` is looked for, in order. The build hands it to TeX
|
|
10
|
-
*
|
|
9
|
+
* `texSearchPath` is where an `\input` is looked for, in order. The build hands it to TeX; lint does
|
|
10
|
+
* not search for an include at all — it reads the files TeX read, from the build's record
|
|
11
|
+
* (docs/design/paper-sources.md §1).
|
|
11
12
|
*/
|
|
12
13
|
import { fileURLToPath } from "node:url";
|
|
13
14
|
|
package/src/paper-files.test.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { tmpdir } from "node:os";
|
|
|
20
20
|
import { join } from "node:path";
|
|
21
21
|
import { afterEach, describe, expect, it } from "vitest";
|
|
22
22
|
import { buildConfig, eslintConfig, run } from "./cli.ts";
|
|
23
|
+
import { texLanguage } from "#eslint-rules/latex-language";
|
|
23
24
|
import {
|
|
24
25
|
narrowToOwners,
|
|
25
26
|
ownedPatterns,
|
|
@@ -247,7 +248,7 @@ describe("`rules` blocks — where the rule lands", () => {
|
|
|
247
248
|
},
|
|
248
249
|
],
|
|
249
250
|
},
|
|
250
|
-
|
|
251
|
+
texLanguage,
|
|
251
252
|
),
|
|
252
253
|
),
|
|
253
254
|
});
|
|
@@ -257,9 +258,9 @@ describe("`rules` blocks — where the rule lands", () => {
|
|
|
257
258
|
const status = await on("papers/a/PIPELINE-STATUS.md");
|
|
258
259
|
expect(status["paper/source"]).toEqual([1]);
|
|
259
260
|
expect(status["paper/section-word"]).toBeUndefined();
|
|
260
|
-
const
|
|
261
|
-
expect(
|
|
262
|
-
expect(
|
|
261
|
+
const source = await on("papers/a/paper.tex");
|
|
262
|
+
expect(source["paper/section-word"]).toEqual([0]);
|
|
263
|
+
expect(source["paper/source"]).toBeUndefined();
|
|
263
264
|
});
|
|
264
265
|
});
|
|
265
266
|
|
package/src/paper-files.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* THE FILES PAPERLINT OWNS — read off its own config, and made the only thing ESLint may reach.
|
|
3
3
|
*
|
|
4
|
-
* paperlint's blocks name the files they lint (`PIPELINE-STATUS.md`, `paper.
|
|
5
|
-
*
|
|
4
|
+
* paperlint's blocks name the files they lint (`PIPELINE-STATUS.md`, `paper.tex`, `reviews/*.md`,
|
|
5
|
+
* …). That list is the whole scope of `paperlint lint`, and this module turns it
|
|
6
6
|
* into two things ESLint understands:
|
|
7
7
|
*
|
|
8
8
|
* 1. **A global ignore of everything else** (`scopeToOwned`). Handing ESLint a directory is not
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* 2. **Each rule scoped to the files it was written for** (`ruleOwners` + `narrowToOwners`).
|
|
18
18
|
* One plugin NAME is bound to different plugin objects in different blocks: `paper` holds
|
|
19
19
|
* `stages`/`source`/`author-list` beside `PIPELINE-STATUS.md` and `typography` beside
|
|
20
|
-
* `paper.
|
|
20
|
+
* `paper.tex`. So a consumer block that named `paper/source`
|
|
21
21
|
* for every file under the papers directory reached `paper.tex`, where `paper` has no `source`
|
|
22
22
|
* — and ESLint threw `Could not find "source" in plugin "paper"` from inside `lintFiles`,
|
|
23
23
|
* after paperlint's own validation had (correctly) accepted the id. A block is therefore split
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
* - a global-ignore pattern that matches a DIRECTORY hides everything inside it, so directories
|
|
37
37
|
* must be un-ignored (`!` + a pattern ending in a slash) before owned files can be;
|
|
38
38
|
* - that un-ignores `node_modules/` and `.git/` too — ESLint's own defaults — so they are
|
|
39
|
-
* re-ignored after, or a `paper.
|
|
39
|
+
* re-ignored after, or a `paper.tex` inside a dependency would be linted;
|
|
40
40
|
* - later global-ignore patterns win, across blocks, in config order;
|
|
41
41
|
* - `files: [["a", "b"]]` matches a file only when BOTH patterns match.
|
|
42
42
|
*/
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `paperlint lint` over a paper whose body is in other files: the rules over ESLint's LaTeX text read
|
|
3
|
-
* every file
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* every body file the last build's record lists (`_build/sources.json`: the files TeX read after
|
|
4
|
+
* `\\begin{document}`), report at that file's own path and line, and fix that file. A paper with no
|
|
5
|
+
* record, or one it has outgrown, is `paper.tex` alone and `paper/sources-fresh` says the rest went
|
|
6
|
+
* unlinted. What a rule decides for the whole paper — the majority reference form and whether the
|
|
7
|
+
* build is in review mode — it decides over all of the paper's files, not over the one it is
|
|
8
|
+
* reporting in.
|
|
7
9
|
*/
|
|
8
10
|
import {
|
|
9
11
|
mkdirSync,
|
|
@@ -16,8 +18,13 @@ import {
|
|
|
16
18
|
import { tmpdir } from "node:os";
|
|
17
19
|
import { join, relative } from "node:path";
|
|
18
20
|
import { afterEach, describe, expect, it } from "vitest";
|
|
21
|
+
import { memoryFiles } from "./adapters/memory/index.ts";
|
|
22
|
+
import { absolutePath } from "./domain/paths.ts";
|
|
23
|
+
import { sourcesCodec } from "./adapters/sources-record/index.ts";
|
|
19
24
|
import { run } from "./cli.ts";
|
|
25
|
+
import { includeBlocks, paperBodies } from "./paper-includes.ts";
|
|
20
26
|
import { lintReport } from "../test/lint-report.ts";
|
|
27
|
+
import { builtFixture, recordedTree } from "../test/recorded-fixture.ts";
|
|
21
28
|
|
|
22
29
|
const dirs: string[] = [];
|
|
23
30
|
afterEach(() => {
|
|
@@ -29,7 +36,21 @@ const doc = (cls: string, preamble: string, body: string): string =>
|
|
|
29
36
|
|
|
30
37
|
const INTRO = "See §3 and p < .05.\nAs Fig.~\\ref{f3} shows.\n";
|
|
31
38
|
|
|
32
|
-
|
|
39
|
+
/** What TeX read of the paper `project` makes, in the order it read it. */
|
|
40
|
+
const READ = [
|
|
41
|
+
{ path: "paper.tex", role: "body" },
|
|
42
|
+
{ path: "macros.tex", role: "preamble" },
|
|
43
|
+
{ path: "sections/intro.tex", role: "body" },
|
|
44
|
+
] as const;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* A project with one paper, `papers/a`, as a build left it: its files, and a record listing `inputs`.
|
|
48
|
+
* `inputs: null` is a paper nothing has built.
|
|
49
|
+
*/
|
|
50
|
+
function project(
|
|
51
|
+
files: Record<string, string>,
|
|
52
|
+
inputs: readonly { path: string; role: "preamble" | "body" }[] | null = READ,
|
|
53
|
+
): string {
|
|
33
54
|
const root = realpathSync(mkdtempSync(join(tmpdir(), "paperlint-inc-")));
|
|
34
55
|
dirs.push(root);
|
|
35
56
|
const all: Record<string, string> = {
|
|
@@ -51,7 +72,8 @@ function project(files: Record<string, string>): string {
|
|
|
51
72
|
),
|
|
52
73
|
...files,
|
|
53
74
|
};
|
|
54
|
-
|
|
75
|
+
const written = inputs === null ? all : recordedTree(all, "papers/a", inputs);
|
|
76
|
+
for (const [p, text] of Object.entries(written)) {
|
|
55
77
|
mkdirSync(join(root, p, ".."), { recursive: true });
|
|
56
78
|
writeFileSync(join(root, p), text);
|
|
57
79
|
}
|
|
@@ -62,7 +84,11 @@ function project(files: Record<string, string>): string {
|
|
|
62
84
|
async function lint(
|
|
63
85
|
root: string,
|
|
64
86
|
args: string[] = [],
|
|
65
|
-
): Promise<{
|
|
87
|
+
): Promise<{
|
|
88
|
+
byFile: Record<string, string[]>;
|
|
89
|
+
said: Record<string, string[]>;
|
|
90
|
+
err: string;
|
|
91
|
+
}> {
|
|
66
92
|
const out: string[] = [];
|
|
67
93
|
const err: string[] = [];
|
|
68
94
|
await run(["lint", "--json", ...args], {
|
|
@@ -70,13 +96,20 @@ async function lint(
|
|
|
70
96
|
log: (s: string) => out.push(s),
|
|
71
97
|
err: (s: string) => err.push(s),
|
|
72
98
|
});
|
|
99
|
+
const report = lintReport(out.join("\n"));
|
|
73
100
|
const byFile = Object.fromEntries(
|
|
74
|
-
|
|
101
|
+
report.map((r) => [
|
|
75
102
|
relative(root, r.filePath),
|
|
76
103
|
r.messages.map((m) => m.ruleId ?? "(fatal)").sort(),
|
|
77
104
|
]),
|
|
78
105
|
);
|
|
79
|
-
|
|
106
|
+
const said = Object.fromEntries(
|
|
107
|
+
report.map((r) => [
|
|
108
|
+
relative(root, r.filePath),
|
|
109
|
+
r.messages.map((m) => m.message),
|
|
110
|
+
]),
|
|
111
|
+
);
|
|
112
|
+
return { byFile, said, err: err.join("\n") };
|
|
80
113
|
}
|
|
81
114
|
|
|
82
115
|
describe("lint reads the files paper.tex includes from its body", () => {
|
|
@@ -104,18 +137,24 @@ describe("lint reads the files paper.tex includes from its body", () => {
|
|
|
104
137
|
|
|
105
138
|
it("🔴 an included file that is not `.tex` — a `.bbl` — is not handed to ESLint, which parsed it as JavaScript (#153)", async () => {
|
|
106
139
|
const { byFile, err } = await lint(
|
|
107
|
-
project(
|
|
108
|
-
|
|
109
|
-
"
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
"
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
140
|
+
project(
|
|
141
|
+
{
|
|
142
|
+
"papers/a/paper.tex": doc(
|
|
143
|
+
"{article}",
|
|
144
|
+
"",
|
|
145
|
+
"Text, as Figure~\\ref{f1} shows.\n\\input{paper.bbl}",
|
|
146
|
+
),
|
|
147
|
+
// What bibtex writes: a comment first, which ESLint's JavaScript parser stops at.
|
|
148
|
+
"papers/a/paper.bbl":
|
|
149
|
+
"% Generated by IEEEtran.bst\n\\begin{thebibliography}{1}\n" +
|
|
150
|
+
"\\bibitem{a} A. Author, ``A result with p < .05,'' 2024.\n" +
|
|
151
|
+
"\\end{thebibliography}\n",
|
|
152
|
+
},
|
|
153
|
+
[
|
|
154
|
+
{ path: "paper.tex", role: "body" },
|
|
155
|
+
{ path: "paper.bbl", role: "body" },
|
|
156
|
+
],
|
|
157
|
+
),
|
|
119
158
|
);
|
|
120
159
|
// Guards: the `.bbl` was the one file of the run with a parse error («Unexpected token %»).
|
|
121
160
|
// It is bibtex's output, not prose the author edits: no fragment rule reports in it, and
|
|
@@ -124,7 +163,6 @@ describe("lint reads the files paper.tex includes from its body", () => {
|
|
|
124
163
|
"papers/a/PIPELINE-STATUS.md": [],
|
|
125
164
|
"papers/a/paper.tex": [],
|
|
126
165
|
});
|
|
127
|
-
// Read, not missing: no «lint did not read» note names it.
|
|
128
166
|
expect(err).not.toMatch(/paper\.bbl/);
|
|
129
167
|
});
|
|
130
168
|
|
|
@@ -162,26 +200,107 @@ describe("what lint decides for the whole paper, it decides over every file of i
|
|
|
162
200
|
);
|
|
163
201
|
expect(review.byFile["papers/a/sections/intro.tex"]).toEqual([]);
|
|
164
202
|
});
|
|
203
|
+
});
|
|
165
204
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
205
|
+
describe("a built paper whose files are clean", () => {
|
|
206
|
+
it("is reported as the files that were checked — the paper's, and the section TeX read", async () => {
|
|
207
|
+
const root = project({
|
|
208
|
+
"papers/a/sections/intro.tex": "Plain words, nothing to flag.\n",
|
|
209
|
+
});
|
|
210
|
+
const out: string[] = [];
|
|
211
|
+
const code = await run(["lint"], {
|
|
212
|
+
cwd: root,
|
|
213
|
+
log: (s: string) => out.push(s),
|
|
214
|
+
err: () => undefined,
|
|
215
|
+
});
|
|
216
|
+
expect({ code, out }).toEqual({
|
|
217
|
+
code: 0,
|
|
218
|
+
out: ["✓ 3 file(s) checked, no findings"],
|
|
219
|
+
});
|
|
175
220
|
});
|
|
221
|
+
});
|
|
176
222
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
223
|
+
describe("a paper whose record is missing, stale or silent about a file", () => {
|
|
224
|
+
it("🔴 a paper no build has recorded is paper.tex alone: its includes are not linted, and paper/sources-fresh says so", async () => {
|
|
225
|
+
const { byFile } = await lint(project({}, null));
|
|
226
|
+
expect(byFile).toEqual({
|
|
227
|
+
"papers/a/PIPELINE-STATUS.md": [],
|
|
228
|
+
"papers/a/paper.tex": ["paper/sources-fresh"],
|
|
181
229
|
});
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("🔴 a paper changed since its build is paper.tex alone, and paper/sources-fresh names the file", async () => {
|
|
233
|
+
const root = project({});
|
|
234
|
+
writeFileSync(
|
|
235
|
+
join(root, "papers/a/sections/intro.tex"),
|
|
236
|
+
"Edited after the build, p < .05.\n",
|
|
237
|
+
);
|
|
238
|
+
const { byFile, said } = await lint(root);
|
|
239
|
+
expect(Object.keys(byFile)).not.toContain("papers/a/sections/intro.tex");
|
|
240
|
+
expect(said["papers/a/paper.tex"]).toEqual([
|
|
241
|
+
expect.stringMatching(/sections\/intro\.tex edited/),
|
|
242
|
+
]);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
describe("paperBodies — the body files of the record TeX's own build left", () => {
|
|
247
|
+
const bodies = (files: Record<string, string>, dir = "/papers/p") =>
|
|
248
|
+
paperBodies([dir], { files: memoryFiles(files), codec: sourcesCodec });
|
|
249
|
+
|
|
250
|
+
it("p1: the one file TeX read after \\begin{document} — not the `.bib`, not the files its text names behind a comment or \\iffalse", () => {
|
|
251
|
+
expect(bodies(builtFixture("p1", "/papers/p"))).toEqual([
|
|
252
|
+
{
|
|
253
|
+
dir: "/papers/p",
|
|
254
|
+
main: "/papers/p/paper.tex",
|
|
255
|
+
files: ["/papers/p/sections/intro.tex"],
|
|
256
|
+
},
|
|
257
|
+
]);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it("a body file that is a `.tex` listed after paper.tex is handed over; a preamble one is not (v16: a block written from a preamble include)", () => {
|
|
261
|
+
expect(
|
|
262
|
+
bodies(builtFixture("v16-included-block", "/papers/p"))[0]?.files,
|
|
263
|
+
).toEqual([]);
|
|
264
|
+
expect(
|
|
265
|
+
bodies(builtFixture("v17-include-order", "/papers/p"))[0]?.files,
|
|
266
|
+
).toEqual(["/papers/p/bibsetup.tex"]);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it("🔴 a body .tex TeX wrote from a filecontents block is not handed to ESLint: its text is the block's, and a fix there is overwritten or splits it from the block", () => {
|
|
270
|
+
const files = builtFixture("v29-generated-input", "/papers/p");
|
|
271
|
+
expect(files["/papers/p/body.tex"]).toBeDefined();
|
|
272
|
+
expect(bodies(files)[0]?.files).toEqual([]);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it("a directory without paper.tex is no paper", () => {
|
|
276
|
+
expect(bodies({}, "/papers/none")).toEqual([]);
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
describe("includeBlocks — what ESLint is told of each paper", () => {
|
|
281
|
+
it("un-ignores each body file by name, and hands the paper to every file of it", () => {
|
|
282
|
+
const main = absolutePath("/papers/p/paper.tex");
|
|
283
|
+
const intro = absolutePath("/papers/p/sections/intro.tex");
|
|
284
|
+
expect(includeBlocks([{ dir: "/papers/p", main, files: [intro] }])).toEqual(
|
|
285
|
+
[
|
|
286
|
+
{ basePath: "/papers/p", ignores: ["!sections/intro.tex"] },
|
|
287
|
+
{
|
|
288
|
+
basePath: "/papers/p",
|
|
289
|
+
files: ["paper.tex", "sections/intro.tex"],
|
|
290
|
+
settings: { paperlint: { paper: { main, files: [main, intro] } } },
|
|
291
|
+
},
|
|
292
|
+
],
|
|
185
293
|
);
|
|
186
294
|
});
|
|
295
|
+
|
|
296
|
+
it("a paper of one file un-ignores nothing", () => {
|
|
297
|
+
const main = absolutePath("/papers/p/paper.tex");
|
|
298
|
+
expect(includeBlocks([{ dir: "/papers/p", main, files: [] }])).toEqual([
|
|
299
|
+
{
|
|
300
|
+
basePath: "/papers/p",
|
|
301
|
+
files: ["paper.tex"],
|
|
302
|
+
settings: { paperlint: { paper: { main, files: [main] } } },
|
|
303
|
+
},
|
|
304
|
+
]);
|
|
305
|
+
});
|
|
187
306
|
});
|