paperlint 10.2.0 → 10.4.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/.github/workflows/ci.yml +15 -4
- package/README.md +1 -0
- package/dist/adapters/git/index.d.ts +10 -0
- package/dist/adapters/git/index.d.ts.map +1 -0
- package/dist/adapters/git/index.js +151 -0
- package/dist/adapters/git/index.js.map +1 -0
- package/dist/adapters/memory/index.d.ts +6 -0
- package/dist/adapters/memory/index.d.ts.map +1 -1
- package/dist/adapters/memory/index.js +8 -0
- package/dist/adapters/memory/index.js.map +1 -1
- package/dist/adapters/node/index.d.ts +1 -0
- package/dist/adapters/node/index.d.ts.map +1 -1
- package/dist/adapters/node/index.js +1 -0
- package/dist/adapters/node/index.js.map +1 -1
- package/dist/adapters/node/list-entries.io.d.ts +3 -0
- package/dist/adapters/node/list-entries.io.d.ts.map +1 -0
- package/dist/adapters/node/list-entries.io.js +24 -0
- package/dist/adapters/node/list-entries.io.js.map +1 -0
- package/dist/adapters/study-registry/index.d.ts +412 -1
- package/dist/adapters/study-registry/index.d.ts.map +1 -1
- package/dist/adapters/study-registry/index.js +245 -8
- package/dist/adapters/study-registry/index.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +38 -6
- package/dist/cli.js.map +1 -1
- package/dist/domain/decide-first.d.ts +100 -0
- package/dist/domain/decide-first.d.ts.map +1 -0
- package/dist/domain/decide-first.js +102 -0
- package/dist/domain/decide-first.js.map +1 -0
- package/dist/domain/index-columns.d.ts +45 -0
- package/dist/domain/index-columns.d.ts.map +1 -0
- package/dist/domain/index-columns.js +62 -0
- package/dist/domain/index-columns.js.map +1 -0
- package/dist/domain/study-mode.d.ts +37 -0
- package/dist/domain/study-mode.d.ts.map +1 -0
- package/dist/domain/study-mode.js +45 -0
- package/dist/domain/study-mode.js.map +1 -0
- package/dist/domain/study-registry.d.ts +253 -10
- package/dist/domain/study-registry.d.ts.map +1 -1
- package/dist/domain/study-registry.js +325 -27
- package/dist/domain/study-registry.js.map +1 -1
- package/dist/outputs-before-decision-rule.d.ts +17 -0
- package/dist/outputs-before-decision-rule.d.ts.map +1 -0
- package/dist/outputs-before-decision-rule.js +90 -0
- package/dist/outputs-before-decision-rule.js.map +1 -0
- package/dist/ports/git-history.d.ts +44 -0
- package/dist/ports/git-history.d.ts.map +1 -0
- package/dist/ports/git-history.js +2 -0
- package/dist/ports/git-history.js.map +1 -0
- package/dist/ports/list-entries.d.ts +17 -0
- package/dist/ports/list-entries.d.ts.map +1 -0
- package/dist/ports/list-entries.js +2 -0
- package/dist/ports/list-entries.js.map +1 -0
- package/dist/ports/study-registry-codec.d.ts +9 -2
- package/dist/ports/study-registry-codec.d.ts.map +1 -1
- package/dist/studies.d.ts +4 -0
- package/dist/studies.d.ts.map +1 -1
- package/dist/studies.js +55 -24
- package/dist/studies.js.map +1 -1
- package/dist/study-gate.d.ts +14 -0
- package/dist/study-gate.d.ts.map +1 -0
- package/dist/study-gate.js +95 -0
- package/dist/study-gate.js.map +1 -0
- package/dist/study-index-input.d.ts +5 -0
- package/dist/study-index-input.d.ts.map +1 -0
- package/dist/study-index-input.js +13 -0
- package/dist/study-index-input.js.map +1 -0
- package/dist/study-modes.d.ts +45 -0
- package/dist/study-modes.d.ts.map +1 -0
- package/dist/study-modes.js +70 -0
- package/dist/study-modes.js.map +1 -0
- package/dist/study-registry-rule.d.ts +29 -6
- package/dist/study-registry-rule.d.ts.map +1 -1
- package/dist/study-registry-rule.js +91 -40
- package/dist/study-registry-rule.js.map +1 -1
- package/dist/study-run.d.ts +19 -0
- package/dist/study-run.d.ts.map +1 -0
- package/dist/study-run.js +9 -0
- package/dist/study-run.js.map +1 -0
- package/docs/design/study-modes.md +247 -0
- package/docs/design/study-registry.md +137 -15
- package/docs/e2e.md +8 -4
- package/docs/layout.md +26 -21
- package/docs/rules/repro/outputs-before-decision.md +119 -0
- package/docs/rules/repro/study-plan-name.md +70 -0
- package/docs/rules/repro/study-registered.md +206 -15
- package/docs/rules.md +54 -52
- package/docs/testing.md +1 -0
- package/package.json +2 -1
- package/scripts/check.ts +6 -0
- package/skills/build-benchmark/SKILL.md +24 -12
- package/skills/build-benchmark/SKILL.md.spec.ts +23 -11
- package/skills/build-label-page/SKILL.md +9 -7
- package/skills/build-label-page/SKILL.md.spec.ts +8 -6
- package/skills/camera-ready/SKILL.md +6 -1
- package/skills/camera-ready/SKILL.md.spec.ts +5 -0
- package/skills/draft-paper/SKILL.md +11 -3
- package/skills/draft-paper/SKILL.md.spec.ts +10 -2
- package/skills/harden-paper/SKILL.md +12 -2
- package/skills/harden-paper/SKILL.md.spec.ts +11 -1
- package/skills/osf-artifact-upload/SKILL.md +7 -1
- package/skills/osf-artifact-upload/SKILL.md.spec.ts +6 -0
- package/skills/pc-panel-review/SKILL.md +10 -3
- package/skills/pc-panel-review/SKILL.md.spec.ts +9 -2
- package/skills/run-agent-grid/SKILL.md +10 -5
- package/skills/run-agent-grid/SKILL.md.spec.ts +9 -4
- package/skills/tighten-paper/SKILL.md +5 -1
- package/skills/tighten-paper/SKILL.md.spec.ts +4 -0
- package/src/adapters/git/history.test.ts +121 -0
- package/src/adapters/git/index.test.ts +268 -0
- package/src/adapters/git/index.ts +227 -0
- package/src/adapters/memory/index.test.ts +15 -1
- package/src/adapters/memory/index.ts +13 -0
- package/src/adapters/node/index.ts +1 -0
- package/src/adapters/node/list-entries.io.ts +26 -0
- package/src/adapters/node/list-entries.test.ts +43 -0
- package/src/adapters/study-registry/index.test.ts +343 -0
- package/src/adapters/study-registry/index.ts +374 -10
- package/src/adapters/study-registry/mode.test.ts +162 -0
- package/src/cli.ts +52 -6
- package/src/domain/decide-first.test.ts +258 -0
- package/src/domain/decide-first.ts +243 -0
- package/src/domain/index-columns.test.ts +216 -0
- package/src/domain/index-columns.ts +105 -0
- package/src/domain/study-measurements.test.ts +633 -0
- package/src/domain/study-mode.test.ts +55 -0
- package/src/domain/study-mode.ts +78 -0
- package/src/domain/study-registry.test.ts +48 -32
- package/src/domain/study-registry.ts +621 -36
- package/src/new-paper.test.ts +6 -1
- package/src/outputs-before-decision-rule.test.ts +206 -0
- package/src/outputs-before-decision-rule.ts +124 -0
- package/src/ports/git-history.ts +44 -0
- package/src/ports/list-entries.ts +18 -0
- package/src/ports/study-registry-codec.ts +13 -2
- package/src/rule-cases.test.ts +103 -1
- package/src/studies-cli.test.ts +2 -2
- package/src/studies-index-modes.test.ts +129 -0
- package/src/studies.test.ts +39 -9
- package/src/studies.ts +99 -29
- package/src/study-gate.test.ts +316 -0
- package/src/study-gate.ts +161 -0
- package/src/study-index-input.ts +23 -0
- package/src/study-modes.ts +120 -0
- package/src/study-registry-rule.test.ts +333 -7
- package/src/study-registry-rule.ts +145 -41
- package/src/study-run.ts +21 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# repro/outputs-before-decision
|
|
2
|
+
|
|
3
|
+
**Level:** error · **Reads:** `repro/studies.json`, each study's `plan.json`, the files under a
|
|
4
|
+
decide-first study's folder, and git's history of them · **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 decided before its run — its plan's `mode` is `decide_before_run`, the default
|
|
10
|
+
`paperlint studies new` writes for a measurement — whose history does not show that order:
|
|
11
|
+
|
|
12
|
+
```json
|
|
13
|
+
{
|
|
14
|
+
"mode": {
|
|
15
|
+
"kind": "decide_before_run",
|
|
16
|
+
"outputs": ["out/**", "RESULTS.md"],
|
|
17
|
+
"decision": {
|
|
18
|
+
"file": "repro/P1-S01-outcome/decision.md",
|
|
19
|
+
"note": "exclusions, veto, and the sample approved"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
- **`output-before-decision`** — an output (a file the `outputs` globs match, under the study's
|
|
26
|
+
folder) whose FIRST commit is not a strict descendant of the decision file's FIRST commit: earlier,
|
|
27
|
+
on a line of history the decision is not on, or in the very same commit, which says nothing about
|
|
28
|
+
which came first. An output committed and later deleted still counts.
|
|
29
|
+
- **`undecided-output`** — an output in the tree, or one ever committed and since deleted, while the
|
|
30
|
+
decision file has never been committed.
|
|
31
|
+
- **`decision-is-output`** — the decision file lies in the study's folder and an output glob matches
|
|
32
|
+
it: the decision would be a file its own run produces.
|
|
33
|
+
- **`decision-deleted`** — the decision file was committed and later deleted: it is not in HEAD, so
|
|
34
|
+
the study has no decision its outputs could follow, and the order is not judged against it.
|
|
35
|
+
- **`decision-not-file`** — the decision path names a directory or a symbolic link: git answers for
|
|
36
|
+
a directory with the commits of anything under it, and records a link rather than the file it
|
|
37
|
+
points to, so no order is read from either. (A path that names the paper's folder
|
|
38
|
+
itself, `.`, or ends in `/` is refused when the plan is parsed.)
|
|
39
|
+
- **`shallow`** — the clone is shallow, so every file's first commit is the clone's: no order can be
|
|
40
|
+
read, and the rule says so instead of judging.
|
|
41
|
+
- **`no-history`** — git could not answer (not installed, not a repository).
|
|
42
|
+
|
|
43
|
+
The study's own files — `plan.json`, `README.md` and the decision file — are never outputs. A symbolic
|
|
44
|
+
link in the study's folder is one entry, as git stores it, and is never followed: a link to the folder
|
|
45
|
+
itself cannot loop the walk, and one to a tree outside brings none of its files into the study.
|
|
46
|
+
|
|
47
|
+
## Why
|
|
48
|
+
|
|
49
|
+
A measurement's inputs — which runs count, which units are excluded, whether the sample is approved
|
|
50
|
+
— must be decided before anyone has seen an outcome; otherwise the decision can follow the result.
|
|
51
|
+
"Decide first, then compute" needs no key and no encryption: the decision is committed, the run
|
|
52
|
+
starts only after (`paperlint studies gate` exits 0 only then), and git's history is the proof of
|
|
53
|
+
order. This rule re-reads that proof on every lint, so a reviewer who clones the repository can run
|
|
54
|
+
the same check and get the same answer. The design is
|
|
55
|
+
[`docs/design/study-modes.md`](../../design/study-modes.md).
|
|
56
|
+
|
|
57
|
+
**It asks git for an order, never pins a sha**, which is why it can live as a lint rule (rules in
|
|
58
|
+
`eslint-rules/` may not run git: a pinned sha is deleted by a squash or a gc). A squash merge keeps
|
|
59
|
+
the order when the decision was merged before the run — the decide-first workflow — and a shallow
|
|
60
|
+
checkout is named, not read as a violation.
|
|
61
|
+
|
|
62
|
+
## Examples
|
|
63
|
+
|
|
64
|
+
Failing:
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
papers/example/PIPELINE-STATUS.md
|
|
68
|
+
1:1 error `repro/P1-S01-outcome/RESULTS.md`, an output of `P1-S01`, entered history in
|
|
69
|
+
4f2a9c0e1b7d (2026-10-16T10:00:00+05:00), the very commit that first recorded its
|
|
70
|
+
decision, 4f2a9c0e1b7d (2026-10-16T10:00:00+05:00). The study is declared decided
|
|
71
|
+
before its run, and its history does not show that order. What to do: if the decision
|
|
72
|
+
really came first, nothing in this history can prove it — declare the study
|
|
73
|
+
`exploratory` with the reason, or run it again after the decision; never rewrite the
|
|
74
|
+
history to make it pass.
|
|
75
|
+
Rule repro/outputs-before-decision: https://github.com/zernie/paperlint/blob/main/docs/rules/repro/outputs-before-decision.md repro/outputs-before-decision
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Passing: `decision.md` committed in one commit; `RESULTS.md` and `out/` committed in a later commit
|
|
79
|
+
that descends from it. An `exploratory` study is not this rule's.
|
|
80
|
+
|
|
81
|
+
## Options / preset fields
|
|
82
|
+
|
|
83
|
+
No rule options. The study declares its mode in its plan.
|
|
84
|
+
|
|
85
|
+
## What it does not check
|
|
86
|
+
|
|
87
|
+
- What the decision says. The gate is that it is committed, not its content.
|
|
88
|
+
- **A decision amended after the outputs**: the order is between FIRST commits. A later edit of the
|
|
89
|
+
decision file is visible in its history (`git log -- <file>`) but not reported.
|
|
90
|
+
- That nobody ran the study before the decision and kept the outputs out of git. History records
|
|
91
|
+
what was committed; a run on a machine that never committed it leaves no trace.
|
|
92
|
+
- A study whose row names a folder that is not one inside `repro/` (`../x`), or a folder that is a
|
|
93
|
+
symbolic link: nothing is read through it, so it is no study here;
|
|
94
|
+
[`repro/study-registered`](study-registered.md) reports the row or reads its plan as missing.
|
|
95
|
+
- A study whose plan is still a draft: only a ready plan's mode decides anything, and
|
|
96
|
+
[`repro/study-registered`](study-registered.md) reports an unfilled plan once the study runs.
|
|
97
|
+
- Commit dates. They are shown in the message, but the order is ancestry, which a rebase can
|
|
98
|
+
rewrite only by making a new history that a reviewer comparing against the published one sees.
|
|
99
|
+
|
|
100
|
+
## How to fix
|
|
101
|
+
|
|
102
|
+
Commit the decision first, then let the run start:
|
|
103
|
+
|
|
104
|
+
```sh
|
|
105
|
+
git add papers/example/repro/P1-S01-outcome/decision.md && git commit -m "P1-S01: decision"
|
|
106
|
+
npx paperlint studies gate papers/example P1-S01 && ./run.sh
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
In CI, check out the whole history: `actions/checkout` with `fetch-depth: 0`. A study whose outputs
|
|
110
|
+
were made before its decision cannot be repaired by editing history; declare it exploratory:
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
"mode": {
|
|
115
|
+
"kind": "exploratory",
|
|
116
|
+
"reason": "outputs existed before the exclusions were fixed"
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# repro/study-plan-name
|
|
2
|
+
|
|
3
|
+
**Level:** warning · **Reads:** `repro/studies.json` · **Reported on:** the first line of the
|
|
4
|
+
paper's `PIPELINE-STATUS.md`, once per study
|
|
5
|
+
|
|
6
|
+
## What it catches
|
|
7
|
+
|
|
8
|
+
A study whose row in `repro/studies.json` names its plan as `prereg.json` — the name the plan had
|
|
9
|
+
before `paperlint studies new` wrote it as `plan.json`.
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"id": "P1-S01",
|
|
14
|
+
"folder": "P1-S01-rule-census",
|
|
15
|
+
"plan": "prereg.json",
|
|
16
|
+
"…": "…"
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The file is still read as the study's plan — by a loose schema, since it was written by hand before
|
|
21
|
+
the plan had one: keys of its own are accepted, a seed makes its draw `seeded` and none makes it a
|
|
22
|
+
draw of `all`, a size is not read, and an absent check is `null` —
|
|
23
|
+
and [`repro/study-registered`](study-registered.md) judges it like any `plan.json`. This rule only
|
|
24
|
+
asks for the new name. A `prereg.json` moved to `plan.json` is parsed strictly: its own keys go in
|
|
25
|
+
`extra`.
|
|
26
|
+
|
|
27
|
+
## Why
|
|
28
|
+
|
|
29
|
+
A study's plan was once a hand-written `prereg.json`. The name misled: it is not a pre-registration
|
|
30
|
+
with an outside registry, it is the study's own plan, fixed before the runs, with the author's check
|
|
31
|
+
of the drawn units. Papers that wrote one before `plan.json` existed keep working; this warning tells
|
|
32
|
+
them where the file moves, without failing their build.
|
|
33
|
+
|
|
34
|
+
## Examples
|
|
35
|
+
|
|
36
|
+
Failing (a warning):
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
papers/example/PIPELINE-STATUS.md
|
|
40
|
+
1:1 warning The plan of `P1-S01` is `prereg.json`, its old name; it is read as the plan. What to
|
|
41
|
+
do: rename it to `plan.json` in the study's folder (`git mv`) and set the row's `plan`
|
|
42
|
+
to `plan.json`.
|
|
43
|
+
Rule repro/study-plan-name: https://github.com/zernie/paperlint/blob/main/docs/rules/repro/study-plan-name.md repro/study-plan-name
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Passing: the row names `"plan": "plan.json"`, as `paperlint studies new` writes it; or the row
|
|
47
|
+
names no plan.
|
|
48
|
+
|
|
49
|
+
## Options / preset fields
|
|
50
|
+
|
|
51
|
+
No rule options.
|
|
52
|
+
|
|
53
|
+
## What it does not check
|
|
54
|
+
|
|
55
|
+
- A `prereg.json` in a study folder whose row names no `plan`: the file is not the study's plan
|
|
56
|
+
until the row says so, and is not read.
|
|
57
|
+
- Anything inside the plan: that is [`repro/study-registered`](study-registered.md).
|
|
58
|
+
|
|
59
|
+
## How to fix
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
git mv papers/example/repro/P1-S01-rule-census/prereg.json papers/example/repro/P1-S01-rule-census/plan.json
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
then set the row's `"plan": "plan.json"` in `repro/studies.json`. To keep the old name for one
|
|
66
|
+
paper, switch the rule off in its `paperlint.json`:
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{ "rules": { "repro/study-plan-name": "off" } }
|
|
70
|
+
```
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# repro/study-registered
|
|
2
2
|
|
|
3
3
|
**Level:** error · **Reads:** the listing of the paper's `repro/`, `repro/studies.json`,
|
|
4
|
-
`repro/README.md`, and each `result_file` the registry names · **Reported on:** the first
|
|
5
|
-
paper's `PIPELINE-STATUS.md`, once per finding
|
|
4
|
+
`repro/README.md`, and each `result_file` and `plan` the registry names · **Reported on:** the first
|
|
5
|
+
line of the paper's `PIPELINE-STATUS.md`, once per finding
|
|
6
6
|
|
|
7
7
|
## What it catches
|
|
8
8
|
|
|
@@ -46,9 +46,142 @@ is named `<id>-<slug>` after its row — `P1-S03-baseline`. The design: [`study-
|
|
|
46
46
|
| `superseded_by` | optional: the id of the study that replaced it; required when `superseded` |
|
|
47
47
|
| `in_paper` | `yes`, `no`, or `registry` (listed in the registry, not in the paper's text) |
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
`
|
|
51
|
-
|
|
49
|
+
| `measurement` | optional: the id of the measurement the study belongs to (below) |
|
|
50
|
+
| `plan` | optional: the study's plan, a file inside its folder — `plan.json` (below) |
|
|
51
|
+
|
|
52
|
+
A row may carry more fields — a paper's own metadata (`frame`, `method`, `outcome`, …) — and
|
|
53
|
+
`paperlint studies new` keeps them when it rewrites the file. Beside `prefix` and `studies`, the top
|
|
54
|
+
level holds only the optional `author`, `requirements` and `measurements`; any other key is an error.
|
|
55
|
+
|
|
56
|
+
### Measurements, requirements, the author
|
|
57
|
+
|
|
58
|
+
One measurement — a census, a benchmark — often spans several studies: a pilot, the main run, a
|
|
59
|
+
replication. A measurement is declared once, at the top level, and each of its studies' rows names
|
|
60
|
+
it in `measurement`. The author's requirements on the measurements are kept in their words, beside
|
|
61
|
+
them:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"prefix": "P1",
|
|
66
|
+
"author": "A. Author",
|
|
67
|
+
"requirements": [
|
|
68
|
+
{
|
|
69
|
+
"id": "R1",
|
|
70
|
+
"text": "Every sample is a random draw from all rules found, no filter by difficulty.",
|
|
71
|
+
"date": "2026-10-10",
|
|
72
|
+
"source": "chat"
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
"measurements": [
|
|
76
|
+
{
|
|
77
|
+
"id": "M1",
|
|
78
|
+
"title": "Rule census",
|
|
79
|
+
"rq": "RQ1",
|
|
80
|
+
"role": "headline",
|
|
81
|
+
"approval": {
|
|
82
|
+
"by": "A. Author",
|
|
83
|
+
"date": "2026-10-12",
|
|
84
|
+
"quote": "This is the one."
|
|
85
|
+
},
|
|
86
|
+
"requirements": ["R1"]
|
|
87
|
+
}
|
|
88
|
+
],
|
|
89
|
+
"studies": [
|
|
90
|
+
{
|
|
91
|
+
"id": "P1-S01",
|
|
92
|
+
"folder": "P1-S01-census-pilot",
|
|
93
|
+
"measurement": "M1",
|
|
94
|
+
"plan": "plan.json",
|
|
95
|
+
"…": "…"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"id": "P1-S02",
|
|
99
|
+
"folder": "P1-S02-census",
|
|
100
|
+
"measurement": "M1",
|
|
101
|
+
"plan": "plan.json",
|
|
102
|
+
"…": "…"
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
| field | what it holds |
|
|
109
|
+
| ----------------------------- | -------------------------------------------------------------------------------- |
|
|
110
|
+
| `author` | optional: the paper's author, the one person who approves a headline measurement |
|
|
111
|
+
| `requirements[].id` | the requirement's id, named by the measurements that must meet it |
|
|
112
|
+
| `requirements[].text` | the author's words, verbatim |
|
|
113
|
+
| `requirements[].date` | `YYYY-MM-DD` |
|
|
114
|
+
| `requirements[].source` | where they said it: a chat, a message, a review comment |
|
|
115
|
+
| `measurements[].id` | the measurement's id, named by its studies' rows |
|
|
116
|
+
| `measurements[].title` | a plain name |
|
|
117
|
+
| `measurements[].rq` | optional: the research question it answers |
|
|
118
|
+
| `measurements[].role` | `headline` (the paper's claim rests on it) or `baseline` |
|
|
119
|
+
| `measurements[].approval` | the author's approval: `by`, `date`, their words in `quote`, optional `source` |
|
|
120
|
+
| `measurements[].requirements` | optional: the ids of the requirements it must meet |
|
|
121
|
+
|
|
122
|
+
A measurement may carry more fields, as a row does; a requirement and an approval are exactly
|
|
123
|
+
their fields. **A registry without `measurements` is judged as before**: a row's `measurement` is
|
|
124
|
+
then a field of the paper's own, and nothing checks it.
|
|
125
|
+
|
|
126
|
+
### The study's plan, `plan.json`
|
|
127
|
+
|
|
128
|
+
Each study's plan is a JSON file in its folder, fixed before the runs: what it measures, the
|
|
129
|
+
population and how the sample is drawn from it, the arms compared, the primary outcome, and the
|
|
130
|
+
author's check of the drawn units. `paperlint studies new` writes the template beside the study's
|
|
131
|
+
`README.md` and names it in the row's `plan`:
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"question": "",
|
|
136
|
+
"population": {
|
|
137
|
+
"source": "",
|
|
138
|
+
"draw": { "kind": "all", "method": "" }
|
|
139
|
+
},
|
|
140
|
+
"arms": [],
|
|
141
|
+
"primary_outcome": "",
|
|
142
|
+
"author_check": null,
|
|
143
|
+
"mode": {
|
|
144
|
+
"kind": "decide_before_run",
|
|
145
|
+
"outputs": [],
|
|
146
|
+
"decision": { "file": "repro/P1-S02-census/decision.md", "note": "" }
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
| field | what it holds |
|
|
152
|
+
| ------------------------ | -------------------------------------------------------------------------------- |
|
|
153
|
+
| `question` | what the study measures, one sentence |
|
|
154
|
+
| `population.source` | where the units come from: the census, crawl or registry, with its date |
|
|
155
|
+
| `population.draw.kind` | `all` (the whole population, a census: no seed) or `seeded` (a sample) |
|
|
156
|
+
| `population.draw.method` | how the sample is drawn: `census`, `random`, `stratified` … |
|
|
157
|
+
| `population.draw.seed` | `seeded` only, and required there: the draw's seed, a string or a number |
|
|
158
|
+
| `arms` | the conditions compared, each `{ "name": … }`, each name once; `[]` for a census |
|
|
159
|
+
| `primary_outcome` | the one number the study reports first |
|
|
160
|
+
| `author_check` | `null` until the author has seen the drawn units; then `by`, `date`, `quote` |
|
|
161
|
+
| `extra` | optional: the paper's own keys (hypotheses, models, budget), kept unread |
|
|
162
|
+
| `mode` | a measurement's: exactly one of `decide_before_run` or `exploratory` |
|
|
163
|
+
|
|
164
|
+
`mode` is required of a `measurement` (other kinds may omit it): how its outcomes are kept from the
|
|
165
|
+
decisions on its inputs — decided before the run (the default the template writes; its `outputs`,
|
|
166
|
+
and its `decision` file relative to the paper, committed before the run) or `exploratory` with its
|
|
167
|
+
`reason`. Without it a measurement's plan is a draft whose empty text is `mode`. The modes, and the
|
|
168
|
+
rule that holds the order in git, [`repro/outputs-before-decision`](outputs-before-decision.md), are
|
|
169
|
+
in [`docs/design/study-modes.md`](../../design/study-modes.md).
|
|
170
|
+
|
|
171
|
+
The template's draw is of all the population; a sampled study changes it to
|
|
172
|
+
`{ "kind": "seeded", "method": "random", "seed": 7 }`. The population's size is not stored: it is
|
|
173
|
+
counted from the source, and a stored copy could only contradict it.
|
|
174
|
+
|
|
175
|
+
The plan is closed at every level, and holds no contradiction. Each of these makes the file "not a
|
|
176
|
+
study plan" (`bad-plan`, with the reason): a misspelt key; a key of the paper's own outside `extra`,
|
|
177
|
+
or a key of `extra` that is a field of the plan; a missing key — `null` is the one way to say "not
|
|
178
|
+
yet"; a `seeded` draw without its seed, or an `all` draw with one; two arms of one name; and an
|
|
179
|
+
`author_check` on a plan whose required texts are still empty — the author approves a complete plan.
|
|
180
|
+
|
|
181
|
+
A plan named `prereg.json`, its old name, is parsed by a loose schema — keys of its own accepted at
|
|
182
|
+
every level, those at the top kept in `extra`; a seed makes the draw `seeded`, none (or `null`)
|
|
183
|
+
makes it `all`; a size is not read; an absent check is `null` — and
|
|
184
|
+
[`repro/study-plan-name`](study-plan-name.md) warns that it moves to `plan.json`.
|
|
52
185
|
|
|
53
186
|
The rule reports:
|
|
54
187
|
|
|
@@ -56,12 +189,13 @@ The rule reports:
|
|
|
56
189
|
managers leave there, like `__pycache__/` and `node_modules/`, are not studies);
|
|
57
190
|
- a row whose folder does not exist, and a folder named by more than one row;
|
|
58
191
|
- an id that is not `<prefix>-S<digits>`, an id on more than one row, and a folder that is not
|
|
59
|
-
`<id>-<slug
|
|
192
|
+
`<id>-<slug>` — a row naming `../x` among them, through which no file is read;
|
|
60
193
|
- a title over 8 words;
|
|
61
194
|
- `done` without `result` or `result_file`, and a `result_file` that is not a file inside the folder;
|
|
62
195
|
- `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
|
|
64
|
-
|
|
196
|
+
- an index out of sync: the block of `repro/README.md` between the markers below must hold the
|
|
197
|
+
cells the registry and the plans generate, whatever padding a formatter gave the table — every
|
|
198
|
+
column but the two `studies index` reads from git (below);
|
|
65
199
|
|
|
66
200
|
```text
|
|
67
201
|
<!-- studies:begin — generated from studies.json by `paperlint studies index`; do not edit -->
|
|
@@ -69,7 +203,21 @@ The rule reports:
|
|
|
69
203
|
```
|
|
70
204
|
|
|
71
205
|
- 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)
|
|
206
|
+
`studies.json` that is not JSON or not of the shape above (one finding with the reasons);
|
|
207
|
+
- when the registry has `measurements`: a row whose `measurement` is not one of them
|
|
208
|
+
(`unknown-measurement`), a measurement no row names (`empty-measurement`), and a measurement that
|
|
209
|
+
must meet a requirement the registry does not hold (`unknown-requirement`);
|
|
210
|
+
- a `headline` measurement with no `approval`, or, when the registry names its `author`, one approved
|
|
211
|
+
by anyone else (`headline-approval`);
|
|
212
|
+
- a row's `plan` that is outside its folder, not a file, or not of the plan's shape — and, once the
|
|
213
|
+
study has run (`running`, `done`, `stopped`, `superseded`), a plan that leaves `question`,
|
|
214
|
+
`population.source`, `population.draw.method` or `primary_outcome` empty, or — for a measurement —
|
|
215
|
+
declares no `mode` or leaves one of its texts empty (`bad-plan`);
|
|
216
|
+
- a study of a `headline` measurement that has run with no `author_check` in its plan — or no plan —
|
|
217
|
+
or, when the registry names its `author`, a check by anyone else (`author-check`).
|
|
218
|
+
|
|
219
|
+
A study that is still `designing` owes neither a filled plan nor the author's check: the template
|
|
220
|
+
`studies new` writes is clean until the first run. A `dropped` study owes neither.
|
|
73
221
|
|
|
74
222
|
## Why
|
|
75
223
|
|
|
@@ -79,9 +227,24 @@ study began, not what it is. The registry gives each study a number that does no
|
|
|
79
227
|
person can read, and says in one place where every study stands. A rule holds it, not a guideline: a
|
|
80
228
|
row the rule does not check is a row that drifts.
|
|
81
229
|
|
|
82
|
-
The index in `repro/README.md` is generated, so it is never edited by hand
|
|
83
|
-
|
|
84
|
-
|
|
230
|
+
The index in `repro/README.md` is generated, so it is never edited by hand: the rule compares its
|
|
231
|
+
cells. It is a table padded the way prettier writes one, so formatting the README leaves it in sync.
|
|
232
|
+
|
|
233
|
+
Three of its columns say how each study kept its outcomes from its decisions
|
|
234
|
+
([`docs/design/study-modes.md`](../../design/study-modes.md)):
|
|
235
|
+
|
|
236
|
+
| column | cell |
|
|
237
|
+
| -------------- | ------------------------------------------------------------------------------------ |
|
|
238
|
+
| `mode` | `decide_before_run`, `exploratory: <reason>`, or `—` for a study whose plan has none |
|
|
239
|
+
| `decision` | the decision file's first commit (12 characters) and its date, or `not committed` |
|
|
240
|
+
| `first output` | the first commit of the oldest output and its date, or `none` |
|
|
241
|
+
|
|
242
|
+
The `mode` comes from the plan, which is in the tree, so the rule compares it. The two commits are
|
|
243
|
+
what git's history said when `paperlint studies index` last ran: a squash merge or a rebase gives the
|
|
244
|
+
same files new commits, and a shallow CI clone has none to read, so the rule never compares those two
|
|
245
|
+
columns. Regenerate the index before reading them; the order itself is judged by
|
|
246
|
+
[`repro/outputs-before-decision`](outputs-before-decision.md), not by the table. In a shallow clone
|
|
247
|
+
`studies index` writes `unknown (shallow clone)`.
|
|
85
248
|
|
|
86
249
|
## Examples
|
|
87
250
|
|
|
@@ -98,6 +261,20 @@ Rule repro/study-registered: https://github.com/zernie/paperlint/blob/main/docs/
|
|
|
98
261
|
|
|
99
262
|
Failing: a row set to `done` by hand, with no result, and the index not regenerated — two findings.
|
|
100
263
|
|
|
264
|
+
Failing: the census of the example above set to `running` while its plan is still the template —
|
|
265
|
+
the plan is reported for its empty fields, and the study for the author's check it lacks:
|
|
266
|
+
|
|
267
|
+
```text
|
|
268
|
+
papers/example/PIPELINE-STATUS.md
|
|
269
|
+
1:1 error The plan of `P1-S02`, `plan.json` in its folder, leaves `question`, `population.source`,
|
|
270
|
+
`population.draw.method`, `primary_outcome`, `mode.outputs` and `mode.decision.note`
|
|
271
|
+
empty, and the study is `running`: a plan is written before the runs. What to do: fill
|
|
272
|
+
them in, or set the study back to `designing`.
|
|
273
|
+
1:1 error `P1-S02` is `running` in the headline measurement `M1`, and its plan has no
|
|
274
|
+
`author_check`. What to do: show the author the drawn units, then record their answer
|
|
275
|
+
in the plan's `author_check` — `by`, `date`, and their words in `quote`.
|
|
276
|
+
```
|
|
277
|
+
|
|
101
278
|
Passing: a paper with no study folder; and a `repro/` made only by `paperlint studies new`, its rows
|
|
102
279
|
edited as the studies end and its index regenerated:
|
|
103
280
|
|
|
@@ -107,6 +284,7 @@ repro/
|
|
|
107
284
|
├── studies.json
|
|
108
285
|
├── P1-S01-baseline-pass-rate/
|
|
109
286
|
│ ├── README.md
|
|
287
|
+
│ ├── plan.json
|
|
110
288
|
│ └── results.tsv
|
|
111
289
|
├── tools/
|
|
112
290
|
└── data/
|
|
@@ -115,9 +293,9 @@ repro/
|
|
|
115
293
|
```markdown
|
|
116
294
|
<!-- studies:begin — generated from studies.json by `paperlint studies index`; do not edit -->
|
|
117
295
|
|
|
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/) |
|
|
296
|
+
| id | title | status | result | in paper | mode | decision | first output | folder |
|
|
297
|
+
| ------ | ------------------ | ------ | ------------------ | -------- | ----------------- | ----------------------- | ----------------------- | -------------------------------------------------------- |
|
|
298
|
+
| P1-S01 | Baseline pass rate | done | 41 of 60 runs pass | yes | decide_before_run | 3f2a9c1d7e40 2026-10-14 | 8b51e0aa42c9 2026-10-15 | [P1-S01-baseline-pass-rate/](P1-S01-baseline-pass-rate/) |
|
|
121
299
|
|
|
122
300
|
<!-- studies:end -->
|
|
123
301
|
```
|
|
@@ -130,7 +308,15 @@ No rule options.
|
|
|
130
308
|
|
|
131
309
|
- Whether `question` is one sentence and `result` one line, beyond their being text.
|
|
132
310
|
- What a study's `README.md` says, or whether its frontmatter matches the row.
|
|
311
|
+
- Whether a plan's texts say anything true, beyond their being filled in; whether `arms` should be
|
|
312
|
+
empty; whether the person in `approval` or `author_check` really said it — the rule sees the
|
|
313
|
+
name, not who typed it.
|
|
314
|
+
- A repeated measurement or requirement id, and a requirement no measurement names.
|
|
315
|
+
- A `prereg.json` or `plan.json` that no row names.
|
|
133
316
|
- Whether the paper cites a study: `in_paper` is declared, not detected.
|
|
317
|
+
- The index's `decision` and `first output` cells: they name commits a squash merge renames.
|
|
318
|
+
- Inside a study folder that is a symbolic link: nothing is read through it, so its plan and its
|
|
319
|
+
result file read as missing.
|
|
134
320
|
- What may stand at the top of `repro/` besides the study folders, and whether a study folder has a
|
|
135
321
|
`README.md`: that is [`repro/allowed-files-only`](allowed-files-only.md).
|
|
136
322
|
|
|
@@ -143,6 +329,11 @@ Create every new study with the command, which takes the next id, makes the fold
|
|
|
143
329
|
npx paperlint studies new papers/example "Baseline pass rate" --kind measurement
|
|
144
330
|
```
|
|
145
331
|
|
|
332
|
+
`studies new` also writes the study's `plan.json`: fill it in before the first run, and, for a
|
|
333
|
+
study of a headline measurement, show the author the drawn units and record their answer in its
|
|
334
|
+
`author_check`. A measurement is added by hand to `measurements`, with the author's `approval` when
|
|
335
|
+
it is the headline, and each of its studies' rows names it in `measurement`.
|
|
336
|
+
|
|
146
337
|
When a study ends, edit its row in `repro/studies.json` — `status`, `result`, `result_file`,
|
|
147
338
|
`in_paper`, and `superseded_by` when another study replaced it — then regenerate the index:
|
|
148
339
|
|