paperlint 7.2.0 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +9 -25
- package/CONTRIBUTING.md +5 -4
- package/README.md +4 -3
- package/dist/adapters/bibtex/index.d.ts +3 -0
- package/dist/adapters/bibtex/index.d.ts.map +1 -0
- package/dist/adapters/bibtex/index.js +103 -0
- package/dist/adapters/bibtex/index.js.map +1 -0
- package/dist/adapters/latex/filecontents.d.ts +33 -0
- package/dist/adapters/latex/filecontents.d.ts.map +1 -0
- package/dist/adapters/latex/filecontents.js +65 -0
- package/dist/adapters/latex/filecontents.js.map +1 -0
- package/dist/adapters/latex/includes.d.ts +3 -3
- package/dist/adapters/latex/includes.d.ts.map +1 -1
- package/dist/adapters/latex/includes.js +18 -10
- package/dist/adapters/latex/includes.js.map +1 -1
- package/dist/adapters/latex/index.d.ts.map +1 -1
- package/dist/adapters/latex/index.js +2 -0
- package/dist/adapters/latex/index.js.map +1 -1
- package/dist/adapters/latex/nodes.d.ts +12 -0
- package/dist/adapters/latex/nodes.d.ts.map +1 -1
- package/dist/adapters/latex/nodes.js +39 -0
- package/dist/adapters/latex/nodes.js.map +1 -1
- package/dist/adapters/latex/parse.d.ts +7 -1
- package/dist/adapters/latex/parse.d.ts.map +1 -1
- package/dist/adapters/latex/parse.js +55 -6
- package/dist/adapters/latex/parse.js.map +1 -1
- package/dist/adapters/memory/index.d.ts +3 -0
- package/dist/adapters/memory/index.d.ts.map +1 -1
- package/dist/adapters/memory/index.js +9 -0
- package/dist/adapters/memory/index.js.map +1 -1
- package/dist/adapters/references/index.d.ts.map +1 -1
- package/dist/adapters/references/index.js +27 -24
- package/dist/adapters/references/index.js.map +1 -1
- package/dist/adapters/sources-record/index.d.ts +9 -0
- package/dist/adapters/sources-record/index.d.ts.map +1 -0
- package/dist/adapters/sources-record/index.js +68 -0
- package/dist/adapters/sources-record/index.js.map +1 -0
- package/dist/adapters/tex-output/blg.d.ts +9 -0
- package/dist/adapters/tex-output/blg.d.ts.map +1 -0
- package/dist/adapters/tex-output/blg.js +20 -0
- package/dist/adapters/tex-output/blg.js.map +1 -0
- package/dist/adapters/tex-output/fls.d.ts +8 -0
- package/dist/adapters/tex-output/fls.d.ts.map +1 -0
- package/dist/adapters/tex-output/fls.js +16 -0
- package/dist/adapters/tex-output/fls.js.map +1 -0
- package/dist/adapters/tex-output/index.d.ts +7 -0
- package/dist/adapters/tex-output/index.d.ts.map +1 -0
- package/dist/adapters/tex-output/index.js +7 -0
- package/dist/adapters/tex-output/index.js.map +1 -0
- package/dist/bib-rules.d.ts +47 -0
- package/dist/bib-rules.d.ts.map +1 -0
- package/dist/bib-rules.js +127 -0
- package/dist/bib-rules.js.map +1 -0
- package/dist/build.d.ts +29 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +41 -30
- package/dist/build.js.map +1 -1
- package/dist/claim-provenance.d.ts +2 -2
- package/dist/cli.d.ts +9 -5
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +43 -68
- package/dist/cli.js.map +1 -1
- package/dist/domain/paper-source.d.ts +1 -10
- package/dist/domain/paper-source.d.ts.map +1 -1
- package/dist/domain/paper-source.js +34 -24
- package/dist/domain/paper-source.js.map +1 -1
- package/dist/domain/paper-sources.d.ts +72 -0
- package/dist/domain/paper-sources.d.ts.map +1 -0
- package/dist/domain/paper-sources.js +18 -0
- package/dist/domain/paper-sources.js.map +1 -0
- package/dist/domain/sources-record.d.ts +102 -0
- package/dist/domain/sources-record.d.ts.map +1 -0
- package/dist/domain/sources-record.js +38 -0
- package/dist/domain/sources-record.js.map +1 -0
- package/dist/domain/tex-run.d.ts +25 -0
- package/dist/domain/tex-run.d.ts.map +1 -0
- package/dist/domain/tex-run.js +79 -0
- package/dist/domain/tex-run.js.map +1 -0
- package/dist/eslint-rules/latex-language.d.ts.map +1 -1
- package/dist/eslint-rules/latex-language.js +10 -13
- package/dist/eslint-rules/latex-language.js.map +1 -1
- package/dist/eslint-rules/paper-context.d.ts.map +1 -1
- package/dist/eslint-rules/paper-context.js +6 -5
- package/dist/eslint-rules/paper-context.js.map +1 -1
- package/dist/eslint-rules/paper-typography.d.ts +0 -9
- package/dist/eslint-rules/paper-typography.d.ts.map +1 -1
- package/dist/eslint-rules/paper-typography.js +34 -96
- package/dist/eslint-rules/paper-typography.js.map +1 -1
- package/dist/eslint-rules/papers.d.ts +1 -3
- package/dist/eslint-rules/papers.d.ts.map +1 -1
- package/dist/eslint-rules/papers.js +2 -4
- package/dist/eslint-rules/papers.js.map +1 -1
- package/dist/eslint-rules/tex-build.d.ts.map +1 -1
- package/dist/eslint-rules/tex-build.js +8 -25
- package/dist/eslint-rules/tex-build.js.map +1 -1
- package/dist/init.d.ts +1 -4
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +3 -9
- package/dist/init.js.map +1 -1
- package/dist/latex-log.d.ts +10 -0
- package/dist/latex-log.d.ts.map +1 -1
- package/dist/latex-log.js +10 -0
- package/dist/latex-log.js.map +1 -1
- package/dist/latex-loop.d.ts +2 -0
- package/dist/latex-loop.d.ts.map +1 -1
- package/dist/latex-loop.js.map +1 -1
- package/dist/lib/markdown.d.ts +5 -81
- package/dist/lib/markdown.d.ts.map +1 -1
- package/dist/lib/markdown.js +5 -116
- package/dist/lib/markdown.js.map +1 -1
- package/dist/lib/paper-config.d.ts.map +1 -1
- package/dist/lib/paper-config.js +0 -2
- package/dist/lib/paper-config.js.map +1 -1
- package/dist/lib/skill-eval-fixture.d.ts +6 -4
- package/dist/lib/skill-eval-fixture.d.ts.map +1 -1
- package/dist/lib/skill-eval-fixture.js +25 -18
- package/dist/lib/skill-eval-fixture.js.map +1 -1
- package/dist/new-paper.d.ts +5 -11
- package/dist/new-paper.d.ts.map +1 -1
- package/dist/new-paper.js +12 -23
- package/dist/new-paper.js.map +1 -1
- package/dist/package-dirs.d.ts.map +1 -1
- package/dist/package-dirs.js +3 -2
- package/dist/package-dirs.js.map +1 -1
- package/dist/paper-files.d.ts +4 -4
- package/dist/paper-includes.d.ts +4 -7
- package/dist/paper-includes.d.ts.map +1 -1
- package/dist/paper-includes.js +29 -21
- package/dist/paper-includes.js.map +1 -1
- package/dist/paper-prose.d.ts +17 -0
- package/dist/paper-prose.d.ts.map +1 -0
- package/dist/paper-prose.js +46 -0
- package/dist/paper-prose.js.map +1 -0
- package/dist/paper-record.d.ts +25 -0
- package/dist/paper-record.d.ts.map +1 -0
- package/dist/paper-record.js +39 -0
- package/dist/paper-record.js.map +1 -0
- package/dist/paper-sources.d.ts +66 -0
- package/dist/paper-sources.d.ts.map +1 -0
- package/dist/paper-sources.js +92 -0
- package/dist/paper-sources.js.map +1 -0
- package/dist/ports/bib-reader.d.ts +18 -0
- package/dist/ports/bib-reader.d.ts.map +1 -0
- package/dist/ports/bib-reader.js +2 -0
- package/dist/ports/bib-reader.js.map +1 -0
- package/dist/ports/check-references.d.ts +9 -4
- package/dist/ports/check-references.d.ts.map +1 -1
- package/dist/ports/latex.d.ts +15 -0
- package/dist/ports/latex.d.ts.map +1 -1
- package/dist/ports/sources-codec.d.ts +11 -0
- package/dist/ports/sources-codec.d.ts.map +1 -0
- package/dist/ports/sources-codec.js +2 -0
- package/dist/ports/sources-codec.js.map +1 -0
- package/dist/ports/tex-output.d.ts +17 -0
- package/dist/ports/tex-output.d.ts.map +1 -0
- package/dist/ports/tex-output.js +2 -0
- package/dist/ports/tex-output.js.map +1 -0
- package/dist/recorded-bibliography.d.ts +89 -0
- package/dist/recorded-bibliography.d.ts.map +1 -0
- package/dist/recorded-bibliography.js +136 -0
- package/dist/recorded-bibliography.js.map +1 -0
- package/dist/reference-rules.d.ts +5 -5
- package/dist/reference-rules.d.ts.map +1 -1
- package/dist/reference-rules.js +150 -77
- package/dist/reference-rules.js.map +1 -1
- package/dist/references.d.ts +71 -16
- package/dist/references.d.ts.map +1 -1
- package/dist/references.js +110 -26
- package/dist/references.js.map +1 -1
- package/dist/register-bands.d.ts +3 -3
- package/dist/register-bands.d.ts.map +1 -1
- package/dist/register-bands.js.map +1 -1
- package/dist/register.d.ts +2 -2
- package/dist/sources-record.d.ts +38 -0
- package/dist/sources-record.d.ts.map +1 -0
- package/dist/sources-record.js +123 -0
- package/dist/sources-record.js.map +1 -0
- package/dist/sources-rules.d.ts +44 -0
- package/dist/sources-rules.d.ts.map +1 -0
- package/dist/sources-rules.js +76 -0
- package/dist/sources-rules.js.map +1 -0
- package/dist/structure.d.ts.map +1 -1
- package/dist/structure.js +4 -3
- package/dist/structure.js.map +1 -1
- package/dist/tex-paper.d.ts +10 -15
- package/dist/tex-paper.d.ts.map +1 -1
- package/dist/tex-paper.js +24 -40
- package/dist/tex-paper.js.map +1 -1
- package/dist/tex-venue-rules.d.ts +3 -3
- package/dist/tex-venue-rules.d.ts.map +1 -1
- package/dist/tex-venue-rules.js.map +1 -1
- package/dist/types.d.ts +0 -2
- package/dist/types.d.ts.map +1 -1
- package/docs/configuration.md +18 -12
- package/docs/design/paper-sources.md +197 -0
- package/docs/e2e.md +4 -6
- package/docs/package-shape-options.md +1 -1
- package/docs/prior-art/paper-folder-scaffolding.md +24 -29
- package/docs/references.md +34 -8
- package/docs/rules/bib/reachable-entry.md +64 -0
- package/docs/rules/paper/sources-fresh.md +81 -0
- package/docs/rules/tex/claim-provenance.md +0 -1
- package/docs/rules.md +62 -61
- package/eslint-rules/latex-language.harness.mjs +3 -3
- package/eslint-rules/latex-language.ts +10 -13
- package/eslint-rules/paper-context.ts +6 -5
- package/eslint-rules/paper-typography.test.mjs +29 -157
- package/eslint-rules/paper-typography.ts +35 -104
- package/eslint-rules/papers.harness.mjs +1 -1
- package/eslint-rules/papers.ts +2 -4
- package/eslint-rules/tex-build.harness.mjs +2 -2
- package/eslint-rules/tex-build.ts +8 -25
- package/eslint.config.mjs +22 -1
- package/fixtures/build-e2e/aidc/paper.tex +1 -1
- package/fixtures/build-e2e/anon-ack/paper.tex +1 -1
- package/fixtures/build-e2e/anon-author/paper.tex +1 -1
- package/fixtures/build-e2e/anon-bib/paper.tex +1 -1
- package/fixtures/build-e2e/anon-href/paper.tex +1 -1
- package/fixtures/build-e2e/anon-macro/paper.tex +1 -1
- package/fixtures/build-e2e/anon-pdfauthor/paper.tex +1 -1
- package/fixtures/paper-sources/p1/paper.tex +30 -0
- package/fixtures/paper-sources/p1/refs.bib +6 -0
- package/fixtures/paper-sources/p1/sections/dead.tex +6 -0
- package/fixtures/paper-sources/p1/sections/intro.tex +7 -0
- package/fixtures/paper-sources/p1/sections/parked.tex +6 -0
- package/fixtures/paper-sources/p1/tex-truth.json +37 -0
- package/fixtures/paper-sources/p1/versions/2026-01-01-review.tex +4 -0
- package/fixtures/paper-sources/v1-stale/paper.tex +14 -0
- package/fixtures/paper-sources/v1-stale/refs.bib +6 -0
- package/fixtures/paper-sources/v1-stale/tex-truth.json +32 -0
- package/fixtures/paper-sources/v10-ifthenelse/anon.bib +1 -0
- package/fixtures/paper-sources/v10-ifthenelse/paper.tex +9 -0
- package/fixtures/paper-sources/v10-ifthenelse/real.bib +1 -0
- package/fixtures/paper-sources/v10-ifthenelse/tex-truth.json +28 -0
- package/fixtures/paper-sources/v11-two-blocks/paper.tex +12 -0
- package/fixtures/paper-sources/v11-two-blocks/tex-truth.json +30 -0
- package/fixtures/paper-sources/v13-percent-text/paper.tex +6 -0
- package/fixtures/paper-sources/v13-percent-text/refs.bib +3 -0
- package/fixtures/paper-sources/v13-percent-text/tex-truth.json +30 -0
- package/fixtures/paper-sources/v15-after-end/paper.tex +6 -0
- package/fixtures/paper-sources/v15-after-end/refs.bib +1 -0
- package/fixtures/paper-sources/v15-after-end/tex-truth.json +17 -0
- package/fixtures/paper-sources/v16-included-block/bibblock.tex +3 -0
- package/fixtures/paper-sources/v16-included-block/paper.tex +7 -0
- package/fixtures/paper-sources/v16-included-block/refs.bib +1 -0
- package/fixtures/paper-sources/v16-included-block/tex-truth.json +35 -0
- package/fixtures/paper-sources/v17-include-order/bibsetup.tex +1 -0
- package/fixtures/paper-sources/v17-include-order/first.bib +1 -0
- package/fixtures/paper-sources/v17-include-order/later.bib +1 -0
- package/fixtures/paper-sources/v17-include-order/paper.tex +7 -0
- package/fixtures/paper-sources/v17-include-order/tex-truth.json +40 -0
- package/fixtures/paper-sources/v2-overwrite/paper.tex +14 -0
- package/fixtures/paper-sources/v2-overwrite/refs.bib +6 -0
- package/fixtures/paper-sources/v2-overwrite/tex-truth.json +30 -0
- package/fixtures/paper-sources/v22-parked-thebibliography/paper.tex +7 -0
- package/fixtures/paper-sources/v22-parked-thebibliography/tex-truth.json +17 -0
- package/fixtures/paper-sources/v23-parked-include/old.bib +1 -0
- package/fixtures/paper-sources/v23-parked-include/paper.tex +7 -0
- package/fixtures/paper-sources/v23-parked-include/parked.tex +1 -0
- package/fixtures/paper-sources/v23-parked-include/refs.bib +1 -0
- package/fixtures/paper-sources/v23-parked-include/tex-truth.json +28 -0
- package/fixtures/paper-sources/v24-quoted-fields/paper.tex +6 -0
- package/fixtures/paper-sources/v24-quoted-fields/refs.bib +5 -0
- package/fixtures/paper-sources/v24-quoted-fields/tex-truth.json +32 -0
- package/fixtures/paper-sources/v25-string-differs/paper.tex +10 -0
- package/fixtures/paper-sources/v25-string-differs/refs.bib +2 -0
- package/fixtures/paper-sources/v25-string-differs/tex-truth.json +32 -0
- package/fixtures/paper-sources/v26-comment-in-names/one.bib +1 -0
- package/fixtures/paper-sources/v26-comment-in-names/paper.tex +7 -0
- package/fixtures/paper-sources/v26-comment-in-names/tex-truth.json +32 -0
- package/fixtures/paper-sources/v26-comment-in-names/two.bib +1 -0
- package/fixtures/paper-sources/v27-shared-string/abbreviations.bib +2 -0
- package/fixtures/paper-sources/v27-shared-string/paper.tex +6 -0
- package/fixtures/paper-sources/v27-shared-string/references.bib +6 -0
- package/fixtures/paper-sources/v27-shared-string/tex-truth.json +31 -0
- package/fixtures/paper-sources/v28-shared-string-block/abbreviations.bib +2 -0
- package/fixtures/paper-sources/v28-shared-string-block/paper.tex +14 -0
- package/fixtures/paper-sources/v28-shared-string-block/tex-truth.json +33 -0
- package/fixtures/paper-sources/v29-generated-input/paper.tex +9 -0
- package/fixtures/paper-sources/v29-generated-input/tex-truth.json +24 -0
- package/fixtures/paper-sources/v3-declared/paper.bib +6 -0
- package/fixtures/paper-sources/v3-declared/paper.tex +6 -0
- package/fixtures/paper-sources/v3-declared/tex-truth.json +28 -0
- package/fixtures/paper-sources/v30-name-next-line/paper.tex +14 -0
- package/fixtures/paper-sources/v30-name-next-line/tex-truth.json +30 -0
- package/fixtures/paper-sources/v4-commented/paper.tex +14 -0
- package/fixtures/paper-sources/v4-commented/refs.bib +6 -0
- package/fixtures/paper-sources/v4-commented/tex-truth.json +28 -0
- package/fixtures/paper-sources/v5-percent-entry/paper.tex +20 -0
- package/fixtures/paper-sources/v5-percent-entry/tex-truth.json +37 -0
- package/fixtures/paper-sources/v6-unclosed/paper.tex +6 -0
- package/fixtures/paper-sources/v6-unclosed/refs.bib +5 -0
- package/fixtures/paper-sources/v6-unclosed/tex-truth.json +37 -0
- package/fixtures/paper-sources/v8-jobname/paper.tex +9 -0
- package/fixtures/paper-sources/v8-jobname/tex-truth.json +30 -0
- package/fixtures/paper-sources/v9-two-bibliographies/one.bib +1 -0
- package/fixtures/paper-sources/v9-two-bibliographies/paper.tex +7 -0
- package/fixtures/paper-sources/v9-two-bibliographies/tex-truth.json +35 -0
- package/fixtures/paper-sources/v9-two-bibliographies/two.bib +1 -0
- package/fixtures/paper-typography/clean-paper/paper.tex +1 -1
- package/fixtures/paper-typography/messy-paper/paper.tex +1 -1
- package/hooks/hooks.harness.ts +15 -15
- package/hooks/paper-edit-guard.hook.mjs +5 -8
- package/hooks/paper-edit-guard.test.ts +12 -3
- package/hooks/paper-skills-nudge.hook.mjs +2 -3
- package/hooks/paper-skills-nudge.test.ts +13 -3
- package/hooks/paper-status-gates.hook.mjs +2 -2
- package/hooks/paper-status-gates.test.ts +14 -2
- package/lib/markdown.test.mjs +5 -60
- package/lib/markdown.ts +9 -136
- package/lib/paper-config.ts +0 -2
- package/lib/skill-checks.test.mjs +4 -1
- package/lib/skill-eval-fixture.ts +25 -18
- package/package.json +2 -12
- package/presets/realm.jsonc +1 -1
- package/scripts/check.test.ts +23 -1
- package/scripts/check.ts +7 -3
- package/scripts/coverage-config.test.ts +19 -10
- package/scripts/harness-api.frozen.json +0 -5
- package/scripts/layer-legacy-frozen.test.ts +41 -2
- package/scripts/layer-legacy-frozen.ts +31 -2
- package/skills/cold-read-diff/SKILL.md +3 -3
- package/skills/cold-read-diff/SKILL.md.spec.ts +2 -2
- package/skills/grade-paper-writing/SKILL.md +15 -22
- package/skills/grade-paper-writing/SKILL.md.spec.ts +14 -21
- package/skills/grade-paper-writing/__snapshots__/prose-lint.test.ts.snap +40 -26
- package/skills/grade-paper-writing/prose-lint.mjs +117 -278
- package/skills/grade-paper-writing/prose-lint.test.ts +230 -79
- package/skills/paper-adversarial-review/SKILL.md +2 -2
- package/skills/paper-adversarial-review/SKILL.md.spec.ts +1 -1
- package/skills/paper-pipeline/SKILL.md +9 -12
- package/skills/paper-pipeline/SKILL.md.spec.ts +8 -11
- package/skills/paper-pipeline/description-language.eval.mjs +25 -15
- package/skills/paper-pipeline/framing-vs-vocabulary.eval.mjs +25 -15
- package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +25 -15
- package/skills/paper-pipeline/pipeline-firing.eval.mjs +27 -16
- package/skills/paper-pipeline/pipeline-language.eval.mjs +25 -15
- package/skills/paper-pipeline/references/acceptance-gate.md +16 -20
- package/skills/paper-pipeline/references/artifact-checklist.md +4 -5
- package/skills/paper-pipeline/references/pipeline-status-template.md +1 -1
- package/skills/paper-pipeline/scripts/README.md +0 -4
- package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +91 -105
- package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +85 -241
- package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +166 -153
- package/skills/paper-pipeline/scripts/generated-code.harness.mjs +3 -3
- package/skills/paper-status/SKILL.md +2 -2
- package/skills/paper-status/SKILL.md.spec.ts +1 -1
- package/skills/render-paper/SKILL.md +2 -2
- package/skills/render-paper/SKILL.md.spec.ts +1 -1
- package/skills/render-paper/extract-pdf-facts.mjs +2 -2
- package/skills/tighten-paper/SKILL.md +29 -42
- package/skills/tighten-paper/SKILL.md.spec.ts +28 -41
- package/skills/verify-citations/SKILL.md +7 -4
- package/skills/verify-citations/SKILL.md.spec.ts +6 -3
- package/skills/verify-citations/scripts/bib-authors.check.test.mjs +17 -32
- package/skills/verify-citations/scripts/bib-authors.cli.test.ts +138 -43
- package/skills/verify-citations/scripts/bib-authors.d.mts +22 -2
- package/skills/verify-citations/scripts/bib-authors.mjs +54 -193
- package/skills/verify-citations/scripts/bib-authors.test.mjs +1 -44
- package/skills/verify-citations/scripts/verify-cites.cli.test.ts +16 -0
- package/skills/verify-citations/scripts/verify-cites.d.mts +19 -3
- package/skills/verify-citations/scripts/verify-cites.mjs +42 -108
- package/skills/verify-citations/scripts/verify-cites.test.mjs +23 -36
- package/src/adapters/bibtex/index.test.ts +163 -0
- package/src/adapters/bibtex/index.ts +155 -0
- package/src/adapters/latex/filecontents.ts +114 -0
- package/src/adapters/latex/includes.ts +18 -10
- package/src/adapters/latex/index.test.ts +73 -0
- package/src/adapters/latex/index.ts +2 -0
- package/src/adapters/latex/nodes.ts +64 -0
- package/src/adapters/latex/parse.test.ts +38 -0
- package/src/adapters/latex/parse.ts +67 -6
- package/src/adapters/memory/index.ts +17 -0
- package/src/adapters/references/index.test.ts +75 -21
- package/src/adapters/references/index.ts +37 -32
- package/src/adapters/sources-record/index.test.ts +87 -0
- package/src/adapters/sources-record/index.ts +82 -0
- package/src/adapters/tex-output/blg.ts +27 -0
- package/src/adapters/tex-output/fls.ts +24 -0
- package/src/adapters/tex-output/index.test.ts +171 -0
- package/src/adapters/tex-output/index.ts +12 -0
- package/src/bib-rules.test.ts +356 -0
- package/src/bib-rules.ts +228 -0
- package/src/bibliography-at.test.ts +286 -0
- package/src/build.harness.mjs +2 -2
- package/src/build.test.ts +211 -1
- package/src/build.ts +101 -41
- package/src/claim-provenance.test.ts +33 -7
- package/src/claim-provenance.ts +2 -2
- package/src/cli.harness.mjs +46 -56
- package/src/cli.test.ts +44 -48
- package/src/cli.ts +51 -82
- package/src/domain/paper-source.test.ts +58 -20
- package/src/domain/paper-source.ts +38 -42
- package/src/domain/paper-sources.ts +94 -0
- package/src/domain/sources-record.test.ts +71 -0
- package/src/domain/sources-record.ts +136 -0
- package/src/domain/tex-run.test.ts +228 -0
- package/src/domain/tex-run.ts +139 -0
- package/src/folder-venue-rule.test.ts +1 -1
- package/src/heading-case.test.ts +24 -10
- package/src/init.test.ts +6 -7
- package/src/init.ts +3 -22
- package/src/latex-log.ts +20 -0
- package/src/latex-loop.harness.ts +1 -0
- package/src/latex-loop.ts +2 -0
- package/src/lint-fix.test.ts +10 -4
- package/src/linted-set.test.ts +9 -1
- package/src/new-paper.harness.mjs +12 -30
- package/src/new-paper.test.ts +15 -11
- package/src/new-paper.ts +11 -30
- package/src/package-dirs.ts +3 -2
- package/src/paper-files.test.ts +5 -4
- package/src/paper-files.ts +4 -4
- package/src/paper-includes.test.ts +157 -38
- package/src/paper-includes.ts +39 -35
- package/src/paper-prose.test.ts +78 -0
- package/src/paper-prose.ts +67 -0
- package/src/paper-record.test.ts +173 -0
- package/src/paper-record.ts +66 -0
- package/src/paper-settings-commands.test.ts +5 -2
- package/src/paper-sources.ts +151 -0
- package/src/parse-args.test.ts +0 -1
- package/src/ports/bib-reader.ts +27 -0
- package/src/ports/check-references.ts +7 -3
- package/src/ports/latex.ts +19 -0
- package/src/ports/sources-codec.ts +11 -0
- package/src/ports/tex-output.ts +17 -0
- package/src/recorded-bibliography.ts +297 -0
- package/src/reference-rules.ts +217 -104
- package/src/references.test.ts +520 -177
- package/src/references.ts +185 -37
- package/src/register-bands.test.ts +5 -1
- package/src/register-bands.ts +3 -5
- package/src/register.test.ts +6 -1
- package/src/register.ts +2 -2
- package/src/rule-cases.test.ts +68 -12
- package/src/rule-docs.test.ts +0 -1
- package/src/sources-record.test.ts +308 -0
- package/src/sources-record.ts +231 -0
- package/src/sources-rules.test.ts +152 -0
- package/src/sources-rules.ts +117 -0
- package/src/structure.harness.ts +5 -11
- package/src/structure.ts +4 -3
- package/src/tex-paper.test.ts +148 -78
- package/src/tex-paper.ts +31 -61
- package/src/tex-venue-rules.test.ts +36 -8
- package/src/tex-venue-rules.ts +3 -5
- package/src/types.ts +0 -2
- package/dist/eslint-rules/bib-reachable-entry.d.ts +0 -22
- package/dist/eslint-rules/bib-reachable-entry.d.ts.map +0 -1
- package/dist/eslint-rules/bib-reachable-entry.js +0 -75
- package/dist/eslint-rules/bib-reachable-entry.js.map +0 -1
- package/eslint-rules/bib-reachable-entry.ts +0 -76
- package/fixtures/real-markdown-paper/baseline.json +0 -22
- package/fixtures/real-markdown-paper/baseline.ts +0 -70
- package/skills/paper-pipeline/PIPELINE-MAP.md +0 -381
- package/skills/paper-pipeline/pipeline-firing.baseline.json +0 -270
- package/skills/paper-pipeline/references/sweep-2026-08-09-ideal-pipeline.md +0 -585
- package/skills/paper-pipeline/scripts/artifact-coverage.harness.mjs +0 -492
- package/skills/paper-pipeline/scripts/artifact-coverage.mjs +0 -397
- package/skills/paper-pipeline/scripts/artifact-coverage.test.ts +0 -86
- package/skills/paper-pipeline/scripts/check-provenance.mjs +0 -184
- package/skills/paper-pipeline/scripts/population-map.harness.mjs +0 -135
- package/skills/paper-pipeline/scripts/population-map.mjs +0 -188
- package/skills/paper-pipeline/scripts/population-map.test.mjs +0 -154
- package/skills/paper-pipeline/scripts/provenance.harness.mjs +0 -266
- package/skills/paper-pipeline/scripts/round-diff.harness.ts +0 -907
- package/skills/paper-pipeline/scripts/round-diff.mjs +0 -583
- package/skills/paper-pipeline/scripts/round-diff.test.mjs +0 -159
- package/skills/tighten-paper/__snapshots__/structure.test.ts.snap +0 -133
- package/skills/tighten-paper/structure.mjs +0 -364
- package/skills/tighten-paper/structure.test.ts +0 -117
- package/templates/paper/paper.md +0 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* extract-ref-facts.mjs beyond its harness: the edges of each reader (
|
|
3
|
-
*
|
|
2
|
+
* extract-ref-facts.mjs beyond its harness: the edges of each reader (.bib, CrossRef, arXiv,
|
|
3
|
+
* cache) and the command itself — sources, flags, the network loop (with `fetch` stubbed in
|
|
4
4
|
* the child process), and the refusals.
|
|
5
5
|
*/
|
|
6
6
|
import assert from "node:assert/strict";
|
|
@@ -9,18 +9,18 @@ import { dirname, join } from "node:path";
|
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
import { test } from "vitest";
|
|
11
11
|
import { runNode, useTempDir, writeTree } from "../../../test/support.ts";
|
|
12
|
+
import { plantedOnDisk, recordOnDisk } from "../../../test/recorded-fixture.ts";
|
|
13
|
+
import { bibliographyUnreadWhy } from "#src/paper-sources";
|
|
12
14
|
import {
|
|
15
|
+
bibliographyFrom,
|
|
13
16
|
buildFacts,
|
|
14
17
|
extractIds,
|
|
15
18
|
loadCache,
|
|
16
19
|
loadEntries,
|
|
17
|
-
parseBib,
|
|
18
|
-
parseMarkdownRefs,
|
|
19
20
|
primaryOf,
|
|
20
21
|
readArxiv,
|
|
21
22
|
readCrossref,
|
|
22
23
|
recordFrom,
|
|
23
|
-
resolveSource,
|
|
24
24
|
} from "./extract-ref-facts.mjs";
|
|
25
25
|
|
|
26
26
|
const SCRIPT = join(
|
|
@@ -29,72 +29,13 @@ const SCRIPT = join(
|
|
|
29
29
|
);
|
|
30
30
|
const root = useTempDir("extract-ref-facts-");
|
|
31
31
|
|
|
32
|
-
test("
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"",
|
|
38
|
-
"1. A. Author, B. Author. *A Title.* Venue,",
|
|
39
|
-
" continued 2021.",
|
|
40
|
-
"2. *Untitled Authors.* 2019 note",
|
|
41
|
-
"3. C. Writer 2018. *Late Year.* Venue",
|
|
42
|
-
"4. No title here at all",
|
|
43
|
-
"",
|
|
44
|
-
"## References",
|
|
45
|
-
"5. Never read. *X.* 2000",
|
|
46
|
-
].join("\n"),
|
|
47
|
-
);
|
|
48
|
-
assert.deepEqual(
|
|
49
|
-
refs.map(({ n, line, authors, title, year, venue_text }) => ({
|
|
50
|
-
n,
|
|
51
|
-
line,
|
|
52
|
-
authors,
|
|
53
|
-
title,
|
|
54
|
-
year,
|
|
55
|
-
venue_text,
|
|
56
|
-
})),
|
|
57
|
-
[
|
|
58
|
-
{
|
|
59
|
-
n: 1,
|
|
60
|
-
line: 3,
|
|
61
|
-
authors: ["A. Author", "B. Author"],
|
|
62
|
-
title: "A Title",
|
|
63
|
-
year: "2021",
|
|
64
|
-
venue_text: "Venue, continued 2021.",
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
n: 2,
|
|
68
|
-
line: 5,
|
|
69
|
-
authors: [],
|
|
70
|
-
title: "Untitled Authors",
|
|
71
|
-
year: "2019",
|
|
72
|
-
venue_text: "2019 note",
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
n: 3,
|
|
76
|
-
line: 6,
|
|
77
|
-
authors: ["C. Writer 2018"],
|
|
78
|
-
title: "Late Year",
|
|
79
|
-
year: "2018",
|
|
80
|
-
venue_text: "Venue",
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
n: 4,
|
|
84
|
-
line: 7,
|
|
85
|
-
authors: [],
|
|
86
|
-
title: null,
|
|
87
|
-
year: null,
|
|
88
|
-
venue_text: "No title here at all",
|
|
89
|
-
},
|
|
90
|
-
],
|
|
91
|
-
);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test(".bib: a key the text spells with spaces has no line; a missing title or year is null, not empty", async () => {
|
|
95
|
-
const [entry] = await parseBib(
|
|
96
|
-
"@misc{ spaced ,\n note={see arXiv:2101.00001}\n}\n",
|
|
32
|
+
test(".bib: an entry's line is where its `@` stands; a missing title or year is null, not empty", () => {
|
|
33
|
+
const bib = join(root, "lines.bib");
|
|
34
|
+
writeFileSync(
|
|
35
|
+
bib,
|
|
36
|
+
"% a comment\n\n@misc{ spaced ,\n note={see arXiv:2101.00001}\n}\n",
|
|
97
37
|
);
|
|
38
|
+
const [entry] = loadEntries(bibliographyFrom(bib).texts[0]);
|
|
98
39
|
assert.deepEqual(
|
|
99
40
|
{
|
|
100
41
|
line: entry.line,
|
|
@@ -104,7 +45,7 @@ test(".bib: a key the text spells with spaces has no line; a missing title or ye
|
|
|
104
45
|
raw: entry.raw,
|
|
105
46
|
},
|
|
106
47
|
{
|
|
107
|
-
line:
|
|
48
|
+
line: 3,
|
|
108
49
|
title: null,
|
|
109
50
|
year: null,
|
|
110
51
|
authors: [],
|
|
@@ -113,52 +54,6 @@ test(".bib: a key the text spells with spaces has no line; a missing title or ye
|
|
|
113
54
|
);
|
|
114
55
|
});
|
|
115
56
|
|
|
116
|
-
test(".bib from a parser that returns no fields, or list and number fields: joined and printed, not dropped", async () => {
|
|
117
|
-
const load = async () => ({
|
|
118
|
-
parse: () => ({
|
|
119
|
-
entries: [
|
|
120
|
-
{ key: "bare" },
|
|
121
|
-
{
|
|
122
|
-
key: "odd",
|
|
123
|
-
fields: {
|
|
124
|
-
title: ["Split", "Title"],
|
|
125
|
-
year: 2021,
|
|
126
|
-
note: null,
|
|
127
|
-
author: [{ firstName: "Ada", lastName: "Lovelace" }],
|
|
128
|
-
},
|
|
129
|
-
},
|
|
130
|
-
],
|
|
131
|
-
}),
|
|
132
|
-
});
|
|
133
|
-
const [bare, odd] = await parseBib("@misc{bare,}\n@misc{odd,}\n", { load });
|
|
134
|
-
assert.deepEqual(
|
|
135
|
-
[
|
|
136
|
-
bare,
|
|
137
|
-
{ title: odd.title, year: odd.year, authors: odd.authors, raw: odd.raw },
|
|
138
|
-
],
|
|
139
|
-
[
|
|
140
|
-
{
|
|
141
|
-
n: 1,
|
|
142
|
-
line: 1,
|
|
143
|
-
key: "bare",
|
|
144
|
-
raw: "",
|
|
145
|
-
authors: [],
|
|
146
|
-
truncated: false,
|
|
147
|
-
title: null,
|
|
148
|
-
year: null,
|
|
149
|
-
venue_text: "",
|
|
150
|
-
doi_field: null,
|
|
151
|
-
},
|
|
152
|
-
{
|
|
153
|
-
title: "Split Title",
|
|
154
|
-
year: "2021",
|
|
155
|
-
authors: ["Ada Lovelace"],
|
|
156
|
-
raw: "Ada Lovelace Split Title 2021",
|
|
157
|
-
},
|
|
158
|
-
],
|
|
159
|
-
);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
57
|
test("identifiers: a declared DOI field comes first; an entry without text fields has none", () => {
|
|
163
58
|
const ids = extractIds({
|
|
164
59
|
doi_field: "10.1111/field",
|
|
@@ -244,19 +139,22 @@ test("cache: no path and a corrupt file both read as empty", () => {
|
|
|
244
139
|
assert.deepEqual([loadCache(undefined), loadCache(corrupt)], [{}, {}]);
|
|
245
140
|
});
|
|
246
141
|
|
|
247
|
-
test("sources: a
|
|
142
|
+
test("sources: a .bib named is read as itself, as BibTeX; facts without a cache say so", async () => {
|
|
248
143
|
const bib = join(root, "one.bib");
|
|
249
144
|
writeFileSync(bib, "@misc{k, title={T}, year={2020}}\n");
|
|
250
|
-
|
|
251
|
-
|
|
145
|
+
const found = bibliographyFrom(bib);
|
|
146
|
+
assert.deepEqual([found.ok, found.texts.map((t) => t.path)], [true, [bib]]);
|
|
147
|
+
const entries = loadEntries(found.texts[0]);
|
|
252
148
|
const facts = buildFacts({
|
|
253
|
-
|
|
254
|
-
text: "",
|
|
149
|
+
texts: found.texts,
|
|
255
150
|
entries,
|
|
256
151
|
cache: {},
|
|
257
152
|
cachePath: null,
|
|
258
153
|
});
|
|
259
|
-
assert.deepEqual(
|
|
154
|
+
assert.deepEqual(
|
|
155
|
+
[facts.source_kind, facts.cache, facts.sources],
|
|
156
|
+
["bibtex", null, [bib]],
|
|
157
|
+
);
|
|
260
158
|
});
|
|
261
159
|
|
|
262
160
|
// ── the command ─────────────────────────────────────────────────────────────
|
|
@@ -268,24 +166,59 @@ const CROSSREF_OK = JSON.stringify({
|
|
|
268
166
|
issued: { "date-parts": [[2020]] },
|
|
269
167
|
},
|
|
270
168
|
});
|
|
169
|
+
/** A paper.tex declaring `\bibliography{refs}`, with `preamble` before its document. */
|
|
170
|
+
const declaring = (preamble = "") =>
|
|
171
|
+
`\\documentclass{article}\n${preamble}\\begin{document}x\\bibliography{refs}\\end{document}\n`;
|
|
271
172
|
writeTree(root, {
|
|
272
|
-
"paper/paper.
|
|
273
|
-
|
|
274
|
-
|
|
173
|
+
"paper/paper.tex": declaring(),
|
|
174
|
+
"paper/refs.bib":
|
|
175
|
+
"@misc{a, author={A. A}, title={Found}, note={doi:10.1234/ok}}\n" +
|
|
176
|
+
"@misc{b, author={B. B}, title={Lost}, note={doi:10.1234/down}}\n",
|
|
177
|
+
"empty/paper.tex": declaring(),
|
|
178
|
+
"empty/refs.bib": "% nothing cited\n",
|
|
179
|
+
"notes/notes.txt": "A. A. Found. doi:10.1234/ok\n",
|
|
275
180
|
// `fetch` in the child: 10.1234/down fails at the transport, anything else is a CrossRef hit.
|
|
276
181
|
"fetch.mjs":
|
|
277
182
|
`globalThis.fetch = async (url) => {\n` +
|
|
278
183
|
` if (url.includes("down")) throw new Error("network down");\n` +
|
|
279
184
|
` return { status: 200, text: async () => ${JSON.stringify(CROSSREF_OK)} };\n` +
|
|
280
185
|
`};\n`,
|
|
281
|
-
|
|
282
|
-
"
|
|
283
|
-
"
|
|
284
|
-
|
|
285
|
-
"
|
|
286
|
-
"import { register } from 'node:module';\n" +
|
|
287
|
-
"register('data:text/javascript,' + encodeURIComponent(\"export async function resolve(s, c, next) { if (s === '@retorquere/bibtex-parser') throw Object.assign(new Error('broken install'), { code: 'EACCES' }); return next(s, c); }\"));\n",
|
|
186
|
+
"bib/paper.tex": declaring(),
|
|
187
|
+
"switched/paper.tex":
|
|
188
|
+
"\\documentclass{article}\\newif\\ifanon\\begin{document}x\\ifanon\\bibliography{anon}\\else\\bibliography{refs}\\fi\\end{document}\n",
|
|
189
|
+
"switched/anon.bib": "@misc{anon1, title={A}, note={doi:10.1234/a}}\n",
|
|
190
|
+
"switched/refs.bib": "@misc{refs1, title={R}, note={doi:10.1234/r}}\n",
|
|
288
191
|
"bib/refs.bib": "@misc{k, title={T}, note={doi:10.1234/ok}}\n",
|
|
192
|
+
// The bibliography is embedded in paper.tex; the refs.bib beside it is what TeX wrote from the block.
|
|
193
|
+
"inline/paper.tex": declaring(
|
|
194
|
+
"\\begin{filecontents*}[overwrite]{refs.bib}\n" +
|
|
195
|
+
"@misc{a, author={A. A}, title={Found}, note={doi:10.1234/ok}}\n" +
|
|
196
|
+
"@misc{b, author={B. B}, title={Lost}, note={doi:10.1234/down}}\n" +
|
|
197
|
+
"\\end{filecontents*}\n",
|
|
198
|
+
),
|
|
199
|
+
"inline/refs.bib":
|
|
200
|
+
"@misc{a, author={A. A}, title={Found}, note={doi:10.1234/ok}}\n" +
|
|
201
|
+
"@misc{b, author={B. B}, title={Lost}, note={doi:10.1234/down}}\n",
|
|
202
|
+
// A paper that was never built has no record: nothing says which bibliography TeX reads.
|
|
203
|
+
"unbuilt/paper.tex": declaring(),
|
|
204
|
+
"unbuilt/refs.bib": "@misc{k, title={T}, note={doi:10.1234/ok}}\n",
|
|
205
|
+
});
|
|
206
|
+
/** What a build records for a paper whose bibliography is the file `db` the author keeps. */
|
|
207
|
+
const keeps = (db = "refs.bib") => ({
|
|
208
|
+
inputs: [
|
|
209
|
+
["paper.tex", "body"],
|
|
210
|
+
[db, "preamble"],
|
|
211
|
+
],
|
|
212
|
+
databases: [db],
|
|
213
|
+
});
|
|
214
|
+
for (const dir of ["paper", "empty", "bib"])
|
|
215
|
+
recordOnDisk(join(root, dir), keeps());
|
|
216
|
+
// bibtex opened refs.bib only: the switch took its `\else`.
|
|
217
|
+
recordOnDisk(join(root, "switched"), keeps());
|
|
218
|
+
recordOnDisk(join(root, "inline"), {
|
|
219
|
+
inputs: [["paper.tex", "body"]],
|
|
220
|
+
written: ["refs.bib"],
|
|
221
|
+
databases: ["refs.bib"],
|
|
289
222
|
});
|
|
290
223
|
const cli = (args, { cwd = root, nodeArgs = [] } = {}) =>
|
|
291
224
|
runNode(SCRIPT, args, {
|
|
@@ -309,7 +242,7 @@ test("online: hits are fetched and cached, a transport failure is named and stay
|
|
|
309
242
|
{
|
|
310
243
|
status: 0,
|
|
311
244
|
stdout:
|
|
312
|
-
"📚
|
|
245
|
+
"📚 refs.bib → paper/_build/refs.facts.json (2 entries, 2 identifiers, 1 resolved, +1 fetched)\n",
|
|
313
246
|
stderr: " … doi:10.1234/down: network down\n",
|
|
314
247
|
cached: ["doi:10.1234/ok"],
|
|
315
248
|
},
|
|
@@ -321,7 +254,7 @@ test("online again: cached keys are not refetched; --quiet hides the failure; --
|
|
|
321
254
|
assert.deepEqual(again, {
|
|
322
255
|
status: 0,
|
|
323
256
|
stdout:
|
|
324
|
-
"📚
|
|
257
|
+
"📚 refs.bib → paper/_build/refs.facts.json (2 entries, 2 identifiers, 1 resolved, all from cache)\n",
|
|
325
258
|
stderr: "",
|
|
326
259
|
});
|
|
327
260
|
const refresh = cli(["paper", "--refresh", "--quiet"], { nodeArgs: FETCH });
|
|
@@ -330,7 +263,7 @@ test("online again: cached keys are not refetched; --quiet hides the failure; --
|
|
|
330
263
|
|
|
331
264
|
test("the paper file itself as the target, with --cache, --out and --offline", () => {
|
|
332
265
|
const r = cli([
|
|
333
|
-
join(root, "paper/
|
|
266
|
+
join(root, "paper/refs.bib"),
|
|
334
267
|
"--offline",
|
|
335
268
|
`--cache=${join(root, "paper/repro/refs-cache.json")}`,
|
|
336
269
|
`--out=${join(root, "out/facts.json")}`,
|
|
@@ -338,7 +271,7 @@ test("the paper file itself as the target, with --cache, --out and --offline", (
|
|
|
338
271
|
assert.deepEqual(r, {
|
|
339
272
|
status: 0,
|
|
340
273
|
stdout:
|
|
341
|
-
"📚
|
|
274
|
+
"📚 refs.bib → out/facts.json (2 entries, 2 identifiers, 1 resolved, offline)\n",
|
|
342
275
|
stderr: "",
|
|
343
276
|
});
|
|
344
277
|
});
|
|
@@ -348,33 +281,113 @@ test("no target: the current directory", () => {
|
|
|
348
281
|
assert.equal(r.status, 0, r.stderr);
|
|
349
282
|
});
|
|
350
283
|
|
|
351
|
-
test("refusals:
|
|
352
|
-
|
|
284
|
+
test("refusals: no bibliography to read (in bibliographyAt's words), and zero entries (not written as a clean bibliography)", () => {
|
|
285
|
+
// The path → bibliography vocabulary is tested once, beside bibliographyAt (src/paper-sources.ts).
|
|
286
|
+
// Here: that the command speaks it, and stops.
|
|
287
|
+
const notes = join(root, "notes/notes.txt");
|
|
288
|
+
assert.deepEqual(cli([notes, "--offline"]), {
|
|
353
289
|
status: 1,
|
|
354
290
|
stdout: "",
|
|
355
|
-
stderr: `🛑
|
|
291
|
+
stderr: `🛑 ${bibliographyUnreadWhy({ kind: "not-bib-or-tex", path: notes })}.\n`,
|
|
356
292
|
});
|
|
357
293
|
assert.deepEqual(cli(["empty", "--offline"]), {
|
|
358
294
|
status: 1,
|
|
359
295
|
stdout: "",
|
|
360
296
|
stderr:
|
|
361
|
-
"🛑 parsed 0 entries out of empty/
|
|
297
|
+
"🛑 parsed 0 entries out of empty/refs.bib. Silence here would look like a clean bibliography.\n",
|
|
362
298
|
});
|
|
363
299
|
});
|
|
364
300
|
|
|
365
|
-
test(".
|
|
366
|
-
const
|
|
367
|
-
|
|
301
|
+
test("🔴 a paper directory whose paper.tex embeds its .bib: the entries are read from the block that wrote it, where the author edits them", () => {
|
|
302
|
+
const r = cli(["inline", "--offline"]);
|
|
303
|
+
// Guards: only materialized .bib files were looked for, so a paper with its bibliography in
|
|
304
|
+
// `filecontents` was refused before a build and read from a stale refs.bib after an edit.
|
|
305
|
+
assert.deepEqual(r, {
|
|
306
|
+
status: 0,
|
|
307
|
+
stdout:
|
|
308
|
+
"📚 paper.tex → inline/_build/refs.facts.json (2 entries, 2 identifiers, 0 resolved, offline)\n",
|
|
309
|
+
stderr: "",
|
|
368
310
|
});
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
311
|
+
const facts = JSON.parse(
|
|
312
|
+
readFileSync(join(root, "inline/_build/refs.facts.json"), "utf8"),
|
|
313
|
+
);
|
|
314
|
+
// Each entry's line is its line in paper.tex, where the author edits it.
|
|
315
|
+
assert.deepEqual(
|
|
316
|
+
facts.entries.map((e) => [e.file, e.key, e.line]),
|
|
317
|
+
[
|
|
318
|
+
["inline/paper.tex", "a", 3],
|
|
319
|
+
["inline/paper.tex", "b", 4],
|
|
320
|
+
],
|
|
373
321
|
);
|
|
374
|
-
|
|
375
|
-
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
test("a bibliography behind a switch: the database bibtex opened is read, not every candidate", () => {
|
|
325
|
+
assert.deepEqual(cli(["switched", "--offline"]), {
|
|
326
|
+
status: 0,
|
|
327
|
+
stdout:
|
|
328
|
+
"📚 refs.bib → switched/_build/refs.facts.json (1 entries, 1 identifiers, 0 resolved, offline)\n",
|
|
329
|
+
stderr: "",
|
|
376
330
|
});
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
test("🔴 a paper that was not built is refused — run `npx paperlint build` first — never read from its source", () => {
|
|
334
|
+
assert.deepEqual(cli(["unbuilt", "--offline"]), {
|
|
335
|
+
status: 1,
|
|
336
|
+
stdout: "",
|
|
337
|
+
stderr: `🛑 ${join(root, "unbuilt")} has not been built — run \`npx paperlint build\` first, which records the databases bibtex reads.\n`,
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test("an embedded entry's line is its line in paper.tex", () => {
|
|
342
|
+
const block = "@misc{ spaced ,\n note={arXiv:2101.00001}\n}\n";
|
|
343
|
+
writeTree(root, {
|
|
344
|
+
"spaced/paper.tex": declaring(
|
|
345
|
+
`\\begin{filecontents*}{refs.bib}\n${block}\\end{filecontents*}\n`,
|
|
346
|
+
),
|
|
347
|
+
"spaced/refs.bib": block,
|
|
348
|
+
});
|
|
349
|
+
recordOnDisk(join(root, "spaced"), {
|
|
350
|
+
inputs: [["paper.tex", "body"]],
|
|
351
|
+
written: ["refs.bib"],
|
|
352
|
+
databases: ["refs.bib"],
|
|
353
|
+
});
|
|
354
|
+
const found = bibliographyFrom(join(root, "spaced", "paper.tex"));
|
|
355
|
+
assert.deepEqual(
|
|
356
|
+
loadEntries(found.texts[0]).map((e) => e.line),
|
|
357
|
+
[3],
|
|
358
|
+
);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// ── the planted papers of fixtures/paper-sources, laid out as a build leaves them ─────
|
|
362
|
+
|
|
363
|
+
test.each([
|
|
364
|
+
// [paper, the file read, the keys read] — tex-truth.json beside each paper is TeX's own answer: the
|
|
365
|
+
// databases bibtex opened. A `.bib` TeX wrote from a block is read from the block (paper.tex).
|
|
366
|
+
["v1-stale", "refs.bib", ["stale2020"]],
|
|
367
|
+
["v2-overwrite", "paper.tex", ["inline2024"]],
|
|
368
|
+
["v3-declared", "paper.bib", ["declared2023"]],
|
|
369
|
+
["v4-commented", "refs.bib", ["stale2020"]],
|
|
370
|
+
// What bibtex reads beyond these — a malformed entry, or one behind `%` or inside `@comment{…}` —
|
|
371
|
+
// the build's post-build check names (src/references.ts).
|
|
372
|
+
["v5-percent-entry", "paper.tex", ["inline2024"]],
|
|
373
|
+
["v6-unclosed", "refs.bib", ["a1", "a3"]],
|
|
374
|
+
["v13-percent-text", "refs.bib", ["ok1"]],
|
|
375
|
+
["v8-jobname", "paper.tex", ["jkey"]],
|
|
376
|
+
])("%s: the bibliography bibtex opened", (paper, file, keys) => {
|
|
377
|
+
const dir = join(root, "planted", paper);
|
|
378
|
+
plantedOnDisk(dir, paper);
|
|
379
|
+
const out = join(root, "planted", `${paper}.json`);
|
|
380
|
+
const r = cli([
|
|
381
|
+
dir,
|
|
382
|
+
"--offline",
|
|
383
|
+
`--out=${out}`,
|
|
384
|
+
`--cache=${join(root, "planted", "cache.json")}`,
|
|
385
|
+
]);
|
|
386
|
+
assert.deepEqual([r.status, r.stderr], [0, ""]);
|
|
387
|
+
assert.match(r.stdout, new RegExp(`^📚 ${file} → `));
|
|
388
|
+
const facts = JSON.parse(readFileSync(out, "utf8"));
|
|
389
|
+
assert.deepEqual(
|
|
390
|
+
facts.entries.map((e) => e.key),
|
|
391
|
+
keys,
|
|
392
|
+
);
|
|
380
393
|
});
|
|
@@ -68,7 +68,7 @@ function run(dir, flagsOnly = true) {
|
|
|
68
68
|
function fixture({ scripts = {}, allow = null, bundle = {}, at = "repro" }) {
|
|
69
69
|
const d = join(tmp, `f${++n}`);
|
|
70
70
|
mkdirSync(d, { recursive: true });
|
|
71
|
-
writeFileSync(join(d, "paper.
|
|
71
|
+
writeFileSync(join(d, "paper.tex"), "\\section{x}\nprose\n");
|
|
72
72
|
if (at === null) return d;
|
|
73
73
|
mkdirSync(join(d, at), { recursive: true });
|
|
74
74
|
for (const [p, src] of Object.entries(scripts)) {
|
|
@@ -142,13 +142,13 @@ function fixture({ scripts = {}, allow = null, bundle = {}, at = "repro" }) {
|
|
|
142
142
|
fixture({
|
|
143
143
|
scripts: {
|
|
144
144
|
"wc.py":
|
|
145
|
-
'text = open("/home/example/papers/paper.
|
|
145
|
+
'text = open("/home/example/papers/paper.tex").read()\nprint(len(text.split()))\n',
|
|
146
146
|
},
|
|
147
147
|
}),
|
|
148
148
|
);
|
|
149
149
|
assert.match(
|
|
150
150
|
r.out,
|
|
151
|
-
/ABS_PATH\s+repro\/wc\.py — hard-codes 1 absolute path\(s\), e\.g\. `\/home\/example\/papers\/paper\.
|
|
151
|
+
/ABS_PATH\s+repro\/wc\.py — hard-codes 1 absolute path\(s\), e\.g\. `\/home\/example\/papers\/paper\.tex`/,
|
|
152
152
|
"a script that only runs on its author's laptop shipped in the reproduction directory unremarked:\n" +
|
|
153
153
|
r.out,
|
|
154
154
|
);
|
|
@@ -4,7 +4,7 @@ description: Answer "what's the status of the paper?" in one pass — MEASURE wh
|
|
|
4
4
|
allowed-tools: [Bash, Read, Grep, Glob]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
<!-- vigiles:sha256:
|
|
7
|
+
<!-- vigiles:sha256:c90bd362b565fa22 compiled from skills/paper-status/SKILL.md.spec.ts -->
|
|
8
8
|
|
|
9
9
|
# paper-status — state, measured before it is narrated
|
|
10
10
|
|
|
@@ -65,7 +65,7 @@ git log --oneline @{u}..HEAD # unpushed commits
|
|
|
65
65
|
A gate is stale if the paper changed after the gate ran. Check it mechanically, not by recall:
|
|
66
66
|
|
|
67
67
|
```bash
|
|
68
|
-
git log -1 --format=%cd --date=short -- paper.
|
|
68
|
+
git log -1 --format=%cd --date=short -- paper.tex # when the text last moved
|
|
69
69
|
ls -la reviews/ | tail -20 # when each gate last reported
|
|
70
70
|
```
|
|
71
71
|
|
|
@@ -71,7 +71,7 @@ git log --oneline @{u}..HEAD # unpushed commits
|
|
|
71
71
|
A gate is stale if the paper changed after the gate ran. Check it mechanically, not by recall:
|
|
72
72
|
|
|
73
73
|
\`\`\`bash
|
|
74
|
-
git log -1 --format=%cd --date=short -- paper.
|
|
74
|
+
git log -1 --format=%cd --date=short -- paper.tex # when the text last moved
|
|
75
75
|
ls -la reviews/ | tail -20 # when each gate last reported
|
|
76
76
|
\`\`\`
|
|
77
77
|
|
|
@@ -4,7 +4,7 @@ description: Compile a LaTeX paper (ACM/IEEE/arXiv) to PDF and render its pages
|
|
|
4
4
|
allowed-tools: [Read, Write, Edit, Grep, Glob, Bash, SendUserFile]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
<!-- vigiles:sha256:
|
|
7
|
+
<!-- vigiles:sha256:ff8dca1e1be3977e compiled from skills/render-paper/SKILL.md.spec.ts -->
|
|
8
8
|
|
|
9
9
|
# render-paper — .tex → PDF → readable page PNGs
|
|
10
10
|
|
|
@@ -15,7 +15,7 @@ first try.
|
|
|
15
15
|
## 🔴 This is a COMMAND, not a stage
|
|
16
16
|
|
|
17
17
|
You run it twenty times a day. It has no position in the pipeline and nothing waits its turn to reach
|
|
18
|
-
it: **any `.tex`
|
|
18
|
+
it: **any `.tex` edit is the trigger** — render, then eyeball pages 1–2 before believing
|
|
19
19
|
anything you say about the paper. Scorecard row **`render`** (in the CONTINUOUS section of
|
|
20
20
|
`paper-pipeline`) records the last render and its page count; that page count is the hard input the
|
|
21
21
|
`tighten-paper` structure gate is blocked on, which is the only sense in which anything "comes after"
|
|
@@ -21,7 +21,7 @@ first try.
|
|
|
21
21
|
## 🔴 This is a COMMAND, not a stage
|
|
22
22
|
|
|
23
23
|
You run it twenty times a day. It has no position in the pipeline and nothing waits its turn to reach
|
|
24
|
-
it: **any \`.tex\`
|
|
24
|
+
it: **any \`.tex\` edit is the trigger** — render, then eyeball pages 1–2 before believing
|
|
25
25
|
anything you say about the paper. Scorecard row **\`render\`** (in the CONTINUOUS section of
|
|
26
26
|
\`paper-pipeline\`) records the last render and its page count; that page count is the hard input the
|
|
27
27
|
\`tighten-paper\` structure gate is blocked on, which is the only sense in which anything "comes after"
|
|
@@ -56,8 +56,8 @@ const ROOT = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
|
56
56
|
/**
|
|
57
57
|
* Accept either a path to a PDF or a PAPER DIRECTORY — and in the second case find the artifact on
|
|
58
58
|
* its own: the path the paper declares in the `pdf` field of `paperlint.json`, else `paper.pdf` beside
|
|
59
|
-
* it. A paper
|
|
60
|
-
*
|
|
59
|
+
* it. A paper built by its own script into `build/acl_latex.pdf` would otherwise be skipped
|
|
60
|
+
* silently. The facts ALWAYS go into `<paper directory>/_build/`, not next to
|
|
61
61
|
* the PDF, where neither a rule's glob nor a human looks.
|
|
62
62
|
*/
|
|
63
63
|
export function resolveTarget(arg) {
|
|
@@ -4,7 +4,7 @@ description: "Use when a paper \"feels long / bloated / overly complex / hard to
|
|
|
4
4
|
allowed-tools: [Read, Write, Edit, Grep, Glob, Agent]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
<!-- vigiles:sha256:
|
|
7
|
+
<!-- vigiles:sha256:c57d6da372b28acf compiled from skills/tighten-paper/SKILL.md.spec.ts -->
|
|
8
8
|
|
|
9
9
|
# tighten-paper — the developmental edit
|
|
10
10
|
|
|
@@ -34,31 +34,12 @@ counter, is not this skill; it is the damage this skill exists to undo.
|
|
|
34
34
|
If you are invoked while the author is still adding material, say so and decline the pass. The right
|
|
35
35
|
answer is "not yet", not a smaller cut.
|
|
36
36
|
|
|
37
|
-
## 🏗 START WITH THE
|
|
37
|
+
## 🏗 START WITH THE SHAPE — weigh every section before judging it
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
🔴 **THE SCRIPT NO LONGER FINDS ANYTHING — it is an INVENTORY (2026-08-26).** All sixteen of its
|
|
45
|
-
checks moved into ESLint rules (`eslint-rules/paper-structure.mjs`). The findings you used to read
|
|
46
|
-
here now come from:
|
|
47
|
-
|
|
48
|
-
```
|
|
49
|
-
npx eslint --no-config-lookup --config eslint.config.mjs <paper.md>
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
⚠️ Do NOT pass `--flags-only`. It is kept only because two harnesses assert on its silence, and it
|
|
53
|
-
prints nothing and exits 0 whatever the paper looks like. Measured 26.08 on `the reference paper`:
|
|
54
|
-
`--flags-only` printed 0 lines while ESLint on the same bytes reported **6 structural findings**
|
|
55
|
-
(subsection-size ×2 · section-lead · free-section-size ×2 · block-ungraded). A mode that cannot
|
|
56
|
-
speak is not a check, and reading its silence as "clean" is the defect this pipeline is named for.
|
|
57
|
-
|
|
58
|
-
What the inventory prints, and why it stays a script: the outline **in order** — every heading, its
|
|
59
|
-
own words, its total with subsections, its share of the body, and the `carries:`/`score:`/`verdict:`
|
|
60
|
-
note the section wrote about itself, printed beside its weight. That comparison is the whole point
|
|
61
|
-
and a linter cannot make it: a finding has nowhere to put a table.
|
|
39
|
+
Write the outline out **in order**: every heading, its own words before its first subsection, its
|
|
40
|
+
total with subsections, its share of the body — and beside each, the `carries:`/`score:`/`verdict:`
|
|
41
|
+
note the section wrote about itself. That comparison, a section's weight printed next to what it
|
|
42
|
+
claims about itself, is the whole point.
|
|
62
43
|
|
|
63
44
|
🔴 **Why this exists, and why word counts alone mislead.** Asked whether a paper short-changed its
|
|
64
45
|
contribution, per-section word counts said no — the contribution had 38% more words than related
|
|
@@ -67,7 +48,7 @@ owned **0, 28 and 38** words before their first subsection. Nothing in the toolc
|
|
|
67
48
|
question, because every metric was per-sentence or per-document and none was per-section. The author's
|
|
68
49
|
word for the result was a "salad" — a jumbled mix — and he was right while the numbers said fine.
|
|
69
50
|
|
|
70
|
-
**What the
|
|
51
|
+
**What the count does NOT decide, and you must:** the ORDER, the NAMES, and whether a section earns
|
|
71
52
|
its place. Those are judgement, and pretending otherwise would be the failure this whole pipeline
|
|
72
53
|
exists to prevent.
|
|
73
54
|
|
|
@@ -188,18 +169,24 @@ called it "fat for no reason" after a pass that cut almost nothing and left it 1
|
|
|
188
169
|
|
|
189
170
|
A verdict that lives in a chat log is gone by the next session, and the next session then re-derives
|
|
190
171
|
it or ignores it. **Every section and subsection gets a one-line verdict comment immediately above its
|
|
191
|
-
heading**, in
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
172
|
+
heading**, in `paper.tex`, as LaTeX `%` comments, stamped with the date and the skill that
|
|
173
|
+
produced it:
|
|
174
|
+
|
|
175
|
+
```latex
|
|
176
|
+
% TIGHTEN 2026-08-06 · score 8/10 · verdict: KEEP · 190w
|
|
177
|
+
% carries: tools get close but none resolves a NAMED rule against a committed config
|
|
178
|
+
% because: it is the only place the tool-ecosystem gap is closed; a reviewer who skips it
|
|
179
|
+
% asks "hasn't this been done" and gets no answer
|
|
180
|
+
\section{Related tools}
|
|
181
|
+
|
|
182
|
+
% TIGHTEN 2026-08-06 · score 5/10 · verdict: SHORTEN -89w · 210w
|
|
183
|
+
% carries: why a linter for the rules file cannot work
|
|
184
|
+
% because: the point lands in two sentences; the rest re-establishes what §2 already did
|
|
185
|
+
\subsection{Why not a linter}
|
|
186
|
+
|
|
187
|
+
% TIGHTEN 2026-08-06 · score 2/10 · verdict: MOVE→artifact · 45w
|
|
188
|
+
% carries: nothing the reader must hold to judge the contribution
|
|
189
|
+
\subsection{Implementation notes}
|
|
203
190
|
```
|
|
204
191
|
|
|
205
192
|
🔴 **Before assigning any `MOVE→` verdict, read `paper-pipeline/references/body-vs-appendix.md`.**
|
|
@@ -224,7 +211,7 @@ written and **not one ever concluded "cut me"**. Author, 2026-08-06, reading the
|
|
|
224
211
|
value on every page and compared nothing.
|
|
225
212
|
|
|
226
213
|
The pairing is the mechanism: **a low score beside a KEEP verdict is a visible contradiction**, and
|
|
227
|
-
|
|
214
|
+
the outline above puts the two side by side. Prose cannot contradict itself; a number can.
|
|
228
215
|
|
|
229
216
|
**Calibration, so the scale does not drift to "everything is an 8":**
|
|
230
217
|
- **9–10** — remove it and a reviewer cannot assess the contribution. The claim, its evidence, its price.
|
|
@@ -279,10 +266,10 @@ Rules for these comments:
|
|
|
279
266
|
unpriced prose accumulates, because nothing there costs a page.
|
|
280
267
|
- **Stale beats absent, but not by much.** A comment whose date precedes the last substantive edit to
|
|
281
268
|
its section is stale; re-grade it rather than trusting it.
|
|
282
|
-
- **A verdict nobody executed is a note, not a decision.**
|
|
283
|
-
|
|
269
|
+
- **A verdict nobody executed is a note, not a decision.** List the blocks still carrying a
|
|
270
|
+
non-KEEP verdict — a paper full of unexecuted CUTs is worse than one never graded,
|
|
284
271
|
because it reads as decided.
|
|
285
|
-
- They are working notes:
|
|
272
|
+
- They are working notes: a `%` comment never reaches the PDF.
|
|
286
273
|
- The chat summary still gets written — but it is the *derivative*, and the paper is the record.
|
|
287
274
|
|
|
288
275
|
## 🧱 Enforce subsections — a section without them is a wall
|