paperlint 8.0.0 → 9.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/CLAUDE.md +22 -0
- package/.github/CLAUDE.md.spec.ts +20 -0
- package/CLAUDE.md +59 -531
- package/CONTRIBUTING.md +7 -6
- package/README.md +4 -3
- package/dist/CLAUDE.md.spec.d.ts +3 -0
- package/dist/CLAUDE.md.spec.d.ts.map +1 -0
- package/dist/CLAUDE.md.spec.js +62 -0
- package/dist/CLAUDE.md.spec.js.map +1 -0
- package/dist/adapters/bibtex/index.d.ts +3 -0
- package/dist/adapters/bibtex/index.d.ts.map +1 -0
- package/dist/adapters/bibtex/index.js +103 -0
- package/dist/adapters/bibtex/index.js.map +1 -0
- package/dist/adapters/latex/filecontents.d.ts +33 -0
- package/dist/adapters/latex/filecontents.d.ts.map +1 -0
- package/dist/adapters/latex/filecontents.js +65 -0
- package/dist/adapters/latex/filecontents.js.map +1 -0
- package/dist/adapters/latex/includes.d.ts +3 -3
- package/dist/adapters/latex/includes.d.ts.map +1 -1
- package/dist/adapters/latex/includes.js +18 -10
- package/dist/adapters/latex/includes.js.map +1 -1
- package/dist/adapters/latex/index.d.ts.map +1 -1
- package/dist/adapters/latex/index.js +2 -0
- package/dist/adapters/latex/index.js.map +1 -1
- package/dist/adapters/latex/nodes.d.ts +12 -0
- package/dist/adapters/latex/nodes.d.ts.map +1 -1
- package/dist/adapters/latex/nodes.js +39 -0
- package/dist/adapters/latex/nodes.js.map +1 -1
- package/dist/adapters/latex/parse.d.ts +7 -1
- package/dist/adapters/latex/parse.d.ts.map +1 -1
- package/dist/adapters/latex/parse.js +55 -6
- package/dist/adapters/latex/parse.js.map +1 -1
- package/dist/adapters/memory/index.d.ts +3 -0
- package/dist/adapters/memory/index.d.ts.map +1 -1
- package/dist/adapters/memory/index.js +9 -0
- package/dist/adapters/memory/index.js.map +1 -1
- package/dist/adapters/references/index.d.ts.map +1 -1
- package/dist/adapters/references/index.js +27 -24
- package/dist/adapters/references/index.js.map +1 -1
- package/dist/adapters/sources-record/index.d.ts +9 -0
- package/dist/adapters/sources-record/index.d.ts.map +1 -0
- package/dist/adapters/sources-record/index.js +68 -0
- package/dist/adapters/sources-record/index.js.map +1 -0
- package/dist/adapters/tex-output/blg.d.ts +9 -0
- package/dist/adapters/tex-output/blg.d.ts.map +1 -0
- package/dist/adapters/tex-output/blg.js +20 -0
- package/dist/adapters/tex-output/blg.js.map +1 -0
- package/dist/adapters/tex-output/fls.d.ts +8 -0
- package/dist/adapters/tex-output/fls.d.ts.map +1 -0
- package/dist/adapters/tex-output/fls.js +16 -0
- package/dist/adapters/tex-output/fls.js.map +1 -0
- package/dist/adapters/tex-output/index.d.ts +7 -0
- package/dist/adapters/tex-output/index.d.ts.map +1 -0
- package/dist/adapters/tex-output/index.js +7 -0
- package/dist/adapters/tex-output/index.js.map +1 -0
- package/dist/bib-rules.d.ts +47 -0
- package/dist/bib-rules.d.ts.map +1 -0
- package/dist/bib-rules.js +127 -0
- package/dist/bib-rules.js.map +1 -0
- package/dist/build.d.ts +29 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +41 -25
- package/dist/build.js.map +1 -1
- package/dist/claim-provenance.d.ts +2 -2
- package/dist/cli.d.ts +6 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +30 -26
- package/dist/cli.js.map +1 -1
- package/dist/domain/paper-source.d.ts +1 -10
- package/dist/domain/paper-source.d.ts.map +1 -1
- package/dist/domain/paper-source.js +34 -24
- package/dist/domain/paper-source.js.map +1 -1
- package/dist/domain/paper-sources.d.ts +72 -0
- package/dist/domain/paper-sources.d.ts.map +1 -0
- package/dist/domain/paper-sources.js +18 -0
- package/dist/domain/paper-sources.js.map +1 -0
- package/dist/domain/sources-record.d.ts +102 -0
- package/dist/domain/sources-record.d.ts.map +1 -0
- package/dist/domain/sources-record.js +38 -0
- package/dist/domain/sources-record.js.map +1 -0
- package/dist/domain/tex-run.d.ts +25 -0
- package/dist/domain/tex-run.d.ts.map +1 -0
- package/dist/domain/tex-run.js +79 -0
- package/dist/domain/tex-run.js.map +1 -0
- package/dist/eslint-rules/CLAUDE.md.spec.d.ts +3 -0
- package/dist/eslint-rules/CLAUDE.md.spec.d.ts.map +1 -0
- package/dist/eslint-rules/CLAUDE.md.spec.js +39 -0
- package/dist/eslint-rules/CLAUDE.md.spec.js.map +1 -0
- package/dist/eslint-rules/install-path-literals.d.ts +1 -1
- package/dist/eslint-rules/install-path-literals.js +4 -4
- package/dist/eslint-rules/install-path-literals.js.map +1 -1
- package/dist/eslint-rules/paper-context.d.ts.map +1 -1
- package/dist/eslint-rules/paper-context.js +4 -3
- package/dist/eslint-rules/paper-context.js.map +1 -1
- package/dist/eslint-rules/paper-typography.d.ts +0 -9
- package/dist/eslint-rules/paper-typography.d.ts.map +1 -1
- package/dist/eslint-rules/paper-typography.js +12 -5
- package/dist/eslint-rules/paper-typography.js.map +1 -1
- package/dist/eslint-rules/pdf-last-page-balance.js +1 -1
- package/dist/latex-log.d.ts +10 -0
- package/dist/latex-log.d.ts.map +1 -1
- package/dist/latex-log.js +10 -0
- package/dist/latex-log.js.map +1 -1
- package/dist/latex-loop.d.ts +2 -0
- package/dist/latex-loop.d.ts.map +1 -1
- package/dist/latex-loop.js.map +1 -1
- package/dist/lib/agent-cli-version.js +1 -1
- package/dist/lib/agent-cli-version.js.map +1 -1
- package/dist/package-dirs.d.ts.map +1 -1
- package/dist/package-dirs.js +3 -2
- package/dist/package-dirs.js.map +1 -1
- package/dist/paper-includes.d.ts +4 -7
- package/dist/paper-includes.d.ts.map +1 -1
- package/dist/paper-includes.js +29 -21
- package/dist/paper-includes.js.map +1 -1
- package/dist/paper-prose.d.ts +2 -2
- package/dist/paper-record.d.ts +25 -0
- package/dist/paper-record.d.ts.map +1 -0
- package/dist/paper-record.js +39 -0
- package/dist/paper-record.js.map +1 -0
- package/dist/paper-sources.d.ts +66 -0
- package/dist/paper-sources.d.ts.map +1 -0
- package/dist/paper-sources.js +92 -0
- package/dist/paper-sources.js.map +1 -0
- package/dist/ports/bib-reader.d.ts +18 -0
- package/dist/ports/bib-reader.d.ts.map +1 -0
- package/dist/ports/bib-reader.js +2 -0
- package/dist/ports/bib-reader.js.map +1 -0
- package/dist/ports/check-references.d.ts +9 -4
- package/dist/ports/check-references.d.ts.map +1 -1
- package/dist/ports/latex.d.ts +15 -0
- package/dist/ports/latex.d.ts.map +1 -1
- package/dist/ports/sources-codec.d.ts +11 -0
- package/dist/ports/sources-codec.d.ts.map +1 -0
- package/dist/ports/sources-codec.js +2 -0
- package/dist/ports/sources-codec.js.map +1 -0
- package/dist/ports/tex-output.d.ts +17 -0
- package/dist/ports/tex-output.d.ts.map +1 -0
- package/dist/ports/tex-output.js +2 -0
- package/dist/ports/tex-output.js.map +1 -0
- package/dist/recorded-bibliography.d.ts +89 -0
- package/dist/recorded-bibliography.d.ts.map +1 -0
- package/dist/recorded-bibliography.js +136 -0
- package/dist/recorded-bibliography.js.map +1 -0
- package/dist/reference-rules.d.ts +5 -5
- package/dist/reference-rules.d.ts.map +1 -1
- package/dist/reference-rules.js +150 -77
- package/dist/reference-rules.js.map +1 -1
- package/dist/references.d.ts +71 -16
- package/dist/references.d.ts.map +1 -1
- package/dist/references.js +110 -26
- package/dist/references.js.map +1 -1
- package/dist/register-bands.d.ts +3 -3
- package/dist/register-bands.d.ts.map +1 -1
- package/dist/register-bands.js.map +1 -1
- package/dist/register.d.ts +2 -2
- package/dist/sources-record.d.ts +38 -0
- package/dist/sources-record.d.ts.map +1 -0
- package/dist/sources-record.js +123 -0
- package/dist/sources-record.js.map +1 -0
- package/dist/sources-rules.d.ts +44 -0
- package/dist/sources-rules.d.ts.map +1 -0
- package/dist/sources-rules.js +76 -0
- package/dist/sources-rules.js.map +1 -0
- package/dist/tex-paper.d.ts +10 -15
- package/dist/tex-paper.d.ts.map +1 -1
- package/dist/tex-paper.js +24 -40
- package/dist/tex-paper.js.map +1 -1
- package/dist/tex-requirements.js +1 -1
- package/dist/tex-venue-rules.d.ts +3 -3
- package/dist/tex-venue-rules.d.ts.map +1 -1
- package/dist/tex-venue-rules.js.map +1 -1
- package/dist/toolchain.js +1 -1
- package/docs/configuration.md +12 -4
- package/docs/design/paper-sources.md +197 -0
- package/docs/e2e.md +1 -1
- package/docs/incidents.md +5 -5
- package/docs/package-shape-options.md +1 -1
- package/docs/references.md +34 -8
- package/docs/rules/bib/reachable-entry.md +64 -0
- package/docs/rules/paper/sources-fresh.md +81 -0
- package/docs/rules.md +62 -57
- package/eslint-rules/CLAUDE.md +44 -0
- package/eslint-rules/CLAUDE.md.spec.ts +43 -0
- package/eslint-rules/install-path-literals.harness.mjs +1 -1
- package/eslint-rules/install-path-literals.ts +4 -4
- package/eslint-rules/paper-context.ts +4 -3
- package/eslint-rules/paper-typography.test.mjs +12 -72
- package/eslint-rules/paper-typography.ts +13 -6
- package/eslint-rules/pdf-last-page-balance.ts +1 -1
- package/eslint.config.mjs +28 -3
- package/fixtures/build-e2e/aidc/paper.tex +1 -1
- package/fixtures/build-e2e/anon-ack/paper.tex +1 -1
- package/fixtures/build-e2e/anon-author/paper.tex +1 -1
- package/fixtures/build-e2e/anon-bib/paper.tex +1 -1
- package/fixtures/build-e2e/anon-href/paper.tex +1 -1
- package/fixtures/build-e2e/anon-macro/paper.tex +1 -1
- package/fixtures/build-e2e/anon-pdfauthor/paper.tex +1 -1
- package/fixtures/paper-sources/p1/paper.tex +30 -0
- package/fixtures/paper-sources/p1/refs.bib +6 -0
- package/fixtures/paper-sources/p1/sections/dead.tex +6 -0
- package/fixtures/paper-sources/p1/sections/intro.tex +7 -0
- package/fixtures/paper-sources/p1/sections/parked.tex +6 -0
- package/fixtures/paper-sources/p1/tex-truth.json +37 -0
- package/fixtures/paper-sources/p1/versions/2026-01-01-review.tex +4 -0
- package/fixtures/paper-sources/v1-stale/paper.tex +14 -0
- package/fixtures/paper-sources/v1-stale/refs.bib +6 -0
- package/fixtures/paper-sources/v1-stale/tex-truth.json +32 -0
- package/fixtures/paper-sources/v10-ifthenelse/anon.bib +1 -0
- package/fixtures/paper-sources/v10-ifthenelse/paper.tex +9 -0
- package/fixtures/paper-sources/v10-ifthenelse/real.bib +1 -0
- package/fixtures/paper-sources/v10-ifthenelse/tex-truth.json +28 -0
- package/fixtures/paper-sources/v11-two-blocks/paper.tex +12 -0
- package/fixtures/paper-sources/v11-two-blocks/tex-truth.json +30 -0
- package/fixtures/paper-sources/v13-percent-text/paper.tex +6 -0
- package/fixtures/paper-sources/v13-percent-text/refs.bib +3 -0
- package/fixtures/paper-sources/v13-percent-text/tex-truth.json +30 -0
- package/fixtures/paper-sources/v15-after-end/paper.tex +6 -0
- package/fixtures/paper-sources/v15-after-end/refs.bib +1 -0
- package/fixtures/paper-sources/v15-after-end/tex-truth.json +17 -0
- package/fixtures/paper-sources/v16-included-block/bibblock.tex +3 -0
- package/fixtures/paper-sources/v16-included-block/paper.tex +7 -0
- package/fixtures/paper-sources/v16-included-block/refs.bib +1 -0
- package/fixtures/paper-sources/v16-included-block/tex-truth.json +35 -0
- package/fixtures/paper-sources/v17-include-order/bibsetup.tex +1 -0
- package/fixtures/paper-sources/v17-include-order/first.bib +1 -0
- package/fixtures/paper-sources/v17-include-order/later.bib +1 -0
- package/fixtures/paper-sources/v17-include-order/paper.tex +7 -0
- package/fixtures/paper-sources/v17-include-order/tex-truth.json +40 -0
- package/fixtures/paper-sources/v2-overwrite/paper.tex +14 -0
- package/fixtures/paper-sources/v2-overwrite/refs.bib +6 -0
- package/fixtures/paper-sources/v2-overwrite/tex-truth.json +30 -0
- package/fixtures/paper-sources/v22-parked-thebibliography/paper.tex +7 -0
- package/fixtures/paper-sources/v22-parked-thebibliography/tex-truth.json +17 -0
- package/fixtures/paper-sources/v23-parked-include/old.bib +1 -0
- package/fixtures/paper-sources/v23-parked-include/paper.tex +7 -0
- package/fixtures/paper-sources/v23-parked-include/parked.tex +1 -0
- package/fixtures/paper-sources/v23-parked-include/refs.bib +1 -0
- package/fixtures/paper-sources/v23-parked-include/tex-truth.json +28 -0
- package/fixtures/paper-sources/v24-quoted-fields/paper.tex +6 -0
- package/fixtures/paper-sources/v24-quoted-fields/refs.bib +5 -0
- package/fixtures/paper-sources/v24-quoted-fields/tex-truth.json +32 -0
- package/fixtures/paper-sources/v25-string-differs/paper.tex +10 -0
- package/fixtures/paper-sources/v25-string-differs/refs.bib +2 -0
- package/fixtures/paper-sources/v25-string-differs/tex-truth.json +32 -0
- package/fixtures/paper-sources/v26-comment-in-names/one.bib +1 -0
- package/fixtures/paper-sources/v26-comment-in-names/paper.tex +7 -0
- package/fixtures/paper-sources/v26-comment-in-names/tex-truth.json +32 -0
- package/fixtures/paper-sources/v26-comment-in-names/two.bib +1 -0
- package/fixtures/paper-sources/v27-shared-string/abbreviations.bib +2 -0
- package/fixtures/paper-sources/v27-shared-string/paper.tex +6 -0
- package/fixtures/paper-sources/v27-shared-string/references.bib +6 -0
- package/fixtures/paper-sources/v27-shared-string/tex-truth.json +31 -0
- package/fixtures/paper-sources/v28-shared-string-block/abbreviations.bib +2 -0
- package/fixtures/paper-sources/v28-shared-string-block/paper.tex +14 -0
- package/fixtures/paper-sources/v28-shared-string-block/tex-truth.json +33 -0
- package/fixtures/paper-sources/v29-generated-input/paper.tex +9 -0
- package/fixtures/paper-sources/v29-generated-input/tex-truth.json +24 -0
- package/fixtures/paper-sources/v3-declared/paper.bib +6 -0
- package/fixtures/paper-sources/v3-declared/paper.tex +6 -0
- package/fixtures/paper-sources/v3-declared/tex-truth.json +28 -0
- package/fixtures/paper-sources/v30-name-next-line/paper.tex +14 -0
- package/fixtures/paper-sources/v30-name-next-line/tex-truth.json +30 -0
- package/fixtures/paper-sources/v4-commented/paper.tex +14 -0
- package/fixtures/paper-sources/v4-commented/refs.bib +6 -0
- package/fixtures/paper-sources/v4-commented/tex-truth.json +28 -0
- package/fixtures/paper-sources/v5-percent-entry/paper.tex +20 -0
- package/fixtures/paper-sources/v5-percent-entry/tex-truth.json +37 -0
- package/fixtures/paper-sources/v6-unclosed/paper.tex +6 -0
- package/fixtures/paper-sources/v6-unclosed/refs.bib +5 -0
- package/fixtures/paper-sources/v6-unclosed/tex-truth.json +37 -0
- package/fixtures/paper-sources/v8-jobname/paper.tex +9 -0
- package/fixtures/paper-sources/v8-jobname/tex-truth.json +30 -0
- package/fixtures/paper-sources/v9-two-bibliographies/one.bib +1 -0
- package/fixtures/paper-sources/v9-two-bibliographies/paper.tex +7 -0
- package/fixtures/paper-sources/v9-two-bibliographies/tex-truth.json +35 -0
- package/fixtures/paper-sources/v9-two-bibliographies/two.bib +1 -0
- package/fixtures/paper-typography/clean-paper/paper.tex +1 -1
- package/fixtures/paper-typography/messy-paper/paper.tex +1 -1
- package/hooks/CLAUDE.md +34 -0
- package/hooks/CLAUDE.md.spec.ts +30 -0
- package/lib/agent-cli-version.ts +1 -1
- package/package.json +3 -11
- package/plugin/CLAUDE.md +22 -0
- package/plugin/CLAUDE.md.spec.ts +22 -0
- package/scripts/check.test.ts +23 -1
- package/scripts/check.ts +7 -3
- package/scripts/coverage-config.test.ts +21 -11
- package/scripts/layer-legacy-frozen.test.ts +41 -2
- package/scripts/layer-legacy-frozen.ts +31 -2
- package/skills/CLAUDE.md +19 -0
- package/skills/CLAUDE.md.spec.ts +18 -0
- package/skills/grade-paper-writing/SKILL.md +4 -3
- package/skills/grade-paper-writing/SKILL.md.spec.ts +3 -2
- package/skills/grade-paper-writing/prose-lint.mjs +11 -2
- package/skills/grade-paper-writing/prose-lint.test.ts +52 -4
- package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +83 -33
- package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +67 -115
- package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +128 -121
- package/skills/plan-paper-timeline/plan-paper-timeline.effects.harness.mjs +21 -102
- package/skills/verify-citations/SKILL.md +7 -4
- package/skills/verify-citations/SKILL.md.spec.ts +6 -3
- package/skills/verify-citations/scripts/bib-authors.check.test.mjs +17 -19
- package/skills/verify-citations/scripts/bib-authors.cli.test.ts +129 -46
- package/skills/verify-citations/scripts/bib-authors.d.mts +22 -2
- package/skills/verify-citations/scripts/bib-authors.mjs +57 -111
- package/skills/verify-citations/scripts/verify-cites.cli.test.ts +16 -0
- package/skills/verify-citations/scripts/verify-cites.d.mts +19 -3
- package/skills/verify-citations/scripts/verify-cites.mjs +42 -108
- package/skills/verify-citations/scripts/verify-cites.test.mjs +23 -36
- package/src/CLAUDE.md +23 -1
- package/src/CLAUDE.md.spec.ts +72 -0
- package/src/adapters/bibtex/index.test.ts +163 -0
- package/src/adapters/bibtex/index.ts +155 -0
- package/src/adapters/latex/filecontents.ts +114 -0
- package/src/adapters/latex/includes.ts +18 -10
- package/src/adapters/latex/index.test.ts +73 -0
- package/src/adapters/latex/index.ts +2 -0
- package/src/adapters/latex/nodes.ts +64 -0
- package/src/adapters/latex/parse.test.ts +38 -0
- package/src/adapters/latex/parse.ts +67 -6
- package/src/adapters/memory/index.ts +17 -0
- package/src/adapters/references/index.test.ts +75 -21
- package/src/adapters/references/index.ts +37 -32
- package/src/adapters/sources-record/index.test.ts +87 -0
- package/src/adapters/sources-record/index.ts +82 -0
- package/src/adapters/tex-output/blg.ts +27 -0
- package/src/adapters/tex-output/fls.ts +24 -0
- package/src/adapters/tex-output/index.test.ts +171 -0
- package/src/adapters/tex-output/index.ts +12 -0
- package/src/bib-rules.test.ts +356 -0
- package/src/bib-rules.ts +228 -0
- package/src/bibliography-at.test.ts +286 -0
- package/src/build.test.ts +210 -0
- package/src/build.ts +101 -36
- package/src/claim-provenance.test.ts +33 -7
- package/src/claim-provenance.ts +2 -2
- package/src/cli.harness.mjs +20 -10
- package/src/cli.test.ts +14 -10
- package/src/cli.ts +37 -28
- package/src/domain/paper-source.test.ts +58 -20
- package/src/domain/paper-source.ts +38 -42
- package/src/domain/paper-sources.ts +94 -0
- package/src/domain/sources-record.test.ts +71 -0
- package/src/domain/sources-record.ts +136 -0
- package/src/domain/tex-run.test.ts +228 -0
- package/src/domain/tex-run.ts +139 -0
- package/src/heading-case.test.ts +24 -10
- package/src/latex-log.ts +20 -0
- package/src/latex-loop.harness.ts +1 -0
- package/src/latex-loop.ts +2 -0
- package/src/linted-set.test.ts +9 -1
- package/src/new-paper.harness.mjs +3 -2
- package/src/new-paper.test.ts +9 -5
- package/src/package-dirs.ts +3 -2
- package/src/paper-includes.test.ts +157 -38
- package/src/paper-includes.ts +39 -35
- package/src/paper-prose.test.ts +34 -5
- package/src/paper-prose.ts +2 -2
- package/src/paper-record.test.ts +173 -0
- package/src/paper-record.ts +66 -0
- package/src/paper-settings-commands.test.ts +5 -2
- package/src/paper-sources.ts +151 -0
- package/src/ports/bib-reader.ts +27 -0
- package/src/ports/check-references.ts +7 -3
- package/src/ports/latex.ts +19 -0
- package/src/ports/sources-codec.ts +11 -0
- package/src/ports/tex-output.ts +17 -0
- package/src/recorded-bibliography.ts +297 -0
- package/src/reference-rules.ts +217 -104
- package/src/references.test.ts +520 -177
- package/src/references.ts +185 -37
- package/src/register-bands.test.ts +5 -1
- package/src/register-bands.ts +3 -5
- package/src/register.test.ts +6 -1
- package/src/register.ts +2 -2
- package/src/rule-cases.test.ts +68 -12
- package/src/rule-docs.test.ts +0 -1
- package/src/sources-record.test.ts +308 -0
- package/src/sources-record.ts +231 -0
- package/src/sources-rules.test.ts +152 -0
- package/src/sources-rules.ts +117 -0
- package/src/tex-paper.test.ts +148 -78
- package/src/tex-paper.ts +31 -61
- package/src/tex-requirements.ts +1 -1
- package/src/tex-venue-rules.test.ts +36 -8
- package/src/tex-venue-rules.ts +3 -5
- package/src/toolchain.ts +1 -1
- package/dist/eslint-rules/bib-reachable-entry.d.ts +0 -22
- package/dist/eslint-rules/bib-reachable-entry.d.ts.map +0 -1
- package/dist/eslint-rules/bib-reachable-entry.js +0 -75
- package/dist/eslint-rules/bib-reachable-entry.js.map +0 -1
- package/eslint-rules/bib-reachable-entry.ts +0 -76
package/CLAUDE.md
CHANGED
|
@@ -1,576 +1,104 @@
|
|
|
1
|
-
|
|
1
|
+
<!-- vigiles:sha256:e445489aca0dd970 compiled from CLAUDE.md.spec.ts -->
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
knowledge base.
|
|
3
|
+
# CLAUDE.md
|
|
5
4
|
|
|
6
|
-
##
|
|
7
|
-
|
|
8
|
-
**Move every paper-writing convention a machine can decide OUT of prose and INTO an engine
|
|
9
|
-
that fails the build** — and keep everything else honestly labelled as prose. The engine is
|
|
10
|
-
ESLint. The unit of progress is "one more verdict decided by the engine instead of by a
|
|
11
|
-
hand-written script".
|
|
12
|
-
|
|
13
|
-
That last clause is the whole direction, and it is the part that keeps being forgotten:
|
|
14
|
-
|
|
15
|
-
```
|
|
16
|
-
prose in a guideline → hand-written script → ESLint rule in the engine
|
|
17
|
-
rots silently runs, but is OURS editor-time · free AST · suppressible
|
|
18
|
-
← where this started ← where most of it is ← where it is going
|
|
19
|
-
```
|
|
5
|
+
## Paperlint
|
|
20
6
|
|
|
21
|
-
|
|
22
|
-
only when the engine genuinely cannot express the check — and "cannot" means MEASURED, not
|
|
23
|
-
assumed. Two things that sound like limits and are not:
|
|
24
|
-
|
|
25
|
-
- _"ESLint only sees one file"_ — true of its AST, false of the rule: a rule is an ordinary
|
|
26
|
-
JS module and may call `execFileSync("git", …)` or read a sibling file. If the reason to
|
|
27
|
-
stay a script is "it needs git", that reason is weak; measure the real cost before using it.
|
|
28
|
-
- _"this runs programs, not lints files"_ — that is a real limit, and the answer is the seam
|
|
29
|
-
already proven here: **a script PRODUCES facts into a JSON file, and ESLint JUDGES that
|
|
30
|
-
file.** The verdict lands in the engine even though the work did not.
|
|
31
|
-
|
|
32
|
-
## 📊 STATE — measured 2026-09-16 (re-measure, never cite)
|
|
33
|
-
|
|
34
|
-
| | |
|
|
35
|
-
| ----------------------- | -----------------------------------------------------------------------------------------: |
|
|
36
|
-
| ESLint rules | **5** — `latex-language` · `tex-build` · `papers` · `review-findings-cause` · `doc-fields` |
|
|
37
|
-
| harnesses | **57** |
|
|
38
|
-
| skills | **24** |
|
|
39
|
-
| hooks (runnable `.mjs`) | **5** |
|
|
40
|
-
| repo-wide scripts | 5 |
|
|
41
|
-
| files tracked / commits | 308 / 43 |
|
|
42
|
-
|
|
43
|
-
**A real consumer dogfoods this package on every CI run**, so a breaking change here turns a
|
|
44
|
-
paper pipeline red somewhere else the same day. That is deliberate — it is the only thing
|
|
45
|
-
keeping the extraction honest.
|
|
46
|
-
|
|
47
|
-
⚠️ **This table is a SNAPSHOT, not a fact about today, and it has already gone stale once.**
|
|
48
|
-
The line standing here until 2026-09-16 said "two ESLint rule modules" while six were shipped,
|
|
49
|
-
24 skills had moved in, and hooks existed at all. Re-measure with `git ls-files` before
|
|
50
|
-
repeating any number from it.
|
|
7
|
+
Machine-checkable gates for writing a research paper in git, extracted from a private knowledge base.
|
|
51
8
|
|
|
52
|
-
|
|
9
|
+
This file loads on every turn, so it holds only what applies to **any** change here. A rule that applies to one kind of work lives in that folder's `CLAUDE.md`, which loads when a file there is read or edited (table below); a fact a machine checks lives beside that check. Every `CLAUDE.md` here is compiled from the `CLAUDE.md.spec.ts` beside it: edit the spec, then `npx vigiles compile`. What broke is in `docs/incidents.md`; the measurements behind the rules are in [git](https://github.com/zernie/paperlint/blob/b4a3dc3a81d436186f41db2d69b65fd93f1af3b6/CLAUDE.md#L62-L199).
|
|
53
10
|
|
|
54
|
-
|
|
55
|
-
npm install
|
|
56
|
-
```
|
|
11
|
+
## 🎯 THE GOAL, in one sentence
|
|
57
12
|
|
|
58
|
-
|
|
59
|
-
harness, spec and hook resolves through it. A container where `npm install` never ran fails
|
|
60
|
-
in ways that look like broken code rather than a missing install.
|
|
61
|
-
|
|
62
|
-
## The ten rules that decide what may live here — and what may not be written
|
|
63
|
-
|
|
64
|
-
**1. Mechanism goes to vigiles, data stays here.** A file that names nothing local — no rule
|
|
65
|
-
of ours, no fixture of ours — is machinery, and machinery belongs in
|
|
66
|
-
[vigiles](https://github.com/zernie/vigiles). Ask it in two steps: is this mechanism or data?
|
|
67
|
-
If mechanism — does it know about _this_ domain? If not, it is not ours.
|
|
68
|
-
|
|
69
|
-
**2. A check over an AST is a LINT RULE, not a script.** If it walks `.ts`/`.js`/`.tex` and
|
|
70
|
-
looks at declarations, names or nodes, write an ESLint rule: it fires in the editor on save,
|
|
71
|
-
gets the AST for free, and has a suppression syntax people already know. Scripts are for
|
|
72
|
-
corpus-wide questions — index connectivity, ratios across many files — not for one file's
|
|
73
|
-
nodes.
|
|
74
|
-
|
|
75
|
-
**3. Every check needs BOTH halves, or it is not tested.** It must FIRE on a planted defect
|
|
76
|
-
and stay QUIET on a clean fixture. A check that has only been seen quiet is
|
|
77
|
-
indistinguishable from a dead one — silence is its success state. Prove the fire half by
|
|
78
|
-
seeing the test go RED before the fix lands ([`docs/testing.md`](docs/testing.md)).
|
|
79
|
-
|
|
80
|
-
**One rule, one requirement.** A rule checks one thing a user would want to switch off, or set a
|
|
81
|
-
severity for, on its own; two checks with different reasons to disable them are two rules. Several
|
|
82
|
-
messageIds for facets of one requirement are fine. Example: `pdf/limits` checked body pages,
|
|
83
|
-
reference pages and the reference font size — the first two are one page budget and became
|
|
84
|
-
`format/page-limit`; the font size is a different requirement and moved to `pdf/body-size`.
|
|
85
|
-
|
|
86
|
-
**A rule that judges a venue requirement is validated on real accepted papers of that venue** (or
|
|
87
|
-
of its parent venue, with the same template), built to PDF, with the ground truth established
|
|
88
|
-
independently of the rule — rendered and looked at — plus variant fixtures derived from those real
|
|
89
|
-
sources, one change each. Synthetic minimal fixtures alone are not enough: they are written by the
|
|
90
|
-
same hand as the rule, and share its blind spots. Example: `format/page-limit`'s body count, on the
|
|
91
|
-
built ACSAC papers in `fixtures/accepted-papers/` (`test/e2e/tex/page-count.e2e.ts`).
|
|
92
|
-
|
|
93
|
-
**A new rule ships with its page**, `docs/rules/<group>/<rule>.md`, in these sections: What it
|
|
94
|
-
catches · Why · Examples (failing / passing) · Options / preset fields · What it does not check ·
|
|
95
|
-
How to fix. Its `meta.docs.url` points at that page, and its row in `docs/rules.md` is one line
|
|
96
|
-
linking there. `src/rule-docs.test.ts` holds every rule the config registers to this; the rules
|
|
97
|
-
older than the convention wait in its `AWAITING_PAGE` list, which only shrinks — #131 writes their
|
|
98
|
-
pages.
|
|
99
|
-
|
|
100
|
-
**4. `exit 0` with empty output is NOT "clean".** A rule whose glob matched no files reports
|
|
101
|
-
exactly like a rule that passed. Any rule shipped here must be loud when its input set is
|
|
102
|
-
empty. This is the specific defect that blocks stage 1 of the plan: in the source base a
|
|
103
|
-
fresh clone yields RC=0, 652 findings, zero errors — because 19 rules saw no files at all.
|
|
104
|
-
|
|
105
|
-
**5. "It can't be done in the engine" must be MEASURED, not assumed.** Every one of these was
|
|
106
|
-
stated confidently on 2026-09-16 and every one fell to a single command:
|
|
107
|
-
|
|
108
|
-
| the claim | what one command showed |
|
|
109
|
-
| ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
110
|
-
| "a lint rule can't know a git date, so this stays a script" | a rule is plain JS; `execFileSync("git", …)` is legal in it. The real costs (per-file invocation, `--cache` keyed on content) are solvable, so this was a preference dressed as a limit |
|
|
111
|
-
| "we need our own glob expander" | `fs.globSync` ships in Node 22 and returned the identical set. 31 hand-written lines of regex existed for nothing |
|
|
112
|
-
| "the tool can't run a file that config excludes" | it can, and says so: `matches exclude … — running because you named it` |
|
|
113
|
-
|
|
114
|
-
⇒ Before writing machinery, **run the thing you are about to replace and paste its output.**
|
|
115
|
-
"I couldn't get it to work" is data about the attempt, not about the tool.
|
|
116
|
-
|
|
117
|
-
**6. A program shipped by this package MUST NOT depend on the consumer's cwd.** Measured
|
|
118
|
-
2026-09-16: three hooks here read their config as `provide("pkg", "cat package.json")`. The
|
|
119
|
-
consumer's session changed directory into a subfolder for unrelated reasons, `cat` failed, and
|
|
120
|
-
the Bash gate — which fails closed, correctly — denied **every command in that session**,
|
|
121
|
-
including the one that would undo it. The nudges next to it would have failed _silently_,
|
|
122
|
-
which is worse.
|
|
123
|
-
|
|
124
|
-
Resolve paths from the repository root (`git rev-parse --show-toplevel`) or from the module's
|
|
125
|
-
own location, never from where the caller happens to stand. The blast radius of a cwd
|
|
126
|
-
assumption is not this package — it is somebody else's whole session.
|
|
127
|
-
|
|
128
|
-
**7. Do not describe what you have not opened.** A `README` here nearly shipped the line
|
|
129
|
-
"MIT — see LICENSE" on 2026-09-16. There is no `LICENSE` file and `package.json` says
|
|
130
|
-
`UNLICENSED`. Publishing is irreversible and this repo is public: every factual claim in a
|
|
131
|
-
document meant for strangers gets checked against the disk in the same pass that writes it.
|
|
132
|
-
|
|
133
|
-
**8. 🔴 NEVER WRITE A GLOB OR A REGEX INSIDE A BLOCK COMMENT.** An asterisk followed by a
|
|
134
|
-
slash **ends the comment**, wherever it appears — in a path (a folder wildcard), in a regex
|
|
135
|
-
whose last literal is an asterisk (one matching bold markup, for instance), in a quoted
|
|
136
|
-
example. The rest of the comment becomes code, the file stops parsing, and the error points
|
|
137
|
-
at a line further down that is perfectly fine.
|
|
138
|
-
|
|
139
|
-
⚠️ Note this rule does not quote the sequence either, not even here. A documented example is
|
|
140
|
-
the thing that gets copied into code — and that is exactly how the fourth occurrence
|
|
141
|
-
happened: it was copied out of a comment written to explain the first three.
|
|
142
|
-
|
|
143
|
-
This is not a hypothetical and not a rare slip. **It fired four times in a single session on
|
|
144
|
-
2026-09-16** — three in the consumer, once here — and each time the diagnosis cost minutes
|
|
145
|
-
because `SyntaxError: Unexpected token '.'` says nothing about comments.
|
|
146
|
-
|
|
147
|
-
| instead of | write |
|
|
148
|
-
| --------------------------------------- | ----------------------------------------------------- |
|
|
149
|
-
| a glob with an asterisk and slash | spell it: "every folder under papers" |
|
|
150
|
-
| a regex with an asterisk before a slash | describe what it matched, in words |
|
|
151
|
-
| an example needing both | put it in a line comment (two slashes), never a block |
|
|
152
|
-
|
|
153
|
-
⇒ **In a block comment, prose describes the pattern; it never quotes it.** If the exact
|
|
154
|
-
characters matter, they belong in the code or in a line comment beside it.
|
|
155
|
-
|
|
156
|
-
**9. Measure the DEFECT before proposing the fix — and read "this is quick" as a warning.**
|
|
157
|
-
Rule 5 is about the tool you are replacing; this one is about the order of work.
|
|
158
|
-
|
|
159
|
-
1. **Show the defect**: command output, or a file quote with a line number. A proposed fix with
|
|
160
|
-
no exhibited defect is not a fix, it is a preference.
|
|
161
|
-
2. **Name the layer and the channel** it touches: ESLint rule · skill · hook · CLI · CI action
|
|
162
|
-
· path resolution. More than one is a conversation, not a commit.
|
|
163
|
-
3. **An adversarial second pass is encouraged, and it is not free.** Spend it on a fork in the
|
|
164
|
-
road, on anything that goes outward, and on a conclusion you are about to act on.
|
|
165
|
-
4. **A quick fix is almost never quick** — it is quick to _propose_ precisely because nothing
|
|
166
|
-
was opened.
|
|
167
|
-
|
|
168
|
-
Four proposals were made and withdrawn in one session on 2026-09-17 for exactly this reason —
|
|
169
|
-
[`docs/incidents.md`](docs/incidents.md).
|
|
170
|
-
|
|
171
|
-
**10. Effects live in adapters, and WHERE paperlint IS INSTALLED lives in ONE of them.** Rule 6 generalised: the caller's cwd is one case of it. Checking logic — lint rules,
|
|
172
|
-
skills, hooks — must not know its own location, nor its distance from anything else. Every
|
|
173
|
-
answer to _where_ comes from one of two modules, each for one side: the consumer (its root, the
|
|
174
|
-
package's name) from `skills/paper-pipeline/scripts/consumer.mjs`, which adapts per channel — own
|
|
175
|
-
checkout · `node_modules` · plugin cache · CI; the package's own data (`presets/`, `presets/tex/`)
|
|
176
|
-
from `src/package-dirs.ts`. A skill naming a script by an install-specific path in its own prose
|
|
177
|
-
walks around that door, and 208 such literals across 190 lines do exactly that.
|
|
178
|
-
|
|
179
|
-
<!-- The ports' paths above are resolved by `npm run check` (vigiles lint), since #67. -->
|
|
180
|
-
<!-- vigiles:file skills/paper-pipeline/scripts/consumer.mjs -->
|
|
181
|
-
<!-- vigiles:file src/package-dirs.ts -->
|
|
182
|
-
|
|
183
|
-
The layer rules for `src/` live in [`src/CLAUDE.md`](src/CLAUDE.md) and are enforced by the linter.
|
|
184
|
-
|
|
185
|
-
⏳ **Still owed: the install-path half** — a lint rule that makes an install-specific path literal
|
|
186
|
-
outside `consumer.mjs` a finding. Prose will not hold this class — four silent breakages happened
|
|
187
|
-
_while_ comments explaining the hazard sat directly above the code
|
|
188
|
-
([`docs/incidents.md`](docs/incidents.md)).
|
|
189
|
-
|
|
190
|
-
**11. Installing and using paperlint must be as smooth as possible.** Count the actions between "I
|
|
191
|
-
want this" and "it works": every command to copy, flag to pass or file to edit is one more place
|
|
192
|
-
to give up. The target is `npm i` plus one command. A per-paper script, a manual TeX install or a
|
|
193
|
-
"now add this to your config" step is a defect in paperlint, not a user task. The only exception is a
|
|
194
|
-
choice that really belongs to the user (irreversible, paid, privacy), and then it is named at the
|
|
195
|
-
moment it is asked. The converse holds too: an automatic step that can fail silently is worse
|
|
196
|
-
than an explicit one — it works, or it says loudly that it did not. Measured example of the
|
|
197
|
-
converse: Tectonic installs as one file but silently replaced Times with Latin Modern on a plain
|
|
198
|
-
`article` paper and still exited 0 (#35, #59).
|
|
199
|
-
|
|
200
|
-
## Before changing the command surface or a delivery channel — read the prior art
|
|
201
|
-
|
|
202
|
-
[`docs/prior-art/`](docs/prior-art/README.md) records how comparable tools solved the same
|
|
203
|
-
problems, each claim with the URL that was checked: Quarto and Vale (the domain and content
|
|
204
|
-
analogues), Biome (one tool, one config, one command), Danger and reviewdog (who decides to
|
|
205
|
-
fail a run), `unicorn/expiring-todo-comments` and Semgrep (checks that read a clock or a diff).
|
|
206
|
-
|
|
207
|
-
It is here as a POINTER and stays a pointer: this file is read on every turn, so it carries
|
|
208
|
-
the instruction and never the evidence — the same split as rules 9 and 10 and `docs/incidents.md`.
|
|
209
|
-
|
|
210
|
-
🔴 The argument "we need another command for X" is, in every tool examined, an argument that
|
|
211
|
-
the CONFIG is not declaring something. Check that before adding a verb.
|
|
212
|
-
|
|
213
|
-
Rejected on 2026-09-27, so it is not proposed again: a separate `paperlint refs` command for the
|
|
214
|
-
reference check. The slowness it would have moved (#107) was a cache thrown away after every build,
|
|
215
|
-
not a missing verb. The fix keeps the check in `build` and commits what the services answered in
|
|
216
|
-
`<paper>/repro/references-cache.json` — **raw responses, never verdicts**: verdicts are derived
|
|
217
|
-
again on every build, so a checker fix reaches every paper ([`docs/references.md`](docs/references.md)).
|
|
218
|
-
|
|
219
|
-
## Distribution — one install path: npm, then `paperlint init`
|
|
220
|
-
|
|
221
|
-
`npm i -D paperlint` brings all the code — rules, skills, hooks, scripts. `paperlint init`
|
|
222
|
-
then does what only a command can, because it depends on the project it lands in: it finds the
|
|
223
|
-
papers directory and declares it in a root `paperlint.json` when it is not the default `papers`, links each skill into `.claude/skills/`, writes
|
|
224
|
-
the hook commands into `.claude/settings.json` (vigiles' `mergeRegistrations`, reading
|
|
225
|
-
`plugin/hooks/hooks.json` as the one source), and offers a CI workflow that runs the action from
|
|
226
|
-
`node_modules` after `npm ci`. `paperlint doctor` reads all of it back. Details: `docs/install.md`.
|
|
227
|
-
|
|
228
|
-
**There is no Claude Code plugin or marketplace entry; it was removed in the release after 1.0.0
|
|
229
|
-
(#82).** Do not bring it back without answering these, each measured:
|
|
230
|
-
|
|
231
|
-
- **A plugin cannot carry the code.** Claude Code runs `npm ci --ignore-scripts` for a plugin only
|
|
232
|
-
when its root holds a `package.json` and a lockfile, with a 60-second timeout, and _"a failed or
|
|
233
|
-
skipped install never blocks the plugin"_: on a slow network the hooks load and fail with
|
|
234
|
-
`cannot find module vigiles`, silently.
|
|
235
|
-
- **The skills need the npm package anyway.** 23 of 24 skills run scripts under
|
|
236
|
-
`paper-pipeline/scripts`, which resolve only through `node_modules/paperlint/`.
|
|
237
|
-
A plugin-only consumer got skills whose first command fails.
|
|
238
|
-
- **Its manifests carried versions nothing updated** (0.0.1 and 0.1.0 while npm was at 1.0.0), and
|
|
239
|
-
Claude Code decides plugin updates from that number.
|
|
240
|
-
|
|
241
|
-
`plugin/hooks/hooks.json` stays where it is: it is not a plugin any more, it is the hook wiring
|
|
242
|
-
`paperlint init` merges into the consumer's settings.
|
|
243
|
-
|
|
244
|
-
## Delivery — how this repo's contents reach a consumer (measured 2026-09-11)
|
|
245
|
-
|
|
246
|
-
Three channels, each measured on a fixture rather than assumed. The consumer here is the
|
|
247
|
-
private knowledge base this was extracted from; nothing below is specific to it.
|
|
248
|
-
|
|
249
|
-
### Skills ship as `skills/`, and the consumer SYMLINKS them
|
|
250
|
-
|
|
251
|
-
**Do not move skills to `.claude/skills/` inside this repo.** They live in `skills/`
|
|
252
|
-
(`SHIPPED_SKILLS_DIR` in `skills/paper-pipeline/scripts/consumer.mjs`, read by the linker and the
|
|
253
|
-
install e2e), which is the standard layout. An ecosystem scan of 855 npm packages (by published
|
|
254
|
-
tarball, not repository) found **336 shipping `skills/<n>/SKILL.md` against 15 shipping `.claude/skills/`** — 22 : 1. The
|
|
255
|
-
top of the market is entirely on `skills/`: `@vitejs/devtools-kit` (330 896 downloads/wk),
|
|
256
|
-
`@slidev/cli` (56 809), `anthropics/skills` (175 673 stars).
|
|
257
|
-
|
|
258
|
-
**The consumer's side is a symlink per skill, made by `paperlint init` (`src/link-skills.ts`):**
|
|
13
|
+
**Move every paper-writing convention a machine can decide OUT of prose and INTO an engine that fails the build** — and keep everything else honestly labelled as prose. The engine is ESLint. The unit of progress is "one more verdict decided by the engine instead of by a hand-written script".
|
|
259
14
|
|
|
260
15
|
```
|
|
261
|
-
|
|
16
|
+
prose in a guideline → hand-written script → ESLint rule in the engine
|
|
17
|
+
rots silently runs, but is OURS editor-time · free AST · suppressible
|
|
262
18
|
```
|
|
263
19
|
|
|
264
|
-
|
|
265
|
-
`name:` in the frontmatter — so the link must be named exactly as the skill.
|
|
266
|
-
|
|
267
|
-
⚠️ Do NOT rely on a consumer picking `.claude/skills/` up out of `node_modules` on its own.
|
|
268
|
-
It does happen — such a directory is an ordinary nested one — but only **lazily and
|
|
269
|
-
silently**, the first time the agent happens to read a file inside that package. A symlink
|
|
270
|
-
loads at startup, deterministically. Measured both ways on claude 2.1.268.
|
|
271
|
-
|
|
272
|
-
### Hooks ship as `.mjs`, NEVER as `.hook.ts`
|
|
273
|
-
|
|
274
|
-
Measured on a fixture — one hook, four locations, both halves (an input that must be denied
|
|
275
|
-
and one that must pass), exit code taken without a pipe:
|
|
276
|
-
|
|
277
|
-
| hook location | deny input | allow input |
|
|
278
|
-
| ----------------------------------------------------- | ----------------------- | --------------------------- |
|
|
279
|
-
| `node_modules/<pkg>/.claude/hooks/probe.hook.**mjs**` | RC=2, fires | RC=0, silent |
|
|
280
|
-
| symlink into `node_modules`, `.mjs` | RC=2, fires | RC=0, silent |
|
|
281
|
-
| local control, `.mjs` | RC=2, fires | RC=0, silent |
|
|
282
|
-
| `node_modules/<pkg>/.claude/hooks/probe.hook.**ts**` | RC=2 `cannot be loaded` | **RC=2 `cannot be loaded`** |
|
|
283
|
-
|
|
284
|
-
The last row is not "it blocks the dangerous thing" — it fails to load and therefore blocks
|
|
285
|
-
**everything**, including `echo hi`. A consumer in that state cannot run any Bash command,
|
|
286
|
-
and the one command that would repair it is also Bash.
|
|
287
|
-
|
|
288
|
-
⇒ **The package ships `.mjs`** — the consumer gets something that loads.
|
|
20
|
+
🔴 **A hand-written script is a WAYPOINT, not a destination.** A new one is allowed only when the engine genuinely cannot express the check, and "cannot" means MEASURED. A rule is an ordinary JS module and may call `execFileSync("git", …)` or read a sibling file. When the work has to run a program, **a script PRODUCES facts into a JSON file, and ESLint JUDGES that file.**
|
|
289
21
|
|
|
290
|
-
|
|
291
|
-
cause is swallowed by a `catch` in vigiles' `hook-runtime.js`, and calling `loadHookProgram`
|
|
292
|
-
directly measures a different load path — it fails even on the control. Knowing _that_ is
|
|
293
|
-
enough to choose the carrier.)
|
|
22
|
+
**A real consumer dogfoods this package on every CI run**, so a breaking change here turns a paper pipeline red somewhere else the same day. CI runs on every pull request (`.github/workflows/ci.yml`). Counts of rules, skills and hooks: measure with `git ls-files`.
|
|
294
23
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
This section used to promise «the `.hook.ts` source lives HERE and is typechecked HERE; the
|
|
298
|
-
package ships the compiled `.mjs`». Shipping the first three hooks retired that plan, and the
|
|
299
|
-
reason is worth keeping: **a twin can drift from its build, and nothing would notice.** One file
|
|
300
|
-
cannot.
|
|
301
|
-
|
|
302
|
-
What the twin was for was the CAPABILITY CHECK — `vigiles compile` refuses a hook that imports
|
|
303
|
-
anything but `vigiles/hook`, because the import list _is_ the capability surface. That check is a
|
|
304
|
-
function, `checkHookImports`, and `hooks/hooks.harness.ts` runs it over every shipped
|
|
305
|
-
`.hook.mjs` directly. Same check, applied to the artifact that actually executes, with no second
|
|
306
|
-
file to keep in step. What is lost is `tsc` on the hook body and the typed `e.ctx` — named here
|
|
307
|
-
rather than left as an omission.
|
|
24
|
+
## First command in a fresh container
|
|
308
25
|
|
|
309
|
-
|
|
310
|
-
Measured 2026-09-12: the check failed on `paper-edit-guard.hook.mjs`'s own docblock, which quoted
|
|
311
|
-
a rejected import while explaining why it was rejected. Do not loosen the check to make prose
|
|
312
|
-
fit — it is the same check any future `compile`/`lint` pass applies to the shipped file. Describe
|
|
313
|
-
a forbidden import in words instead of writing one.
|
|
26
|
+
`npm install` — not optional: `vigiles` is a real dependency, and every harness, spec and hook resolves through it. Without it, failures look like broken code rather than a missing install.
|
|
314
27
|
|
|
315
|
-
|
|
28
|
+
## Where the other rules live
|
|
316
29
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
30
|
+
| when you are… | read | holds |
|
|
31
|
+
| --- | --- | --- |
|
|
32
|
+
| writing a lint rule, or a check that judges prose | [`eslint-rules/CLAUDE.md`](eslint-rules/CLAUDE.md) | lint rule over script, what holds each requirement of a new rule, the five writing principles |
|
|
33
|
+
| writing or changing a hook | [`hooks/CLAUDE.md`](hooks/CLAUDE.md) | how hooks ship, and what was rejected |
|
|
34
|
+
| writing or changing a skill | [`skills/CLAUDE.md`](skills/CLAUDE.md) | how skills ship |
|
|
35
|
+
| in `src/` | [`src/CLAUDE.md`](src/CLAUDE.md) | the two layer axes |
|
|
36
|
+
| thinking of a Claude Code plugin | [`plugin/CLAUDE.md`](plugin/CLAUDE.md) | why there is none, and what must be measured to bring it back |
|
|
37
|
+
| changing CI, Dependabot or the release | [`.github/CLAUDE.md`](.github/CLAUDE.md) | cost, Dependabot cadence, release from the PR title |
|
|
38
|
+
| writing a test | [`docs/testing.md`](docs/testing.md), [`docs/e2e.md`](docs/e2e.md) | which kind of test, fakes, the e2e triggers |
|
|
320
39
|
|
|
321
|
-
|
|
322
|
-
$ npx vigiles compile
|
|
323
|
-
✗ .vigiles/hooks/probe.hook.ts — hook program uses capabilities outside `vigiles/hook`:
|
|
324
|
-
<pkg>/hooks/decide.mjs — only the sanctioned API is allowed (capability = API surface).
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
`compile` is also what writes the tamper-evident stamp, so a hook it refuses **can never be
|
|
328
|
-
re-stamped** — and the runtime fails CLOSED on a stamp that no longer matches its source:
|
|
329
|
-
|
|
330
|
-
```
|
|
331
|
-
vigiles: hook … does not match its compiled stamp (tampered).
|
|
332
|
-
… the way out is a FILE WRITE, not a command — this refusal blocks the recompile too.
|
|
333
|
-
```
|
|
40
|
+
## Before every push
|
|
334
41
|
|
|
335
|
-
|
|
336
|
-
state is clearing the stamp to `{}` and running permanently unstamped. Shipping the whole program
|
|
337
|
-
keeps the stamp question from arising (no sidecar ⇒ no check) and pins the source by lockfile
|
|
338
|
-
integrity instead — stronger than a local stamp, since a consumer cannot hand-edit an installed
|
|
339
|
-
tree without the next install reverting it.
|
|
42
|
+
`npm run check`, exit code read without a pipe. Red first · assert the whole value · no test touches the real network · 100% coverage, no `c8 ignore`. In a container without TeX Live, only the TeX e2e gate fails, and it says so; `npx paperlint toolchain` installs it.
|
|
340
43
|
|
|
341
|
-
|
|
44
|
+
## Commits
|
|
342
45
|
|
|
343
|
-
|
|
344
|
-
no compile step on the consumer's side:
|
|
46
|
+
Conventional-commit subject, body says what was MEASURED, not what was intended. A number in a commit message that no command produced is the thing this repo exists to make impossible.
|
|
345
47
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
"type": "command",
|
|
349
|
-
"command": "node \"$CLAUDE_PROJECT_DIR/node_modules/vigiles/dist/cli.js\" hook-runtime run-program \"$CLAUDE_PROJECT_DIR/node_modules/paperlint/hooks/paper-edit-guard.hook.mjs\""
|
|
350
|
-
}
|
|
351
|
-
```
|
|
48
|
+
**The subject is also the release**: semantic-release reads the squash commit, which is the PR title. `feat:` → minor, `fix:` or `perf:` → patch, `!` or a `BREAKING CHANGE:` footer → major,
|
|
49
|
+
`docs:` `chore:` `ci:` `test:` `refactor:` → no release. `.github/workflows/pr-title.yml` rejects a title without a conventional prefix.
|
|
352
50
|
|
|
353
|
-
|
|
354
|
-
the point — so the papers-root resolver is spelled out in all three hook files. Duplication that
|
|
355
|
-
cannot be removed is CHECKED instead: part VII of the harness compares the captured values
|
|
356
|
-
against each other, rather than grepping for a literal (a substring search finds the same text in
|
|
357
|
-
the prose _about_ the value one line above it).
|
|
51
|
+
## Rules
|
|
358
52
|
|
|
359
|
-
###
|
|
53
|
+
### Mechanism Goes To Vigiles
|
|
360
54
|
|
|
361
|
-
|
|
55
|
+
**Guidance only** — A file that names nothing local — no rule or fixture of ours — is machinery, and belongs in [vigiles](https://github.com/zernie/vigiles); data stays here.
|
|
362
56
|
|
|
363
|
-
|
|
364
|
-
resolve OK -> <consumer>/node_modules/vigiles/dist/hook.js
|
|
365
|
-
```
|
|
57
|
+
### Measure Before Calling It Impossible
|
|
366
58
|
|
|
367
|
-
|
|
368
|
-
for its own `vigiles test` and `vigiles compile`. That is `devDependencies`, which `npm i` of
|
|
369
|
-
a dependency does not install. Putting it in `dependencies` risks npm installing a **second**
|
|
370
|
-
copy under `node_modules/paperlint/node_modules/vigiles` whenever the ranges
|
|
371
|
-
drift — two runtimes, two sets of stamps and state.
|
|
372
|
-
|
|
373
|
-
🔴 **THE PARAGRAPH ABOVE WAS TRUE AND THE INSTALL DID THE OPPOSITE — measured 2026-09-17.**
|
|
374
|
-
`devDependencies` is not the only entry naming `vigiles`: `peerDependencies` names it too, and
|
|
375
|
-
**npm 7+ installs peers automatically**. So every consumer got it anyway, together with its
|
|
376
|
-
transitive weight. `npm pack`, then install the tarball into an empty project:
|
|
377
|
-
|
|
378
|
-
| | packages | `du -sm node_modules` |
|
|
379
|
-
| ------------------------------------------------------- | -------: | --------------------: |
|
|
380
|
-
| peer as declared before | 188 | **142 MB** |
|
|
381
|
-
| `peerDependenciesMeta: { vigiles: { optional: true } }` | 164 | **56 MB** |
|
|
382
|
-
|
|
383
|
-
The 86 MB are `@ast-grep/napi` (51 MB) and `typescript` (23 MB), pulled through `vigiles` — and
|
|
384
|
-
paid for by a consumer who only wants the ESLint rules and never loads a hook.
|
|
385
|
-
|
|
386
|
-
`optional: true` is the entry that matches what this section already argues: the consumer brings
|
|
387
|
-
its own `vigiles` _when it uses the hooks_, and npm stops deciding that for them. Both halves
|
|
388
|
-
measured on the 56 MB tree: `eslint-rules/latex-language.mjs` and the skill scripts load and
|
|
389
|
-
run (RC=0), while
|
|
390
|
-
`hooks/paper-edit-guard.hook.mjs` fails with `ERR_MODULE_NOT_FOUND` — which is this contract
|
|
391
|
-
working, not a defect, exactly as argued below.
|
|
392
|
-
|
|
393
|
-
⚠️ The consumer in this project's own base is unaffected: it declares `vigiles` itself
|
|
394
|
-
(`devDependencies: ^27.2.0`), so nothing about its tree changes.
|
|
395
|
-
|
|
396
|
-
### The `.bib` parser is optional for the same reason, and the failure says so out loud
|
|
397
|
-
|
|
398
|
-
`@retorquere/bibtex-parser` is imported at exactly one site
|
|
399
|
-
(`skills/paper-pipeline/scripts/extract-ref-facts.mjs`, and already through a dynamic
|
|
400
|
-
`await import`), and it costs **15 MB of a 56 MB tree**: 9 MB itself, plus
|
|
401
|
-
`wink-eng-lite-web-model` (4 MB, an English NLP model) and `unicode2latex` (2 MB). That is 27%
|
|
402
|
-
of the install for one call that only a consumer extracting bibliography facts ever makes.
|
|
403
|
-
|
|
404
|
-
| | packages | `du -sm node_modules` |
|
|
405
|
-
| ------------------------------------------ | -------: | --------------------: |
|
|
406
|
-
| after the `vigiles` peer was made optional | 164 | 56 MB |
|
|
407
|
-
| parser moved to an optional peer as well | 149 | **39 MB** |
|
|
408
|
-
|
|
409
|
-
🔴 **`optionalDependencies` is the wrong entry and was tried first** — npm _installs_ those and
|
|
410
|
-
only tolerates failure, so the weight stays. What makes a dependency genuinely opt-in is
|
|
411
|
-
`peerDependencies` + `peerDependenciesMeta: { optional: true }`, the same pair used for `vigiles`.
|
|
412
|
-
It stays in `devDependencies` too, because this package's own harnesses parse `.bib`.
|
|
413
|
-
|
|
414
|
-
⚠️ A silent skip here would be the worst outcome: a missing checker and a passing one look
|
|
415
|
-
identical, and "the bibliography was not checked" reads as "the bibliography is fine". So the
|
|
416
|
-
absence throws, and the message carries the cure rather than the diagnosis:
|
|
59
|
+
**Guidance only** — "It can't be done in the engine" must be MEASURED, not assumed. Each of these was stated confidently on 2026-09-16 and fell to a single command:
|
|
417
60
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
61
|
+
| the claim | what one command showed |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| "a lint rule can't know a git date, so this stays a script" | a rule is plain JS; `execFileSync("git", …)` is legal in it, and its real costs are solvable |
|
|
64
|
+
| "we need our own glob expander" | `fs.globSync` ships in Node 22 and returned the identical set |
|
|
65
|
+
| "the tool can't run a file that config excludes" | it can, and says so: `matches exclude … — running because you named it` |
|
|
423
66
|
|
|
424
|
-
|
|
425
|
-
mismatch means a hook compiled by one version is executed by another: the stamp does not
|
|
426
|
-
verify, the hook does not load, and `PreToolUse` refuses every command. That already happened
|
|
427
|
-
in the consumer on 2026-09-10 (25.1.0 -> 27.1.4) and cost real recovery work.
|
|
67
|
+
Before writing machinery, **run the thing you are about to replace and paste its output.**
|
|
428
68
|
|
|
429
|
-
|
|
430
|
-
npm ls vigiles # prints `invalid` when what is installed does not satisfy the manifest
|
|
431
|
-
```
|
|
69
|
+
### Do Not Describe What You Have Not Opened
|
|
432
70
|
|
|
433
|
-
|
|
434
|
-
like a freshness check.** It compares what is INSTALLED against this repo's MANIFEST. It says
|
|
435
|
-
nothing about the registry. Measured 2026-09-16: manifest `^27.1.4`, installed `27.1.6`,
|
|
436
|
-
published `27.2.0` — exit code **0**, because the range is satisfied. The repo had been one
|
|
437
|
-
minor behind for days and every local check was green.
|
|
71
|
+
**Guidance only** — Every factual claim in a document meant for strangers is checked against the disk in the same pass that writes it.
|
|
438
72
|
|
|
439
|
-
|
|
73
|
+
### No Glob Or Regex In A Block Comment
|
|
440
74
|
|
|
441
|
-
|
|
442
|
-
because the obvious objection to a bot in this org is already recorded and does NOT apply:
|
|
443
|
-
a bot was switched off in a sibling repository for burning Actions minutes — **that
|
|
444
|
-
repository is private**. This one is public, minutes are free, so the objection does not
|
|
445
|
-
travel. If this repo is ever made private, revisit the file along with it.
|
|
75
|
+
**Guidance only** — 🔴 An asterisk followed by a slash ends the comment wherever it appears. In a block comment, prose describes the pattern and never quotes it; exact characters go in code or a line comment.
|
|
446
76
|
|
|
447
|
-
|
|
448
|
-
until you read the reference:
|
|
77
|
+
### Measure The Defect Before The Fix
|
|
449
78
|
|
|
450
|
-
|
|
451
|
-
| ------------------- | ---------: | --------------------------------------------------------------------------------------- |
|
|
452
|
-
| `schedule.interval` | — | the check runs on the schedule and only on the schedule |
|
|
453
|
-
| **`cooldown`** | **3 days** | _"a new version is not considered for a version update until 3 days after its release"_ |
|
|
79
|
+
**Guidance only** — Read "this is quick" as a warning. Show the defect (output, or a quoted line), name the layer and channel (more than one is a conversation, not a commit), and spend an adversarial pass on forks and on anything that goes outward.
|
|
454
80
|
|
|
455
|
-
|
|
456
|
-
`daily`, and `vigiles` is listed in `cooldown.exclude`, so for THIS package the window is one
|
|
457
|
-
schedule tick.
|
|
81
|
+
### Installing Paperlint Is Smooth
|
|
458
82
|
|
|
459
|
-
**
|
|
460
|
-
yanked hours later. That is a real risk for a third-party package and an empty one for our own
|
|
461
|
-
— we would be the ones yanking it, and we can ship several versions of it in a single day, so
|
|
462
|
-
a three-day hold would have the bot proposing the version from the day before yesterday.
|
|
83
|
+
**Guidance only** — The target is `npm i` plus one command; any further manual step is a paperlint defect, except a choice that really belongs to the user, named when it is asked. An automatic step that can fail silently is worse than an explicit one.
|
|
463
84
|
|
|
464
|
-
|
|
465
|
-
the primary path, because no schedule can outrun same-day releases.** The primary path is the
|
|
466
|
-
rule already recorded in the consumer's base — merge a PR in `vigiles`, bump every consumer in
|
|
467
|
-
the same pass. The bot is the backstop for the case that actually bit us: the rule named ONE
|
|
468
|
-
consumer while there were two, and this repo sat forgotten on `^27.1.4`.
|
|
85
|
+
### Every Check Has Both Halves
|
|
469
86
|
|
|
470
|
-
|
|
471
|
-
it: `dependabot.yml` configures _version_ updates only. **Security** updates are a separate
|
|
472
|
-
mechanism driven by advisories and a repository SETTING; their cadence cannot be changed from
|
|
473
|
-
this file, and deleting the file would not stop them.
|
|
87
|
+
**Guidance only** — Every check — lint rule, script, hook, harness — fires on a planted defect AND stays quiet on a clean fixture, and the test goes red before the fix lands. A check only ever seen quiet is indistinguishable from a dead one. [Details](docs/testing.md)
|
|
474
88
|
|
|
475
|
-
|
|
476
|
-
for updates**.
|
|
89
|
+
### Never The Callers Cwd
|
|
477
90
|
|
|
478
|
-
|
|
91
|
+
**Guidance only** — No program this package ships (`bin/`, `lib/`, hooks, skill scripts) depends on the directory it is started from: resolve from `git rev-parse --show-toplevel` or from the module's own location.
|
|
479
92
|
|
|
480
|
-
|
|
481
|
-
lints the repository, and asks ESLint for the effective config of every linted file. A rule
|
|
482
|
-
enabled for **zero** files is named and the script exits 1.
|
|
93
|
+
### One Module Knows Where Paperlint Is Installed
|
|
483
94
|
|
|
484
|
-
|
|
485
|
-
node scripts/rules-see-files.mjs # also part of npm run check
|
|
486
|
-
```
|
|
95
|
+
**Guidance only** — Lint rules, skills, hooks and scripts never compute where paperlint is installed: the package's own data comes from `src/package-dirs.ts`, the consumer's side from `skills/paper-pipeline/scripts/consumer.mjs`. `port/js-install-path` and `port/md-install-path` flag a literal. [Details](eslint-rules/install-path-literals.ts)
|
|
487
96
|
|
|
488
|
-
|
|
489
|
-
block whose glob is empty while a different block is busy, so "some glob matched something" is
|
|
490
|
-
not evidence about the rule you care about. Both halves are tested
|
|
491
|
-
(`scripts/rules-see-files.harness.mjs`), under-reporting and over-reporting each by an
|
|
492
|
-
assertion of its own.
|
|
493
|
-
|
|
494
|
-
## Cost
|
|
495
|
-
|
|
496
|
-
⛽ **This repository is PUBLIC, so its Actions minutes are FREE.** Verified against the API on
|
|
497
|
-
2026-09-17: `"private": false`, `"visibility": "public"`, and three active workflows — `ci`,
|
|
498
|
-
`dependabot auto-merge`, and Dependabot's own updates runner.
|
|
499
|
-
|
|
500
|
-
🔴 **This paragraph said the exact opposite until now, and the correction is the lesson, not the
|
|
501
|
-
fact.** It read «This is a **private** repository … Until then there is no CI here, and that is
|
|
502
|
-
deliberate» — both halves false, and false in the file an agent loads FIRST. The flip to public
|
|
503
|
-
happened on 2026-09-12 and _was_ recorded, at `.github/workflows/ci.yml:8-9`, which is a file
|
|
504
|
-
nobody opens before deciding whether there is any CI to check. Reported as issue #6.
|
|
505
|
-
|
|
506
|
-
⚠️ So the rule this leaves behind is about WHERE a correction lands: a measurement written into
|
|
507
|
-
the artifact it describes is not written down for the reader who needs it. **Status that changes
|
|
508
|
-
what a session DOES belongs in this file**; the workflow header can carry the detail.
|
|
509
|
-
|
|
510
|
-
What stays true, because the reasoning outlives the flip: minutes on a **private** repo come out
|
|
511
|
-
of the account-wide 3000/month shared with every other private repo, and the budget is decided
|
|
512
|
-
**before** the first workflow file, not after the first bill. **If this repository is ever made
|
|
513
|
-
private again, this section and `.github/dependabot.yml` are revisited together** — the bot is
|
|
514
|
-
justified two hundred lines above precisely by these minutes being free.
|
|
515
|
-
|
|
516
|
-
## Writing checks — what keeps them from pushing papers toward a blog post
|
|
517
|
-
|
|
518
|
-
The rules and skills that judge a paper's prose follow five principles. A new rule, or a new
|
|
519
|
-
criterion in a skill, is held to them.
|
|
520
|
-
|
|
521
|
-
1. **Every writing check is two-sided.** A check with only a ceiling — too long, too many markers,
|
|
522
|
-
too many of X — pushes every paper toward clipped, unsignposted prose, and the one equilibrium of
|
|
523
|
-
a set of ceilings is a blog post. A new check has a floor as well, or measures the distance to a
|
|
524
|
-
reference (accepted papers of the target venue); never only "less is better".
|
|
525
|
-
2. **Rules count, skills judge.** What is countable on the parse tree — a density, a number with no
|
|
526
|
-
owner (`tex/claim-provenance`), a missing section — is a lint rule. What needs reading — register
|
|
527
|
-
relative to a venue, genre, whether a claim changed strength — is a skill criterion. A skill does
|
|
528
|
-
not grep prose for what a rule can count; a rule is not asked to judge register.
|
|
529
|
-
3. **The model reader is the venue's reviewer, not only a smart outsider.** A check that simulates a
|
|
530
|
-
reader includes a skimming reviewer who reads the abstract, the introduction and the conclusion,
|
|
531
|
-
and judges whether it reads as a paper at that venue and whose result each sentence states.
|
|
532
|
-
4. **Thresholds are calibrated, not borrowed.** A threshold is set with the same counter on real
|
|
533
|
-
accepted papers, never taken from a published figure measured by a different counter. Those
|
|
534
|
-
papers are [`fixtures/accepted-papers/`](fixtures/accepted-papers/README.md), and every rule's
|
|
535
|
-
count on them is recorded there.
|
|
536
|
-
5. **A rewrite is accepted on what reviewers see.** That is the combined count of every surface form
|
|
537
|
-
of a tic (a narrow rule is gamed by rewording), plus a claims-preservation check that no claim was
|
|
538
|
-
softened or strengthened — not the one pattern the triggering rule matched.
|
|
539
|
-
|
|
540
|
-
## Testing
|
|
541
|
-
|
|
542
|
-
| you are testing | kind |
|
|
543
|
-
| -------------------------------------------------------------------------- | ----------------------------------------------------------- |
|
|
544
|
-
| a pure function | unit — vitest, `<module>.test.ts` beside it |
|
|
545
|
-
| a use case or adapter that reaches disk, a process, the network, the clock | integration — vitest, fakes through the ports |
|
|
546
|
-
| a hook's decision, a skill's contract | harness — vigiles, `<surface>.harness.ts` |
|
|
547
|
-
| the installed package, a real TeX build | e2e — `test/e2e/<area>/*.e2e.ts`, `npm run test:e2e:<area>` |
|
|
548
|
-
|
|
549
|
-
Red first · assert the whole value · test what the code does, never what its source says · no test
|
|
550
|
-
touches the real network · 100% coverage, no `c8 ignore`. **Before every push: `npm run check`,
|
|
551
|
-
exit code read without a pipe.** Everything else — where files live, how fakes are injected, the
|
|
552
|
-
`--min=1` trap: [`docs/testing.md`](docs/testing.md).
|
|
553
|
-
|
|
554
|
-
**A new rule** needs unit tests beside it and a reporting and a silent case in
|
|
555
|
-
`src/rule-cases.test.ts`, which runs every registered rule through real ESLint and fails on one
|
|
556
|
-
without cases. An e2e fixture only when [`docs/e2e.md`](docs/e2e.md)'s triggers say so.
|
|
557
|
-
|
|
558
|
-
**New code is functional.** In `src/`, `eslint-rules/` and `lib/` (tests aside) there is no
|
|
559
|
-
`let`, no mutation, no loop, and types are read-only (`eslint-plugin-functional`); a `switch` over a
|
|
560
|
-
union names every member. Collections go through [Remeda](https://remedajs.com/) — `lodash`,
|
|
561
|
-
`lodash-es` and `ramda` are refused by `npm run lint`. Old code is listed in
|
|
562
|
-
`eslint-suppressions.json`, ESLint's own bulk suppressions, which may only shrink: fixing a listed
|
|
563
|
-
site without `npx eslint . --prune-suppressions` fails lint, and #134 burns the list down.
|
|
97
|
+
### Prior Art Before A Command Or A Channel
|
|
564
98
|
|
|
565
|
-
|
|
99
|
+
**Guidance only** — Before adding a CLI command or changing a delivery channel (skill links, hook wiring, the composite action, the npm package), read how comparable tools did it. "We need another command for X" has, in every tool examined, been the CONFIG not declaring something. [Details](docs/prior-art/README.md)
|
|
566
100
|
|
|
567
|
-
|
|
568
|
-
a commit message that no command produced is the thing this repo exists to make impossible.
|
|
101
|
+
### New Code Is Functional
|
|
569
102
|
|
|
570
|
-
**
|
|
571
|
-
(
|
|
572
|
-
`feat:` → minor, `fix:` or `perf:` → patch, `!` or a `BREAKING CHANGE:` footer → major,
|
|
573
|
-
`docs:` `chore:` `ci:` `test:` `refactor:` → no release. `pr-title.yml` rejects a title without
|
|
574
|
-
a conventional prefix, and for a one-commit PR checks the commit subject too, since GitHub
|
|
575
|
-
squashes to that. npm, the git tag and the GitHub Release always carry the same version. Never
|
|
576
|
-
`npm publish` by hand.
|
|
103
|
+
**Enforced by:** `eslint/functional/no-let`
|
|
104
|
+
**Why:** In `src/`, `eslint-rules/` and `lib/`, tests and harnesses aside: no `let`, no mutation, no loop, read-only types; collections through [Remeda](https://remedajs.com/). `functional/immutable-data`, `functional/no-loop-statements` and `functional/prefer-immutable-types` hold the rest, and `eslint-suppressions.json` (old code) may only shrink.
|