paperlint 13.0.3 → 13.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/CLAUDE.md +9 -1
  2. package/dist/adapters/git/index.d.ts.map +1 -1
  3. package/dist/adapters/git/index.js +13 -0
  4. package/dist/adapters/git/index.js.map +1 -1
  5. package/dist/adapters/study-registry/index.d.ts +142 -1
  6. package/dist/adapters/study-registry/index.d.ts.map +1 -1
  7. package/dist/adapters/study-registry/index.js +160 -1
  8. package/dist/adapters/study-registry/index.js.map +1 -1
  9. package/dist/cli.d.ts.map +1 -1
  10. package/dist/cli.js +3 -0
  11. package/dist/cli.js.map +1 -1
  12. package/dist/domain/decide-first.d.ts +43 -0
  13. package/dist/domain/decide-first.d.ts.map +1 -1
  14. package/dist/domain/decide-first.js +45 -2
  15. package/dist/domain/decide-first.js.map +1 -1
  16. package/dist/domain/study-gates.d.ts +96 -0
  17. package/dist/domain/study-gates.d.ts.map +1 -0
  18. package/dist/domain/study-gates.js +56 -0
  19. package/dist/domain/study-gates.js.map +1 -0
  20. package/dist/domain/study-mode.d.ts +6 -0
  21. package/dist/domain/study-mode.d.ts.map +1 -1
  22. package/dist/domain/study-mode.js.map +1 -1
  23. package/dist/domain/study-registry.d.ts +24 -4
  24. package/dist/domain/study-registry.d.ts.map +1 -1
  25. package/dist/domain/study-registry.js +39 -2
  26. package/dist/domain/study-registry.js.map +1 -1
  27. package/dist/domain/study-result.d.ts +151 -0
  28. package/dist/domain/study-result.d.ts.map +1 -0
  29. package/dist/domain/study-result.js +175 -0
  30. package/dist/domain/study-result.js.map +1 -0
  31. package/dist/outputs-before-decision-rule.d.ts.map +1 -1
  32. package/dist/outputs-before-decision-rule.js +37 -3
  33. package/dist/outputs-before-decision-rule.js.map +1 -1
  34. package/dist/ports/git-history.d.ts +12 -0
  35. package/dist/ports/git-history.d.ts.map +1 -1
  36. package/dist/ports/study-registry-codec.d.ts +3 -0
  37. package/dist/ports/study-registry-codec.d.ts.map +1 -1
  38. package/dist/skills/build-label-page/scripts/label-page-cli.d.ts +6 -0
  39. package/dist/skills/build-label-page/scripts/label-page-commands.d.ts +14 -0
  40. package/dist/skills/build-label-page/scripts/label-page-core.d.ts +149 -0
  41. package/dist/skills/build-label-page/scripts/label-page-html.d.ts +5 -0
  42. package/dist/skills/build-label-page/scripts/label-page.d.ts +2 -0
  43. package/dist/study-modes.d.ts +3 -0
  44. package/dist/study-modes.d.ts.map +1 -1
  45. package/dist/study-modes.js +8 -1
  46. package/dist/study-modes.js.map +1 -1
  47. package/dist/study-registry-rule.d.ts.map +1 -1
  48. package/dist/study-registry-rule.js +1 -0
  49. package/dist/study-registry-rule.js.map +1 -1
  50. package/dist/study-result-rule.d.ts +12 -0
  51. package/dist/study-result-rule.d.ts.map +1 -0
  52. package/dist/study-result-rule.js +87 -0
  53. package/dist/study-result-rule.js.map +1 -0
  54. package/docs/design/study-gates.md +208 -0
  55. package/docs/rules/repro/outputs-before-decision.md +8 -0
  56. package/docs/rules/repro/study-registered.md +38 -15
  57. package/docs/rules/repro/study-result.md +100 -0
  58. package/docs/rules.md +1 -0
  59. package/eslint.config.mjs +1 -1
  60. package/hooks/hooks.harness.ts +51 -13
  61. package/hooks/paper-skills-nudge.hook.d.mts +22 -0
  62. package/hooks/paper-skills-nudge.hook.mjs +85 -23
  63. package/hooks/paper-skills-nudge.test.ts +82 -4
  64. package/package.json +2 -1
  65. package/plugin/hooks/hooks.json +7 -2
  66. package/schemas/plan.v2.schema.json +341 -0
  67. package/skills/argument-arc/SKILL.md.spec.ts +1 -1
  68. package/skills/build-label-page/SKILL.md +85 -54
  69. package/skills/build-label-page/SKILL.md.spec.ts +85 -53
  70. package/skills/build-label-page/build-label-page.test.ts +10 -941
  71. package/skills/build-label-page/references/checklist.md +45 -3
  72. package/skills/build-label-page/references/closed-choice-generator.md +88 -0
  73. package/skills/build-label-page/scripts/label-page-cli.js +72 -0
  74. package/skills/build-label-page/scripts/label-page-cli.ts +85 -0
  75. package/skills/build-label-page/scripts/label-page-commands.js +14 -0
  76. package/skills/build-label-page/scripts/label-page-commands.ts +14 -0
  77. package/skills/build-label-page/scripts/label-page-core.js +850 -0
  78. package/skills/build-label-page/scripts/label-page-core.test.ts +1164 -0
  79. package/skills/build-label-page/scripts/label-page-core.ts +1210 -0
  80. package/skills/build-label-page/scripts/label-page-html.js +33 -0
  81. package/skills/build-label-page/scripts/label-page-html.test.ts +413 -0
  82. package/skills/build-label-page/scripts/label-page-html.ts +43 -0
  83. package/skills/build-label-page/scripts/label-page.js +70 -0
  84. package/skills/build-label-page/scripts/label-page.test.ts +71 -0
  85. package/skills/build-label-page/scripts/label-page.ts +71 -0
  86. package/skills/build-label-page/scripts/page-template.html +513 -0
  87. package/skills/find-venue/SKILL.md.spec.ts +1 -1
  88. package/skills/paper-status/SKILL.md.spec.ts +1 -1
  89. package/skills/render-paper/SKILL.md.spec.ts +1 -1
  90. package/src/adapters/git/index.test.ts +33 -0
  91. package/src/adapters/git/index.ts +19 -0
  92. package/src/adapters/study-registry/gates.test.ts +181 -0
  93. package/src/adapters/study-registry/index.ts +219 -1
  94. package/src/adapters/study-registry/mode.test.ts +6 -0
  95. package/src/cli.ts +3 -0
  96. package/src/domain/decide-first.test.ts +131 -0
  97. package/src/domain/decide-first.ts +92 -2
  98. package/src/domain/study-gates.ts +125 -0
  99. package/src/domain/study-measurements.test.ts +71 -0
  100. package/src/domain/study-mode.ts +6 -0
  101. package/src/domain/study-registry.ts +64 -2
  102. package/src/domain/study-result.test.ts +300 -0
  103. package/src/domain/study-result.ts +358 -0
  104. package/src/outputs-before-decision-rule.test.ts +115 -2
  105. package/src/outputs-before-decision-rule.ts +50 -2
  106. package/src/ports/git-history.ts +12 -0
  107. package/src/ports/study-registry-codec.ts +3 -0
  108. package/src/rule-cases.test.ts +74 -0
  109. package/src/studies.test.ts +3 -0
  110. package/src/study-modes.ts +10 -1
  111. package/src/study-registry-rule.ts +1 -0
  112. package/src/study-result-rule.test.ts +281 -0
  113. package/src/study-result-rule.ts +123 -0
  114. package/skills/build-label-page/references/example-generator.py +0 -803
@@ -0,0 +1,341 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "properties": {
5
+ "question": {
6
+ "type": "string"
7
+ },
8
+ "population": {
9
+ "type": "object",
10
+ "properties": {
11
+ "source": {
12
+ "type": "string"
13
+ },
14
+ "draw": {
15
+ "oneOf": [
16
+ {
17
+ "type": "object",
18
+ "properties": {
19
+ "kind": {
20
+ "type": "string",
21
+ "const": "seeded"
22
+ },
23
+ "method": {
24
+ "type": "string"
25
+ },
26
+ "seed": {
27
+ "type": ["string", "number"]
28
+ }
29
+ },
30
+ "required": ["kind", "method", "seed"],
31
+ "additionalProperties": false
32
+ },
33
+ {
34
+ "type": "object",
35
+ "properties": {
36
+ "kind": {
37
+ "type": "string",
38
+ "const": "all"
39
+ },
40
+ "method": {
41
+ "type": "string"
42
+ }
43
+ },
44
+ "required": ["kind", "method"],
45
+ "additionalProperties": false
46
+ }
47
+ ]
48
+ }
49
+ },
50
+ "required": ["source", "draw"],
51
+ "additionalProperties": false
52
+ },
53
+ "arms": {
54
+ "type": "array",
55
+ "items": {
56
+ "type": "object",
57
+ "properties": {
58
+ "name": {
59
+ "type": "string",
60
+ "minLength": 1
61
+ }
62
+ },
63
+ "required": ["name"],
64
+ "additionalProperties": false
65
+ }
66
+ },
67
+ "primary_outcome": {
68
+ "type": "string"
69
+ },
70
+ "author_check": {
71
+ "anyOf": [
72
+ {
73
+ "type": "object",
74
+ "properties": {
75
+ "by": {
76
+ "type": "string",
77
+ "minLength": 1
78
+ },
79
+ "date": {
80
+ "type": "string",
81
+ "format": "date",
82
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
83
+ },
84
+ "quote": {
85
+ "type": "string",
86
+ "minLength": 1
87
+ }
88
+ },
89
+ "required": ["by", "date", "quote"],
90
+ "additionalProperties": false
91
+ },
92
+ {
93
+ "type": "null"
94
+ }
95
+ ]
96
+ },
97
+ "extra": {
98
+ "type": "object",
99
+ "propertyNames": {
100
+ "type": "string"
101
+ },
102
+ "additionalProperties": {}
103
+ },
104
+ "mode": {
105
+ "oneOf": [
106
+ {
107
+ "type": "object",
108
+ "properties": {
109
+ "kind": {
110
+ "type": "string",
111
+ "const": "decide_before_run"
112
+ },
113
+ "outputs": {
114
+ "type": "array",
115
+ "items": {
116
+ "type": "string",
117
+ "minLength": 1
118
+ }
119
+ },
120
+ "decision": {
121
+ "type": "object",
122
+ "properties": {
123
+ "file": {
124
+ "type": "string"
125
+ },
126
+ "note": {
127
+ "type": "string"
128
+ }
129
+ },
130
+ "required": ["file", "note"],
131
+ "additionalProperties": false
132
+ },
133
+ "we_lose_if": {
134
+ "anyOf": [
135
+ {
136
+ "anyOf": [
137
+ {
138
+ "type": "object",
139
+ "properties": {
140
+ "result": {
141
+ "type": "string",
142
+ "minLength": 1
143
+ },
144
+ "below": {
145
+ "type": "number"
146
+ }
147
+ },
148
+ "required": ["result", "below"],
149
+ "additionalProperties": false
150
+ },
151
+ {
152
+ "type": "object",
153
+ "properties": {
154
+ "result": {
155
+ "type": "string",
156
+ "minLength": 1
157
+ },
158
+ "above": {
159
+ "type": "number"
160
+ }
161
+ },
162
+ "required": ["result", "above"],
163
+ "additionalProperties": false
164
+ }
165
+ ]
166
+ },
167
+ {
168
+ "type": "null"
169
+ }
170
+ ]
171
+ }
172
+ },
173
+ "required": ["kind", "outputs", "decision"],
174
+ "additionalProperties": false
175
+ },
176
+ {
177
+ "type": "object",
178
+ "properties": {
179
+ "kind": {
180
+ "type": "string",
181
+ "const": "exploratory"
182
+ },
183
+ "reason": {
184
+ "type": "string"
185
+ }
186
+ },
187
+ "required": ["kind", "reason"],
188
+ "additionalProperties": false
189
+ }
190
+ ]
191
+ },
192
+ "$schema": {
193
+ "type": "string",
194
+ "const": "https://unpkg.com/paperlint/schemas/plan.v2.schema.json"
195
+ },
196
+ "second_check": {
197
+ "anyOf": [
198
+ {
199
+ "anyOf": [
200
+ {
201
+ "type": "string",
202
+ "const": "none"
203
+ },
204
+ {
205
+ "minItems": 1,
206
+ "type": "array",
207
+ "items": {
208
+ "type": "object",
209
+ "properties": {
210
+ "step": {
211
+ "type": "string",
212
+ "minLength": 1
213
+ },
214
+ "min_kappa": {
215
+ "type": "number",
216
+ "exclusiveMinimum": 0,
217
+ "maximum": 1
218
+ }
219
+ },
220
+ "required": ["step", "min_kappa"],
221
+ "additionalProperties": false
222
+ }
223
+ }
224
+ ]
225
+ },
226
+ {
227
+ "type": "null"
228
+ }
229
+ ]
230
+ },
231
+ "self_test": {
232
+ "anyOf": [
233
+ {
234
+ "anyOf": [
235
+ {
236
+ "type": "string",
237
+ "const": "none"
238
+ },
239
+ {
240
+ "type": "object",
241
+ "properties": {
242
+ "made_by": {
243
+ "type": "string",
244
+ "minLength": 1
245
+ }
246
+ },
247
+ "required": ["made_by"],
248
+ "additionalProperties": false
249
+ },
250
+ {
251
+ "type": "object",
252
+ "properties": {
253
+ "must_catch": {
254
+ "type": "string",
255
+ "minLength": 1
256
+ },
257
+ "must_pass": {
258
+ "type": "string",
259
+ "minLength": 1
260
+ }
261
+ },
262
+ "required": ["must_catch", "must_pass"],
263
+ "additionalProperties": false
264
+ }
265
+ ]
266
+ },
267
+ {
268
+ "type": "null"
269
+ }
270
+ ]
271
+ },
272
+ "funnel": {
273
+ "anyOf": [
274
+ {
275
+ "anyOf": [
276
+ {
277
+ "type": "string",
278
+ "const": "none"
279
+ },
280
+ {
281
+ "minItems": 1,
282
+ "type": "array",
283
+ "items": {
284
+ "type": "string",
285
+ "minLength": 1
286
+ }
287
+ }
288
+ ]
289
+ },
290
+ {
291
+ "type": "null"
292
+ }
293
+ ]
294
+ },
295
+ "recall_audit": {
296
+ "anyOf": [
297
+ {
298
+ "anyOf": [
299
+ {
300
+ "type": "string",
301
+ "const": "none"
302
+ },
303
+ {
304
+ "type": "object",
305
+ "properties": {
306
+ "n_files": {
307
+ "type": "integer",
308
+ "exclusiveMinimum": 0,
309
+ "maximum": 9007199254740991
310
+ },
311
+ "min_recall": {
312
+ "type": "number",
313
+ "exclusiveMinimum": 0,
314
+ "maximum": 1
315
+ }
316
+ },
317
+ "required": ["n_files", "min_recall"],
318
+ "additionalProperties": false
319
+ }
320
+ ]
321
+ },
322
+ {
323
+ "type": "null"
324
+ }
325
+ ]
326
+ }
327
+ },
328
+ "required": [
329
+ "question",
330
+ "population",
331
+ "arms",
332
+ "primary_outcome",
333
+ "author_check",
334
+ "$schema",
335
+ "second_check",
336
+ "self_test",
337
+ "funnel",
338
+ "recall_audit"
339
+ ],
340
+ "additionalProperties": false
341
+ }
@@ -3,7 +3,7 @@
3
3
  // Adopted 2026-08-17 as part of the second batch. The body is the previous SKILL.md
4
4
  // VERBATIM, so the compiled diff shows only what the compiler adds. No `disallowedTools`
5
5
  // fence yet: the field exists on `SkillSpec` as of the branch `claude/skill-disallowed-tools`
6
- // but is not in a release `mine` installs, so adding it here would not compile.
6
+ // but is not in a release the consumer installs, so adding it here would not compile.
7
7
  import { experimental_skill } from "vigiles/spec";
8
8
 
9
9
  export default experimental_skill({
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  name: build-label-page
3
- description: Build a phone labelling page published as a claude.ai Artifact with a shared answer store, and export the answers into the paper's study folder. Use when a study needs a person to label items (gold labels, a blind review of model outputs, "is this a real boundary case"), or on "a page to label these", "a gold-labelling page", "a blind review page". Carries the seven rules that keep such a page honest. Composes with build-benchmark and draft-paper.
3
+ description: Build a phone labelling page published as a claude.ai Artifact with a shared answer store, and export the answers into the paper's study folder. Use when a study needs a person to label items (gold labels, a blind review of model outputs, "is this a real boundary case"), or on "a page to label these", "a gold-labelling page", "a blind review page". Carries the eight rules that keep such a page honest, the last one a check that the labeller understands each question before the real items. Composes with build-benchmark and draft-paper.
4
4
  allowed-tools: [Read, Write, Edit, Grep, Glob, Bash, Artifact, ArtifactData]
5
5
  ---
6
6
 
7
- <!-- vigiles:sha256:dd742c1fcb34cfd6 compiled from skills/build-label-page/SKILL.md.spec.ts -->
7
+ <!-- vigiles:sha256:6323fa135f385e8b compiled from skills/build-label-page/SKILL.md.spec.ts -->
8
8
 
9
9
  # build-label-page — a page the person labels on a phone, and the labels back in the repo
10
10
 
@@ -18,10 +18,39 @@ committed, not left in the store.
18
18
  **Not for** an LLM judge (no person answers), a questionnaire about the person, or a form that collects
19
19
  anything but labels.
20
20
 
21
- ## The seven rules
22
-
23
- The full text, each with its reason and its check, is
24
- [`references/checklist.md`](references/checklist.md). Read it before building. In one line each:
21
+ ## Which page you need
22
+
23
+ | the labeller… | page |
24
+ | --- | --- |
25
+ | looks at one item and picks from a closed list: gold labels, a blind review of model outputs, fits / text silent / contradicts, an approval (keep / drop, and why), with an optional reason box and code blocks | **the generator** of this skill |
26
+ | compares two outputs side by side, ranks several items, marks parts (spans) of a text — a recall audit, reading a whole file and marking every rule in it —, gives a number on a slider, or writes free text | **written by the agent** |
27
+
28
+ - **The generator** is `scripts/label-page.js`, run from the skill (commands in the procedure):
29
+ a core with no page layout (`label-page-core.ts`: the `items.json` checks, the leak guard, the
30
+ neutral ids, the key, the export) and the one closed-choice page built from the core's payload
31
+ (`label-page-html.ts`). How to write its `items.json` — examples, practice, `ask_why`, the
32
+ approval page — is
33
+ [`references/closed-choice-generator.md`](references/closed-choice-generator.md).
34
+ - **A page written by the agent** meets the same eight rules, and each rule's **Check** line in
35
+ [`references/checklist.md`](references/checklist.md) is its acceptance list: every one verified,
36
+ and stated in the hand-over, before the link is sent. It also looks the way the generator's pages
37
+ look: one dark palette, one column, answer buttons as full-width rows with space between them,
38
+ icons rather than coloured side stripes, code shown as code with its whitespace visible, built for
39
+ a phone first, and screenshotted at 400px and 1280px wide and looked at before publishing.
40
+ It can still take its blindness and its export from the core: `prepareBuild` checks
41
+ `items.json`, gives the items their neutral ids, writes `key.json` and returns the blind
42
+ payload to lay out, and `export` reads any store that keeps the generator's collections.
43
+ - A second generator, for another page shape, is added only after that shape has been built by
44
+ hand, following the eight rules, twice without its fields changing. Until then the agent writes
45
+ such a page itself.
46
+ - The other ways to build pages that were considered (a JS kit plus an automatic page check; rules
47
+ only; one growing generator), and when to switch: #335.
48
+
49
+ ## The eight rules
50
+
51
+ They hold for every labelling page, generated or written by hand. The full text, each with its
52
+ reason and its check, is [`references/checklist.md`](references/checklist.md). Read it before
53
+ building. In one line each:
25
54
 
26
55
  1. **Test it in the claude.ai phone viewer before sending the link.** The viewer can size the frame to
27
56
  the page, so the page cannot scroll itself and a fixed toast may be off-screen. A plain browser
@@ -32,7 +61,7 @@ The full text, each with its reason and its check, is
32
61
  question; the progress counter counts complete items only.
33
62
  4. **Blind.** Neutral ids (`G001`…), order shuffled with a fixed seed, no model answers, verdicts or
34
63
  source labels on the page. The generator refuses to build when an item carries a field outside an
35
- allow-list; new question wording is added to the allow-list on purpose.
64
+ allow-list; a page written by hand checks its items against the same list.
36
65
  5. **Ask only what the labeller can answer.** Only the languages and tools they know; one "how well do
37
66
  you know it" question per group, used later as an analysis filter; "clear-cut or real boundary"
38
67
  is the labeller's judgement, not the designer's.
@@ -41,69 +70,71 @@ The full text, each with its reason and its check, is
41
70
  take the last answers in chat; patch the page blind at most once.
42
71
  7. **Save on click**, no submit button; "Saved <time>" on every card; survive a reload; render
43
72
  readable, with a banner, when the store is unavailable.
73
+ 8. **The labeller understands each question first.** Every answer option carries one real example,
74
+ shown on the first screen; 3 or more practice items with known answers come before the real
75
+ items, and the page shows the expected answer after each; one question per screen. A practice
76
+ miss means the question is reworded and the practice taken again; practice answers never reach
77
+ the labels.
44
78
 
45
79
  ## Procedure
46
80
 
47
- 1. **Decide the labelling.** What exactly does the labeller decide per item (the questions, each with
48
- a closed set of answers)? Which groups (languages, tools) does the labeller know? How many items
49
- fit the time they have on a phone? Who is the labeller: a page with a store opens only for people
50
- with access to the artifact, so the labeller is the owner or has been shared as a Contributor.
51
- If the study design is not written yet, `build-benchmark` comes first.
52
- If the items come from a drawn sample, the study's `plan.json` (written by
53
- `paperlint studies new`) declares the population and its draw before the sample is frozen, and its
54
- `author_check` records that the author has been shown the drawn **units** (the rules or cases, not
55
- the items) with "is this what you meet in practice?". The labelling page is not that check: items
56
- made from easy units each look fine on their own.
57
- 2. **Copy the generator into the study and write the items.** The study folder is
58
- `<paper-dir>/repro/<id>-<slug>/`, made by `npx paperlint studies new <paper-dir> "Gold labels"
59
- --kind data` when the labelling is a study of its own (`P1-S04-gold-labels`): the command writes
60
- its `README.md`, its `plan.json` and its row in `repro/studies.json`, and `repro/study-registered`
61
- checks them; fill the plan (what is labelled, the items' source, `arms: []`, the primary outcome)
62
- before the first label. When
63
- the labels are in, update the row (`status`, `result`, `result_file`) and run
64
- `npx paperlint studies index <paper-dir>`. Where this sits in stage 3, and what checks each place:
65
- `docs/layout.md` in the paperlint package.
66
- `items.json` follows the header of the generator. Source ids in `items.json` are the study's own;
67
- the page never shows them.
68
-
69
- ```bash
70
- cp ${CLAUDE_SKILL_DIR}/references/example-generator.py <study-dir>/make_label_page.py
71
- ```
72
-
73
- The generator is a template: change the questions and keep the guards (allow-list, neutral ids,
74
- store-unavailable banner, filter instead of scrolling). Do not loosen the allow-list to make a
75
- build pass; take the field out of the item.
76
- 3. **Build and read it as the labeller.** From the study folder (`<study-dir>`):
81
+ 1. **Decide the labelling and the page.** What exactly does the labeller decide per item (the
82
+ questions, each with its answers)? Which page that takes (the table above)? Which groups
83
+ (languages, tools) does the labeller know? How many items fit the time they have on a phone? Who
84
+ is the labeller: a page with a store opens only for people with access to the artifact, so the
85
+ labeller is the owner or has been shared as a Contributor. If the study design is not written
86
+ yet, `build-benchmark` comes first. If the items come from a drawn sample, the study's
87
+ `plan.json` declares the population and its draw before the sample is frozen, and its
88
+ `author_check` records that the author has been shown the drawn **units** (the rules or cases,
89
+ not the items) — on an approval page, not on the labelling page, where items made from easy units
90
+ each look fine on their own.
91
+ 2. **Make the study folder.** `<paper-dir>/repro/<id>-<slug>/`, made by `npx paperlint studies new
92
+ <paper-dir> "Gold labels" --kind data` when the labelling is a study of its own: the command
93
+ writes its `README.md`, its `plan.json` and its row in `repro/studies.json`, and
94
+ `repro/study-registered` checks them; fill the plan before the first label. When the labels are
95
+ in, update the row (`status`, `result`, `result_file`) and run `npx paperlint studies index
96
+ <paper-dir>`. Where this sits in stage 3: `docs/layout.md` in the paperlint package.
97
+ 3. **Build the page.** With the generator, write `items.json`
98
+ ([`references/closed-choice-generator.md`](references/closed-choice-generator.md)) and, from the
99
+ study folder:
77
100
 
78
101
  ```bash
79
- python3 make_label_page.py build items.json label-page
102
+ node ${CLAUDE_SKILL_DIR}/scripts/label-page.js build items.json label-page
80
103
  ```
81
104
 
82
- This writes `label-page/page.html` and `label-page/key.json` (the only link from `G001` to the
83
- source id; it never goes on the page). Rebuilding with the same seed keeps every id, and a new item
84
- gets the next one. Read the page text once as a person who knows nothing about the source: if
85
- anything on it tells the answer, take it out of the item.
86
- 4. **Publish.** Load `artifact-design` and `artifact-capabilities`, then publish `page.html` with
105
+ It writes `label-page/page.html` and `label-page/key.json` (the only link from `G001` to the
106
+ source id; it never goes on the page). Written by hand, the page meets the requirements above.
107
+ 4. **Read it as the labeller, then look at it.** Read the page text once as a person who knows
108
+ nothing about the source: if anything on it tells the answer, take it out. Read each question as
109
+ someone who did not design the study: can they tell what each answer means from the question, its
110
+ examples and nothing else? If not, reword. Then screenshot it at 400px and at 1280px wide with the
111
+ pre-installed Chromium (Playwright), once before any answer and once after the practice, open both
112
+ images, and fix what they show. A page that only passed its tests has not been seen.
113
+ 5. **Publish.** Load `artifact-design` and `artifact-capabilities`, then publish `page.html` with
87
114
  `capabilities: {db: {}}`. Use a title of two to four words. Do not ask the page for `user` or
88
115
  anything else it does not use.
89
- 5. **Test in the viewer (rule 1)** and list the store once with `ArtifactData` (`list` of
116
+ 6. **Test in the viewer (rule 1)** and list the store once with `ArtifactData` (`list` of
90
117
  `answers`) to confirm the test answer arrived. `ArtifactData` can be a deferred tool: load it with
91
118
  `ToolSearch` first. Then delete the test answer's document, or tell the person which one it is
92
119
  and keep it out of the export.
93
- 6. **Send the link** with one line on how it works: tap an answer, it saves; "Next unanswered" shows
94
- what is left. Say what you tested and what you could not.
95
- 7. **Export when the person is done, or stops** (rule 6). Save the store's two collections, `answers`
96
- and `groups`, as `dump.json` in the shape the generator's header gives (answers given in chat go
97
- in with `"source": "chat"`), then:
120
+ 7. **Send the link** with one line on how it works: start with the practice; tap an answer, it saves;
121
+ "Next unanswered" shows what is left. Say what you tested and what you could not. **Check the
122
+ practice before the real answers count** (rule 8): list the store's `practice` collection. A
123
+ miss means a question was unclear: ask the labeller which one and why, reword it and its
124
+ examples, rebuild, republish, and have them take the practice again. Write one line per round in
125
+ the study's README: what was reworded and why.
126
+ 8. **Export when the person is done, or stops** (rule 6). Save the store's collections as
127
+ `dump.json` (answers given in chat go in with `"source": "chat"`). With the generator:
98
128
 
99
129
  ```bash
100
- python3 make_label_page.py export dump.json label-page/key.json data
130
+ node ${CLAUDE_SKILL_DIR}/scripts/label-page.js export dump.json label-page/key.json data
101
131
  ```
102
132
 
103
- It writes `data/<study>-labels-<date>.json` and prints every incomplete item, every answer or
104
- familiarity value that is not one of the offered options (not counted, same rule as on the page;
105
- correct it in the dump and export again) and every group with no familiarity answer. Commit the file in the same pass, and add the line "labels →
106
- `data/<study>-labels-<date>.json`" to the paper's `NOTES.md` for the numbers that use them.
133
+ It writes `data/<study>-labels-<date>.json`, scores the practice into
134
+ `data/<study>-practice-r<round>-<date>.json` beside it, and prints every item it could not count. A page
135
+ written by hand exports to the same two files, practice kept out of the labels. Commit both in the
136
+ same pass, and add the line "labels → `data/<study>-labels-<date>.json`" to the paper's
137
+ `NOTES.md` for the numbers that use them.
107
138
 
108
139
  ## When the page misbehaves
109
140