paperlint 9.0.1 → 10.1.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 (196) hide show
  1. package/CONTRIBUTING.md +16 -4
  2. package/README.md +68 -128
  3. package/dist/build.d.ts +1 -1
  4. package/dist/build.js +1 -1
  5. package/dist/cli.d.ts +5 -1
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +52 -6
  8. package/dist/cli.js.map +1 -1
  9. package/dist/domain/layout-tree.d.ts +19 -0
  10. package/dist/domain/layout-tree.d.ts.map +1 -0
  11. package/dist/domain/layout-tree.js +32 -0
  12. package/dist/domain/layout-tree.js.map +1 -0
  13. package/dist/domain/lookup-cache.d.ts +2 -2
  14. package/dist/domain/lookup-cache.js +2 -2
  15. package/dist/domain/paper-layout.d.ts +81 -0
  16. package/dist/domain/paper-layout.d.ts.map +1 -0
  17. package/dist/domain/paper-layout.js +259 -0
  18. package/dist/domain/paper-layout.js.map +1 -0
  19. package/dist/domain/repro-layout.d.ts +80 -0
  20. package/dist/domain/repro-layout.d.ts.map +1 -0
  21. package/dist/domain/repro-layout.js +247 -0
  22. package/dist/domain/repro-layout.js.map +1 -0
  23. package/dist/domain/scaffold-layout.d.ts +28 -0
  24. package/dist/domain/scaffold-layout.d.ts.map +1 -0
  25. package/dist/domain/scaffold-layout.js +81 -0
  26. package/dist/domain/scaffold-layout.js.map +1 -0
  27. package/dist/eslint-rules/js-needs-reason.d.ts +55 -0
  28. package/dist/eslint-rules/js-needs-reason.d.ts.map +1 -0
  29. package/dist/eslint-rules/js-needs-reason.js +35 -0
  30. package/dist/eslint-rules/js-needs-reason.js.map +1 -0
  31. package/dist/eslint-rules/prereg-coverage.d.ts +98 -0
  32. package/dist/eslint-rules/prereg-coverage.d.ts.map +1 -0
  33. package/dist/eslint-rules/prereg-coverage.js +274 -0
  34. package/dist/eslint-rules/prereg-coverage.js.map +1 -0
  35. package/dist/folder-layout-hook.d.ts +21 -0
  36. package/dist/folder-layout-hook.d.ts.map +1 -0
  37. package/dist/folder-layout-hook.js +117 -0
  38. package/dist/folder-layout-hook.js.map +1 -0
  39. package/dist/folder-layout-rule.d.ts +22 -0
  40. package/dist/folder-layout-rule.d.ts.map +1 -0
  41. package/dist/folder-layout-rule.js +107 -0
  42. package/dist/folder-layout-rule.js.map +1 -0
  43. package/dist/init.js +1 -1
  44. package/dist/init.js.map +1 -1
  45. package/dist/new-paper.d.ts +4 -1
  46. package/dist/new-paper.d.ts.map +1 -1
  47. package/dist/new-paper.js +40 -6
  48. package/dist/new-paper.js.map +1 -1
  49. package/dist/package-dirs.d.ts +6 -0
  50. package/dist/package-dirs.d.ts.map +1 -1
  51. package/dist/package-dirs.js +6 -0
  52. package/dist/package-dirs.js.map +1 -1
  53. package/dist/references.d.ts +1 -1
  54. package/dist/references.d.ts.map +1 -1
  55. package/dist/references.js +2 -2
  56. package/dist/references.js.map +1 -1
  57. package/dist/repro-layout-rule.d.ts +16 -0
  58. package/dist/repro-layout-rule.d.ts.map +1 -0
  59. package/dist/repro-layout-rule.js +81 -0
  60. package/dist/repro-layout-rule.js.map +1 -0
  61. package/dist/skills/label-agent-runs/scripts/labels-commands.d.ts +13 -0
  62. package/dist/skills/label-agent-runs/scripts/labels.d.ts +2 -0
  63. package/dist/skills/run-agent-grid/scripts/grid-guard-commands.d.ts +20 -0
  64. package/dist/skills/run-agent-grid/scripts/grid-guard.d.ts +2 -0
  65. package/docs/ci.md +60 -0
  66. package/docs/configuration.md +2 -0
  67. package/docs/e2e.md +2 -2
  68. package/docs/install.md +20 -16
  69. package/docs/layout.md +154 -0
  70. package/docs/optional-rules.md +5 -4
  71. package/docs/references.md +5 -2
  72. package/docs/rules/paper/allowed-files-only.md +192 -0
  73. package/docs/rules/repro/allowed-files-only.md +128 -0
  74. package/docs/rules/study/declare-sample.md +146 -0
  75. package/docs/rules.md +53 -50
  76. package/docs/skills.md +8 -3
  77. package/docs/testing.md +13 -0
  78. package/docs/venues.md +94 -0
  79. package/eslint-rules/js-needs-reason.test.ts +141 -0
  80. package/eslint-rules/js-needs-reason.ts +77 -0
  81. package/eslint-rules/prereg-coverage.test.ts +448 -0
  82. package/eslint-rules/prereg-coverage.ts +419 -0
  83. package/eslint.config.mjs +22 -2
  84. package/lib/skill-trigger-cases.harness.mjs +51 -5
  85. package/lib/skill-trigger-cases.mjs +53 -15
  86. package/lib/skill-trigger-cases.test.ts +211 -0
  87. package/package.json +8 -4
  88. package/plugin/hooks/hooks.json +9 -0
  89. package/scripts/coverage-config.test.ts +5 -3
  90. package/skills/analyze-sibling-paper/SKILL.md +2 -2
  91. package/skills/analyze-sibling-paper/SKILL.md.spec.ts +1 -1
  92. package/skills/build-benchmark/SKILL.md +17 -1
  93. package/skills/build-benchmark/SKILL.md.spec.ts +16 -0
  94. package/skills/build-label-page/SKILL.md +120 -0
  95. package/skills/build-label-page/SKILL.md.spec.ts +131 -0
  96. package/skills/build-label-page/build-label-page.eval.mjs +15 -0
  97. package/skills/build-label-page/build-label-page.test.ts +959 -0
  98. package/skills/build-label-page/references/checklist.md +148 -0
  99. package/skills/build-label-page/references/example-generator.py +803 -0
  100. package/skills/find-venue/SKILL.md +5 -3
  101. package/skills/find-venue/SKILL.md.spec.ts +4 -2
  102. package/skills/grade-paper-writing/SKILL.md +7 -7
  103. package/skills/grade-paper-writing/SKILL.md.spec.ts +6 -6
  104. package/skills/label-agent-runs/SKILL.md +103 -0
  105. package/skills/label-agent-runs/SKILL.md.spec.ts +106 -0
  106. package/skills/label-agent-runs/label-agent-runs.eval.ts +55 -0
  107. package/skills/label-agent-runs/label-agent-runs.harness.ts +123 -0
  108. package/skills/label-agent-runs/references/formats.md +64 -0
  109. package/skills/label-agent-runs/scripts/labels-commands.js +13 -0
  110. package/skills/label-agent-runs/scripts/labels-commands.ts +13 -0
  111. package/skills/label-agent-runs/scripts/labels.js +396 -0
  112. package/skills/label-agent-runs/scripts/labels.test.ts +538 -0
  113. package/skills/label-agent-runs/scripts/labels.ts +501 -0
  114. package/skills/map-prior-work/SKILL.md +2 -2
  115. package/skills/map-prior-work/SKILL.md.spec.ts +1 -1
  116. package/skills/paper-pipeline/SKILL.md +15 -6
  117. package/skills/paper-pipeline/SKILL.md.spec.ts +14 -5
  118. package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +19 -9
  119. package/skills/paper-pipeline/references/acceptance-gate.md +19 -19
  120. package/skills/paper-pipeline/references/artifact-checklist.md +5 -0
  121. package/skills/paper-pipeline/references/pipeline-status-template.md +30 -1
  122. package/skills/paper-pipeline/repro/2026-08-07-fork-check.log +1 -1
  123. package/skills/paper-pipeline/repro/2026-08-07-fork-check2.log +1 -1
  124. package/skills/paper-pipeline/repro/2026-08-07-language-eval-pilot.log +1 -1
  125. package/skills/paper-pipeline/repro/2026-08-07-language-eval-raw.log +1 -1
  126. package/skills/paper-pipeline/repro/2026-08-08-framing-vs-vocabulary-oracle.log +48 -48
  127. package/skills/paper-pipeline/repro/2026-08-08-grade-paper-writing-ablation-A6-oracle.log +8 -8
  128. package/skills/paper-pipeline/repro/2026-08-08-grade-paper-writing-ablation-oracle.log +48 -48
  129. package/skills/paper-pipeline/repro/2026-08-08-parent-replication-gpw.log +2 -2
  130. package/skills/paper-pipeline/repro/2026-08-08-parent-replication.mjs +3 -3
  131. package/skills/paper-pipeline/repro/2026-10-13-grade-paper-writing-ablation-preflight-after.log +81 -0
  132. package/skills/paper-pipeline/repro/2026-10-13-grade-paper-writing-ablation-preflight-before.log +81 -0
  133. package/skills/paper-pipeline/repro/2026-10-13-grade-paper-writing-ablation-setupdiff.log +59 -0
  134. package/skills/paper-pipeline/repro/2026-10-13-grade-paper-writing-trigger.jsonl +1 -0
  135. package/skills/paper-pipeline/repro/2026-10-13-grade-paper-writing-trigger.log +22 -0
  136. package/skills/paper-pipeline/repro/README.md +4 -0
  137. package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +2 -2
  138. package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +2 -2
  139. package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +4 -2
  140. package/skills/paper-pipeline/scripts/generated-code.harness.mjs +7 -2
  141. package/skills/paper-pipeline/scripts/generated-code.mjs +8 -4
  142. package/skills/paper-pipeline/scripts/generated-code.test.ts +15 -1
  143. package/skills/paper-status/SKILL.md +2 -2
  144. package/skills/paper-status/SKILL.md.spec.ts +1 -1
  145. package/skills/run-agent-grid/SKILL.md +181 -0
  146. package/skills/run-agent-grid/SKILL.md.spec.ts +184 -0
  147. package/skills/run-agent-grid/references/failure-catalog.md +36 -0
  148. package/skills/run-agent-grid/references/registry-and-ledger.md +142 -0
  149. package/skills/run-agent-grid/run-agent-grid.eval.ts +59 -0
  150. package/skills/run-agent-grid/run-agent-grid.harness.ts +115 -0
  151. package/skills/run-agent-grid/scripts/grid-guard-commands.js +18 -0
  152. package/skills/run-agent-grid/scripts/grid-guard-commands.ts +21 -0
  153. package/skills/run-agent-grid/scripts/grid-guard.js +700 -0
  154. package/skills/run-agent-grid/scripts/grid-guard.test.ts +1090 -0
  155. package/skills/run-agent-grid/scripts/grid-guard.ts +869 -0
  156. package/skills/study-accepted-papers/SKILL.md +163 -161
  157. package/skills/study-accepted-papers/SKILL.md.spec.ts +167 -165
  158. package/skills/study-accepted-papers/references/base-rates.md +69 -0
  159. package/skills/study-accepted-papers/references/mechanisms.md +181 -0
  160. package/skills/verify-citations/SKILL.md +2 -2
  161. package/skills/verify-citations/SKILL.md.spec.ts +1 -1
  162. package/src/build.ts +1 -1
  163. package/src/cli.harness.mjs +42 -0
  164. package/src/cli.test.ts +90 -6
  165. package/src/cli.ts +78 -5
  166. package/src/domain/layout-tree.test.ts +32 -0
  167. package/src/domain/layout-tree.ts +47 -0
  168. package/src/domain/lookup-cache.ts +2 -2
  169. package/src/domain/paper-layout.test.ts +264 -0
  170. package/src/domain/paper-layout.ts +336 -0
  171. package/src/domain/repro-layout.test.ts +222 -0
  172. package/src/domain/repro-layout.ts +311 -0
  173. package/src/domain/scaffold-layout.ts +104 -0
  174. package/src/folder-layout-hook.test.ts +214 -0
  175. package/src/folder-layout-hook.ts +141 -0
  176. package/src/folder-layout-rule.test.ts +235 -0
  177. package/src/folder-layout-rule.ts +151 -0
  178. package/src/hooks-settings.harness.ts +9 -8
  179. package/src/init.ts +1 -1
  180. package/src/layout-docs.test.ts +52 -0
  181. package/src/new-paper.harness.mjs +23 -1
  182. package/src/new-paper.test.ts +299 -6
  183. package/src/new-paper.ts +51 -5
  184. package/src/package-dirs.ts +8 -0
  185. package/src/paper-files.test.ts +9 -6
  186. package/src/references.test.ts +5 -5
  187. package/src/references.ts +2 -2
  188. package/src/repro-layout-rule.test.ts +114 -0
  189. package/src/repro-layout-rule.ts +121 -0
  190. package/src/rule-cases.test.ts +98 -0
  191. package/templates/paper/PIPELINE-STATUS.md +41 -7
  192. package/tsconfig.base.json +22 -0
  193. package/tsconfig.json +6 -24
  194. package/tsconfig.pkg.json +3 -1
  195. package/tsconfig.skills.json +14 -0
  196. package/tsconfig.src.json +12 -0
package/CONTRIBUTING.md CHANGED
@@ -132,13 +132,25 @@ site without `npx eslint . --prune-suppressions` fails lint, and #134 burns the
132
132
  New code is TypeScript (#78). The last block of [`eslint.config.mjs`](eslint.config.mjs) makes any
133
133
  `.js`, `.mjs` or `.cjs` file an error in the directories already converted (`TYPESCRIPT_ONLY`), so
134
134
  `npm run lint` fails on one; a directory that still mixes the two is not listed yet, and the change
135
- that converts it adds it. Code that only this repository runs (tests, `scripts/`, `test/e2e/`) is
135
+ that converts it adds it. Everywhere else, a NEW `.js`, `.mjs` or `.cjs` file fails `local/js-needs-reason`
136
+ unless its top comment says `JavaScript, not TypeScript: evidence|fixture|config-loader — <why>`;
137
+ the files that predate the rule are bulk suppressions in `eslint-suppressions.json`, which only
138
+ shrinks. Code that only this repository runs (tests, `scripts/`, `test/e2e/`) is
136
139
  run by `node` directly; code a consumer runs from `node_modules` needs a build first, because Node
137
140
  does not strip types there.
138
141
 
139
- `npm run build` is one `tsc` over two projects into one `dist/`: `tsconfig.json` builds `src/` to
140
- `dist/*.js`, and `tsconfig.pkg.json` builds `lib/` and `eslint-rules/` to `dist/lib/` and
141
- `dist/eslint-rules/`. Everything else imports those two through the package's subpath imports,
142
+ `npm run build` is `tsc -b`: TypeScript's build mode over the project references in the root
143
+ `tsconfig.json`, which is a solution file and compiles nothing itself. The three projects share
144
+ `tsconfig.base.json`: `tsconfig.pkg.json` builds `lib/` and `eslint-rules/` to `dist/lib/` and
145
+ `dist/eslint-rules/`; `tsconfig.src.json` builds `src/` to `dist/*.js` and references the first,
146
+ whose declarations it reads; `tsconfig.skills.json` compiles a skill's scripts
147
+ (`skills/*/scripts/*.ts`) to a `.js` BESIDE each, gitignored and shipped through `files` (their
148
+ declarations go to `dist/skills/`): a skill names its script as `${CLAUDE_SKILL_DIR}/scripts/<name>.js`,
149
+ and only a path inside the skill's own directory resolves the same through the consumer's
150
+ `.claude/skills/<name>` link and in this checkout. A second `tsc -b` with nothing changed is a no-op;
151
+ it decides that from `dist/.tsbuildinfo/`, so an output deleted by hand is rebuilt only by
152
+ `npx tsc -b --force` (or by deleting `dist/` with it). `tsconfig.test.json` is not part of the build:
153
+ it type-checks every `.ts` without emitting. Everything else imports `lib/` and `eslint-rules/` through the package's subpath imports,
142
154
  `#lib/<name>` and `#eslint-rules/<name>` (`"imports"` in `package.json`): the default target is the
143
155
  build, which is what an install runs; the `paperlint-source` condition points at the `.ts` instead,
144
156
  and `npm test` (so `npm run coverage` and `npm run check` too) and `tsconfig.test.json` set it so
package/README.md CHANGED
@@ -6,6 +6,7 @@ README style — keep it scannable, not a wall of text:
6
6
  - Tables for comparisons (commands, venues, checks). Code blocks for every command and file.
7
7
  - A blank line between every block. Bold only for the one phrase a skimmer must see.
8
8
  - Mechanics and edge cases live in docs/, linked — not inline.
9
+ - Budgets, held by test/readme-shape.test.ts: ≤ 300 lines, no ## section over 60, the install command in the first 90 lines. Over budget → move material to docs/ and link it; raising a budget is a decision, not a fix.
9
10
  - A reader must be able to answer "what is it, which venues, how do I start" from the first screen.
10
11
  - Sparse emoji are fine where they help rhythm and scanning (e.g. one per section heading or feature bullet); never decorative, never several in a row.
11
12
  - One name per thing, everywhere (lint, build, venue preset, kind, skill, papersDir), each explained once, before it is used.
@@ -37,37 +38,15 @@ before you submit.** It comes in two parts:
37
38
 
38
39
  ## Contents
39
40
 
40
- - [The pipeline](#-the-pipeline)
41
41
  - [Supported venues](#-supported-venues)
42
42
  - [Getting started](#-getting-started)
43
- - [A new paper](#a-new-paper)
44
- - [A paper you already have](#a-paper-you-already-have)
43
+ - [The pipeline](#-the-pipeline)
45
44
  - [Commands](#-commands)
46
45
  - [Skills](#-skills)
47
46
  - [Lint and build](#-lint-and-build)
48
- - [Configuration](#-configuration)
49
- - [Add a venue that isn't listed](#-add-a-venue-that-isnt-listed)
47
+ - [Configuration](#-configuration) · [add a venue](#-add-a-venue-that-isnt-listed)
50
48
  - [Run it in CI](#-run-it-in-ci)
51
- - [FAQ](#-faq)
52
- - [Docs](#-docs)
53
-
54
- ## 🧭 The pipeline
55
-
56
- ```text
57
- stage what you get skills linter
58
- 1 idea a go / no-go, with the reason yes
59
- 2 venue venues ranked, deadlines planned yes
60
- 3 study a study design, honest statistics yes
61
- 4 draft a first draft, then a tighter one yes lint
62
- 5 review the objections, before the reviewers yes lint
63
- 6 submit a PDF within the limit, recorded yes build, lint
64
- 7 camera-ready the final, de-anonymised PDF yes build, lint
65
- 8 extend a plan for the next paper yes
66
-
67
- skills: optional, with Claude Code
68
- lint: every edit and every CI run, once the paper exists (paperlint new)
69
- build: compiles and measures the PDF; lint then checks pages, fonts, references
70
- ```
49
+ - [FAQ](#-faq) · [Docs](#-docs)
71
50
 
72
51
  ## 🎯 Supported venues
73
52
 
@@ -94,24 +73,18 @@ REALM.
94
73
 
95
74
  <!-- `vigiles:symbol src/init.ts#init` — `npm run check` fails if this function is renamed or removed. -->
96
75
 
97
- 1. Install (Node 22.13 or newer):
76
+ 1. Install (Node 22.13 or newer) and set up. `init` asks where your papers live (default
77
+ `papers/`), whether to install TeX Live now, and whether to add a CI workflow; with Claude Code
78
+ it also installs the skills and four hooks ([what they do](docs/install.md)).
98
79
 
99
80
  ```sh
100
81
  npm i -D paperlint
101
- ```
102
-
103
- 2. Set up. `init` asks three questions: where your papers live (default `papers/`), whether to
104
- install TeX Live now, and whether to add a CI workflow. With Claude Code it also installs the
105
- skills and three hooks ([what they do](docs/install.md)).
106
-
107
- ```sh
108
82
  npx paperlint init
109
83
  ```
110
84
 
111
- 3. Create a paper. `--venue` names its venue preset from the table above, `--kind` which of its page
112
- limits applies. Name the folder after the work, not the venue: a rejected paper moves to another
113
- venue and keeps its folder, so `new` refuses a name like `aisec-2026` (`--allow-venue-name` when
114
- the word really is the work's):
85
+ 2. Create a paper. `--venue` names its venue preset from the table above, `--kind` which of its page
86
+ limits applies. Name the folder after the work, not the venue: a rejected paper moves on and keeps
87
+ its folder, so `new` refuses a name like `aisec-2026`.
115
88
 
116
89
  <!-- `vigiles:symbol src/new-paper.ts#newPaper` — `npm run check` fails if this function is renamed or removed. -->
117
90
  <!-- `vigiles:symbol src/new-paper.ts#ALLOW_VENUE_NAME` — the flag's one spelling; `npm run check` fails if it is renamed or removed. -->
@@ -120,25 +93,19 @@ REALM.
120
93
  npx paperlint new my-paper --venue agenticdev --kind short
121
94
  ```
122
95
 
123
- It writes `papers/my-paper/` (in the folder you gave `init`) with three files:
124
- - `paper.tex` — your paper;
125
- - `paperlint.json` — its venue preset and kind;
126
- - `PIPELINE-STATUS.md` — which stage the paper is at; the skills read and update it.
96
+ It writes `papers/my-paper/` with `paper.tex`, `paperlint.json` (venue and kind),
97
+ `PIPELINE-STATUS.md` (the stage the paper is at) and the empty folders of the
98
+ [layout](docs/layout.md#the-map).
127
99
 
128
- 4. Build the PDF. **This needs TeX Live, a ~270 MB download (~3 min, once).** If you said no in
129
- `init`, run `npx paperlint toolchain` first.
100
+ 3. Build the PDF. **This needs TeX Live, a ~270 MB download (~3 min, once)**; if you said no in
101
+ `init`, run `npx paperlint toolchain` first. Then check it:
130
102
 
131
103
  ```sh
132
104
  npx paperlint build papers/my-paper
133
- ```
134
-
135
- 5. Check it:
136
-
137
- ```sh
138
105
  npx paperlint lint
139
106
  ```
140
107
 
141
- On the stub from step 3 the first run already catches something (output shortened):
108
+ On the stub from step 2 the first run already catches something (output shortened):
142
109
 
143
110
  ```text
144
111
  papers/my-paper/paper.tex
@@ -152,13 +119,34 @@ REALM.
152
119
 
153
120
  ### A paper you already have
154
121
 
155
- paperlint finds a paper by its folder: `<papersDir>/<name>/`, with the main file named `paper.tex`.
122
+ Put it at `papers/my-paper/paper.tex` (or [set `papersDir`](#-configuration) to where your papers
123
+ live), then run the same `new` command: on a folder that holds anything it only adds the missing
124
+ `paperlint.json` and `PIPELINE-STATUS.md`, and never touches `paper.tex`. Build and lint as above.
125
+
126
+ ## 🧭 The pipeline
127
+
128
+ A paper goes through eight stages. The skills help at each one; lint checks the folder from the
129
+ moment it exists.
130
+
131
+ ```text
132
+ stage what you get where it goes
133
+ 1 idea a go / no-go, with the reason siblings/
134
+ 2 venue venues ranked, deadlines planned paperlint.json, the venue's corpus
135
+ 3 study a study design, honest statistics repro/<study>/
136
+ 4 draft a first draft, then a tighter one paper.tex, sections/, figures/
137
+ 5 review the objections, before the reviewers reviews/, PIPELINE-STATUS.md
138
+ 6 submit a PDF within the limit, recorded paper.pdf, versions/, submission/
139
+ 7 camera-ready the final, de-anonymised PDF versions/, talk/
140
+ 8 extend a plan for the next paper a new paper folder
141
+
142
+ skills: every stage; optional, with Claude Code
143
+ lint: any stage once the paper folder exists (paperlint new) — when you run it, and in CI
144
+ build: stages 6–7; compiles and measures the PDF, and lint then checks pages, fonts, references
145
+ hook: with Claude Code, refuses a new file in the wrong place before it is written
146
+ ```
156
147
 
157
- 1. Put the paper there — for example `papers/my-paper/paper.tex`, the rest of its files beside it. If
158
- your paper folders already live elsewhere, say `thesis/`, [set `papersDir`](#-configuration) instead.
159
- 2. Run `npx paperlint new my-paper --venue <preset> --kind <kind>`. On an existing folder it only
160
- adds the missing `paperlint.json` and `PIPELINE-STATUS.md`; it never touches `paper.tex`.
161
- 3. Build and lint, as above.
148
+ Each stage's skills, what it writes and what checks it, drawn as one flow, with the folder layout:
149
+ [`docs/layout.md`](docs/layout.md).
162
150
 
163
151
  ## 🧰 Commands
164
152
 
@@ -182,6 +170,7 @@ what it does — "is this idea worth a paper?", "find me a venue for this". `ini
182
170
 
183
171
  - **A go / no-go on your idea, with the reason.** `research-ideate`
184
172
  - **A ranked list of venues that fit** — deadline, page limit, indexing. `find-venue`
173
+ - **A page to label the study's items on your phone**, blind, with the answers exported into the paper's folder. `build-label-page`
185
174
  - **A first draft from your results.** `draft-paper`
186
175
  - **A hostile review before the real one.** `paper-adversarial-review`
187
176
  - **A ready / not ready verdict before you submit**, worst problem first. `harden-paper`
@@ -192,52 +181,34 @@ Every skill, by stage: [`docs/skills.md`](docs/skills.md).
192
181
 
193
182
  ## 🔍 Lint and build
194
183
 
195
- | | `paperlint build` | `paperlint lint` |
196
- | ------ | ------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
197
- | needs | Node, TeX Live, the network | Node |
198
- | does | compiles `paper.tex`, measures the PDF, checks each reference online (exists, right authors) | checks the source, and judges what build recorded: page limit, fonts, the reference results — offline |
199
- | writes | `paper.pdf`, in `_build/` the measurements and the reference results, and `repro/references-cache.json` | nothing (`--fix`: the three fixable slips) |
184
+ | | `paperlint build` | `paperlint lint` |
185
+ | ------ | -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
186
+ | needs | Node, TeX Live, the network | Node |
187
+ | does | compiles `paper.tex`, measures the PDF, checks each reference online (exists, right authors) | checks the source, and judges what build recorded: page limit, fonts, references; offline |
188
+ | writes | `paper.pdf`, `_build/`, `repro/data/references-cache.json` (commit it) | nothing (`--fix`: the three fixable slips) |
200
189
 
201
190
  - If `paper.pdf` or the bibliography changed since the last build, lint fails and tells you to rebuild.
202
- - Commit `repro/references-cache.json`: it keeps what the citation services answered, so a build
203
- asks only about new or edited entries (and answers older than 30 days), so an unchanged
204
- bibliography usually builds without the network ([`docs/references.md`](docs/references.md)).
205
- - A paper whose body is in other files (`\input{sections/intro}`) is linted file by file: a finding
206
- lands at the file and line it is in, and `--fix` edits that file. The files are the ones TeX read
207
- after `\begin{document}` in the last build (`_build/sources.json`) — not every `.tex` beside it,
208
- and not an included `.bbl` (a tool's output, read only as part of the whole paper). A paper that
209
- was not built, or changed since, is linted as `paper.tex` alone, and `paper/sources-fresh` says so.
210
- - Lint also checks the pipeline's own records: `PIPELINE-STATUS.md`, reviews, notes on related
211
- papers ([`docs/rules.md`](docs/rules.md)).
212
- - The rules run on ESLint, so a deliberate exception is a comment on the line above:
191
+ - Lint also checks the pipeline's own records (`PIPELINE-STATUS.md`, reviews, notes on related
192
+ papers) and the paper folder's layout.
193
+ - A deliberate exception is a comment on the line above:
213
194
  `% eslint-disable-next-line paper/leading-zero -- quoted from the reviewer`.
214
- - Errors fail the run; warnings only print, unless you pass `--max-warnings <n>`.
215
195
 
216
196
  Every check: [`docs/rules.md`](docs/rules.md). How build compiles:
217
197
  [`docs/configuration.md`](docs/configuration.md#how-paperlint-build-compiles-a-paper).
218
198
 
219
199
  ## 🧩 Configuration
220
200
 
221
- Two levels, one file name, both optional:
201
+ Two levels, one file name, both optional; a paper's file overrides the project's key by key, and
202
+ **an unknown key is an error**, so a typo cannot silently turn a setting off.
222
203
 
223
204
  ```
224
- paperlint.json the project: papersDir, rules, defaults for every paper
205
+ paperlint.json the project: papersDir (default papers), rules, defaults for every paper
225
206
  papers/my-paper/paperlint.json one paper: its venue preset ("extends"), its kind, its own rules
226
207
  ```
227
208
 
228
- - `papersDir` is the folder that holds your paper folders; it defaults to `papers`.
229
- - The paper's venue preset is its `"extends"` key — the one `new --venue` writes.
230
- - A paper's `paperlint.json` overrides the project's, key by key.
231
- - **An unknown key is an error**, so a typo cannot silently turn a setting off.
232
- - A paper that moves between venues keeps a list of attempts, `cycles`, instead of `"extends"`; each
233
- records its deadlines as read from the portal and the call, plus any override the chairs granted,
234
- and the earlier reading binds ([`docs/configuration.md`](docs/configuration.md#deadlines)).
235
- - **Deadlines of a shipped venue preset stay current on their own:** paperlint's repository re-reads
236
- each preset's portal daily and releases a patch when a date moves, so your normal dependency update
237
- (Dependabot, Renovate) brings it. `paperlint lint` itself never reads the clock or the network.
238
-
239
- Every key: [`docs/configuration.md`](docs/configuration.md). Already use ESLint for other files? See
240
- [`docs/configuration.md`](docs/configuration.md#using-the-rules-from-an-existing-eslint-config).
209
+ Shipped venue presets keep their deadlines current: a date that moves is released as a patch, so your
210
+ normal dependency update brings it. Every key, a paper that moves between venues (`cycles`), and
211
+ using the rules from your own ESLint config: [`docs/configuration.md`](docs/configuration.md).
241
212
 
242
213
  ### ➕ Add a venue that isn't listed
243
214
 
@@ -267,51 +238,17 @@ Give the path from where you run the command; `new` rewrites it relative to the
267
238
 
268
239
  ## 🤖 Run it in CI
269
240
 
270
- Two options:
271
-
272
- | job | time | checks |
273
- | -------------------------- | ----------------------------- | ------------------------------------------ |
274
- | build, then lint | ~3 min first run, then cached | everything |
275
- | lint only (`init` adds it) | seconds | the source; not pages, fonts or references |
276
-
277
- Lint judges pages, fonts and references from what build measured, so the full job builds first:
278
-
279
- ```yaml
280
- name: papers
281
- on: [push, pull_request]
282
- jobs:
283
- papers:
284
- runs-on: ubuntu-latest
285
- steps:
286
- - uses: actions/checkout@v4
287
- - uses: actions/setup-node@v4
288
- with:
289
- node-version: 22
290
- - run: npm ci
291
- - uses: actions/cache@v4
292
- with:
293
- path: ~/.cache/paperlint/texlive
294
- key: texlive-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
295
- - run: npx paperlint toolchain
296
- - run: npx paperlint build --all
297
- - run: npx paperlint lint
298
- ```
299
-
300
- Lint only — each paper then gets one warning that it was not built. Call the action from the
301
- installed package, after `npm ci`: the action and the CLI it runs are then one revision, pinned by
302
- your lockfile.
241
+ `init` offers a lint-only workflow (seconds; skips pages, fonts and references). The full job builds
242
+ first (~3 min, then cached) and checks everything:
303
243
 
304
244
  ```yaml
305
- steps:
306
- - uses: actions/checkout@v4
307
- - uses: actions/setup-node@v4
308
- with:
309
- node-version: 22
310
- - run: npm ci
311
- - uses: ./node_modules/paperlint
245
+ - run: npm ci
246
+ - run: npx paperlint toolchain
247
+ - run: npx paperlint build --all
248
+ - run: npx paperlint lint
312
249
  ```
313
250
 
314
- With no `paths` it lints the `papersDir` of your `paperlint.json`; pass `paths:` only to lint less.
251
+ Both workflows in full, with the TeX Live cache: [`docs/ci.md`](docs/ci.md).
315
252
 
316
253
  ## ❓ FAQ
317
254
 
@@ -329,6 +266,9 @@ Only `paperlint lint --fix`, and only three rules: `paper/section-word` (`§`
329
266
  ## 📚 Docs
330
267
 
331
268
  - [`docs/skills.md`](docs/skills.md) — every skill, by stage
269
+ - [`docs/layout.md`](docs/layout.md) — the pipeline's flow, where each stage writes, and what checks it when
270
+ - [`docs/venues.md`](docs/venues.md) — a venue's corpus of accepted papers, presets and cards: three places, three jobs
271
+ - [`docs/ci.md`](docs/ci.md) — the two CI workflows, in full
332
272
  - [`docs/install.md`](docs/install.md) — what `init` does, the hooks, package managers, troubleshooting
333
273
  - [`docs/configuration.md`](docs/configuration.md) — every setting, how `build` compiles, using your own ESLint
334
274
  - [`docs/rules.md`](docs/rules.md) — every check, venue presets, recording a submitted PDF
package/dist/build.d.ts CHANGED
@@ -133,7 +133,7 @@ export declare const measureStep: BuildStep;
133
133
  * `_build/references.json` for the offline lint rules (`reference-rules.ts`). OPTIONAL and NEVER
134
134
  * FAILING: without network the PDF is still built, and the record says `not-checked`.
135
135
  *
136
- * What the services answered is read from and added to `<paper>/repro/references-cache.json`
136
+ * What the services answered is read from and added to `<paper>/repro/data/references-cache.json`
137
137
  * (`references.ts`, `domain/lookup-cache.ts`), committed with the paper, so only a new or edited
138
138
  * entry is asked, and an unchanged bibliography is checked with no network at all (#107).
139
139
  */
package/dist/build.js CHANGED
@@ -420,7 +420,7 @@ export const measureStep = {
420
420
  * `_build/references.json` for the offline lint rules (`reference-rules.ts`). OPTIONAL and NEVER
421
421
  * FAILING: without network the PDF is still built, and the record says `not-checked`.
422
422
  *
423
- * What the services answered is read from and added to `<paper>/repro/references-cache.json`
423
+ * What the services answered is read from and added to `<paper>/repro/data/references-cache.json`
424
424
  * (`references.ts`, `domain/lookup-cache.ts`), committed with the paper, so only a new or edited
425
425
  * entry is asked, and an unchanged bibliography is checked with no network at all (#107).
426
426
  */
package/dist/cli.d.ts CHANGED
@@ -146,7 +146,7 @@ export declare function toPaths(papers: unknown): string[];
146
146
  * real verdict (2 included) passes through. Silent degradation is worse than explicit degradation,
147
147
  * but blocking everything is worse than both.
148
148
  */
149
- export declare function runHook(name: string | undefined, { err, run, resolve, }?: {
149
+ export declare function runHook(name: string | undefined, { err, run, resolve, stdin, out, }?: {
150
150
  err?: typeof console.error;
151
151
  /** Runs the hook with the terminal's streams: `spawnSync` is one, and only its status is read. */
152
152
  run?: (file: string, args: readonly string[], options: {
@@ -155,6 +155,10 @@ export declare function runHook(name: string | undefined, { err, run, resolve, }
155
155
  readonly status: number | null;
156
156
  };
157
157
  resolve?: (spec: string) => string;
158
+ /** The hook's payload, as Claude Code sends it on stdin. */
159
+ stdin?: () => string;
160
+ /** Where an in-process hook's output goes: stdout, which Claude Code reads. */
161
+ out?: (text: string) => void;
158
162
  }): number;
159
163
  /**
160
164
  * Create one paper and lint it — shared by `paperlint new` and `paperlint init --paper`, so the two cannot
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -71,7 +71,7 @@ import { paperPreset, settingsProblemLine, presetProblemText, resolvePreset, shi
71
71
  import { mergeRequirements, declaredUnion, requirementsFor, NO_REQUIREMENTS, } from "./tex-requirements.js";
72
72
  import { doctor } from "./doctor.js";
73
73
  import { askOnTerminal, askOrDefault, init, processInteractivity, } from "./init.js";
74
- import { newPaper, reportNewPaper } from "./new-paper.js";
74
+ import { newPaper, reportNewPaper, STATUS_FILE, } from "./new-paper.js";
75
75
  // The one source for the consumer's config key lives in lib/ (the ESLint rules and the skill scripts
76
76
  // import it too). It is imported compiled, from dist/, the one path that resolves the same from src/
77
77
  // and from dist/ — see CONTRIBUTING.md.
@@ -80,9 +80,13 @@ import { parseRuleBlocks, parseRuleEntries, shippedRuleIds, unknownKeys, } from
80
80
  export { init };
81
81
  export { nextSteps } from "./init.js";
82
82
  import paperStages from "#eslint-rules/paper-stages";
83
+ import preregCoverage from "#eslint-rules/prereg-coverage";
83
84
  import cycleStage from "#eslint-rules/cycle-stage";
84
85
  import { CYCLE_RULE_LEVELS, cycleRules } from "./cycle-rules.js";
85
86
  import { FOLDER_VENUE_RULE_LEVELS, folderVenueRules, } from "./folder-venue-rule.js";
87
+ import { FOLDER_LAYOUT_RULE_LEVELS, folderLayoutRules, } from "./folder-layout-rule.js";
88
+ import { folderLayoutHook } from "./folder-layout-hook.js";
89
+ import { REPRO_LAYOUT_RULE_LEVELS, reproLayoutRules, } from "./repro-layout-rule.js";
86
90
  import typography from "#eslint-rules/paper-typography";
87
91
  import texBuild from "#eslint-rules/tex-build";
88
92
  import reviewFrontmatter from "#eslint-rules/review-frontmatter";
@@ -90,6 +94,7 @@ import siblingFrontmatter from "#eslint-rules/sibling-frontmatter";
90
94
  import pdfRules from "#eslint-rules/pdf-last-page-balance";
91
95
  import { messageOf } from "./domain/text.js";
92
96
  import { isRecord } from "./domain/record.js";
97
+ import { ESCAPE_HATCH } from "./domain/paper-layout.js";
93
98
  /** The shipped venue presets, read from the package's venues directory — the one list. */
94
99
  const SHIPPED_VENUES = () => shippedPresets(presetsDir());
95
100
  const USAGE = `paperlint — machine-checkable gates for a paper kept in git
@@ -251,6 +256,8 @@ const FRAGMENT_RULES = {
251
256
  "future-promise": texBuild["future-promise"],
252
257
  "heading-case": REGISTER_AND_CASE_RULES["heading-case"],
253
258
  };
259
+ /** What the two layout rules read with: the disk and its directory listings. */
260
+ const LAYOUT_RULE_DEPS = { files: nodeFiles, listDir: nodeListDir };
254
261
  /** What the talk rules read with: the disk, the shipped presets, and the three media readers. */
255
262
  const TALK_RULE_DEPS = {
256
263
  files: nodeFiles,
@@ -278,7 +285,9 @@ export function buildConfig(opts = {}, texLanguage) {
278
285
  // its files carry every marker a paper does. Flat config does NOT ignore dot-directories by
279
286
  // default (only `node_modules/` and `.git/`), so without this block `paperlint lint` would lint the
280
287
  // template as a paper — and a richer template with placeholder stages would fail the run.
281
- { ignores: ["**/.template/"] },
288
+ // The same for a paper's `extra/`: the layout promises it is never judged, and an archived
289
+ // snapshot there carries a paper's markers too, so its folder read as a paper root with strays.
290
+ { ignores: ["**/.template/", `**/${ESCAPE_HATCH}/`] },
282
291
  // The `pdf` plugin is registered for EVERY file. A consumer's block (`rules`, appended below)
283
292
  // may name its rules for a glob that also matches markdown files; with the plugin defined only
284
293
  // beside `paper.tex`, ESLint would refuse those files with "could not find plugin". Every rule
@@ -307,8 +316,12 @@ export function buildConfig(opts = {}, texLanguage) {
307
316
  rules: {
308
317
  ...paperStages.rules,
309
318
  ...folderVenueRules({ files: nodeFiles, venuesDir: presetsDir() }),
319
+ ...folderLayoutRules(LAYOUT_RULE_DEPS),
310
320
  },
311
321
  },
322
+ repro: { rules: reproLayoutRules(LAYOUT_RULE_DEPS) },
323
+ // Registered, on for no paper: `"study/declare-sample": "error"` in paperlint.json turns it on.
324
+ study: { rules: preregCoverage.rules },
312
325
  cycle: { rules: CYCLE_STATUS_RULES },
313
326
  },
314
327
  ...md,
@@ -316,6 +329,8 @@ export function buildConfig(opts = {}, texLanguage) {
316
329
  "paper/stages": "error",
317
330
  "paper/source": "error",
318
331
  ...FOLDER_VENUE_RULE_LEVELS,
332
+ ...FOLDER_LAYOUT_RULE_LEVELS,
333
+ ...REPRO_LAYOUT_RULE_LEVELS,
319
334
  ...CYCLE_RULE_LEVELS,
320
335
  },
321
336
  },
@@ -516,7 +531,7 @@ export const OPTIONAL_RULES = new Set([...SHIPPED_RULES].filter((id) => !buildCo
516
531
  * 🔴 AN OPTIONAL RULE THAT IS ON AND REACHES NO PAPER IS A GREEN ZERO. A `files` glob that matches
517
532
  * nothing — a typo, a path relative to the wrong directory — leaves the rule never invoked, and a
518
533
  * rule that never runs reports exactly like a rule that passed. So for every optional rule the
519
- * consumer turned on, some `paper.tex` must actually have it enabled; the ones none has are
534
+ * consumer turned on, some paper's `paper.tex` or `PIPELINE-STATUS.md` must actually have it enabled; the ones none has are
520
535
  * returned, for the caller to refuse.
521
536
  *
522
537
  * `files` is every `paper.tex` the guard may count: the ones this run linted AND every paper of the
@@ -533,9 +548,14 @@ export async function silentOptionalRules(eslint, files, opts) {
533
548
  .filter(([id, e]) => OPTIONAL_RULES.has(id) && isOn(e))
534
549
  .map(([id]) => id)));
535
550
  const reached = new Set();
536
- for (const f of new Set(files.filter((p) => basename(p) === MAIN))) {
551
+ // A paper's rules reach it through two files: `paper.tex` and the `PIPELINE-STATUS.md` beside it,
552
+ // where the rules about the paper folder itself run.
553
+ const probes = [
554
+ ...new Set(files.filter((p) => basename(p) === MAIN)),
555
+ ].flatMap((f) => [f, join(dirname(f), STATUS_FILE)]);
556
+ for (const probe of probes) {
537
557
  // Undefined for a file outside ESLint's cwd or scope: it reaches nothing.
538
- const cfg = ComputedConfig.parse(await eslint.calculateConfigForFile(f));
558
+ const cfg = ComputedConfig.parse(await eslint.calculateConfigForFile(probe));
539
559
  // Reached is "some block names the rule for this paper", on OR off: a paper that turns off what
540
560
  // its venue preset turned on is the documented way out, and reads the same as a glob that missed
541
561
  // only if "off" is counted as "not there" — which refused every such paper.
@@ -877,6 +897,27 @@ export function toPaths(papers) {
877
897
  return papers.filter((x) => typeof x === "string" && x.trim() !== "");
878
898
  return [];
879
899
  }
900
+ /**
901
+ * Hooks that run inside this process instead of as a vigiles program, each because it must call the
902
+ * package's own code (`src/folder-layout-hook.ts` says why for its one). Advisory: whatever goes
903
+ * wrong, the tool call has already happened, so it says so on stderr and exits 0.
904
+ */
905
+ const IN_PROCESS_HOOKS = {
906
+ "paper-folder-layout": ({ err, stdin, out }) => {
907
+ try {
908
+ const said = folderLayoutHook(JSON.parse(stdin()), {
909
+ files: nodeFiles,
910
+ listDir: nodeListDir,
911
+ });
912
+ if (said !== null)
913
+ out(`${JSON.stringify(said)}\n`);
914
+ }
915
+ catch (e) {
916
+ err(`paperlint: the paper-folder-layout hook did not run: ${messageOf(e)}`);
917
+ }
918
+ return 0;
919
+ },
920
+ };
880
921
  /**
881
922
  * `paperlint hook <name>` — run an editor hook. It exists for ONE thing: so that the wiring does not
882
923
  * address the runtime from the project root.
@@ -912,11 +953,16 @@ export function runHook(name, { err = console.error, run = spawnSync,
912
953
  // This is neither our oversight nor their bug: a closed export map is normal practice. So we
913
954
  // resolve the root entry ("." → dist/test.js), take its directory and put `cli.js` next to it —
914
955
  // the very file the package itself declares as its `bin`.
915
- resolve = (spec) => createRequire(import.meta.url).resolve(spec), } = {}) {
956
+ resolve = (spec) => createRequire(import.meta.url).resolve(spec), stdin = () => readFileSync(0, "utf8"), out = (text) => {
957
+ process.stdout.write(text);
958
+ }, } = {}) {
916
959
  if (!name) {
917
960
  err(`\`hook\` needs a name, e.g. \`paperlint hook paper-edit-guard\``);
918
961
  return 2;
919
962
  }
963
+ const inProcess = IN_PROCESS_HOOKS[name];
964
+ if (inProcess !== undefined)
965
+ return inProcess({ err, stdin, out });
920
966
  const program = fileURLToPath(new URL(`../hooks/${name}.hook.mjs`, import.meta.url));
921
967
  if (!existsSync(program)) {
922
968
  err(`unknown hook \`${name}\` — no such program at ${program}`);