paperlint 9.0.0 → 10.0.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 (190) hide show
  1. package/.github/CLAUDE.md +22 -0
  2. package/.github/CLAUDE.md.spec.ts +20 -0
  3. package/CLAUDE.md +59 -515
  4. package/CONTRIBUTING.md +2 -2
  5. package/README.md +68 -128
  6. package/dist/CLAUDE.md.spec.d.ts +3 -0
  7. package/dist/CLAUDE.md.spec.d.ts.map +1 -0
  8. package/dist/CLAUDE.md.spec.js +62 -0
  9. package/dist/CLAUDE.md.spec.js.map +1 -0
  10. package/dist/build.d.ts +1 -1
  11. package/dist/build.js +2 -2
  12. package/dist/cli.d.ts +5 -1
  13. package/dist/cli.d.ts.map +1 -1
  14. package/dist/cli.js +52 -6
  15. package/dist/cli.js.map +1 -1
  16. package/dist/domain/layout-tree.d.ts +19 -0
  17. package/dist/domain/layout-tree.d.ts.map +1 -0
  18. package/dist/domain/layout-tree.js +32 -0
  19. package/dist/domain/layout-tree.js.map +1 -0
  20. package/dist/domain/lookup-cache.d.ts +2 -2
  21. package/dist/domain/lookup-cache.js +2 -2
  22. package/dist/domain/paper-layout.d.ts +81 -0
  23. package/dist/domain/paper-layout.d.ts.map +1 -0
  24. package/dist/domain/paper-layout.js +259 -0
  25. package/dist/domain/paper-layout.js.map +1 -0
  26. package/dist/domain/repro-layout.d.ts +80 -0
  27. package/dist/domain/repro-layout.d.ts.map +1 -0
  28. package/dist/domain/repro-layout.js +247 -0
  29. package/dist/domain/repro-layout.js.map +1 -0
  30. package/dist/domain/scaffold-layout.d.ts +28 -0
  31. package/dist/domain/scaffold-layout.d.ts.map +1 -0
  32. package/dist/domain/scaffold-layout.js +81 -0
  33. package/dist/domain/scaffold-layout.js.map +1 -0
  34. package/dist/eslint-rules/CLAUDE.md.spec.d.ts +3 -0
  35. package/dist/eslint-rules/CLAUDE.md.spec.d.ts.map +1 -0
  36. package/dist/eslint-rules/CLAUDE.md.spec.js +39 -0
  37. package/dist/eslint-rules/CLAUDE.md.spec.js.map +1 -0
  38. package/dist/eslint-rules/install-path-literals.d.ts +1 -1
  39. package/dist/eslint-rules/install-path-literals.js +4 -4
  40. package/dist/eslint-rules/install-path-literals.js.map +1 -1
  41. package/dist/eslint-rules/pdf-last-page-balance.js +1 -1
  42. package/dist/eslint-rules/prereg-coverage.d.ts +98 -0
  43. package/dist/eslint-rules/prereg-coverage.d.ts.map +1 -0
  44. package/dist/eslint-rules/prereg-coverage.js +274 -0
  45. package/dist/eslint-rules/prereg-coverage.js.map +1 -0
  46. package/dist/folder-layout-hook.d.ts +21 -0
  47. package/dist/folder-layout-hook.d.ts.map +1 -0
  48. package/dist/folder-layout-hook.js +117 -0
  49. package/dist/folder-layout-hook.js.map +1 -0
  50. package/dist/folder-layout-rule.d.ts +22 -0
  51. package/dist/folder-layout-rule.d.ts.map +1 -0
  52. package/dist/folder-layout-rule.js +107 -0
  53. package/dist/folder-layout-rule.js.map +1 -0
  54. package/dist/init.js +1 -1
  55. package/dist/init.js.map +1 -1
  56. package/dist/lib/agent-cli-version.js +1 -1
  57. package/dist/lib/agent-cli-version.js.map +1 -1
  58. package/dist/new-paper.d.ts +4 -1
  59. package/dist/new-paper.d.ts.map +1 -1
  60. package/dist/new-paper.js +40 -6
  61. package/dist/new-paper.js.map +1 -1
  62. package/dist/references.d.ts +1 -1
  63. package/dist/references.d.ts.map +1 -1
  64. package/dist/references.js +2 -2
  65. package/dist/references.js.map +1 -1
  66. package/dist/repro-layout-rule.d.ts +16 -0
  67. package/dist/repro-layout-rule.d.ts.map +1 -0
  68. package/dist/repro-layout-rule.js +81 -0
  69. package/dist/repro-layout-rule.js.map +1 -0
  70. package/dist/tex-requirements.js +1 -1
  71. package/dist/toolchain.js +1 -1
  72. package/docs/ci.md +60 -0
  73. package/docs/configuration.md +2 -0
  74. package/docs/e2e.md +3 -3
  75. package/docs/incidents.md +5 -5
  76. package/docs/install.md +20 -16
  77. package/docs/layout.md +154 -0
  78. package/docs/optional-rules.md +5 -4
  79. package/docs/references.md +5 -2
  80. package/docs/rules/paper/allowed-files-only.md +192 -0
  81. package/docs/rules/repro/allowed-files-only.md +128 -0
  82. package/docs/rules/study/declare-sample.md +146 -0
  83. package/docs/rules.md +53 -50
  84. package/docs/skills.md +6 -3
  85. package/docs/venues.md +94 -0
  86. package/eslint-rules/CLAUDE.md +44 -0
  87. package/eslint-rules/CLAUDE.md.spec.ts +43 -0
  88. package/eslint-rules/install-path-literals.harness.mjs +1 -1
  89. package/eslint-rules/install-path-literals.ts +4 -4
  90. package/eslint-rules/pdf-last-page-balance.ts +1 -1
  91. package/eslint-rules/prereg-coverage.test.ts +448 -0
  92. package/eslint-rules/prereg-coverage.ts +419 -0
  93. package/eslint.config.mjs +6 -3
  94. package/hooks/CLAUDE.md +34 -0
  95. package/hooks/CLAUDE.md.spec.ts +30 -0
  96. package/lib/agent-cli-version.ts +1 -1
  97. package/lib/skill-trigger-cases.harness.mjs +51 -5
  98. package/lib/skill-trigger-cases.mjs +53 -15
  99. package/package.json +2 -2
  100. package/plugin/CLAUDE.md +22 -0
  101. package/plugin/CLAUDE.md.spec.ts +22 -0
  102. package/plugin/hooks/hooks.json +9 -0
  103. package/scripts/coverage-config.test.ts +2 -1
  104. package/skills/CLAUDE.md +19 -0
  105. package/skills/CLAUDE.md.spec.ts +18 -0
  106. package/skills/analyze-sibling-paper/SKILL.md +2 -2
  107. package/skills/analyze-sibling-paper/SKILL.md.spec.ts +1 -1
  108. package/skills/build-benchmark/SKILL.md +14 -1
  109. package/skills/build-benchmark/SKILL.md.spec.ts +13 -0
  110. package/skills/build-label-page/SKILL.md +118 -0
  111. package/skills/build-label-page/SKILL.md.spec.ts +129 -0
  112. package/skills/build-label-page/build-label-page.eval.mjs +15 -0
  113. package/skills/build-label-page/build-label-page.test.ts +959 -0
  114. package/skills/build-label-page/references/checklist.md +148 -0
  115. package/skills/build-label-page/references/example-generator.py +803 -0
  116. package/skills/find-venue/SKILL.md +5 -3
  117. package/skills/find-venue/SKILL.md.spec.ts +4 -2
  118. package/skills/grade-paper-writing/SKILL.md +7 -7
  119. package/skills/grade-paper-writing/SKILL.md.spec.ts +6 -6
  120. package/skills/map-prior-work/SKILL.md +2 -2
  121. package/skills/map-prior-work/SKILL.md.spec.ts +1 -1
  122. package/skills/paper-pipeline/SKILL.md +8 -6
  123. package/skills/paper-pipeline/SKILL.md.spec.ts +7 -5
  124. package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +19 -9
  125. package/skills/paper-pipeline/references/acceptance-gate.md +19 -19
  126. package/skills/paper-pipeline/references/artifact-checklist.md +5 -0
  127. package/skills/paper-pipeline/references/pipeline-status-template.md +30 -1
  128. package/skills/paper-pipeline/repro/2026-08-07-fork-check.log +1 -1
  129. package/skills/paper-pipeline/repro/2026-08-07-fork-check2.log +1 -1
  130. package/skills/paper-pipeline/repro/2026-08-07-language-eval-pilot.log +1 -1
  131. package/skills/paper-pipeline/repro/2026-08-07-language-eval-raw.log +1 -1
  132. package/skills/paper-pipeline/repro/2026-08-08-framing-vs-vocabulary-oracle.log +48 -48
  133. package/skills/paper-pipeline/repro/2026-08-08-grade-paper-writing-ablation-A6-oracle.log +8 -8
  134. package/skills/paper-pipeline/repro/2026-08-08-grade-paper-writing-ablation-oracle.log +48 -48
  135. package/skills/paper-pipeline/repro/2026-08-08-parent-replication-gpw.log +2 -2
  136. package/skills/paper-pipeline/repro/2026-08-08-parent-replication.mjs +3 -3
  137. package/skills/paper-pipeline/repro/2026-10-13-grade-paper-writing-ablation-preflight-after.log +81 -0
  138. package/skills/paper-pipeline/repro/2026-10-13-grade-paper-writing-ablation-preflight-before.log +81 -0
  139. package/skills/paper-pipeline/repro/2026-10-13-grade-paper-writing-ablation-setupdiff.log +59 -0
  140. package/skills/paper-pipeline/repro/2026-10-13-grade-paper-writing-trigger.jsonl +1 -0
  141. package/skills/paper-pipeline/repro/2026-10-13-grade-paper-writing-trigger.log +22 -0
  142. package/skills/paper-pipeline/repro/README.md +4 -0
  143. package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +2 -2
  144. package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +2 -2
  145. package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +4 -2
  146. package/skills/paper-pipeline/scripts/generated-code.harness.mjs +7 -2
  147. package/skills/paper-pipeline/scripts/generated-code.mjs +8 -4
  148. package/skills/paper-pipeline/scripts/generated-code.test.ts +15 -1
  149. package/skills/paper-status/SKILL.md +2 -2
  150. package/skills/paper-status/SKILL.md.spec.ts +1 -1
  151. package/skills/plan-paper-timeline/plan-paper-timeline.effects.harness.mjs +21 -102
  152. package/skills/study-accepted-papers/SKILL.md +163 -161
  153. package/skills/study-accepted-papers/SKILL.md.spec.ts +167 -165
  154. package/skills/study-accepted-papers/references/base-rates.md +69 -0
  155. package/skills/study-accepted-papers/references/mechanisms.md +181 -0
  156. package/skills/verify-citations/SKILL.md +2 -2
  157. package/skills/verify-citations/SKILL.md.spec.ts +1 -1
  158. package/src/CLAUDE.md +23 -1
  159. package/src/CLAUDE.md.spec.ts +72 -0
  160. package/src/build.ts +2 -2
  161. package/src/cli.harness.mjs +42 -0
  162. package/src/cli.test.ts +90 -6
  163. package/src/cli.ts +78 -5
  164. package/src/domain/layout-tree.test.ts +32 -0
  165. package/src/domain/layout-tree.ts +47 -0
  166. package/src/domain/lookup-cache.ts +2 -2
  167. package/src/domain/paper-layout.test.ts +264 -0
  168. package/src/domain/paper-layout.ts +336 -0
  169. package/src/domain/repro-layout.test.ts +222 -0
  170. package/src/domain/repro-layout.ts +311 -0
  171. package/src/domain/scaffold-layout.ts +104 -0
  172. package/src/folder-layout-hook.test.ts +214 -0
  173. package/src/folder-layout-hook.ts +141 -0
  174. package/src/folder-layout-rule.test.ts +235 -0
  175. package/src/folder-layout-rule.ts +151 -0
  176. package/src/hooks-settings.harness.ts +9 -8
  177. package/src/init.ts +1 -1
  178. package/src/layout-docs.test.ts +52 -0
  179. package/src/new-paper.harness.mjs +23 -1
  180. package/src/new-paper.test.ts +299 -6
  181. package/src/new-paper.ts +51 -5
  182. package/src/paper-files.test.ts +9 -6
  183. package/src/references.test.ts +5 -5
  184. package/src/references.ts +2 -2
  185. package/src/repro-layout-rule.test.ts +114 -0
  186. package/src/repro-layout-rule.ts +121 -0
  187. package/src/rule-cases.test.ts +98 -0
  188. package/src/tex-requirements.ts +1 -1
  189. package/src/toolchain.ts +1 -1
  190. package/templates/paper/PIPELINE-STATUS.md +41 -7
@@ -12,11 +12,11 @@
12
12
  *
13
13
  * skill the skill under test
14
14
  * why the territory it owns, in one line — the thing a sibling must NOT take
15
- * prompts should-fire. FOUR each, deliberately mixed (below)
15
+ * prompts should-fire. At least FOUR, deliberately mixed (below)
16
16
  * collides siblings whose own should-fire prompts become this case's
17
- * irrelevantPrompts. That is how "the WRONG skill must not fire" is
18
- * asserted, and it is symmetric: each side of a collision is measured
19
- * from both directions.
17
+ * irrelevantPrompts — at least one from EVERY collider. That is how
18
+ * "the WRONG skill must not fire" is asserted, and it is symmetric:
19
+ * each side of a collision is measured from both directions.
20
20
  *
21
21
  * 🔴 THE PROMPTS ARE MIXED ON PURPOSE, AND THE MIX DECIDES THE NUMBER.
22
22
  * `framing-vs-vocabulary.eval.mjs` measured the same skills two ways: prompts that
@@ -55,13 +55,36 @@ const PACKAGE_CASES = [
55
55
  {
56
56
  skill: "grade-paper-writing",
57
57
  why: "prose craft — the sentence is the unit, NOT the section",
58
+ // The first four are HARVESTED and measured; they stay first and unchanged, so the rate on
59
+ // them stays comparable with pipeline-firing.eval.mjs and the siblings that collide with this
60
+ // case keep receiving the same irrelevant prompts. The last four were added 2026-10-13 with
61
+ // the rewritten description and have never been run — rates UNKNOWN. Eight, not four, because
62
+ // this skill's firing was measured at 12%, then 50–54%, on the same prompts: four prompts
63
+ // cannot tell those apart.
58
64
  prompts: [
59
65
  "оцени как написано — читается как стена жаргона",
60
66
  "is the writing any good or does it read like shit",
61
67
  "abstract звучит криво хотя по смыслу всё на месте, дай оценку прозе",
62
68
  "grade the craft: title, abstract, sentence clarity, hedge stacking",
69
+ "прочитай черновик и скажи честно, нормально ли он читается",
70
+ "the reviewer says our paper reads like a blog post — is he right about the prose",
71
+ "каждое предложение по отдельности понятно, а абзацы всё равно сливаются в сплошную простыню",
72
+ "grade the prose in section 3 and give me the sentences to rewrite",
73
+ ],
74
+ // Every sibling the description sends a request to. Each is a different wrong answer to "is
75
+ // this paper any good?": cut it, re-read one edit, attack the science, predict the committee,
76
+ // gate the submission, study the venue. Of the five added 2026-10-13, only cold-read-diff
77
+ // already named this case; the other four do not name it back, because that would change the
78
+ // irrelevant sets their own rates are measured on.
79
+ collides: [
80
+ "tighten-paper",
81
+ "argument-arc",
82
+ "cold-read-diff",
83
+ "paper-adversarial-review",
84
+ "pc-panel-review",
85
+ "harden-paper",
86
+ "study-accepted-papers",
63
87
  ],
64
- collides: ["tighten-paper", "argument-arc"],
65
88
  },
66
89
  {
67
90
  skill: "argument-arc",
@@ -153,6 +176,17 @@ const PACKAGE_CASES = [
153
176
  ],
154
177
  collides: ["research-ideate", "draft-paper"],
155
178
  },
179
+ {
180
+ skill: "build-label-page",
181
+ why: "a PERSON labels items on a phone page with a shared store, and the answers are exported into the study",
182
+ prompts: [
183
+ "сделай мне страницу, где я разметил бы эти примеры с телефона",
184
+ "build a gold-labelling page for these 40 items so I can answer on my phone",
185
+ "нужна слепая страница для ревью ответов модели, чтобы я не видел, какой из них чей",
186
+ "make me a page to label these, and put the answers back into the study folder",
187
+ ],
188
+ collides: ["build-benchmark", "draft-paper"],
189
+ },
156
190
  {
157
191
  skill: "camera-ready",
158
192
  why: "AFTER accept — de-anonymize, archive, disclose",
@@ -248,7 +282,7 @@ const PACKAGE_CASES = [
248
282
  "посмотри что принимали на этой площадке и чем те статьи сильны",
249
283
  "хочу дотянуть с accept до strong accept — что для этого нужно именно там",
250
284
  "read a dozen accepted papers from this venue and diff our draft against them",
251
- "какие приёмы у них заходят — артефакты, threat model, запоминающееся название",
285
+ "before we design the study: what does this venue actually reward? look at its award winners",
252
286
  ],
253
287
  collides: ["find-venue", "harden-paper"],
254
288
  },
@@ -436,14 +470,18 @@ export function irrelevantFor(skill, want = 4) {
436
470
  // verify-citations — seven of twenty-one, exactly the set with `collides.length
437
471
  // === 1`. Two-collider cases are unaffected (ceil(4/2) === 2), so numbers
438
472
  // measured before the fix stay comparable.
473
+ //
474
+ // `want` is a FLOOR, not a cap (2026-10-13). The set used to be cut at `want`,
475
+ // so a case with more than four colliders silently dropped the rest: the table
476
+ // said "must not take X's requests" and no request of X's was ever sent. Cases
477
+ // with one or two colliders get exactly what they got before (4); a case with
478
+ // three gets 6, with seven gets 7 — one from each.
439
479
  const per = Math.ceil(want / c.collides.length);
440
- return c.collides
441
- .flatMap((sib, i) => {
442
- const s = BY_SKILL.get(sib);
443
- if (!s) throw new Error(`${skill} collides with unknown skill "${sib}"`);
444
- // Rotate by the collider's index so two cases sharing a collider do not
445
- // receive byte-identical irrelevant sets.
446
- return [...s.prompts.slice(i), ...s.prompts.slice(0, i)].slice(0, per);
447
- })
448
- .slice(0, want);
480
+ return c.collides.flatMap((sib, i) => {
481
+ const s = BY_SKILL.get(sib);
482
+ if (!s) throw new Error(`${skill} collides with unknown skill "${sib}"`);
483
+ // Rotate by the collider's index so two cases sharing a collider do not
484
+ // receive byte-identical irrelevant sets.
485
+ return [...s.prompts.slice(i), ...s.prompts.slice(0, i)].slice(0, per);
486
+ });
449
487
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paperlint",
3
- "version": "9.0.0",
3
+ "version": "10.0.0",
4
4
  "description": "A linter for scientific papers written in LaTeX: catches mistakes before you submit to a conference or journal.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -34,7 +34,7 @@
34
34
  "ts-essentials": "^10.2.1",
35
35
  "typescript": "^6.0.3",
36
36
  "unpdf": "^1.8.1",
37
- "vigiles": "^32.1.0",
37
+ "vigiles": "^38.2.0",
38
38
  "zod": "^4.6.5"
39
39
  },
40
40
  "devDependencies": {
@@ -0,0 +1,22 @@
1
+ <!-- vigiles:sha256:6312a3b8f9804eba compiled from plugin/CLAUDE.md.spec.ts -->
2
+
3
+ # CLAUDE.md
4
+
5
+ ## `plugin/`
6
+
7
+ Not a plugin any more. `plugin/hooks/hooks.json` is the hook wiring that `paperlint init` merges
8
+ into the consumer's `.claude/settings.json`, and it is the only source of that wiring.
9
+
10
+ ## Why there is no Claude Code plugin
11
+
12
+ There is no Claude Code plugin or marketplace entry; it was removed in the release after 1.0.0
13
+ (#82). Do not bring it back without answering each of these, measured (`docs/install.md`,
14
+ "Why it is shaped this way"):
15
+
16
+ - **A plugin cannot carry the code.** Claude Code installs a plugin's dependencies with a 60-second
17
+ timeout, and a failed install never blocks the plugin: the hooks load and fail with
18
+ `cannot find module vigiles`, silently.
19
+ - **The skills need the npm package anyway.** 23 of 24 skills ran scripts that resolve only through
20
+ `node_modules/paperlint/`.
21
+ - **Its manifests carried versions nothing updated**, and Claude Code decides plugin updates from
22
+ that number.
@@ -0,0 +1,22 @@
1
+ // Compiled to plugin/CLAUDE.md by `npx vigiles compile`. Edit THIS file, never the markdown.
2
+ import { file, instructionFile, prose } from "vigiles/spec";
3
+
4
+ export default instructionFile({
5
+ sections: {
6
+ "`plugin/`": prose`Not a plugin any more. ${file("plugin/hooks/hooks.json")} is the hook wiring that \`paperlint init\` merges
7
+ into the consumer's \`.claude/settings.json\`, and it is the only source of that wiring.`,
8
+
9
+ "Why there is no Claude Code plugin": prose`There is no Claude Code plugin or marketplace entry; it was removed in the release after 1.0.0
10
+ (#82). Do not bring it back without answering each of these, measured (${file("docs/install.md")},
11
+ "Why it is shaped this way"):
12
+
13
+ - **A plugin cannot carry the code.** Claude Code installs a plugin's dependencies with a 60-second
14
+ timeout, and a failed install never blocks the plugin: the hooks load and fail with
15
+ \`cannot find module vigiles\`, silently.
16
+ - **The skills need the npm package anyway.** 23 of 24 skills ran scripts that resolve only through
17
+ \`node_modules/paperlint/\`.
18
+ - **Its manifests carried versions nothing updated**, and Claude Code decides plugin updates from
19
+ that number.`,
20
+ },
21
+ rules: {},
22
+ });
@@ -9,6 +9,15 @@
9
9
  "command": "node \"${CLAUDE_PROJECT_DIR}/node_modules/paperlint/bin/paperlint.mjs\" hook paper-edit-guard"
10
10
  }
11
11
  ]
12
+ },
13
+ {
14
+ "matcher": "Edit|Write|MultiEdit",
15
+ "hooks": [
16
+ {
17
+ "type": "command",
18
+ "command": "node \"${CLAUDE_PROJECT_DIR}/node_modules/paperlint/bin/paperlint.mjs\" hook paper-folder-layout"
19
+ }
20
+ ]
12
21
  }
13
22
  ],
14
23
  "PostToolUse": [
@@ -400,8 +400,9 @@ test("the exclude list is exactly the justified set", () => {
400
400
  "**/*.test.*",
401
401
  // PAID LLM evaluations, never run by `npm test`
402
402
  "**/*.eval.mjs",
403
- // skill sources vigiles compiles into SKILL.md
403
+ // skill and CLAUDE.md sources vigiles compiles; a glob's `**` skips a dot directory
404
404
  "**/*.spec.ts",
405
+ ".github/*.spec.ts",
405
406
  // type declarations
406
407
  "**/*.d.ts",
407
408
  "**/*.d.mts",
@@ -0,0 +1,19 @@
1
+ <!-- vigiles:sha256:e0aa2cec8926b04f compiled from skills/CLAUDE.md.spec.ts -->
2
+
3
+ # CLAUDE.md
4
+
5
+ ## `skills/`
6
+
7
+ The skills this package ships. Loaded when working under `skills/`. The consumer's root and the
8
+ package's location come from `skills/paper-pipeline/scripts/consumer.mjs`; the links are made by
9
+ `src/link-skills.ts`; install-specific paths are flagged by `eslint-rules/install-path-literals.ts`.
10
+
11
+ ## Rules
12
+
13
+ ### Skills Ship In Skills
14
+
15
+ **Guidance only** — Skills ship in `skills/`, never `.claude/skills/`. `paperlint init` symlinks each one into the consumer's `.claude/skills/<name>`; the link's directory name is the skill's name. `skills/` is the npm layout: of 855 packages scanned, 336 shipped `skills/` against 15 `.claude/skills/`. Rejected: leaving the skills for Claude Code to find inside `node_modules` (`docs/install.md`); a symlink loads at startup, deterministically.
16
+
17
+ ### A Criterion That Judges Prose
18
+
19
+ **Guidance only** — It follows the five principles in `eslint-rules/CLAUDE.md` ("A rule that judges prose"). A skill does not grep prose for what a rule can count.
@@ -0,0 +1,18 @@
1
+ // Compiled to skills/CLAUDE.md by `npx vigiles compile`. Edit THIS file, never the markdown.
2
+ import { file, guidance, instructionFile, prose } from "vigiles/spec";
3
+
4
+ export default instructionFile({
5
+ sections: {
6
+ "`skills/`": prose`The skills this package ships. Loaded when working under \`skills/\`. The consumer's root and the
7
+ package's location come from ${file("skills/paper-pipeline/scripts/consumer.mjs")}; the links are made by
8
+ ${file("src/link-skills.ts")}; install-specific paths are flagged by ${file("eslint-rules/install-path-literals.ts")}.`,
9
+ },
10
+ rules: {
11
+ "skills-ship-in-skills": guidance(
12
+ "Skills ship in `skills/`, never `.claude/skills/`. `paperlint init` symlinks each one into the consumer's `.claude/skills/<name>`; the link's directory name is the skill's name. `skills/` is the npm layout: of 855 packages scanned, 336 shipped `skills/` against 15 `.claude/skills/`. Rejected: leaving the skills for Claude Code to find inside `node_modules` (`docs/install.md`); a symlink loads at startup, deterministically.",
13
+ ),
14
+ "a-criterion-that-judges-prose": guidance(
15
+ 'It follows the five principles in `eslint-rules/CLAUDE.md` ("A rule that judges prose"). A skill does not grep prose for what a rule can count.',
16
+ ),
17
+ },
18
+ });
@@ -5,7 +5,7 @@ context: fork
5
5
  allowed-tools: [WebSearch, WebFetch, Read, Write, Grep, Glob, Bash, Agent]
6
6
  ---
7
7
 
8
- <!-- vigiles:sha256:238796152df07749 compiled from skills/analyze-sibling-paper/SKILL.md.spec.ts -->
8
+ <!-- vigiles:sha256:834f128ace92288a compiled from skills/analyze-sibling-paper/SKILL.md.spec.ts -->
9
9
 
10
10
  # analyze-sibling-paper — settle "isn't this just X?" before a reviewer does
11
11
 
@@ -115,7 +115,7 @@ again at rebuttal. **A close sibling with no file in `<paper-dir>/siblings/` is
115
115
  ## Compose with
116
116
  - `verify-citations` — surfaces the nearest-neighbor works; hand each genuine sibling to this skill.
117
117
  - `harden-paper` — its nearest-neighbor-scoop axis (axis 3) calls this for each close competitor.
118
- - `study-accepted-papers` — venue-corpus levers (different axis: the venue's bar, not a specific rival).
118
+ - `study-accepted-papers` — venue-corpus levers (different axis: what the venue rewards, not a specific rival).
119
119
  - `extend-paper` — a scooped angle, or the un-scooped remainder, can become the next paper's pivot.
120
120
 
121
121
  ## Provenance
@@ -130,7 +130,7 @@ again at rebuttal. **A close sibling with no file in \`<paper-dir>/siblings/\` i
130
130
  ## Compose with
131
131
  - \`verify-citations\` — surfaces the nearest-neighbor works; hand each genuine sibling to this skill.
132
132
  - \`harden-paper\` — its nearest-neighbor-scoop axis (axis 3) calls this for each close competitor.
133
- - \`study-accepted-papers\` — venue-corpus levers (different axis: the venue's bar, not a specific rival).
133
+ - \`study-accepted-papers\` — venue-corpus levers (different axis: what the venue rewards, not a specific rival).
134
134
  - \`extend-paper\` — a scooped angle, or the un-scooped remainder, can become the next paper's pivot.
135
135
 
136
136
  ## Provenance
@@ -5,7 +5,7 @@ context: fork
5
5
  allowed-tools: [Read, Write, Edit, Grep, Glob, Bash, Agent]
6
6
  ---
7
7
 
8
- <!-- vigiles:sha256:0b91129ca6a6b2a3 compiled from skills/build-benchmark/SKILL.md.spec.ts -->
8
+ <!-- vigiles:sha256:5eb2ba9561e082d9 compiled from skills/build-benchmark/SKILL.md.spec.ts -->
9
9
 
10
10
  # build-benchmark — the study + the artifact reviewers can run
11
11
 
@@ -51,6 +51,17 @@ is the *inverse* of the flaw:
51
51
 
52
52
  Write the design as the answer to "what would make the critiqued pitfall impossible here?"
53
53
 
54
+ **Declare the population, the screens and the coverage before freezing the sample.** Each screen
55
+ between the population and the sample (fits one excerpt, one classifier lane, a model-picked item)
56
+ changes what the sample can say, not only its size; screens that are reasonable one by one can leave a
57
+ sample nobody meets in practice. So, before the draw is frozen, the study's `prereg.json` says what
58
+ the claim is about, what each screen removed per stratum and what share the sample can represent; then
59
+ show the author the drawn **units** (the rules or cases, not the items made from them) and ask: *is
60
+ this what you meet in practice?* The fields are the experimental rule
61
+ `study/declare-sample` (`docs/rules/study/declare-sample.md`), off by default: do this step either way,
62
+ and turn the check on with `"rules": { "study/declare-sample": "error" }` in the paper's `paperlint.json`
63
+ for `paperlint lint` to fail until they are there.
64
+
54
65
  ## 2. Honest statistics — the part reviewers attack first
55
66
 
56
67
  - **Paired where the design is paired; Welch where variances differ.** Use a **paired-t** when the same
@@ -156,6 +167,8 @@ Before submitting, verify the artifact you built with the reviewer-side reproduc
156
167
  - **argument-arc (frame mode)** — 🔴 **hard input.** Owns row `frame`, the stated claim. No `frame`, no study.
157
168
  - **research-ideate** (upstream) — supplies the contribution and the pitfall to invert; this skill turns
158
169
  it into evidence.
170
+ - **build-label-page** — when the study needs a person to label items (gold labels, a blind review of
171
+ outputs): it builds the page, publishes it and exports the answers into the study's `data/`.
159
172
  - **draft-paper** — the study's stats, bound, and artifact-number table feed Methods/Results/Availability.
160
173
  - **pc-panel-review** — its artifact-runner reviewer `cd`s in and re-executes every harness; build so
161
174
  that pass is deterministic.
@@ -57,6 +57,17 @@ is the *inverse* of the flaw:
57
57
 
58
58
  Write the design as the answer to "what would make the critiqued pitfall impossible here?"
59
59
 
60
+ **Declare the population, the screens and the coverage before freezing the sample.** Each screen
61
+ between the population and the sample (fits one excerpt, one classifier lane, a model-picked item)
62
+ changes what the sample can say, not only its size; screens that are reasonable one by one can leave a
63
+ sample nobody meets in practice. So, before the draw is frozen, the study's \`prereg.json\` says what
64
+ the claim is about, what each screen removed per stratum and what share the sample can represent; then
65
+ show the author the drawn **units** (the rules or cases, not the items made from them) and ask: *is
66
+ this what you meet in practice?* The fields are the experimental rule
67
+ \`study/declare-sample\` (\`docs/rules/study/declare-sample.md\`), off by default: do this step either way,
68
+ and turn the check on with \`"rules": { "study/declare-sample": "error" }\` in the paper's \`paperlint.json\`
69
+ for \`paperlint lint\` to fail until they are there.
70
+
60
71
  ## 2. Honest statistics — the part reviewers attack first
61
72
 
62
73
  - **Paired where the design is paired; Welch where variances differ.** Use a **paired-t** when the same
@@ -162,6 +173,8 @@ Before submitting, verify the artifact you built with the reviewer-side reproduc
162
173
  - **argument-arc (frame mode)** — 🔴 **hard input.** Owns row \`frame\`, the stated claim. No \`frame\`, no study.
163
174
  - **research-ideate** (upstream) — supplies the contribution and the pitfall to invert; this skill turns
164
175
  it into evidence.
176
+ - **build-label-page** — when the study needs a person to label items (gold labels, a blind review of
177
+ outputs): it builds the page, publishes it and exports the answers into the study's \`data/\`.
165
178
  - **draft-paper** — the study's stats, bound, and artifact-number table feed Methods/Results/Availability.
166
179
  - **pc-panel-review** — its artifact-runner reviewer \`cd\`s in and re-executes every harness; build so
167
180
  that pass is deterministic.
@@ -0,0 +1,118 @@
1
+ ---
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.
4
+ allowed-tools: [Read, Write, Edit, Grep, Glob, Bash, Artifact, ArtifactData]
5
+ ---
6
+
7
+ <!-- vigiles:sha256:894d8a2df3f5c319 compiled from skills/build-label-page/SKILL.md.spec.ts -->
8
+
9
+ # build-label-page — a page the person labels on a phone, and the labels back in the repo
10
+
11
+ When a study needs a person to decide something about each of many items, a chat thread is the wrong
12
+ tool: it loses answers, shows the model's own output next to the question and gives no count of what
13
+ is left. Build a page instead. The agent generates it from the items, publishes it as an Artifact with
14
+ a shared answer store (the `db` capability), the person answers on a phone, and the agent exports the
15
+ answers into the study's `data/` folder. The labels are data the paper's numbers trace to, so they are
16
+ committed, not left in the store.
17
+
18
+ **Not for** an LLM judge (no person answers), a questionnaire about the person, or a form that collects
19
+ anything but labels.
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:
25
+
26
+ 1. **Test it in the claude.ai phone viewer before sending the link.** The viewer can size the frame to
27
+ the page, so the page cannot scroll itself and a fixed toast may be off-screen. A plain browser
28
+ passing proves nothing about this.
29
+ 2. **Navigation never relies on in-page scrolling or toasts.** "Next unanswered" is a filter: it hides
30
+ every finished item and group, and a note at the top lists what is left and what each still needs.
31
+ 3. **An item is done only when every question on it is answered.** The card names the missing
32
+ question; the progress counter counts complete items only.
33
+ 4. **Blind.** Neutral ids (`G001`…), order shuffled with a fixed seed, no model answers, verdicts or
34
+ 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.
36
+ 5. **Ask only what the labeller can answer.** Only the languages and tools they know; one "how well do
37
+ you know it" question per group, used later as an analysis filter; "clear-cut or real boundary"
38
+ is the labeller's judgement, not the designer's.
39
+ 6. **Export into the repository right away**, one JSON per page into the study's `data/` with the
40
+ export date, commit it, and record answers given in chat with `source: "chat"`. If the page fails,
41
+ take the last answers in chat; patch the page blind at most once.
42
+ 7. **Save on click**, no submit button; "Saved <time>" on every card; survive a reload; render
43
+ readable, with a banner, when the store is unavailable.
44
+
45
+ ## Procedure
46
+
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 `prereg.json` declares the population, the
53
+ screens and the coverage before the sample is frozen (the experimental rule `study/declare-sample` checks it, off by default), and the
54
+ author has been shown the drawn **units** (the rules or cases, not the items) with "is this what
55
+ you meet in practice?". The labelling page is not that check: items made from easy units each look
56
+ fine on their own.
57
+ 2. **Copy the generator into the study and write the items.** The study folder is
58
+ `<paper-dir>/repro/<slug>-YYYY-MM-DD/` (`gold-labels-2026-10-12`; `repro/allowed-files-only` wants the
59
+ date, a `README.md` in it saying what the study asks, and a line in `repro/README.md`; where this
60
+ sits in stage 3, and what checks each place: `docs/layout.md` in the paperlint package).
61
+ `items.json` follows the header of the generator. Source ids in `items.json` are the study's own;
62
+ the page never shows them.
63
+
64
+ ```bash
65
+ cp ${CLAUDE_SKILL_DIR}/references/example-generator.py <study-dir>/make_label_page.py
66
+ ```
67
+
68
+ The generator is a template: change the questions and keep the guards (allow-list, neutral ids,
69
+ store-unavailable banner, filter instead of scrolling). Do not loosen the allow-list to make a
70
+ build pass; take the field out of the item.
71
+ 3. **Build and read it as the labeller.** From the study folder (`<study-dir>`):
72
+
73
+ ```bash
74
+ python3 make_label_page.py build items.json label-page
75
+ ```
76
+
77
+ This writes `label-page/page.html` and `label-page/key.json` (the only link from `G001` to the
78
+ source id; it never goes on the page). Rebuilding with the same seed keeps every id, and a new item
79
+ gets the next one. Read the page text once as a person who knows nothing about the source: if
80
+ anything on it tells the answer, take it out of the item.
81
+ 4. **Publish.** Load `artifact-design` and `artifact-capabilities`, then publish `page.html` with
82
+ `capabilities: {db: {}}`. Use a title of two to four words. Do not ask the page for `user` or
83
+ anything else it does not use.
84
+ 5. **Test in the viewer (rule 1)** and list the store once with `ArtifactData` (`list` of
85
+ `answers`) to confirm the test answer arrived. `ArtifactData` can be a deferred tool: load it with
86
+ `ToolSearch` first. Then delete the test answer's document, or tell the person which one it is
87
+ and keep it out of the export.
88
+ 6. **Send the link** with one line on how it works: tap an answer, it saves; "Next unanswered" shows
89
+ what is left. Say what you tested and what you could not.
90
+ 7. **Export when the person is done, or stops** (rule 6). Save the store's two collections, `answers`
91
+ and `groups`, as `dump.json` in the shape the generator's header gives (answers given in chat go
92
+ in with `"source": "chat"`), then:
93
+
94
+ ```bash
95
+ python3 make_label_page.py export dump.json label-page/key.json data
96
+ ```
97
+
98
+ It writes `data/<study>-labels-<date>.json` and prints every incomplete item, every answer or
99
+ familiarity value that is not one of the offered options (not counted, same rule as on the page;
100
+ 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 →
101
+ `data/<study>-labels-<date>.json`" to the paper's `NOTES.md` for the numbers that use them.
102
+
103
+ ## When the page misbehaves
104
+
105
+ Look at what the store holds (`ArtifactData` `list`) before changing anything: the page may be fine
106
+ and the person may have opened it without access. One fix to the page is allowed. If it fails again,
107
+ ask for the remaining answers in chat, write them into the dump with `"source": "chat"`, and export.
108
+ Do not iterate on a page you cannot see.
109
+
110
+ ## Compose with
111
+
112
+ - **build-benchmark** — owns the study design and the inter-rater rule: agreement with the person who
113
+ wrote the codebook measures trainability, not reliability, so the labeller for gold labels is
114
+ someone who did not write the scheme.
115
+ - **draft-paper** — the exported labels, the familiarity filter and the complete-item count feed the
116
+ Methods and the threats.
117
+ - **submit-paper** — a published artifact page is not anonymous. Do not link it from an anonymized
118
+ submission; the exported JSON is what goes into the reproduction artifact.
@@ -0,0 +1,129 @@
1
+ // Compiled to SKILL.md by `vigiles compile`. Edit THIS file, never the markdown.
2
+ import { experimental_skill } from "vigiles/spec";
3
+
4
+ export default experimental_skill({
5
+ name: "build-label-page",
6
+ description:
7
+ '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.',
8
+ tools: [
9
+ "Read",
10
+ "Write",
11
+ "Edit",
12
+ "Grep",
13
+ "Glob",
14
+ "Bash",
15
+ "Artifact",
16
+ "ArtifactData",
17
+ ],
18
+ body: `
19
+ # build-label-page — a page the person labels on a phone, and the labels back in the repo
20
+
21
+ When a study needs a person to decide something about each of many items, a chat thread is the wrong
22
+ tool: it loses answers, shows the model's own output next to the question and gives no count of what
23
+ is left. Build a page instead. The agent generates it from the items, publishes it as an Artifact with
24
+ a shared answer store (the \`db\` capability), the person answers on a phone, and the agent exports the
25
+ answers into the study's \`data/\` folder. The labels are data the paper's numbers trace to, so they are
26
+ committed, not left in the store.
27
+
28
+ **Not for** an LLM judge (no person answers), a questionnaire about the person, or a form that collects
29
+ anything but labels.
30
+
31
+ ## The seven rules
32
+
33
+ The full text, each with its reason and its check, is
34
+ [\`references/checklist.md\`](references/checklist.md). Read it before building. In one line each:
35
+
36
+ 1. **Test it in the claude.ai phone viewer before sending the link.** The viewer can size the frame to
37
+ the page, so the page cannot scroll itself and a fixed toast may be off-screen. A plain browser
38
+ passing proves nothing about this.
39
+ 2. **Navigation never relies on in-page scrolling or toasts.** "Next unanswered" is a filter: it hides
40
+ every finished item and group, and a note at the top lists what is left and what each still needs.
41
+ 3. **An item is done only when every question on it is answered.** The card names the missing
42
+ question; the progress counter counts complete items only.
43
+ 4. **Blind.** Neutral ids (\`G001\`…), order shuffled with a fixed seed, no model answers, verdicts or
44
+ source labels on the page. The generator refuses to build when an item carries a field outside an
45
+ allow-list; new question wording is added to the allow-list on purpose.
46
+ 5. **Ask only what the labeller can answer.** Only the languages and tools they know; one "how well do
47
+ you know it" question per group, used later as an analysis filter; "clear-cut or real boundary"
48
+ is the labeller's judgement, not the designer's.
49
+ 6. **Export into the repository right away**, one JSON per page into the study's \`data/\` with the
50
+ export date, commit it, and record answers given in chat with \`source: "chat"\`. If the page fails,
51
+ take the last answers in chat; patch the page blind at most once.
52
+ 7. **Save on click**, no submit button; "Saved <time>" on every card; survive a reload; render
53
+ readable, with a banner, when the store is unavailable.
54
+
55
+ ## Procedure
56
+
57
+ 1. **Decide the labelling.** What exactly does the labeller decide per item (the questions, each with
58
+ a closed set of answers)? Which groups (languages, tools) does the labeller know? How many items
59
+ fit the time they have on a phone? Who is the labeller: a page with a store opens only for people
60
+ with access to the artifact, so the labeller is the owner or has been shared as a Contributor.
61
+ If the study design is not written yet, \`build-benchmark\` comes first.
62
+ If the items come from a drawn sample, the study's \`prereg.json\` declares the population, the
63
+ screens and the coverage before the sample is frozen (the experimental rule \`study/declare-sample\` checks it, off by default), and the
64
+ author has been shown the drawn **units** (the rules or cases, not the items) with "is this what
65
+ you meet in practice?". The labelling page is not that check: items made from easy units each look
66
+ fine on their own.
67
+ 2. **Copy the generator into the study and write the items.** The study folder is
68
+ \`<paper-dir>/repro/<slug>-YYYY-MM-DD/\` (\`gold-labels-2026-10-12\`; \`repro/allowed-files-only\` wants the
69
+ date, a \`README.md\` in it saying what the study asks, and a line in \`repro/README.md\`; where this
70
+ sits in stage 3, and what checks each place: \`docs/layout.md\` in the paperlint package).
71
+ \`items.json\` follows the header of the generator. Source ids in \`items.json\` are the study's own;
72
+ the page never shows them.
73
+
74
+ \`\`\`bash
75
+ cp \${CLAUDE_SKILL_DIR}/references/example-generator.py <study-dir>/make_label_page.py
76
+ \`\`\`
77
+
78
+ The generator is a template: change the questions and keep the guards (allow-list, neutral ids,
79
+ store-unavailable banner, filter instead of scrolling). Do not loosen the allow-list to make a
80
+ build pass; take the field out of the item.
81
+ 3. **Build and read it as the labeller.** From the study folder (\`<study-dir>\`):
82
+
83
+ \`\`\`bash
84
+ python3 make_label_page.py build items.json label-page
85
+ \`\`\`
86
+
87
+ This writes \`label-page/page.html\` and \`label-page/key.json\` (the only link from \`G001\` to the
88
+ source id; it never goes on the page). Rebuilding with the same seed keeps every id, and a new item
89
+ gets the next one. Read the page text once as a person who knows nothing about the source: if
90
+ anything on it tells the answer, take it out of the item.
91
+ 4. **Publish.** Load \`artifact-design\` and \`artifact-capabilities\`, then publish \`page.html\` with
92
+ \`capabilities: {db: {}}\`. Use a title of two to four words. Do not ask the page for \`user\` or
93
+ anything else it does not use.
94
+ 5. **Test in the viewer (rule 1)** and list the store once with \`ArtifactData\` (\`list\` of
95
+ \`answers\`) to confirm the test answer arrived. \`ArtifactData\` can be a deferred tool: load it with
96
+ \`ToolSearch\` first. Then delete the test answer's document, or tell the person which one it is
97
+ and keep it out of the export.
98
+ 6. **Send the link** with one line on how it works: tap an answer, it saves; "Next unanswered" shows
99
+ what is left. Say what you tested and what you could not.
100
+ 7. **Export when the person is done, or stops** (rule 6). Save the store's two collections, \`answers\`
101
+ and \`groups\`, as \`dump.json\` in the shape the generator's header gives (answers given in chat go
102
+ in with \`"source": "chat"\`), then:
103
+
104
+ \`\`\`bash
105
+ python3 make_label_page.py export dump.json label-page/key.json data
106
+ \`\`\`
107
+
108
+ It writes \`data/<study>-labels-<date>.json\` and prints every incomplete item, every answer or
109
+ familiarity value that is not one of the offered options (not counted, same rule as on the page;
110
+ 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 →
111
+ \`data/<study>-labels-<date>.json\`" to the paper's \`NOTES.md\` for the numbers that use them.
112
+
113
+ ## When the page misbehaves
114
+
115
+ Look at what the store holds (\`ArtifactData\` \`list\`) before changing anything: the page may be fine
116
+ and the person may have opened it without access. One fix to the page is allowed. If it fails again,
117
+ ask for the remaining answers in chat, write them into the dump with \`"source": "chat"\`, and export.
118
+ Do not iterate on a page you cannot see.
119
+
120
+ ## Compose with
121
+
122
+ - **build-benchmark** — owns the study design and the inter-rater rule: agreement with the person who
123
+ wrote the codebook measures trainability, not reliability, so the labeller for gold labels is
124
+ someone who did not write the scheme.
125
+ - **draft-paper** — the exported labels, the familiarity filter and the complete-item count feed the
126
+ Methods and the threats.
127
+ - **submit-paper** — a published artifact page is not anonymous. Do not link it from an anonymized
128
+ submission; the exported JSON is what goes into the reproduction artifact.`,
129
+ });
@@ -0,0 +1,15 @@
1
+ /**
2
+ * build-label-page — the PAID tier: does this skill's description actually fire?
3
+ *
4
+ * The prompts live in `lib/skill-trigger-cases.mjs` beside every other skill's, so collisions with
5
+ * `build-benchmark` and `draft-paper` are visible in one table. Measures recall on its own
6
+ * territory and precision against those siblings, against the real harness.
7
+ *
8
+ * ⚠️ NEVER RUN. Written 2026-10-12 with the skill; its rate is UNKNOWN, not assumed good.
9
+ *
10
+ * Costs money; not CI.
11
+ * node skills/build-label-page/build-label-page.eval.mjs [trials]
12
+ */
13
+ import { runSkillTriggerEval } from "../../lib/skill-eval-kit.mjs";
14
+
15
+ await runSkillTriggerEval("build-label-page");