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
@@ -1,12 +1,15 @@
1
1
  # Labelling-page checklist
2
2
 
3
- Seven rules for a page where a person labels items from a paper's study (gold labels, a blind review of
3
+ Eight rules for a page where a person labels items from a paper's study (gold labels, a blind review of
4
4
  model outputs, "is this a real boundary case"). Each rule says what to do, why, and how to check it.
5
5
  They come from four such pages built in one week; each rule is a defect one of them shipped, or a
6
6
  failure the next answer would have hidden.
7
7
 
8
- The generator beside this file, `example-generator.py`, does rules 2 to 5 and 7 by construction. Rules
9
- 1 and 6 are things the agent does. Read the rule before changing the part of the generator that
8
+ The rules hold for every labelling page, generated or written by hand. The skill's generator,
9
+ `scripts/label-page.ts`, does rules 2 to 5, 7 and the page half of 8 by construction
10
+ ([`closed-choice-generator.md`](closed-choice-generator.md)); a page written by hand meets each rule
11
+ itself, and each rule's **Check** line is its acceptance list. Rules 1 and 6, and the rewording in 8,
12
+ are things the agent does. Read the rule before changing the part of the generator that
10
13
  carries it.
11
14
 
12
15
  ## 1. Test it in the claude.ai phone viewer before sending the link
@@ -140,6 +143,45 @@ not run: banner, items readable, buttons off.
140
143
 
141
144
  ---
142
145
 
146
+ ## 8. The labeller understands each question before the real items
147
+
148
+ **Do.**
149
+
150
+ - Write each question in plain words, and give every answer option one `example`: a real item of
151
+ the study, in a line, that takes that answer. The page shows them on its first screen ("How to
152
+ answer"). The build refuses an option without one.
153
+ - Write 3 or more practice items whose answers are known (`practice`, each with `expected` for every
154
+ question and a one-line `why`). The page shows them before the real items; after each answer it
155
+ shows the expected answer and the reason. The real items open once all are answered.
156
+ - Make the answers tell apart at a glance: give each option an `icon` (an emoji), never a coloured
157
+ side stripe on a button (it reads as decoration; the look test refuses one), and put code, configs and identifiers in
158
+ `backticks` or the item's `code` field, so they render as code. During the practice each button also
159
+ shows its example; on the real items the buttons stay short.
160
+ - Code is shown so it can be judged, not counted: a `code` block marks leading and trailing
161
+ whitespace (a space as ·, a tab as →) and prints each line's length at its end.
162
+ - One question per screen: an item card asks one question at a time, and the answered ones stay as a
163
+ line with "Change".
164
+ - A practice miss does not lock the page, but it stops the agent: ask the labeller which question
165
+ was unclear, reword it (and its examples), raise `practice_round`, rebuild, and have the labeller
166
+ take the practice again before their real answers count. The page keeps only the FIRST answer to a
167
+ practice item, so the score is not a retry.
168
+ - Keep a record in the study folder: the practice file `export` writes
169
+ (`<study>-practice-r<round>-<date>.json`: the round, who answered, what matched), and one line in the
170
+ study's README per round on what was reworded after it.
171
+
172
+ **Why.** A labeller who misreads the question gives labels that agree with nothing, and the agreement
173
+ statistic then blames the items. Pages that passed rules 1 to 7 (blind, phone-tested, saved on click)
174
+ still left their labeller unable to say what was being asked. A pilot round before the real labelling
175
+ is ordinary practice in labelling studies: one rule-mining study labelled 30 pilot samples, resolved
176
+ the disagreements, aligned its criteria, and only then labelled the full set.
177
+
178
+ **Check.** The page opens on "How to answer" with an example under every option, then "Practice: 0
179
+ of N answered", and no real item. Answer the practice wrongly once: the expected answer and the
180
+ reason appear, and the banner says to tell the agent. `export` writes the labels without any
181
+ practice answer and the practice file beside them.
182
+
183
+ ---
184
+
143
185
  ## Who can answer
144
186
 
145
187
  A page with the `db` capability opens only for people with access to the artifact: the owner, or a
@@ -0,0 +1,88 @@
1
+ # The closed-choice generator
2
+
3
+ The page shape this generator builds: the labeller looks at one item and picks from a closed list of
4
+ answers, one question at a time, with an optional reason box and code blocks. For any other shape,
5
+ see "Which page you need" in `SKILL.md`: the agent writes that page itself.
6
+
7
+ The generator is `scripts/label-page.ts` in this skill, compiled beside itself to `label-page.js`.
8
+ It runs from the skill and is never copied into a study, so a fix to it reaches every page built
9
+ after the fix. The commands are in `SKILL.md` (procedure steps 3 and 8); its third subcommand,
10
+ `schema`, prints the declared schema of `items.json`. Its header holds the full `items.json`
11
+ example; this file says how to fill it.
12
+
13
+ It is two parts. The core, `label-page-core.ts`, has no page layout: the `items.json` schema and its
14
+ checks, the leak guard, the neutral ids, the key with its `allowed` and `needs_why` lists, the check
15
+ of the store's dump, and the export of labels and practice. The page, `label-page-html.ts`, fills the
16
+ one closed-choice template (`page-template.html`, its style and browser script kept as exact bytes)
17
+ with the core's payload. Another page shape would be another template on the same core.
18
+
19
+ ## What it does by construction
20
+
21
+ Rules 2 to 5, 7 and the page half of 8 of [`checklist.md`](checklist.md): a filter instead of
22
+ scrolling, complete-item counting, neutral ids in a seeded order, the leak guard (an item field
23
+ outside the allow-list stops the build), save on click with a time, the store-unavailable banner,
24
+ the "How to answer" screen, the practice before the real items, one question per screen. Its look:
25
+ one dark palette, one column, answer buttons as full-width rows with space between them, an optional
26
+ icon per answer and never a coloured side stripe, `backticks` and the `code` field shown as code,
27
+ code blocks that show leading and trailing whitespace (a space as ·, a tab as →) and each line's
28
+ length.
29
+
30
+ ## items.json
31
+
32
+ - **`questions`**: each with a closed set of `options`. Every option has a `value`, a `label` and a
33
+ required `example`: a real case of the study, in a line, that takes that answer. `icon` (an
34
+ emoji) is optional. Two labels that read the same, or two equal values, stop the build.
35
+ - **`practice`**: 3 or more real cases whose answer you are sure of, each with `expected` (one answer
36
+ per question) and a one-line `why`. Never an item that is also on the page to be labelled.
37
+ - **`practice_round`**: raise it after rewording a question; the practice reopens and the answers of
38
+ the earlier round are kept but not counted.
39
+ - **`items`**: `source_id` (the study's own, never shown), `group`, `body`, and optionally `title`,
40
+ `code`, `context`. Nothing else: the allow-list is fixed in the generator, and a field outside it
41
+ is taken out of the item, not added to the list to make a build pass.
42
+ - **`seed`**: fixes the order. Rebuilding with the same seed keeps every id, and a new item gets the
43
+ next one; a different seed is refused once a key exists.
44
+ - **`ask_why`** on an option: a prompt. That answer is incomplete, on the page and in the export,
45
+ until a reason is saved.
46
+
47
+ ## The approval page
48
+
49
+ The page on which the author checks a drawn sample before the runs (the plan's `author_check`: "is
50
+ this what you meet in practice?") is this generator's shape: one question whose answers are keep and
51
+ drop, the drop carrying `ask_why`.
52
+
53
+ ```json
54
+ {
55
+ "id": "keep",
56
+ "text": "Is this what you meet in practice?",
57
+ "short": "keep?",
58
+ "options": [
59
+ {
60
+ "value": "keep",
61
+ "label": "Keep",
62
+ "icon": "👍",
63
+ "example": "`Use 2-space indent.`"
64
+ },
65
+ {
66
+ "value": "drop",
67
+ "label": "Drop",
68
+ "icon": "🗑️",
69
+ "example": "`Be nice to reviewers.`",
70
+ "ask_why": "Why drop it? (not a rule, a process rule, a misreading…)"
71
+ }
72
+ ]
73
+ }
74
+ ```
75
+
76
+ The items are the drawn units verbatim. The practice is three units whose fate is obvious. Each
77
+ dropped unit carries its reason in the labels file. The author's words, with the date, then go into
78
+ `author_check` of the plan; a dropped unit is replaced by the next one in the draw.
79
+
80
+ ## The export
81
+
82
+ `export` reads the store's three collections (`answers`, `groups`, `practice`) from `dump.json`, in
83
+ the shape the generator's header gives. It refuses, naming the path, any name the key does not know
84
+ or any document of the wrong type. An answer or familiarity value outside the offered options is not
85
+ counted: the item is marked incomplete and printed (same rule as on the page); correct it in the dump
86
+ and export again. It also prints every item that is missing an answer or a reason, and every group
87
+ with no familiarity answer. The practice of the current round is scored into its own file and never
88
+ into the labels.
@@ -0,0 +1,72 @@
1
+ /**
2
+ * The command line of build-label-page's generator, as a function: one argv in, what it printed and
3
+ * its exit code out. `label-page.ts` runs it on the process's own argv.
4
+ */
5
+ import { parseArgs } from "node:util";
6
+ import { COMMANDS } from "./label-page-commands.js";
7
+ import { CONFIG_SCHEMA, exportLabels, fail, Refusal, say, takePrinted, } from "./label-page-core.js";
8
+ import { build } from "./label-page-html.js";
9
+ const USAGE = "usage: label-page.js build <items.json> <out-dir> | export <dump.json> <key.json> <data-dir> [--date YYYY-MM-DD] | schema";
10
+ const usage = () => {
11
+ throw new Refusal(2, USAGE);
12
+ };
13
+ const today = () => {
14
+ const d = new Date();
15
+ const two = (n) => String(n).padStart(2, "0");
16
+ return `${String(d.getFullYear())}-${two(d.getMonth() + 1)}-${two(d.getDate())}`;
17
+ };
18
+ /** How many paths each subcommand takes, and whether it takes `--date`. */
19
+ const ARITY = {
20
+ [COMMANDS.schema]: { paths: 0, dated: false },
21
+ [COMMANDS.build]: { paths: 2, dated: false },
22
+ [COMMANDS.export]: { paths: 3, dated: true },
23
+ };
24
+ const parsedArgs = (argv) => {
25
+ try {
26
+ return parseArgs({
27
+ args: [...argv],
28
+ options: { date: { type: "string" } },
29
+ allowPositionals: true,
30
+ });
31
+ }
32
+ catch {
33
+ return usage();
34
+ }
35
+ };
36
+ function exportCommand(paths, date) {
37
+ const [dump = "", key = "", out = ""] = paths;
38
+ const day = date ?? today();
39
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(day))
40
+ fail("--date must be YYYY-MM-DD");
41
+ exportLabels(dump, key, out, day);
42
+ }
43
+ function main(argv) {
44
+ const { values, positionals } = parsedArgs(argv);
45
+ const [cmd = "", ...paths] = positionals;
46
+ const arity = ARITY[cmd];
47
+ const fits = arity !== undefined &&
48
+ arity.paths === paths.length &&
49
+ (arity.dated || values.date === undefined);
50
+ if (!fits)
51
+ usage();
52
+ if (cmd === COMMANDS.schema)
53
+ say(JSON.stringify(CONFIG_SCHEMA, null, 2));
54
+ else if (cmd === COMMANDS.build)
55
+ build(String(paths[0]), String(paths[1]));
56
+ else
57
+ exportCommand(paths, values.date);
58
+ }
59
+ /** One command line: what it printed to stdout and to stderr, and its exit code. */
60
+ export function cli(argv) {
61
+ takePrinted(); // nothing left over from an earlier run in the same process
62
+ try {
63
+ main(argv);
64
+ return { status: 0, stdout: takePrinted(), stderr: "" };
65
+ }
66
+ catch (e) {
67
+ // An unexpected error (a file that cannot be written) prints its message, not a stack.
68
+ const message = String(Reflect.get(Object(e), "message"));
69
+ const status = e instanceof Refusal ? e.exitCode : 1;
70
+ return { status, stdout: takePrinted(), stderr: `${message}\n` };
71
+ }
72
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * The command line of build-label-page's generator, as a function: one argv in, what it printed and
3
+ * its exit code out. `label-page.ts` runs it on the process's own argv.
4
+ */
5
+ import { parseArgs } from "node:util";
6
+ import { COMMANDS } from "./label-page-commands.ts";
7
+ import {
8
+ CONFIG_SCHEMA,
9
+ exportLabels,
10
+ fail,
11
+ Refusal,
12
+ say,
13
+ takePrinted,
14
+ } from "./label-page-core.ts";
15
+ import { build } from "./label-page-html.ts";
16
+
17
+ const USAGE =
18
+ "usage: label-page.js build <items.json> <out-dir> | export <dump.json> <key.json> <data-dir> [--date YYYY-MM-DD] | schema";
19
+ const usage = (): never => {
20
+ throw new Refusal(2, USAGE);
21
+ };
22
+ const today = (): string => {
23
+ const d = new Date();
24
+ const two = (n: number): string => String(n).padStart(2, "0");
25
+ return `${String(d.getFullYear())}-${two(d.getMonth() + 1)}-${two(d.getDate())}`;
26
+ };
27
+ /** How many paths each subcommand takes, and whether it takes `--date`. */
28
+ const ARITY: Readonly<
29
+ Record<string, { readonly paths: number; readonly dated: boolean }>
30
+ > = {
31
+ [COMMANDS.schema]: { paths: 0, dated: false },
32
+ [COMMANDS.build]: { paths: 2, dated: false },
33
+ [COMMANDS.export]: { paths: 3, dated: true },
34
+ };
35
+ const parsedArgs = (argv: readonly string[]) => {
36
+ try {
37
+ return parseArgs({
38
+ args: [...argv],
39
+ options: { date: { type: "string" } },
40
+ allowPositionals: true,
41
+ });
42
+ } catch {
43
+ return usage();
44
+ }
45
+ };
46
+ function exportCommand(
47
+ paths: readonly string[],
48
+ date: string | undefined,
49
+ ): void {
50
+ const [dump = "", key = "", out = ""] = paths;
51
+ const day = date ?? today();
52
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(day)) fail("--date must be YYYY-MM-DD");
53
+ exportLabels(dump, key, out, day);
54
+ }
55
+ function main(argv: readonly string[]): void {
56
+ const { values, positionals } = parsedArgs(argv);
57
+ const [cmd = "", ...paths] = positionals;
58
+ const arity = ARITY[cmd];
59
+ const fits =
60
+ arity !== undefined &&
61
+ arity.paths === paths.length &&
62
+ (arity.dated || values.date === undefined);
63
+ if (!fits) usage();
64
+ if (cmd === COMMANDS.schema) say(JSON.stringify(CONFIG_SCHEMA, null, 2));
65
+ else if (cmd === COMMANDS.build) build(String(paths[0]), String(paths[1]));
66
+ else exportCommand(paths, values.date);
67
+ }
68
+
69
+ /** One command line: what it printed to stdout and to stderr, and its exit code. */
70
+ export function cli(argv: readonly string[]): {
71
+ readonly status: number;
72
+ readonly stdout: string;
73
+ readonly stderr: string;
74
+ } {
75
+ takePrinted(); // nothing left over from an earlier run in the same process
76
+ try {
77
+ main(argv);
78
+ return { status: 0, stdout: takePrinted(), stderr: "" };
79
+ } catch (e) {
80
+ // An unexpected error (a file that cannot be written) prints its message, not a stack.
81
+ const message = String(Reflect.get(Object(e), "message"));
82
+ const status = e instanceof Refusal ? e.exitCode : 1;
83
+ return { status, stdout: takePrinted(), stderr: `${message}\n` };
84
+ }
85
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The subcommands of `label-page`, by name. One table, read twice: the dispatcher in `label-page.ts`
3
+ * compares against these, and `SKILL.md.spec.ts` interpolates them into every command it instructs
4
+ * (a name that is not here is a type error there). So the skill cannot instruct a subcommand the
5
+ * script does not have.
6
+ *
7
+ * A module of its own, with no side effects: the spec imports it at compile time, and importing
8
+ * `label-page.ts` itself would run the command line.
9
+ */
10
+ export const COMMANDS = {
11
+ build: "build",
12
+ export: "export",
13
+ schema: "schema",
14
+ };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The subcommands of `label-page`, by name. One table, read twice: the dispatcher in `label-page.ts`
3
+ * compares against these, and `SKILL.md.spec.ts` interpolates them into every command it instructs
4
+ * (a name that is not here is a type error there). So the skill cannot instruct a subcommand the
5
+ * script does not have.
6
+ *
7
+ * A module of its own, with no side effects: the spec imports it at compile time, and importing
8
+ * `label-page.ts` itself would run the command line.
9
+ */
10
+ export const COMMANDS = {
11
+ build: "build",
12
+ export: "export",
13
+ schema: "schema",
14
+ } as const;