paperlint 10.2.0 → 10.3.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 (46) hide show
  1. package/dist/adapters/study-registry/index.d.ts +352 -1
  2. package/dist/adapters/study-registry/index.d.ts.map +1 -1
  3. package/dist/adapters/study-registry/index.js +171 -8
  4. package/dist/adapters/study-registry/index.js.map +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +6 -4
  7. package/dist/cli.js.map +1 -1
  8. package/dist/domain/study-registry.d.ts +201 -5
  9. package/dist/domain/study-registry.d.ts.map +1 -1
  10. package/dist/domain/study-registry.js +224 -2
  11. package/dist/domain/study-registry.js.map +1 -1
  12. package/dist/ports/study-registry-codec.d.ts +6 -2
  13. package/dist/ports/study-registry-codec.d.ts.map +1 -1
  14. package/dist/studies.d.ts.map +1 -1
  15. package/dist/studies.js +7 -4
  16. package/dist/studies.js.map +1 -1
  17. package/dist/study-registry-rule.d.ts +12 -5
  18. package/dist/study-registry-rule.d.ts.map +1 -1
  19. package/dist/study-registry-rule.js +67 -37
  20. package/dist/study-registry-rule.js.map +1 -1
  21. package/docs/design/study-registry.md +131 -15
  22. package/docs/layout.md +23 -21
  23. package/docs/rules/repro/study-plan-name.md +70 -0
  24. package/docs/rules/repro/study-registered.md +164 -6
  25. package/docs/rules.md +53 -52
  26. package/package.json +1 -1
  27. package/skills/build-benchmark/SKILL.md +24 -12
  28. package/skills/build-benchmark/SKILL.md.spec.ts +23 -11
  29. package/skills/build-label-page/SKILL.md +9 -7
  30. package/skills/build-label-page/SKILL.md.spec.ts +8 -6
  31. package/skills/run-agent-grid/SKILL.md +10 -5
  32. package/skills/run-agent-grid/SKILL.md.spec.ts +9 -4
  33. package/src/adapters/study-registry/index.test.ts +340 -0
  34. package/src/adapters/study-registry/index.ts +268 -10
  35. package/src/cli.ts +6 -4
  36. package/src/domain/study-measurements.test.ts +629 -0
  37. package/src/domain/study-registry.test.ts +10 -1
  38. package/src/domain/study-registry.ts +456 -10
  39. package/src/new-paper.test.ts +1 -0
  40. package/src/ports/study-registry-codec.ts +6 -2
  41. package/src/rule-cases.test.ts +27 -0
  42. package/src/studies-cli.test.ts +2 -2
  43. package/src/studies.test.ts +6 -0
  44. package/src/studies.ts +12 -3
  45. package/src/study-registry-rule.test.ts +140 -1
  46. package/src/study-registry-rule.ts +106 -39
@@ -1,8 +1,8 @@
1
1
  # repro/study-registered
2
2
 
3
3
  **Level:** error · **Reads:** the listing of the paper's `repro/`, `repro/studies.json`,
4
- `repro/README.md`, and each `result_file` the registry names · **Reported on:** the first line of the
5
- paper's `PIPELINE-STATUS.md`, once per finding
4
+ `repro/README.md`, and each `result_file` and `plan` the registry names · **Reported on:** the first
5
+ line of the paper's `PIPELINE-STATUS.md`, once per finding
6
6
 
7
7
  ## What it catches
8
8
 
@@ -46,9 +46,129 @@ is named `<id>-<slug>` after its row — `P1-S03-baseline`. The design: [`study-
46
46
  | `superseded_by` | optional: the id of the study that replaced it; required when `superseded` |
47
47
  | `in_paper` | `yes`, `no`, or `registry` (listed in the registry, not in the paper's text) |
48
48
 
49
- A row may carry more fields — a paper's own measurement metadata (`role`, `frame`, `method`,
50
- `outcome`, …) — and `paperlint studies new` keeps them when it rewrites the file. The top level holds
51
- only `prefix` and `studies`.
49
+ | `measurement` | optional: the id of the measurement the study belongs to (below) |
50
+ | `plan` | optional: the study's plan, a file inside its folder — `plan.json` (below) |
51
+
52
+ A row may carry more fields — a paper's own metadata (`frame`, `method`, `outcome`, …) — and
53
+ `paperlint studies new` keeps them when it rewrites the file. Beside `prefix` and `studies`, the top
54
+ level holds only the optional `author`, `requirements` and `measurements`; any other key is an error.
55
+
56
+ ### Measurements, requirements, the author
57
+
58
+ One measurement — a census, a benchmark — often spans several studies: a pilot, the main run, a
59
+ replication. A measurement is declared once, at the top level, and each of its studies' rows names
60
+ it in `measurement`. The author's requirements on the measurements are kept in their words, beside
61
+ them:
62
+
63
+ ```json
64
+ {
65
+ "prefix": "P1",
66
+ "author": "A. Author",
67
+ "requirements": [
68
+ {
69
+ "id": "R1",
70
+ "text": "Every sample is a random draw from all rules found, no filter by difficulty.",
71
+ "date": "2026-10-10",
72
+ "source": "chat"
73
+ }
74
+ ],
75
+ "measurements": [
76
+ {
77
+ "id": "M1",
78
+ "title": "Rule census",
79
+ "rq": "RQ1",
80
+ "role": "headline",
81
+ "approval": {
82
+ "by": "A. Author",
83
+ "date": "2026-10-12",
84
+ "quote": "This is the one."
85
+ },
86
+ "requirements": ["R1"]
87
+ }
88
+ ],
89
+ "studies": [
90
+ {
91
+ "id": "P1-S01",
92
+ "folder": "P1-S01-census-pilot",
93
+ "measurement": "M1",
94
+ "plan": "plan.json",
95
+ "…": "…"
96
+ },
97
+ {
98
+ "id": "P1-S02",
99
+ "folder": "P1-S02-census",
100
+ "measurement": "M1",
101
+ "plan": "plan.json",
102
+ "…": "…"
103
+ }
104
+ ]
105
+ }
106
+ ```
107
+
108
+ | field | what it holds |
109
+ | ----------------------------- | -------------------------------------------------------------------------------- |
110
+ | `author` | optional: the paper's author, the one person who approves a headline measurement |
111
+ | `requirements[].id` | the requirement's id, named by the measurements that must meet it |
112
+ | `requirements[].text` | the author's words, verbatim |
113
+ | `requirements[].date` | `YYYY-MM-DD` |
114
+ | `requirements[].source` | where they said it: a chat, a message, a review comment |
115
+ | `measurements[].id` | the measurement's id, named by its studies' rows |
116
+ | `measurements[].title` | a plain name |
117
+ | `measurements[].rq` | optional: the research question it answers |
118
+ | `measurements[].role` | `headline` (the paper's claim rests on it) or `baseline` |
119
+ | `measurements[].approval` | the author's approval: `by`, `date`, their words in `quote`, optional `source` |
120
+ | `measurements[].requirements` | optional: the ids of the requirements it must meet |
121
+
122
+ A measurement may carry more fields, as a row does; a requirement and an approval are exactly
123
+ their fields. **A registry without `measurements` is judged as before**: a row's `measurement` is
124
+ then a field of the paper's own, and nothing checks it.
125
+
126
+ ### The study's plan, `plan.json`
127
+
128
+ Each study's plan is a JSON file in its folder, fixed before the runs: what it measures, the
129
+ population and how the sample is drawn from it, the arms compared, the primary outcome, and the
130
+ author's check of the drawn units. `paperlint studies new` writes the template beside the study's
131
+ `README.md` and names it in the row's `plan`:
132
+
133
+ ```json
134
+ {
135
+ "question": "",
136
+ "population": {
137
+ "source": "",
138
+ "draw": { "kind": "all", "method": "" }
139
+ },
140
+ "arms": [],
141
+ "primary_outcome": "",
142
+ "author_check": null
143
+ }
144
+ ```
145
+
146
+ | field | what it holds |
147
+ | ------------------------ | -------------------------------------------------------------------------------- |
148
+ | `question` | what the study measures, one sentence |
149
+ | `population.source` | where the units come from: the census, crawl or registry, with its date |
150
+ | `population.draw.kind` | `all` (the whole population, a census: no seed) or `seeded` (a sample) |
151
+ | `population.draw.method` | how the sample is drawn: `census`, `random`, `stratified` … |
152
+ | `population.draw.seed` | `seeded` only, and required there: the draw's seed, a string or a number |
153
+ | `arms` | the conditions compared, each `{ "name": … }`, each name once; `[]` for a census |
154
+ | `primary_outcome` | the one number the study reports first |
155
+ | `author_check` | `null` until the author has seen the drawn units; then `by`, `date`, `quote` |
156
+ | `extra` | optional: the paper's own keys (hypotheses, models, budget), kept unread |
157
+
158
+ The template's draw is of all the population; a sampled study changes it to
159
+ `{ "kind": "seeded", "method": "random", "seed": 7 }`. The population's size is not stored: it is
160
+ counted from the source, and a stored copy could only contradict it.
161
+
162
+ The plan is closed at every level, and holds no contradiction. Each of these makes the file "not a
163
+ study plan" (`bad-plan`, with the reason): a misspelt key; a key of the paper's own outside `extra`,
164
+ or a key of `extra` that is a field of the plan; a missing key — `null` is the one way to say "not
165
+ yet"; a `seeded` draw without its seed, or an `all` draw with one; two arms of one name; and an
166
+ `author_check` on a plan whose required texts are still empty — the author approves a complete plan.
167
+
168
+ A plan named `prereg.json`, its old name, is parsed by a loose schema — keys of its own accepted at
169
+ every level, those at the top kept in `extra`; a seed makes the draw `seeded`, none (or `null`)
170
+ makes it `all`; a size is not read; an absent check is `null` — and
171
+ [`repro/study-plan-name`](study-plan-name.md) warns that it moves to `plan.json`.
52
172
 
53
173
  The rule reports:
54
174
 
@@ -69,7 +189,20 @@ The rule reports:
69
189
  ```
70
190
 
71
191
  - a `repro/` with study folders and no `repro/studies.json` (one finding naming the file), and a
72
- `studies.json` that is not JSON or not of the shape above (one finding with the reasons).
192
+ `studies.json` that is not JSON or not of the shape above (one finding with the reasons);
193
+ - when the registry has `measurements`: a row whose `measurement` is not one of them
194
+ (`unknown-measurement`), a measurement no row names (`empty-measurement`), and a measurement that
195
+ must meet a requirement the registry does not hold (`unknown-requirement`);
196
+ - a `headline` measurement with no `approval`, or, when the registry names its `author`, one approved
197
+ by anyone else (`headline-approval`);
198
+ - a row's `plan` that is outside its folder, not a file, or not of the plan's shape — and, once the
199
+ study has run (`running`, `done`, `stopped`, `superseded`), a plan that leaves `question`,
200
+ `population.source`, `population.draw.method` or `primary_outcome` empty (`bad-plan`);
201
+ - a study of a `headline` measurement that has run with no `author_check` in its plan — or no plan —
202
+ or, when the registry names its `author`, a check by anyone else (`author-check`).
203
+
204
+ A study that is still `designing` owes neither a filled plan nor the author's check: the template
205
+ `studies new` writes is clean until the first run. A `dropped` study owes neither.
73
206
 
74
207
  ## Why
75
208
 
@@ -98,6 +231,20 @@ Rule repro/study-registered: https://github.com/zernie/paperlint/blob/main/docs/
98
231
 
99
232
  Failing: a row set to `done` by hand, with no result, and the index not regenerated — two findings.
100
233
 
234
+ Failing: the census of the example above set to `running` while its plan is still the template —
235
+ the plan is reported for its empty fields, and the study for the author's check it lacks:
236
+
237
+ ```text
238
+ papers/example/PIPELINE-STATUS.md
239
+ 1:1 error The plan of `P1-S02`, `plan.json` in its folder, leaves `question`, `population.source`,
240
+ `population.draw.method` and `primary_outcome` empty, and the study is `running`: a
241
+ plan is written before the runs. What to do: fill them in, or set the study back to
242
+ `designing`.
243
+ 1:1 error `P1-S02` is `running` in the headline measurement `M1`, and its plan has no
244
+ `author_check`. What to do: show the author the drawn units, then record their answer
245
+ in the plan's `author_check` — `by`, `date`, and their words in `quote`.
246
+ ```
247
+
101
248
  Passing: a paper with no study folder; and a `repro/` made only by `paperlint studies new`, its rows
102
249
  edited as the studies end and its index regenerated:
103
250
 
@@ -107,6 +254,7 @@ repro/
107
254
  ├── studies.json
108
255
  ├── P1-S01-baseline-pass-rate/
109
256
  │ ├── README.md
257
+ │ ├── plan.json
110
258
  │ └── results.tsv
111
259
  ├── tools/
112
260
  └── data/
@@ -130,6 +278,11 @@ No rule options.
130
278
 
131
279
  - Whether `question` is one sentence and `result` one line, beyond their being text.
132
280
  - What a study's `README.md` says, or whether its frontmatter matches the row.
281
+ - Whether a plan's texts say anything true, beyond their being filled in; whether `arms` should be
282
+ empty; whether the person in `approval` or `author_check` really said it — the rule sees the
283
+ name, not who typed it.
284
+ - A repeated measurement or requirement id, and a requirement no measurement names.
285
+ - A `prereg.json` or `plan.json` that no row names.
133
286
  - Whether the paper cites a study: `in_paper` is declared, not detected.
134
287
  - What may stand at the top of `repro/` besides the study folders, and whether a study folder has a
135
288
  `README.md`: that is [`repro/allowed-files-only`](allowed-files-only.md).
@@ -143,6 +296,11 @@ Create every new study with the command, which takes the next id, makes the fold
143
296
  npx paperlint studies new papers/example "Baseline pass rate" --kind measurement
144
297
  ```
145
298
 
299
+ `studies new` also writes the study's `plan.json`: fill it in before the first run, and, for a
300
+ study of a headline measurement, show the author the drawn units and record their answer in its
301
+ `author_check`. A measurement is added by hand to `measurements`, with the author's `approval` when
302
+ it is the headline, and each of its studies' rows names it in `measurement`.
303
+
146
304
  When a study ends, edit its row in `repro/studies.json` — `status`, `result`, `result_file`,
147
305
  `in_paper`, and `superseded_by` when another study replaced it — then regenerate the index:
148
306
 
package/docs/rules.md CHANGED
@@ -3,58 +3,59 @@
3
3
  The README lists what each check catches in one line. This page says exactly which file each one
4
4
  reads and when it fails. Errors fail `paperlint lint`; warnings print and do not.
5
5
 
6
- | Rule | Level | Reads | Fails when |
7
- | --------------------------------------------------------------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
8
- | `paper/stages` | error | `PIPELINE-STATUS.md` | a declared stage's PDF is missing, or the `bytes:` it names does not match the file's real size, or a frozen PDF exists that no stage declares |
9
- | `paper/source` | error | `PIPELINE-STATUS.md` | a declared stage has no frozen `.tex` beside its PDF (a commit hash does not count — squash and gc destroy it) |
10
- | [`study/declare-sample`](rules/study/declare-sample.md) | experimental; off unless a paper turns it on (`"error"`) | each study's `repro/<study>/prereg.json` | a study's pre-registration does not declare its `population` (what the claim is about, source, size), the `strata` and what each of its `screens` removed per stratum value, its `coverage` (share 0–1 or null with a reason, and the strata it cannot reach), or the `author_check` of the drawn units (a doubtful verdict needs a recorded `decision`); reported on `PIPELINE-STATUS.md`, once per problem — [page](rules/study/declare-sample.md) |
11
- | [`cycle/record`](rules/cycle/record.md) | error | `paperlint.json` (`cycles`) | the paper's attempts at venues (`cycles`, one entry per attempt) hold one source's deadlines out of order (registration ≤ submission ≤ resubmission < notification < camera-ready), or copy a portal reading the venue's preset carries; reported on `PIPELINE-STATUS.md`. A record that does not parse — a bare date, two open cycles, an unknown key — is refused by `paperlint lint` itself before any rule runs, exit 2 — [page](rules/cycle/record.md) |
12
- | [`cycle/evidence`](rules/cycle/evidence.md) | error | `paperlint.json` (`cycles`) → the paper folder | a closed cycle (`accepted`, `rejected`, `withdrawn`) or a deadline's `override` names an `evidence` file — the decision mail, the reviews, the withdrawal note, the chairs' mail — that is not in the paper folder; reported on `PIPELINE-STATUS.md` — [page](rules/cycle/evidence.md) |
13
- | [`cycle/stage`](rules/cycle/stage.md) | error | `PIPELINE-STATUS.md` → `paperlint.json` (`cycles`) | with `cycles` declared, a stage names no `cycle: <id>`, names one no cycle has, or a `submitted`/`camera-ready` stage belongs to a cycle whose `phase` is still `porting` — [page](rules/cycle/stage.md) |
14
- | [`cycle/port-done`](rules/cycle/port-done.md) | error | `paper.tex` → `paperlint.json` (`cycles`) → the preset's `template` | the current cycle still declares `"phase": "porting"` (which silences the venue's format rules) while the `\documentclass` is already the venue's template: the port is done, drop the phase — [page](rules/cycle/port-done.md) |
15
- | [`paper/folder-venue-leftover`](rules/paper/folder-venue-leftover.md) | warn | the folder name → `paperlint.json` | the paper's folder is named after another shipped venue than the one it extends (`papers/aisec-2026/` for a paper now set for AIDC); reported on `PIPELINE-STATUS.md` — [page](rules/paper/folder-venue-leftover.md) |
16
- | [`paper/allowed-files-only`](rules/paper/allowed-files-only.md) | error | the paper folder's root listing → `paperlint.json` | a file or folder at the paper's root is not part of the layout (`PLAN.md`, `archive/`, a stray PDF); reported on `PIPELINE-STATUS.md`, once per entry, with where it goes; `extra/` takes anything — [page](rules/paper/allowed-files-only.md) |
17
- | [`repro/allowed-files-only`](rules/repro/allowed-files-only.md) | error | the listing of the paper's `repro/` | an entry at the top of `repro/` is not `README.md`, `studies.json`, `.gitignore`, `tools/`, `data/` or a study folder named `<id>-<slug>` (or, in a paper without the registry, `<slug>-YYYY-MM-DD`), or a study folder has no `README.md`; reported on `PIPELINE-STATUS.md`, once per entry, with where it goes by kind (a script → `repro/tools/`, data → `repro/data/`, a note → its study or `research/`) — [page](rules/repro/allowed-files-only.md) |
18
- | [`repro/study-registered`](rules/repro/study-registered.md) | error | the listing of the paper's `repro/`, `repro/studies.json`, `repro/README.md` | a study folder with no row in `repro/studies.json`, a row whose folder is missing or not `<id>-<slug>`, a bad or repeated id, a title over 8 words, `done` without a result and an existing result file, `superseded` without an existing successor, or the index in `repro/README.md` out of sync with the registry; `paperlint studies new` and `paperlint studies index` write both — [page](rules/repro/study-registered.md) |
19
- | [`paper/sources-fresh`](rules/paper/sources-fresh.md) | warn | `paper.tex` → `_build/sources.json` | the paper has not been built (no record of the files TeX read), or a file changed since the last build (its SHA-256 differs) — run `npx paperlint build` |
20
- | `paper/author-list` | error | `paper.tex` → `_build/references.json`, `_build/sources.json` | an entry's authors are not those of the version it cites — typically the arXiv preprint's list under a published venue. Checked online by `paperlint build` (DBLP), reported on the entry's own line (or at the top of `paper.tex`, naming the entry's file); silent without a current build record |
21
- | `paper/section-word` | warn | `paper.tex` and its body files | `§` or `\S\ref` instead of the word Section. **Fixable:** `--fix` writes `Section~\ref{…}` / `Section 5` |
22
- | `paper/leading-zero` | warn | `paper.tex` and its body files | a decimal below 1 without its leading zero (`.05` instead of `0.05`, IEEE / ISO 80000-1 style), counted only where the reader sees it — prose, math and table cells, not macro options, column specs, comments, code or tikz. A number right after `¶` or `§` (`¶¶.42`) is a paragraph designator and is not reported; in a paragraph that cites numbers that way, another bare decimal is reported without a fix, with the zero as a suggestion. **Fixable** |
23
- | `paper/figure-ref-style` | warn | `paper.tex` and its body files | `Fig.~\ref` and `Figure~\ref` mixed in one document; each minority occurrence is reported. **Fixable:** `--fix` writes the majority form |
24
- | [`bib/reachable-entry`](rules/bib/reachable-entry.md) | warn | `paper.tex` → `_build/sources.json`: the databases the last build's bibtex opened | an entry has no doi, url or arXiv id — a reader has nothing to follow. Reported on the entry in the `filecontents` block of `paper.tex` that wrote its `.bib`, or at the top of `paper.tex` with the entry's file and line first; silent until a build records the paper; no fix, the link has to be looked up — [page](rules/bib/reachable-entry.md) |
25
- | `paper/cite-exists` | error | `paper.tex` → `_build/references.json` | an entry's doi or arXiv id resolves to nothing, or to a different work. Checked online by `paperlint build`; a work that was merely not found is not a finding |
26
- | `paper/refs-fresh` | error | `paper.tex` → `_build/references.json` | the databases bibtex opened changed since the build checked them (their SHA-256 differs) — run `npx paperlint build` |
27
- | `paper/refs-checked` | warn | `paper.tex` → `_build/references.json` | a build recorded the paper's databases and none has checked their references, or the last build could not (no network), or its bibtex typeset a key paperlint's reader did not read (an entry behind `%`, inside `@comment{…}`), or bibtex found a database outside the paper directory (`BIBINPUTS`) — so the two rules above did not run on it. Silent without a record of the build (`paper/sources-fresh` speaks) |
28
- | `tex/future-promise` | warn | `paper.tex` and its body files | a camera-ready build still says "will be released" about something already handed over |
29
- | `tex/acm-frontmatter-override` | error | `paper.tex` | an `acmart` build overrides ACM's front-matter commands and drops template elements from page 1 |
30
- | [`tex/template`](rules/tex/template.md) | error | `paper.tex` → `paperlint.json` | the `\documentclass` is not the preset's `template` class, or lacks an option it names — [page](rules/tex/template.md) |
31
- | [`tex/required-section`](rules/tex/required-section.md) | error | `paper.tex` → `paperlint.json` | a section the preset requires (AIDC: «LLM Usage Statement», last) is missing or not where it must be — [page](rules/tex/required-section.md) |
32
- | [`tex/heading-case`](rules/tex/heading-case.md) | optional; the AgenticDev preset sets `error` | `paper.tex` and the files it includes | a word in the paper's `\title` or in a `\section`, `\subsection`, `\subsubsection`, `\paragraph` or `\subparagraph` title is not in the style its scope asks: `{ "title", "headings", "levels"? }`, each `chicago-headline` (Conference Publishing's quote of the Chicago Manual of Style), `sentence` (first word only) or `any`. Words that are a preposition in one title and a particle in another (`up`, `on`, `in`) are not read mid-title. **Fixable:** `--fix` — [page](rules/tex/heading-case.md) |
33
- | [`format/layout-override`](rules/format/layout-override.md) | error | `paper.tex` → `paperlint.json` | the source changes the page layout the venue's template sets for the whole document — `geometry`, a layout `\setlength`, `\linespread`, `\baselinestretch` — [page](rules/format/layout-override.md) |
34
- | [`anonymity/identity`](rules/anonymity/identity.md) | error | `paper.tex` → `_build/paper.facts.json` | a double-blind venue (`"blind": true`) and no `identity` declared, or a declared name, handle, email or project in the PDF's text, metadata or link targets — [page](rules/anonymity/identity.md) |
35
- | [`tex/venue-leftover`](rules/tex/venue-leftover.md) | warn | `paper.tex` → `paperlint.json` | the text names another shipped venue than the one the paper extends («Submission to VenueA 2026» in a paper now set for VenueB) — [page](rules/tex/venue-leftover.md) |
36
- | [`tex/claim-provenance`](rules/tex/claim-provenance.md) | warn | `paper.tex` | a sentence of the body states a number and does not say whose it is — no citation or link, no _we_/_our_, no `\ref` or "Table 2", no sample ("n = 134", "48 runs") — [page](rules/tex/claim-provenance.md) |
37
- | [`tex/register`](rules/tex/register.md) | warn | `paper.tex` and the files it includes | the body runs to a post's register: more than 0.8 sentences per 1000 words open with _And_, _So_, _But_, _Nor_, _Or_ or _Yet_ — the limit is an option — [page](rules/tex/register.md) |
38
- | [`tex/contrast-frames`](rules/tex/contrast-frames.md) | warn | `paper.tex` and the files it includes → `paperlint.json` | contrast frames (_X, not Y_, _not X but Y_, _rather than_, _instead of_, _as opposed to_) per 10,000 words fall outside the band the venue preset's anchors set (AIDC: 0–10.4, from five accepted ACSAC papers); above it, each frame is reported too — [page](rules/tex/contrast-frames.md) |
39
- | [`tex/claim-emphasis`](rules/tex/claim-emphasis.md) | warn | `paper.tex` and the files it includes → `paperlint.json` | claims set in bold inside a sentence (six words or more, or holding a number; not a term, not a label opening a paragraph) per 10,000 words fall outside the preset's band (AIDC: 0–17.8); above it, each is reported too — [page](rules/tex/claim-emphasis.md) |
40
- | [`tex/relation-markers`](rules/tex/relation-markers.md) | warn | `paper.tex` and the files it includes → `paperlint.json` | words that name how a sentence relates to the one before (_because_, _therefore_, _however_, _for example_ …) per 10,000 words fall outside the preset's band (AIDC: 25.4–135.0) — the floor that catches relations left to infer — [page](rules/tex/relation-markers.md) |
41
- | `review/frontmatter` | error | `reviews/*.md` | the review's frontmatter fails paperlint's JSON Schema — a `findings` record without `id`/`status`, an unknown field, or an **open** finding with no `cause` (`skill-defect` · `missing-skill` · `hook` · `rule`). A review with no `findings` key is not checked for findings |
42
- | `sibling/frontmatter` | warn | `siblings/*.md` (not `README.md`) | a sibling card's frontmatter has no `read:` saying how much of the competing paper was read (`full` · `abstract` · `none`) |
43
- | `pdf/fresh` | error | `paper.tex` → `_build/paper.facts.json` | the paper names a venue and the facts cannot be judged: not JSON, a schema other than 3, or they describe a PDF that is gone or differs from the one on disk (its SHA-256) |
44
- | `pdf/profile` | error | `paper.tex` → `paperlint.json` | `paperlint.json` is not JSON or has an unknown key, its `extends` does not resolve (not found, a cycle, a chain longer than four, a preset that fails the schema, an npm name), it names no `kind` while the preset has kinds, or names a kind the preset does not have |
45
- | `pdf/fonts` | error | `paper.tex` → `_build/paper.facts.json` | a font the pages draw is Type 3 or not embedded, or no font starts with the family the venue preset names for body text (`fonts_text`) or headings (`fonts_title`) |
46
- | `pdf/geometry` | error | `paper.tex` → `_build/paper.facts.json` | the page width or height is more than `dimTol` (default 0.05 in) off the preset's, or the column count differs |
47
- | [`format/page-limit`](rules/format/page-limit.md) | error | `paper.tex` → `_build/paper.facts.json` | body or reference pages exceed the limits of the paper's kind. With `body_ends_at: "references"` (AIDC) the body ends at the references or the appendix, whichever comes first — found by hyperref's anchors, then the heading line — and the page it ends on counts when body text stands above the heading; with `appendix_in_body` too (MSR) the appendix is body wherever it sits and the reference pages are the ones holding only references; an end it cannot find, or signals that disagree, are reported — [page](rules/format/page-limit.md) |
48
- | [`pdf/body-size`](rules/pdf/body-size.md) | error | `paper.tex` → `_build/paper.facts.json` | the body font size is more than `body_pt_tol` off the preset's, or the reference font size is outside the preset's range widened by `body_pt_tol`. banal reads 0.3 pt above the size the glyphs are set in; checked on five built accepted papers (none reported) and three variants one size off (each reported) — [page](rules/pdf/body-size.md) |
49
- | `pdf/measured` | warn | `paper.tex` → `_build/paper.facts.json` | the paper's `paperlint.json` names no venue preset yet; or it names one and there are no facts (it was not built), or the facts carry no page geometry (banal was not found) — so the checks above did not run |
50
- | [`talk/profile`](rules/talk/profile.md) | error | `paper.tex` → `paperlint.json` | the paper declares `talk` and names no preset, the preset has no `talk` block, the mode is not one the venue allows, or the venue sets no talk slot for the paper's kind — [page](rules/talk/profile.md) |
51
- | [`talk/required-files`](rules/talk/required-files.md) | error | `paper.tex` → `paperlint.json`, the talk folder | a file the venue requires for the paper's talk mode (video, one-slide, captions…) is not in the talk folder — [page](rules/talk/required-files.md) |
52
- | [`talk/undeclared`](rules/talk/undeclared.md) | warn | `paper.tex` → the paper's `talk/` folder | an `.mp4` sits in `talk/` and the paper declares no `talk`, so nothing checks it — [page](rules/talk/undeclared.md) |
53
- | [`talk/duration`](rules/talk/duration.md) | error | `paper.tex` → the talk video | the video runs longer than the slot's `talk_s_max`, or its audio track is more than 1 s off the video's length — [page](rules/talk/duration.md) |
54
- | [`talk/duration-floor`](rules/talk/duration-floor.md) | warn | `paper.tex` → the talk video | the video runs shorter than the slot's `talk_s_min` — [page](rules/talk/duration-floor.md) |
55
- | [`talk/video-format`](rules/talk/video-format.md) | error | `paper.tex` → the talk video | the video is not ISO media, is QuickTime where the venue asks for MP4, is under `min_height_px`, or over `max_bytes` — [page](rules/talk/video-format.md) |
56
- | [`talk/one-slide-size`](rules/talk/one-slide-size.md) | error | `paper.tex` → the one-slide PNG | the one-slide image is not the size the paper or the preset states, or not a PNG — [page](rules/talk/one-slide-size.md) |
57
- | [`talk/captions-cover`](rules/talk/captions-cover.md) | warn | `paper.tex` → the captions and the video | the captions start late, end early or leave a long gap (uncalibrated thresholds) — [page](rules/talk/captions-cover.md) |
6
+ | Rule | Level | Reads | Fails when |
7
+ | --------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
8
+ | `paper/stages` | error | `PIPELINE-STATUS.md` | a declared stage's PDF is missing, or the `bytes:` it names does not match the file's real size, or a frozen PDF exists that no stage declares |
9
+ | `paper/source` | error | `PIPELINE-STATUS.md` | a declared stage has no frozen `.tex` beside its PDF (a commit hash does not count — squash and gc destroy it) |
10
+ | [`study/declare-sample`](rules/study/declare-sample.md) | experimental; off unless a paper turns it on (`"error"`) | each study's `repro/<study>/prereg.json` | a study's pre-registration does not declare its `population` (what the claim is about, source, size), the `strata` and what each of its `screens` removed per stratum value, its `coverage` (share 0–1 or null with a reason, and the strata it cannot reach), or the `author_check` of the drawn units (a doubtful verdict needs a recorded `decision`); reported on `PIPELINE-STATUS.md`, once per problem — [page](rules/study/declare-sample.md) |
11
+ | [`cycle/record`](rules/cycle/record.md) | error | `paperlint.json` (`cycles`) | the paper's attempts at venues (`cycles`, one entry per attempt) hold one source's deadlines out of order (registration ≤ submission ≤ resubmission < notification < camera-ready), or copy a portal reading the venue's preset carries; reported on `PIPELINE-STATUS.md`. A record that does not parse — a bare date, two open cycles, an unknown key — is refused by `paperlint lint` itself before any rule runs, exit 2 — [page](rules/cycle/record.md) |
12
+ | [`cycle/evidence`](rules/cycle/evidence.md) | error | `paperlint.json` (`cycles`) → the paper folder | a closed cycle (`accepted`, `rejected`, `withdrawn`) or a deadline's `override` names an `evidence` file — the decision mail, the reviews, the withdrawal note, the chairs' mail — that is not in the paper folder; reported on `PIPELINE-STATUS.md` — [page](rules/cycle/evidence.md) |
13
+ | [`cycle/stage`](rules/cycle/stage.md) | error | `PIPELINE-STATUS.md` → `paperlint.json` (`cycles`) | with `cycles` declared, a stage names no `cycle: <id>`, names one no cycle has, or a `submitted`/`camera-ready` stage belongs to a cycle whose `phase` is still `porting` — [page](rules/cycle/stage.md) |
14
+ | [`cycle/port-done`](rules/cycle/port-done.md) | error | `paper.tex` → `paperlint.json` (`cycles`) → the preset's `template` | the current cycle still declares `"phase": "porting"` (which silences the venue's format rules) while the `\documentclass` is already the venue's template: the port is done, drop the phase — [page](rules/cycle/port-done.md) |
15
+ | [`paper/folder-venue-leftover`](rules/paper/folder-venue-leftover.md) | warn | the folder name → `paperlint.json` | the paper's folder is named after another shipped venue than the one it extends (`papers/aisec-2026/` for a paper now set for AIDC); reported on `PIPELINE-STATUS.md` — [page](rules/paper/folder-venue-leftover.md) |
16
+ | [`paper/allowed-files-only`](rules/paper/allowed-files-only.md) | error | the paper folder's root listing → `paperlint.json` | a file or folder at the paper's root is not part of the layout (`PLAN.md`, `archive/`, a stray PDF); reported on `PIPELINE-STATUS.md`, once per entry, with where it goes; `extra/` takes anything — [page](rules/paper/allowed-files-only.md) |
17
+ | [`repro/allowed-files-only`](rules/repro/allowed-files-only.md) | error | the listing of the paper's `repro/` | an entry at the top of `repro/` is not `README.md`, `studies.json`, `.gitignore`, `tools/`, `data/` or a study folder named `<id>-<slug>` (or, in a paper without the registry, `<slug>-YYYY-MM-DD`), or a study folder has no `README.md`; reported on `PIPELINE-STATUS.md`, once per entry, with where it goes by kind (a script → `repro/tools/`, data → `repro/data/`, a note → its study or `research/`) — [page](rules/repro/allowed-files-only.md) |
18
+ | [`repro/study-registered`](rules/repro/study-registered.md) | error | the listing of the paper's `repro/`, `repro/studies.json`, `repro/README.md`, each study's `plan.json` | a study folder with no row in `repro/studies.json`, a row whose folder is missing or not `<id>-<slug>`, a bad or repeated id, a title over 8 words, `done` without a result and an existing result file, `superseded` without an existing successor, a row naming a measurement the registry does not declare, a measurement with no study or an unknown requirement, a headline measurement not approved by the author, a plan that is missing, malformed or — once the study runs — unfilled, a headline study that has run without the author's check in its plan, or the index in `repro/README.md` out of sync with the registry; `paperlint studies new` and `paperlint studies index` write both — [page](rules/repro/study-registered.md) |
19
+ | [`repro/study-plan-name`](rules/repro/study-plan-name.md) | warn | `repro/studies.json` | a study's plan still named `prereg.json`, its old name: read as the plan, and to be renamed `plan.json` — [page](rules/repro/study-plan-name.md) |
20
+ | [`paper/sources-fresh`](rules/paper/sources-fresh.md) | warn | `paper.tex` → `_build/sources.json` | the paper has not been built (no record of the files TeX read), or a file changed since the last build (its SHA-256 differs) — run `npx paperlint build` |
21
+ | `paper/author-list` | error | `paper.tex` → `_build/references.json`, `_build/sources.json` | an entry's authors are not those of the version it cites — typically the arXiv preprint's list under a published venue. Checked online by `paperlint build` (DBLP), reported on the entry's own line (or at the top of `paper.tex`, naming the entry's file); silent without a current build record |
22
+ | `paper/section-word` | warn | `paper.tex` and its body files | `§` or `\S\ref` instead of the word Section. **Fixable:** `--fix` writes `Section~\ref{…}` / `Section 5` |
23
+ | `paper/leading-zero` | warn | `paper.tex` and its body files | a decimal below 1 without its leading zero (`.05` instead of `0.05`, IEEE / ISO 80000-1 style), counted only where the reader sees it — prose, math and table cells, not macro options, column specs, comments, code or tikz. A number right after `¶` or `§` (`¶¶.42`) is a paragraph designator and is not reported; in a paragraph that cites numbers that way, another bare decimal is reported without a fix, with the zero as a suggestion. **Fixable** |
24
+ | `paper/figure-ref-style` | warn | `paper.tex` and its body files | `Fig.~\ref` and `Figure~\ref` mixed in one document; each minority occurrence is reported. **Fixable:** `--fix` writes the majority form |
25
+ | [`bib/reachable-entry`](rules/bib/reachable-entry.md) | warn | `paper.tex` → `_build/sources.json`: the databases the last build's bibtex opened | an entry has no doi, url or arXiv id — a reader has nothing to follow. Reported on the entry in the `filecontents` block of `paper.tex` that wrote its `.bib`, or at the top of `paper.tex` with the entry's file and line first; silent until a build records the paper; no fix, the link has to be looked up — [page](rules/bib/reachable-entry.md) |
26
+ | `paper/cite-exists` | error | `paper.tex` → `_build/references.json` | an entry's doi or arXiv id resolves to nothing, or to a different work. Checked online by `paperlint build`; a work that was merely not found is not a finding |
27
+ | `paper/refs-fresh` | error | `paper.tex` → `_build/references.json` | the databases bibtex opened changed since the build checked them (their SHA-256 differs) — run `npx paperlint build` |
28
+ | `paper/refs-checked` | warn | `paper.tex` → `_build/references.json` | a build recorded the paper's databases and none has checked their references, or the last build could not (no network), or its bibtex typeset a key paperlint's reader did not read (an entry behind `%`, inside `@comment{…}`), or bibtex found a database outside the paper directory (`BIBINPUTS`) — so the two rules above did not run on it. Silent without a record of the build (`paper/sources-fresh` speaks) |
29
+ | `tex/future-promise` | warn | `paper.tex` and its body files | a camera-ready build still says "will be released" about something already handed over |
30
+ | `tex/acm-frontmatter-override` | error | `paper.tex` | an `acmart` build overrides ACM's front-matter commands and drops template elements from page 1 |
31
+ | [`tex/template`](rules/tex/template.md) | error | `paper.tex` → `paperlint.json` | the `\documentclass` is not the preset's `template` class, or lacks an option it names — [page](rules/tex/template.md) |
32
+ | [`tex/required-section`](rules/tex/required-section.md) | error | `paper.tex` → `paperlint.json` | a section the preset requires (AIDC: «LLM Usage Statement», last) is missing or not where it must be — [page](rules/tex/required-section.md) |
33
+ | [`tex/heading-case`](rules/tex/heading-case.md) | optional; the AgenticDev preset sets `error` | `paper.tex` and the files it includes | a word in the paper's `\title` or in a `\section`, `\subsection`, `\subsubsection`, `\paragraph` or `\subparagraph` title is not in the style its scope asks: `{ "title", "headings", "levels"? }`, each `chicago-headline` (Conference Publishing's quote of the Chicago Manual of Style), `sentence` (first word only) or `any`. Words that are a preposition in one title and a particle in another (`up`, `on`, `in`) are not read mid-title. **Fixable:** `--fix` — [page](rules/tex/heading-case.md) |
34
+ | [`format/layout-override`](rules/format/layout-override.md) | error | `paper.tex` → `paperlint.json` | the source changes the page layout the venue's template sets for the whole document — `geometry`, a layout `\setlength`, `\linespread`, `\baselinestretch` — [page](rules/format/layout-override.md) |
35
+ | [`anonymity/identity`](rules/anonymity/identity.md) | error | `paper.tex` → `_build/paper.facts.json` | a double-blind venue (`"blind": true`) and no `identity` declared, or a declared name, handle, email or project in the PDF's text, metadata or link targets — [page](rules/anonymity/identity.md) |
36
+ | [`tex/venue-leftover`](rules/tex/venue-leftover.md) | warn | `paper.tex` → `paperlint.json` | the text names another shipped venue than the one the paper extends («Submission to VenueA 2026» in a paper now set for VenueB) — [page](rules/tex/venue-leftover.md) |
37
+ | [`tex/claim-provenance`](rules/tex/claim-provenance.md) | warn | `paper.tex` | a sentence of the body states a number and does not say whose it is — no citation or link, no _we_/_our_, no `\ref` or "Table 2", no sample ("n = 134", "48 runs") — [page](rules/tex/claim-provenance.md) |
38
+ | [`tex/register`](rules/tex/register.md) | warn | `paper.tex` and the files it includes | the body runs to a post's register: more than 0.8 sentences per 1000 words open with _And_, _So_, _But_, _Nor_, _Or_ or _Yet_ — the limit is an option — [page](rules/tex/register.md) |
39
+ | [`tex/contrast-frames`](rules/tex/contrast-frames.md) | warn | `paper.tex` and the files it includes → `paperlint.json` | contrast frames (_X, not Y_, _not X but Y_, _rather than_, _instead of_, _as opposed to_) per 10,000 words fall outside the band the venue preset's anchors set (AIDC: 0–10.4, from five accepted ACSAC papers); above it, each frame is reported too — [page](rules/tex/contrast-frames.md) |
40
+ | [`tex/claim-emphasis`](rules/tex/claim-emphasis.md) | warn | `paper.tex` and the files it includes → `paperlint.json` | claims set in bold inside a sentence (six words or more, or holding a number; not a term, not a label opening a paragraph) per 10,000 words fall outside the preset's band (AIDC: 0–17.8); above it, each is reported too — [page](rules/tex/claim-emphasis.md) |
41
+ | [`tex/relation-markers`](rules/tex/relation-markers.md) | warn | `paper.tex` and the files it includes → `paperlint.json` | words that name how a sentence relates to the one before (_because_, _therefore_, _however_, _for example_ …) per 10,000 words fall outside the preset's band (AIDC: 25.4–135.0) — the floor that catches relations left to infer — [page](rules/tex/relation-markers.md) |
42
+ | `review/frontmatter` | error | `reviews/*.md` | the review's frontmatter fails paperlint's JSON Schema — a `findings` record without `id`/`status`, an unknown field, or an **open** finding with no `cause` (`skill-defect` · `missing-skill` · `hook` · `rule`). A review with no `findings` key is not checked for findings |
43
+ | `sibling/frontmatter` | warn | `siblings/*.md` (not `README.md`) | a sibling card's frontmatter has no `read:` saying how much of the competing paper was read (`full` · `abstract` · `none`) |
44
+ | `pdf/fresh` | error | `paper.tex` → `_build/paper.facts.json` | the paper names a venue and the facts cannot be judged: not JSON, a schema other than 3, or they describe a PDF that is gone or differs from the one on disk (its SHA-256) |
45
+ | `pdf/profile` | error | `paper.tex` → `paperlint.json` | `paperlint.json` is not JSON or has an unknown key, its `extends` does not resolve (not found, a cycle, a chain longer than four, a preset that fails the schema, an npm name), it names no `kind` while the preset has kinds, or names a kind the preset does not have |
46
+ | `pdf/fonts` | error | `paper.tex` → `_build/paper.facts.json` | a font the pages draw is Type 3 or not embedded, or no font starts with the family the venue preset names for body text (`fonts_text`) or headings (`fonts_title`) |
47
+ | `pdf/geometry` | error | `paper.tex` → `_build/paper.facts.json` | the page width or height is more than `dimTol` (default 0.05 in) off the preset's, or the column count differs |
48
+ | [`format/page-limit`](rules/format/page-limit.md) | error | `paper.tex` → `_build/paper.facts.json` | body or reference pages exceed the limits of the paper's kind. With `body_ends_at: "references"` (AIDC) the body ends at the references or the appendix, whichever comes first — found by hyperref's anchors, then the heading line — and the page it ends on counts when body text stands above the heading; with `appendix_in_body` too (MSR) the appendix is body wherever it sits and the reference pages are the ones holding only references; an end it cannot find, or signals that disagree, are reported — [page](rules/format/page-limit.md) |
49
+ | [`pdf/body-size`](rules/pdf/body-size.md) | error | `paper.tex` → `_build/paper.facts.json` | the body font size is more than `body_pt_tol` off the preset's, or the reference font size is outside the preset's range widened by `body_pt_tol`. banal reads 0.3 pt above the size the glyphs are set in; checked on five built accepted papers (none reported) and three variants one size off (each reported) — [page](rules/pdf/body-size.md) |
50
+ | `pdf/measured` | warn | `paper.tex` → `_build/paper.facts.json` | the paper's `paperlint.json` names no venue preset yet; or it names one and there are no facts (it was not built), or the facts carry no page geometry (banal was not found) — so the checks above did not run |
51
+ | [`talk/profile`](rules/talk/profile.md) | error | `paper.tex` → `paperlint.json` | the paper declares `talk` and names no preset, the preset has no `talk` block, the mode is not one the venue allows, or the venue sets no talk slot for the paper's kind — [page](rules/talk/profile.md) |
52
+ | [`talk/required-files`](rules/talk/required-files.md) | error | `paper.tex` → `paperlint.json`, the talk folder | a file the venue requires for the paper's talk mode (video, one-slide, captions…) is not in the talk folder — [page](rules/talk/required-files.md) |
53
+ | [`talk/undeclared`](rules/talk/undeclared.md) | warn | `paper.tex` → the paper's `talk/` folder | an `.mp4` sits in `talk/` and the paper declares no `talk`, so nothing checks it — [page](rules/talk/undeclared.md) |
54
+ | [`talk/duration`](rules/talk/duration.md) | error | `paper.tex` → the talk video | the video runs longer than the slot's `talk_s_max`, or its audio track is more than 1 s off the video's length — [page](rules/talk/duration.md) |
55
+ | [`talk/duration-floor`](rules/talk/duration-floor.md) | warn | `paper.tex` → the talk video | the video runs shorter than the slot's `talk_s_min` — [page](rules/talk/duration-floor.md) |
56
+ | [`talk/video-format`](rules/talk/video-format.md) | error | `paper.tex` → the talk video | the video is not ISO media, is QuickTime where the venue asks for MP4, is under `min_height_px`, or over `max_bytes` — [page](rules/talk/video-format.md) |
57
+ | [`talk/one-slide-size`](rules/talk/one-slide-size.md) | error | `paper.tex` → the one-slide PNG | the one-slide image is not the size the paper or the preset states, or not a PNG — [page](rules/talk/one-slide-size.md) |
58
+ | [`talk/captions-cover`](rules/talk/captions-cover.md) | warn | `paper.tex` → the captions and the video | the captions start late, end early or leave a long gap (uncalibrated thresholds) — [page](rules/talk/captions-cover.md) |
58
59
 
59
60
  **Body files** are the files of the paper's directory that TeX read after `\begin{document}` in the
60
61
  last build, as `paperlint build` recorded them in `_build/sources.json` — not a preamble file, not a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paperlint",
3
- "version": "10.2.0",
3
+ "version": "10.3.0",
4
4
  "description": "A linter for scientific papers written in LaTeX: catches mistakes before you submit to a conference or journal.",
5
5
  "type": "module",
6
6
  "license": "MIT",