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,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bibliographyAt` — a path a person gave, and the bibliography to read for it. A `.bib` is read alone,
|
|
3
|
+
* as named; a `.tex`, or a directory holding `paper.tex`, is the bibliography the last build's bibtex
|
|
4
|
+
* opened (`_build/sources.json`, docs/design/paper-sources.md §1) — read from the text the author
|
|
5
|
+
* edits: the `filecontents` block that wrote a database, or the file itself. A paper with no current
|
|
6
|
+
* record is refused, in one vocabulary, naming the command that records it.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, expect, it } from "vitest";
|
|
9
|
+
import { bibReader } from "./adapters/bibtex/index.ts";
|
|
10
|
+
import { latexReader } from "./adapters/latex/index.ts";
|
|
11
|
+
import { memoryFiles } from "./adapters/memory/index.ts";
|
|
12
|
+
import { sourcesCodec } from "./adapters/sources-record/index.ts";
|
|
13
|
+
import type { BibText } from "./domain/paper-sources.ts";
|
|
14
|
+
import { absolutePath } from "./domain/paths.ts";
|
|
15
|
+
import { bibliographyAt, bibliographyUnreadWhy } from "./paper-sources.ts";
|
|
16
|
+
import {
|
|
17
|
+
leftByTeX,
|
|
18
|
+
planted,
|
|
19
|
+
recordText,
|
|
20
|
+
type Shape,
|
|
21
|
+
} from "../test/recorded-fixture.ts";
|
|
22
|
+
|
|
23
|
+
const doc = (body: string): string =>
|
|
24
|
+
`\\documentclass{article}\n\\begin{document}\n${body}\n\\end{document}\n`;
|
|
25
|
+
const A = "@misc{a, title = {A}}\n";
|
|
26
|
+
const B = "@misc{b, title = {B}}\n";
|
|
27
|
+
|
|
28
|
+
/** A directory as TeX and the build leave it: the files, and the record over them (none: not built). */
|
|
29
|
+
function built(
|
|
30
|
+
dir: string,
|
|
31
|
+
files: Readonly<Record<string, string>>,
|
|
32
|
+
shape: Shape | string | null,
|
|
33
|
+
): Record<string, string> {
|
|
34
|
+
const record =
|
|
35
|
+
shape === null
|
|
36
|
+
? {}
|
|
37
|
+
: {
|
|
38
|
+
[`${dir}/_build/sources.json`]:
|
|
39
|
+
typeof shape === "string" ? shape : recordText(files, shape),
|
|
40
|
+
};
|
|
41
|
+
return {
|
|
42
|
+
...Object.fromEntries(
|
|
43
|
+
Object.entries(files).map(([f, t]) => [`${dir}/${f}`, t]),
|
|
44
|
+
),
|
|
45
|
+
...record,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const PAPER = doc("\\bibliography{refs}");
|
|
50
|
+
const SHAPE: Shape = {
|
|
51
|
+
inputs: [
|
|
52
|
+
["paper.tex", "body"],
|
|
53
|
+
["refs.bib", "preamble"],
|
|
54
|
+
],
|
|
55
|
+
databases: ["refs.bib"],
|
|
56
|
+
};
|
|
57
|
+
const OWN = { "paper.tex": PAPER, "refs.bib": A, "other.bib": B };
|
|
58
|
+
|
|
59
|
+
/** Each text read, with the keys of the entries it holds. */
|
|
60
|
+
const keysIn = (
|
|
61
|
+
r: ReturnType<typeof bibliographyAt>,
|
|
62
|
+
): readonly (readonly [string, readonly string[]])[] =>
|
|
63
|
+
r.ok ? r.value.texts.map((t) => [t.path, entryKeys(t)] as const) : [];
|
|
64
|
+
const entryKeys = (t: BibText): readonly string[] =>
|
|
65
|
+
t.entries.map((e) => e.key);
|
|
66
|
+
|
|
67
|
+
const depsOf = (disk: Readonly<Record<string, string>>) => ({
|
|
68
|
+
files: memoryFiles({ "/p/notes.txt": "notes", ...disk }),
|
|
69
|
+
codec: sourcesCodec,
|
|
70
|
+
latex: latexReader,
|
|
71
|
+
bib: bibReader,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
/** What a path comes to: the paper's directory and the files read, or the one sentence why not. */
|
|
75
|
+
const at = (disk: Readonly<Record<string, string>>, path: string) => {
|
|
76
|
+
const r = bibliographyAt(absolutePath(path), depsOf(disk));
|
|
77
|
+
return r.ok
|
|
78
|
+
? [r.value.paperDir, r.value.texts.map((t) => t.path)]
|
|
79
|
+
: bibliographyUnreadWhy(r.error);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
describe("bibliographyAt — the paths it reads", () => {
|
|
83
|
+
const disk = built("/p", OWN, SHAPE);
|
|
84
|
+
|
|
85
|
+
it("a .bib is read alone, as named — with no record of any build", () => {
|
|
86
|
+
expect([
|
|
87
|
+
at(built("/p", OWN, null), "/p/other.bib"),
|
|
88
|
+
at(disk, "/p/other.bib"),
|
|
89
|
+
]).toEqual([
|
|
90
|
+
["/p", ["/p/other.bib"]],
|
|
91
|
+
["/p", ["/p/other.bib"]],
|
|
92
|
+
]);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("a .tex, or the directory of a paper.tex, is the bibliography the last build's bibtex opened — not every .bib in the folder", () => {
|
|
96
|
+
expect([at(disk, "/p/paper.tex"), at(disk, "/p")]).toEqual([
|
|
97
|
+
["/p", ["/p/refs.bib"]],
|
|
98
|
+
["/p", ["/p/refs.bib"]],
|
|
99
|
+
]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("🔴 a .bib TeX wrote from a block is read from the block of the .tex the author edits, where its entries stand", () => {
|
|
103
|
+
const tex =
|
|
104
|
+
"\\documentclass{article}\n\\begin{filecontents*}[overwrite]{refs.bib}\n" +
|
|
105
|
+
A +
|
|
106
|
+
"\\end{filecontents*}\n\\begin{document}\nx\\bibliography{refs}\n\\end{document}\n";
|
|
107
|
+
const files = { "paper.tex": tex, "refs.bib": A };
|
|
108
|
+
const d = built("/p", files, {
|
|
109
|
+
inputs: [["paper.tex", "body"]],
|
|
110
|
+
written: ["refs.bib"],
|
|
111
|
+
databases: ["refs.bib"],
|
|
112
|
+
});
|
|
113
|
+
expect(keysIn(bibliographyAt(absolutePath("/p"), depsOf(d)))).toEqual([
|
|
114
|
+
["/p/paper.tex", ["a"]],
|
|
115
|
+
]);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe("bibliographyAt — where a block is", () => {
|
|
120
|
+
it("a block that is in an included file is read from that file", () => {
|
|
121
|
+
const files = {
|
|
122
|
+
"paper.tex": doc("\\input{bibblock}\\bibliography{refs}"),
|
|
123
|
+
"bibblock.tex": `\\begin{filecontents*}{refs.bib}\n${A}\\end{filecontents*}\n`,
|
|
124
|
+
"refs.bib": A,
|
|
125
|
+
};
|
|
126
|
+
const d = built("/p", files, {
|
|
127
|
+
inputs: [
|
|
128
|
+
["paper.tex", "body"],
|
|
129
|
+
["bibblock.tex", "body"],
|
|
130
|
+
],
|
|
131
|
+
written: ["refs.bib"],
|
|
132
|
+
databases: ["refs.bib"],
|
|
133
|
+
});
|
|
134
|
+
expect(at(d, "/p")).toEqual(["/p", ["/p/bibblock.tex"]]);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("when no text holds what bibtex read, the file TeX left is read", () => {
|
|
138
|
+
const files = {
|
|
139
|
+
"paper.tex": doc("\\bibliography{refs}"),
|
|
140
|
+
"refs.bib": A,
|
|
141
|
+
};
|
|
142
|
+
const d = built("/p", files, {
|
|
143
|
+
inputs: [["paper.tex", "body"]],
|
|
144
|
+
written: ["refs.bib"],
|
|
145
|
+
databases: ["refs.bib"],
|
|
146
|
+
});
|
|
147
|
+
expect(at(d, "/p")).toEqual(["/p", ["/p/refs.bib"]]);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe("bibliographyAt — every refusal in one vocabulary, naming the path and what to give instead", () => {
|
|
152
|
+
it("a path that is not a paper, a .bib or a .tex", () => {
|
|
153
|
+
const disk = built("/p", OWN, SHAPE);
|
|
154
|
+
expect([
|
|
155
|
+
at(disk, "/p/gone.bib"),
|
|
156
|
+
at(disk, "/nowhere"),
|
|
157
|
+
at(disk, "/p/notes.txt"),
|
|
158
|
+
at(disk, "/p/gone.tex"),
|
|
159
|
+
]).toEqual([
|
|
160
|
+
"/p/gone.bib does not exist — nowhere to take a bibliography from",
|
|
161
|
+
"no paper.tex in /nowhere — name the paper's .tex, or a .bib to read it alone",
|
|
162
|
+
"/p/notes.txt is neither a .bib nor a .tex — name the paper's directory, its .tex, or a .bib",
|
|
163
|
+
"/p/gone.tex does not exist — nowhere to take a bibliography from",
|
|
164
|
+
]);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("🔴 a paper with no record of a build is not read from its source — run `npx paperlint build` first", () => {
|
|
168
|
+
const disk = built("/p", OWN, null);
|
|
169
|
+
expect([at(disk, "/p"), at(disk, "/p/paper.tex")]).toEqual([
|
|
170
|
+
"/p has not been built — run `npx paperlint build` first, which records the databases bibtex reads",
|
|
171
|
+
"/p has not been built — run `npx paperlint build` first, which records the databases bibtex reads",
|
|
172
|
+
]);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("a record the paper has changed since, and one that cannot be read, name the command too", () => {
|
|
176
|
+
const stale = built("/p", OWN, SHAPE);
|
|
177
|
+
stale["/p/refs.bib"] = `${A}${B}`;
|
|
178
|
+
expect(at(stale, "/p")).toBe(
|
|
179
|
+
"/p changed since the last build (refs.bib edited) — run `npx paperlint build` first",
|
|
180
|
+
);
|
|
181
|
+
const broken = built("/p", OWN, "{ not json");
|
|
182
|
+
expect(at(broken, "/p")).toMatch(
|
|
183
|
+
/^the last build's record of \/p cannot be used \(.+\) — run `npx paperlint build` first$/,
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("a build whose bibtex read no database, and one whose databases are gone, have nothing to read", () => {
|
|
188
|
+
const none = built(
|
|
189
|
+
"/q",
|
|
190
|
+
{ "paper.tex": doc("no bibliography") },
|
|
191
|
+
{
|
|
192
|
+
inputs: [["paper.tex", "body"]],
|
|
193
|
+
},
|
|
194
|
+
);
|
|
195
|
+
expect(at(none, "/q")).toBe(
|
|
196
|
+
"bibtex read no database in the last build of /q — there is nothing to read",
|
|
197
|
+
);
|
|
198
|
+
const gone = built(
|
|
199
|
+
"/s",
|
|
200
|
+
{ "paper.tex": doc("\\bibliography{gone}") },
|
|
201
|
+
{
|
|
202
|
+
inputs: [["paper.tex", "body"]],
|
|
203
|
+
databases: ["gone.bib"],
|
|
204
|
+
},
|
|
205
|
+
);
|
|
206
|
+
expect(at(gone, "/s")).toBe(
|
|
207
|
+
"bibtex opened gone.bib in the last build of /s, and none of them is on disk now",
|
|
208
|
+
);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe("bibliographyAt — a bibliography it can read only in part", () => {
|
|
213
|
+
it("🔴 a build whose bibtex also opened a database outside the paper directory (BIBINPUTS) is refused, not read in part", () => {
|
|
214
|
+
// Measured: with BIBINPUTS=../shared, bibtex's .blg names `Database file #2: shared.bib` and the
|
|
215
|
+
// .bbl typesets its entry; the paper directory holds refs.bib only.
|
|
216
|
+
const partly = built(
|
|
217
|
+
"/r",
|
|
218
|
+
{ "paper.tex": doc("\\bibliography{refs,shared}"), "refs.bib": A },
|
|
219
|
+
{
|
|
220
|
+
inputs: [["paper.tex", "body"]],
|
|
221
|
+
databases: ["refs.bib", "shared.bib"],
|
|
222
|
+
},
|
|
223
|
+
);
|
|
224
|
+
expect(at(partly, "/r")).toBe(
|
|
225
|
+
"bibtex opened shared.bib in the last build of /r from outside it (its search path, BIBINPUTS), and paperlint reads only the paper's own databases — copy it into /r, or name each .bib to read it alone",
|
|
226
|
+
);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
describe("bibliographyAt on the planted papers, with the record TeX wrote for each", () => {
|
|
231
|
+
it.each([
|
|
232
|
+
["v3-declared", ["/p/paper.bib"]],
|
|
233
|
+
["v26-comment-in-names", ["/p/one.bib", "/p/two.bib"]],
|
|
234
|
+
["v16-included-block", ["/p/refs.bib"]],
|
|
235
|
+
["v25-string-differs", ["/p/refs.bib"]],
|
|
236
|
+
// A block that TeX wrote: read from the block where the author edits it.
|
|
237
|
+
["v8-jobname", ["/p/paper.tex"]],
|
|
238
|
+
["v5-percent-entry", ["/p/paper.tex"]],
|
|
239
|
+
// The block names its file on the line after \\begin: still read from the block.
|
|
240
|
+
["v30-name-next-line", ["/p/paper.tex"]],
|
|
241
|
+
])("%s", (name, files) => {
|
|
242
|
+
const p = planted(name);
|
|
243
|
+
const disk = built("/p", leftByTeX(p), p.record);
|
|
244
|
+
expect(at(disk, "/p")).toEqual(["/p", files]);
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
describe("bibliographyAt — an @string one database defines and a later one uses", () => {
|
|
249
|
+
/** Each text read, with each entry's author and booktitle as the reader read them. */
|
|
250
|
+
const fieldsAt = (name: string) => {
|
|
251
|
+
const p = planted(name);
|
|
252
|
+
const r = bibliographyAt(
|
|
253
|
+
absolutePath("/p"),
|
|
254
|
+
depsOf(built("/p", leftByTeX(p), p.record)),
|
|
255
|
+
);
|
|
256
|
+
return r.ok
|
|
257
|
+
? r.value.texts.map((t) => [
|
|
258
|
+
t.path,
|
|
259
|
+
t.entries.map((e) => [
|
|
260
|
+
e.key,
|
|
261
|
+
e.names["author"],
|
|
262
|
+
e.fields["booktitle"],
|
|
263
|
+
]),
|
|
264
|
+
])
|
|
265
|
+
: r.error;
|
|
266
|
+
};
|
|
267
|
+
const LATE = [
|
|
268
|
+
"late",
|
|
269
|
+
[{ lastName: "Lovelace", firstName: "Ada" }],
|
|
270
|
+
"Proceedings of the Shared Venue",
|
|
271
|
+
];
|
|
272
|
+
|
|
273
|
+
it("🔴 v27-shared-string: references.bib's entry is read with the @strings abbreviations.bib defined, as bibtex read it", () => {
|
|
274
|
+
expect(fieldsAt("v27-shared-string")).toEqual([
|
|
275
|
+
["/p/abbreviations.bib", []],
|
|
276
|
+
["/p/references.bib", [LATE]],
|
|
277
|
+
]);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("🔴 v28-shared-string-block: a block that uses them is still the database TeX wrote, read where the author edits it", () => {
|
|
281
|
+
expect(fieldsAt("v28-shared-string-block")).toEqual([
|
|
282
|
+
["/p/abbreviations.bib", []],
|
|
283
|
+
["/p/paper.tex", [LATE]],
|
|
284
|
+
]);
|
|
285
|
+
});
|
|
286
|
+
});
|
package/src/build.harness.mjs
CHANGED
|
@@ -482,7 +482,7 @@ try {
|
|
|
482
482
|
);
|
|
483
483
|
|
|
484
484
|
// ── no paper.tex: a REFUSAL, not a skip ───────────────────────────────────────────────
|
|
485
|
-
const bare = paper("bare", { "
|
|
485
|
+
const bare = paper("bare", { "PIPELINE-STATUS.md": "# x" });
|
|
486
486
|
const ntex = fakeTex();
|
|
487
487
|
const nr = await buildPaper(bare, {
|
|
488
488
|
cwd: root,
|
|
@@ -546,7 +546,7 @@ try {
|
|
|
546
546
|
|
|
547
547
|
// (c) no paper.tex: refused, and a PDF left from some earlier build goes too.
|
|
548
548
|
const bareStale = paper("bare-stale", {
|
|
549
|
-
"
|
|
549
|
+
"PIPELINE-STATUS.md": "# x",
|
|
550
550
|
"paper.pdf": "%PDF-stale-from-yesterday",
|
|
551
551
|
});
|
|
552
552
|
const bsTex = fakeTex();
|
package/src/build.test.ts
CHANGED
|
@@ -22,7 +22,14 @@ import {
|
|
|
22
22
|
type PaperFacts,
|
|
23
23
|
} from "./build.ts";
|
|
24
24
|
import type { Geometry } from "./domain/geometry.ts";
|
|
25
|
+
import { bibReader } from "./adapters/bibtex/index.ts";
|
|
26
|
+
import { nodeFiles } from "./adapters/node/index.ts";
|
|
27
|
+
import { sourcesCodec } from "./adapters/sources-record/index.ts";
|
|
28
|
+
import { sha256Hex } from "./domain/sha256.ts";
|
|
29
|
+
import { serializeSourcesRecord } from "./domain/sources-record.ts";
|
|
30
|
+
import { bibliographyReader } from "./references.ts";
|
|
25
31
|
import type { PdfReader } from "./pdf-facts.ts";
|
|
32
|
+
import type { Recorded, TexRun } from "./sources-record.ts";
|
|
26
33
|
|
|
27
34
|
const root = useTempDir("paperlint-build-test-");
|
|
28
35
|
const TEX = "\\documentclass{article}\\begin{document}x\\end{document}";
|
|
@@ -44,6 +51,8 @@ interface Fake {
|
|
|
44
51
|
noLog?: boolean;
|
|
45
52
|
/** Each pass writes a different .aux, so the loop never settles. */
|
|
46
53
|
churn?: boolean;
|
|
54
|
+
/** What pdflatex's `-recorder` file holds after pass `n`; without it, pdflatex records nothing. */
|
|
55
|
+
fls?: (pass: number) => string;
|
|
47
56
|
}
|
|
48
57
|
/** A pdflatex pass: writes the .aux, the log unless it dies first, and a PDF when it exits 0. */
|
|
49
58
|
function latexPass(f: Fake, cwd: string, pass: number) {
|
|
@@ -51,6 +60,7 @@ function latexPass(f: Fake, cwd: string, pass: number) {
|
|
|
51
60
|
const aux = f.churn ? `\\relax % ${String(pass)}\n` : (f.aux ?? "\\relax\n");
|
|
52
61
|
writeFileSync(join(cwd, "paper.aux"), aux);
|
|
53
62
|
if (!f.noLog) writeFileSync(join(cwd, "paper.log"), f.log ?? "");
|
|
63
|
+
if (f.fls) writeFileSync(join(cwd, "paper.fls"), f.fls(pass));
|
|
54
64
|
if (status === 0) writeFileSync(join(cwd, "paper.pdf"), "%PDF-fake");
|
|
55
65
|
return f.noLog ? { status } : { status, stdout: "", stderr: "" };
|
|
56
66
|
}
|
|
@@ -162,7 +172,7 @@ test("documentclass: an empty name is no class; a macro inside the name contribu
|
|
|
162
172
|
});
|
|
163
173
|
|
|
164
174
|
test("facts of a paper with no paper.tex: no class", () => {
|
|
165
|
-
assert.deepEqual(readFacts(paper({ "
|
|
175
|
+
assert.deepEqual(readFacts(paper({ "PIPELINE-STATUS.md": "# P\n" })), {
|
|
166
176
|
main: null,
|
|
167
177
|
documentclass: null,
|
|
168
178
|
venue: null,
|
|
@@ -263,6 +273,55 @@ test("compile: warnings the final log still reports are named in the note", asyn
|
|
|
263
273
|
assert.match(r.notes?.[0] ?? "", /— ⚠️ the final log still reports /);
|
|
264
274
|
});
|
|
265
275
|
|
|
276
|
+
test("references: without a reader the step says it was not wired; with one and no checker, the record says not checked", async () => {
|
|
277
|
+
const text = {
|
|
278
|
+
"paper.tex":
|
|
279
|
+
"\\documentclass{article}\\begin{document}x\\bibliography{refs}\\end{document}",
|
|
280
|
+
"refs.bib": "@misc{a, url = {https://example.org}}\n",
|
|
281
|
+
};
|
|
282
|
+
// What a build of this paper records: TeX read paper.tex, bibtex opened refs.bib and typeset `a`.
|
|
283
|
+
const digest = (s: string) => sha256Hex(new TextEncoder().encode(s));
|
|
284
|
+
const bib = {
|
|
285
|
+
...text,
|
|
286
|
+
"_build/sources.json": serializeSourcesRecord({
|
|
287
|
+
schema: 1,
|
|
288
|
+
inputs: [{ path: "paper.tex", role: "body" }],
|
|
289
|
+
written: [],
|
|
290
|
+
bibdata: ["refs"],
|
|
291
|
+
bibtex: {
|
|
292
|
+
ran: true,
|
|
293
|
+
databases: ["refs.bib"],
|
|
294
|
+
keys: ["a"],
|
|
295
|
+
exit: 0,
|
|
296
|
+
errors: [],
|
|
297
|
+
},
|
|
298
|
+
sha256: {
|
|
299
|
+
"paper.tex": digest(text["paper.tex"]),
|
|
300
|
+
"refs.bib": digest(text["refs.bib"]),
|
|
301
|
+
},
|
|
302
|
+
}),
|
|
303
|
+
};
|
|
304
|
+
const unwired = await build(paper(bib));
|
|
305
|
+
assert.ok(
|
|
306
|
+
unwired.r.notes?.includes(
|
|
307
|
+
"references NOT checked — no bibliography reader was wired into this build; lint will say so",
|
|
308
|
+
),
|
|
309
|
+
JSON.stringify(unwired.r.notes),
|
|
310
|
+
);
|
|
311
|
+
const readBibliography = bibliographyReader({
|
|
312
|
+
files: nodeFiles,
|
|
313
|
+
codec: sourcesCodec,
|
|
314
|
+
bib: bibReader,
|
|
315
|
+
});
|
|
316
|
+
const wired = await build(paper(bib), {}, { readBibliography });
|
|
317
|
+
assert.ok(
|
|
318
|
+
wired.r.notes?.includes(
|
|
319
|
+
"references NOT checked — no reference checker was wired into this build; lint will say so",
|
|
320
|
+
),
|
|
321
|
+
JSON.stringify(wired.r.notes),
|
|
322
|
+
);
|
|
323
|
+
});
|
|
324
|
+
|
|
266
325
|
test("measure: a review build and a stub last page are named; a measured geometry adds nothing", async () => {
|
|
267
326
|
const review = await build(paper(), {}, { readPdf: reader(page(20, 60)) });
|
|
268
327
|
assert.match(
|
|
@@ -382,3 +441,154 @@ test("a failed result without its lines still says what failed", () => {
|
|
|
382
441
|
].join("\n"),
|
|
383
442
|
);
|
|
384
443
|
});
|
|
444
|
+
|
|
445
|
+
// ── the record of the build's sources ────────────────────────────────────────────────────────
|
|
446
|
+
|
|
447
|
+
/** What a recorder was handed, for each paper it was asked about. */
|
|
448
|
+
function spyRecorder(answer: Recorded) {
|
|
449
|
+
const asked: { dir: string; run: TexRun }[] = [];
|
|
450
|
+
return {
|
|
451
|
+
asked,
|
|
452
|
+
recordSources: (dir: string, run: TexRun): Recorded => {
|
|
453
|
+
asked.push({ dir, run });
|
|
454
|
+
return answer;
|
|
455
|
+
},
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
const RECORDED: Recorded = {
|
|
459
|
+
kind: "recorded",
|
|
460
|
+
path: "/p/_build/sources.json",
|
|
461
|
+
record: {
|
|
462
|
+
schema: 1,
|
|
463
|
+
inputs: [{ path: "paper.tex", role: "body" }],
|
|
464
|
+
written: [],
|
|
465
|
+
bibdata: [],
|
|
466
|
+
bibtex: { ran: false },
|
|
467
|
+
sha256: {},
|
|
468
|
+
},
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
test("sources: every pdflatex pass runs with -recorder, so TeX itself says which files it read", async () => {
|
|
472
|
+
const args: (readonly string[])[] = [];
|
|
473
|
+
const run = fakeRun({});
|
|
474
|
+
await build(
|
|
475
|
+
paper(),
|
|
476
|
+
{},
|
|
477
|
+
{
|
|
478
|
+
run: (bin, a, opts) => {
|
|
479
|
+
if (bin === "pdflatex") args.push(a);
|
|
480
|
+
return run(bin, a, opts);
|
|
481
|
+
},
|
|
482
|
+
},
|
|
483
|
+
);
|
|
484
|
+
assert.ok(args.length >= 2);
|
|
485
|
+
assert.ok(
|
|
486
|
+
args.every((a) => a.includes("-recorder")),
|
|
487
|
+
JSON.stringify(args),
|
|
488
|
+
);
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
test("sources: the recorder is handed the .fls of every pass, in order, and bibtex's exit when it ran", async () => {
|
|
492
|
+
const spy = spyRecorder(RECORDED);
|
|
493
|
+
const { r } = await build(
|
|
494
|
+
paper({ "paper.tex": TEX, "refs.bib": "@misc{k}\n" }),
|
|
495
|
+
{
|
|
496
|
+
aux: "\\citation{k}\n\\bibstyle{plain}\n\\bibdata{refs}\n",
|
|
497
|
+
fls: (pass) => `pass ${String(pass)}`,
|
|
498
|
+
},
|
|
499
|
+
{ recordSources: spy.recordSources },
|
|
500
|
+
);
|
|
501
|
+
assert.equal(r.status, "built");
|
|
502
|
+
const [only] = spy.asked;
|
|
503
|
+
assert.equal(spy.asked.length, 1);
|
|
504
|
+
assert.match(only?.dir ?? "", /\/p\d+$/);
|
|
505
|
+
const passes = only?.run.fls ?? [];
|
|
506
|
+
assert.ok(passes.length >= 2);
|
|
507
|
+
assert.deepEqual(
|
|
508
|
+
passes,
|
|
509
|
+
passes.map((_, i) => `pass ${String(i + 1)}`),
|
|
510
|
+
);
|
|
511
|
+
assert.equal(only?.run.bibtexExit, 0);
|
|
512
|
+
assert.match(
|
|
513
|
+
r.notes?.[0] ?? "",
|
|
514
|
+
/bibtex runs?; sources: 1 file read → _build\/sources\.json$/,
|
|
515
|
+
);
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
test("sources: a build that ran no bibtex says so to the recorder", async () => {
|
|
519
|
+
const spy = spyRecorder(RECORDED);
|
|
520
|
+
await build(
|
|
521
|
+
paper(),
|
|
522
|
+
{ fls: () => "x" },
|
|
523
|
+
{ recordSources: spy.recordSources },
|
|
524
|
+
);
|
|
525
|
+
assert.equal(spy.asked[0]?.run.bibtexExit, null);
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
test("sources: a paper.fls an earlier build left is not this build's: pdflatex that wrote none hands the recorder none", async () => {
|
|
529
|
+
const spy = spyRecorder(RECORDED);
|
|
530
|
+
const dir = paper({ "paper.tex": TEX, "paper.fls": "INPUT stale.tex\n" });
|
|
531
|
+
await build(dir, {}, { recordSources: spy.recordSources });
|
|
532
|
+
assert.deepEqual(spy.asked[0]?.run.fls, []);
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
test("sources: without a recorder, or when it records nothing, the build still builds and the note says it did not record", async () => {
|
|
536
|
+
const unwired = await build(paper());
|
|
537
|
+
assert.equal(unwired.r.status, "built");
|
|
538
|
+
assert.ok(
|
|
539
|
+
unwired.r.notes?.[0]?.endsWith(
|
|
540
|
+
"; sources NOT recorded — no recorder was wired into this build; lint will say so",
|
|
541
|
+
),
|
|
542
|
+
JSON.stringify(unwired.r.notes),
|
|
543
|
+
);
|
|
544
|
+
const empty = await build(
|
|
545
|
+
paper(),
|
|
546
|
+
{},
|
|
547
|
+
{
|
|
548
|
+
recordSources: spyRecorder({
|
|
549
|
+
kind: "not-recorded",
|
|
550
|
+
why: "pdflatex wrote no paper.fls — nothing to record",
|
|
551
|
+
}).recordSources,
|
|
552
|
+
},
|
|
553
|
+
);
|
|
554
|
+
assert.equal(empty.r.status, "built");
|
|
555
|
+
assert.ok(
|
|
556
|
+
empty.r.notes?.[0]?.endsWith(
|
|
557
|
+
"; sources NOT recorded — pdflatex wrote no paper.fls — nothing to record; lint will say so",
|
|
558
|
+
),
|
|
559
|
+
JSON.stringify(empty.r.notes),
|
|
560
|
+
);
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
test("sources: the note counts the files the record lists", async () => {
|
|
564
|
+
const two: Recorded = {
|
|
565
|
+
...RECORDED,
|
|
566
|
+
record: {
|
|
567
|
+
...RECORDED.record,
|
|
568
|
+
inputs: [
|
|
569
|
+
{ path: "paper.tex", role: "body" },
|
|
570
|
+
{ path: "sections/intro.tex", role: "body" },
|
|
571
|
+
],
|
|
572
|
+
},
|
|
573
|
+
};
|
|
574
|
+
const { r } = await build(
|
|
575
|
+
paper(),
|
|
576
|
+
{},
|
|
577
|
+
{ recordSources: spyRecorder(two).recordSources },
|
|
578
|
+
);
|
|
579
|
+
assert.ok(
|
|
580
|
+
r.notes?.[0]?.endsWith("; sources: 2 files read → _build/sources.json"),
|
|
581
|
+
JSON.stringify(r.notes),
|
|
582
|
+
);
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
test("sources: a build that fails records nothing", async () => {
|
|
586
|
+
const spy = spyRecorder(RECORDED);
|
|
587
|
+
const { r } = await build(
|
|
588
|
+
paper(),
|
|
589
|
+
{ latex: 1 },
|
|
590
|
+
{ recordSources: spy.recordSources },
|
|
591
|
+
);
|
|
592
|
+
assert.equal(r.status, "failed");
|
|
593
|
+
assert.deepEqual(spy.asked, []);
|
|
594
|
+
});
|