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
@@ -0,0 +1,81 @@
1
+ /**
2
+ * WHAT `paperlint new` PUTS IN A NEW PAPER FOLDER BESIDE ITS THREE FILES — the layout, visible from the
3
+ * first commit.
4
+ *
5
+ * The rules `paper/allowed-files-only` and `repro/allowed-files-only` say where every kind of material goes. A
6
+ * paper that starts with three files shows none of it, and the first reviewer's report, the first
7
+ * figure and the first script each land loose at the root. So a new folder already holds the places.
8
+ *
9
+ * ── ONE SOURCE OF TRUTH ──────────────────────────────────────────────────────────
10
+ * Nothing here lists a folder by hand. The folders are `ROOT_DIR_NOTES` (paper-layout.ts) minus the
11
+ * ones named in `NOT_SCAFFOLDED`, each with its reason; `repro/` gets its own shared folders from
12
+ * `REPRO_DIR_NOTES`. A folder added to the layout is scaffolded unless someone writes down why not,
13
+ * and a test fails on an excluded name the layout no longer has.
14
+ *
15
+ * ── `.gitkeep` ───────────────────────────────────────────────────────────────────
16
+ * Git keeps files, not folders: an empty folder is not in the first commit, which is the point of
17
+ * scaffolding. A folder that holds nothing else gets a `.gitkeep`; a folder that holds a real file
18
+ * (`repro/` with its README.md) gets none. `.gitkeep` is the convention, not a paperlint file, and no
19
+ * rule reads it: `paper/stages` looks for `*.pdf` in `versions/`, the review and sibling rules lint
20
+ * `*.md`, `repro/allowed-files-only` judges the top of `repro/` only, and neither `tools/` nor `data/` is
21
+ * listed by any rule.
22
+ *
23
+ * Pure: a list of paths and texts, written by `new-paper.ts`.
24
+ */
25
+ import { ROOT_DIR_NOTES } from "./paper-layout.js";
26
+ import { DATA_DIR, README, REPRO_DIR, REPRO_DIR_NOTES, TOOLS_DIR, } from "./repro-layout.js";
27
+ /** The placeholder git needs to keep an otherwise empty folder. */
28
+ export const GITKEEP = ".gitkeep";
29
+ /**
30
+ * The folders of the layout that `paperlint new` does not create, each with why. A folder here is
31
+ * allowed at the root all the same; it appears when the work needs it.
32
+ */
33
+ export const NOT_SCAFFOLDED = [
34
+ {
35
+ name: "_build",
36
+ why: "`paperlint build` writes it, and an empty one would claim a build that never ran",
37
+ },
38
+ {
39
+ name: "siblings",
40
+ why: "a card is written when a close competitor turns up (skill analyze-sibling-paper, which also writes the folder's README.md index); a paper may have none, and an empty folder reads as an unfinished analysis",
41
+ },
42
+ {
43
+ name: "sections",
44
+ why: "the body is split only when it outgrows `paper.tex`; the stub is one file, and an empty `sections/` suggests the split is expected",
45
+ },
46
+ {
47
+ name: "talk",
48
+ why: "only a paper that declares a talk (`talk` in paperlint.json) has one, and its folder is the declared `talk.dir`, which need not be `talk/`",
49
+ },
50
+ {
51
+ name: "extra",
52
+ why: "the escape hatch for material nothing else takes; it is never judged, so it has no place in a layout meant to be filled by kind",
53
+ },
54
+ ];
55
+ /** The folders at the paper's root that a new paper holds from the first commit, in layout order. */
56
+ export const SCAFFOLD_DIRS = ROOT_DIR_NOTES.map((d) => d.name).filter((name) => !NOT_SCAFFOLDED.some((n) => n.name === name));
57
+ /**
58
+ * The stub index of `repro/`: it makes the folder non-empty (no `.gitkeep`) and says what the rule
59
+ * asks of it. A line per study is added when the first study is.
60
+ */
61
+ export const REPRO_INDEX_STUB = `# Reproduction index
62
+
63
+ One line per study: \`<slug>-YYYY-MM-DD/\` — what it asks and what it found. Scripts used across the
64
+ paper go in \`${TOOLS_DIR}/\`, shared data in \`${DATA_DIR}/\`.
65
+ `;
66
+ /**
67
+ * Every file of the layout scaffold, relative to the paper folder, in a fixed order: a `.gitkeep` in
68
+ * each empty folder, and `repro/README.md` where `repro/` would otherwise hold only folders.
69
+ */
70
+ export const SCAFFOLD_FILES = [
71
+ ...SCAFFOLD_DIRS.flatMap((dir) => dir === REPRO_DIR
72
+ ? [
73
+ { path: `${REPRO_DIR}/${README}`, text: REPRO_INDEX_STUB },
74
+ ...REPRO_DIR_NOTES.map((d) => ({
75
+ path: `${REPRO_DIR}/${d.name}/${GITKEEP}`,
76
+ text: "",
77
+ })),
78
+ ]
79
+ : [{ path: `${dir}/${GITKEEP}`, text: "" }]),
80
+ ];
81
+ //# sourceMappingURL=scaffold-layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold-layout.js","sourceRoot":"","sources":["../../src/domain/scaffold-layout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EACL,QAAQ,EACR,MAAM,EACN,SAAS,EACT,eAAe,EACf,SAAS,GACV,MAAM,mBAAmB,CAAC;AAE3B,mEAAmE;AACnE,MAAM,CAAC,MAAM,OAAO,GAAG,UAAU,CAAC;AAElC;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAGrB;IACJ;QACE,IAAI,EAAE,QAAQ;QACd,GAAG,EAAE,kFAAkF;KACxF;IACD;QACE,IAAI,EAAE,UAAU;QAChB,GAAG,EAAE,8MAA8M;KACpN;IACD;QACE,IAAI,EAAE,UAAU;QAChB,GAAG,EAAE,oIAAoI;KAC1I;IACD;QACE,IAAI,EAAE,MAAM;QACZ,GAAG,EAAE,4IAA4I;KAClJ;IACD;QACE,IAAI,EAAE,OAAO;QACb,GAAG,EAAE,iIAAiI;KACvI;CACF,CAAC;AAEF,qGAAqG;AACrG,MAAM,CAAC,MAAM,aAAa,GAAsB,cAAc,CAAC,GAAG,CAChE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CACd,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC;AAEjE;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;gBAGhB,SAAS,yBAAyB,QAAQ;CACzD,CAAC;AAQF;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAA4B;IACrD,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC,GAAG,EAA2B,EAAE,CACxD,GAAG,KAAK,SAAS;QACf,CAAC,CAAC;YACE,EAAE,IAAI,EAAE,GAAG,SAAS,IAAI,MAAM,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE;YAC1D,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC7B,IAAI,EAAE,GAAG,SAAS,IAAI,CAAC,CAAC,IAAI,IAAI,OAAO,EAAE;gBACzC,IAAI,EAAE,EAAE;aACT,CAAC,CAAC;SACJ;QACH,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,GAAG,IAAI,OAAO,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAC9C;CACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ declare const _default: import("vigiles/spec").ClaudeSpec;
2
+ export default _default;
3
+ //# sourceMappingURL=CLAUDE.md.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CLAUDE.md.spec.d.ts","sourceRoot":"","sources":["../../eslint-rules/CLAUDE.md.spec.ts"],"names":[],"mappings":";AAMA,wBAoCG"}
@@ -0,0 +1,39 @@
1
+ // Compiled to eslint-rules/CLAUDE.md by `npx vigiles compile`. Edit THIS file, never the markdown.
2
+ //
3
+ // The requirements below are held by tests and scripts, not by an ESLint rule `enforce()` could
4
+ // name, so they are prose with verified file references rather than `enforce()` rules.
5
+ import { file, guidance, instructionFile, prose } from "vigiles/spec";
6
+ export default instructionFile({
7
+ sections: {
8
+ "`eslint-rules/`": prose `The rules this package ships. Loaded when working under \`eslint-rules/\`; the rules for every change
9
+ are in the root \`CLAUDE.md\`. Each requirement below names what holds it; the reasoning lives in
10
+ that file's header.`,
11
+ "What a new rule ships with, and what holds it": prose `| requirement | held by |
12
+ | --- | --- |
13
+ | **Both halves** (root \`CLAUDE.md\`), for every registered rule | ${file("src/rule-cases.test.ts")} fails on a registered rule with no cases |
14
+ | **One rule, one requirement**: two checks with different reasons to switch them off are two rules | review |
15
+ | **A venue requirement is validated on real accepted papers** of that venue, built to PDF, with the ground truth established independently | review; the papers are in ${file("fixtures/accepted-papers/README.md")} |
16
+ | **Its page**, \`docs/rules/<group>/<rule>.md\`, and \`meta.docs.url\` pointing at it | ${file("src/rule-docs.test.ts")}; its \`AWAITING_PAGE\` list only shrinks |
17
+ | **Loud on an empty input set**: a rule that matched no files must not report like one that passed | ${file("scripts/rules-see-files.mjs")}, per rule and not per glob, in \`npm run check\` |
18
+
19
+ The page's sections: What it catches · Why · Examples (failing / passing) · Options / preset fields ·
20
+ What it does not check · How to fix. Its row in ${file("docs/rules.md")} is one line linking there.`,
21
+ "A rule that judges prose": prose `A rule, or a skill criterion, that judges a paper's prose is held to five principles. Without them
22
+ the checks push every paper toward a blog post.
23
+
24
+ 1. **Two-sided**: a floor as well as a ceiling, or the distance to accepted papers of the target
25
+ venue. A set of ceilings alone converges on clipped, unsignposted prose.
26
+ 2. **Rules count, skills judge.** Countable on the parse tree → a lint rule; needs reading (register,
27
+ genre, whether a claim changed strength) → a skill criterion.
28
+ 3. **The model reader is the venue's reviewer**, including one who skims the abstract, the
29
+ introduction and the conclusion.
30
+ 4. **Thresholds are calibrated, not borrowed**: set with the same counter on the accepted papers,
31
+ never taken from a figure measured by a different counter.
32
+ 5. **A rewrite is accepted on what reviewers see**: the combined count of every surface form of a
33
+ tic, plus a check that no claim was softened or strengthened.`,
34
+ },
35
+ rules: {
36
+ "lint-rule-over-script": guidance("A check over a syntax tree is a lint rule, not a script. It fires in the editor on save, gets the AST for free, and has a suppression syntax people already know. A script is for corpus-wide questions — index connectivity, ratios across many files. When the work has to run a program, a script may do the work and write facts to a JSON file, and a rule judges that file: the verdict still lands in the engine."),
37
+ },
38
+ });
39
+ //# sourceMappingURL=CLAUDE.md.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CLAUDE.md.spec.js","sourceRoot":"","sources":["../../eslint-rules/CLAUDE.md.spec.ts"],"names":[],"mappings":"AAAA,mGAAmG;AACnG,EAAE;AACF,gGAAgG;AAChG,uFAAuF;AACvF,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAEtE,eAAe,eAAe,CAAC;IAC7B,QAAQ,EAAE;QACR,iBAAiB,EAAE,KAAK,CAAA;;oBAER;QAEhB,+CAA+C,EAAE,KAAK,CAAA;;sEAEY,IAAI,CAAC,wBAAwB,CAAC;;0KAEsE,IAAI,CAAC,oCAAoC,CAAC;2FACzH,IAAI,CAAC,uBAAuB,CAAC;wGAChB,IAAI,CAAC,6BAA6B,CAAC;;;kDAGzF,IAAI,CAAC,eAAe,CAAC,6BAA6B;QAEhG,0BAA0B,EAAE,KAAK,CAAA;;;;;;;;;;;;iEAY4B;KAC9D;IACD,KAAK,EAAE;QACL,uBAAuB,EAAE,QAAQ,CAC/B,0ZAA0Z,CAC3Z;KACF;CACF,CAAC,CAAC"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Rule 10's mechanical half: nothing outside the port may name a file by WHERE IT IS
2
+ * The install-location rule's mechanical half (root CLAUDE.md): nothing outside the port may name a file by WHERE IT IS
3
3
  * INSTALLED.
4
4
  *
5
5
  * The class this exists for has broken this repository five times, silently every time, and
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Rule 10's mechanical half: nothing outside the port may name a file by WHERE IT IS
2
+ * The install-location rule's mechanical half (root CLAUDE.md): nothing outside the port may name a file by WHERE IT IS
3
3
  * INSTALLED.
4
4
  *
5
5
  * The class this exists for has broken this repository five times, silently every time, and
@@ -43,7 +43,7 @@ function channelPrefixIn(s) {
43
43
  const PORT = "skills/paper-pipeline/scripts/consumer.mjs";
44
44
  const MESSAGES = {
45
45
  installPath: "This names a file by where it is INSTALLED ('{{prefix}}'), so it resolves in one " +
46
- "delivery channel and does not exist in another. Rule 10: every answer to *where* comes " +
46
+ "delivery channel and does not exist in another. Every answer to *where* comes " +
47
47
  `from the port, ${PORT}. In a skill, use the substitution the harness performs ` +
48
48
  "(CLAUDE_SKILL_DIR / CLAUDE_PLUGIN_ROOT); in a module, ask the port.",
49
49
  };
@@ -52,7 +52,7 @@ const mdInstallPath = {
52
52
  meta: {
53
53
  type: "problem",
54
54
  docs: {
55
- description: "a skill must not name its scripts by an install-specific path — rule 10, the port owns where",
55
+ description: "a skill must not name its scripts by an install-specific path — the port owns where",
56
56
  },
57
57
  schema: [],
58
58
  messages: MESSAGES,
@@ -73,7 +73,7 @@ const jsInstallPath = {
73
73
  meta: {
74
74
  type: "problem",
75
75
  docs: {
76
- description: "a shipped module must not carry an install-specific path literal — rule 10, the port owns where",
76
+ description: "a shipped module must not carry an install-specific path literal — the port owns where",
77
77
  },
78
78
  schema: [],
79
79
  messages: MESSAGES,
@@ -1 +1 @@
1
- {"version":3,"file":"install-path-literals.js","sourceRoot":"","sources":["../../eslint-rules/install-path-literals.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"install-path-literals.js","sourceRoot":"","sources":["../../eslint-rules/install-path-literals.ts"],"names":[],"mappings":"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"}
@@ -11,7 +11,7 @@
11
11
  * { "rules": { "pdf/last-page-balance": ["error", { "tolerancePt": 120 }] } }
12
12
  *
13
13
  * (That is a line comment's worth of JSON in a block comment only because it has no asterisk
14
- * followed by a slash; see rule 8 of CLAUDE.md before editing it.)
14
+ * followed by a slash; see the block-comment rule in the root CLAUDE.md before editing it.)
15
15
  *
16
16
  * ── WHAT IT READS: THE FACTS THE BUILD WROTE, NOT THE PDF ───────────────────────
17
17
  * `paperlint build` measures the PDF with pdf.js and writes `<paper>/_build/paper.facts.json`. This rule
@@ -0,0 +1,98 @@
1
+ import { z } from "zod";
2
+ import type { RuleContext } from "./rule-context.ts";
3
+ /**
4
+ * The folder of a paper that holds its studies, the folders in it that are shared rather than a
5
+ * study's, and the file a study pre-registers in. The first two are `repro/allowed-files-only`'s
6
+ * (`src/domain/repro-layout.ts`, `REPRO_DIR` and `REPRO_DIRS`), copied because a module here is
7
+ * compiled before `src/`; the test beside this file holds the copies equal.
8
+ */
9
+ export declare const REPRO_DIR = "repro";
10
+ export declare const SHARED_DIRS: readonly string[];
11
+ export declare const PREREG_FILE = "prereg.json";
12
+ /** The one sentence every finding ends with: why the fields exist. */
13
+ export declare const WHY = "Why: a study once screened its sample down to rules that are easy to judge \u2014 its labeller found 93 % of the items designed as borderline clear-cut \u2014 and nothing in its design made anyone state what share of the real population the sample covered or which screens removed which kinds of units.";
14
+ /** The answers to "is this what you meet in practice?"; any but the first needs a decision. */
15
+ export declare const VERDICTS: string[];
16
+ /** The declaration a `prereg.json` must carry. Every other key of the file is the author's. */
17
+ export declare const PREREG: z.ZodObject<{
18
+ population: z.ZodObject<{
19
+ about: z.ZodString;
20
+ source: z.ZodString;
21
+ size: z.ZodNumber;
22
+ }, z.core.$strip>;
23
+ strata: z.ZodObject<{
24
+ by: z.ZodString;
25
+ values: z.ZodArray<z.ZodString>;
26
+ }, z.core.$strip>;
27
+ screens: z.ZodArray<z.ZodObject<{
28
+ name: z.ZodString;
29
+ removed: z.ZodRecord<z.ZodString, z.ZodNumber>;
30
+ }, z.core.$strip>>;
31
+ coverage: z.ZodObject<{
32
+ share: z.ZodNullable<z.ZodNumber>;
33
+ reason: z.ZodOptional<z.ZodString>;
34
+ unreachable: z.ZodArray<z.ZodString>;
35
+ }, z.core.$strip>;
36
+ author_check: z.ZodObject<{
37
+ who: z.ZodString;
38
+ when: z.ZodISODate;
39
+ units_seen: z.ZodNumber;
40
+ units_drawn: z.ZodNumber;
41
+ verdict: z.ZodEnum<{
42
+ [x: string]: string;
43
+ }>;
44
+ decision: z.ZodOptional<z.ZodObject<{
45
+ proceed: z.ZodBoolean;
46
+ by: z.ZodString;
47
+ why: z.ZodString;
48
+ }, z.core.$strip>>;
49
+ }, z.core.$strip>;
50
+ }, z.core.$strip>;
51
+ export type Prereg = z.infer<typeof PREREG>;
52
+ /** A zod path as it is written in the file: `screens[0].removed.vague`. */
53
+ export declare const pathText: (path: readonly PropertyKey[]) => string;
54
+ /** One problem: the field it is about, and what is wrong with it. */
55
+ export interface Problem {
56
+ readonly at: string;
57
+ readonly text: string;
58
+ }
59
+ /** What a schema of one field cannot see: how the fields of a parsed declaration relate. */
60
+ export declare function crossProblems(p: Readonly<Prereg>): readonly Problem[];
61
+ /** Everything wrong with the parsed JSON of one `prereg.json`, in the file's field order. */
62
+ export declare function judgePrereg(data: unknown): readonly Problem[];
63
+ /** The problems of one `prereg.json` whose text is `source`: a parse failure, or the judgement. */
64
+ export declare function preregProblems(source: string): readonly Problem[];
65
+ /** The one text a problem in the file `file` (relative to the paper) is reported with. */
66
+ export declare const problemMessage: (file: string, p: Problem) => string;
67
+ /**
68
+ * The `prereg.json` of every study folder at the top of `<paperDir>/repro/`, relative to the paper:
69
+ * every folder there but the shared ones, whatever its name — a study folder misnamed is
70
+ * `repro/allowed-files-only`'s finding, and its pre-registration is still judged here.
71
+ */
72
+ export declare function preregFiles(paperDir: string): readonly string[];
73
+ /** Every finding for the paper folder `paperDir`, one text each. */
74
+ export declare const paperFindings: (paperDir: string) => readonly string[];
75
+ declare const _default: {
76
+ rules: {
77
+ "declare-sample": {
78
+ meta: {
79
+ type: "problem";
80
+ docs: {
81
+ description: string;
82
+ url: string;
83
+ };
84
+ schema: never[];
85
+ messages: {
86
+ coverage: string;
87
+ };
88
+ };
89
+ create(context: RuleContext): {
90
+ root?: never;
91
+ } | {
92
+ root(): void;
93
+ };
94
+ };
95
+ };
96
+ };
97
+ export default _default;
98
+ //# sourceMappingURL=prereg-coverage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prereg-coverage.d.ts","sourceRoot":"","sources":["../../eslint-rules/prereg-coverage.ts"],"names":[],"mappings":"AAqCA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD;;;;;GAKG;AACH,eAAO,MAAM,SAAS,UAAU,CAAC;AACjC,eAAO,MAAM,WAAW,EAAE,SAAS,MAAM,EAAsB,CAAC;AAChE,eAAO,MAAM,WAAW,gBAAgB,CAAC;AAKzC,sEAAsE;AACtE,eAAO,MAAM,GAAG,mTACwR,CAAC;AA2GzS,+FAA+F;AAC/F,eAAO,MAAM,QAAQ,UAAqD,CAAC;AAoC3E,+FAA+F;AAC/F,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAWlB,CAAC;AAEF,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,CAAC;AAE5C,2EAA2E;AAC3E,eAAO,MAAM,QAAQ,GAAI,MAAM,SAAS,WAAW,EAAE,KAAG,MAIhC,CAAC;AAEzB,qEAAqE;AACrE,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAkDD,4FAA4F;AAC5F,wBAAgB,aAAa,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,SAAS,OAAO,EAAE,CAoBrE;AAED,6FAA6F;AAC7F,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,GAAG,SAAS,OAAO,EAAE,CAQ7D;AAOD,mGAAmG;AACnG,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,OAAO,EAAE,CAiBjE;AAED,0FAA0F;AAC1F,eAAO,MAAM,cAAc,GAAI,MAAM,MAAM,EAAE,GAAG,OAAO,KAAG,MAO7C,CAAC;AAEd;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAkB/D;AAUD,oEAAoE;AACpE,eAAO,MAAM,aAAa,GAAI,UAAU,MAAM,KAAG,SAAS,MAAM,EAO5D,CAAC;;;;;;;;;;;;;;;4BAeiB,WAAW;;;;;;;;AAbjC,wBA8BE"}
@@ -0,0 +1,274 @@
1
+ /**
2
+ * `study/declare-sample` — a study's pre-registration says which population the paper's claim is
3
+ * about, what the screens between that population and the sample removed, what share of it the
4
+ * sample can represent, and that the author looked at the drawn units before the run.
5
+ *
6
+ * ── THE FAILURE IT PREVENTS ──────────────────────────────────────────────────────
7
+ * A measurement study of how language models read coding rules drew its rules from three pools,
8
+ * kept only the rules "judgeable from one code excerpt of at most 40 lines", restricted one pool to a
9
+ * classifier's "enforceable" lane, and let a model pick short excerpts. Each screen was reasonable
10
+ * alone; together they left a sample of crisp rules. The labeller found 93 % of the items designed as
11
+ * borderline clear-cut, and the rules looked nothing like the ones met in practice. A spot-check of
12
+ * ten items flagged it (seven of ten clear-cut) and the run went ahead: nothing in the design made
13
+ * anyone state what share of the population the sample covered, or which screens removed which kinds
14
+ * of rules. The four fields below are those statements, and this rule makes them part of the record.
15
+ *
16
+ * ── WHERE IT LOOKS ───────────────────────────────────────────────────────────────
17
+ * A study lives in a folder at the top of the paper's `repro/` (`repro/allowed-files-only` names the
18
+ * layout); `tools/` and `data/` beside them are shared, not studies. Every study folder that holds a
19
+ * `prereg.json` is judged; a study without one owes nothing here. The findings are reported on the paper's `PIPELINE-STATUS.md`, first line, as the other rules
20
+ * about a paper's folders are: `prereg.json` is not a file paperlint lints, and every paper has a
21
+ * status file.
22
+ *
23
+ * ── THE FIELDS ───────────────────────────────────────────────────────────────────
24
+ * population { about, source, size } what the claim is about, where it comes from, how many
25
+ * strata { by, values } the one stratification the screens are counted by
26
+ * screens [{ name, removed: {value: n} }] each screen, and what it removed per stratum value
27
+ * coverage { share, reason?, unreachable } 0–1, or null with a reason; strata it cannot reach
28
+ * author_check { who, when, units_seen, units_drawn, verdict, decision? }
29
+ *
30
+ * A pre-registration holds much more (hypotheses, arms, models, budget); every other key is the
31
+ * author's and is not read. The shape is `PREREG` below — a zod schema whose every field carries the
32
+ * text a finding gives for it — and the relations between fields that a schema of one field cannot
33
+ * see (`removed` keyed by the declared strata, a null share with a reason, a doubtful verdict with a
34
+ * decision) are `crossProblems`.
35
+ */
36
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
37
+ import { basename, dirname, join } from "node:path";
38
+ import { z } from "zod";
39
+ /**
40
+ * The folder of a paper that holds its studies, the folders in it that are shared rather than a
41
+ * study's, and the file a study pre-registers in. The first two are `repro/allowed-files-only`'s
42
+ * (`src/domain/repro-layout.ts`, `REPRO_DIR` and `REPRO_DIRS`), copied because a module here is
43
+ * compiled before `src/`; the test beside this file holds the copies equal.
44
+ */
45
+ export const REPRO_DIR = "repro";
46
+ export const SHARED_DIRS = ["tools", "data"];
47
+ export const PREREG_FILE = "prereg.json";
48
+ const STATUS_FILE = "PIPELINE-STATUS.md";
49
+ const PAGE = "https://github.com/zernie/paperlint/blob/main/docs/rules/study/declare-sample.md";
50
+ /** The one sentence every finding ends with: why the fields exist. */
51
+ export const WHY = "Why: a study once screened its sample down to rules that are easy to judge — its labeller found 93 % of the items designed as borderline clear-cut — and nothing in its design made anyone state what share of the real population the sample covered or which screens removed which kinds of units.";
52
+ /** A zod error option: `what` is the text a finding gives for the field, missing or malformed. */
53
+ const field = (what) => ({
54
+ error: (iss) => iss.input === undefined
55
+ ? `is missing — add ${what}`
56
+ : `is not valid — it must be ${what}`,
57
+ });
58
+ /** Text that says something: a string with more than white space in it. */
59
+ const text = (what) => z
60
+ .string(field(what))
61
+ .trim()
62
+ .min(1, { error: `is empty — write ${what}` });
63
+ /** A whole number above 0. */
64
+ const positive = (what) => z.number(field(what)).int(field(what)).positive(field(what));
65
+ const POPULATION = z.object({
66
+ about: text("what the paper's claim is about, in the paper's own words (e.g. the rules written in agent instruction files of public repositories)"),
67
+ source: text("where the population's units come from — the census, crawl or registry, with its date"),
68
+ size: positive("the number of units in the population, a whole number above 0 (an estimate is fine; say how it was made in `source`)"),
69
+ }, field("`population`: what the paper's claim is about (`about`), where its units come from (`source`) and how many there are (`size`)"));
70
+ const STRATA = z.object({
71
+ by: text("the property the units are stratified by, e.g. rule type"),
72
+ values: z
73
+ .array(text("a value of the stratum"), field("the stratum's values, a list of names"))
74
+ .min(1, {
75
+ error: "is empty — list the stratum's values, at least one",
76
+ }),
77
+ }, field("`strata`: the property the screens are counted by (`by`, e.g. rule type) and its values (`values`)"));
78
+ const REMOVED_WHAT = "the number of candidates the screen removed in each declared stratum value, an object {<value>: <count>} with 0 where it removed none";
79
+ const SCREEN = z.object({
80
+ name: text("what the screen keeps, as it was applied (e.g. judgeable from one code excerpt of at most 40 lines)"),
81
+ removed: z.record(z.string(), z
82
+ .number(field("a count, a whole number from 0"))
83
+ .int(field("a count, a whole number from 0"))
84
+ .nonnegative(field("a count, a whole number from 0")), field(REMOVED_WHAT)),
85
+ }, field("a screen: `name` and `removed`"));
86
+ const SCREENS = z.array(SCREEN, field("`screens`: one entry per screen applied between the population and the sample, each with `name` and `removed` — [] when none was"));
87
+ const SHARE_WHAT = "the share of the population the sample can represent, a number from 0 to 1, or null with a `reason`";
88
+ const COVERAGE = z.object({
89
+ share: z
90
+ .number(field(SHARE_WHAT))
91
+ .min(0, field(SHARE_WHAT))
92
+ .max(1, field(SHARE_WHAT))
93
+ .nullable(),
94
+ reason: text("why the share cannot be stated").optional(),
95
+ unreachable: z.array(text("a stratum or kind of unit"), field("the strata (or kinds of units) the sample cannot reach, a list of names — [] only when it reaches them all")),
96
+ }, field("`coverage`: the share of the population the sample can represent (`share`, 0 to 1, or null with a `reason`) and the strata it cannot reach (`unreachable`)"));
97
+ /** The answers to "is this what you meet in practice?"; any but the first needs a decision. */
98
+ export const VERDICTS = ["representative", "partly", "not-representative"];
99
+ const DECISION = z.object({
100
+ proceed: z.boolean(field("whether the study runs on this sample, true or false")),
101
+ by: text("who decided"),
102
+ why: text("why, in a sentence"),
103
+ }, field("the decision taken after the verdict: `proceed` (true or false), `by` whom, and `why`"));
104
+ const AUTHOR_CHECK = z.object({
105
+ who: text("who looked at the drawn units (a name or a role)"),
106
+ when: z.iso.date(field("the date of the check, YYYY-MM-DD")),
107
+ units_seen: positive("how many of the drawn units — the rules or cases drawn, not the items made from them — the author looked at, a whole number above 0"),
108
+ units_drawn: positive("how many units the sample drew"),
109
+ verdict: z.enum(VERDICTS, field(`the author's answer to "is this what you meet in practice?" — one of ${VERDICTS.join(", ")}`)),
110
+ decision: DECISION.optional(),
111
+ }, field("`author_check`: who looked at the drawn units before the run (`who`), when (`when`), how many of how many (`units_seen`, `units_drawn`), and what they said (`verdict`)"));
112
+ /** The declaration a `prereg.json` must carry. Every other key of the file is the author's. */
113
+ export const PREREG = z.object({
114
+ population: POPULATION,
115
+ strata: STRATA,
116
+ screens: SCREENS,
117
+ coverage: COVERAGE,
118
+ author_check: AUTHOR_CHECK,
119
+ }, field("a JSON object holding `population`, `strata`, `screens`, `coverage` and `author_check`"));
120
+ /** A zod path as it is written in the file: `screens[0].removed.vague`. */
121
+ export const pathText = (path) => path
122
+ .map((k) => (typeof k === "number" ? `[${String(k)}]` : `.${String(k)}`))
123
+ .join("")
124
+ .replace(/^\./u, "");
125
+ /** The values that occur more than once in `values`, each once. */
126
+ const repeated = (values) => [
127
+ ...new Set(values.filter((v, i) => values.indexOf(v) !== i)),
128
+ ];
129
+ /** What one screen's `removed` lacks and holds beyond the declared strata. */
130
+ const removedProblems = (removed, i, values) => [
131
+ ...values
132
+ .filter((v) => !Object.hasOwn(removed, v))
133
+ .map((v) => ({
134
+ at: `screens[${String(i)}].removed.${v}`,
135
+ text: `is missing — every screen states how many candidates it removed in each declared stratum value (${values.join(", ")}), 0 where it removed none`,
136
+ })),
137
+ ...Object.keys(removed)
138
+ .filter((k) => !values.includes(k))
139
+ .map((k) => ({
140
+ at: `screens[${String(i)}].removed.${k}`,
141
+ text: `is not a declared stratum value — \`strata.values\` declares ${values.join(", ")}; add it there or count it under one of them`,
142
+ })),
143
+ ];
144
+ /** A problem when `when` holds, else none. */
145
+ const problemIf = (when, at, text) => (when ? [{ at, text }] : []);
146
+ /** The author's check against itself: no more units seen than drawn, a doubtful verdict decided. */
147
+ const checkProblems = (check) => [
148
+ ...problemIf(check.units_seen > check.units_drawn, "author_check.units_seen", `is ${String(check.units_seen)}, more than the ${String(check.units_drawn)} units drawn (\`units_drawn\`)`),
149
+ ...problemIf(check.verdict !== "representative" && check.decision === undefined, "author_check.decision", `is missing — the verdict is «${check.verdict}», and a doubtful verdict with no recorded decision is how a sample the author doubted gets run anyway: add \`proceed\` (true or false), \`by\` whom and \`why\``),
150
+ ];
151
+ /** What a schema of one field cannot see: how the fields of a parsed declaration relate. */
152
+ export function crossProblems(p) {
153
+ const values = p.strata.values;
154
+ return [
155
+ ...repeated(values).map((v) => ({
156
+ at: "strata.values",
157
+ text: `names «${v}» more than once — each stratum value once`,
158
+ })),
159
+ ...p.screens.flatMap((s, i) => removedProblems(s.removed, i, values)),
160
+ ...problemIf(p.coverage.share === null && p.coverage.reason === undefined, "coverage.reason", "is missing — `share` is null, so say why the share cannot be stated"),
161
+ ...problemIf(p.coverage.share === 1 && p.coverage.unreachable.length > 0, "coverage.unreachable", `contradicts \`share\` 1 — a sample that cannot reach ${p.coverage.unreachable.map((u) => `«${u}»`).join(", ")} does not represent the whole population: lower \`share\`, or empty the list`),
162
+ ...checkProblems(p.author_check),
163
+ ];
164
+ }
165
+ /** Everything wrong with the parsed JSON of one `prereg.json`, in the file's field order. */
166
+ export function judgePrereg(data) {
167
+ const parsed = PREREG.safeParse(data);
168
+ return parsed.success
169
+ ? crossProblems(parsed.data)
170
+ : parsed.error.issues.map((i) => ({
171
+ at: pathText(i.path),
172
+ text: i.message,
173
+ }));
174
+ }
175
+ /** What `JSON.parse` says about the text it refused. */
176
+ const Thrown = z
177
+ .object({ message: z.string() })
178
+ .catch({ message: "unparseable" });
179
+ /** The problems of one `prereg.json` whose text is `source`: a parse failure, or the judgement. */
180
+ export function preregProblems(source) {
181
+ const parsed = (() => {
182
+ try {
183
+ const data = JSON.parse(source);
184
+ return { data };
185
+ }
186
+ catch (e) {
187
+ return { why: Thrown.parse(e).message };
188
+ }
189
+ })();
190
+ return "why" in parsed
191
+ ? [
192
+ {
193
+ at: "",
194
+ text: `does not parse as JSON (${parsed.why}) — there is no declaration to read`,
195
+ },
196
+ ]
197
+ : judgePrereg(parsed.data);
198
+ }
199
+ /** The one text a problem in the file `file` (relative to the paper) is reported with. */
200
+ export const problemMessage = (file, p) => [
201
+ p.at === ""
202
+ ? `\`${file}\` ${p.text}.`
203
+ : `\`${file}\`: \`${p.at}\` ${p.text}.`,
204
+ WHY,
205
+ `Rule study/declare-sample: ${PAGE}`,
206
+ ].join(" ");
207
+ /**
208
+ * The `prereg.json` of every study folder at the top of `<paperDir>/repro/`, relative to the paper:
209
+ * every folder there but the shared ones, whatever its name — a study folder misnamed is
210
+ * `repro/allowed-files-only`'s finding, and its pre-registration is still judged here.
211
+ */
212
+ export function preregFiles(paperDir) {
213
+ const repro = join(paperDir, REPRO_DIR);
214
+ return existsSync(repro) && statSync(repro).isDirectory()
215
+ ? readdirSync(repro, { withFileTypes: true })
216
+ // stat, not the entry's own type: a study folder linked into repro/ is a folder to
217
+ // repro/allowed-files-only too, and must not escape the check by being a link
218
+ .filter((e) => !SHARED_DIRS.includes(e.name) &&
219
+ (statSync(join(repro, e.name), {
220
+ throwIfNoEntry: false,
221
+ })?.isDirectory() ??
222
+ false))
223
+ .map((e) => `${REPRO_DIR}/${e.name}/${PREREG_FILE}`)
224
+ .filter((rel) => existsSync(join(paperDir, rel)))
225
+ .sort()
226
+ : [];
227
+ }
228
+ /** A `prereg.json` that is not a regular file (a folder, a socket): reading it would abort the run. */
229
+ const NOT_A_FILE = [
230
+ {
231
+ at: "",
232
+ text: "is not a file — the declaration is one JSON file; move what the folder holds elsewhere",
233
+ },
234
+ ];
235
+ /** Every finding for the paper folder `paperDir`, one text each. */
236
+ export const paperFindings = (paperDir) => preregFiles(paperDir).flatMap((rel) => {
237
+ const path = join(paperDir, rel);
238
+ const problems = statSync(path).isFile()
239
+ ? preregProblems(readFileSync(path, "utf8"))
240
+ : NOT_A_FILE;
241
+ return problems.map((p) => problemMessage(rel, p));
242
+ });
243
+ export default {
244
+ rules: {
245
+ "declare-sample": {
246
+ meta: {
247
+ type: "problem",
248
+ docs: {
249
+ description: "every study's prereg.json declares its population, the screens and what each removed per stratum, the sample's coverage, and the author's check of the drawn units",
250
+ url: PAGE,
251
+ },
252
+ schema: [],
253
+ messages: { coverage: "{{text}}" },
254
+ },
255
+ create(context) {
256
+ if (basename(context.filename) !== STATUS_FILE)
257
+ return {};
258
+ return {
259
+ root() {
260
+ const at = context.sourceCode.getLocFromIndex(0);
261
+ paperFindings(dirname(context.filename)).forEach((t) => {
262
+ context.report({
263
+ loc: { start: at, end: at },
264
+ messageId: "coverage",
265
+ data: { text: t },
266
+ });
267
+ });
268
+ },
269
+ };
270
+ },
271
+ },
272
+ },
273
+ };
274
+ //# sourceMappingURL=prereg-coverage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prereg-coverage.js","sourceRoot":"","sources":["../../eslint-rules/prereg-coverage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { type FolderLayoutDeps } from "./folder-layout-rule.ts";
2
+ import type { Files } from "./ports/files.ts";
3
+ /** What Claude Code reads from a PreToolUse hook's stdout to refuse the call, with the reason. */
4
+ export interface HookOutput {
5
+ readonly hookSpecificOutput: {
6
+ readonly hookEventName: "PreToolUse";
7
+ readonly permissionDecision: "deny";
8
+ readonly permissionDecisionReason: string;
9
+ };
10
+ }
11
+ /** The target file's absolute path from the payload; null when there is none to read. */
12
+ export declare function writtenPath(payload: unknown): string | null;
13
+ /**
14
+ * The nearest folder above `file` holding `PIPELINE-STATUS.md`, up to the root; null when none, and
15
+ * null for anything under a folder named `extra/`, which lint never reads: an archived paper kept
16
+ * there carries its own status file and is not a paper to hold to the layout.
17
+ */
18
+ export declare function paperFolderOf(file: string, files: Files): string | null;
19
+ /** The hook: the payload of an Edit or a Write in, Claude Code's refusal out, or null to allow. */
20
+ export declare function folderLayoutHook(payload: unknown, deps: FolderLayoutDeps): HookOutput | null;
21
+ //# sourceMappingURL=folder-layout-hook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"folder-layout-hook.d.ts","sourceRoot":"","sources":["../src/folder-layout-hook.ts"],"names":[],"mappings":"AA8CA,OAAO,EAAc,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAM9C,kGAAkG;AAClG,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,kBAAkB,EAAE;QAC3B,QAAQ,CAAC,aAAa,EAAE,YAAY,CAAC;QACrC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;QACpC,QAAQ,CAAC,wBAAwB,EAAE,MAAM,CAAC;KAC3C,CAAC;CACH;AAED,yFAAyF;AACzF,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAO3D;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,MAAM,GAAG,IAAI,CASvE;AAmCD,mGAAmG;AACnG,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,gBAAgB,GACrB,UAAU,GAAG,IAAI,CAenB"}