paperlint 2.1.0 → 3.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.
Files changed (191) hide show
  1. package/CLAUDE.md +6 -1
  2. package/README.md +233 -224
  3. package/action.yml +4 -6
  4. package/dist/adapters/references/index.d.ts +13 -0
  5. package/dist/adapters/references/index.d.ts.map +1 -0
  6. package/dist/adapters/references/index.js +55 -0
  7. package/dist/adapters/references/index.js.map +1 -0
  8. package/dist/adapters/references/reach.io.d.ts +3 -0
  9. package/dist/adapters/references/reach.io.d.ts.map +1 -0
  10. package/dist/adapters/references/reach.io.js +14 -0
  11. package/dist/adapters/references/reach.io.js.map +1 -0
  12. package/dist/build.d.ts +15 -0
  13. package/dist/build.d.ts.map +1 -1
  14. package/dist/build.js +38 -4
  15. package/dist/build.js.map +1 -1
  16. package/dist/cli.d.ts +28 -25
  17. package/dist/cli.d.ts.map +1 -1
  18. package/dist/cli.js +277 -265
  19. package/dist/cli.js.map +1 -1
  20. package/dist/doctor.d.ts.map +1 -1
  21. package/dist/doctor.js +40 -72
  22. package/dist/doctor.js.map +1 -1
  23. package/dist/facts-file.d.ts +2 -2
  24. package/dist/facts-file.js +2 -2
  25. package/dist/hooks-settings.d.ts +0 -21
  26. package/dist/hooks-settings.d.ts.map +1 -1
  27. package/dist/hooks-settings.js +17 -135
  28. package/dist/hooks-settings.js.map +1 -1
  29. package/dist/init.d.ts +39 -29
  30. package/dist/init.d.ts.map +1 -1
  31. package/dist/init.js +143 -172
  32. package/dist/init.js.map +1 -1
  33. package/dist/link-skills.d.ts +1 -1
  34. package/dist/link-skills.d.ts.map +1 -1
  35. package/dist/link-skills.js +11 -29
  36. package/dist/link-skills.js.map +1 -1
  37. package/dist/new-paper.d.ts.map +1 -1
  38. package/dist/new-paper.js +2 -13
  39. package/dist/new-paper.js.map +1 -1
  40. package/dist/paper-files.d.ts +63 -0
  41. package/dist/paper-files.d.ts.map +1 -0
  42. package/dist/paper-files.js +69 -0
  43. package/dist/paper-files.js.map +1 -0
  44. package/dist/paper-settings.d.ts +13 -41
  45. package/dist/paper-settings.d.ts.map +1 -1
  46. package/dist/paper-settings.js +71 -71
  47. package/dist/paper-settings.js.map +1 -1
  48. package/dist/ports/check-references.d.ts +31 -0
  49. package/dist/ports/check-references.d.ts.map +1 -0
  50. package/dist/ports/check-references.js +7 -0
  51. package/dist/ports/check-references.js.map +1 -0
  52. package/dist/presets.d.ts +1 -1
  53. package/dist/presets.d.ts.map +1 -1
  54. package/dist/presets.js +4 -6
  55. package/dist/presets.js.map +1 -1
  56. package/dist/reference-rules.d.ts +47 -0
  57. package/dist/reference-rules.d.ts.map +1 -0
  58. package/dist/reference-rules.js +156 -0
  59. package/dist/reference-rules.js.map +1 -0
  60. package/dist/references.d.ts +36 -0
  61. package/dist/references.d.ts.map +1 -0
  62. package/dist/references.js +91 -0
  63. package/dist/references.js.map +1 -0
  64. package/dist/rules-config.d.ts +5 -3
  65. package/dist/rules-config.d.ts.map +1 -1
  66. package/dist/rules-config.js +15 -6
  67. package/dist/rules-config.js.map +1 -1
  68. package/dist/structure.js +3 -3
  69. package/dist/structure.js.map +1 -1
  70. package/dist/tex-requirements.js +2 -2
  71. package/dist/tex-requirements.js.map +1 -1
  72. package/dist/types.d.ts +20 -18
  73. package/dist/types.d.ts.map +1 -1
  74. package/dist/venue-rules.d.ts.map +1 -1
  75. package/dist/venue-rules.js +11 -9
  76. package/dist/venue-rules.js.map +1 -1
  77. package/docs/configuration.md +119 -104
  78. package/docs/e2e.md +1 -1
  79. package/docs/install.md +25 -40
  80. package/docs/optional-rules.md +5 -4
  81. package/docs/rules.md +60 -23
  82. package/eslint-rules/bib-reachable-entry.mjs +75 -0
  83. package/eslint-rules/latex-language.mjs +29 -1
  84. package/eslint-rules/paper-stages.harness.mjs +1 -117
  85. package/eslint-rules/paper-stages.mjs +0 -120
  86. package/eslint-rules/paper-stages.mutations.mjs +0 -76
  87. package/eslint-rules/paper-typography.mjs +267 -192
  88. package/eslint-rules/paper-typography.test.mjs +297 -0
  89. package/eslint-rules/papers.harness.mjs +8 -12
  90. package/eslint-rules/papers.mjs +17 -31
  91. package/eslint-rules/pdf-last-page-balance.mjs +4 -7
  92. package/eslint-rules/review-frontmatter.mjs +118 -0
  93. package/eslint-rules/review-frontmatter.schema.json +36 -0
  94. package/eslint-rules/review-frontmatter.test.mjs +118 -0
  95. package/eslint-rules/sibling-frontmatter.mjs +19 -0
  96. package/eslint-rules/sibling-frontmatter.schema.json +14 -0
  97. package/eslint.config.mjs +0 -15
  98. package/fixtures/real-markdown-paper/baseline.json +6 -7
  99. package/hooks/hooks.harness.mjs +39 -98
  100. package/hooks/hooks.mutations.mjs +6 -6
  101. package/hooks/paper-edit-guard.hook.d.mts +2 -3
  102. package/hooks/paper-edit-guard.hook.mjs +31 -42
  103. package/hooks/paper-skills-nudge.hook.mjs +21 -19
  104. package/hooks/paper-status-gates.hook.mjs +21 -19
  105. package/hooks/paper-status-gates.sh +8 -13
  106. package/lib/paper-config.d.mts +15 -25
  107. package/lib/paper-config.harness.mjs +18 -35
  108. package/lib/paper-config.mjs +83 -105
  109. package/lib/paper-config.mutations.mjs +2 -2
  110. package/lib/skill-checks.mjs +1 -1
  111. package/lib/skill-trigger-cases.harness.mjs +9 -8
  112. package/lib/skill-trigger-cases.mjs +7 -15
  113. package/package.json +1 -1
  114. package/scripts/eslint-report-guard.mjs +1 -1
  115. package/scripts/harness-api.frozen.json +0 -3
  116. package/scripts/mutation-batteries.frozen.json +4 -13
  117. package/skills/analyze-sibling-paper/SKILL.md +16 -12
  118. package/skills/analyze-sibling-paper/SKILL.md.spec.ts +15 -11
  119. package/skills/cold-read-diff/SKILL.md +20 -1
  120. package/skills/cold-read-diff/SKILL.md.spec.ts +19 -0
  121. package/skills/grade-paper-writing/SKILL.md +20 -1
  122. package/skills/grade-paper-writing/SKILL.md.spec.ts +19 -0
  123. package/skills/paper-pipeline/scripts/consumer.d.mts +0 -1
  124. package/skills/paper-pipeline/scripts/consumer.harness.mjs +8 -12
  125. package/skills/paper-pipeline/scripts/consumer.mjs +19 -33
  126. package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +6 -4
  127. package/skills/paper-pipeline/scripts/run-mechanical.mjs +2 -11
  128. package/skills/plan-paper-timeline/SKILL.md +2 -2
  129. package/skills/plan-paper-timeline/SKILL.md.spec.ts +1 -1
  130. package/skills/plan-paper-timeline/plan-paper-timeline.effects.harness.mjs +1 -1
  131. package/skills/plan-paper-timeline/plan-paper-timeline.effects.mutations.mjs +2 -2
  132. package/skills/render-paper/check-render.sh +2 -3
  133. package/skills/render-paper/render-paper.harness.mjs +2 -2
  134. package/skills/verify-citations/SKILL.md +10 -1
  135. package/skills/verify-citations/SKILL.md.spec.ts +9 -0
  136. package/skills/verify-citations/scripts/bib-authors.mjs +48 -26
  137. package/src/adapters/references/index.ts +102 -0
  138. package/src/adapters/references/reach.io.ts +12 -0
  139. package/src/build.harness.mjs +8 -4
  140. package/src/build.ts +62 -6
  141. package/src/cli.harness.mjs +147 -292
  142. package/src/cli.mutations.mjs +35 -44
  143. package/src/cli.ts +344 -304
  144. package/src/doctor.harness.mjs +41 -84
  145. package/src/doctor.mutations.mjs +9 -9
  146. package/src/doctor.ts +44 -89
  147. package/src/facts-file.test.ts +0 -7
  148. package/src/facts-file.ts +2 -2
  149. package/src/hooks-settings.harness.mjs +7 -92
  150. package/src/hooks-settings.mutations.mjs +2 -17
  151. package/src/hooks-settings.ts +18 -162
  152. package/src/init.test.ts +206 -0
  153. package/src/init.ts +186 -230
  154. package/src/link-skills.harness.mjs +1 -38
  155. package/src/link-skills.mutations.mjs +9 -5
  156. package/src/link-skills.ts +10 -34
  157. package/src/lint-fix.test.ts +120 -0
  158. package/src/new-paper.test.ts +1 -13
  159. package/src/new-paper.ts +2 -17
  160. package/src/paper-files.test.ts +290 -0
  161. package/src/paper-files.ts +144 -0
  162. package/src/paper-settings-commands.test.ts +84 -121
  163. package/src/paper-settings.test.ts +93 -64
  164. package/src/paper-settings.ts +99 -97
  165. package/src/ports/check-references.ts +28 -0
  166. package/src/presets.ts +5 -15
  167. package/src/reference-rules.ts +229 -0
  168. package/src/references.test.ts +209 -0
  169. package/src/references.ts +138 -0
  170. package/src/rules-config.harness.mjs +24 -8
  171. package/src/rules-config.ts +19 -7
  172. package/src/structure.harness.mjs +3 -1
  173. package/src/structure.ts +3 -3
  174. package/src/tex-requirements.ts +2 -2
  175. package/src/types.ts +30 -18
  176. package/src/venue-rules.test.ts +20 -11
  177. package/src/venue-rules.ts +11 -13
  178. package/eslint-rules/doc-fields.harness.mjs +0 -336
  179. package/eslint-rules/doc-fields.mjs +0 -186
  180. package/eslint-rules/doc-fields.mutations.mjs +0 -96
  181. package/eslint-rules/paper-typography.harness.mjs +0 -291
  182. package/eslint-rules/paper-typography.mutations.mjs +0 -131
  183. package/eslint-rules/review-findings-cause.harness.mjs +0 -228
  184. package/eslint-rules/review-findings-cause.mjs +0 -135
  185. package/eslint-rules/review-findings-cause.mutations.mjs +0 -72
  186. package/fixtures/paper-stages/authors-ran/PIPELINE-STATUS.md +0 -16
  187. package/fixtures/paper-stages/marker-in-prose/PIPELINE-STATUS.md +0 -17
  188. package/fixtures/review-findings-cause/clean.md +0 -17
  189. package/fixtures/review-findings-cause/defect.md +0 -14
  190. package/fixtures/review-findings-cause/old-debt.md +0 -14
  191. package/fixtures/review-findings-cause/quiet-in-fence.md +0 -16
@@ -1,85 +1,74 @@
1
1
  # Configuration reference
2
2
 
3
- One key in your `package.json`, written by `paperlint init`. It holds the facts only your repository can
4
- supply — nothing in it is guessable by a package that has never seen your corpus.
3
+ paperlint reads one file name at two levels, `paperlint.json`, with one schema. Both files are
4
+ optional: a project with its papers in `papers/` and no special wishes needs neither.
5
5
 
6
- The README carries the minimal version of this. Everything below is the full surface, moved out on
7
- 2026-09-19.
6
+ ```
7
+ paperlint.json the PROJECT (optional): where the papers are, the project's rules,
8
+ defaults for every paper
9
+ papers/
10
+ my-paper/
11
+ paper.tex
12
+ PIPELINE-STATUS.md
13
+ paperlint.json THIS PAPER: its venue preset, its kind, its own rules — `paperlint new` writes it
14
+ venues/usenix-sec.jsonc (optional, your own) a VENUE PRESET: format, page limits, TeX packages, rules
15
+ ```
16
+
17
+ The shipped presets (`paperlint:<name>`) live in the package, under
18
+ `skills/submit-paper/references/venues/`: `acm-sigconf`, `agenticdev`, `aisec`, `realm`.
19
+
20
+ ## The root `paperlint.json`
21
+
22
+ It sits beside your `package.json`. Without it every setting has its default.
8
23
 
9
24
  ```json
10
25
  {
11
- "paperlint": {
12
- "papersDir": "papers",
13
- "authorListCommand": "node scripts/bib-authors.mjs",
14
- "typographyDebt": { "papers/my-paper": { "sectionSign": 12 } },
15
- "docFields": { "read": { "values": ["full", "abstract", "none"] } },
16
- "reviewSince": "2026-08-23",
17
- "minFindings": 3,
18
- "causeMarker": "Cause:",
19
- "rules": [
20
- {
21
- "files": ["papers/old-draft/**"],
22
- "rules": { "paper/typography": "off" }
23
- }
24
- ]
25
- }
26
+ "papersDir": "docs/papers",
27
+ "extends": "paperlint:acm-sigconf",
28
+ "rules": [
29
+ {
30
+ "files": ["docs/papers/old-draft/**"],
31
+ "rules": { "paper/section-word": "off" }
32
+ }
33
+ ]
26
34
  }
27
35
  ```
28
36
 
29
- | key | required | what it is |
30
- | ------------------- | -------- | ----------------------------------------------------------------------------------------- |
31
- | `papersDir` | **yes** | the directory your papers live in, relative to the file holding it. One string or a list. |
32
- | `structure` | no | which files every paper directory must contain — see below. `false` turns it off. |
33
- | `authorListCommand` | no | the command `paper/author-list` tells you to run when the check is missing |
34
- | `typographyDebt` | no | per-paper allowance of existing typography findings, so the count can only go down |
35
- | `docFields` | no | required front-matter fields in review files, and the values each may hold |
36
- | `reviewSince` | no | only review files created on or after this date are checked |
37
- | `minFindings` | no | a review with fewer findings than this is not required to name causes |
38
- | `causeMarker` | no | the phrase a review uses to introduce a cause (default `Cause:`), in any language |
39
- | `rules` | no | extra ESLint config blocks: turn optional rules on, change a rule's severity — see below |
40
-
41
- The skill scripts read a few more keys of the same object — `ledger`, `scripts`, `timezone`,
42
- `contactEmail`, `citeChecks`, `triggerCases` — documented with the skills that use them.
43
-
44
- **Any other key is an error**, named in the message: `package.json → "paperlint":
45
- unknown key "typographyDept"`. A misspelt key would otherwise read as "not set", and the setting
46
- you meant would silently do nothing. The list of known keys is `SETTINGS_KEYS` in
47
- `lib/paper-config.mjs`.
48
-
49
- `papersDir` is required because the scope is the one thing that must not default: a default of `"."`
50
- turns every run into a green report over the whole checkout. `paperlint init` fills it by measuring —
51
- and when nothing on disk looks like a papers directory, it writes the documented default and says
52
- in the same breath that it is a guess.
53
-
54
- Until 2026-09-24 this field was called `papers`. The old name is not read as a fallback: `paperlint lint`,
55
- `paperlint init`, `paperlint doctor`, the ESLint helper and the edit guard all stop with
56
- `"papers" was renamed to "papersDir" in package.json → "paperlint"`. The two advisory
57
- hooks stay silent instead. The name is defined once, as `PAPERS_DIR_FIELD` in
58
- `lib/paper-config.mjs`.
59
-
60
- ## Three levels of settings
61
-
62
- Each level is named after the tool, and each says something the others cannot:
37
+ | key | default | what it is |
38
+ | ----------- | ---------- | ------------------------------------------------------------------------------------------ |
39
+ | `papersDir` | `"papers"` | the directory your papers live in, relative to this file. One string or a list. Root only. |
40
+ | `structure` | see below | which files every paper directory must contain. `false` turns it off. Root only. |
41
+ | `rules` | none | rule overrides — see [below](#the-rules-key-turning-rules-on-and-off) |
42
+ | `extends` | none | the venue preset for every paper that names none |
43
+ | `kind` | none | the kind of paper for every paper that names none |
44
+ | `pdf` | none | where the built PDF is, relative to each paper, when it is not `paper.pdf` |
45
+ | `$comment` | — | a note for humans (JSON Schema's comment keyword); ignored |
46
+
47
+ The skill scripts read a few more root keys — `ledger`, `scripts`, `timezone`, `contactEmail`,
48
+ `citeChecks`, `triggerCases` — documented with the skills that use them.
49
+
50
+ **`paperlint lint` finds the file** by walking up from the current directory to the nearest
51
+ `paperlint.json` that is not a paper's own (a paper's sits beside its `paper.tex`); if there is
52
+ none, the project root is the nearest directory with a `package.json`. It prints which file it
53
+ found. `--config <file>` names another file of the same shape.
54
+
55
+ **No papers where `papersDir` points is an error**, not a clean run:
63
56
 
64
57
  ```
65
- package.json "paperlint": { … } the PROJECT: where the papers are, what every paper gets
66
- papers/
67
- my-paper/
68
- paper.tex
69
- PIPELINE-STATUS.md
70
- paperlint.json { "extends": … } THIS PAPER: its venue preset, its kind, its own rules — `paperlint new` writes it
71
- venues/usenix-sec.jsonc (optional, your own) a VENUE PRESET: format, page limits, TeX packages, rules
72
- node_modules/paperlint/skills/submit-paper/references/venues/
73
- acm-sigconf.jsonc agenticdev.jsonc aisec.jsonc realm.jsonc the shipped presets (paperlint:<name>)
58
+ no papers in papers/ — create one with `npx paperlint new <name>`, or set "papersDir" in paperlint.json if your papers live elsewhere
74
59
  ```
75
60
 
76
- `paperlint new` writes `<paper>/paperlint.json` from the template (`templates/paper/paperlint.json`,
77
- or your `<papers>/.template/paperlint.json` if you keep one), with `"extends": null` — no venue chosen
78
- yet — and a `$comment` saying what goes there. Until `extends` names a preset, `paperlint lint` gives
79
- that paper one warning, `pdf/measured`: "this paper names no venue preset yet … set "extends" in
80
- papers/my-paper/paperlint.json". A paper folder with no `paperlint.json` at all — one created before
81
- 2.1.0 — gets no venue checks and no warning; `npx paperlint new <its name>` adds the file and
82
- changes nothing else.
61
+ `paperlint init` writes the root file only when something differs from the defaults: it measures
62
+ where your papers are, and writes `{ "papersDir": … }` only when that is not `papers`. A
63
+ `papersDir` already declared is kept, and nothing is measured or asked.
64
+
65
+ **Under `papersDir`, only the files paperlint's own rules are written for are linted:**
66
+ `PIPELINE-STATUS.md`, `paper.md`, `draft.md`, `paper.tex`, `reviews/*.md` and `siblings/*.md`.
67
+ Everything else — a paper's `repro/` scripts, vendored JavaScript, data files — is never handed to
68
+ ESLint, so it cannot fail the run. A file you name on the command line that is not one of these is
69
+ refused by name. `node_modules/`, `.git/` and `<papers>/.template/` are skipped.
70
+
71
+ ## A paper's `paperlint.json`
83
72
 
84
73
  ```json
85
74
  {
@@ -89,51 +78,70 @@ changes nothing else.
89
78
  }
90
79
  ```
91
80
 
81
+ The same keys as the root file, minus the project-only ones (`papersDir`, `structure` and the
82
+ skills' keys), which are refused here by name. **It merges over the root file:** its `extends`,
83
+ `kind` and `pdf` win; one it does not set comes from the root.
84
+
92
85
  | key | what it is |
93
86
  | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
94
87
  | `extends` | the venue preset the built PDF is judged against: `paperlint:<name>` (shipped) or `./path` / `../path` (your own, relative to this file) — [`rules.md`](rules.md#checks-against-the-venue) |
95
88
  | `kind` | the kind of paper (`short`, `research`, …) whose page limit applies |
96
89
  | `pdf` | where the built PDF is, relative to the paper, when it is not `paper.pdf` |
97
- | `rules` | rule id → severity, for this paper alone — the same entries as a `rules` block below |
98
- | `$comment` | a note for humans (JSON Schema's comment keyword); ignored |
90
+ | `rules` | rule overrides for this paper alone — `{ "<rule>": "<severity>" }`, or blocks with globs relative to the paper |
91
+ | `$comment` | a note for humans; ignored |
92
+
93
+ `paperlint new` writes this file from the template (`templates/paper/paperlint.json`, or your
94
+ `<papers>/.template/paperlint.json` if you keep one), with `"extends": null` — no venue chosen yet —
95
+ and a `$comment` saying what goes there. Until `extends` names a preset (here or in the root file),
96
+ `paperlint lint` gives that paper one warning, `pdf/measured`: "this paper names no venue preset
97
+ yet … set "extends" in papers/my-paper/paperlint.json".
99
98
 
100
- Any other key is an error naming the file and the key, as in `package.json`.
99
+ **Any other key is an error**, in either file, named in the message: `paperlint.json: unknown key
100
+ "papersdir"`. A misspelt key would otherwise read as "not set", and the setting you meant would
101
+ silently do nothing. The key list is `SETTINGS_KEYS` in `lib/paper-config.mjs`.
101
102
 
102
103
  **Where a paper's rules come from, in order — a later one wins, rule by rule:** paperlint's own
103
104
  configuration → the preset chain's `rules`, from the root preset to the one the paper extends →
104
- the paper's own `rules` → the project's `rules` blocks in `package.json`. So a venue can turn a
105
- rule on for its papers, a paper can turn it off for itself, and the project can still override
106
- both. Only rules paperlint ships may be named, at every level.
105
+ the root `paperlint.json`'s `rules` → the paper's own `rules`. So a venue can turn a rule on, the
106
+ project can change that for every paper, and one paper can still change it for itself. Only rules
107
+ paperlint ships may be named, at every level.
107
108
 
108
- **`paperlint.json` replaces `venue.json` (2.1.0); `npx paperlint init` moves it.** The old name is not
109
- read: a paper with only a `venue.json` gets a `pdf/profile` error and `paperlint doctor` names the
110
- file, both pointing at `init`. `init` writes the same settings as `paperlint.json` —
111
- `"venue": "aisec"` becomes `"extends": "paperlint:aisec"`, and the `"_"` some files used as a
112
- comment becomes `"$comment"` — then deletes `venue.json`. It removes a `venue.json` whose
113
- `paperlint.json` already says the same, and refuses, changing nothing, when both exist and differ.
109
+ ## Records in frontmatter, checked by shipped JSON Schemas
114
110
 
115
- ## Why the key lives in `package.json`
111
+ Two kinds of file under a paper keep a record in their YAML frontmatter, and paperlint validates
112
+ each against a JSON Schema it ships (`eslint-rules/*.schema.json`):
116
113
 
117
- Because of a count: the `package.json` key has **five** readers — the three editor hooks, the
118
- ESLint helper, the skill scripts — and a separate config file had **one**, the CLI. A hook cannot
119
- import code and cannot walk up a tree looking for a config; it can read a path it is able to name,
120
- and the one path it can always name is the project's `package.json`.
114
+ - **a review** under `reviews/` (`review/frontmatter`, error) — its findings, each with a status and,
115
+ when open, the pipeline cause that let it through:
121
116
 
122
- `paperlint lint` looks for it in the current directory and then upwards, the way eslint and tsc find
123
- theirs, and prints which file it found. `--config <file>` overrides the search; the file has the
124
- same shape, with the settings under the `paperlint` key.
117
+ ```yaml
118
+ ---
119
+ findings:
120
+ - id: 1
121
+ status: open # open | fixed | wontfix
122
+ cause: missing-skill # skill-defect | missing-skill | hook | rule — required when open
123
+ ---
124
+ ```
125
+
126
+ A review with no `findings` key is not checked for findings.
127
+
128
+ - **a sibling card** under `siblings/` (`sibling/frontmatter`, warn; `siblings/README.md` is the index,
129
+ not a card) — how much of the competing paper was actually read: `read: full | abstract | none`. A
130
+ card without it, or without frontmatter, is a finding.
125
131
 
126
132
  ## The `rules` key: turning rules on and off
127
133
 
128
- `rules` is a list of blocks in ESLint's own
129
- [flat-config shape](https://eslint.org/docs/latest/use/configure/configuration-files), limited to
130
- the three keys that make sense in JSON — `files`, `ignores` and `rules`. paperlint appends the blocks
131
- **after** its own configuration, so, as in ESLint, a later block wins: a block can turn on a rule
132
- that is off by default, or change the severity of one that is on.
134
+ `rules` takes two shapes, in either file:
135
+
136
+ - **`{ "<rule>": "<severity>" }`** — for every paper file in the file's scope: every paper in the
137
+ root file, that one paper in a paper's file. The simple form, with no glob to get wrong.
138
+ - **a list of blocks** in ESLint's own
139
+ [flat-config shape](https://eslint.org/docs/latest/use/configure/configuration-files), limited to
140
+ the three keys that make sense in JSON — `files`, `ignores` and `rules` — for a rule across some
141
+ papers but not others.
133
142
 
134
- For ONE paper, the paper's own `paperlint.json` is simpler — no glob to get wrong
135
- ([above](#three-levels-of-settings)). Use a block here for a rule across several papers, or to
136
- override what a paper says:
143
+ paperlint applies them **after** its own configuration, so, as in ESLint, a later one wins: they
144
+ can turn on a rule that is off by default, or change the severity of one that is on.
137
145
 
138
146
  ```json
139
147
  "rules": [
@@ -143,20 +151,25 @@ override what a paper says:
143
151
  },
144
152
  {
145
153
  "files": ["papers/old-draft/**"],
146
- "rules": { "paper/typography": "off" }
154
+ "rules": { "paper/section-word": "off" }
147
155
  }
148
156
  ]
149
157
  ```
150
158
 
151
- - **`files` and `ignores` are globs relative to the file that holds the settings** — the
152
- directory of your `package.json` — exactly as ESLint resolves them relative to its config file,
159
+ - **`files` and `ignores` are globs relative to the file that holds them** — the project root for
160
+ the root file, the paper for a paper's — exactly as ESLint resolves them relative to its config file,
153
161
  whatever directory you run `paperlint lint` from. A block without `files` applies to every linted file.
154
162
  A pattern ending in `/**` is the usual way to name one paper.
163
+ - **Each rule reaches only the files it is written for.** A block with `"files": ["papers/**"]` and
164
+ `"rules": { "paper/source": "warn" }` turns `paper/source` on for every `PIPELINE-STATUS.md` under
165
+ `papers/`; `paper/section-word` in the same block lands on every `paper.md`, `draft.md` and
166
+ `paper.tex`. You do not need to know which file a rule reads: your `files` narrow where it runs,
167
+ never widen it. The [rule tables](rules.md) name each rule's file.
155
168
  - **A rule entry** is a severity (`"off"`, `"warn"`, `"error"`, or `0`/`1`/`2`), or a list whose
156
169
  first element is a severity and the rest are the rule's options.
157
170
  - **Only rules paperlint ships can be named** — the ones in [`docs/rules.md`](rules.md) and
158
171
  [`docs/optional-rules.md`](optional-rules.md). A rule id paperlint does not ship, a bad severity, a
159
- `rules` that is not a list, or a block key other than `files`, `ignores` and `rules` stops the run
172
+ `rules` of another shape, or a block key other than `files`, `ignores` and `rules` stops the run
160
173
  with a message naming the exact key, before anything is linted.
161
174
  - **An optional rule you turned on must reach a paper.** If no linted `paper.tex` gets the rule —
162
175
  usually a `files` glob with a typo — `paperlint lint` fails and says so: a rule that never runs
@@ -271,7 +284,7 @@ having run on it, because a missing build read as nothing to do.
271
284
 
272
285
  ⚠️ **A `build.sh` or `repro/build-submission.sh` in the paper directory is IGNORED.** Earlier
273
286
  versions ran it; `paperlint build` now says one line — `build.sh is ignored — paperlint builds the paper
274
- itself` — and builds the paper itself. The `buildScripts` key is ignored the same way.
287
+ itself` — and builds the paper itself.
275
288
  Why: [#59](https://github.com/zernie/paperlint/issues/59).
276
289
 
277
290
  ## Using the rules from an existing ESLint config
@@ -285,7 +298,9 @@ the exact config the CLI uses, so the shortest path is:
285
298
  // eslint.config.mjs
286
299
  import { buildConfig } from "paperlint/bin/paperlint.mjs";
287
300
  import { texLanguage } from "paperlint/eslint-rules/latex-language.mjs";
288
- export default buildConfig({ minFindings: 3 }, texLanguage);
301
+ export default buildConfig({}, texLanguage);
289
302
  ```
290
303
 
291
- Then point the CI action's `config` input at that file.
304
+ That config is the whole config for your papers: it starts with a global ignore of every file its
305
+ rules are not written for, so `eslint .` with it lints only the paper files. Do not spread it into a
306
+ config that also lints your JavaScript — that code would be ignored.
package/docs/e2e.md CHANGED
@@ -35,7 +35,7 @@ temporary tree, **under npm and under pnpm separately**, and drives the installe
35
35
 
36
36
  - the install itself finishes
37
37
  - `paperlint --help` answers with zero
38
- - `paperlint init` declares the papers directory in `package.json`
38
+ - `paperlint init` takes the default papers directory and writes no `paperlint.json` for it
39
39
  - `paperlint init` finishes with zero — its doctor found no discrepancy
40
40
  - `paperlint init` wires the hooks into `.claude/settings.json` — the same commands `hooks.json`
41
41
  publishes, once each — and says they need `npm install` in a fresh clone; a second `init`
package/docs/install.md CHANGED
@@ -14,21 +14,6 @@ npx paperlint init
14
14
  Two commands in one terminal, in this order: `init` links the skills and hooks to the copy in
15
15
  your project's `node_modules`, so it needs the install first.
16
16
 
17
- **Upgrading from `research-paper-pipeline`** (the package's name before 2.0.0):
18
- `npm rm research-paper-pipeline && npm i -D paperlint`, then `npx paperlint init`. It moves the
19
- `"research-paper-pipeline"` key in `package.json` to `"paperlint"`, and replaces the hook commands
20
- and skill links that pointed into the old package. Until then the old key is still read, with a
21
- warning.
22
-
23
- **Upgrading from 2.0.0**: 2.0.0 wrote hook commands that run `node_modules/paperlint/bin/rpp.mjs`,
24
- a file later versions do not ship, so those hooks stop running. `npx paperlint doctor` names them;
25
- `npx paperlint init` replaces them and keeps your own commands in the same matcher. The TeX Live
26
- and banal caches moved from `~/.cache/rpp/` to `~/.cache/paperlint/`, so `npx paperlint toolchain`
27
- downloads TeX Live once more (delete the old directory afterwards), and the environment variables
28
- are now `PAPERLINT_TEXLIVE_DIR`, `PAPERLINT_BANAL_DIR` and `PAPERLINT_CTAN_MIRROR` — the `RPP_*`
29
- names are no longer read. A leftover `rpp.json` is ignored; its settings belong under the
30
- `"paperlint"` key of `package.json`.
31
-
32
17
  The npm package carries everything: the `paperlint` command, the ESLint rules, the Claude Code skills
33
18
  and hooks, and the scripts the skills run. External programs are separate:
34
19
 
@@ -43,15 +28,18 @@ and hooks, and the scripts the skills run. External programs are separate:
43
28
 
44
29
  In order:
45
30
 
46
- 1. **Finds the papers directory**, or asks for it. The project must have a `package.json`; without
47
- one `init` stops and tells you to run `npm init -y` first.
48
- 2. **Declares it once**, as the `paperlint` key in your `package.json`.
31
+ 1. **Finds the papers directory**, or asks for it when several directories look like one.
32
+ 2. **Declares it**, in a root `paperlint.json`, only when it is not the default `papers`. A project
33
+ on the defaults gets no config file at all.
49
34
  3. **Links each shipped skill** into `.claude/skills/<name>`, where Claude Code looks for skills.
50
35
  4. **Writes the hook commands** into `.claude/settings.json`, beside your own entries.
51
36
  5. **Offers a GitHub Actions workflow**, pinned to the release tag of the version you installed.
52
37
  6. **Offers a first paper** (`paperlint new`) if the papers directory has none.
53
- 7. **Reports missing external programs** and the command that installs each. It installs nothing.
54
- 8. **Runs `paperlint doctor`** and exits with its verdict.
38
+ 7. **Offers TeX Live** for `paperlint build` (`paperlint toolchain`, ~270 MB, ~3 min, once), with
39
+ the size in the question and NO as the default. Without a terminal it asks nothing and lists the
40
+ command as the next step. `paperlint lint` needs no TeX.
41
+ 8. **Reports missing external programs** and the command that installs each. It installs nothing.
42
+ 9. **Runs `paperlint doctor`** and exits with its verdict.
55
43
 
56
44
  It asks only what it cannot guess or what costs something. A human at a terminal is asked; an
57
45
  agent, CI or `--yes` takes the defaults, and `init` prints which default it took. An unanswered
@@ -61,23 +49,23 @@ question (Ctrl+D) also takes the default.
61
49
 
62
50
  | what | where | when |
63
51
  | ------------------------------------------------------------------ | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
64
- | a `paperlint` key naming your papers directory | your `package.json` | always |
52
+ | `{ "papersDir": … }` naming your papers directory | `paperlint.json` | only when it is not the default `papers` |
65
53
  | the three hook commands, merged in beside your own entries | `.claude/settings.json` | by default. A human at a terminal is asked [Y/n]; an agent, CI or `--yes` gets YES; `--no-hooks` skips. Hand-wired under another spelling: nothing written, so nothing runs twice |
66
54
  | a GitHub Actions workflow, pinned to `@v<installed version>` | `.github/workflows/` | only if you say yes; it asks once, and only when a human is at a terminal (stdin and stdout, no `CI`, no `--yes`) |
67
55
  | a first paper, via `paperlint new` | `<papers>/<name>/` | only when the papers directory holds none: asked of a human at a terminal, otherwise only with `--paper <name>` |
68
56
  | one relative symlink per shipped skill, into the installed package | `.claude/skills/<name>` | always — except where that name is already taken (a directory, a file, a link elsewhere): that entry is left as it is and named in the report |
69
57
 
70
- It installs no software and touches nothing else. Commit `.claude/settings.json` so every clone
58
+ It installs no software you did not say yes to, and touches nothing else. Commit `.claude/settings.json` so every clone
71
59
  gets the hooks; the hook commands run files inside `node_modules`, so a fresh clone needs
72
60
  `npm install` first.
73
61
 
74
62
  ## Why it is shaped this way
75
63
 
76
- **One declaration, in `package.json`.** A hook cannot import code or walk up a tree looking for a
77
- config; it can only read a path it can spell, and the one it can always spell is
78
- `$CLAUDE_PROJECT_DIR/package.json`. That key is read by the three hooks, `eslint-rules/papers.mjs`,
79
- `lib/skill-trigger-cases.mjs` and `skills/paper-pipeline/scripts/consumer.mjs`; a separate
80
- config file would be read only by the CLI, so there is none.
64
+ **One file, at the project root.** A hook cannot import code or walk up a tree looking for a
65
+ config; it can only read a path it can spell, and `$CLAUDE_PROJECT_DIR/paperlint.json` is one. The
66
+ same file is read by the CLI, the three hooks, `eslint-rules/papers.mjs`,
67
+ `lib/skill-trigger-cases.mjs` and `skills/paper-pipeline/scripts/consumer.mjs`. It is optional:
68
+ absent, every reader takes the same defaults.
81
69
 
82
70
  **Nothing runs at install time.** No postinstall script and no automatic TeX download. npm's rule
83
71
  is that _"the only valid use of install or preinstall scripts is for compilation"_; husky removed
@@ -110,9 +98,7 @@ resolve.
110
98
  nothing while guarding and says nothing while watching a directory that does not exist, so
111
99
  "installed" and "protecting you" look the same from outside. `doctor` prints the papers directory
112
100
  `paperlint lint` resolves and the one the hooks resolve, whether they match, whether the hooks are wired
113
- (once, twice, or not at all), and which external programs are missing. It also warns when the
114
- project still enables the old `research-paper-pipeline` plugin, because plugin plus settings would
115
- run every hook twice. A plugin installed at user scope is outside what it can read, and it says so.
101
+ (once, twice, or not at all), and which external programs are missing.
116
102
 
117
103
  ### How comparable tools install
118
104
 
@@ -166,13 +152,12 @@ Neither `paperlint lint` nor any of the three hooks loads `@ast-grep` or `typesc
166
152
 
167
153
  ## Troubleshooting
168
154
 
169
- | symptom | cause and fix |
170
- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
171
- | `init`: skills "nothing linked", or hooks that fail to start | the package is not installed in this project (`npx` ran a temporary copy). Run `npm i -D paperlint`, then `npx paperlint init` again |
172
- | `init` stops: no `package.json` | run `npm init -y`, then `npx paperlint init` again |
173
- | `paperlint lint`: `nothing to lint` | no declaration was found. Run `npx paperlint init`, or pass the directory: `paperlint lint papers` |
174
- | hooks fail with `Cannot find module` in a fresh clone | the hook commands run files in `node_modules`: run `npm install` |
175
- | `paperlint: not built` | installed from git with `--ignore-scripts`, or a clone before building: run `npm run build` in the package |
176
- | every hook runs twice | the project still enables the old plugin. `init` and `doctor` print the uninstall command; also remove it from `enabledPlugins` |
177
- | a skill does not show up in Claude Code | its name was already taken in `.claude/skills/`. `init` names it and leaves it alone; rename or remove yours and run `npx paperlint init` again |
178
- | anything else | `npx paperlint doctor` — it checks the setup and exits non-zero on anything miswired |
155
+ | symptom | cause and fix |
156
+ | ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
157
+ | `init`: skills "nothing linked", or hooks that fail to start | the package is not installed in this project (`npx` ran a temporary copy). Run `npm i -D paperlint`, then `npx paperlint init` again |
158
+ | `paperlint lint`: `no papers in papers/` | there is no paper where `papersDir` points. Create one with `npx paperlint new <name>`, or set `"papersDir"` in `paperlint.json` |
159
+ | hooks fail with `Cannot find module` in a fresh clone | the hook commands run files in `node_modules`: run `npm install` |
160
+ | `paperlint: not built` | installed from git with `--ignore-scripts`, or a clone before building: run `npm run build` in the package |
161
+ | a skill does not show up in Claude Code | its name was already taken in `.claude/skills/`. `init` names it and leaves it alone; rename or remove yours and run `npx paperlint init` again |
162
+ | **every** shell command is blocked in Claude Code | `paper-edit-guard` cannot parse the root `paperlint.json` (merge-conflict markers, a typo), and refuses rather than guess. Fix it with a normal file edit — file edits are not blocked |
163
+ | anything else | `npx paperlint doctor` — it checks the setup and exits non-zero on anything miswired |
@@ -3,9 +3,10 @@
3
3
  Some checks matter only for some venues. paperlint ships them **off**. A venue preset that needs one
4
4
  turns it on for its papers (AgenticDev's turns on `pdf/last-page-balance`), and a paper can turn one
5
5
  on or off for itself in its own `paperlint.json`
6
- ([`configuration.md`](configuration.md#three-levels-of-settings)). No glob is involved: a paper's
7
- rules apply to that paper. The `rules` setting of your `package.json` still overrides both, for one
8
- paper or many ([`configuration.md`](configuration.md#the-rules-key-turning-rules-on-and-off)).
6
+ ([`configuration.md`](configuration.md#a-papers-paperlintjson)). No glob is involved: a paper's
7
+ rules apply to that paper. The root `paperlint.json` can turn it on for every paper, or for some
8
+ with a block ([`configuration.md`](configuration.md#the-rules-key-turning-rules-on-and-off)); a
9
+ paper's own file still has the last word for that paper.
9
10
 
10
11
  | rule | what it checks | who needs it |
11
12
  | ----------------------- | ------------------------------------------------------------- | --------------------------------------------------------- |
@@ -48,7 +49,7 @@ So: **build, then lint.**
48
49
  | a review build with numbered lines | nothing — the numbers run down the whole page, so both columns measure full height, and balance is a camera-ready requirement |
49
50
 
50
51
  If you turn the rule on — in a `paperlint.json` beside no `paper.tex`, or with a `files` glob in
51
- `package.json` that reaches none — `paperlint lint` fails and says so, rather than reporting a clean
52
+ the root `paperlint.json` that reaches none — `paperlint lint` fails and says so, rather than reporting a clean
52
53
  run for a rule that never ran.
53
54
 
54
55
  ### Which venues need it