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
|
@@ -10,54 +10,61 @@
|
|
|
10
10
|
* and the zero was an artifact of the empty directory, not a fact about the skill.
|
|
11
11
|
*
|
|
12
12
|
* Extracted from `paper-pipeline/pipeline-firing.eval.mjs`, which had it right
|
|
13
|
-
* from the start
|
|
13
|
+
* from the start; the `paper-pipeline/*.eval.mjs` experiments carry the same
|
|
14
|
+
* block, byte for byte.
|
|
14
15
|
*
|
|
15
16
|
* ⚠️ AND THE PRICE, so this file is not a one-sided story (measured 2026-08-12).
|
|
16
17
|
* With a fixture present the model OPENS THE PAPER and looks around, so the number
|
|
17
18
|
* stops being "did the DESCRIPTION fire" and becomes "did the model go investigate
|
|
18
19
|
* and then fire". A different quantity, and a costlier one: ~590k → ~780k tokens
|
|
19
|
-
* per skill (+190k) and 67s → 84s, while
|
|
20
|
-
* ≈ 384 tokens. The growth is reading, not input size (`cache`
|
|
20
|
+
* per skill (+190k) and 67s → 84s, while the fixture measured then was 1,537
|
|
21
|
+
* characters ≈ 384 tokens. The growth is reading, not input size (`cache`
|
|
22
|
+
* 470k → 652k).
|
|
21
23
|
* Cite the trigger numbers with that caveat attached — it is written out in
|
|
22
24
|
* `vigiles/repro/skill-trigger-2026-08-11/README.md` under "How to read".
|
|
23
25
|
*/
|
|
24
26
|
export const FIXTURE = {
|
|
25
|
-
"paper/paper.
|
|
26
|
-
"
|
|
27
|
+
"paper/paper.tex": [
|
|
28
|
+
"\\documentclass{article}",
|
|
29
|
+
"\\title{Prose Isn't Policy: Measuring Whether Agent-Config Rules Are Enforceable}",
|
|
30
|
+
"\\begin{document}",
|
|
31
|
+
"\\maketitle",
|
|
27
32
|
"",
|
|
28
|
-
"
|
|
33
|
+
"\\begin{abstract}",
|
|
29
34
|
"Agent configuration files state rules in prose and assume the model obeys them. We compile a",
|
|
30
|
-
"corpus of real rules and measure what fraction can be mechanically enforced. We find that 84
|
|
35
|
+
"corpus of real rules and measure what fraction can be mechanically enforced. We find that 84\\%",
|
|
31
36
|
"of rules in our corpus are enforceable, and that LLM-authored checkers for the remainder leak",
|
|
32
|
-
"silently in 84
|
|
37
|
+
"silently in 84--96\\% of adversarial cases~\\cite{greshake2023}.",
|
|
38
|
+
"\\end{abstract}",
|
|
33
39
|
"",
|
|
34
|
-
"
|
|
40
|
+
"\\section{Introduction}",
|
|
35
41
|
"Every agent harness ships a natural-language rulebook. Nothing checks it. This is the same",
|
|
36
|
-
"mistake as a code comment that claims an invariant no test enforces
|
|
42
|
+
"mistake as a code comment that claims an invariant no test enforces~\\cite{thompson1984}.",
|
|
37
43
|
"",
|
|
38
|
-
"
|
|
44
|
+
"\\section{Method}",
|
|
39
45
|
"We gather rules from public repositories, classify each by enforceability, and build a",
|
|
40
46
|
"two-stage adversarial gate that validates a synthesized rule against a blind gold set.",
|
|
41
47
|
"",
|
|
42
|
-
"
|
|
43
|
-
"See Table
|
|
48
|
+
"\\section{Results}",
|
|
49
|
+
"See Table~1. The headline number is 84\\%.",
|
|
44
50
|
"",
|
|
45
|
-
"
|
|
51
|
+
"\\section{Discussion}",
|
|
46
52
|
"The result generalizes beyond our corpus in the sense that the mechanism is not corpus-specific,",
|
|
47
53
|
"though of course the specific percentages are, and it is important to note in this context that",
|
|
48
54
|
"the framing itself may be what carries, rather than the measurement.",
|
|
49
55
|
"",
|
|
50
|
-
"
|
|
56
|
+
"\\section{Threats to Validity}",
|
|
51
57
|
"Our corpus is drawn from public repositories and may not represent private configurations.",
|
|
52
58
|
"",
|
|
53
|
-
"
|
|
59
|
+
"\\section{Related Work}",
|
|
54
60
|
"TODO",
|
|
55
61
|
"",
|
|
56
|
-
"
|
|
62
|
+
"\\section{Conclusion}",
|
|
57
63
|
"Prose is not policy.",
|
|
64
|
+
"\\end{document}",
|
|
58
65
|
].join("\n"),
|
|
59
66
|
"paper/repro/README.md":
|
|
60
|
-
"# Reproduction artifact\n\n`python3 paper_numbers.py` recomputes every bolded figure in paper.
|
|
67
|
+
"# Reproduction artifact\n\n`python3 paper_numbers.py` recomputes every bolded figure in paper.tex.\n",
|
|
61
68
|
"paper/PIPELINE-STATUS.md":
|
|
62
69
|
"# Pipeline status\n\n| gate | state |\n|---|---|\n| numbers | pass |\n| structure | not run |\n| citations | not run |\n",
|
|
63
70
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "paperlint",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.0.0",
|
|
4
4
|
"description": "A linter for scientific papers written in LaTeX: catches mistakes before you submit to a conference or journal.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@eslint/markdown": "^8.0.3",
|
|
22
22
|
"@eslint/plugin-kit": "^0.7.2",
|
|
23
|
+
"@retorquere/bibtex-parser": "^10.0.1",
|
|
23
24
|
"@unified-latex/unified-latex-types": "^1.8.4",
|
|
24
25
|
"@unified-latex/unified-latex-util-parse": "^1.8.4",
|
|
25
26
|
"@unified-latex/unified-latex-util-visit": "^1.8.4",
|
|
@@ -37,7 +38,6 @@
|
|
|
37
38
|
"zod": "^4.6.5"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
|
-
"@retorquere/bibtex-parser": "^10.0.1",
|
|
41
41
|
"@semantic-release/commit-analyzer": "13.0.1",
|
|
42
42
|
"@semantic-release/release-notes-generator": "14.1.1",
|
|
43
43
|
"@types/js-yaml": "^4.0.9",
|
|
@@ -56,14 +56,6 @@
|
|
|
56
56
|
"typescript-eslint": "^8.70.1",
|
|
57
57
|
"vitest": "^4.1.11"
|
|
58
58
|
},
|
|
59
|
-
"peerDependencies": {
|
|
60
|
-
"@retorquere/bibtex-parser": "^10.0.1"
|
|
61
|
-
},
|
|
62
|
-
"peerDependenciesMeta": {
|
|
63
|
-
"@retorquere/bibtex-parser": {
|
|
64
|
-
"optional": true
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
59
|
"repository": {
|
|
68
60
|
"type": "git",
|
|
69
61
|
"url": "git+https://github.com/zernie/paperlint.git"
|
|
@@ -114,8 +106,6 @@
|
|
|
114
106
|
"eslint.config.mjs",
|
|
115
107
|
"fixtures",
|
|
116
108
|
"!fixtures/accepted-papers",
|
|
117
|
-
"!fixtures/real-markdown-paper",
|
|
118
|
-
"fixtures/real-markdown-paper/baseline.*",
|
|
119
109
|
"hooks",
|
|
120
110
|
"lib",
|
|
121
111
|
"presets",
|
package/presets/realm.jsonc
CHANGED
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
//
|
|
73
73
|
// A gate on banal's number would give 13 against a limit of 8 and would redden on a paper that
|
|
74
74
|
// is within the limit. For an `error`-level rule that is the worst outcome. The limit stays
|
|
75
|
-
// owned by the counter inside `build-submission.sh`: it reads the
|
|
75
|
+
// owned by the counter inside `build-submission.sh`: it reads the paper's source and therefore
|
|
76
76
|
// KNOWS where the body ends, which the PDF does not.
|
|
77
77
|
//
|
|
78
78
|
// The numbers below are DATA for that counter and for humans, not an input to the rule. Source:
|
package/scripts/check.test.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import assert from "node:assert/strict";
|
|
7
7
|
import { spawnSync } from "node:child_process";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
|
-
import { test } from "vitest";
|
|
9
|
+
import { test, vi } from "vitest";
|
|
10
10
|
import {
|
|
11
11
|
GATES,
|
|
12
12
|
NOT_COVERED,
|
|
@@ -122,6 +122,28 @@ test("🔴 runGates: a vitest gate that exits 0 but leaves no report is a FAILUR
|
|
|
122
122
|
);
|
|
123
123
|
});
|
|
124
124
|
|
|
125
|
+
test("🔴 every gate runs with CI=true, as CI runs it — a local check is the CI check", () => {
|
|
126
|
+
// Libraries change behaviour under CI=true (typescript-eslint infers a single run and parses from
|
|
127
|
+
// disk): a gate run without it passed locally and failed in CI on 2026-10-07.
|
|
128
|
+
// Whatever the caller's shell says: on CI it is already "true", so the test sets it otherwise.
|
|
129
|
+
vi.stubEnv("CI", "false");
|
|
130
|
+
const seen: (string | undefined)[] = [];
|
|
131
|
+
runGates({
|
|
132
|
+
gates: [
|
|
133
|
+
{ name: "a", job: "gates", run: ["true"] },
|
|
134
|
+
{ name: "b", job: "gates", script: "build" },
|
|
135
|
+
],
|
|
136
|
+
spawn: (_cmd, _args, options) => {
|
|
137
|
+
seen.push(options.env["CI"]);
|
|
138
|
+
return { status: 0 };
|
|
139
|
+
},
|
|
140
|
+
write: () => {},
|
|
141
|
+
...capture().io,
|
|
142
|
+
});
|
|
143
|
+
vi.unstubAllEnvs();
|
|
144
|
+
assert.deepEqual(seen, ["true", "true"]);
|
|
145
|
+
});
|
|
146
|
+
|
|
125
147
|
test("run as a program with every gate passing: the plain verdict, exit 0", () => {
|
|
126
148
|
// spawnSync is replaced before check.ts loads, so the real file runs end to end — every gate
|
|
127
149
|
// "passes" in microseconds instead of the eleven real ones running.
|
package/scripts/check.ts
CHANGED
|
@@ -30,8 +30,10 @@
|
|
|
30
30
|
*
|
|
31
31
|
* ── NO `--fast` FLAG, ON PURPOSE ───────────────────────────────────────────────────────────
|
|
32
32
|
* A subset flag re-creates the exact failure above: the cheap half gets run and reported as
|
|
33
|
-
* "the gates".
|
|
34
|
-
*
|
|
33
|
+
* "the gates". Every gate runs with CI=true, as in CI, so a local check is the CI check: the e2e
|
|
34
|
+
* steps are strict (`test/e2e/need.ts`) — a missing prerequisite (no TeX) FAILS with what to
|
|
35
|
+
* install, as it does in CI — and a test that still skips is named in the tail, never counted as a
|
|
36
|
+
* pass. "I ran check" then means one thing.
|
|
35
37
|
*/
|
|
36
38
|
import { spawnSync } from "node:child_process";
|
|
37
39
|
import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
@@ -293,7 +295,9 @@ function runEach(
|
|
|
293
295
|
cwd: ROOT,
|
|
294
296
|
stdio: "inherit",
|
|
295
297
|
encoding: "utf8",
|
|
296
|
-
|
|
298
|
+
// CI=true, as CI runs every gate: libraries read it (typescript-eslint infers a single run and
|
|
299
|
+
// parses from disk), so without it a local check is not the CI check (2026-10-07).
|
|
300
|
+
env: { ...process.env, CI: "true", PATH: BIN_FIRST_PATH },
|
|
297
301
|
});
|
|
298
302
|
// 🔴 The exit code is read from the command itself, never from a pipe. `cmd | tail && …`
|
|
299
303
|
// reports the FILTER's status, which is almost always zero — that is how a red harness
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
import assert from "node:assert/strict";
|
|
22
22
|
import { spawnSync } from "node:child_process";
|
|
23
23
|
import { globSync, readFileSync } from "node:fs";
|
|
24
|
-
import { dirname, join, matchesGlob, resolve } from "node:path";
|
|
24
|
+
import { basename, dirname, join, matchesGlob, resolve } from "node:path";
|
|
25
25
|
import { fileURLToPath } from "node:url";
|
|
26
26
|
import yaml from "js-yaml";
|
|
27
27
|
import ts from "typescript";
|
|
@@ -146,16 +146,23 @@ test("every typeOnly exclusion has no runtime code", () => {
|
|
|
146
146
|
});
|
|
147
147
|
|
|
148
148
|
test("every evalOnly exclusion is imported only by an eval or another evalOnly module", () => {
|
|
149
|
-
const
|
|
149
|
+
const only = expand(CONFIG.evalOnly);
|
|
150
|
+
const evalOnly = new Set(only.map((f) => join(ROOT, f)));
|
|
151
|
+
// Any import of one names it — `./x.mjs`, `#lib/x` — so a source whose text holds none of their
|
|
152
|
+
// base names imports none of them, and only the others are parsed.
|
|
153
|
+
const names = only.map((f) => basename(f).replace(/\.[^.]+$/, ""));
|
|
150
154
|
const sources = globSync("**/*.{mjs,ts}", {
|
|
151
155
|
cwd: ROOT,
|
|
152
156
|
exclude: (p) => p === "node_modules" || p === "dist" || p === "coverage",
|
|
153
157
|
});
|
|
154
158
|
const importers = sources.flatMap((f) => {
|
|
155
159
|
const abs = join(ROOT, f);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
160
|
+
const text = readFileSync(abs, "utf8");
|
|
161
|
+
return names.some((n) => text.includes(n))
|
|
162
|
+
? importedPaths(abs, text)
|
|
163
|
+
.filter((target) => evalOnly.has(target))
|
|
164
|
+
.map(() => f)
|
|
165
|
+
: [];
|
|
159
166
|
});
|
|
160
167
|
const outsiders = importers.filter(
|
|
161
168
|
(f) => !f.endsWith(".eval.mjs") && !evalOnly.has(join(ROOT, f)),
|
|
@@ -258,11 +265,13 @@ test("no measured source carries a coverage-ignore comment", () => {
|
|
|
258
265
|
// The scan covers exactly what c8 measures: the same set, derived from the tracked files.
|
|
259
266
|
assert.deepEqual(files, trackedSources().filter(isMeasured));
|
|
260
267
|
assert.ok(files.length > 0);
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
),
|
|
265
|
-
|
|
268
|
+
// An ignore comment's text matches IGNORE, so a source whose text does not holds none, and only
|
|
269
|
+
// the others have their comments read off the tree.
|
|
270
|
+
const offenders = files
|
|
271
|
+
.map((f) => [f, readFileSync(join(ROOT, f), "utf8")] as const)
|
|
272
|
+
.filter(([, text]) => IGNORE.test(text))
|
|
273
|
+
.filter(([f, text]) => comments(f, text).some((c) => IGNORE.test(c)))
|
|
274
|
+
.map(([f]) => f);
|
|
266
275
|
assert.deepEqual(offenders, []);
|
|
267
276
|
});
|
|
268
277
|
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
"eslint-rules/pdf-last-page-balance.harness.mjs",
|
|
11
11
|
"eslint-rules/temp-root-realpath.harness.mjs",
|
|
12
12
|
"eslint-rules/tex-build.harness.mjs",
|
|
13
|
-
"fixtures/real-markdown-paper/real-markdown-paper.harness.ts",
|
|
14
13
|
"lib/agent-cli-version.harness.mjs",
|
|
15
14
|
"lib/paper-config.harness.mjs",
|
|
16
15
|
"lib/skill-trigger-cases.harness.mjs",
|
|
@@ -30,13 +29,9 @@
|
|
|
30
29
|
"skills/osf-artifact-upload/osf-artifact-upload.harness.ts",
|
|
31
30
|
"skills/paper-adversarial-review/paper-adversarial-review.harness.mjs",
|
|
32
31
|
"skills/paper-pipeline/paper-pipeline.harness.mjs",
|
|
33
|
-
"skills/paper-pipeline/scripts/artifact-coverage.harness.mjs",
|
|
34
32
|
"skills/paper-pipeline/scripts/consumer.harness.mjs",
|
|
35
33
|
"skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs",
|
|
36
34
|
"skills/paper-pipeline/scripts/generated-code.harness.mjs",
|
|
37
|
-
"skills/paper-pipeline/scripts/population-map.harness.mjs",
|
|
38
|
-
"skills/paper-pipeline/scripts/provenance.harness.mjs",
|
|
39
|
-
"skills/paper-pipeline/scripts/round-diff.harness.ts",
|
|
40
35
|
"skills/paper-status/paper-status.harness.mjs",
|
|
41
36
|
"skills/pc-panel-review/pc-panel-review.harness.mjs",
|
|
42
37
|
"skills/plan-paper-timeline/plan-paper-timeline.harness.mjs",
|
|
@@ -9,6 +9,7 @@ import { fileURLToPath } from "node:url";
|
|
|
9
9
|
import { test } from "vitest";
|
|
10
10
|
import {
|
|
11
11
|
checkFrozen,
|
|
12
|
+
filesToLint,
|
|
12
13
|
FROZEN_FILE,
|
|
13
14
|
judge,
|
|
14
15
|
main,
|
|
@@ -164,8 +165,9 @@ test("main: problems are listed and fail; a clean count passes", async () => {
|
|
|
164
165
|
);
|
|
165
166
|
});
|
|
166
167
|
|
|
167
|
-
//
|
|
168
|
-
//
|
|
168
|
+
// Loads the real config (about 2.5 s) and lints the src/ files that carry a directive (21 of 223),
|
|
169
|
+
// measured 2026-10-07: 5.5 s alone, 10–11 s under c8 — past vitest's 5 s default, so the budget is
|
|
170
|
+
// stated here. Linting all of src/ took 22–23 s under c8 and timed out with the suite in parallel.
|
|
169
171
|
test(
|
|
170
172
|
"checkFrozen on this repository: nothing new, nothing grown — and on a tree with no src/, ESLint refuses loudly",
|
|
171
173
|
{
|
|
@@ -186,6 +188,43 @@ test(
|
|
|
186
188
|
},
|
|
187
189
|
);
|
|
188
190
|
|
|
191
|
+
test("filesToLint: every src/ file whose text carries an eslint-disable directive, in any form — and no other", () => {
|
|
192
|
+
const root = useTempDir("layer-frozen-files-");
|
|
193
|
+
writeTree(root, {
|
|
194
|
+
"src/line.ts": "export const a = 1; // eslint-disable-line no-console\n",
|
|
195
|
+
"src/next.ts":
|
|
196
|
+
"// eslint-disable-next-line no-console\nexport const b = 1;\n",
|
|
197
|
+
"src/block.ts": "/* eslint-disable no-console */\nexport const c = 1;\n",
|
|
198
|
+
"src/deep/inner.ts":
|
|
199
|
+
"// eslint-disable-next-line x -- why\nexport const d = 1;\n",
|
|
200
|
+
"src/plain.ts": "export const e = 1;\n",
|
|
201
|
+
"lib/outside.ts": "// eslint-disable-next-line no-console\n",
|
|
202
|
+
});
|
|
203
|
+
assert.deepEqual(filesToLint(root), [
|
|
204
|
+
"src/block.ts",
|
|
205
|
+
"src/deep/inner.ts",
|
|
206
|
+
"src/line.ts",
|
|
207
|
+
"src/next.ts",
|
|
208
|
+
]);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("checkFrozen with the real ESLint on a tree: a new legacy directive in a file the list does not name still fails", async () => {
|
|
212
|
+
const root = useTempDir("layer-frozen-real-");
|
|
213
|
+
writeTree(root, {
|
|
214
|
+
[FROZEN_FILE]: JSON.stringify({ files: {} }),
|
|
215
|
+
"eslint.config.mjs":
|
|
216
|
+
'export default [{ files: ["src/**/*.ts"], rules: { "no-restricted-globals": ["error", "process"] } }];\n',
|
|
217
|
+
"src/plain.ts": "export const quiet = 1;\n",
|
|
218
|
+
"src/new.ts": `// eslint-disable-next-line no-restricted-globals -- ${IO}\nexport const env = process;\n`,
|
|
219
|
+
});
|
|
220
|
+
const r = await checkFrozen(root);
|
|
221
|
+
assert.deepEqual(r.counts, { "src/new.ts": { "no-restricted-globals": 1 } });
|
|
222
|
+
assert.match(
|
|
223
|
+
r.problems.join("\n"),
|
|
224
|
+
/^src\/new\.ts: a new legacy layer exemption/,
|
|
225
|
+
);
|
|
226
|
+
});
|
|
227
|
+
|
|
189
228
|
test("checkFrozen: a lint that returns no result reports that nothing was counted", async () => {
|
|
190
229
|
const root = useTempDir("layer-frozen-empty-");
|
|
191
230
|
writeTree(root, {
|
|
@@ -32,10 +32,15 @@
|
|
|
32
32
|
* counted by construction — and a range directive (`/* eslint-disable … *\/` around a multi-line
|
|
33
33
|
* import) counts exactly what it silences.
|
|
34
34
|
*
|
|
35
|
+
* ESLint lints only the files that can carry one (`filesToLint`): a suppressed finding comes from a
|
|
36
|
+
* directive, and every directive's text holds `eslint-disable` (`-line`, `-next-line`, the block
|
|
37
|
+
* form). The bulk-suppressions file is not applied here. So the narrowing drops only files that
|
|
38
|
+
* cannot add to the count — 21 of 223 today — and the count still comes from ESLint's report.
|
|
39
|
+
*
|
|
35
40
|
* Run: `node scripts/layer-legacy-frozen.ts` (also part of `npm run check`)
|
|
36
41
|
*/
|
|
37
42
|
import { isMain } from "../skills/paper-pipeline/scripts/consumer.mjs";
|
|
38
|
-
import { readFileSync } from "node:fs";
|
|
43
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
39
44
|
import { dirname, join, relative, sep } from "node:path";
|
|
40
45
|
import { fileURLToPath } from "node:url";
|
|
41
46
|
import { ESLint } from "eslint";
|
|
@@ -195,11 +200,35 @@ function layerLint(cwd: string): ESLint {
|
|
|
195
200
|
});
|
|
196
201
|
}
|
|
197
202
|
|
|
203
|
+
/**
|
|
204
|
+
* The `src/**\/*.ts` files of `root` whose text holds `eslint-disable` — every file that can carry a
|
|
205
|
+
* suppressed finding — relative to `root`, POSIX separators, sorted.
|
|
206
|
+
*/
|
|
207
|
+
export function filesToLint(root: string): string[] {
|
|
208
|
+
const src = join(root, "src");
|
|
209
|
+
const all = readdirSync(src, { recursive: true, withFileTypes: true })
|
|
210
|
+
.filter((e) => e.isFile() && e.name.endsWith(".ts"))
|
|
211
|
+
.map((e) => join(e.parentPath, e.name));
|
|
212
|
+
return all
|
|
213
|
+
.filter((f) => readFileSync(f, "utf8").includes("eslint-disable"))
|
|
214
|
+
.map((f) => relative(root, f).split(sep).join("/"))
|
|
215
|
+
.sort();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* The files ESLint lints: those that can carry a suppression, or — when there are none, or no
|
|
220
|
+
* `src/` — the whole glob, so ESLint itself refuses a tree with no source.
|
|
221
|
+
*/
|
|
222
|
+
const lintTargets = (root: string): string[] => {
|
|
223
|
+
const narrowed = existsSync(join(root, "src")) ? filesToLint(root) : [];
|
|
224
|
+
return narrowed.length > 0 ? narrowed : ["src/**/*.ts"];
|
|
225
|
+
};
|
|
226
|
+
|
|
198
227
|
/** Lint `src/` with the repository's configuration and run the whole check. */
|
|
199
228
|
export async function checkFrozen(
|
|
200
229
|
root: string = ROOT,
|
|
201
230
|
{
|
|
202
|
-
lint = (cwd: string) => layerLint(cwd).lintFiles(
|
|
231
|
+
lint = (cwd: string) => layerLint(cwd).lintFiles(lintTargets(cwd)),
|
|
203
232
|
}: { lint?: (cwd: string) => Promise<readonly Suppressed[]> } = {},
|
|
204
233
|
): Promise<FrozenCheck> {
|
|
205
234
|
const data = FrozenFile.parse(
|
|
@@ -5,7 +5,7 @@ context: fork
|
|
|
5
5
|
allowed-tools: [Bash, Read, Agent]
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
<!-- vigiles:sha256:
|
|
8
|
+
<!-- vigiles:sha256:c3a1f2644781eb6f compiled from skills/cold-read-diff/SKILL.md.spec.ts -->
|
|
9
9
|
|
|
10
10
|
# cold-read-diff — the reader who cannot fake understanding
|
|
11
11
|
|
|
@@ -55,7 +55,7 @@ one, and its confusion is the finding.
|
|
|
55
55
|
|
|
56
56
|
## How to run it
|
|
57
57
|
|
|
58
|
-
1. **Take the diff.** `git diff -U0 -- <paper.
|
|
58
|
+
1. **Take the diff.** `git diff -U0 -- <paper.tex>` since the last commit, or the `new_string` of the
|
|
59
59
|
edits just applied. Prose only — skip comments, tables, bibliography.
|
|
60
60
|
2. **Spawn TWO agents with no repository context, one per persona, in parallel.** Give each the
|
|
61
61
|
changed paragraphs **as text in the prompt**, not as a file path. A path is an invitation to read
|
|
@@ -105,7 +105,7 @@ Two more things the same run proved:
|
|
|
105
105
|
- **Expand build-time macros first.** The reader saw seven raw `{{placeholders}}` and correctly
|
|
106
106
|
reported that every headline number was missing. That is a true statement about the extract and a
|
|
107
107
|
false one about the paper.
|
|
108
|
-
- **Strip working comments.** He graded
|
|
108
|
+
- **Strip working comments.** He graded `% JUSTIFY …` comment notes as prose because he could not tell
|
|
109
109
|
them apart — and said so, which is itself a finding about the extract.
|
|
110
110
|
|
|
111
111
|
**So:** take the changed lines, then widen each to its enclosing paragraph, expand macros, drop
|
|
@@ -60,7 +60,7 @@ one, and its confusion is the finding.
|
|
|
60
60
|
|
|
61
61
|
## How to run it
|
|
62
62
|
|
|
63
|
-
1. **Take the diff.** \`git diff -U0 -- <paper.
|
|
63
|
+
1. **Take the diff.** \`git diff -U0 -- <paper.tex>\` since the last commit, or the \`new_string\` of the
|
|
64
64
|
edits just applied. Prose only — skip comments, tables, bibliography.
|
|
65
65
|
2. **Spawn TWO agents with no repository context, one per persona, in parallel.** Give each the
|
|
66
66
|
changed paragraphs **as text in the prompt**, not as a file path. A path is an invitation to read
|
|
@@ -110,7 +110,7 @@ Two more things the same run proved:
|
|
|
110
110
|
- **Expand build-time macros first.** The reader saw seven raw \`{{placeholders}}\` and correctly
|
|
111
111
|
reported that every headline number was missing. That is a true statement about the extract and a
|
|
112
112
|
false one about the paper.
|
|
113
|
-
- **Strip working comments.** He graded
|
|
113
|
+
- **Strip working comments.** He graded \`% JUSTIFY …\` comment notes as prose because he could not tell
|
|
114
114
|
them apart — and said so, which is itself a finding about the extract.
|
|
115
115
|
|
|
116
116
|
**So:** take the changed lines, then widen each to its enclosing paragraph, expand macros, drop
|
|
@@ -4,7 +4,7 @@ description: Use when asking "does this paper read well?" / "is the writing any
|
|
|
4
4
|
allowed-tools: [Read, Write, Edit, Grep, Glob, Agent, Skill]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
<!-- vigiles:sha256:
|
|
7
|
+
<!-- vigiles:sha256:21c8899648c9b223 compiled from skills/grade-paper-writing/SKILL.md.spec.ts -->
|
|
8
8
|
|
|
9
9
|
# grade-paper-writing — grade how the paper READS, then fix it sentence by sentence
|
|
10
10
|
|
|
@@ -53,7 +53,7 @@ derived from that gap.
|
|
|
53
53
|
### 🔴 Read the BUILT PDF, not the source
|
|
54
54
|
|
|
55
55
|
**The stall pass reads the typeset pages.** The source is for locating the fix afterwards, never for
|
|
56
|
-
judging the read. This was previously written as "read the `.tex
|
|
56
|
+
judging the read. This was previously written as "read the `.tex`, **or** run `render-paper`",
|
|
57
57
|
and that *or* is why the pass kept missing what the author saw immediately: he reads the PDF.
|
|
58
58
|
|
|
59
59
|
Things that exist only in the render, every one of which produces a real stall:
|
|
@@ -153,7 +153,7 @@ is what happened.
|
|
|
153
153
|
|
|
154
154
|
## How to run it
|
|
155
155
|
|
|
156
|
-
1. **Read the actual draft** (the `.tex
|
|
156
|
+
1. **Read the actual draft** (the `.tex`, or run `render-paper` to read the built pages as a reader
|
|
157
157
|
would). Grade the writing as written — never from a summary.
|
|
158
158
|
2. **Run the cold-read stall pass FIRST — the full specification is below, under "The stall pass".**
|
|
159
159
|
It was previously a pointer to a reference section that did not exist, which is why the pass drifted
|
|
@@ -215,10 +215,18 @@ is what happened.
|
|
|
215
215
|
5. **Flag the two prose traps by name:** (a) Attention-style dense contribution-less prose used where the
|
|
216
216
|
audience does NOT already care; (b) hedge-stacking ("may possibly in some cases potentially") — replace
|
|
217
217
|
each pile with one precise scope sentence.
|
|
218
|
-
6. 🔴 **Run `prose-lint.mjs` and treat every FLAG as a finding, not as context
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
218
|
+
6. 🔴 **Run `prose-lint.mjs` on the paper and treat every FLAG as a finding, not as context:**
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
node ${CLAUDE_SKILL_DIR}/prose-lint.mjs <paper-dir>/paper.tex # the report
|
|
222
|
+
node ${CLAUDE_SKILL_DIR}/prose-lint.mjs --headings <paper-dir>/paper.tex # the headings as a set
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
It measures the prose a reader sees — the files the last build read spliced in, no preamble,
|
|
226
|
+
comments, floats or inline `.bib` — so its rates are the paper's, not the source file's. Run
|
|
227
|
+
`npx paperlint build` first: with no current build record it reads `paper.tex` alone and says so. The skill is what must ACT on it.
|
|
228
|
+
Its thresholds are sourced; a FLAG means the text sits above a published human or machine
|
|
229
|
+
baseline, which is a defect with a number attached, not an opinion.
|
|
222
230
|
Two entries in `THRESHOLDS` exist because this skill previously ran five times without either
|
|
223
231
|
firing: the `"not X, but Y"` density had **no threshold at all** and printed as a neutral line
|
|
224
232
|
while standing at 50.8 per 10,000 words against an LLM baseline of 7.8, and the lexicon missed
|
|
@@ -227,21 +235,6 @@ is what happened.
|
|
|
227
235
|
A number printed without a threshold is prose; that is this project's own thesis, and the linter
|
|
228
236
|
was violating it.
|
|
229
237
|
|
|
230
|
-
6a. 🔴 **`prose-lint.mjs` IS NO LONGER ALONE — seven of its twelve gating metrics moved
|
|
231
|
-
on 2026-08-26 into ESLint rules, and running only it shows you five twelfths.** A second
|
|
232
|
-
run is mandatory; without it, step 6 is incomplete:
|
|
233
|
-
|
|
234
|
-
```
|
|
235
|
-
npx eslint "papers/*/paper.md" "papers/*/draft.md"
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
From there come: `paper/citation-density` · `paper/unexplained-jargon` ·
|
|
239
|
-
`paper/multi-claim-sentence` · `paper/conceits` · `paper/hedge-density` ·
|
|
240
|
-
`paper/discourse-subject` · `paper/undefined-coinage` (file — `eslint-rules/paper-craft.mjs`).
|
|
241
|
-
Gain from the move — `file:line:col` on each finding: the old report printed the sentence head,
|
|
242
|
-
and you had to find it in the article by eye. Everything step 6b says about
|
|
243
|
-
meaning regression applies to them just the same: this is diagnosis, not objective.
|
|
244
|
-
|
|
245
238
|
6b. 🔴 **A lint fix that costs meaning is a REGRESSION, and no linter can see it.** The metrics above
|
|
246
239
|
are diagnostics, never objectives. Optimising one directly is how a sentence gets worse while the
|
|
247
240
|
number gets better — and the number is exactly what makes it feel like progress.
|
|
@@ -59,7 +59,7 @@ derived from that gap.
|
|
|
59
59
|
### 🔴 Read the BUILT PDF, not the source
|
|
60
60
|
|
|
61
61
|
**The stall pass reads the typeset pages.** The source is for locating the fix afterwards, never for
|
|
62
|
-
judging the read. This was previously written as "read the \`.tex
|
|
62
|
+
judging the read. This was previously written as "read the \`.tex\`, **or** run \`render-paper\`",
|
|
63
63
|
and that *or* is why the pass kept missing what the author saw immediately: he reads the PDF.
|
|
64
64
|
|
|
65
65
|
Things that exist only in the render, every one of which produces a real stall:
|
|
@@ -159,7 +159,7 @@ is what happened.
|
|
|
159
159
|
|
|
160
160
|
## How to run it
|
|
161
161
|
|
|
162
|
-
1. **Read the actual draft** (the \`.tex
|
|
162
|
+
1. **Read the actual draft** (the \`.tex\`, or run \`render-paper\` to read the built pages as a reader
|
|
163
163
|
would). Grade the writing as written — never from a summary.
|
|
164
164
|
2. **Run the cold-read stall pass FIRST — the full specification is below, under "The stall pass".**
|
|
165
165
|
It was previously a pointer to a reference section that did not exist, which is why the pass drifted
|
|
@@ -221,10 +221,18 @@ is what happened.
|
|
|
221
221
|
5. **Flag the two prose traps by name:** (a) Attention-style dense contribution-less prose used where the
|
|
222
222
|
audience does NOT already care; (b) hedge-stacking ("may possibly in some cases potentially") — replace
|
|
223
223
|
each pile with one precise scope sentence.
|
|
224
|
-
6. 🔴 **Run \`prose-lint.mjs\` and treat every FLAG as a finding, not as context
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
224
|
+
6. 🔴 **Run \`prose-lint.mjs\` on the paper and treat every FLAG as a finding, not as context:**
|
|
225
|
+
|
|
226
|
+
\`\`\`
|
|
227
|
+
node \${CLAUDE_SKILL_DIR}/prose-lint.mjs <paper-dir>/paper.tex # the report
|
|
228
|
+
node \${CLAUDE_SKILL_DIR}/prose-lint.mjs --headings <paper-dir>/paper.tex # the headings as a set
|
|
229
|
+
\`\`\`
|
|
230
|
+
|
|
231
|
+
It measures the prose a reader sees — the files the last build read spliced in, no preamble,
|
|
232
|
+
comments, floats or inline \`.bib\` — so its rates are the paper's, not the source file's. Run
|
|
233
|
+
\`npx paperlint build\` first: with no current build record it reads \`paper.tex\` alone and says so. The skill is what must ACT on it.
|
|
234
|
+
Its thresholds are sourced; a FLAG means the text sits above a published human or machine
|
|
235
|
+
baseline, which is a defect with a number attached, not an opinion.
|
|
228
236
|
Two entries in \`THRESHOLDS\` exist because this skill previously ran five times without either
|
|
229
237
|
firing: the \`"not X, but Y"\` density had **no threshold at all** and printed as a neutral line
|
|
230
238
|
while standing at 50.8 per 10,000 words against an LLM baseline of 7.8, and the lexicon missed
|
|
@@ -233,21 +241,6 @@ is what happened.
|
|
|
233
241
|
A number printed without a threshold is prose; that is this project's own thesis, and the linter
|
|
234
242
|
was violating it.
|
|
235
243
|
|
|
236
|
-
6a. 🔴 **\`prose-lint.mjs\` IS NO LONGER ALONE — seven of its twelve gating metrics moved
|
|
237
|
-
on 2026-08-26 into ESLint rules, and running only it shows you five twelfths.** A second
|
|
238
|
-
run is mandatory; without it, step 6 is incomplete:
|
|
239
|
-
|
|
240
|
-
\`\`\`
|
|
241
|
-
npx eslint "papers/*/paper.md" "papers/*/draft.md"
|
|
242
|
-
\`\`\`
|
|
243
|
-
|
|
244
|
-
From there come: \`paper/citation-density\` · \`paper/unexplained-jargon\` ·
|
|
245
|
-
\`paper/multi-claim-sentence\` · \`paper/conceits\` · \`paper/hedge-density\` ·
|
|
246
|
-
\`paper/discourse-subject\` · \`paper/undefined-coinage\` (file — \`eslint-rules/paper-craft.mjs\`).
|
|
247
|
-
Gain from the move — \`file:line:col\` on each finding: the old report printed the sentence head,
|
|
248
|
-
and you had to find it in the article by eye. Everything step 6b says about
|
|
249
|
-
meaning regression applies to them just the same: this is diagnosis, not objective.
|
|
250
|
-
|
|
251
244
|
6b. 🔴 **A lint fix that costs meaning is a REGRESSION, and no linter can see it.** The metrics above
|
|
252
245
|
are diagnostics, never objectives. Optimising one directly is how a sentence gets worse while the
|
|
253
246
|
number gets better — and the number is exactly what makes it feel like progress.
|