paperlint 10.1.1 → 10.2.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 (70) hide show
  1. package/README.md +2 -0
  2. package/dist/adapters/study-registry/index.d.ts +6 -0
  3. package/dist/adapters/study-registry/index.d.ts.map +1 -0
  4. package/dist/adapters/study-registry/index.js +58 -0
  5. package/dist/adapters/study-registry/index.js.map +1 -0
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +40 -2
  8. package/dist/cli.js.map +1 -1
  9. package/dist/domain/repro-layout.d.ts +12 -4
  10. package/dist/domain/repro-layout.d.ts.map +1 -1
  11. package/dist/domain/repro-layout.js +49 -18
  12. package/dist/domain/repro-layout.js.map +1 -1
  13. package/dist/domain/scaffold-layout.d.ts +4 -3
  14. package/dist/domain/scaffold-layout.d.ts.map +1 -1
  15. package/dist/domain/scaffold-layout.js +7 -4
  16. package/dist/domain/scaffold-layout.js.map +1 -1
  17. package/dist/domain/study-registry.d.ts +176 -0
  18. package/dist/domain/study-registry.d.ts.map +1 -0
  19. package/dist/domain/study-registry.js +371 -0
  20. package/dist/domain/study-registry.js.map +1 -0
  21. package/dist/ports/study-registry-codec.d.ts +11 -0
  22. package/dist/ports/study-registry-codec.d.ts.map +1 -0
  23. package/dist/ports/study-registry-codec.js +2 -0
  24. package/dist/ports/study-registry-codec.js.map +1 -0
  25. package/dist/repro-layout-rule.d.ts.map +1 -1
  26. package/dist/repro-layout-rule.js +4 -3
  27. package/dist/repro-layout-rule.js.map +1 -1
  28. package/dist/studies.d.ts +27 -0
  29. package/dist/studies.d.ts.map +1 -0
  30. package/dist/studies.js +160 -0
  31. package/dist/studies.js.map +1 -0
  32. package/dist/study-registry-rule.d.ts +17 -0
  33. package/dist/study-registry-rule.d.ts.map +1 -0
  34. package/dist/study-registry-rule.js +115 -0
  35. package/dist/study-registry-rule.js.map +1 -0
  36. package/docs/design/study-registry.md +130 -0
  37. package/docs/layout.md +47 -44
  38. package/docs/rules/paper/allowed-files-only.md +3 -2
  39. package/docs/rules/repro/allowed-files-only.md +40 -30
  40. package/docs/rules/repro/study-registered.md +162 -0
  41. package/docs/rules/study/declare-sample.md +1 -1
  42. package/docs/rules.md +2 -1
  43. package/package.json +1 -1
  44. package/skills/build-benchmark/SKILL.md +20 -1
  45. package/skills/build-benchmark/SKILL.md.spec.ts +19 -0
  46. package/skills/build-label-page/SKILL.md +7 -4
  47. package/skills/build-label-page/SKILL.md.spec.ts +6 -3
  48. package/skills/paper-pipeline/references/artifact-checklist.md +2 -1
  49. package/skills/paper-pipeline/references/pipeline-status-template.md +5 -3
  50. package/skills/run-agent-grid/SKILL.md +8 -1
  51. package/skills/run-agent-grid/SKILL.md.spec.ts +7 -0
  52. package/src/adapters/study-registry/index.test.ts +144 -0
  53. package/src/adapters/study-registry/index.ts +70 -0
  54. package/src/cli.ts +51 -2
  55. package/src/domain/paper-layout.test.ts +1 -1
  56. package/src/domain/repro-layout.test.ts +18 -9
  57. package/src/domain/repro-layout.ts +58 -18
  58. package/src/domain/scaffold-layout.ts +7 -4
  59. package/src/domain/study-registry.test.ts +657 -0
  60. package/src/domain/study-registry.ts +598 -0
  61. package/src/folder-layout-hook.test.ts +10 -0
  62. package/src/new-paper.test.ts +28 -2
  63. package/src/ports/study-registry-codec.ts +11 -0
  64. package/src/repro-layout-rule.ts +4 -3
  65. package/src/rule-cases.test.ts +41 -0
  66. package/src/studies-cli.test.ts +113 -0
  67. package/src/studies.test.ts +353 -0
  68. package/src/studies.ts +259 -0
  69. package/src/study-registry-rule.test.ts +317 -0
  70. package/src/study-registry-rule.ts +172 -0
package/docs/layout.md CHANGED
@@ -15,14 +15,14 @@ flowchart TB
15
15
  S1["<b>1 · idea</b><br/>research-ideate · map-prior-work<br/>analyze-sibling-paper<br/>writes: siblings/<br/>checked by: sibling/frontmatter"]
16
16
  S2["<b>2 · venue</b><br/>find-venue · plan-paper-timeline<br/>study-accepted-papers, mode A or B<br/>writes: paperlint.json, a report in reviews/<br/>checked by: review/frontmatter"]
17
17
  V[("<b>#lt;papers-root#gt;/venues/#lt;venue#gt;/</b><br/>beside the papers, not in one:<br/>the venue's accepted papers,<br/>shared by every paper aimed at it<br/>checked by: nothing")]
18
- subgraph S3["3 · study: repro/#lt;slug#gt;-YYYY-MM-DD/"]
18
+ subgraph S3["3 · study: repro/#lt;id#gt;-#lt;slug#gt;/"]
19
19
  direction TB
20
- D["<b>design</b> · build-benchmark<br/>writes: the study's README.md<br/>checked by: repro/allowed-files-only"]
20
+ D["<b>design</b> · build-benchmark<br/>paperlint studies new<br/>writes: the study's folder, README.md,<br/>its row in repro/studies.json<br/>checked by: repro/allowed-files-only,<br/>repro/study-registered"]
21
21
  P["<b>pre-register</b> · build-benchmark<br/>writes: prereg.json, before the draw<br/>checked by: study/declare-sample (experimental, off)"]
22
22
  A["<b>author check</b> · you see the drawn units<br/>writes: author_check in prereg.json<br/>checked by: study/declare-sample (experimental, off)"]
23
23
  R["<b>run</b> · your scripts<br/>writes: the study folder,<br/>repro/tools/, repro/data/<br/>checked by: repro/allowed-files-only"]
24
24
  L["<b>labels</b> · build-label-page<br/>a phone page; the answers go<br/>into the study's data/"]
25
- Y["<b>analysis</b> · build-benchmark<br/>writes: the numbers, NOTES.md"]
25
+ Y["<b>analysis</b> · build-benchmark<br/>writes: the numbers, NOTES.md,<br/>the study's row: status, result<br/>then paperlint studies index<br/>checked by: repro/study-registered"]
26
26
  D --> P --> A --> R --> L --> Y
27
27
  end
28
28
  S4["<b>4 · draft</b><br/>draft-paper · argument-arc · tighten-paper<br/>writes: paper.tex, sections/, figures/<br/>checked by: the tex/* rules<br/>hook paper-edit-guard: no shell writes"]
@@ -69,11 +69,12 @@ creates one.
69
69
  ├── siblings/ ← the cards of competing papers
70
70
  ├── research/ ← dated research and decision records
71
71
  ├── repro/ ← the reproduction artifact
72
- │ ├── README.md ← the index: one line per study
72
+ │ ├── README.md ← the index: one line per study, generated from studies.json
73
+ │ ├── studies.json ← the study registry: id, title, status and result of each study
73
74
  │ ├── .gitignore ← keeps the scripts' byproducts out of git
74
75
  │ ├── tools/ ← scripts used across the paper (numbers, figures, anonymity check, harnesses)
75
76
  │ ├── data/ ← shared data (tables, caches)
76
- │ └── <slug>-YYYY-MM-DD/ ← one folder per study or spike (<slug>-YYYY-MM for one that ran over weeks)
77
+ │ └── <id>-<slug>/ ← one folder per study or spike, named by its row in studies.json (P1-S03-baseline)
77
78
  │ └── README.md ← required: what it asks, how to run it, what it found
78
79
  ├── figures/ ← graphics and their sources
79
80
  ├── sections/ ← the body, when it is split
@@ -97,28 +98,29 @@ Both are explained in [`venues.md`](venues.md).
97
98
  "Stage" is the stage of [the pipeline](#the-flow) that writes the place first.
98
99
  "Checked by" is the rule `paperlint lint` runs over it; "nothing" means no rule reads its content.
99
100
 
100
- | place | stage | written by | checked by |
101
- | ------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
102
- | `<papers-root>/venues/<venue>/` | 2 venue | `study-accepted-papers`, its first run for that venue (mode A, usually) | nothing ([why](venues.md#what-checks-it)) |
103
- | the paper folder's root | before 1 | `paperlint new`: the three files, and an empty `.gitkeep` folder for `reviews/`, `research/`, `figures/`, `versions/`, `submission/`, `repro/tools/`, `repro/data/` (`repro/` gets a stub index) | [`paper/allowed-files-only`](rules/paper/allowed-files-only.md); the hook `paper-folder-layout` refuses a new stray entry |
104
- | `PIPELINE-STATUS.md` | every stage | `paperlint new`, then `paper-pipeline`, `paper-status`; `study-accepted-papers` (the scorecard) | `paper/stages`, `paper/source`, [`cycle/stage`](rules/cycle/stage.md) ([rules](rules.md)) |
105
- | `paperlint.json` | before 1, then 2 | `paperlint new`; `find-venue` names the venue | `paperlint lint` refuses an unknown key; [`cycle/record`](rules/cycle/record.md) |
106
- | `siblings/` | 1 idea | `map-prior-work`, `analyze-sibling-paper` | `sibling/frontmatter` ([records](configuration.md#records-in-frontmatter-checked-by-shipped-json-schemas)) |
107
- | `reviews/` | 2, then 4–5 | `study-accepted-papers` (its report), `grade-paper-writing`, `cold-read-diff`, the review skills | `review/frontmatter` ([records](configuration.md#records-in-frontmatter-checked-by-shipped-json-schemas)) |
108
- | `research/` | any | you and the skills: dated research and decision records | nothing |
109
- | `repro/`, its top | 3 study | `build-benchmark`, your scripts | [`repro/allowed-files-only`](rules/repro/allowed-files-only.md); the hook `paper-folder-layout` refuses a new loose entry |
110
- | `repro/<slug>-YYYY-MM-DD/` | 3 study | `build-benchmark` (the design, in its `README.md`) | [`repro/allowed-files-only`](rules/repro/allowed-files-only.md): the dated name, and a `README.md` in it |
111
- | `repro/<slug>-YYYY-MM-DD/prereg.json` | 3 study | `build-benchmark`, before the sample is drawn; you add the author's check | [`study/declare-sample`](rules/study/declare-sample.md) |
112
- | `repro/<slug>-YYYY-MM-DD/data/` | 3 study | `build-label-page` exports the labels here | nothing |
113
- | `repro/tools/`, `repro/data/` | 3 study | your scripts; `paperlint build` keeps `repro/data/references-cache.json` | nothing inside them |
114
- | `NOTES.md` | 3, then 4 | you: each number → the data it comes from; `build-label-page` adds a line per label file | nothing |
115
- | `paper.tex`, `sections/`, `*.bib` | 4 draft | `draft-paper`, `argument-arc`, `tighten-paper` | the `tex/*`, `paper/*` and `bib/*` rules ([rules](rules.md)); the hook `paper-edit-guard` refuses a shell write |
116
- | `CLAIMS.md`, `figures/` | 4 draft | you | nothing |
117
- | `paper.pdf`, `_build/` | 6 submit | `paperlint build` | `pdf/*`, [`format/page-limit`](rules/format/page-limit.md), [`anonymity/identity`](rules/anonymity/identity.md), the reference rules |
118
- | `versions/` | 6, then 7 | you, at submission and at camera-ready (`submit-paper` says how): the frozen PDF and source of each stage | `paper/stages`, `paper/source` |
119
- | `submission/`, `SUBMIT-CHECKLIST.md` | 6 submit | you, while `submit-paper` and `submission-portal` do the submitting: forms, portal records, mail with the venue | [`cycle/evidence`](rules/cycle/evidence.md), for the files a closed cycle names |
120
- | `talk/` | 7 camera-ready | you | the `talk/*` rules ([talk](talk.md)) |
121
- | `extra/` | any | you | nothing, ever |
101
+ | place | stage | written by | checked by |
102
+ | ------------------------------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
103
+ | `<papers-root>/venues/<venue>/` | 2 venue | `study-accepted-papers`, its first run for that venue (mode A, usually) | nothing ([why](venues.md#what-checks-it)) |
104
+ | the paper folder's root | before 1 | `paperlint new`: the three files, and an empty `.gitkeep` folder for `reviews/`, `research/`, `figures/`, `versions/`, `submission/`, `repro/tools/`, `repro/data/` (`repro/` gets a stub index) | [`paper/allowed-files-only`](rules/paper/allowed-files-only.md); the hook `paper-folder-layout` refuses a new stray entry |
105
+ | `PIPELINE-STATUS.md` | every stage | `paperlint new`, then `paper-pipeline`, `paper-status`; `study-accepted-papers` (the scorecard) | `paper/stages`, `paper/source`, [`cycle/stage`](rules/cycle/stage.md) ([rules](rules.md)) |
106
+ | `paperlint.json` | before 1, then 2 | `paperlint new`; `find-venue` names the venue | `paperlint lint` refuses an unknown key; [`cycle/record`](rules/cycle/record.md) |
107
+ | `siblings/` | 1 idea | `map-prior-work`, `analyze-sibling-paper` | `sibling/frontmatter` ([records](configuration.md#records-in-frontmatter-checked-by-shipped-json-schemas)) |
108
+ | `reviews/` | 2, then 4–5 | `study-accepted-papers` (its report), `grade-paper-writing`, `cold-read-diff`, the review skills | `review/frontmatter` ([records](configuration.md#records-in-frontmatter-checked-by-shipped-json-schemas)) |
109
+ | `research/` | any | you and the skills: dated research and decision records | nothing |
110
+ | `repro/`, its top | 3 study | `build-benchmark`, your scripts | [`repro/allowed-files-only`](rules/repro/allowed-files-only.md); the hook `paper-folder-layout` refuses a new loose entry |
111
+ | `repro/studies.json` | 3 study | `paperlint studies new` appends a row; you edit a row when its study ends; `paperlint studies index` regenerates the index in `repro/README.md` | [`repro/study-registered`](rules/repro/study-registered.md): a row per study folder, its fields, the index in sync |
112
+ | `repro/<id>-<slug>/` | 3 study | `paperlint studies new` (the folder and its `README.md`), then `build-benchmark` (the design, in its `README.md`) | [`repro/allowed-files-only`](rules/repro/allowed-files-only.md): the name, and a `README.md` in it; [`repro/study-registered`](rules/repro/study-registered.md): its row |
113
+ | `repro/<id>-<slug>/prereg.json` | 3 study | `build-benchmark`, before the sample is drawn; you add the author's check | [`study/declare-sample`](rules/study/declare-sample.md) |
114
+ | `repro/<id>-<slug>/data/` | 3 study | `build-label-page` exports the labels here | nothing |
115
+ | `repro/tools/`, `repro/data/` | 3 study | your scripts; `paperlint build` keeps `repro/data/references-cache.json` | nothing inside them |
116
+ | `NOTES.md` | 3, then 4 | you: each number → the data it comes from; `build-label-page` adds a line per label file | nothing |
117
+ | `paper.tex`, `sections/`, `*.bib` | 4 draft | `draft-paper`, `argument-arc`, `tighten-paper` | the `tex/*`, `paper/*` and `bib/*` rules ([rules](rules.md)); the hook `paper-edit-guard` refuses a shell write |
118
+ | `CLAIMS.md`, `figures/` | 4 draft | you | nothing |
119
+ | `paper.pdf`, `_build/` | 6 submit | `paperlint build` | `pdf/*`, [`format/page-limit`](rules/format/page-limit.md), [`anonymity/identity`](rules/anonymity/identity.md), the reference rules |
120
+ | `versions/` | 6, then 7 | you, at submission and at camera-ready (`submit-paper` says how): the frozen PDF and source of each stage | `paper/stages`, `paper/source` |
121
+ | `submission/`, `SUBMIT-CHECKLIST.md` | 6 submit | you, while `submit-paper` and `submission-portal` do the submitting: forms, portal records, mail with the venue | [`cycle/evidence`](rules/cycle/evidence.md), for the files a closed cycle names |
122
+ | `talk/` | 7 camera-ready | you | the `talk/*` rules ([talk](talk.md)) |
123
+ | `extra/` | any | you | nothing, ever |
122
124
 
123
125
  ## What runs when
124
126
 
@@ -132,23 +134,24 @@ A check runs at one of four moments:
132
134
  The lint-only job skips what needs a build (pages, fonts, references).
133
135
  - **On demand** — a skill, when you ask for what it does. A skill stops nothing; it writes.
134
136
 
135
- | what | kind | when it runs | what it stops |
136
- | ------------------------------------------------------------------------------------------- | ----- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
137
- | `paper-folder-layout` | hook | before an agent creates a file (Edit, Write) | a new entry the layout does not allow, at a paper's root or the top of its `repro/`; never an existing file |
138
- | `paper-edit-guard` | hook | before an agent's shell command | a shell command that writes or moves a paper's source, which would skip the hooks that run after an edit |
139
- | `paper-skills-nudge` | hook | after an agent edits a paper's source | nothing: it shows the agent the pre-submit checklist |
140
- | `paper-status-gates` | hook | after an agent edits a paper's source | nothing: it prints the paper's readiness and its unrun gates, for a person reading the transcript |
141
- | [`paper/allowed-files-only`](rules/paper/allowed-files-only.md) | rule | every `paperlint lint`, and CI | a stray file or folder at a paper's root (error) |
142
- | [`repro/allowed-files-only`](rules/repro/allowed-files-only.md) | rule | every `paperlint lint`, and CI | a loose entry at the top of `repro/`, an undated study folder, a study folder without `README.md` (error) |
143
- | [`study/declare-sample`](rules/study/declare-sample.md) | rule | every `paperlint lint`, and CI, once a paper turns it on | a study's `prereg.json` without its population, strata, screens, coverage or the author's check (experimental, off by default) |
144
- | [`paper/folder-venue-leftover`](rules/paper/folder-venue-leftover.md) | rule | every `paperlint lint`, and CI | nothing: it warns when the folder is named after another venue |
145
- | `review/frontmatter`, `sibling/frontmatter` | rule | every `paperlint lint`, and CI | a review whose findings break the schema (error); a sibling card that does not say how much was read (warning) |
146
- | `paper/stages`, `paper/source` | rule | every `paperlint lint`, and CI | a stage whose frozen PDF or source is missing or changed (error) |
147
- | the rules on the source (`tex/*`, `paper/*`, `bib/*`) | rule | every `paperlint lint`, and CI | what each one's [row](rules.md) says |
148
- | the rules on what `paperlint build` measured (`pdf/*`, `format/page-limit`, the references) | rule | every `paperlint lint` after a build; CI's full job | a PDF over the page limit, in the wrong font or format, or with a bad reference |
149
- | `study-accepted-papers` | skill | when you ask; stage 2 (modes A, B) and stage 5 (modes C, D) | nothing: it fills the venue's corpus, and writes the strong-accept scorecard in `PIPELINE-STATUS.md` |
150
- | `build-benchmark` | skill | when you ask; stage 3 | nothing: it writes the study's design and its `prereg.json` |
151
- | `build-label-page` | skill | when you ask; stage 3 | nothing: it builds the labelling page, and exports the answers into the study's `data/` |
137
+ | what | kind | when it runs | what it stops |
138
+ | ------------------------------------------------------------------------------------------- | ----- | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
139
+ | `paper-folder-layout` | hook | before an agent creates a file (Edit, Write) | a new entry the layout does not allow, at a paper's root or the top of its `repro/`; never an existing file |
140
+ | `paper-edit-guard` | hook | before an agent's shell command | a shell command that writes or moves a paper's source, which would skip the hooks that run after an edit |
141
+ | `paper-skills-nudge` | hook | after an agent edits a paper's source | nothing: it shows the agent the pre-submit checklist |
142
+ | `paper-status-gates` | hook | after an agent edits a paper's source | nothing: it prints the paper's readiness and its unrun gates, for a person reading the transcript |
143
+ | [`paper/allowed-files-only`](rules/paper/allowed-files-only.md) | rule | every `paperlint lint`, and CI | a stray file or folder at a paper's root (error) |
144
+ | [`repro/allowed-files-only`](rules/repro/allowed-files-only.md) | rule | every `paperlint lint`, and CI | a loose entry at the top of `repro/`, a folder that is not a study's, a study folder without `README.md` (error) |
145
+ | [`repro/study-registered`](rules/repro/study-registered.md) | rule | every `paperlint lint`, and CI | a study folder without its row in `repro/studies.json`, a row that does not match, an index out of sync (error) |
146
+ | [`study/declare-sample`](rules/study/declare-sample.md) | rule | every `paperlint lint`, and CI, once a paper turns it on | a study's `prereg.json` without its population, strata, screens, coverage or the author's check (experimental, off by default) |
147
+ | [`paper/folder-venue-leftover`](rules/paper/folder-venue-leftover.md) | rule | every `paperlint lint`, and CI | nothing: it warns when the folder is named after another venue |
148
+ | `review/frontmatter`, `sibling/frontmatter` | rule | every `paperlint lint`, and CI | a review whose findings break the schema (error); a sibling card that does not say how much was read (warning) |
149
+ | `paper/stages`, `paper/source` | rule | every `paperlint lint`, and CI | a stage whose frozen PDF or source is missing or changed (error) |
150
+ | the rules on the source (`tex/*`, `paper/*`, `bib/*`) | rule | every `paperlint lint`, and CI | what each one's [row](rules.md) says |
151
+ | the rules on what `paperlint build` measured (`pdf/*`, `format/page-limit`, the references) | rule | every `paperlint lint` after a build; CI's full job | a PDF over the page limit, in the wrong font or format, or with a bad reference |
152
+ | `study-accepted-papers` | skill | when you ask; stage 2 (modes A, B) and stage 5 (modes C, D) | nothing: it fills the venue's corpus, and writes the strong-accept scorecard in `PIPELINE-STATUS.md` |
153
+ | `build-benchmark` | skill | when you ask; stage 3 | nothing: it registers the study with `paperlint studies new`, writes its design and its `prereg.json`, and updates its row when it ends |
154
+ | `build-label-page` | skill | when you ask; stage 3 | nothing: it builds the labelling page, and exports the answers into the study's `data/` |
152
155
 
153
156
  The hooks are wired by `paperlint init` ([`install.md`](install.md)). A rule's level and options:
154
157
  [`rules.md`](rules.md).
@@ -34,11 +34,12 @@ it: [the map](../../layout.md).
34
34
  ├── siblings/ ← the cards of competing papers
35
35
  ├── research/ ← dated research and decision records
36
36
  ├── repro/ ← the reproduction artifact
37
- │ ├── README.md ← the index: one line per study
37
+ │ ├── README.md ← the index: one line per study, generated from studies.json
38
+ │ ├── studies.json ← the study registry: id, title, status and result of each study
38
39
  │ ├── .gitignore ← keeps the scripts' byproducts out of git
39
40
  │ ├── tools/ ← scripts used across the paper (numbers, figures, anonymity check, harnesses)
40
41
  │ ├── data/ ← shared data (tables, caches)
41
- │ └── <slug>-YYYY-MM-DD/ ← one folder per study or spike (<slug>-YYYY-MM for one that ran over weeks)
42
+ │ └── <id>-<slug>/ ← one folder per study or spike, named by its row in studies.json (P1-S03-baseline)
42
43
  │ └── README.md ← required: what it asks, how to run it, what it found
43
44
  ├── figures/ ← graphics and their sources
44
45
  ├── sections/ ← the body, when it is split
@@ -7,8 +7,8 @@ per misplaced entry
7
7
  ## What it catches
8
8
 
9
9
  An entry at the top of a paper's `repro/` that is not part of its layout, and a study folder there
10
- without a `README.md`. `repro/` holds an index, the tools and data the whole paper shares, and one
11
- dated folder per study. Stage 3 of the pipeline, the study, writes here; the rule runs on every
10
+ without a `README.md`. `repro/` holds an index, the study registry, the tools and data the whole
11
+ paper shares, and one folder per study. Stage 3 of the pipeline, the study, writes here; the rule runs on every
12
12
  `paperlint lint` and in CI, and a hook refuses a new loose entry before it is written. The whole map:
13
13
  [`layout.md`](../../layout.md).
14
14
 
@@ -16,12 +16,13 @@ dated folder per study. Stage 3 of the pipeline, the study, writes here; the rul
16
16
 
17
17
  ```text
18
18
  repro/
19
- ├── README.md ← the index: one line per study
20
- ├── .gitignore ← keeps the scripts' byproducts out of git
21
- ├── tools/ ← scripts used across the paper (numbers, figures, anonymity check, harnesses)
22
- ├── data/ ← shared data (tables, caches)
23
- └── <slug>-YYYY-MM-DD/ ← one folder per study or spike (<slug>-YYYY-MM for one that ran over weeks)
24
- └── README.md ← required: what it asks, how to run it, what it found
19
+ ├── README.md ← the index: one line per study, generated from studies.json
20
+ ├── studies.json ← the study registry: id, title, status and result of each study
21
+ ├── .gitignore ← keeps the scripts' byproducts out of git
22
+ ├── tools/ ← scripts used across the paper (numbers, figures, anonymity check, harnesses)
23
+ ├── data/ ← shared data (tables, caches)
24
+ └── <id>-<slug>/ ← one folder per study or spike, named by its row in studies.json (P1-S03-baseline)
25
+ └── README.md ← required: what it asks, how to run it, what it found
25
26
  ```
26
27
 
27
28
  <!-- layout:end -->
@@ -33,10 +34,16 @@ above, and it already holds the index.
33
34
  A loose note belongs in the folder of the study it is about, or in the paper's `research/` when it is
34
35
  research or a decision rather than the record of a run.
35
36
 
36
- **The date in a study's name** is the day the study began: `ablation-2026-10-12`. A month is
37
- accepted too, `human-labels-2026-10`: a study that ran over weeks (labelling, reading a corpus) has a
38
- month as its honest date, and both forms sort by time. The date must be a real one — no month 13, no
39
- 30 February. The slug is lowercase letters and digits, words joined by single hyphens.
37
+ **A study's name** is `<id>-<slug>`: the id of its row in `repro/studies.json` — the paper's prefix,
38
+ `-S`, two or more digits — then a slug, lowercase letters and digits in words joined by single
39
+ hyphens: `P1-S03-baseline`. `npx paperlint studies new` makes the folder, its `README.md` and its row.
40
+ This rule accepts the shape; whether the row exists, and matches, is
41
+ [`repro/study-registered`](study-registered.md).
42
+
43
+ **The dated form, `<slug>-YYYY-MM-DD`**, is accepted too, so a paper that keeps its dated folders and
44
+ turns `repro/study-registered` off is not broken: `ablation-2026-10-12`, the day the study began. A
45
+ month is accepted as well, `human-labels-2026-10`, for a study that ran over weeks. The date must be a
46
+ real one — no month 13, no 30 February.
40
47
 
41
48
  Never reported: what interpreters, package managers, test runners and the operating system leave in
42
49
  the folder they run in — `__pycache__/`, `.pytest_cache/`, `.mypy_cache/`, `.ruff_cache/`,
@@ -55,14 +62,15 @@ or where a new run goes. Each kind of material now has one place, and each study
55
62
  Failing: a script loose at the top.
56
63
 
57
64
  ```text
58
- papers/agent-rule-drift/PIPELINE-STATUS.md
65
+ papers/example/PIPELINE-STATUS.md
59
66
  1:1 error `repro/paper_numbers.py` is loose at the top of the paper's repro/ folder, which holds
60
- only an index, the shared tools and data, and one dated folder per study. Where it
61
- goes: repro/tools/ — the scripts used across the paper (numbers, figures, the anonymity
62
- check, harnesses); a script only one study runs goes in that study's folder instead.
67
+ only an index, the study registry, the shared tools and data, and one folder per study.
68
+ Where it goes: repro/tools/ — the scripts used across the paper (numbers, figures, the
69
+ anonymity check, harnesses); a script only one study runs goes in that study's folder
70
+ instead.
63
71
  What repro/ holds (what interpreters and package managers leave there, like __pycache__/ and node_modules/, is never reported):
64
72
  repro/
65
- ├── README.md ← the index: one line per study
73
+ ├── README.md ← the index: one line per study, generated from studies.json
66
74
  …
67
75
  Rule repro/allowed-files-only: https://github.com/zernie/paperlint/blob/main/docs/rules/repro/allowed-files-only.md repro/allowed-files-only
68
76
  ```
@@ -70,14 +78,14 @@ Rule repro/allowed-files-only: https://github.com/zernie/paperlint/blob/main/doc
70
78
  Each message names the entry and where it goes, and draws the layout above, so it reads on its own. Where
71
79
  an entry goes depends on what it is:
72
80
 
73
- | entry | where it goes |
74
- | ------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- |
75
- | a script (`.py`, `.ipynb`, `.sh`, `.js`, `.mjs`, `.ts`, `.r`, `.jl`, `.sql` …), `Makefile`, `requirements.txt`, `package.json` | `repro/tools/`, or the folder of the one study that runs it |
76
- | data (`.tsv`, `.csv`, `.json`, `.jsonl`, `.yaml`, `.txt`, `.log`, `.parquet`, `.zip` …) | `repro/data/`, or the folder of the one study that produced it |
77
- | a note (`.md`) | the dated folder of the study it is about, or the paper's `research/` |
78
- | a folder whose name is not `<slug>-YYYY-MM-DD` | rename it to `<slug>-YYYY-MM-DD` and give it a `README.md`; its first commit's date is a fine default |
79
- | a study folder without `README.md` | add one: what the study asks, how to run it, what it found; and a line in `repro/README.md` |
80
- | anything else | by what it is, as above |
81
+ | entry | where it goes |
82
+ | ------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
83
+ | a script (`.py`, `.ipynb`, `.sh`, `.js`, `.mjs`, `.ts`, `.r`, `.jl`, `.sql` …), `Makefile`, `requirements.txt`, `package.json` | `repro/tools/`, or the folder of the one study that runs it |
84
+ | data (`.tsv`, `.csv`, `.json`, `.jsonl`, `.yaml`, `.txt`, `.log`, `.parquet`, `.zip` …) | `repro/data/`, or the folder of the one study that produced it |
85
+ | a note (`.md`) | the folder of the study it is about, or the paper's `research/` |
86
+ | a folder whose name is neither `<id>-<slug>` nor `<slug>-YYYY-MM-DD` | rename it to `<id>-<slug>` with its row in `repro/studies.json` and a `README.md`; with `repro/study-registered` off, `<slug>-YYYY-MM-DD`, dated by its first commit |
87
+ | a study folder without `README.md` | add one: what the study asks, how to run it, what it found; and the study's row in `repro/studies.json` |
88
+ | anything else | by what it is, as above |
81
89
 
82
90
  Three files paperlint itself once kept at the top of `repro/` are named with their new place and the
83
91
  command that moves them: `references-cache.json` → `repro/data/` (read by `paperlint build`),
@@ -98,7 +106,8 @@ No rule options.
98
106
  ## What it does not check
99
107
 
100
108
  - What is inside `tools/`, `data/` and the study folders.
101
- - Whether `repro/README.md` exists, or has a line for every study.
109
+ - Whether every study folder has its row in `repro/studies.json`, and whether `repro/README.md`'s index
110
+ is in sync: that is [`repro/study-registered`](study-registered.md).
102
111
  - What a study's `README.md` says.
103
112
  - `repro/` itself: whether the paper has one is `paper/allowed-files-only`'s concern, and that rule never
104
113
  looks inside it. Each entry is judged by exactly one of the two rules.
@@ -106,18 +115,19 @@ No rule options.
106
115
  ## The hook
107
116
 
108
117
  The hook `paper-folder-layout` refuses, before it is written, a new file that would add an entry
109
- this rule reports — a loose file at the top of `repro/`, an undated folder, or a study folder whose
118
+ this rule reports — a loose file at the top of `repro/`, a folder that is not a study's, or a study folder whose
110
119
  first file is not its `README.md` — with this rule's message as the reason. It never refuses a file
111
120
  that already exists, and it cannot see a shell `mv` or `cp`: the lint is the guarantee. What it does
112
121
  and does not do is on [`paper/allowed-files-only`'s page](../paper/allowed-files-only.md#the-hook-a-new-stray-entry-is-refused-before-it-is-written).
113
122
 
114
123
  ## How to fix
115
124
 
116
- Move or rename the entry where its message says, and update whatever names its path:
125
+ Move or rename the entry where its message says, and update whatever names its path; a renamed study
126
+ folder gets its row in `repro/studies.json` in the same commit:
117
127
 
118
128
  ```sh
119
- git mv papers/agent-rule-drift/repro/paper_numbers.py papers/agent-rule-drift/repro/tools/
120
- git mv papers/agent-rule-drift/repro/baseline papers/agent-rule-drift/repro/baseline-2026-08-05
129
+ git mv papers/example/repro/paper_numbers.py papers/example/repro/tools/
130
+ git mv papers/example/repro/baseline papers/example/repro/P1-S04-baseline
121
131
  ```
122
132
 
123
133
  To switch the rule off for one paper — say, one whose `repro/` predates the layout — set it in that
@@ -0,0 +1,162 @@
1
+ # repro/study-registered
2
+
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
6
+
7
+ ## What it catches
8
+
9
+ A study that is not in the paper's study registry, a row that does not match the folders, and an
10
+ index that does not match the registry. The registry is `repro/studies.json`: one row per study,
11
+ with a stable id, a plain name, its status and its result. Every study folder at the top of `repro/`
12
+ is named `<id>-<slug>` after its row — `P1-S03-baseline`. The design: [`study-registry.md`](../../design/study-registry.md).
13
+
14
+ ```json
15
+ {
16
+ "prefix": "P1",
17
+ "studies": [
18
+ {
19
+ "id": "P1-S01",
20
+ "folder": "P1-S01-baseline-pass-rate",
21
+ "title": "Baseline pass rate",
22
+ "question": "How often does the unmodified agent pass the task?",
23
+ "kind": "measurement",
24
+ "status": "done",
25
+ "date": "2026-10-15",
26
+ "result": "41 of 60 runs pass",
27
+ "result_file": "results.tsv",
28
+ "in_paper": "yes"
29
+ }
30
+ ]
31
+ }
32
+ ```
33
+
34
+ | field | what it holds |
35
+ | --------------- | -------------------------------------------------------------------------------- |
36
+ | `prefix` | the paper's prefix: a capital letter, then capitals or digits (`P1`) |
37
+ | `id` | `<prefix>-S` and two or more digits; unique |
38
+ | `folder` | `<id>-<slug>`, the study's folder at the top of `repro/` |
39
+ | `title` | a plain name of at most 8 words |
40
+ | `question` | one sentence: what the study asks |
41
+ | `kind` | `measurement`, `spike`, `tooling`, `data`, `artifact` or `salvage` |
42
+ | `status` | `designing`, `running`, `done`, `stopped`, `superseded` or `dropped` |
43
+ | `date` | `YYYY-MM-DD`, the day the study was registered |
44
+ | `result` | optional: the result in one line; required when `done` |
45
+ | `result_file` | optional: the file inside the folder the result is read from; required when done |
46
+ | `superseded_by` | optional: the id of the study that replaced it; required when `superseded` |
47
+ | `in_paper` | `yes`, `no`, or `registry` (listed in the registry, not in the paper's text) |
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`.
52
+
53
+ The rule reports:
54
+
55
+ - a folder at the top of `repro/` with no row (`tools/`, `data/` and what interpreters and package
56
+ managers leave there, like `__pycache__/` and `node_modules/`, are not studies);
57
+ - a row whose folder does not exist, and a folder named by more than one row;
58
+ - an id that is not `<prefix>-S<digits>`, an id on more than one row, and a folder that is not
59
+ `<id>-<slug>`;
60
+ - a title over 8 words;
61
+ - `done` without `result` or `result_file`, and a `result_file` that is not a file inside the folder;
62
+ - `superseded` without `superseded_by`, or naming an id that is not in the registry;
63
+ - an index out of sync: the block of `repro/README.md` between the markers below must equal the
64
+ text the registry generates, character for character;
65
+
66
+ ```text
67
+ <!-- studies:begin — generated from studies.json by `paperlint studies index`; do not edit -->
68
+ <!-- studies:end -->
69
+ ```
70
+
71
+ - 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).
73
+
74
+ ## Why
75
+
76
+ A paper's `repro/` grew to 79 study folders named `<slug>-YYYY-MM-DD`, and nobody could tell which was
77
+ which, which had ended, or which one a number in the paper came from. A date and a slug say when a
78
+ study began, not what it is. The registry gives each study a number that does not change and a name a
79
+ person can read, and says in one place where every study stands. A rule holds it, not a guideline: a
80
+ row the rule does not check is a row that drifts.
81
+
82
+ The index in `repro/README.md` is generated, so it is never edited by hand and never parsed: the rule
83
+ compares strings. It is a table padded the way prettier writes one, so formatting the README leaves
84
+ it in sync.
85
+
86
+ ## Examples
87
+
88
+ Failing: a folder in the dated form beside a registered study.
89
+
90
+ ```text
91
+ papers/example/PIPELINE-STATUS.md
92
+ 1:1 error `repro/baseline-2026-10-02/` has no row in `repro/studies.json`, and every study folder
93
+ has exactly one. What to do: add its row with the next free id, `P1-S02`, and rename the
94
+ folder to `P1-S02-<slug>`; a new study is created with
95
+ `npx paperlint studies new <paper-dir> "<title>"`, which does both.
96
+ Rule repro/study-registered: https://github.com/zernie/paperlint/blob/main/docs/rules/repro/study-registered.md repro/study-registered
97
+ ```
98
+
99
+ Failing: a row set to `done` by hand, with no result, and the index not regenerated — two findings.
100
+
101
+ Passing: a paper with no study folder; and a `repro/` made only by `paperlint studies new`, its rows
102
+ edited as the studies end and its index regenerated:
103
+
104
+ ```text
105
+ repro/
106
+ ├── README.md
107
+ ├── studies.json
108
+ ├── P1-S01-baseline-pass-rate/
109
+ │ ├── README.md
110
+ │ └── results.tsv
111
+ ├── tools/
112
+ └── data/
113
+ ```
114
+
115
+ ```markdown
116
+ <!-- studies:begin — generated from studies.json by `paperlint studies index`; do not edit -->
117
+
118
+ | id | title | status | result | in paper | folder |
119
+ | ------ | ------------------ | ------ | ------------------ | -------- | -------------------------------------------------------- |
120
+ | P1-S01 | Baseline pass rate | done | 41 of 60 runs pass | yes | [P1-S01-baseline-pass-rate/](P1-S01-baseline-pass-rate/) |
121
+
122
+ <!-- studies:end -->
123
+ ```
124
+
125
+ ## Options / preset fields
126
+
127
+ No rule options.
128
+
129
+ ## What it does not check
130
+
131
+ - Whether `question` is one sentence and `result` one line, beyond their being text.
132
+ - What a study's `README.md` says, or whether its frontmatter matches the row.
133
+ - Whether the paper cites a study: `in_paper` is declared, not detected.
134
+ - What may stand at the top of `repro/` besides the study folders, and whether a study folder has a
135
+ `README.md`: that is [`repro/allowed-files-only`](allowed-files-only.md).
136
+
137
+ ## How to fix
138
+
139
+ Create every new study with the command, which takes the next id, makes the folder and its
140
+ `README.md`, appends the row and regenerates the index:
141
+
142
+ ```sh
143
+ npx paperlint studies new papers/example "Baseline pass rate" --kind measurement
144
+ ```
145
+
146
+ When a study ends, edit its row in `repro/studies.json` — `status`, `result`, `result_file`,
147
+ `in_paper`, and `superseded_by` when another study replaced it — then regenerate the index:
148
+
149
+ ```sh
150
+ npx paperlint studies index papers/example
151
+ ```
152
+
153
+ A paper without a registry writes `repro/studies.json` once, with its prefix:
154
+ `{ "prefix": "P1", "studies": [] }`. A folder that predates the registry gets a row by hand and is
155
+ renamed to `<id>-<slug>` with `git mv`.
156
+
157
+ To switch the rule off for one paper — one that keeps its dated `<slug>-YYYY-MM-DD` folders —
158
+ set it in that paper's `paperlint.json`; `repro/allowed-files-only` still accepts the dated form:
159
+
160
+ ```json
161
+ { "rules": { "repro/study-registered": "off" } }
162
+ ```
@@ -12,7 +12,7 @@ its name) · **Reported on:** the first line of the paper's
12
12
  ## What it catches
13
13
 
14
14
  A study's pre-registration that does not say how its sample relates to the population the paper
15
- talks about. Every `prereg.json` in a study folder (`repro/<slug>-YYYY-MM-DD/`) must carry five
15
+ talks about. Every `prereg.json` in a study folder (`repro/<id>-<slug>/`) must carry five
16
16
  fields; every other key in the file is the author's and is not read.
17
17
 
18
18
  | field | holds |
package/docs/rules.md CHANGED
@@ -14,7 +14,8 @@ reads and when it fails. Errors fail `paperlint lint`; warnings print and do not
14
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
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
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`, `.gitignore`, `tools/`, `data/` or a study folder named `<slug>-YYYY-MM-DD` (or `<slug>-YYYY-MM`), 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) |
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) |
18
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` |
19
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 |
20
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` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paperlint",
3
- "version": "10.1.1",
3
+ "version": "10.2.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",
@@ -5,7 +5,7 @@ context: fork
5
5
  allowed-tools: [Read, Write, Edit, Grep, Glob, Bash, Agent]
6
6
  ---
7
7
 
8
- <!-- vigiles:sha256:eed7d0afdfee87a6 compiled from skills/build-benchmark/SKILL.md.spec.ts -->
8
+ <!-- vigiles:sha256:93d83b56c6c1baea compiled from skills/build-benchmark/SKILL.md.spec.ts -->
9
9
 
10
10
  # build-benchmark — the study + the artifact reviewers can run
11
11
 
@@ -36,6 +36,25 @@ about *preventing* something, write the one sentence saying **what physically st
36
36
  the experimental arm**. "There is different text in it" means you are measuring persuasion, not
37
37
  prevention, and the design is wrong before a single run.
38
38
 
39
+ ## 0.5 Register the study before its first file
40
+
41
+ Every study gets its folder, its id and its row from one command, never from a folder made by hand:
42
+
43
+ ```bash
44
+ npx paperlint studies new <paper-dir> "<plain title, at most 8 words>" --kind measurement
45
+ ```
46
+
47
+ It takes the next id in `repro/studies.json`, creates `repro/<id>-<slug>/README.md`, appends the row
48
+ (`status: designing`) and regenerates the index in `repro/README.md`. The kinds: `measurement`,
49
+ `spike`, `tooling`, `data`, `artifact`, `salvage`. Write the study's one-sentence `question` into its
50
+ row at once.
51
+
52
+ As the study moves, edit its row in `repro/studies.json`: `running` when the first counted run starts;
53
+ when it ends, `done` with `result` (one line) and `result_file` (the file inside the folder the result
54
+ is read from) — or `stopped`, `dropped`, or `superseded` with `superseded_by` — and `in_paper`. Then
55
+ run `npx paperlint studies index <paper-dir>`. The rule `repro/study-registered` fails `paperlint lint`
56
+ on a folder without a row, a `done` row without its result, and an index out of sync.
57
+
39
58
  ## 1. Design that inverts the pitfall you're critiquing
40
59
 
41
60
  The strongest measurement papers are a corrected version of the mistake they name. Pick the design that
@@ -42,6 +42,25 @@ about *preventing* something, write the one sentence saying **what physically st
42
42
  the experimental arm**. "There is different text in it" means you are measuring persuasion, not
43
43
  prevention, and the design is wrong before a single run.
44
44
 
45
+ ## 0.5 Register the study before its first file
46
+
47
+ Every study gets its folder, its id and its row from one command, never from a folder made by hand:
48
+
49
+ \`\`\`bash
50
+ npx paperlint studies new <paper-dir> "<plain title, at most 8 words>" --kind measurement
51
+ \`\`\`
52
+
53
+ It takes the next id in \`repro/studies.json\`, creates \`repro/<id>-<slug>/README.md\`, appends the row
54
+ (\`status: designing\`) and regenerates the index in \`repro/README.md\`. The kinds: \`measurement\`,
55
+ \`spike\`, \`tooling\`, \`data\`, \`artifact\`, \`salvage\`. Write the study's one-sentence \`question\` into its
56
+ row at once.
57
+
58
+ As the study moves, edit its row in \`repro/studies.json\`: \`running\` when the first counted run starts;
59
+ when it ends, \`done\` with \`result\` (one line) and \`result_file\` (the file inside the folder the result
60
+ is read from) — or \`stopped\`, \`dropped\`, or \`superseded\` with \`superseded_by\` — and \`in_paper\`. Then
61
+ run \`npx paperlint studies index <paper-dir>\`. The rule \`repro/study-registered\` fails \`paperlint lint\`
62
+ on a folder without a row, a \`done\` row without its result, and an index out of sync.
63
+
45
64
  ## 1. Design that inverts the pitfall you're critiquing
46
65
 
47
66
  The strongest measurement papers are a corrected version of the mistake they name. Pick the design that