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
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which files of the paper directory a build's TeX passes read, and which `.bib` files they wrote —
|
|
3
|
+
* decided from the `.fls` lines of each pass. The inputs are `.fls` files captured whole from real
|
|
4
|
+
* runs of the planted papers (test/fixtures/tex-output/README.md); the few shapes no planted paper
|
|
5
|
+
* has are written out, and say so.
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { describe, expect, it } from "vitest";
|
|
11
|
+
import { texOutput } from "../adapters/tex-output/index.ts";
|
|
12
|
+
import { filesOfRun } from "./tex-run.ts";
|
|
13
|
+
|
|
14
|
+
const CAPTURES = join(
|
|
15
|
+
dirname(dirname(dirname(fileURLToPath(import.meta.url)))),
|
|
16
|
+
"test",
|
|
17
|
+
"fixtures",
|
|
18
|
+
"tex-output",
|
|
19
|
+
);
|
|
20
|
+
/** A planted paper's passes, as `.fls` text → lines, in the order they ran. */
|
|
21
|
+
const passes = (paper: string) =>
|
|
22
|
+
["first-pass.fls", "paper.fls"].map((f) =>
|
|
23
|
+
texOutput.fls(readFileSync(join(CAPTURES, paper, f), "utf8")),
|
|
24
|
+
);
|
|
25
|
+
const BIBTEX_RAN = { jobname: "paper", generated: ["paper.bbl"] };
|
|
26
|
+
|
|
27
|
+
describe("the files a build's TeX read", () => {
|
|
28
|
+
it("a paper of one file: the main file, a body", () => {
|
|
29
|
+
expect(filesOfRun(passes("v3-declared"), BIBTEX_RAN)).toEqual({
|
|
30
|
+
inputs: [{ path: "paper.tex", role: "body" }],
|
|
31
|
+
written: [],
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("an include read after \\begin{document} is body, a database TeX read before it is preamble — in first-read order", () => {
|
|
36
|
+
expect(filesOfRun(passes("p1"), BIBTEX_RAN)).toEqual({
|
|
37
|
+
inputs: [
|
|
38
|
+
{ path: "paper.tex", role: "body" },
|
|
39
|
+
{ path: "refs.bib", role: "preamble" },
|
|
40
|
+
{ path: "sections/intro.tex", role: "body" },
|
|
41
|
+
],
|
|
42
|
+
written: [],
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("a file read before \\begin{document} and never after is preamble", () => {
|
|
47
|
+
expect(filesOfRun(passes("v16-included-block"), BIBTEX_RAN)).toEqual({
|
|
48
|
+
inputs: [
|
|
49
|
+
{ path: "paper.tex", role: "body" },
|
|
50
|
+
{ path: "bibblock.tex", role: "preamble" },
|
|
51
|
+
{ path: "refs.bib", role: "preamble" },
|
|
52
|
+
],
|
|
53
|
+
written: [],
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("the .bib files TeX wrote", () => {
|
|
59
|
+
it("a .bib TeX wrote (filecontents) is written, and is no input: its bytes are the block's", () => {
|
|
60
|
+
expect(filesOfRun(passes("v8-jobname"), BIBTEX_RAN)).toEqual({
|
|
61
|
+
inputs: [{ path: "paper.tex", role: "body" }],
|
|
62
|
+
written: ["paper.bib"],
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("🔴 a block writes its file on the first pass only (the later passes find it there and read it): the last pass alone would list it as an input and not as written", () => {
|
|
67
|
+
const [first, last] = passes("plain-block");
|
|
68
|
+
expect(last?.lines.filter((l) => l.path.endsWith(".bib"))).toEqual([
|
|
69
|
+
{ op: "INPUT", path: "./refs.bib" },
|
|
70
|
+
{ op: "INPUT", path: "refs.bib" },
|
|
71
|
+
]);
|
|
72
|
+
expect(filesOfRun(passes("plain-block"), BIBTEX_RAN)).toEqual({
|
|
73
|
+
inputs: [{ path: "paper.tex", role: "body" }],
|
|
74
|
+
written: ["refs.bib"],
|
|
75
|
+
});
|
|
76
|
+
expect(first?.lines.filter((l) => l.path.endsWith(".bib"))).toEqual([
|
|
77
|
+
{ op: "OUTPUT", path: "refs.bib" },
|
|
78
|
+
]);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("a .tex TeX wrote and then read — the paper's text, generated", () => {
|
|
83
|
+
it("🔴 a block that writes a .tex the body \\input's: the file is written AND an input, a body file — its text is in the PDF", () => {
|
|
84
|
+
// v29-generated-input: pass 1 writes body.tex and reads it after \begin{document}; the later
|
|
85
|
+
// passes find it there, read it in the preamble (the block's check) and again in the body.
|
|
86
|
+
const [first, last] = passes("v29-generated-input");
|
|
87
|
+
expect(first?.lines.filter((l) => l.path.endsWith("body.tex"))[0]).toEqual({
|
|
88
|
+
op: "OUTPUT",
|
|
89
|
+
path: "body.tex",
|
|
90
|
+
});
|
|
91
|
+
expect(
|
|
92
|
+
last?.lines.some((l) => l.op === "OUTPUT" && l.path === "body.tex"),
|
|
93
|
+
).toBe(false);
|
|
94
|
+
expect(filesOfRun(passes("v29-generated-input"), BIBTEX_RAN)).toEqual({
|
|
95
|
+
inputs: [
|
|
96
|
+
{ path: "paper.tex", role: "body" },
|
|
97
|
+
{ path: "body.tex", role: "body" },
|
|
98
|
+
],
|
|
99
|
+
written: ["body.tex"],
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("with [overwrite] the last pass writes it again before reading it: still an input", () => {
|
|
104
|
+
const fls = texOutput.fls(
|
|
105
|
+
[
|
|
106
|
+
"PWD /work/paper",
|
|
107
|
+
"INPUT paper.tex",
|
|
108
|
+
"INPUT ./body.tex",
|
|
109
|
+
"OUTPUT body.tex",
|
|
110
|
+
"OUTPUT paper.aux",
|
|
111
|
+
"INPUT ./body.tex",
|
|
112
|
+
].join("\n"),
|
|
113
|
+
);
|
|
114
|
+
expect(filesOfRun([fls], BIBTEX_RAN)).toEqual({
|
|
115
|
+
inputs: [
|
|
116
|
+
{ path: "paper.tex", role: "body" },
|
|
117
|
+
{ path: "body.tex", role: "body" },
|
|
118
|
+
],
|
|
119
|
+
written: ["body.tex"],
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("what is not the paper's: its TeX tree, a sibling directory, what TeX writes", () => {
|
|
125
|
+
it("the .bbl bibtex wrote is no input of the paper; with no bibtex run it is a file TeX read, and so is one", () => {
|
|
126
|
+
const last = passes("v3-declared");
|
|
127
|
+
expect(
|
|
128
|
+
filesOfRun(last, { jobname: "paper", generated: [] }).inputs,
|
|
129
|
+
).toEqual([
|
|
130
|
+
{ path: "paper.tex", role: "body" },
|
|
131
|
+
{ path: "paper.bbl", role: "body" },
|
|
132
|
+
]);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("files the paper's TeX tree supplies are not the paper's: only the paper directory's files are listed", () => {
|
|
136
|
+
const fls = texOutput.fls(
|
|
137
|
+
[
|
|
138
|
+
"PWD /work/paper",
|
|
139
|
+
"INPUT /usr/share/texlive/texmf-dist/tex/latex/base/article.cls",
|
|
140
|
+
"INPUT paper.tex",
|
|
141
|
+
"INPUT /work/paper/inside.tex",
|
|
142
|
+
"INPUT ../shared/outside.tex",
|
|
143
|
+
"INPUT /elsewhere/other.tex",
|
|
144
|
+
"OUTPUT paper.aux",
|
|
145
|
+
].join("\n"),
|
|
146
|
+
);
|
|
147
|
+
expect(
|
|
148
|
+
filesOfRun([fls], { jobname: "paper", generated: [] }).inputs,
|
|
149
|
+
).toEqual([
|
|
150
|
+
{ path: "paper.tex", role: "body" },
|
|
151
|
+
{ path: "inside.tex", role: "preamble" },
|
|
152
|
+
]);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe("an .fls with no directory, and a name that merely starts with dots", () => {
|
|
157
|
+
it("an .fls that names no directory lists the files named relative to it and no absolute path", () => {
|
|
158
|
+
const fls = texOutput.fls(
|
|
159
|
+
"INPUT /usr/share/texlive/article.cls\nINPUT paper.tex\nINPUT ..x.tex\n",
|
|
160
|
+
);
|
|
161
|
+
expect(
|
|
162
|
+
filesOfRun([fls], { jobname: "paper", generated: [] }).inputs,
|
|
163
|
+
).toEqual([
|
|
164
|
+
{ path: "paper.tex", role: "body" },
|
|
165
|
+
{ path: "..x.tex", role: "preamble" },
|
|
166
|
+
]);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe("roles on the edges: both places, no body, no passes", () => {
|
|
171
|
+
it("a file read in the preamble and again in the body is body: its first read alone would drop it from the prose", () => {
|
|
172
|
+
const fls = texOutput.fls(
|
|
173
|
+
[
|
|
174
|
+
"PWD /work/paper",
|
|
175
|
+
"INPUT paper.tex",
|
|
176
|
+
"INPUT ./macros.tex",
|
|
177
|
+
"OUTPUT paper.aux",
|
|
178
|
+
"INPUT ./macros.tex",
|
|
179
|
+
"INPUT ./intro.tex",
|
|
180
|
+
].join("\n"),
|
|
181
|
+
);
|
|
182
|
+
expect(
|
|
183
|
+
filesOfRun([fls], { jobname: "paper", generated: [] }).inputs,
|
|
184
|
+
).toEqual([
|
|
185
|
+
{ path: "paper.tex", role: "body" },
|
|
186
|
+
{ path: "macros.tex", role: "body" },
|
|
187
|
+
{ path: "intro.tex", role: "body" },
|
|
188
|
+
]);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("a run that stopped before opening the .aux has no body: everything but the main file is preamble", () => {
|
|
192
|
+
const fls = texOutput.fls(
|
|
193
|
+
"PWD /work/paper\nINPUT paper.tex\nINPUT ./a.tex\nINPUT ./b.tex\n",
|
|
194
|
+
);
|
|
195
|
+
expect(
|
|
196
|
+
filesOfRun([fls], { jobname: "paper", generated: [] }).inputs,
|
|
197
|
+
).toEqual([
|
|
198
|
+
{ path: "paper.tex", role: "body" },
|
|
199
|
+
{ path: "a.tex", role: "preamble" },
|
|
200
|
+
{ path: "b.tex", role: "preamble" },
|
|
201
|
+
]);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("a file TeX wrote on any pass is not an input, even one it also reads on a later pass (.toc, .out)", () => {
|
|
205
|
+
const first = texOutput.fls(
|
|
206
|
+
"PWD /w\nINPUT paper.tex\nOUTPUT paper.aux\nOUTPUT paper.toc\n",
|
|
207
|
+
);
|
|
208
|
+
const second = texOutput.fls(
|
|
209
|
+
"PWD /w\nINPUT paper.tex\nINPUT ./paper.toc\nOUTPUT paper.aux\n",
|
|
210
|
+
);
|
|
211
|
+
expect(
|
|
212
|
+
filesOfRun([first, second], { jobname: "paper", generated: [] }).inputs,
|
|
213
|
+
).toEqual([{ path: "paper.tex", role: "body" }]);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("no passes, or passes that opened nothing of the paper, list nothing", () => {
|
|
217
|
+
expect(filesOfRun([], { jobname: "paper", generated: [] })).toEqual({
|
|
218
|
+
inputs: [],
|
|
219
|
+
written: [],
|
|
220
|
+
});
|
|
221
|
+
expect(
|
|
222
|
+
filesOfRun([texOutput.fls("PWD /w\nINPUT /usr/x.cls\n")], {
|
|
223
|
+
jobname: "paper",
|
|
224
|
+
generated: [],
|
|
225
|
+
}),
|
|
226
|
+
).toEqual({ inputs: [], written: [] });
|
|
227
|
+
});
|
|
228
|
+
});
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHICH FILES A BUILD'S TeX PASSES READ — decided from the lines of each pass's `.fls` (pdflatex
|
|
3
|
+
* `-recorder`), not from any TeX source (docs/design/paper-sources.md §1).
|
|
4
|
+
*
|
|
5
|
+
* inputs the files of the paper directory the LAST pass read, in the order it first read them,
|
|
6
|
+
* each a `preamble` or `body` file: before or after TeX opened `<jobname>.aux` for writing,
|
|
7
|
+
* which `\begin{document}` does (measured). The main file, read first, is `body`.
|
|
8
|
+
* written the `.bib` and `.tex` files ANY pass wrote — a `filecontents` block's.
|
|
9
|
+
*
|
|
10
|
+
* 🔴 WHY THE LAST PASS FOR INPUTS AND EVERY PASS FOR WHAT WAS WRITTEN. A block without `[overwrite]`
|
|
11
|
+
* writes its file on the first pass only; the later passes find it there and read it (measured on TeX
|
|
12
|
+
* Live 2026: pass 1 `OUTPUT refs.bib`, passes 2 and 3 `INPUT refs.bib`, no OUTPUT). The last pass alone
|
|
13
|
+
* would list TeX's own copy of a `.bib` as a file of the paper. And the first pass reads no `.aux`,
|
|
14
|
+
* `.toc` or `.out` yet, so only a later pass lists what the paper reads and the passes after it write.
|
|
15
|
+
*
|
|
16
|
+
* 🔴 A FILE TeX WROTE IS AN INPUT ONLY WHEN IT IS TeX SOURCE OF THE PAPER: a `.tex`. A block that
|
|
17
|
+
* writes `body.tex` the body `\input`s puts that text in the PDF (measured: pass 1 `OUTPUT body.tex`
|
|
18
|
+
* then `INPUT ./body.tex` after `\begin{document}`; the later passes read it again), so it is an
|
|
19
|
+
* input like any other: hashed, spliced where its include stands. Every other file a pass wrote is
|
|
20
|
+
* TeX's bookkeeping or a database — its `.aux`, `.out` and `.toc`, a block's `.bib` — and is no input.
|
|
21
|
+
* A file another program wrote from TeX's output (`generated`: the `.bbl`, when bibtex ran) is none
|
|
22
|
+
* either.
|
|
23
|
+
*/
|
|
24
|
+
import { isAbsolute, posix } from "node:path";
|
|
25
|
+
import type { RecordedInput } from "./sources-record.ts";
|
|
26
|
+
|
|
27
|
+
/** One line of an `.fls`: TeX opened a file to read it, or to write it. */
|
|
28
|
+
export interface FlsLine {
|
|
29
|
+
readonly op: "INPUT" | "OUTPUT";
|
|
30
|
+
/** As TeX named the file: `./a.tex` and `a.tex` for one file, an absolute path for the TeX tree's. */
|
|
31
|
+
readonly path: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A pdflatex `-recorder` file: where it ran, and every file it opened, in order. */
|
|
35
|
+
export interface Fls {
|
|
36
|
+
/** The directory pdflatex ran in, or null when the file names none. */
|
|
37
|
+
readonly pwd: string | null;
|
|
38
|
+
readonly lines: readonly FlsLine[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface RunFiles {
|
|
42
|
+
readonly inputs: readonly RecordedInput[];
|
|
43
|
+
readonly written: readonly string[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* `path` as TeX named it → relative to the paper directory with forward slashes, or null when the file
|
|
48
|
+
* is not in it: the TeX tree's, a file of a sibling directory, an absolute path outside `pwd`.
|
|
49
|
+
*/
|
|
50
|
+
function inPaperDir(path: string, pwd: string | null): string | null {
|
|
51
|
+
const rel = isAbsolute(path)
|
|
52
|
+
? pwd === null
|
|
53
|
+
? null
|
|
54
|
+
: posix.relative(pwd, path)
|
|
55
|
+
: posix.normalize(path);
|
|
56
|
+
// `..` or `../x`: a file of a sibling directory. (`..x` is a file of this one.)
|
|
57
|
+
return rel === null || rel.split("/")[0] === ".." ? null : rel;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const distinct = <T>(xs: readonly T[]): readonly T[] => [...new Set(xs)];
|
|
61
|
+
|
|
62
|
+
/** A file TeX writes that the record names: TeX source of the paper (`.tex`) or a database (`.bib`). */
|
|
63
|
+
const isTexSource = (path: string): boolean => posix.extname(path) === ".tex";
|
|
64
|
+
const isRecordedWrite = (path: string): boolean =>
|
|
65
|
+
isTexSource(path) || posix.extname(path) === ".bib";
|
|
66
|
+
|
|
67
|
+
type Opened = FlsLine & { readonly rel: string };
|
|
68
|
+
|
|
69
|
+
/** The files of the paper directory a pass opened, with the op, in order. */
|
|
70
|
+
const opened = (pass: Fls): readonly Opened[] =>
|
|
71
|
+
pass.lines.flatMap((l) => {
|
|
72
|
+
const rel = inPaperDir(l.path, pass.pwd);
|
|
73
|
+
return rel === null ? [] : [{ ...l, rel }];
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
export interface RunOptions {
|
|
77
|
+
/** The job name: `<jobname>.aux` is the file whose opening marks `\begin{document}`. */
|
|
78
|
+
readonly jobname: string;
|
|
79
|
+
/** Files other programs wrote from TeX's own output, relative to the paper directory (`paper.bbl`). */
|
|
80
|
+
readonly generated: readonly string[];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** One read of a file of the paper, and whether it came after `\begin{document}`. */
|
|
84
|
+
interface Read {
|
|
85
|
+
readonly path: string;
|
|
86
|
+
readonly afterBegin: boolean;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The reads of the last pass: INPUT lines of files nothing wrote, or that a pass wrote as TeX source,
|
|
91
|
+
* each with its place against the line that opens `<jobname>.aux` — `\begin{document}`. No such line
|
|
92
|
+
* (the run stopped first): none is after it.
|
|
93
|
+
*/
|
|
94
|
+
function readsOf(
|
|
95
|
+
last: readonly Opened[],
|
|
96
|
+
outputs: ReadonlySet<string>,
|
|
97
|
+
{ jobname, generated }: RunOptions,
|
|
98
|
+
): readonly Read[] {
|
|
99
|
+
const boundary = last.findIndex(
|
|
100
|
+
(l) => l.op === "OUTPUT" && l.rel === `${jobname}.aux`,
|
|
101
|
+
);
|
|
102
|
+
return last.flatMap((l, i) =>
|
|
103
|
+
l.op === "INPUT" &&
|
|
104
|
+
(!outputs.has(l.rel) || isTexSource(l.rel)) &&
|
|
105
|
+
!generated.includes(l.rel)
|
|
106
|
+
? [{ path: l.rel, afterBegin: boundary >= 0 && i > boundary }]
|
|
107
|
+
: [],
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function filesOfRun(
|
|
112
|
+
passes: readonly Fls[],
|
|
113
|
+
options: RunOptions,
|
|
114
|
+
): RunFiles {
|
|
115
|
+
const everyPass = passes.flatMap(opened);
|
|
116
|
+
const wrote = (l: Opened): boolean => l.op === "OUTPUT";
|
|
117
|
+
const outputs = new Set(everyPass.filter(wrote).map((l) => l.rel));
|
|
118
|
+
const last = passes.at(-1);
|
|
119
|
+
const reads = readsOf(
|
|
120
|
+
last === undefined ? [] : opened(last),
|
|
121
|
+
outputs,
|
|
122
|
+
options,
|
|
123
|
+
);
|
|
124
|
+
// The main file holds `\begin{document}` itself, so it is body wherever TeX opened it.
|
|
125
|
+
const body = (path: string): boolean =>
|
|
126
|
+
path === reads[0]?.path ||
|
|
127
|
+
reads.some((r) => r.path === path && r.afterBegin);
|
|
128
|
+
return {
|
|
129
|
+
inputs: distinct(reads.map((r) => r.path)).map((path): RecordedInput => ({
|
|
130
|
+
path,
|
|
131
|
+
role: body(path) ? "body" : "preamble",
|
|
132
|
+
})),
|
|
133
|
+
written: distinct(
|
|
134
|
+
everyPass
|
|
135
|
+
.filter((l) => wrote(l) && isRecordedWrite(l.rel))
|
|
136
|
+
.map((l) => l.rel),
|
|
137
|
+
),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
@@ -160,7 +160,7 @@ describe("paper/folder-venue-leftover — silent", () => {
|
|
|
160
160
|
});
|
|
161
161
|
|
|
162
162
|
it("judged once per paper, on PIPELINE-STATUS.md only", () => {
|
|
163
|
-
expect(lint("aisec-2026", AIDC, { file: "paper.
|
|
163
|
+
expect(lint("aisec-2026", AIDC, { file: "paper.tex" })).toEqual([]);
|
|
164
164
|
});
|
|
165
165
|
|
|
166
166
|
it("a paper on a bare template family declares no venue: a venue in its folder may be its own", () => {
|
package/src/heading-case.test.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { run } from "./cli.ts";
|
|
|
12
12
|
import { headingCaseRule } from "./heading-case.ts";
|
|
13
13
|
import { latexReader } from "./adapters/latex/index.ts";
|
|
14
14
|
import { useTempDir, writeTree } from "../test/support.ts";
|
|
15
|
+
import { recordedTree } from "../test/recorded-fixture.ts";
|
|
15
16
|
|
|
16
17
|
const Results = z.array(
|
|
17
18
|
z.looseObject({
|
|
@@ -369,10 +370,15 @@ describe("tex/heading-case refuses, when ESLint loads it, a configuration that a
|
|
|
369
370
|
});
|
|
370
371
|
|
|
371
372
|
describe("tex/heading-case in the files a paper includes", () => {
|
|
372
|
-
const tree =
|
|
373
|
-
doc("\\section{Introduction}\n\\input{sections/results}"),
|
|
374
|
-
|
|
375
|
-
|
|
373
|
+
const tree = recordedTree(
|
|
374
|
+
paper(doc("\\section{Introduction}\n\\input{sections/results}"), PRESET, {
|
|
375
|
+
"papers/p/sections/results.tex": "\\section{Results and discussion}\n",
|
|
376
|
+
}),
|
|
377
|
+
"papers/p",
|
|
378
|
+
[
|
|
379
|
+
{ path: "paper.tex", role: "body" },
|
|
380
|
+
{ path: "sections/results.tex", role: "body" },
|
|
381
|
+
],
|
|
376
382
|
);
|
|
377
383
|
|
|
378
384
|
it("reports a heading at its own file and line, and --fix edits that file", async () => {
|
|
@@ -391,12 +397,20 @@ describe("tex/heading-case in the files a paper includes", () => {
|
|
|
391
397
|
});
|
|
392
398
|
|
|
393
399
|
it("🔴 reads a \\title kept in a file the body includes, where it stands", async () => {
|
|
394
|
-
const withTitle =
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
+
const withTitle = recordedTree(
|
|
401
|
+
paper(
|
|
402
|
+
`${ACM}\n\\begin{document}\n\\input{front}\n\\maketitle\n\\end{document}\n`,
|
|
403
|
+
PRESET,
|
|
404
|
+
{
|
|
405
|
+
"papers/p/front.tex":
|
|
406
|
+
"% the front matter\n\\title{Measuring things}\n",
|
|
407
|
+
},
|
|
408
|
+
),
|
|
409
|
+
"papers/p",
|
|
410
|
+
[
|
|
411
|
+
{ path: "paper.tex", role: "body" },
|
|
412
|
+
{ path: "front.tex", role: "body" },
|
|
413
|
+
],
|
|
400
414
|
);
|
|
401
415
|
const { found } = await lint(withTitle);
|
|
402
416
|
expect(found).toEqual([
|
package/src/init.test.ts
CHANGED
|
@@ -55,8 +55,8 @@ function project(): string {
|
|
|
55
55
|
"paperlint.json": JSON.stringify({ papersDir: "papers" }),
|
|
56
56
|
"papers/a/PIPELINE-STATUS.md": "---\nstages: []\n---\n",
|
|
57
57
|
"eslint-rules/fixtures/x/PIPELINE-STATUS.md": "---\nstages: []\n---\n",
|
|
58
|
-
"eslint-rules/fixtures/y/paper.
|
|
59
|
-
"drafts/z/paper.
|
|
58
|
+
"eslint-rules/fixtures/y/paper.tex": "% y\n",
|
|
59
|
+
"drafts/z/paper.tex": "% z\n",
|
|
60
60
|
};
|
|
61
61
|
for (const [p, text] of Object.entries(files)) {
|
|
62
62
|
mkdirSync(join(root, p, ".."), { recursive: true });
|
|
@@ -291,7 +291,7 @@ describe("paperlint init — a paperlint.json it cannot use stops it before anyt
|
|
|
291
291
|
for (const json of ["null\n", "[1]\n"]) {
|
|
292
292
|
const root = tree({
|
|
293
293
|
"paperlint.json": json,
|
|
294
|
-
"drafts/a/paper.
|
|
294
|
+
"drafts/a/paper.tex": "% a\n",
|
|
295
295
|
});
|
|
296
296
|
const decl = await declarePapers(root, () =>
|
|
297
297
|
choosePapers(root, { interactive: false }),
|
|
@@ -308,7 +308,7 @@ describe("paperlint init — a paperlint.json it cannot use stops it before anyt
|
|
|
308
308
|
it("a file without a trailing newline keeps having none", async () => {
|
|
309
309
|
const root = tree({
|
|
310
310
|
"paperlint.json": '{"x":1}',
|
|
311
|
-
"drafts/a/paper.
|
|
311
|
+
"drafts/a/paper.tex": "% a\n",
|
|
312
312
|
});
|
|
313
313
|
const decl = await declarePapers(root, () =>
|
|
314
314
|
choosePapers(root, { interactive: false }),
|
|
@@ -322,7 +322,7 @@ describe("paperlint init — a paperlint.json it cannot use stops it before anyt
|
|
|
322
322
|
|
|
323
323
|
describe("paperlint init — how several candidate directories were decided, said", () => {
|
|
324
324
|
const several = () =>
|
|
325
|
-
tree({ "drafts/a/paper.
|
|
325
|
+
tree({ "drafts/a/paper.tex": "% a\n", "writing/b/paper.tex": "% b\n" });
|
|
326
326
|
const section = (out: string[]) =>
|
|
327
327
|
out.slice(out.indexOf("papers directory") + 1, out.indexOf("settings") - 1);
|
|
328
328
|
|
|
@@ -530,7 +530,6 @@ describe("paperlint init — a first paper named after a venue", () => {
|
|
|
530
530
|
createPaper: (_root, name) => Promise.resolve((made.push(name), 0)),
|
|
531
531
|
});
|
|
532
532
|
expect(made).toEqual([]);
|
|
533
|
-
expect(asked.filter((q) => /format/.test(q))).toEqual([]);
|
|
534
533
|
expect(r.out[r.out.indexOf("first paper") + 1]).toBe(
|
|
535
534
|
' ✗ no paper created — `aisec-2026` — the paper folder names a venue ("AISec"); venues change on resubmission, so name it after the work\n if the venue really belongs in the name, rerun `paperlint new` with --allow-venue-name',
|
|
536
535
|
);
|
|
@@ -539,7 +538,7 @@ describe("paperlint init — a first paper named after a venue", () => {
|
|
|
539
538
|
|
|
540
539
|
describe("paperlint init — paths are shown relative to where it was run", () => {
|
|
541
540
|
it("a path that IS the working directory is shown whole, not as an empty string", async () => {
|
|
542
|
-
const root = tree({ "papers/a/paper.
|
|
541
|
+
const root = tree({ "papers/a/paper.tex": "% a\n" });
|
|
543
542
|
const home = join(root, ".claude", "skills");
|
|
544
543
|
mkdirSync(home, { recursive: true });
|
|
545
544
|
const r = await initRun(root, {
|
package/src/init.ts
CHANGED
|
@@ -63,12 +63,7 @@ import {
|
|
|
63
63
|
type Merge,
|
|
64
64
|
type WireResult,
|
|
65
65
|
} from "./hooks-settings.ts";
|
|
66
|
-
import {
|
|
67
|
-
DEFAULT_FORMAT,
|
|
68
|
-
isFormat,
|
|
69
|
-
nameProblem,
|
|
70
|
-
type PaperFormat,
|
|
71
|
-
} from "./new-paper.ts";
|
|
66
|
+
import { nameProblem } from "./new-paper.ts";
|
|
72
67
|
// The one source for the consumer's config key lives in lib/ (the ESLint rules and the skill scripts
|
|
73
68
|
// import it too). It is imported compiled, from dist/, the one path that resolves the same from src/
|
|
74
69
|
// and from dist/ — see CONTRIBUTING.md.
|
|
@@ -572,17 +567,11 @@ export interface InitOptions {
|
|
|
572
567
|
merge?: Merge | undefined;
|
|
573
568
|
/** `--paper <name>`: create this paper, even without a terminal. */
|
|
574
569
|
paper?: string | null;
|
|
575
|
-
/** `--format tex|md` for that paper. */
|
|
576
|
-
format?: PaperFormat | null;
|
|
577
570
|
/**
|
|
578
571
|
* Creates one paper and lints it — `paperlint new`'s own routine, passed in by the CLI so `init` and
|
|
579
572
|
* `new` cannot drift into two implementations.
|
|
580
573
|
*/
|
|
581
|
-
createPaper?: (
|
|
582
|
-
papersRoot: string,
|
|
583
|
-
name: string,
|
|
584
|
-
format: PaperFormat,
|
|
585
|
-
) => Promise<number>;
|
|
574
|
+
createPaper?: (papersRoot: string, name: string) => Promise<number>;
|
|
586
575
|
/**
|
|
587
576
|
* The venues a paper name may not name (`shippedVenueNames`), so `--paper aisec-2026` is refused
|
|
588
577
|
* before anything is asked. `createPaper` refuses it again whatever this holds.
|
|
@@ -717,7 +706,6 @@ export async function init(
|
|
|
717
706
|
hooks = true,
|
|
718
707
|
merge,
|
|
719
708
|
paper = null,
|
|
720
|
-
format = null,
|
|
721
709
|
createPaper,
|
|
722
710
|
venues = [],
|
|
723
711
|
run = spawnSync,
|
|
@@ -824,18 +812,11 @@ export async function init(
|
|
|
824
812
|
}
|
|
825
813
|
if (wanted !== null && createPaper) {
|
|
826
814
|
const problem = nameProblem(wanted, venues);
|
|
827
|
-
let fmt: PaperFormat = format ?? DEFAULT_FORMAT;
|
|
828
|
-
if (!problem && format === null && interactive) {
|
|
829
|
-
const f = (
|
|
830
|
-
await askOrDefault(ask, ` format: tex / md [${DEFAULT_FORMAT}] `)
|
|
831
|
-
)?.trim();
|
|
832
|
-
if (isFormat(f)) fmt = f;
|
|
833
|
-
}
|
|
834
815
|
if (problem) {
|
|
835
816
|
log(` ✗ no paper created — ${problem}`);
|
|
836
817
|
paperCode = 2;
|
|
837
818
|
} else {
|
|
838
|
-
const code = await createPaper(papersAbs, wanted
|
|
819
|
+
const code = await createPaper(papersAbs, wanted);
|
|
839
820
|
if (code !== 0) {
|
|
840
821
|
log(` ⚠ the new paper's lint exited ${String(code)} — see above`);
|
|
841
822
|
paperCode = code;
|
package/src/latex-log.ts
CHANGED
|
@@ -146,6 +146,26 @@ export function bibtexExcerpt(stdout: string): string[] {
|
|
|
146
146
|
.slice(0, MAX_EXCERPT);
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
/** What a bibtex run read: the databases its `.blg` names, the entries its `.bbl` holds. */
|
|
150
|
+
export interface BibtexRead {
|
|
151
|
+
readonly databases: readonly string[];
|
|
152
|
+
readonly bibitems: readonly string[];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** `\bibitem{key}`, `\bibitem[label]{key}` — a label may hold one level of braces (natbib's `[{A}(2020)]`). */
|
|
156
|
+
const BIBITEM = /\\bibitem\s*(?:\[(?:[^[\]{}]|\{[^{}]*\})*\])?\s*\{([^}]+)\}/g;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* What a bibtex run read, from the files it left: `Database file #n: name` in its `.blg` (the name
|
|
160
|
+
* as the `.aux` gave it, relative to where bibtex ran), and every key its `.bbl` typesets.
|
|
161
|
+
*/
|
|
162
|
+
export const bibtexRead = (blg: string, bbl: string): BibtexRead => ({
|
|
163
|
+
databases: [...blg.matchAll(/^Database file #\d+: (.+)$/gm)].flatMap((m) =>
|
|
164
|
+
m.slice(1, 2),
|
|
165
|
+
),
|
|
166
|
+
bibitems: [...bbl.matchAll(BIBITEM)].flatMap((m) => m.slice(1, 2)),
|
|
167
|
+
});
|
|
168
|
+
|
|
149
169
|
/** What the `.aux` tells bibtex: the cited keys, the databases and the style. */
|
|
150
170
|
export interface AuxBib {
|
|
151
171
|
/** Sorted, unique. `*` (`\nocite{*}`) is a key like any other. */
|
package/src/latex-loop.ts
CHANGED
|
@@ -86,6 +86,8 @@ export type Observation =
|
|
|
86
86
|
readonly bib: BibInput;
|
|
87
87
|
/** The first error and its source context, from the log. Empty on success. */
|
|
88
88
|
readonly errorLines: readonly string[];
|
|
89
|
+
/** What `-recorder` wrote after this pass (`paper.fls`), or null when pdflatex wrote none. */
|
|
90
|
+
readonly fls: string | null;
|
|
89
91
|
}
|
|
90
92
|
| {
|
|
91
93
|
readonly step: "bibtex";
|
package/src/lint-fix.test.ts
CHANGED
|
@@ -20,6 +20,10 @@ afterEach(() => {
|
|
|
20
20
|
for (const d of dirs.splice(0)) rmSync(d, { recursive: true, force: true });
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
+
/** A paper.tex whose whole body is `body`. */
|
|
24
|
+
const PAPER = (body: string): string =>
|
|
25
|
+
`\\documentclass{article}\n\\begin{document}\n${body}\n\\end{document}\n`;
|
|
26
|
+
|
|
23
27
|
function project(
|
|
24
28
|
settings: Record<string, unknown>,
|
|
25
29
|
files: Record<string, string> = {},
|
|
@@ -30,7 +34,7 @@ function project(
|
|
|
30
34
|
"package.json": JSON.stringify({ name: "c", private: true }),
|
|
31
35
|
"paperlint.json": JSON.stringify({ papersDir: "papers", ...settings }),
|
|
32
36
|
"papers/a/PIPELINE-STATUS.md": "---\nstages: []\n---\n",
|
|
33
|
-
"papers/a/paper.
|
|
37
|
+
"papers/a/paper.tex": PAPER("See §5, §6 and §7; $p < .05$."),
|
|
34
38
|
...files,
|
|
35
39
|
};
|
|
36
40
|
for (const [p, text] of Object.entries(all)) {
|
|
@@ -64,15 +68,17 @@ describe("paperlint lint --fix", () => {
|
|
|
64
68
|
const fixed = await lint(root, ["--fix"]);
|
|
65
69
|
expect(ruleIds(fixed.out)).not.toContain("paper/section-word");
|
|
66
70
|
expect(ruleIds(fixed.out)).not.toContain("paper/leading-zero");
|
|
67
|
-
expect(readFileSync(join(root, "papers/a/paper.
|
|
68
|
-
"
|
|
71
|
+
expect(readFileSync(join(root, "papers/a/paper.tex"), "utf8")).toBe(
|
|
72
|
+
PAPER("See Section 5, Section 6 and Section 7; $p < 0.05$."),
|
|
69
73
|
);
|
|
70
74
|
});
|
|
71
75
|
|
|
72
76
|
it("without --fix nothing is written", async () => {
|
|
73
77
|
const root = project({});
|
|
74
78
|
await lint(root);
|
|
75
|
-
expect(readFileSync(join(root, "papers/a/paper.
|
|
79
|
+
expect(readFileSync(join(root, "papers/a/paper.tex"), "utf8")).toMatch(
|
|
80
|
+
/§5/,
|
|
81
|
+
);
|
|
76
82
|
});
|
|
77
83
|
});
|
|
78
84
|
|
package/src/linted-set.test.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { join, relative } from "node:path";
|
|
|
20
20
|
import { afterEach, describe, expect, it } from "vitest";
|
|
21
21
|
import { run } from "./cli.ts";
|
|
22
22
|
import { lintReport } from "../test/lint-report.ts";
|
|
23
|
+
import { recordedTree } from "../test/recorded-fixture.ts";
|
|
23
24
|
|
|
24
25
|
const dirs: string[] = [];
|
|
25
26
|
afterEach(() => {
|
|
@@ -87,7 +88,14 @@ function project(): string {
|
|
|
87
88
|
"papers/a/sections/intro.tex": "Intro.\n",
|
|
88
89
|
...NEIGHBOURS,
|
|
89
90
|
};
|
|
90
|
-
|
|
91
|
+
// As a build left it: TeX read paper.tex, the macro file in the preamble and the section in the body
|
|
92
|
+
// (`paper-guards` is paperlint's own input, outside the paper's directory, so the record has none).
|
|
93
|
+
const built = recordedTree(all, "papers/a", [
|
|
94
|
+
{ path: "paper.tex", role: "body" },
|
|
95
|
+
{ path: "macros.tex", role: "preamble" },
|
|
96
|
+
{ path: "sections/intro.tex", role: "body" },
|
|
97
|
+
]);
|
|
98
|
+
for (const [p, text] of Object.entries(built)) {
|
|
91
99
|
mkdirSync(join(root, p, ".."), { recursive: true });
|
|
92
100
|
writeFileSync(join(root, p), text);
|
|
93
101
|
}
|