paperlint 10.1.1 → 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.
- package/README.md +2 -0
- package/dist/adapters/study-registry/index.d.ts +357 -0
- package/dist/adapters/study-registry/index.d.ts.map +1 -0
- package/dist/adapters/study-registry/index.js +221 -0
- package/dist/adapters/study-registry/index.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +42 -2
- package/dist/cli.js.map +1 -1
- package/dist/domain/repro-layout.d.ts +12 -4
- package/dist/domain/repro-layout.d.ts.map +1 -1
- package/dist/domain/repro-layout.js +49 -18
- package/dist/domain/repro-layout.js.map +1 -1
- package/dist/domain/scaffold-layout.d.ts +4 -3
- package/dist/domain/scaffold-layout.d.ts.map +1 -1
- package/dist/domain/scaffold-layout.js +7 -4
- package/dist/domain/scaffold-layout.js.map +1 -1
- package/dist/domain/study-registry.d.ts +372 -0
- package/dist/domain/study-registry.d.ts.map +1 -0
- package/dist/domain/study-registry.js +593 -0
- package/dist/domain/study-registry.js.map +1 -0
- package/dist/ports/study-registry-codec.d.ts +15 -0
- package/dist/ports/study-registry-codec.d.ts.map +1 -0
- package/dist/ports/study-registry-codec.js +2 -0
- package/dist/ports/study-registry-codec.js.map +1 -0
- package/dist/repro-layout-rule.d.ts.map +1 -1
- package/dist/repro-layout-rule.js +4 -3
- package/dist/repro-layout-rule.js.map +1 -1
- package/dist/studies.d.ts +27 -0
- package/dist/studies.d.ts.map +1 -0
- package/dist/studies.js +163 -0
- package/dist/studies.js.map +1 -0
- package/dist/study-registry-rule.d.ts +24 -0
- package/dist/study-registry-rule.d.ts.map +1 -0
- package/dist/study-registry-rule.js +145 -0
- package/dist/study-registry-rule.js.map +1 -0
- package/docs/design/study-registry.md +246 -0
- package/docs/layout.md +51 -46
- package/docs/rules/paper/allowed-files-only.md +3 -2
- package/docs/rules/repro/allowed-files-only.md +40 -30
- package/docs/rules/repro/study-plan-name.md +70 -0
- package/docs/rules/repro/study-registered.md +320 -0
- package/docs/rules/study/declare-sample.md +1 -1
- package/docs/rules.md +53 -51
- package/package.json +1 -1
- package/skills/build-benchmark/SKILL.md +39 -8
- package/skills/build-benchmark/SKILL.md.spec.ts +38 -7
- package/skills/build-label-page/SKILL.md +14 -9
- package/skills/build-label-page/SKILL.md.spec.ts +13 -8
- package/skills/paper-pipeline/references/artifact-checklist.md +2 -1
- package/skills/paper-pipeline/references/pipeline-status-template.md +5 -3
- package/skills/run-agent-grid/SKILL.md +13 -1
- package/skills/run-agent-grid/SKILL.md.spec.ts +12 -0
- package/src/adapters/study-registry/index.test.ts +484 -0
- package/src/adapters/study-registry/index.ts +328 -0
- package/src/cli.ts +53 -2
- package/src/domain/paper-layout.test.ts +1 -1
- package/src/domain/repro-layout.test.ts +18 -9
- package/src/domain/repro-layout.ts +58 -18
- package/src/domain/scaffold-layout.ts +7 -4
- package/src/domain/study-measurements.test.ts +629 -0
- package/src/domain/study-registry.test.ts +666 -0
- package/src/domain/study-registry.ts +1044 -0
- package/src/folder-layout-hook.test.ts +10 -0
- package/src/new-paper.test.ts +29 -2
- package/src/ports/study-registry-codec.ts +15 -0
- package/src/repro-layout-rule.ts +4 -3
- package/src/rule-cases.test.ts +68 -0
- package/src/studies-cli.test.ts +113 -0
- package/src/studies.test.ts +359 -0
- package/src/studies.ts +268 -0
- package/src/study-registry-rule.test.ts +456 -0
- package/src/study-registry-rule.ts +239 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# The study registry — design
|
|
2
|
+
|
|
3
|
+
A paper's `repro/` holds one folder per study. Named `<slug>-YYYY-MM-DD`, they sort by date and say
|
|
4
|
+
nothing else: a real paper reached 79 study folders, and nobody could tell which one a number in the
|
|
5
|
+
paper came from, which ones had ended, or which were current. This note fixes the shape of the
|
|
6
|
+
registry that gives every study a stable number and a plain name, the lint rule that holds it, and the
|
|
7
|
+
two commands that write it.
|
|
8
|
+
|
|
9
|
+
## What is decided
|
|
10
|
+
|
|
11
|
+
1. **One registry, `repro/studies.json`, JSON with a schema.** Never markdown: a table in a README is
|
|
12
|
+
what people edit by hand and what a parser has to guess at. The schema (zod, in
|
|
13
|
+
`src/adapters/study-registry/`) is checked where the file is read; nothing after that re-validates.
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"prefix": "P1",
|
|
18
|
+
"studies": [
|
|
19
|
+
{
|
|
20
|
+
"id": "P1-S01",
|
|
21
|
+
"folder": "P1-S01-baseline-pass-rate",
|
|
22
|
+
"title": "Baseline pass rate",
|
|
23
|
+
"question": "How often does the unmodified agent pass the task?",
|
|
24
|
+
"kind": "measurement",
|
|
25
|
+
"status": "done",
|
|
26
|
+
"date": "2026-10-15",
|
|
27
|
+
"result": "41 of 60 runs pass",
|
|
28
|
+
"result_file": "results.tsv",
|
|
29
|
+
"in_paper": "yes"
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
| field | values |
|
|
36
|
+
| --------------- | ------------------------------------------------------------------------- |
|
|
37
|
+
| `prefix` | the paper's prefix: a capital letter, then capitals or digits (`P1`) |
|
|
38
|
+
| `id` | `<prefix>-S<two or more digits>`, unique |
|
|
39
|
+
| `folder` | `<id>-<slug>`: the study's folder at the top of `repro/` |
|
|
40
|
+
| `title` | a plain name, at most 8 words |
|
|
41
|
+
| `question` | one sentence: what the study asks |
|
|
42
|
+
| `kind` | `measurement`, `spike`, `tooling`, `data`, `artifact`, `salvage` |
|
|
43
|
+
| `status` | `designing`, `running`, `done`, `stopped`, `superseded`, `dropped` |
|
|
44
|
+
| `date` | `YYYY-MM-DD`, the day it was registered |
|
|
45
|
+
| `result` | optional; one line. Required when `done` |
|
|
46
|
+
| `result_file` | optional; a path inside the folder. Required, and must exist, when `done` |
|
|
47
|
+
| `superseded_by` | optional; an id in the registry. Required when `superseded` |
|
|
48
|
+
| `in_paper` | `yes`, `no`, `registry` (listed only in the registry, not in the text) |
|
|
49
|
+
|
|
50
|
+
| `measurement` | optional; the id of the measurement the study belongs to (6) |
|
|
51
|
+
| `plan` | optional; the study's plan, a path inside its folder: `plan.json` (7) |
|
|
52
|
+
|
|
53
|
+
**A row may carry more fields**, and they are kept: a paper merges its own metadata into the rows
|
|
54
|
+
(`frame`, `method`, `outcome`, …). The top level is closed — `prefix`, `studies`, and the optional
|
|
55
|
+
`author`, `requirements` and `measurements` (6) — so a misspelt key is an error, not an ignored
|
|
56
|
+
key.
|
|
57
|
+
|
|
58
|
+
2. **The rule `repro/study-registered`, an error,** reported on the first line of
|
|
59
|
+
`PIPELINE-STATUS.md` like `repro/allowed-files-only`, once per finding:
|
|
60
|
+
|
|
61
|
+
- every folder at the top of `repro/` that is not `tools/`, `data/` or a byproduct the layout
|
|
62
|
+
ignores (`__pycache__/`, `node_modules/`, …) has exactly one row, and every row's folder exists;
|
|
63
|
+
- the folder is `<id>-<slug>`, the id is `<prefix>-S<digits>`, ids are unique;
|
|
64
|
+
- `done` has a `result` and an existing `result_file`; `superseded` names an existing id in
|
|
65
|
+
`superseded_by`; a title has at most 8 words;
|
|
66
|
+
- **the index is in sync:** the block of `repro/README.md` between the two markers below equals the
|
|
67
|
+
text generated from `studies.json`. The comparison is of strings. The markdown is output only and
|
|
68
|
+
is never parsed.
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
<!-- studies:begin — generated from studies.json by `paperlint studies index`; do not edit -->
|
|
72
|
+
<!-- studies:end -->
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
- a paper with study folders and no `repro/studies.json` gets one finding naming the file.
|
|
76
|
+
- a `studies.json` that is not JSON, or not of the schema, gets one finding with the reasons.
|
|
77
|
+
|
|
78
|
+
3. **Two commands**, in `src/studies.ts`, wired in `src/cli.ts`:
|
|
79
|
+
|
|
80
|
+
- `paperlint studies new <paper-dir> "<title>" [--kind measurement]` takes the next id, creates
|
|
81
|
+
`repro/<id>-<slug>/README.md` (frontmatter `id:` and `title:`) and the plan's template,
|
|
82
|
+
`plan.json` (7), appends the row with `status: designing`, `in_paper: no`, today's date and
|
|
83
|
+
`plan: "plan.json"`, and regenerates the index block. It refuses a
|
|
84
|
+
title over 8 words, a title with no letter or digit to make a slug from, an unknown kind, a folder
|
|
85
|
+
that already exists, and a paper with no `studies.json` (the prefix is the author's choice, so the
|
|
86
|
+
refusal names the file to write and does not guess one).
|
|
87
|
+
- `paperlint studies index <paper-dir>` regenerates the index block: a table with the columns id ·
|
|
88
|
+
title · status · result · in paper · folder. With no markers in `repro/README.md` the block is
|
|
89
|
+
appended; with no `repro/README.md` the file is written.
|
|
90
|
+
|
|
91
|
+
4. **The layout accepts `<id>-<slug>` beside `<slug>-YYYY-MM-DD`.** `repro/allowed-files-only` keeps
|
|
92
|
+
accepting the dated form, so a paper that turns the registry off is not broken by this change; it
|
|
93
|
+
also allows `studies.json` at the top of `repro/`. A folder in the dated form is still reported by
|
|
94
|
+
`repro/study-registered` as unregistered — that is the point of the registry — and the paper decides
|
|
95
|
+
whether to register it or turn the rule off.
|
|
96
|
+
|
|
97
|
+
5. **Skills.** `build-benchmark`, `run-agent-grid` and `build-label-page` create a study only with
|
|
98
|
+
`paperlint studies new`, and update its row (status, result, result file, in paper) when it ends,
|
|
99
|
+
then run `paperlint studies index`.
|
|
100
|
+
|
|
101
|
+
6. **Measurements above studies, and the author's requirements.** One measurement (a census, a
|
|
102
|
+
benchmark) spans several study folders: a pilot, the main run, a replication. Before this, a paper
|
|
103
|
+
put the measurement's fields on the row of its first study under prefixed names
|
|
104
|
+
(`measurement_title`, `measurement_status`) and the other rows carried `measurement: <id>`, which
|
|
105
|
+
nothing checked; the author's verbatim requirements went to a separate file nothing read. Now:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"prefix": "P1",
|
|
110
|
+
"author": "A. Author",
|
|
111
|
+
"requirements": [
|
|
112
|
+
{
|
|
113
|
+
"id": "R1",
|
|
114
|
+
"text": "Every sample is a random draw from all rules found.",
|
|
115
|
+
"date": "2026-10-10",
|
|
116
|
+
"source": "chat"
|
|
117
|
+
}
|
|
118
|
+
],
|
|
119
|
+
"measurements": [
|
|
120
|
+
{
|
|
121
|
+
"id": "M1",
|
|
122
|
+
"title": "Rule census",
|
|
123
|
+
"rq": "RQ1",
|
|
124
|
+
"role": "headline",
|
|
125
|
+
"approval": {
|
|
126
|
+
"by": "A. Author",
|
|
127
|
+
"date": "2026-10-12",
|
|
128
|
+
"quote": "This is the one."
|
|
129
|
+
},
|
|
130
|
+
"requirements": ["R1"]
|
|
131
|
+
}
|
|
132
|
+
],
|
|
133
|
+
"studies": [
|
|
134
|
+
{ "id": "P1-S01", "measurement": "M1", "plan": "plan.json", "…": "…" }
|
|
135
|
+
]
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`role` is `headline` (the paper's claim rests on it) or `baseline`. A measurement keeps extra
|
|
140
|
+
fields like a row; a requirement and an approval are exactly their fields. The rule reports, each
|
|
141
|
+
finding its own message id: a row naming an undeclared measurement (`unknown-measurement`), a
|
|
142
|
+
measurement no row names (`empty-measurement`), a measurement naming an undeclared requirement
|
|
143
|
+
(`unknown-requirement`), and a headline without `approval`, or approved by anyone but `author`
|
|
144
|
+
when the registry names one (`headline-approval`). These cross-references are the rule's, not the
|
|
145
|
+
parser's: one dangling id must not fail the whole parse and hide every other finding. **These checks run only when the registry has
|
|
146
|
+
`measurements`**: a registry written before them, whose rows already carry `measurement` as a field
|
|
147
|
+
of their own, stays valid.
|
|
148
|
+
|
|
149
|
+
7. **The study's plan, `plan.json`.** Each study folder held a hand-written `prereg.json`, its
|
|
150
|
+
fields differing study to study, read only by the experimental `study/declare-sample`. The name
|
|
151
|
+
misled — it is not an outside pre-registration — so the plan is `plan.json`, written as a
|
|
152
|
+
template by `paperlint studies new` beside the README, and named in the row's `plan`:
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
{
|
|
156
|
+
"question": "",
|
|
157
|
+
"population": {
|
|
158
|
+
"source": "",
|
|
159
|
+
"draw": { "kind": "all", "method": "" }
|
|
160
|
+
},
|
|
161
|
+
"arms": [],
|
|
162
|
+
"primary_outcome": "",
|
|
163
|
+
"author_check": null
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The template's draw is of all the population (`"kind": "all"`, a census); a sampled study changes
|
|
168
|
+
it to `{ "kind": "seeded", "method": "random", "seed": 7 }`. `arms` is empty for a census;
|
|
169
|
+
`author_check` becomes `{ "by", "date", "quote" }` once the author has seen the drawn units.
|
|
170
|
+
**Parse, don't validate:**
|
|
171
|
+
|
|
172
|
+
- **Closed at every level.** A misspelt key is an error; the paper's own keys (hypotheses,
|
|
173
|
+
models, budget) go in the one optional key `extra`, an object of anything.
|
|
174
|
+
- **One spelling of "none".** `author_check` is required, and `null` is "not yet"; an absent key
|
|
175
|
+
is an error, never a second way to say the same.
|
|
176
|
+
- **No contradictory state.** Each of these is a parse error, not a later finding:
|
|
177
|
+
- an `author_check` on a draft — the author approved a plan whose texts are empty;
|
|
178
|
+
- a draw that is not one of two kinds: `seeded`, whose `seed` is required and not null, or
|
|
179
|
+
`all` (a census), which has no seed;
|
|
180
|
+
- two arms of one name;
|
|
181
|
+
- a key of `extra` that is a field of the plan, which would say one thing twice.
|
|
182
|
+
|
|
183
|
+
The population's size is not stored: it is counted from the source, and a stored copy could
|
|
184
|
+
only contradict it. Nothing downstream read it.
|
|
185
|
+
|
|
186
|
+
- **Complete or not is a type.** The parser returns a plan `ready` (every required text filled)
|
|
187
|
+
or a `draft` with the list of its empty texts, or why it is no plan. The rule never looks at a
|
|
188
|
+
text again: a study that has run on a `draft` is `bad-plan`, with the parser's list.
|
|
189
|
+
- **The schemas are the domain's types.** The domain's types are written by hand (the domain
|
|
190
|
+
imports no zod); the adapter holds a compile-time proof that each schema's output — registry,
|
|
191
|
+
study, measurement, requirement, approval, author check, plan, ready-or-draft — and its domain type are the
|
|
192
|
+
same type both ways, so a field added to one alone stops the build.
|
|
193
|
+
|
|
194
|
+
The schemas live beside the registry's, in `src/adapters/study-registry/`. The rule reports a
|
|
195
|
+
`plan` outside the folder, missing, or not of the schema, and — once the study has run (`running`,
|
|
196
|
+
`done`, `stopped`, `superseded`) — a draft (`bad-plan`); a study of a headline measurement
|
|
197
|
+
that has run with no `author_check` in its plan, no plan, or a check by anyone but `author`
|
|
198
|
+
(`author-check`). A `designing` study's template is clean, so `studies new` never leaves the lint
|
|
199
|
+
red.
|
|
200
|
+
|
|
201
|
+
**Compatibility.** A row may name `prereg.json` as its plan: it is parsed by a loose schema of
|
|
202
|
+
its own — keys of its own accepted at every level, those at the top kept in `extra`, a seed makes
|
|
203
|
+
the draw `seeded`, none (or `null`) makes it `all`; a size is not read; an absent check is
|
|
204
|
+
`null` — and judged as the plan, the same contradictions refused; a second rule, `repro/study-plan-name`, warns that it moves to `plan.json` — a warning cannot
|
|
205
|
+
come from an error rule, since ESLint sets the level per rule. A `prereg.json` no row names is not
|
|
206
|
+
read, so no existing paper turns red.
|
|
207
|
+
|
|
208
|
+
## Where each part lives
|
|
209
|
+
|
|
210
|
+
| part | file | layer |
|
|
211
|
+
| -------------------------------------- | ----------------------------------- | ------- |
|
|
212
|
+
| the types, the judge, the index, ids | `src/domain/study-registry.ts` | domain |
|
|
213
|
+
| the plan's type and template | `src/domain/study-registry.ts` | domain |
|
|
214
|
+
| the schemas (zod) of registry and plan | `src/adapters/study-registry/` | adapter |
|
|
215
|
+
| what the app asks of a parser | `src/ports/study-registry-codec.ts` | port |
|
|
216
|
+
| both rules: list, read, report | `src/study-registry-rule.ts` | app |
|
|
217
|
+
| the two commands | `src/studies.ts` | app |
|
|
218
|
+
| wiring, the clock for today's date | `src/cli.ts` | root |
|
|
219
|
+
|
|
220
|
+
The judge is pure: it takes the folder names, the parsed registry (or why it is absent or unreadable),
|
|
221
|
+
the README's text, which result files exist, and each named plan parsed, and returns findings. The rule and the commands are
|
|
222
|
+
the only places that touch the disk, through the `Files` and `ListDir` ports.
|
|
223
|
+
|
|
224
|
+
## Generated index, and prettier
|
|
225
|
+
|
|
226
|
+
The index is a table whose columns are padded to their widest cell, the way prettier writes a
|
|
227
|
+
markdown table, so formatting `repro/README.md` with prettier does not make the block stale. A `|` in
|
|
228
|
+
a cell is escaped and a line break becomes a space.
|
|
229
|
+
|
|
230
|
+
## Rejected, by the author on 2026-10-15
|
|
231
|
+
|
|
232
|
+
- Commands that change a study's status: the row is edited in `studies.json`, the rule checks it.
|
|
233
|
+
- Detecting automatically whether a study is cited in the paper: `in_paper` is declared.
|
|
234
|
+
- An adopt or migration command for existing folders: there are no other users; the folders are
|
|
235
|
+
renamed by hand once.
|
|
236
|
+
- A PreToolUse hook for the registry: the lint is the guarantee, and the layout hook already refuses
|
|
237
|
+
a loose entry.
|
|
238
|
+
|
|
239
|
+
## What it does not check
|
|
240
|
+
|
|
241
|
+
- Whether the `question` is one sentence, or the `result` one line, beyond their being strings.
|
|
242
|
+
- What a study's `README.md` says, or whether its frontmatter matches the row.
|
|
243
|
+
- Files inside a study folder other than `result_file` and the `plan` its row names.
|
|
244
|
+
- `study/declare-sample` still reads only `prereg.json`, and asks for different fields
|
|
245
|
+
(`strata`, `screens`, `coverage`, and an `author_check` of `who`, `when`, `units_seen`,
|
|
246
|
+
`units_drawn`, `verdict`); merging the two shapes is its own decision.
|
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
|
|
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"]
|
|
21
|
-
P["<b>
|
|
22
|
-
A["<b>author check</b> · you see the drawn units<br/>writes: author_check in
|
|
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
|
+
P["<b>plan</b> · build-benchmark<br/>fills: plan.json, before the draw<br/>checked by: repro/study-registered,<br/>once the study runs"]
|
|
22
|
+
A["<b>author check</b> · you see the drawn units<br/>writes: author_check in plan.json<br/>checked by: repro/study-registered,<br/>for a headline measurement"]
|
|
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
|
|
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,30 @@ 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
|
|
101
|
-
|
|
|
102
|
-
| `<papers-root>/venues/<venue>/`
|
|
103
|
-
| the paper folder's root
|
|
104
|
-
| `PIPELINE-STATUS.md`
|
|
105
|
-
| `paperlint.json`
|
|
106
|
-
| `siblings/`
|
|
107
|
-
| `reviews/`
|
|
108
|
-
| `research/`
|
|
109
|
-
| `repro/`, its top
|
|
110
|
-
| `repro
|
|
111
|
-
| `repro/<slug
|
|
112
|
-
| `repro/<slug
|
|
113
|
-
| `repro
|
|
114
|
-
| `
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
| `paper.
|
|
118
|
-
| `
|
|
119
|
-
| `
|
|
120
|
-
| `
|
|
121
|
-
| `
|
|
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>/plan.json` | 3 study | `paperlint studies new` writes the template; `build-benchmark` fills it before the sample is drawn; you add the author's check | [`repro/study-registered`](rules/repro/study-registered.md): its fields, filled once the study runs, and `author_check` in a headline measurement |
|
|
114
|
+
| `repro/<id>-<slug>/prereg.json` | 3 study | the plan's old name, still read when the row names it | [`repro/study-plan-name`](rules/repro/study-plan-name.md) (warning); [`study/declare-sample`](rules/study/declare-sample.md), off by default |
|
|
115
|
+
| `repro/<id>-<slug>/data/` | 3 study | `build-label-page` exports the labels here | nothing |
|
|
116
|
+
| `repro/tools/`, `repro/data/` | 3 study | your scripts; `paperlint build` keeps `repro/data/references-cache.json` | nothing inside them |
|
|
117
|
+
| `NOTES.md` | 3, then 4 | you: each number → the data it comes from; `build-label-page` adds a line per label file | nothing |
|
|
118
|
+
| `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 |
|
|
119
|
+
| `CLAIMS.md`, `figures/` | 4 draft | you | nothing |
|
|
120
|
+
| `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 |
|
|
121
|
+
| `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` |
|
|
122
|
+
| `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 |
|
|
123
|
+
| `talk/` | 7 camera-ready | you | the `talk/*` rules ([talk](talk.md)) |
|
|
124
|
+
| `extra/` | any | you | nothing, ever |
|
|
122
125
|
|
|
123
126
|
## What runs when
|
|
124
127
|
|
|
@@ -132,23 +135,25 @@ A check runs at one of four moments:
|
|
|
132
135
|
The lint-only job skips what needs a build (pages, fonts, references).
|
|
133
136
|
- **On demand** — a skill, when you ask for what it does. A skill stops nothing; it writes.
|
|
134
137
|
|
|
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/`,
|
|
143
|
-
| [`study
|
|
144
|
-
| [`
|
|
145
|
-
| `
|
|
146
|
-
| `paper/
|
|
147
|
-
|
|
|
148
|
-
|
|
|
149
|
-
|
|
|
150
|
-
| `build
|
|
151
|
-
| `
|
|
138
|
+
| what | kind | when it runs | what it stops |
|
|
139
|
+
| ------------------------------------------------------------------------------------------- | ----- | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
140
|
+
| `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 |
|
|
141
|
+
| `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 |
|
|
142
|
+
| `paper-skills-nudge` | hook | after an agent edits a paper's source | nothing: it shows the agent the pre-submit checklist |
|
|
143
|
+
| `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 |
|
|
144
|
+
| [`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) |
|
|
145
|
+
| [`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) |
|
|
146
|
+
| [`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, a measurement or plan that does not hold, an index out of sync (error) |
|
|
147
|
+
| [`repro/study-plan-name`](rules/repro/study-plan-name.md) | rule | every `paperlint lint`, and CI | nothing: it warns when a study's plan is still named `prereg.json` |
|
|
148
|
+
| [`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) |
|
|
149
|
+
| [`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 |
|
|
150
|
+
| `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) |
|
|
151
|
+
| `paper/stages`, `paper/source` | rule | every `paperlint lint`, and CI | a stage whose frozen PDF or source is missing or changed (error) |
|
|
152
|
+
| the rules on the source (`tex/*`, `paper/*`, `bib/*`) | rule | every `paperlint lint`, and CI | what each one's [row](rules.md) says |
|
|
153
|
+
| 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 |
|
|
154
|
+
| `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` |
|
|
155
|
+
| `build-benchmark` | skill | when you ask; stage 3 | nothing: it registers the study with `paperlint studies new`, writes its design, fills its `plan.json`, and updates its row when it ends |
|
|
156
|
+
| `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
157
|
|
|
153
158
|
The hooks are wired by `paperlint init` ([`install.md`](install.md)). A rule's level and options:
|
|
154
159
|
[`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
|
|
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
|
|
11
|
-
|
|
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
|
|
20
|
-
├── .
|
|
21
|
-
├──
|
|
22
|
-
├──
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
**
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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/
|
|
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
|
|
61
|
-
goes: repro/tools/ — the scripts used across the paper (numbers, figures, the
|
|
62
|
-
check, harnesses); a script only one study runs goes in that study's folder
|
|
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
|
|
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
|
|
78
|
-
| a folder whose name is
|
|
79
|
-
| a study folder without `README.md` | add one: what the study asks, how to run it, what it found; and
|
|
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
|
|
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/`,
|
|
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/
|
|
120
|
-
git mv papers/
|
|
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
|