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,247 @@
|
|
|
1
|
+
# Study modes: decide first, or exploratory — design
|
|
2
|
+
|
|
3
|
+
## Why
|
|
4
|
+
|
|
5
|
+
A study's outcomes are computed by an automated runner in the same workspace where the people
|
|
6
|
+
deciding its inputs work: which runs count, which units are excluded, whether a veto applies, whether
|
|
7
|
+
the drawn sample is approved. If those decisions can be made — or changed — after an outcome has been
|
|
8
|
+
seen, the decision can follow the result, and nothing in the repository says which came first.
|
|
9
|
+
|
|
10
|
+
What is needed:
|
|
11
|
+
|
|
12
|
+
1. every measurement says how its outcomes are kept from the decisions on its inputs, and none can
|
|
13
|
+
say nothing;
|
|
14
|
+
2. the default needs no key and no outside service: the decision is recorded first, the run starts
|
|
15
|
+
after, and anyone with the repository can re-check the order;
|
|
16
|
+
3. a study that cannot work that way is declared exploratory, with its reason, and reported as such;
|
|
17
|
+
4. the paper's Methods and its reviewers read each study's mode, decision commit and first output
|
|
18
|
+
from one generated place, not from prose someone keeps in sync by hand.
|
|
19
|
+
|
|
20
|
+
## What is decided
|
|
21
|
+
|
|
22
|
+
### 1. One mode per measurement, in its plan
|
|
23
|
+
|
|
24
|
+
A study's `plan.json` holds one `mode`, a union on its `kind` (`src/domain/study-mode.ts`):
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{ "mode": { "kind": "decide_before_run",
|
|
28
|
+
"outputs": ["out/**", "RESULTS.md"],
|
|
29
|
+
"decision": { "file": "repro/P1-S03-x/decision.md", "note": "exclusions and veto" } } }
|
|
30
|
+
|
|
31
|
+
{ "mode": { "kind": "exploratory", "reason": "a first look at the logs, to find the question" } }
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- **Exactly one, by construction.** The modes are one discriminated union, each variant closed: a
|
|
35
|
+
second mode's key in the same object is an unknown key, and an unknown `kind` is refused. This is
|
|
36
|
+
the parser's (`src/adapters/study-registry/`), not a later check.
|
|
37
|
+
- **None is not ready.** The parser is told the study's `kind` (from its row). A `measurement`'s plan
|
|
38
|
+
without a mode is a **draft** naming `mode` among its empty texts, like an empty `question`: legal
|
|
39
|
+
while the study is `designing`, reported by `repro/study-registered` (`bad-plan`, `unfilled`) once
|
|
40
|
+
it runs, and a parse error when the author's check is already on it (the author approved an
|
|
41
|
+
incomplete plan). A spike, a tool, a data set, an artifact or a salvage may omit it.
|
|
42
|
+
- **The default is written for you.** `paperlint studies new` writes a measurement's plan with
|
|
43
|
+
`decide_before_run`, its decision file named (`repro/<folder>/decision.md`), its outputs and note
|
|
44
|
+
left to fill — a draft until they are. The decision file itself is NOT created: an empty
|
|
45
|
+
placeholder that got committed would open the gate on nothing.
|
|
46
|
+
- **Paths.** `outputs` are globs relative to the study's folder, inside it, never the study's
|
|
47
|
+
`README.md`. A decision `file` is relative to the **paper's** folder, so it may live beside the
|
|
48
|
+
study or anywhere in the paper (`research/decisions.md`); it may not leave the paper, and it is a
|
|
49
|
+
file's path — names joined by `/`, none empty, `.` or `..` — so `.` (the paper itself) or a path
|
|
50
|
+
ending in `/` cannot be written. A name that is a directory on disk is the gate's to refuse and the
|
|
51
|
+
rule's to report (`decision-not-file`): git answers for a directory with the commits of anything
|
|
52
|
+
under it. Whether a
|
|
53
|
+
decision inside the study's folder is matched by an output glob needs the folder, which the plan
|
|
54
|
+
does not hold: the rule reports it (`decision-is-output`) and the gate refuses it.
|
|
55
|
+
- **Empty texts of a mode** — no outputs yet, an empty decision file or note, an empty reason — are a
|
|
56
|
+
draft's empty texts, named field by field (`mode.outputs`, `mode.decision.file`, …).
|
|
57
|
+
|
|
58
|
+
### 2. `paperlint studies gate <paper-dir> <id>`
|
|
59
|
+
|
|
60
|
+
Exit 0 only if the decision file exists and is committed as it stands (git's last commit for it, and
|
|
61
|
+
nothing changed since), printing the commit and its date; exit 1 saying why otherwise (not
|
|
62
|
+
committed, changed since its commit, committed and since deleted — not in HEAD, though `git log -1`
|
|
63
|
+
names the deleting commit and the tree is clean — git unable to answer, no decision file named, a
|
|
64
|
+
decision that is a directory, a decision its own outputs match). A runner calls it first:
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
npx paperlint studies gate papers/my-paper P1-S03 && ./run.sh
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
For an exploratory study it exits 0 and says it has no gate. A study that declares no mode, or whose
|
|
71
|
+
plan is still a draft, is refused.
|
|
72
|
+
|
|
73
|
+
### 3. `repro/outputs-before-decision` — the proof of order a reviewer can re-run
|
|
74
|
+
|
|
75
|
+
An error, reported on `PIPELINE-STATUS.md`. For each decide-first study: every output whose first
|
|
76
|
+
commit is not a strict descendant of the decision file's first commit (earlier, on another line of
|
|
77
|
+
history, or in the same commit) is a finding; deleted outputs count. An output in the tree or in history while the
|
|
78
|
+
decision was never committed is a finding, and so is a decision committed and since deleted
|
|
79
|
+
(`decision-deleted`): it is not in HEAD, so no order is judged against it. A shallow clone, and a
|
|
80
|
+
history git cannot read, are
|
|
81
|
+
findings that say so — never a pass. The study's folder is walked without following a symbolic link
|
|
82
|
+
(`ListEntries`, `src/ports/list-entries.ts`): a link is one entry, as git stores it, so a link to the
|
|
83
|
+
folder itself cannot loop the walk and one outside brings no foreign file into the study.
|
|
84
|
+
|
|
85
|
+
**Why a lint rule may ask git here.** Rules in `eslint-rules/` may not run git
|
|
86
|
+
(`eslint.config.mjs`, `GIT_IN_A_RULE`): a pinned sha is deleted by a squash or a gc, and a shallow CI
|
|
87
|
+
checkout has none. This rule pins no sha; it asks the history the checkout has for an order. A squash
|
|
88
|
+
merge keeps that order when the decision was merged before the run — which is the decide-first
|
|
89
|
+
workflow — and a shallow checkout is named by the rule (`fetch-depth: 0`).
|
|
90
|
+
|
|
91
|
+
### 4. The index carries each study's mode
|
|
92
|
+
|
|
93
|
+
`repro/README.md`'s study index (`paperlint studies index`, docs/design/study-registry.md) has three
|
|
94
|
+
columns for this (`src/domain/index-columns.ts`):
|
|
95
|
+
|
|
96
|
+
| column | cell |
|
|
97
|
+
| -------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
98
|
+
| `mode` | `decide_before_run`, `exploratory: <reason>`, or `—` for a study whose plan has none |
|
|
99
|
+
| `decision` | the decision file's first commit (12 characters) and its date (`, deleted since` when it is not in HEAD), or `not committed` |
|
|
100
|
+
| `first output` | the first commit of the oldest output and its date, or `none` |
|
|
101
|
+
|
|
102
|
+
A sample row:
|
|
103
|
+
|
|
104
|
+
```markdown
|
|
105
|
+
| id | title | status | result | in paper | mode | decision | first output | folder |
|
|
106
|
+
| ------ | ------- | ------ | -------- | -------- | ----------------- | ----------------------- | ----------------------- | ---------------------------------- |
|
|
107
|
+
| P1-S01 | Outcome | done | 41 of 60 | yes | decide_before_run | 3f2a9c1d7e40 2026-10-14 | 8b51e0aa42c9 2026-10-15 | [P1-S01-outcome/](P1-S01-outcome/) |
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
- **The mode is compared, the commits are not.** The mode comes from the plan, which is in the tree,
|
|
111
|
+
so `repro/study-registered` reports an index whose mode column is not the plan's. The two commits
|
|
112
|
+
are what git's history said when `studies index` last ran: a squash merge or a rebase gives the
|
|
113
|
+
same files new commits, and a shallow CI clone has none to read, so the lint never compares those
|
|
114
|
+
two columns — comparing them would turn every squash merge red. `studies index` rewrites the README
|
|
115
|
+
whenever any column moved, and in a shallow clone writes `unknown (shallow clone)`. The order
|
|
116
|
+
itself is `repro/outputs-before-decision`'s to judge, not the table's.
|
|
117
|
+
- **The skills read the table.** `draft-paper` writes the Methods sentence from it (decided before
|
|
118
|
+
the run, the decision's commit and date; exploratory studies labelled with their reason);
|
|
119
|
+
`harden-paper` and `pc-panel-review` check that every confirmatory claim rests on a
|
|
120
|
+
`decide_before_run` study whose decision commit precedes its first output, and flag a
|
|
121
|
+
"pre-registered" or "decided before" claim the table does not support; `tighten-paper` keeps that
|
|
122
|
+
sentence; `osf-artifact-upload` and `camera-ready` ship each plan, its decision file and the index,
|
|
123
|
+
with the whole history. Each regenerates the index first, so the commits are this checkout's.
|
|
124
|
+
|
|
125
|
+
### 5. Reads stay inside the paper
|
|
126
|
+
|
|
127
|
+
**What these checks guard against.** paperlint's checks guard against accidents and honest
|
|
128
|
+
mistakes — a run started before the decision was committed, a decision edited or deleted afterwards,
|
|
129
|
+
a path typed wrong — not against an author who controls the repository's history and sets out to
|
|
130
|
+
forge it. The path rules below exist to keep every read inside the paper, so a mistyped row cannot
|
|
131
|
+
make a command or a rule read, judge or write files that belong to nothing in it.
|
|
132
|
+
|
|
133
|
+
Every command and rule that reads a study's folder — `studies gate`, `studies index` and `new`,
|
|
134
|
+
`repro/outputs-before-decision`, and `repro/study-registered`'s reads of plans and result files —
|
|
135
|
+
reads a row only through one predicate, `readableFolders` (`src/study-registry-rule.ts`): its
|
|
136
|
+
`folder` is one folder directly inside `repro/` (`isFolderName`: not empty, not `.` or `..`, no
|
|
137
|
+
separator or NUL) and not a symbolic link (`ListEntries`, read without following links). Such a row
|
|
138
|
+
is never read through: `studies index` and `new` refuse the registry, the gate refuses the study,
|
|
139
|
+
the order rule treats it as no study, and `repro/study-registered` reads its plan and results as
|
|
140
|
+
missing. A plan's path is held inside its folder by the registry rule's own `isInside`, the same on
|
|
141
|
+
every platform (a backslash is refused outright); the decision file is held inside the paper by the
|
|
142
|
+
schema (`staysInside`) and must be a regular file on disk — a directory or a symbolic link is refused
|
|
143
|
+
by the gate and reported by the rule (`decision-not-file`). Only a **ready** plan's mode decides
|
|
144
|
+
anything: the gate refuses a draft, naming its empty fields, and the order rule judges no draft — a
|
|
145
|
+
draft may name no outputs, or an empty decision file.
|
|
146
|
+
|
|
147
|
+
### 6. Legacy plans
|
|
148
|
+
|
|
149
|
+
A plan still named `prereg.json` is parsed by its loose legacy schema, which never asks for a mode,
|
|
150
|
+
and `repro/study-plan-name` keeps warning that it moves to `plan.json` — where the strict parser asks
|
|
151
|
+
for one. So an old paper keeps linting as before until it renames the file. A `plan.json` written by
|
|
152
|
+
the release before modes existed, for a measurement, reads as a draft whose empty text is `mode`:
|
|
153
|
+
silent while the study is `designing`, reported once it runs, and an unreadable plan when the
|
|
154
|
+
author's check is already on it (the message names `mode`). Adding one line — usually the
|
|
155
|
+
decide-first mode — is the migration. An index generated before the mode columns existed is out of
|
|
156
|
+
sync until `paperlint studies index` regenerates it.
|
|
157
|
+
|
|
158
|
+
### 7. Why: confirmatory vs exploratory, and venues that review the plan first
|
|
159
|
+
|
|
160
|
+
Read for this note (2026-10-16):
|
|
161
|
+
|
|
162
|
+
- Nosek, Ebersole, DeHaven, Mellor, "The preregistration revolution", _PNAS_ 115(11):2600–2606,
|
|
163
|
+
2018 (metadata and abstract from Crossref, doi:10.1073/pnas.1708274114). The abstract, verbatim:
|
|
164
|
+
|
|
165
|
+
> Progress in science relies in part on generating hypotheses with existing observations and
|
|
166
|
+
> testing hypotheses with new observations. This distinction between postdiction and prediction
|
|
167
|
+
> is appreciated conceptually but is not respected in practice. Mistaking generation of
|
|
168
|
+
> postdictions with testing of predictions reduces the credibility of research findings. […] An
|
|
169
|
+
> effective solution is to define the research questions and analysis plan before observing the
|
|
170
|
+
> research outcomes—a process called preregistration. Preregistration distinguishes analyses and
|
|
171
|
+
> outcomes that result from predictions from those that result from postdictions.
|
|
172
|
+
|
|
173
|
+
This is the distinction the modes encode: decided first is prediction; exploratory is postdiction,
|
|
174
|
+
legitimate when it is labelled.
|
|
175
|
+
|
|
176
|
+
- The MSR (Mining Software Repositories) Registered Reports track, 2020 edition
|
|
177
|
+
(https://2020.msrconf.org/track/msr-2020-Registered-Reports; the 2021, 2025 and 2026 editions have a
|
|
178
|
+
track page at the same address, the 2019 site has none there). Quoted from the 2020 page:
|
|
179
|
+
|
|
180
|
+
> Methods and proposed analyses are pre-registered and reviewed prior to research being conducted.
|
|
181
|
+
|
|
182
|
+
> (MSR 2020) Phase 1: Introduction, Methods (including proposed analyses), and Pilot Data (where
|
|
183
|
+
> applicable).
|
|
184
|
+
|
|
185
|
+
> (EMSE) Phase 2: full study, after data collection and analysis.
|
|
186
|
+
|
|
187
|
+
> Additional exploratory analyses in Phase 2 may be conducted, if they are justified.
|
|
188
|
+
|
|
189
|
+
> Any deviation from the protocol must be justified and logged in detail to ensure replicability.
|
|
190
|
+
|
|
191
|
+
A venue of this field already reviews the plan before the data, and accepts exploratory analyses
|
|
192
|
+
beside it only when they are justified. The modes, the order rule and the index give a paper the
|
|
193
|
+
same evidence in its own repository: what was fixed before the run, with its commit, and what was
|
|
194
|
+
exploratory, with its reason.
|
|
195
|
+
|
|
196
|
+
## Alternatives considered
|
|
197
|
+
|
|
198
|
+
- **Encrypting the outputs until the decision (age, to a key the author holds): rejected.** It needs
|
|
199
|
+
an external binary that `paperlint toolchain` does not install, and a key whose custody has no good
|
|
200
|
+
home: a session in the cloud has nowhere private to keep the identity, and a key in a CI secret is
|
|
201
|
+
readable by any workflow that can be edited. Decide first already proves the order from git with no
|
|
202
|
+
key at all, so encryption would add a dependency and a secret to guard to buy what the history
|
|
203
|
+
shows. Blind analysis in physics (Klein & Roodman, _Annu. Rev. Nucl. Part. Sci._ 55, 2005) and
|
|
204
|
+
MacCoun & Perlmutter, "Hide results to seek the truth" (_Nature_ 526, 2015) — both cited from
|
|
205
|
+
memory, not re-read for this note — are where the idea comes from; what is kept is that the
|
|
206
|
+
decision is fixed and recorded before the outcome is seen.
|
|
207
|
+
- **Paragraph links (`% study: <id>` comments in `paper.tex`) checked by lint rules: rejected.** Such
|
|
208
|
+
rules check only the paragraphs that carry a convention authors do not write, so an unlinked
|
|
209
|
+
overclaim passes silently. The review skills check the Methods sentence and every confirmatory
|
|
210
|
+
claim against the index instead, which covers the whole text.
|
|
211
|
+
- **A separate `studies methods` command printing the modes and commits: rejected.** The same facts
|
|
212
|
+
are three columns of the `studies index` table the registry already generates; a second command
|
|
213
|
+
would be a second rendering of one set of facts.
|
|
214
|
+
- **Comparing the decision and first-output columns in the lint: rejected.** A squash merge renames
|
|
215
|
+
the commits, so the index would go stale on every merge, and a shallow CI clone cannot read them;
|
|
216
|
+
the order rule judges the history directly instead (§4).
|
|
217
|
+
- **An outside pre-registration (OSF, AsPredicted) as the record: not required.** A timestamped
|
|
218
|
+
record before the outcome is what they give; here the repository's history is that record,
|
|
219
|
+
re-checkable by anyone who clones it. A paper may still register outside and cite it.
|
|
220
|
+
|
|
221
|
+
## Where each part lives
|
|
222
|
+
|
|
223
|
+
| part | file | layer |
|
|
224
|
+
| ------------------------------------------------------------- | ------------------------------------- | ------- |
|
|
225
|
+
| the modes, what is an output, which decision a mode waits on | `src/domain/study-mode.ts` | domain |
|
|
226
|
+
| the order judge and its messages | `src/domain/decide-first.ts` | domain |
|
|
227
|
+
| the index's mode columns and the history they show | `src/domain/index-columns.ts` | domain |
|
|
228
|
+
| the index, which columns the lint compares, `isFolderName` | `src/domain/study-registry.ts` | domain |
|
|
229
|
+
| the schema of `mode`; readiness by kind | `src/adapters/study-registry/` | adapter |
|
|
230
|
+
| git: committed state, first commits, ancestry, shallow | `src/adapters/git/` | adapter |
|
|
231
|
+
| what the app asks of git | `src/ports/git-history.ts` | port |
|
|
232
|
+
| a directory's entries, links not followed | `src/ports/list-entries.ts` | port |
|
|
233
|
+
| the studies with a mode, and the files under a study's folder | `src/study-modes.ts` | app |
|
|
234
|
+
| the order rule | `src/outputs-before-decision-rule.ts` | app |
|
|
235
|
+
| the plans and histories `studies index` and `new` write from | `src/study-index-input.ts` | app |
|
|
236
|
+
| `gate`; `new` and `index` | `src/study-gate.ts`, `src/studies.ts` | app |
|
|
237
|
+
| wiring: git with PATH and HOME, the clock | `src/cli.ts` | root |
|
|
238
|
+
|
|
239
|
+
## What it does not check
|
|
240
|
+
|
|
241
|
+
- What a decision says. The gate is that it is committed, not its content.
|
|
242
|
+
- A decision amended after the outputs: `repro/outputs-before-decision` orders FIRST commits; later
|
|
243
|
+
edits of the decision are in its history but not reported.
|
|
244
|
+
- That nobody ran a decide-first study before its decision and kept the outputs out of git. The rule
|
|
245
|
+
keeps early outputs out of the repository's history, not out of a person's hands.
|
|
246
|
+
- Files outside the study's folder as outputs: outputs are relative to it by construction.
|
|
247
|
+
- Whether the paper's text matches the index: that is the review skills' job, not a lint rule's.
|
|
@@ -47,9 +47,13 @@ two commands that write it.
|
|
|
47
47
|
| `superseded_by` | optional; an id in the registry. Required when `superseded` |
|
|
48
48
|
| `in_paper` | `yes`, `no`, `registry` (listed only in the registry, not in the text) |
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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.
|
|
53
57
|
|
|
54
58
|
2. **The rule `repro/study-registered`, an error,** reported on the first line of
|
|
55
59
|
`PIPELINE-STATUS.md` like `repro/allowed-files-only`, once per finding:
|
|
@@ -74,8 +78,9 @@ two commands that write it.
|
|
|
74
78
|
3. **Two commands**, in `src/studies.ts`, wired in `src/cli.ts`:
|
|
75
79
|
|
|
76
80
|
- `paperlint studies new <paper-dir> "<title>" [--kind measurement]` takes the next id, creates
|
|
77
|
-
`repro/<id>-<slug>/README.md` (frontmatter `id:` and `title:`)
|
|
78
|
-
`status: designing`, `in_paper: no
|
|
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
|
|
79
84
|
title over 8 words, a title with no letter or digit to make a slug from, an unknown kind, a folder
|
|
80
85
|
that already exists, and a paper with no `studies.json` (the prefix is the author's choice, so the
|
|
81
86
|
refusal names the file to write and does not guess one).
|
|
@@ -93,19 +98,133 @@ two commands that write it.
|
|
|
93
98
|
`paperlint studies new`, and update its row (status, result, result file, in paper) when it ends,
|
|
94
99
|
then run `paperlint studies index`.
|
|
95
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
|
+
8. **A study's mode.** A measurement's plan declares how its outcomes are kept from the decisions
|
|
209
|
+
on its inputs: `decide_before_run` (the default `studies new` writes) or `exploratory` with its
|
|
210
|
+
reason. `paperlint studies gate` and the rule `repro/outputs-before-decision` hold it, and the
|
|
211
|
+
index carries it in three columns: the mode, the decision's first commit and date, and the first
|
|
212
|
+
output's commit. Its own note: [`study-modes.md`](study-modes.md).
|
|
213
|
+
|
|
96
214
|
## Where each part lives
|
|
97
215
|
|
|
98
|
-
| part
|
|
99
|
-
|
|
|
100
|
-
| the types, the judge, the index, ids
|
|
101
|
-
| the
|
|
102
|
-
|
|
|
103
|
-
| the
|
|
104
|
-
|
|
|
105
|
-
|
|
|
216
|
+
| part | file | layer |
|
|
217
|
+
| -------------------------------------- | ----------------------------------- | ------- |
|
|
218
|
+
| the types, the judge, the index, ids | `src/domain/study-registry.ts` | domain |
|
|
219
|
+
| the plan's type and template | `src/domain/study-registry.ts` | domain |
|
|
220
|
+
| the schemas (zod) of registry and plan | `src/adapters/study-registry/` | adapter |
|
|
221
|
+
| what the app asks of a parser | `src/ports/study-registry-codec.ts` | port |
|
|
222
|
+
| both rules: list, read, report | `src/study-registry-rule.ts` | app |
|
|
223
|
+
| the two commands | `src/studies.ts` | app |
|
|
224
|
+
| wiring, the clock for today's date | `src/cli.ts` | root |
|
|
106
225
|
|
|
107
226
|
The judge is pure: it takes the folder names, the parsed registry (or why it is absent or unreadable),
|
|
108
|
-
the README's text,
|
|
227
|
+
the README's text, which result files exist, and each named plan parsed, and returns findings. The rule and the commands are
|
|
109
228
|
the only places that touch the disk, through the `Files` and `ListDir` ports.
|
|
110
229
|
|
|
111
230
|
## Generated index, and prettier
|
|
@@ -127,4 +246,7 @@ a cell is escaped and a line break becomes a space.
|
|
|
127
246
|
|
|
128
247
|
- Whether the `question` is one sentence, or the `result` one line, beyond their being strings.
|
|
129
248
|
- What a study's `README.md` says, or whether its frontmatter matches the row.
|
|
130
|
-
- Files inside a study folder other than `result_file
|
|
249
|
+
- Files inside a study folder other than `result_file` and the `plan` its row names.
|
|
250
|
+
- `study/declare-sample` still reads only `prereg.json`, and asks for different fields
|
|
251
|
+
(`strata`, `screens`, `coverage`, and an `author_check` of `who`, `when`, `units_seen`,
|
|
252
|
+
`units_drawn`, `verdict`); merging the two shapes is its own decision.
|
package/docs/e2e.md
CHANGED
|
@@ -8,10 +8,14 @@ or CI:
|
|
|
8
8
|
| -------------------------- | ------------- | -------------------------------------------- | --------------------------- |
|
|
9
9
|
| `npm run test:e2e:install` | `e2e-install` | npm and pnpm, and the network for `npm pack` | `test/e2e/install/*.e2e.ts` |
|
|
10
10
|
| `npm run test:e2e:tex` | `e2e-tex` | TeX Live from `paperlint toolchain`, banal | `test/e2e/tex/*.e2e.ts` |
|
|
11
|
-
| `npm run test:e2e`
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
| `npm run test:e2e:studies` | `e2e-studies` | git | `test/e2e/studies/*.e2e.ts` |
|
|
12
|
+
| `npm run test:e2e` | all three | | |
|
|
13
|
+
|
|
14
|
+
All three areas are part of `npm run check`, so nobody has to remember to call them. CI runs each in
|
|
15
|
+
the job that has its environment: `test:e2e:install` and `test:e2e:studies` in `gates`, `test:e2e:tex`
|
|
16
|
+
in `build-e2e`. The studies run drives a study decided before its run through the real binary in a
|
|
17
|
+
real git repository: the gate, the order the lint reads, a shallow clone named, and the two commits
|
|
18
|
+
`studies index` writes into the index.
|
|
15
19
|
|
|
16
20
|
This page says what they prove, what they deliberately do not, and when a change owes a new one.
|
|
17
21
|
|
package/docs/layout.md
CHANGED
|
@@ -18,8 +18,8 @@ flowchart TB
|
|
|
18
18
|
subgraph S3["3 · study: repro/#lt;id#gt;-#lt;slug#gt;/"]
|
|
19
19
|
direction TB
|
|
20
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>
|
|
22
|
-
A["<b>author check</b> · you see the drawn units<br/>writes: author_check in
|
|
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
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"]
|
|
@@ -110,7 +110,9 @@ Both are explained in [`venues.md`](venues.md).
|
|
|
110
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
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
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>/
|
|
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>/decision.md` | 3 study | you: the decisions on the inputs of a study decided before its run, committed before the run (`paperlint studies gate`); the plan names it, and it may live elsewhere in the paper | [`repro/outputs-before-decision`](rules/repro/outputs-before-decision.md) |
|
|
114
116
|
| `repro/<id>-<slug>/data/` | 3 study | `build-label-page` exports the labels here | nothing |
|
|
115
117
|
| `repro/tools/`, `repro/data/` | 3 study | your scripts; `paperlint build` keeps `repro/data/references-cache.json` | nothing inside them |
|
|
116
118
|
| `NOTES.md` | 3, then 4 | you: each number → the data it comes from; `build-label-page` adds a line per label file | nothing |
|
|
@@ -134,24 +136,27 @@ A check runs at one of four moments:
|
|
|
134
136
|
The lint-only job skips what needs a build (pages, fonts, references).
|
|
135
137
|
- **On demand** — a skill, when you ask for what it does. A skill stops nothing; it writes.
|
|
136
138
|
|
|
137
|
-
| what | kind
|
|
138
|
-
| ------------------------------------------------------------------------------------------- |
|
|
139
|
-
| `paper-folder-layout` | hook
|
|
140
|
-
| `paper-edit-guard` | hook
|
|
141
|
-
| `paper-skills-nudge` | hook
|
|
142
|
-
| `paper-status-gates` | hook
|
|
143
|
-
| [`paper/allowed-files-only`](rules/paper/allowed-files-only.md) | rule
|
|
144
|
-
| [`repro/allowed-files-only`](rules/repro/allowed-files-only.md) | rule
|
|
145
|
-
| [`repro/study-registered`](rules/repro/study-registered.md) | rule
|
|
146
|
-
| [`study
|
|
147
|
-
| [`
|
|
148
|
-
| `
|
|
149
|
-
| `
|
|
150
|
-
|
|
|
151
|
-
|
|
|
152
|
-
| `
|
|
153
|
-
|
|
|
154
|
-
| `build
|
|
139
|
+
| what | kind | when it runs | what it stops |
|
|
140
|
+
| ------------------------------------------------------------------------------------------- | ------- | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
141
|
+
| `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 |
|
|
142
|
+
| `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 |
|
|
143
|
+
| `paper-skills-nudge` | hook | after an agent edits a paper's source | nothing: it shows the agent the pre-submit checklist |
|
|
144
|
+
| `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 |
|
|
145
|
+
| [`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) |
|
|
146
|
+
| [`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) |
|
|
147
|
+
| [`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) |
|
|
148
|
+
| [`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` |
|
|
149
|
+
| [`repro/outputs-before-decision`](rules/repro/outputs-before-decision.md) | rule | every `paperlint lint`, and CI (with the whole history) | an output of a study decided before its run that git shows was not made after the decision |
|
|
150
|
+
| `paperlint studies gate` | command | before a decide-first study's run | the run, until the decision is committed |
|
|
151
|
+
| [`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) |
|
|
152
|
+
| [`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 |
|
|
153
|
+
| `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) |
|
|
154
|
+
| `paper/stages`, `paper/source` | rule | every `paperlint lint`, and CI | a stage whose frozen PDF or source is missing or changed (error) |
|
|
155
|
+
| the rules on the source (`tex/*`, `paper/*`, `bib/*`) | rule | every `paperlint lint`, and CI | what each one's [row](rules.md) says |
|
|
156
|
+
| 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 |
|
|
157
|
+
| `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` |
|
|
158
|
+
| `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 |
|
|
159
|
+
| `build-label-page` | skill | when you ask; stage 3 | nothing: it builds the labelling page, and exports the answers into the study's `data/` |
|
|
155
160
|
|
|
156
161
|
The hooks are wired by `paperlint init` ([`install.md`](install.md)). A rule's level and options:
|
|
157
162
|
[`rules.md`](rules.md).
|