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,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* prose-lint.mjs as a process — the way the skill runs it. Every mode, on
|
|
2
|
+
* prose-lint.mjs as a process — the way the skill runs it. Every mode, on an accepted paper and on
|
|
3
3
|
* fixtures written here, with the WHOLE output compared: the report is the product, so a changed
|
|
4
4
|
* number or a dropped line is a changed result.
|
|
5
5
|
*/
|
|
@@ -8,87 +8,146 @@ import { join } from "node:path";
|
|
|
8
8
|
import { dirname } from "node:path";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
import { expect, test } from "vitest";
|
|
11
|
+
import { cpSync } from "node:fs";
|
|
12
|
+
import { layRecord, recordedTree } from "../../test/recorded-fixture.ts";
|
|
11
13
|
import { runNode, useTempDir, writeTree } from "../../test/support.ts";
|
|
12
14
|
|
|
13
15
|
const SCRIPT = join(dirname(fileURLToPath(import.meta.url)), "prose-lint.mjs");
|
|
16
|
+
/**
|
|
17
|
+
* One process that loads the LaTeX parser and reads a full accepted paper: about 1 s alone, 5.6 s
|
|
18
|
+
* measured under coverage with the suite running in parallel — past vitest's 5 s default.
|
|
19
|
+
*/
|
|
20
|
+
const SPAWN_LATEX_MS = 60_000;
|
|
21
|
+
/** An accepted paper whose bibliography sits inline in `filecontents`, beside a long preamble. */
|
|
22
|
+
const ACCEPTED_DIR = join(
|
|
23
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
24
|
+
"..",
|
|
25
|
+
"..",
|
|
26
|
+
"fixtures",
|
|
27
|
+
"accepted-papers",
|
|
28
|
+
"agenticdev-acm26",
|
|
29
|
+
);
|
|
14
30
|
const root = useTempDir("prose-lint-");
|
|
31
|
+
/** The accepted paper as a build left it: a copy with TeX's record laid at `_build/sources.json`. */
|
|
32
|
+
const ACCEPTED = join(root, "accepted", "paper.tex");
|
|
15
33
|
|
|
16
34
|
const LONG_CAPTION_SENTENCE = Array.from(
|
|
17
35
|
{ length: 45 },
|
|
18
36
|
(_, i) => `word${String(i)}`,
|
|
19
37
|
).join(" ");
|
|
20
|
-
const PAPER = `---
|
|
21
|
-
title: A title the linter must not read as a sentence
|
|
22
|
-
---
|
|
23
|
-
# The paper
|
|
24
|
-
|
|
25
|
-
<!-- TIGHTEN: a working note, not prose -->
|
|
26
|
-
|
|
27
|
-
## Introduction
|
|
28
|
-
|
|
29
|
-
In this paper we present a linter. It might possibly help, but this is not a proof. Clearly the
|
|
30
|
-
first result matters (Section 3). Prior work agrees [1, 2]. The speedup was 40% on average across the
|
|
31
|
-
full suite of papers, which suggests the method might hold generally.
|
|
32
|
-
|
|
33
|
-
- The first item is short.
|
|
34
|
-
- The second item is not long either.
|
|
35
|
-
|
|
36
|
-
\`\`\`
|
|
37
|
-
# a heading inside a code block is code
|
|
38
|
-
\`\`\`
|
|
39
|
-
|
|
40
|
-
| a | table |
|
|
41
|
-
|---|---|
|
|
42
|
-
| is | data |
|
|
43
|
-
|
|
44
|
-
### Method
|
|
45
|
-
|
|
46
|
-
We believe that rather than guessing, we measure. Instead of prose, the numbers speak.
|
|
47
38
|
|
|
48
|
-
|
|
39
|
+
/** A paper.tex whose body is `body`. */
|
|
40
|
+
const tex = (body: string): string =>
|
|
41
|
+
`\\documentclass{article}\n\\begin{document}\n${body}\n\\end{document}\n`;
|
|
49
42
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"p/paper.md": PAPER,
|
|
43
|
+
const TREE = {
|
|
44
|
+
"p/paper.tex": tex(
|
|
45
|
+
"\\section{Introduction}\nIn this paper we present a linter. It might possibly help.\n\\input{figures/fig1}\n\\input{figures/fig2}\n\\input{figures/fig4}\n\\input{figures/fig5}",
|
|
46
|
+
),
|
|
55
47
|
"p/figures/fig1.tex": `\\begin{figure}\\caption{\\textbf{Big.} ${LONG_CAPTION_SENTENCE}. ${"More words here. ".repeat(20)}}\\end{figure}`,
|
|
56
48
|
// Empty once LaTeX commands and braces are stripped: skipped, not counted as a zero-word caption.
|
|
57
49
|
"p/figures/fig2.tex": "\\caption{\\centering}",
|
|
58
|
-
// A lone "#": no word at all
|
|
50
|
+
// A lone "#": no word at all — measured as zero words, never flagged.
|
|
59
51
|
"p/figures/fig4.tex": "\\caption{{#}}",
|
|
52
|
+
// Rules only: seen by the reader, but no word once the dashes are stripped — skipped.
|
|
53
|
+
"p/figures/fig5.tex": "\\caption{---}",
|
|
60
54
|
"p/figures/notes.txt": "not a figure",
|
|
61
|
-
|
|
62
|
-
// the
|
|
63
|
-
"
|
|
64
|
-
|
|
55
|
+
"clean/paper.tex": tex("Short and plain. Nothing else."),
|
|
56
|
+
// Captions in the paper itself and in a section it includes — not under figures/.
|
|
57
|
+
"inline/paper.tex": tex(
|
|
58
|
+
`Prose here. \\begin{figure}\\caption{${LONG_CAPTION_SENTENCE}.}\\end{figure}\n\\input{sec/results}`,
|
|
59
|
+
),
|
|
60
|
+
"inline/sec/results.tex": `\\begin{figure}\\caption{${"Short words here. ".repeat(40)}}\\end{figure}`,
|
|
61
|
+
// One real over-long caption, and the same caption commented out and inside verbatim.
|
|
62
|
+
"commented/paper.tex": tex(
|
|
63
|
+
`Prose here.\n% \\caption{Commented ${LONG_CAPTION_SENTENCE}.}\n\\begin{verbatim}\n\\caption{Quoted ${LONG_CAPTION_SENTENCE}.}\n\\end{verbatim}\n\\begin{figure}\\caption{Real ${LONG_CAPTION_SENTENCE}.}\\end{figure}`,
|
|
64
|
+
),
|
|
65
|
+
"ends-on-marks/paper.tex": tex(
|
|
66
|
+
"The method holds on every input~\\cite{knuth}. The proof is given in full (Section~\\ref{proof}). The result is new.",
|
|
67
|
+
),
|
|
68
|
+
"empty/paper.tex": "\\documentclass{article}",
|
|
65
69
|
"page.txt":
|
|
66
70
|
"A rendered page. It was hyphen-\nated across a line.\n\n12\n\fNext page text here.\nReferences\n[1] cut here.\n",
|
|
67
|
-
"
|
|
68
|
-
}
|
|
71
|
+
"notes.rtf": "not a paper",
|
|
72
|
+
};
|
|
73
|
+
/** What each paper's last build read: its paper.tex, and the files it includes. */
|
|
74
|
+
const READ: Readonly<Record<string, readonly string[]>> = {
|
|
75
|
+
p: [
|
|
76
|
+
"paper.tex",
|
|
77
|
+
"figures/fig1.tex",
|
|
78
|
+
"figures/fig2.tex",
|
|
79
|
+
"figures/fig4.tex",
|
|
80
|
+
"figures/fig5.tex",
|
|
81
|
+
],
|
|
82
|
+
clean: ["paper.tex"],
|
|
83
|
+
inline: ["paper.tex", "sec/results.tex"],
|
|
84
|
+
commented: ["paper.tex"],
|
|
85
|
+
"ends-on-marks": ["paper.tex"],
|
|
86
|
+
empty: ["paper.tex"],
|
|
87
|
+
};
|
|
88
|
+
writeTree(
|
|
89
|
+
root,
|
|
90
|
+
Object.entries(READ).reduce<Record<string, string>>(
|
|
91
|
+
(tree, [dir, files]) =>
|
|
92
|
+
recordedTree(
|
|
93
|
+
tree,
|
|
94
|
+
dir,
|
|
95
|
+
files.map((path) => ({ path, role: "body" as const })),
|
|
96
|
+
),
|
|
97
|
+
TREE,
|
|
98
|
+
),
|
|
99
|
+
);
|
|
100
|
+
cpSync(ACCEPTED_DIR, join(root, "accepted"), { recursive: true });
|
|
101
|
+
layRecord(join(root, "accepted"), join(ACCEPTED_DIR, "tex-truth.json"));
|
|
69
102
|
|
|
70
103
|
test("no arguments: the usage line, exit 0", () => {
|
|
71
104
|
assert.deepEqual(runNode(SCRIPT), {
|
|
72
105
|
status: 0,
|
|
73
106
|
stdout: "",
|
|
74
|
-
stderr:
|
|
107
|
+
stderr:
|
|
108
|
+
"usage: node prose-lint.mjs <paper.tex|page.txt> [more files] [--flags-only|--headings]\n",
|
|
75
109
|
});
|
|
76
110
|
});
|
|
77
111
|
|
|
78
|
-
test(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
112
|
+
test(
|
|
113
|
+
"🔴 a paper.tex is measured on the prose a reader sees — not its preamble, comments or inline .bib",
|
|
114
|
+
{ timeout: SPAWN_LATEX_MS },
|
|
115
|
+
() => {
|
|
116
|
+
const r = runNode(SCRIPT, [ACCEPTED]);
|
|
117
|
+
// Guards: a .tex read as raw text counted the preamble, the comments and the `filecontents`
|
|
118
|
+
// bibliography — 8127 "words" on this paper's submitted version against about 4636 real ones.
|
|
119
|
+
const words = Number(
|
|
120
|
+
/^=== paper\.tex — (\d+) words, \d+ sentences ===$/m.exec(r.stdout)?.[1],
|
|
121
|
+
);
|
|
122
|
+
assert.deepEqual(
|
|
123
|
+
{
|
|
124
|
+
status: r.status,
|
|
125
|
+
stderr: r.stderr,
|
|
126
|
+
inBand: words > 4000 && words < 5500,
|
|
127
|
+
},
|
|
128
|
+
{ status: 0, stderr: "", inBand: true },
|
|
129
|
+
);
|
|
130
|
+
// No BibTeX field and no preamble command reaches the measured text.
|
|
131
|
+
assert.doesNotMatch(r.stdout, /author\s*=|\\usepackage|\\documentclass/);
|
|
132
|
+
expect(r.stdout).toMatchSnapshot();
|
|
133
|
+
},
|
|
134
|
+
);
|
|
86
135
|
|
|
87
|
-
test(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
136
|
+
test(
|
|
137
|
+
"--headings on a paper.tex lists its section titles, in order",
|
|
138
|
+
{ timeout: SPAWN_LATEX_MS },
|
|
139
|
+
() => {
|
|
140
|
+
const r = runNode(SCRIPT, ["--headings", ACCEPTED]);
|
|
141
|
+
assert.deepEqual(
|
|
142
|
+
{ status: r.status, stderr: r.stderr },
|
|
143
|
+
{ status: 0, stderr: "" },
|
|
144
|
+
);
|
|
145
|
+
expect(r.stdout).toMatchSnapshot();
|
|
146
|
+
},
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
test("the report over a rendered page: page numbers, hyphenation and the references are dropped", () => {
|
|
150
|
+
const r = runNode(SCRIPT, [join(root, "page.txt")]);
|
|
92
151
|
assert.deepEqual(
|
|
93
152
|
{ status: r.status, stderr: r.stderr },
|
|
94
153
|
{ status: 0, stderr: "" },
|
|
@@ -96,17 +155,26 @@ test("the full report over markdown and rendered text", () => {
|
|
|
96
155
|
expect(r.stdout).toMatchSnapshot();
|
|
97
156
|
});
|
|
98
157
|
|
|
99
|
-
test("--headings
|
|
100
|
-
const
|
|
101
|
-
assert.deepEqual(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
158
|
+
test("--headings on a rendered page is refused: it marks no heading", () => {
|
|
159
|
+
const file = join(root, "page.txt");
|
|
160
|
+
assert.deepEqual(runNode(SCRIPT, ["--headings", file]), {
|
|
161
|
+
status: 2,
|
|
162
|
+
stdout: "",
|
|
163
|
+
stderr: `prose-lint: --headings reads a paper.tex — a rendered page (${file}) marks no heading\n`,
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("a file that is neither a paper.tex nor a rendered page is refused with exit 2, and nothing is measured", () => {
|
|
168
|
+
const file = join(root, "notes.rtf");
|
|
169
|
+
assert.deepEqual(runNode(SCRIPT, [file]), {
|
|
170
|
+
status: 2,
|
|
171
|
+
stdout: "",
|
|
172
|
+
stderr: `prose-lint reads a paper.tex or the text of its rendered PDF (.txt), not: ${file}\n`,
|
|
173
|
+
});
|
|
106
174
|
});
|
|
107
175
|
|
|
108
176
|
test("--flags-only: an over-long caption flags and exits 1", () => {
|
|
109
|
-
const r = runNode(SCRIPT, ["--flags-only", join(root, "p", "paper.
|
|
177
|
+
const r = runNode(SCRIPT, ["--flags-only", join(root, "p", "paper.tex")]);
|
|
110
178
|
assert.deepEqual(
|
|
111
179
|
{ status: r.status, stdout: r.stdout },
|
|
112
180
|
{ status: 1, stdout: "" },
|
|
@@ -116,38 +184,121 @@ test("--flags-only: an over-long caption flags and exits 1", () => {
|
|
|
116
184
|
|
|
117
185
|
test("--flags-only: a paper with no figures directory is clean, exit 0 and silent", () => {
|
|
118
186
|
assert.deepEqual(
|
|
119
|
-
runNode(SCRIPT, ["--flags-only", join(root, "clean", "paper.
|
|
187
|
+
runNode(SCRIPT, ["--flags-only", join(root, "clean", "paper.tex")]),
|
|
188
|
+
{ status: 0, stdout: "", stderr: "" },
|
|
189
|
+
);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("🔴 a paper no build has recorded is read as paper.tex alone, and the run says so", () => {
|
|
193
|
+
writeTree(root, {
|
|
194
|
+
"unbuilt/paper.tex": tex("Short and plain. Nothing else."),
|
|
195
|
+
});
|
|
196
|
+
const file = join(root, "unbuilt", "paper.tex");
|
|
197
|
+
const r = runNode(SCRIPT, ["--flags-only", file]);
|
|
198
|
+
assert.deepEqual(
|
|
199
|
+
{ status: r.status, stdout: r.stdout, stderr: r.stderr },
|
|
120
200
|
{
|
|
121
201
|
status: 0,
|
|
122
202
|
stdout: "",
|
|
123
|
-
stderr:
|
|
203
|
+
stderr: `prose-lint: ${file} has no current build record (_build/sources.json), so only paper.tex was read, not the files it includes — run \`npx paperlint build\` first\n`,
|
|
124
204
|
},
|
|
125
205
|
);
|
|
126
206
|
});
|
|
127
207
|
|
|
128
|
-
test("a
|
|
129
|
-
|
|
130
|
-
"empty/paper.md":
|
|
131
|
-
"---\ntitle: only frontmatter\n---\n<!-- and a note -->\n",
|
|
132
|
-
});
|
|
133
|
-
const file = join(root, "empty", "paper.md");
|
|
208
|
+
test("a .tex with no prose in its body is refused with exit 2, not reported as NaN", () => {
|
|
209
|
+
const file = join(root, "empty", "paper.tex");
|
|
134
210
|
assert.deepEqual(runNode(SCRIPT, [file]), {
|
|
135
211
|
status: 2,
|
|
136
212
|
stdout: "",
|
|
137
213
|
stderr:
|
|
138
|
-
`prose-lint: ${file} has no prose to measure — after the
|
|
139
|
-
"
|
|
214
|
+
`prose-lint: ${file} has no prose to measure — after the preamble, comments, floats and ` +
|
|
215
|
+
"the back matter are left out, no word is left.\n",
|
|
140
216
|
});
|
|
141
217
|
});
|
|
142
218
|
|
|
143
|
-
test("a
|
|
144
|
-
writeTree(root, { "
|
|
145
|
-
const file = join(root, "
|
|
219
|
+
test("a rendered page with no sentence before its references is refused the same way", () => {
|
|
220
|
+
writeTree(root, { "blank.txt": "12\nReferences\n[1] only this.\n" });
|
|
221
|
+
const file = join(root, "blank.txt");
|
|
146
222
|
assert.deepEqual(runNode(SCRIPT, [file]), {
|
|
147
223
|
status: 2,
|
|
148
224
|
stdout: "",
|
|
149
|
-
stderr:
|
|
150
|
-
|
|
151
|
-
|
|
225
|
+
stderr: `prose-lint: ${file} has no prose to measure — no sentence is left before the references.\n`,
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test("🔴 in a paper.tex, a sentence ending on a citation or a cross-reference wastes its stress position", () => {
|
|
230
|
+
const r = runNode(SCRIPT, [join(root, "ends-on-marks", "paper.tex")]);
|
|
231
|
+
// Guards: the marks used to be dropped from the prose, so neither ending could be seen.
|
|
232
|
+
assert.match(
|
|
233
|
+
r.stdout,
|
|
234
|
+
/^FLAG sentences ending on a cross-ref\/citation\/hedge \(wasted stress position\): 2$/m,
|
|
235
|
+
);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("🔴 --flags-only reads the captions of the paper.tex and of the files it includes", () => {
|
|
239
|
+
const r = runNode(SCRIPT, [
|
|
240
|
+
"--flags-only",
|
|
241
|
+
join(root, "inline", "paper.tex"),
|
|
242
|
+
]);
|
|
243
|
+
// Guards: captions were read only from figures/*.tex beside the paper, so a caption set inline,
|
|
244
|
+
// or in an included section, was never checked and the run exited clean.
|
|
245
|
+
assert.deepEqual(
|
|
246
|
+
{
|
|
247
|
+
status: r.status,
|
|
248
|
+
stdout: r.stdout,
|
|
249
|
+
flagged: r.stderr
|
|
250
|
+
.split("\n")
|
|
251
|
+
.filter((l) => l.startsWith(" "))
|
|
252
|
+
.map((l) => l.split(":")[0]),
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
status: 1,
|
|
256
|
+
stdout: "",
|
|
257
|
+
flagged: [
|
|
258
|
+
" paper.tex",
|
|
259
|
+
" sec/results.tex",
|
|
260
|
+
" run prose-lint.mjs on the file without --flags-only for the sentences",
|
|
261
|
+
],
|
|
262
|
+
},
|
|
263
|
+
);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("--flags-only on a rendered page reads the captions in figures/*.tex beside it", () => {
|
|
267
|
+
writeTree(root, {
|
|
268
|
+
"rendered/page.txt": "A rendered page. Its prose is fine.\n",
|
|
269
|
+
"rendered/figures/f.tex": `\\caption{${LONG_CAPTION_SENTENCE}.}`,
|
|
270
|
+
"rendered/figures/notes.txt": "not a figure",
|
|
152
271
|
});
|
|
272
|
+
const r = runNode(SCRIPT, [
|
|
273
|
+
"--flags-only",
|
|
274
|
+
join(root, "rendered", "page.txt"),
|
|
275
|
+
]);
|
|
276
|
+
assert.deepEqual(
|
|
277
|
+
{ status: r.status, first: r.stderr.split("\n")[1]?.split(":")[0] },
|
|
278
|
+
{ status: 1, first: " figures/f.tex" },
|
|
279
|
+
);
|
|
280
|
+
// …and a page with no figures/ beside it has no caption to flag.
|
|
281
|
+
assert.deepEqual(runNode(SCRIPT, ["--flags-only", join(root, "page.txt")]), {
|
|
282
|
+
status: 0,
|
|
283
|
+
stdout: "",
|
|
284
|
+
stderr: "",
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test("🔴 a caption commented out or quoted in verbatim is not measured; a real one still is", () => {
|
|
289
|
+
const r = runNode(SCRIPT, [
|
|
290
|
+
"--flags-only",
|
|
291
|
+
join(root, "commented", "paper.tex"),
|
|
292
|
+
]);
|
|
293
|
+
// Guards: the scan matched every `\caption{` in the raw text, comments and verbatim included.
|
|
294
|
+
assert.deepEqual(
|
|
295
|
+
{
|
|
296
|
+
status: r.status,
|
|
297
|
+
findings: r.stderr
|
|
298
|
+
.split("\n")
|
|
299
|
+
.filter((l) => l.startsWith(" paper.tex:"))
|
|
300
|
+
.map((l) => /— "(\w+)/.exec(l)?.[1]),
|
|
301
|
+
},
|
|
302
|
+
{ status: 1, findings: ["Real"] },
|
|
303
|
+
);
|
|
153
304
|
});
|
|
@@ -5,7 +5,7 @@ context: fork
|
|
|
5
5
|
allowed-tools: [Read, Write, Grep, Glob, Agent, Skill]
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
<!-- vigiles:sha256:
|
|
8
|
+
<!-- vigiles:sha256:cb852f6fb5fb9988 compiled from skills/paper-adversarial-review/SKILL.md.spec.ts -->
|
|
9
9
|
|
|
10
10
|
# paper-adversarial-review — be the reviewer who wants to reject it
|
|
11
11
|
|
|
@@ -22,7 +22,7 @@ reviewer and returns an actionable critique.
|
|
|
22
22
|
|
|
23
23
|
## How to run it
|
|
24
24
|
Prefer a **separate model as the reviewer** (e.g. Fable via a subagent) so it's not the author
|
|
25
|
-
grading itself. Give the reviewer the full paper text (Read the `.tex
|
|
25
|
+
grading itself. Give the reviewer the full paper text (Read the `.tex`/PDF) and the target
|
|
26
26
|
venue + paper type (short/full/position/benchmark). Ask for the structured output below.
|
|
27
27
|
|
|
28
28
|
## The review must cover (ranked by how often it kills a paper)
|
|
@@ -28,7 +28,7 @@ reviewer and returns an actionable critique.
|
|
|
28
28
|
|
|
29
29
|
## How to run it
|
|
30
30
|
Prefer a **separate model as the reviewer** (e.g. Fable via a subagent) so it's not the author
|
|
31
|
-
grading itself. Give the reviewer the full paper text (Read the \`.tex
|
|
31
|
+
grading itself. Give the reviewer the full paper text (Read the \`.tex\`/PDF) and the target
|
|
32
32
|
venue + paper type (short/full/position/benchmark). Ask for the structured output below.
|
|
33
33
|
|
|
34
34
|
## The review must cover (ranked by how often it kills a paper)
|
|
@@ -4,7 +4,7 @@ description: The orchestrator for writing a research paper end-to-end, from idea
|
|
|
4
4
|
allowed-tools: [Read, Write, Edit, Grep, Glob, Bash, WebSearch, WebFetch, Agent, Skill]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
<!-- vigiles:sha256:
|
|
7
|
+
<!-- vigiles:sha256:67a3989629952dc1 compiled from skills/paper-pipeline/SKILL.md.spec.ts -->
|
|
8
8
|
|
|
9
9
|
# paper-pipeline — the conductor for the whole organism
|
|
10
10
|
|
|
@@ -191,7 +191,7 @@ These have no position, because a position is what made them look finished. Each
|
|
|
191
191
|
| any `\cite` added or moved | **`verify-citations`** | An edit on the last day adds a citation the last run never saw. "We checked last cycle" does not count — the project rule, and it was numbered as a one-shot anyway. |
|
|
192
192
|
| a genuine sibling surfaces | **`analyze-sibling-paper`** | Deep-read, scoop/delta, saved in `<paper-dir>/siblings/`. |
|
|
193
193
|
| the contribution's framing moves | **`map-prior-work`** (delta mode) | A cheap re-sweep. ⚠️ If a rival first surfaces during citation checking, the SETUP sweep was skipped and you are reshaping the contribution under deadline. |
|
|
194
|
-
| any `.tex`
|
|
194
|
+
| any `.tex` edit | **`render-paper`** + page count | **Not a stage — a command.** Render, then eyeball pages 1–2 before believing anything about the paper. 🔴 **Rendering is continuous; ACTING on the page count is not** — see the rule below. |
|
|
195
195
|
| the deadline approaches | the `access` row | Re-check that the profile is still ACTIVE and the portal still opens. External state rots without telling you. |
|
|
196
196
|
|
|
197
197
|
## GATES — late, expensive, each blocked on a named input
|
|
@@ -310,25 +310,22 @@ binds every row from now on, and the existing rows are *enumerated rather than e
|
|
|
310
310
|
says out loud how many figures still have no machine-checked path to data. On the paper that has it:
|
|
311
311
|
7 guarded, 248 grandfathered. That ratio is a finding, not a score.
|
|
312
312
|
|
|
313
|
-
**Adopting it in another paper:** copy `repro/paper_numbers.py`, `repro/paper_numbers.selftest.py`
|
|
314
|
-
the
|
|
315
|
-
current draft; register the numbers a reviewer will actually check first.
|
|
316
|
-
reports `no-numbers-gate` for any paper that has not.
|
|
313
|
+
**Adopting it in another paper:** copy `repro/paper_numbers.py`, `repro/paper_numbers.selftest.py`
|
|
314
|
+
and the build step that calls `expand()` on `paper.tex` before `pdflatex`; generate the
|
|
315
|
+
grandfather list from the current draft; register the numbers a reviewer will actually check first.
|
|
317
316
|
|
|
318
317
|
🔴 **Do not write the substitution half — it is `jinja2`, and `{{name}}` is already its syntax.**
|
|
319
|
-
Eleven candidates were installed and run on a real
|
|
320
|
-
|
|
318
|
+
Eleven candidates were installed and run on a real ACL paper on 2026-08-05. The result is not a
|
|
319
|
+
matter of taste:
|
|
321
320
|
|
|
322
321
|
| candidate | verdict, from an actual run |
|
|
323
322
|
|---|---|
|
|
324
323
|
| **jinja2** | **adopt.** Our `{{annotated.failRate}}` was already valid Jinja2; dotted names are attribute access. Zero characters changed in the paper, byte-identical `.tex`. This is Manubot's mechanism, taken without Manubot. Use `StrictUndefined`, and move the block/comment delimiters off `{%`/`{#` if the paper might ever quote a config file |
|
|
325
324
|
| **knitr** | equally proven, also byte-identical. Rejected only for putting an R runtime into an artifact that otherwise needs `python3` + `pdflatex` |
|
|
326
|
-
| `\newcommand` + `numbers.tex` | works
|
|
327
|
-
| **Quarto**, **pandoc lua filters** | ❌ **destroy the paper.** Markdown→markdown is a pandoc AST round-trip: HTML comments become raw blocks and `[58]` becomes `\[58\]`, so every citation renders as literal `\textbackslash{}[58]`. 3,120 diff lines on the real source. No ACL format exists for Quarto either |
|
|
325
|
+
| `\newcommand` + `numbers.tex` | works; the folk convention, ~12 GitHub hits, all hand-rolled. No package exists |
|
|
328
326
|
| **cog.py** | ❌ line-oriented; cannot substitute mid-sentence |
|
|
329
327
|
| **pythontex** | ❌ not in Ubuntu TeX Live at any level, not on PyPI; CTAN zip + `.ins` build |
|
|
330
|
-
| **showyourwork** | ❌
|
|
331
|
-
| **Pweave** | ❌ dead — the one candidate with a markdown native format, broken against modern IPython |
|
|
328
|
+
| **showyourwork** | ❌ it manages *figures*, not in-text numbers |
|
|
332
329
|
| DVC · datalad · papermill | orthogonal and **worth adding**: they pin the data file to the pipeline that made it, a layer below anything here |
|
|
333
330
|
|
|
334
331
|
**🔴 Computing the value DISSOLVES the freshness problem — do not build a checker for it.** With no
|
|
@@ -208,7 +208,7 @@ These have no position, because a position is what made them look finished. Each
|
|
|
208
208
|
| any \`\\cite\` added or moved | **\`verify-citations\`** | An edit on the last day adds a citation the last run never saw. "We checked last cycle" does not count — the project rule, and it was numbered as a one-shot anyway. |
|
|
209
209
|
| a genuine sibling surfaces | **\`analyze-sibling-paper\`** | Deep-read, scoop/delta, saved in \`<paper-dir>/siblings/\`. |
|
|
210
210
|
| the contribution's framing moves | **\`map-prior-work\`** (delta mode) | A cheap re-sweep. ⚠️ If a rival first surfaces during citation checking, the SETUP sweep was skipped and you are reshaping the contribution under deadline. |
|
|
211
|
-
| any \`.tex\`
|
|
211
|
+
| any \`.tex\` edit | **\`render-paper\`** + page count | **Not a stage — a command.** Render, then eyeball pages 1–2 before believing anything about the paper. 🔴 **Rendering is continuous; ACTING on the page count is not** — see the rule below. |
|
|
212
212
|
| the deadline approaches | the \`access\` row | Re-check that the profile is still ACTIVE and the portal still opens. External state rots without telling you. |
|
|
213
213
|
|
|
214
214
|
## GATES — late, expensive, each blocked on a named input
|
|
@@ -327,25 +327,22 @@ binds every row from now on, and the existing rows are *enumerated rather than e
|
|
|
327
327
|
says out loud how many figures still have no machine-checked path to data. On the paper that has it:
|
|
328
328
|
7 guarded, 248 grandfathered. That ratio is a finding, not a score.
|
|
329
329
|
|
|
330
|
-
**Adopting it in another paper:** copy \`repro/paper_numbers.py\`, \`repro/paper_numbers.selftest.py\`
|
|
331
|
-
the
|
|
332
|
-
current draft; register the numbers a reviewer will actually check first.
|
|
333
|
-
reports \`no-numbers-gate\` for any paper that has not.
|
|
330
|
+
**Adopting it in another paper:** copy \`repro/paper_numbers.py\`, \`repro/paper_numbers.selftest.py\`
|
|
331
|
+
and the build step that calls \`expand()\` on \`paper.tex\` before \`pdflatex\`; generate the
|
|
332
|
+
grandfather list from the current draft; register the numbers a reviewer will actually check first.
|
|
334
333
|
|
|
335
334
|
🔴 **Do not write the substitution half — it is \`jinja2\`, and \`{{name}}\` is already its syntax.**
|
|
336
|
-
Eleven candidates were installed and run on a real
|
|
337
|
-
|
|
335
|
+
Eleven candidates were installed and run on a real ACL paper on 2026-08-05. The result is not a
|
|
336
|
+
matter of taste:
|
|
338
337
|
|
|
339
338
|
| candidate | verdict, from an actual run |
|
|
340
339
|
|---|---|
|
|
341
340
|
| **jinja2** | **adopt.** Our \`{{annotated.failRate}}\` was already valid Jinja2; dotted names are attribute access. Zero characters changed in the paper, byte-identical \`.tex\`. This is Manubot's mechanism, taken without Manubot. Use \`StrictUndefined\`, and move the block/comment delimiters off \`{%\`/\`{#\` if the paper might ever quote a config file |
|
|
342
341
|
| **knitr** | equally proven, also byte-identical. Rejected only for putting an R runtime into an artifact that otherwise needs \`python3\` + \`pdflatex\` |
|
|
343
|
-
| \`\\newcommand\` + \`numbers.tex\` | works
|
|
344
|
-
| **Quarto**, **pandoc lua filters** | ❌ **destroy the paper.** Markdown→markdown is a pandoc AST round-trip: HTML comments become raw blocks and \`[58]\` becomes \`\\[58\\]\`, so every citation renders as literal \`\\textbackslash{}[58]\`. 3,120 diff lines on the real source. No ACL format exists for Quarto either |
|
|
342
|
+
| \`\\newcommand\` + \`numbers.tex\` | works; the folk convention, ~12 GitHub hits, all hand-rolled. No package exists |
|
|
345
343
|
| **cog.py** | ❌ line-oriented; cannot substitute mid-sentence |
|
|
346
344
|
| **pythontex** | ❌ not in Ubuntu TeX Live at any level, not on PyPI; CTAN zip + \`.ins\` build |
|
|
347
|
-
| **showyourwork** | ❌
|
|
348
|
-
| **Pweave** | ❌ dead — the one candidate with a markdown native format, broken against modern IPython |
|
|
345
|
+
| **showyourwork** | ❌ it manages *figures*, not in-text numbers |
|
|
349
346
|
| DVC · datalad · papermill | orthogonal and **worth adding**: they pin the data file to the pipeline that made it, a layer below anything here |
|
|
350
347
|
|
|
351
348
|
**🔴 Computing the value DISSOLVES the freshness problem — do not build a checker for it.** With no
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* description-language.eval.mjs — does adding Russian to a skill's DESCRIPTION close the language gap?
|
|
3
3
|
*
|
|
4
|
+
* ⚠️ THE FIXTURE CHANGED AFTER THE RECORDED RUNS. The paper the model is shown is now `paper.tex`;
|
|
5
|
+
* every result recorded for this eval (here and in `repro/`) was measured on the previous fixture,
|
|
6
|
+
* the same paper in a source form paperlint no longer reads. The next run is a fresh, deliberate
|
|
7
|
+
* measurement — record it, do not compare it against those numbers.
|
|
8
|
+
*
|
|
4
9
|
* Run: node .claude/skills/paper-pipeline/description-language.eval.mjs --arm before|after [--trials N]
|
|
5
10
|
*
|
|
6
11
|
* --arm before measure with the descriptions AS THEY ARE (English only) <- run this FIRST
|
|
@@ -119,42 +124,47 @@ if (ARM !== "before" && ARM !== "after")
|
|
|
119
124
|
// Byte-identical to the other two evals. If these drift, a difference between runs stops being
|
|
120
125
|
// attributable to the descriptions.
|
|
121
126
|
const FIXTURE = {
|
|
122
|
-
"paper/paper.
|
|
123
|
-
"
|
|
127
|
+
"paper/paper.tex": [
|
|
128
|
+
"\\documentclass{article}",
|
|
129
|
+
"\\title{Prose Isn't Policy: Measuring Whether Agent-Config Rules Are Enforceable}",
|
|
130
|
+
"\\begin{document}",
|
|
131
|
+
"\\maketitle",
|
|
124
132
|
"",
|
|
125
|
-
"
|
|
133
|
+
"\\begin{abstract}",
|
|
126
134
|
"Agent configuration files state rules in prose and assume the model obeys them. We compile a",
|
|
127
|
-
"corpus of real rules and measure what fraction can be mechanically enforced. We find that 84
|
|
135
|
+
"corpus of real rules and measure what fraction can be mechanically enforced. We find that 84\\%",
|
|
128
136
|
"of rules in our corpus are enforceable, and that LLM-authored checkers for the remainder leak",
|
|
129
|
-
"silently in 84
|
|
137
|
+
"silently in 84--96\\% of adversarial cases~\\cite{greshake2023}.",
|
|
138
|
+
"\\end{abstract}",
|
|
130
139
|
"",
|
|
131
|
-
"
|
|
140
|
+
"\\section{Introduction}",
|
|
132
141
|
"Every agent harness ships a natural-language rulebook. Nothing checks it. This is the same",
|
|
133
|
-
"mistake as a code comment that claims an invariant no test enforces
|
|
142
|
+
"mistake as a code comment that claims an invariant no test enforces~\\cite{thompson1984}.",
|
|
134
143
|
"",
|
|
135
|
-
"
|
|
144
|
+
"\\section{Method}",
|
|
136
145
|
"We gather rules from public repositories, classify each by enforceability, and build a",
|
|
137
146
|
"two-stage adversarial gate that validates a synthesized rule against a blind gold set.",
|
|
138
147
|
"",
|
|
139
|
-
"
|
|
140
|
-
"See Table
|
|
148
|
+
"\\section{Results}",
|
|
149
|
+
"See Table~1. The headline number is 84\\%.",
|
|
141
150
|
"",
|
|
142
|
-
"
|
|
151
|
+
"\\section{Discussion}",
|
|
143
152
|
"The result generalizes beyond our corpus in the sense that the mechanism is not corpus-specific,",
|
|
144
153
|
"though of course the specific percentages are, and it is important to note in this context that",
|
|
145
154
|
"the framing itself may be what carries, rather than the measurement.",
|
|
146
155
|
"",
|
|
147
|
-
"
|
|
156
|
+
"\\section{Threats to Validity}",
|
|
148
157
|
"Our corpus is drawn from public repositories and may not represent private configurations.",
|
|
149
158
|
"",
|
|
150
|
-
"
|
|
159
|
+
"\\section{Related Work}",
|
|
151
160
|
"TODO",
|
|
152
161
|
"",
|
|
153
|
-
"
|
|
162
|
+
"\\section{Conclusion}",
|
|
154
163
|
"Prose is not policy.",
|
|
164
|
+
"\\end{document}",
|
|
155
165
|
].join("\n"),
|
|
156
166
|
"paper/repro/README.md":
|
|
157
|
-
"# Reproduction artifact\n\n`python3 paper_numbers.py` recomputes every bolded figure in paper.
|
|
167
|
+
"# Reproduction artifact\n\n`python3 paper_numbers.py` recomputes every bolded figure in paper.tex.\n",
|
|
158
168
|
"paper/PIPELINE-STATUS.md":
|
|
159
169
|
"# Pipeline status\n\n| gate | state |\n|---|---|\n| numbers | pass |\n| structure | not run |\n| citations | not run |\n",
|
|
160
170
|
};
|