paperlint 8.0.0 → 9.0.1
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/.github/CLAUDE.md +22 -0
- package/.github/CLAUDE.md.spec.ts +20 -0
- package/CLAUDE.md +59 -531
- package/CONTRIBUTING.md +7 -6
- package/README.md +4 -3
- package/dist/CLAUDE.md.spec.d.ts +3 -0
- package/dist/CLAUDE.md.spec.d.ts.map +1 -0
- package/dist/CLAUDE.md.spec.js +62 -0
- package/dist/CLAUDE.md.spec.js.map +1 -0
- 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 -25
- package/dist/build.js.map +1 -1
- package/dist/claim-provenance.d.ts +2 -2
- package/dist/cli.d.ts +6 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +30 -26
- 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/CLAUDE.md.spec.d.ts +3 -0
- package/dist/eslint-rules/CLAUDE.md.spec.d.ts.map +1 -0
- package/dist/eslint-rules/CLAUDE.md.spec.js +39 -0
- package/dist/eslint-rules/CLAUDE.md.spec.js.map +1 -0
- package/dist/eslint-rules/install-path-literals.d.ts +1 -1
- package/dist/eslint-rules/install-path-literals.js +4 -4
- package/dist/eslint-rules/install-path-literals.js.map +1 -1
- package/dist/eslint-rules/paper-context.d.ts.map +1 -1
- package/dist/eslint-rules/paper-context.js +4 -3
- 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 +12 -5
- package/dist/eslint-rules/paper-typography.js.map +1 -1
- package/dist/eslint-rules/pdf-last-page-balance.js +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/agent-cli-version.js +1 -1
- package/dist/lib/agent-cli-version.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-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 +2 -2
- 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/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-requirements.js +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/toolchain.js +1 -1
- package/docs/configuration.md +12 -4
- package/docs/design/paper-sources.md +197 -0
- package/docs/e2e.md +1 -1
- package/docs/incidents.md +5 -5
- package/docs/package-shape-options.md +1 -1
- 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.md +62 -57
- package/eslint-rules/CLAUDE.md +44 -0
- package/eslint-rules/CLAUDE.md.spec.ts +43 -0
- package/eslint-rules/install-path-literals.harness.mjs +1 -1
- package/eslint-rules/install-path-literals.ts +4 -4
- package/eslint-rules/paper-context.ts +4 -3
- package/eslint-rules/paper-typography.test.mjs +12 -72
- package/eslint-rules/paper-typography.ts +13 -6
- package/eslint-rules/pdf-last-page-balance.ts +1 -1
- package/eslint.config.mjs +28 -3
- 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/CLAUDE.md +34 -0
- package/hooks/CLAUDE.md.spec.ts +30 -0
- package/lib/agent-cli-version.ts +1 -1
- package/package.json +3 -11
- package/plugin/CLAUDE.md +22 -0
- package/plugin/CLAUDE.md.spec.ts +22 -0
- package/scripts/check.test.ts +23 -1
- package/scripts/check.ts +7 -3
- package/scripts/coverage-config.test.ts +21 -11
- package/scripts/layer-legacy-frozen.test.ts +41 -2
- package/scripts/layer-legacy-frozen.ts +31 -2
- package/skills/CLAUDE.md +19 -0
- package/skills/CLAUDE.md.spec.ts +18 -0
- package/skills/grade-paper-writing/SKILL.md +4 -3
- package/skills/grade-paper-writing/SKILL.md.spec.ts +3 -2
- package/skills/grade-paper-writing/prose-lint.mjs +11 -2
- package/skills/grade-paper-writing/prose-lint.test.ts +52 -4
- package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +83 -33
- package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +67 -115
- package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +128 -121
- package/skills/plan-paper-timeline/plan-paper-timeline.effects.harness.mjs +21 -102
- 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 -19
- package/skills/verify-citations/scripts/bib-authors.cli.test.ts +129 -46
- package/skills/verify-citations/scripts/bib-authors.d.mts +22 -2
- package/skills/verify-citations/scripts/bib-authors.mjs +57 -111
- 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/CLAUDE.md +23 -1
- package/src/CLAUDE.md.spec.ts +72 -0
- 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.test.ts +210 -0
- package/src/build.ts +101 -36
- package/src/claim-provenance.test.ts +33 -7
- package/src/claim-provenance.ts +2 -2
- package/src/cli.harness.mjs +20 -10
- package/src/cli.test.ts +14 -10
- package/src/cli.ts +37 -28
- 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/heading-case.test.ts +24 -10
- package/src/latex-log.ts +20 -0
- package/src/latex-loop.harness.ts +1 -0
- package/src/latex-loop.ts +2 -0
- package/src/linted-set.test.ts +9 -1
- package/src/new-paper.harness.mjs +3 -2
- package/src/new-paper.test.ts +9 -5
- package/src/package-dirs.ts +3 -2
- package/src/paper-includes.test.ts +157 -38
- package/src/paper-includes.ts +39 -35
- package/src/paper-prose.test.ts +34 -5
- package/src/paper-prose.ts +2 -2
- 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/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/tex-paper.test.ts +148 -78
- package/src/tex-paper.ts +31 -61
- package/src/tex-requirements.ts +1 -1
- package/src/tex-venue-rules.test.ts +36 -8
- package/src/tex-venue-rules.ts +3 -5
- package/src/toolchain.ts +1 -1
- 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
|
@@ -1,51 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* plan-paper-timeline — the EFFECTS tier. What the run
|
|
3
|
-
* SKILL.md says it
|
|
2
|
+
* plan-paper-timeline — the EFFECTS tier. What the run actually does, not what
|
|
3
|
+
* SKILL.md says it does.
|
|
4
4
|
*
|
|
5
5
|
* SIBLING, NOT REPLACEMENT. `plan-paper-timeline.harness.mjs` next door is static
|
|
6
6
|
* (`checkSkill()`): frontmatter, wiring, script paths, verdict vocabulary. It is untouched.
|
|
7
7
|
* This file runs the REAL `claude` CLI against a SCRIPTED MOCK MODEL (`runHarnessTest`),
|
|
8
|
-
* so it costs $0 and no API key
|
|
9
|
-
* cannot: whether the tools this skill DECLARES EXIST WHERE IT RUNS.
|
|
8
|
+
* so it costs $0 and no API key.
|
|
10
9
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* declared entries are MCP calendar tools. A declaration naming a tool that is not there is
|
|
15
|
-
* invisible to every static check in this repo and to `vigiles audit`, which scores this
|
|
16
|
-
* corpus Structure 100.
|
|
10
|
+
* Run A: the skill activates, its body reaches the model, and a declared, granted tool
|
|
11
|
+
* (`Read`) works. Run B: §3's prescribed `create_event` call, parsed out of SKILL.md,
|
|
12
|
+
* replayed against a strict calendar double.
|
|
17
13
|
*
|
|
18
|
-
* ──
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* ── WHAT THIS PINS (a characterization test, deliberately) ───────────────────────
|
|
27
|
-
* Under `claude -p` at `CHARACTERIZED_CLI` (`lib/agent-cli-version.mjs`, 2.1.227) the whole
|
|
28
|
-
* `mcp__*` surface is ABSENT, and so is `ToolSearch`.
|
|
29
|
-
*
|
|
30
|
-
* 🔴 THAT NUMBER IS A VALUE, NOT PROSE, and it was prose until issue #7. The probe below used
|
|
31
|
-
* to run with `stdio: "ignore"`, so the version the CLI printed was never read — this tier
|
|
32
|
-
* characterized whatever the runner installed that day while this paragraph kept saying 2.1.227.
|
|
33
|
-
* Measured: 2.1.273 on the reporter's machine and on this one, forty-six patch releases of drift
|
|
34
|
-
* that nothing in a green log could show. The version is now OBSERVED, reported in the summary
|
|
35
|
-
* below, and named in every failure message — so a red says which version it characterized.
|
|
36
|
-
* That means:
|
|
37
|
-
* 1. §3 of this skill is UNEXECUTABLE in any headless / CI context. It is an
|
|
38
|
-
* interactive-session-only step. Nothing in the file says so.
|
|
39
|
-
* 2. 🔴 THE RECORDED `ToolSearch` FINDING DOES NOT REPRODUCE HERE, AND THE CORRECTION
|
|
40
|
-
* MATTERS. `.claude/lib/skill-effects-results/README.md` (2026-08-10, $0.82)
|
|
41
|
-
* recorded "the model called ToolSearch … it isn't in allowed-tools," with the caveat
|
|
42
|
-
* that it might be an artifact of the headless environment. It is the opposite:
|
|
43
|
-
* headless has NO ToolSearch to call. Adding `ToolSearch` to `allowed-tools` would
|
|
44
|
-
* therefore fix nothing here. The reachability of these four tools is a property of
|
|
45
|
-
* the ENVIRONMENT, not of the declaration.
|
|
46
|
-
*
|
|
47
|
-
* If either fact changes, this file fails and says which one — that is its whole job.
|
|
48
|
-
* It is not asserting that absence is good.
|
|
14
|
+
* ── WHY RUN A NO LONGER PROBES THE CALENDAR ─────────────────────────────────────
|
|
15
|
+
* It used to call `mcp__Google_Calendar__list_events` and `ToolSearch` and pin "No such
|
|
16
|
+
* tool available" as a finding about headless runs. It was a finding about the FIXTURE:
|
|
17
|
+
* the run happens in an empty `mkdtemp` with no MCP server configured, so the calendar
|
|
18
|
+
* tool is missing by construction, whatever the CLI can do. vigiles 38 refuses a
|
|
19
|
+
* scripted call to a tool the CLI does not have, for exactly this reason. Run B is
|
|
20
|
+
* where the calendar step is tested, with the surface supplied.
|
|
49
21
|
*
|
|
50
22
|
* Cost: $0 (scripted mock model, no API key). Wall clock ~30-60s for one CLI spawn.
|
|
51
23
|
*/
|
|
@@ -114,37 +86,19 @@ const r = await runHarnessTest({
|
|
|
114
86
|
transcript: true,
|
|
115
87
|
timeoutMs: 180_000,
|
|
116
88
|
prompt: "plan the AISec timeline",
|
|
117
|
-
// The SESSION grant
|
|
118
|
-
|
|
119
|
-
// a control and a measurement inside ONE permission configuration.
|
|
120
|
-
allowedTools: ["Read", "Write", "Skill", "ToolSearch", ...declaredMcp],
|
|
89
|
+
// The SESSION grant: the tools the script below calls, and no others.
|
|
90
|
+
allowedTools: ["Read", "Skill"],
|
|
121
91
|
files: { "cfp.md": "AISec 2026 — submission 24 Jul AoE\n" },
|
|
122
92
|
model: [
|
|
123
93
|
{ tool: "Skill", input: { skill: "plan-paper-timeline" } },
|
|
124
|
-
// CONTROL: a tool that is present and permitted.
|
|
125
|
-
// verdict below would be a broken run, not a finding.
|
|
94
|
+
// CONTROL: a tool that is present and permitted.
|
|
126
95
|
{ tool: "Read", input: { file_path: "cfp.md" } },
|
|
127
|
-
// READ-ONLY calendar probe. Never create_event / update_event — see header.
|
|
128
|
-
{
|
|
129
|
-
tool: "mcp__Google_Calendar__list_events",
|
|
130
|
-
input: { calendarId: "primary" },
|
|
131
|
-
},
|
|
132
|
-
// The tool the 2026-08-10 sweep said was missing from the declaration.
|
|
133
|
-
{
|
|
134
|
-
tool: "ToolSearch",
|
|
135
|
-
input: {
|
|
136
|
-
query: "select:mcp__Google_Calendar__create_event",
|
|
137
|
-
max_results: 1,
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
96
|
{ text: "done" },
|
|
141
97
|
],
|
|
142
98
|
});
|
|
143
99
|
|
|
144
100
|
try {
|
|
145
101
|
const call = (name) => r.toolCalls.find((c) => c.name === name);
|
|
146
|
-
const absent = (c) =>
|
|
147
|
-
c !== undefined && c.isError && /No such tool available/.test(c.resultText);
|
|
148
102
|
|
|
149
103
|
// ── 1. The run actually happened and the skill actually loaded ──────────────────
|
|
150
104
|
// Asserted FIRST and separately from everything below. A confident "nothing was
|
|
@@ -172,48 +126,14 @@ try {
|
|
|
172
126
|
throw new Error(
|
|
173
127
|
`POSITIVE CONTROL FAILED: Read — a tool this skill declares and the session grants — ` +
|
|
174
128
|
`did not succeed (${ctrl ? ctrl.resultText.slice(0, 160) : "never called"}). ` +
|
|
175
|
-
`
|
|
176
|
-
);
|
|
177
|
-
|
|
178
|
-
// ── 2. The pinned environment fact: the declared MCP surface is not there ───────
|
|
179
|
-
const cal = call("mcp__Google_Calendar__list_events");
|
|
180
|
-
if (!absent(cal))
|
|
181
|
-
throw new Error(
|
|
182
|
-
`THE ENVIRONMENT CHANGED — and this is good news that must be read, not silenced. ` +
|
|
183
|
-
`Observed \`claude\` ${cli.version} (characterized against ${cli.characterized}). ` +
|
|
184
|
-
`\`mcp__Google_Calendar__list_events\` is now REACHABLE from \`claude -p\` ` +
|
|
185
|
-
`(result: ${cal ? cal.resultText.slice(0, 200) : "no call recorded"}). ` +
|
|
186
|
-
`Consequences: (a) §3 of this skill is now executable headlessly, so it can finally be ` +
|
|
187
|
-
`tested for real instead of characterized; (b) 🔴 every future run of this tier can now ` +
|
|
188
|
-
`WRITE A REAL CALENDAR — re-check the safety argument in this file's header BEFORE ` +
|
|
189
|
-
`adding a create_event probe; (c) update this assertion deliberately, do not delete it.`,
|
|
190
|
-
);
|
|
191
|
-
|
|
192
|
-
const ts = call("ToolSearch");
|
|
193
|
-
if (!absent(ts))
|
|
194
|
-
throw new Error(
|
|
195
|
-
`\`ToolSearch\` is now available under \`claude -p\` ${cli.version} ` +
|
|
196
|
-
`(characterized against ${cli.characterized}) (result: ` +
|
|
197
|
-
`${ts ? ts.resultText.slice(0, 200) : "no call recorded"}). The 2026-08-10 sweep recorded ` +
|
|
198
|
-
`"ToolSearch missing from allowed-tools" as a finding about THIS SKILL; measured on ` +
|
|
199
|
-
`2026-08-11 it was a fact about the ENVIRONMENT (headless had no ToolSearch at all). ` +
|
|
200
|
-
`If it exists now, that finding becomes actionable again: add it to allowed-tools.`,
|
|
129
|
+
`The skill loaded but the run cannot use the tools it declares.`,
|
|
201
130
|
);
|
|
202
131
|
|
|
203
|
-
// ──
|
|
132
|
+
// ── 2. The version goes in the REPORT, not only into failures ──────────────────
|
|
204
133
|
console.log(
|
|
205
134
|
`✓ plan-paper-timeline [effects]: skill activated and its body reached the model; ` +
|
|
206
135
|
`Read (declared+granted) works — the run is live.\n` +
|
|
207
|
-
|
|
208
|
-
// silently still holds is indistinguishable from one nobody re-checked, so the run says
|
|
209
|
-
// which version it held against — and says it whether or not the numbers agree.
|
|
210
|
-
` CLI: ${cli.note}\n` +
|
|
211
|
-
` PINNED: ${String(declaredMcp.length)} of its 10 declared entries are MCP calendar tools, ` +
|
|
212
|
-
`and the whole mcp__* surface is ABSENT under \`claude -p\`; \`ToolSearch\` is absent too.\n` +
|
|
213
|
-
` → §3 ("Create the calendar events") is an INTERACTIVE-SESSION-ONLY step. Nothing in ` +
|
|
214
|
-
`SKILL.md says so, and no static check can see it.\n` +
|
|
215
|
-
` → Corrects the 2026-08-10 sweep: the ToolSearch gap was environmental, not a defect ` +
|
|
216
|
-
`in this declaration.`,
|
|
136
|
+
` CLI: ${cli.note}`,
|
|
217
137
|
);
|
|
218
138
|
} finally {
|
|
219
139
|
r.cleanup();
|
|
@@ -222,9 +142,8 @@ try {
|
|
|
222
142
|
// ════════════════════════════════════════════════════════════════════════════════
|
|
223
143
|
// RUN B — §3 EXECUTED, not characterized
|
|
224
144
|
//
|
|
225
|
-
//
|
|
226
|
-
//
|
|
227
|
-
// checked by anything. Run B supplies the surface with a stand-in
|
|
145
|
+
// A headless run in an empty work dir has no calendar surface, so without a stand-in
|
|
146
|
+
// the skill's CENTRAL STEP is checked by nothing. Run B supplies the surface with a stand-in
|
|
228
147
|
// (`fixtures/fake-google-calendar.ts`) carrying the REAL API's required-field sets,
|
|
229
148
|
// so the step becomes executable for the first time.
|
|
230
149
|
//
|
|
@@ -4,7 +4,7 @@ description: Verify every citation is a real work with correct metadata, and tha
|
|
|
4
4
|
allowed-tools: [Read, Write, Edit, Grep, Glob, Bash, WebSearch, WebFetch, Agent]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
<!-- vigiles:sha256:
|
|
7
|
+
<!-- vigiles:sha256:fdb023ed7c613e1b compiled from skills/verify-citations/SKILL.md.spec.ts -->
|
|
8
8
|
|
|
9
9
|
# verify-citations — every cite real, the delta explicit
|
|
10
10
|
|
|
@@ -165,10 +165,13 @@ heavier than a typo, and it is exactly the class §"any regalia about third part
|
|
|
165
165
|
|
|
166
166
|
**How to run.**
|
|
167
167
|
```
|
|
168
|
-
node scripts/bib-authors.mjs <paper-dir> # or a .bib, or a .tex
|
|
168
|
+
node scripts/bib-authors.mjs <paper-dir> # the databases bibtex opened in the last build; or a .bib, or a .tex
|
|
169
169
|
node scripts/bib-authors.mjs <paper-dir> --json
|
|
170
170
|
```
|
|
171
|
-
Exits **1** on any author-set or author-order disagreement, **0** when clean, **2** on usage/IO error.
|
|
171
|
+
Exits **1** on any author-set or author-order disagreement, **0** when clean, **2** on usage/IO error. A paper
|
|
172
|
+
directory or a `.tex` is read through the build's record (`_build/sources.json`): a paper that was never built,
|
|
173
|
+
or changed since, is refused (exit 2) and told to run `npx paperlint build` first — the databases are TeX's answer,
|
|
174
|
+
not a reading of the source. A `.bib` named is read as itself.
|
|
172
175
|
|
|
173
176
|
**Source is DBLP, and that choice is the evidence.** DBLP indexes the proceedings record and the CoRR record
|
|
174
177
|
**separately**, so a single query returns both lists side by side — the cleanest proof available that the
|
|
@@ -244,7 +247,7 @@ It used to be one script, `verify-refs.mjs`; the measurement half stayed a scrip
|
|
|
244
247
|
became twelve ESLint rules over the facts it writes.
|
|
245
248
|
|
|
246
249
|
```
|
|
247
|
-
# 1. measure: parse the bibliography, ask CrossRef/arXiv, write _build/refs.facts.json
|
|
250
|
+
# 1. measure: parse the bibliography bibtex opened (build the paper first), ask CrossRef/arXiv, write _build/refs.facts.json
|
|
248
251
|
node .claude/skills/paper-pipeline/scripts/extract-ref-facts.mjs <paper-dir>
|
|
249
252
|
node .claude/skills/paper-pipeline/scripts/extract-ref-facts.mjs <paper-dir> --offline # cached responses only
|
|
250
253
|
|
|
@@ -181,10 +181,13 @@ heavier than a typo, and it is exactly the class §"any regalia about third part
|
|
|
181
181
|
|
|
182
182
|
**How to run.**
|
|
183
183
|
\`\`\`
|
|
184
|
-
node scripts/bib-authors.mjs <paper-dir> # or a .bib, or a .tex
|
|
184
|
+
node scripts/bib-authors.mjs <paper-dir> # the databases bibtex opened in the last build; or a .bib, or a .tex
|
|
185
185
|
node scripts/bib-authors.mjs <paper-dir> --json
|
|
186
186
|
\`\`\`
|
|
187
|
-
Exits **1** on any author-set or author-order disagreement, **0** when clean, **2** on usage/IO error.
|
|
187
|
+
Exits **1** on any author-set or author-order disagreement, **0** when clean, **2** on usage/IO error. A paper
|
|
188
|
+
directory or a \`.tex\` is read through the build's record (\`_build/sources.json\`): a paper that was never built,
|
|
189
|
+
or changed since, is refused (exit 2) and told to run \`npx paperlint build\` first — the databases are TeX's answer,
|
|
190
|
+
not a reading of the source. A \`.bib\` named is read as itself.
|
|
188
191
|
|
|
189
192
|
**Source is DBLP, and that choice is the evidence.** DBLP indexes the proceedings record and the CoRR record
|
|
190
193
|
**separately**, so a single query returns both lists side by side — the cleanest proof available that the
|
|
@@ -260,7 +263,7 @@ It used to be one script, \`verify-refs.mjs\`; the measurement half stayed a scr
|
|
|
260
263
|
became twelve ESLint rules over the facts it writes.
|
|
261
264
|
|
|
262
265
|
\`\`\`
|
|
263
|
-
# 1. measure: parse the bibliography, ask CrossRef/arXiv, write _build/refs.facts.json
|
|
266
|
+
# 1. measure: parse the bibliography bibtex opened (build the paper first), ask CrossRef/arXiv, write _build/refs.facts.json
|
|
264
267
|
node .claude/skills/paper-pipeline/scripts/extract-ref-facts.mjs <paper-dir>
|
|
265
268
|
node .claude/skills/paper-pipeline/scripts/extract-ref-facts.mjs <paper-dir> --offline # cached responses only
|
|
266
269
|
|
|
@@ -13,9 +13,15 @@ import {
|
|
|
13
13
|
checkAuthors,
|
|
14
14
|
claimsPublished,
|
|
15
15
|
dblpHits,
|
|
16
|
-
|
|
16
|
+
authorEntries,
|
|
17
17
|
surnames,
|
|
18
18
|
} from "./bib-authors.mjs";
|
|
19
|
+
import { bibReader } from "#src/adapters/bibtex/index";
|
|
20
|
+
import { absolutePath } from "#src/domain/paths";
|
|
21
|
+
|
|
22
|
+
/** This check's entries of a BibTeX text, read by the one bibtex reader. */
|
|
23
|
+
const entriesIn = (bib) =>
|
|
24
|
+
authorEntries(bibReader.readFile(absolutePath("/p/refs.bib"), bib).entries);
|
|
19
25
|
|
|
20
26
|
afterEach(() => vi.unstubAllGlobals());
|
|
21
27
|
|
|
@@ -215,35 +221,27 @@ test("dblpHits: no hits is an empty list; 429 is retryable; other errors are not
|
|
|
215
221
|
);
|
|
216
222
|
});
|
|
217
223
|
|
|
218
|
-
test("
|
|
224
|
+
test("authorEntries: names written family first (`and others` kept), a quoted value, a field that is absent", () => {
|
|
219
225
|
assert.deepEqual(
|
|
220
|
-
|
|
221
|
-
'@article{k, author = "Ada Lovelace", title = {
|
|
226
|
+
entriesIn(
|
|
227
|
+
'@article{k, author = "Ada Lovelace and van der Berg, Jan and others", title = {The {BERT} Model}, journal = {J}}\n@misc{untitled, author = {Hopper, Grace}}',
|
|
222
228
|
),
|
|
223
229
|
[
|
|
224
230
|
{
|
|
225
231
|
type: "article",
|
|
226
232
|
key: "k",
|
|
227
|
-
author: "Ada
|
|
228
|
-
title: "
|
|
233
|
+
author: "Lovelace, Ada and van der Berg, Jan and others",
|
|
234
|
+
title: "The BERT Model",
|
|
229
235
|
booktitle: "",
|
|
230
236
|
journal: "J",
|
|
231
237
|
},
|
|
232
|
-
],
|
|
233
|
-
);
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
test("parseBib: braces nested inside a value, and a bare last value with no comma after it", () => {
|
|
237
|
-
assert.deepEqual(
|
|
238
|
-
parseBib("@article{k, title = {The {BERT} Model}, journal = J}"),
|
|
239
|
-
[
|
|
240
238
|
{
|
|
241
|
-
type: "
|
|
242
|
-
key: "
|
|
243
|
-
author: "",
|
|
244
|
-
title: "
|
|
239
|
+
type: "misc",
|
|
240
|
+
key: "untitled",
|
|
241
|
+
author: "Hopper, Grace",
|
|
242
|
+
title: "",
|
|
245
243
|
booktitle: "",
|
|
246
|
-
journal: "
|
|
244
|
+
journal: "",
|
|
247
245
|
},
|
|
248
246
|
],
|
|
249
247
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* bib-authors.mjs as a process:
|
|
3
|
-
*
|
|
4
|
-
* the report and exit code for each outcome. DBLP is a preload that answers
|
|
2
|
+
* bib-authors.mjs as a process: the bibliography it reads (a .bib named, or what the last build's
|
|
3
|
+
* bibtex opened for a paper directory or a .tex — `_build/sources.json`, through `bibliographyAt`), the
|
|
4
|
+
* targets it refuses, and the report and exit code for each outcome. DBLP is a preload that answers
|
|
5
5
|
* by title from a table (the documented `result.hits.hit[].info` shape) — no network.
|
|
6
6
|
*/
|
|
7
7
|
import assert from "node:assert/strict";
|
|
@@ -15,6 +15,13 @@ import {
|
|
|
15
15
|
useTempDir,
|
|
16
16
|
writeTree,
|
|
17
17
|
} from "../../../test/support.ts";
|
|
18
|
+
import {
|
|
19
|
+
plantedOnDisk,
|
|
20
|
+
recordOnDisk,
|
|
21
|
+
type Shape,
|
|
22
|
+
} from "../../../test/recorded-fixture.ts";
|
|
23
|
+
import { absolutePath } from "#src/domain/paths";
|
|
24
|
+
import { bibliographyUnreadWhy } from "#src/paper-sources";
|
|
18
25
|
|
|
19
26
|
const SCRIPT = join(dirname(fileURLToPath(import.meta.url)), "bib-authors.mjs");
|
|
20
27
|
const root = useTempDir("bib-authors-cli-");
|
|
@@ -32,6 +39,10 @@ const bib = (
|
|
|
32
39
|
) =>
|
|
33
40
|
`@inproceedings{${key}, author = {${author}}, title = {${title}}, ${venue}}\n`;
|
|
34
41
|
|
|
42
|
+
/** A .tex that declares the database `name`, with `preamble` before its document. */
|
|
43
|
+
const declaring = (name: string, preamble = "") =>
|
|
44
|
+
`\\documentclass{article}\n${preamble}\\begin{document}x\\bibliography{${name}}\\end{document}\n`;
|
|
45
|
+
|
|
35
46
|
writeTree(root, {
|
|
36
47
|
// The retry backoff (1.5 s, then 3 s) and the 900 ms courtesy pause are real sleeps in the
|
|
37
48
|
// script. Under CPU load they made "DBLP down" take 11.7 s once and time out. The preload
|
|
@@ -46,24 +57,53 @@ writeTree(root, {
|
|
|
46
57
|
" const hit = (answers[q] ?? []).map((authors) => ({ info: { title: q, venue: 'X', year: '2024',\n" +
|
|
47
58
|
" type: 'Conference and Workshop Papers', authors: { author: authors.map((text) => ({ text })) } } }));\n" +
|
|
48
59
|
" return new Response(JSON.stringify({ result: { hits: { hit } } }), { status: 200 });\n};\n",
|
|
60
|
+
"findings/paper.tex": declaring("paper"),
|
|
49
61
|
"findings/paper.bib":
|
|
50
62
|
bib("dropped", "Ada Lovelace and Grace Hopper", "Dropped Author") +
|
|
51
63
|
bib("order", "Alan Turing and Ada Lovelace", "Swapped Order"),
|
|
52
64
|
"findings/other.bib": bib("never", "X", "Never Read"),
|
|
53
65
|
"twotex/b.tex": "\\documentclass{article}\n",
|
|
54
|
-
"twotex/
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
66
|
+
"twotex/paper.tex": declaring(
|
|
67
|
+
"refs",
|
|
68
|
+
"\\begin{filecontents*}[overwrite]{refs.bib}\n" +
|
|
69
|
+
bib("pre", "Ada Lovelace", "A Preprint", "journal = {arXiv preprint}") +
|
|
70
|
+
"\\end{filecontents*}\n",
|
|
71
|
+
),
|
|
72
|
+
// What TeX wrote from that block: the file bibtex opened.
|
|
73
|
+
"twotex/refs.bib": bib(
|
|
74
|
+
"pre",
|
|
75
|
+
"Ada Lovelace",
|
|
76
|
+
"A Preprint",
|
|
77
|
+
"journal = {arXiv preprint}",
|
|
78
|
+
),
|
|
79
|
+
"sibling/paper.tex": declaring("refs"),
|
|
59
80
|
"sibling/refs.bib": bib("good", "Ada Lovelace", "All Good"),
|
|
60
|
-
"
|
|
81
|
+
"etal/paper.tex": declaring("refs"),
|
|
61
82
|
"etal/notes.txt": "not a bibliography\n",
|
|
62
83
|
"etal/refs.bib":
|
|
63
84
|
bib("good", "Ada Lovelace", "All Good") +
|
|
64
85
|
bib("etal", "Ada Lovelace and others", "Truncated"),
|
|
86
|
+
"down/paper.tex": declaring("refs"),
|
|
65
87
|
"down/refs.bib": bib("down", "Ada Lovelace", "Service Down"),
|
|
66
|
-
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
/** The record of a paper whose bibliography is `db`, a file the author keeps. */
|
|
91
|
+
const keeps = (db: string): Shape => ({
|
|
92
|
+
inputs: [
|
|
93
|
+
["paper.tex", "body"],
|
|
94
|
+
[db, "preamble"],
|
|
95
|
+
],
|
|
96
|
+
databases: [db],
|
|
97
|
+
});
|
|
98
|
+
recordOnDisk(join(root, "findings"), keeps("paper.bib"));
|
|
99
|
+
recordOnDisk(join(root, "sibling"), keeps("refs.bib"));
|
|
100
|
+
recordOnDisk(join(root, "etal"), keeps("refs.bib"));
|
|
101
|
+
recordOnDisk(join(root, "down"), keeps("refs.bib"));
|
|
102
|
+
// A block TeX wrote into refs.bib: the file is no input, the block is.
|
|
103
|
+
recordOnDisk(join(root, "twotex"), {
|
|
104
|
+
inputs: [["paper.tex", "body"]],
|
|
105
|
+
written: ["refs.bib"],
|
|
106
|
+
databases: ["refs.bib"],
|
|
67
107
|
});
|
|
68
108
|
|
|
69
109
|
const run = (...args: string[]) => {
|
|
@@ -77,14 +117,16 @@ const run = (...args: string[]) => {
|
|
|
77
117
|
};
|
|
78
118
|
};
|
|
79
119
|
|
|
80
|
-
test("refusals: no argument
|
|
120
|
+
test("refusals: no argument; a path with no bibliography to read, in bibliographyAt's words", () => {
|
|
121
|
+
// The path → bibliography vocabulary is tested once, beside bibliographyAt (src/paper-sources.ts).
|
|
122
|
+
// Here: that the command speaks it, and exits 2.
|
|
123
|
+
const notes = join(root, "etal", "notes.txt");
|
|
81
124
|
assert.deepEqual(
|
|
82
|
-
[
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
].map(({ status, stdout, stderr }) => ({ status, stdout, stderr })),
|
|
125
|
+
[run(), run(notes)].map(({ status, stdout, stderr }) => ({
|
|
126
|
+
status,
|
|
127
|
+
stdout,
|
|
128
|
+
stderr,
|
|
129
|
+
})),
|
|
88
130
|
[
|
|
89
131
|
{
|
|
90
132
|
status: 2,
|
|
@@ -95,24 +137,13 @@ test("refusals: no argument, no such path, a directory with nothing to read, a .
|
|
|
95
137
|
{
|
|
96
138
|
status: 2,
|
|
97
139
|
stdout: "",
|
|
98
|
-
stderr:
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
status: 2,
|
|
102
|
-
stdout: "",
|
|
103
|
-
stderr: "bib-authors: no .bib or .tex in <root>/empty\n",
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
status: 2,
|
|
107
|
-
stdout: "",
|
|
108
|
-
stderr:
|
|
109
|
-
"bib-authors: no bibliography found in <root>/nobib/paper.tex (no filecontents block, no refs.bib beside it)\n",
|
|
140
|
+
stderr: `bib-authors: ${bibliographyUnreadWhy({ kind: "not-bib-or-tex", path: absolutePath(notes) }).replaceAll(root, "<root>")}\n`,
|
|
110
141
|
},
|
|
111
142
|
],
|
|
112
143
|
);
|
|
113
144
|
});
|
|
114
145
|
|
|
115
|
-
test("a paper directory: the
|
|
146
|
+
test("a paper directory: the paper.bib its \\bibliography{paper} declares is read, and each difference is printed — FAIL, exit 1", () => {
|
|
116
147
|
assert.deepEqual(run(join(root, "findings")), {
|
|
117
148
|
status: 1,
|
|
118
149
|
stderr: "",
|
|
@@ -135,16 +166,16 @@ test("a paper directory: the canonical paper.bib is read, and each difference is
|
|
|
135
166
|
});
|
|
136
167
|
});
|
|
137
168
|
|
|
138
|
-
test("
|
|
139
|
-
const r = run(join(root, "twotex"), "--json");
|
|
169
|
+
test("a .tex named is its folder's paper: the database bibtex opened is read from the block that wrote it", () => {
|
|
170
|
+
const r = run(join(root, "twotex", "paper.tex"), "--json");
|
|
140
171
|
assert.deepEqual(
|
|
141
172
|
{ status: r.status, stderr: r.stderr, report: parseJson(r.stdout) },
|
|
142
173
|
{
|
|
143
174
|
status: 0,
|
|
144
|
-
stderr:
|
|
145
|
-
"⚠️ <root>/twotex holds several .tex files and no canonical paper.tex: a.tex, b.tex — took a.tex. If that is the wrong one, name the file explicitly.\n",
|
|
175
|
+
stderr: "",
|
|
146
176
|
report: {
|
|
147
|
-
file: "<root>/twotex/
|
|
177
|
+
file: "<root>/twotex/paper.tex",
|
|
178
|
+
files: ["<root>/twotex/paper.tex"],
|
|
148
179
|
entries: 1,
|
|
149
180
|
findings: [],
|
|
150
181
|
skipped: [],
|
|
@@ -154,7 +185,18 @@ test("two .tex files and no paper.tex: the first by name, said out loud; its fil
|
|
|
154
185
|
);
|
|
155
186
|
});
|
|
156
187
|
|
|
157
|
-
test("a .
|
|
188
|
+
test("a .bib named is read as itself", () => {
|
|
189
|
+
const r = run(join(root, "findings", "paper.bib"), "--json");
|
|
190
|
+
const report: unknown = parseJson(r.stdout);
|
|
191
|
+
assert.deepEqual(
|
|
192
|
+
report !== null && typeof report === "object" && "files" in report
|
|
193
|
+
? report.files
|
|
194
|
+
: report,
|
|
195
|
+
["<root>/findings/paper.bib"],
|
|
196
|
+
);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("a .tex that declares refs and has no block reads refs.bib — PASS, exit 0", () => {
|
|
158
200
|
const r = run(join(root, "sibling", "paper.tex"));
|
|
159
201
|
assert.deepEqual(r, {
|
|
160
202
|
status: 0,
|
|
@@ -164,16 +206,6 @@ test("a .tex with no filecontents reads the refs.bib beside it — PASS, exit 0"
|
|
|
164
206
|
});
|
|
165
207
|
});
|
|
166
208
|
|
|
167
|
-
test("a file that is neither a .bib nor a .tex is refused — not swapped for the refs.bib beside it", () => {
|
|
168
|
-
const r = run(join(root, "etal", "notes.txt"));
|
|
169
|
-
assert.deepEqual(r, {
|
|
170
|
-
status: 2,
|
|
171
|
-
stdout: "",
|
|
172
|
-
stderr:
|
|
173
|
-
"bib-authors: <root>/etal/notes.txt is not a .bib or a .tex — give the paper's .bib or paper.tex\n",
|
|
174
|
-
});
|
|
175
|
-
});
|
|
176
|
-
|
|
177
209
|
test("an `and others` entry is printed as not applicable, not dropped", () => {
|
|
178
210
|
const r = run(join(root, "etal"));
|
|
179
211
|
assert.deepEqual(r, {
|
|
@@ -198,3 +230,54 @@ test("DBLP down: NOT CHECKED, PARTIAL, exit 2 — never a pass", () => {
|
|
|
198
230
|
"PARTIAL: no disagreement among the entries reached, but 1 could not be checked — this is NOT a pass.\n",
|
|
199
231
|
});
|
|
200
232
|
});
|
|
233
|
+
|
|
234
|
+
// The planted papers of fixtures/paper-sources, laid out as a build leaves them: the file read is the
|
|
235
|
+
// one bibtex opened (tex-truth.json), or the block that wrote it.
|
|
236
|
+
test.each([
|
|
237
|
+
["v1-stale", "refs.bib"],
|
|
238
|
+
["v2-overwrite", "paper.tex"],
|
|
239
|
+
["v3-declared", "paper.bib"],
|
|
240
|
+
["v4-commented", "refs.bib"],
|
|
241
|
+
])("%s: reads %s", (paper, file) => {
|
|
242
|
+
const dir = join(root, "planted", paper);
|
|
243
|
+
plantedOnDisk(dir, paper);
|
|
244
|
+
const r = runNode(SCRIPT, [dir, "--json"], {
|
|
245
|
+
nodeArgs: ["--import", join(root, "dblp.mjs")],
|
|
246
|
+
});
|
|
247
|
+
const report: unknown = parseJson(r.stdout);
|
|
248
|
+
assert.deepEqual(
|
|
249
|
+
report !== null && typeof report === "object" && "files" in report
|
|
250
|
+
? report.files
|
|
251
|
+
: report,
|
|
252
|
+
[join(dir, file)],
|
|
253
|
+
);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("🔴 a paper that was not built is refused — run `npx paperlint build` first — and so is one changed since", () => {
|
|
257
|
+
const unbuilt = join(root, "unbuilt");
|
|
258
|
+
writeTree(unbuilt, {
|
|
259
|
+
"paper.tex": declaring("refs"),
|
|
260
|
+
"refs.bib": bib("good", "Ada Lovelace", "All Good"),
|
|
261
|
+
});
|
|
262
|
+
assert.deepEqual(
|
|
263
|
+
[run(unbuilt), run(join(unbuilt, "paper.tex"))].map(
|
|
264
|
+
({ status, stdout, stderr }) => ({ status, stdout, stderr }),
|
|
265
|
+
),
|
|
266
|
+
Array.from({ length: 2 }, () => ({
|
|
267
|
+
status: 2,
|
|
268
|
+
stdout: "",
|
|
269
|
+
stderr:
|
|
270
|
+
"bib-authors: <root>/unbuilt has not been built — run `npx paperlint build` first, which records the databases bibtex reads\n",
|
|
271
|
+
})),
|
|
272
|
+
);
|
|
273
|
+
recordOnDisk(unbuilt, keeps("refs.bib"));
|
|
274
|
+
writeTree(unbuilt, {
|
|
275
|
+
"refs.bib": bib("good", "Ada Lovelace", "All Good Edited"),
|
|
276
|
+
});
|
|
277
|
+
assert.deepEqual(run(unbuilt), {
|
|
278
|
+
status: 2,
|
|
279
|
+
stdout: "",
|
|
280
|
+
stderr:
|
|
281
|
+
"bib-authors: <root>/unbuilt changed since the last build (refs.bib edited) — run `npx paperlint build` first\n",
|
|
282
|
+
});
|
|
283
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Types for bib-authors.mjs, for the exports `src/` imports — the same arrangement as
|
|
2
2
|
// `verify-cites.d.mts` beside it. Only what `src/` imports is declared.
|
|
3
3
|
|
|
4
|
-
/** One entry as `
|
|
4
|
+
/** One entry as `authorEntries` returns it; a field the entry lacks is "". */
|
|
5
5
|
export interface BibEntry {
|
|
6
6
|
readonly type: string;
|
|
7
7
|
readonly key: string;
|
|
@@ -11,7 +11,27 @@ export interface BibEntry {
|
|
|
11
11
|
readonly journal: string;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
/** A name of a name list, in its parts (`BibName`). */
|
|
15
|
+
export interface ReadName {
|
|
16
|
+
readonly lastName?: string;
|
|
17
|
+
readonly firstName?: string;
|
|
18
|
+
readonly prefix?: string;
|
|
19
|
+
readonly suffix?: string;
|
|
20
|
+
readonly name?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** An entry as the bibtex reader reads it (src/domain/paper-sources.ts), as far as this check uses it. */
|
|
24
|
+
export interface ReadEntry {
|
|
25
|
+
readonly type: string;
|
|
26
|
+
readonly key: string;
|
|
27
|
+
readonly fields: Readonly<Record<string, string>>;
|
|
28
|
+
readonly names: Readonly<Record<string, readonly ReadName[]>>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The entries this check reads, from the bibtex reader's. */
|
|
32
|
+
export declare function authorEntries(
|
|
33
|
+
entries: readonly ReadEntry[],
|
|
34
|
+
): BibEntry[];
|
|
15
35
|
|
|
16
36
|
/** True when the author list ends in `and others` — there is nothing complete to compare. */
|
|
17
37
|
export declare const truncated: (authorField: string) => boolean;
|