paperlint 3.1.3 → 4.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 (527) hide show
  1. package/.github/workflows/ci.yml +12 -7
  2. package/CLAUDE.md +5 -5
  3. package/CONTRIBUTING.md +34 -5
  4. package/dist/adapters/banal/failure.d.ts +1 -7
  5. package/dist/adapters/banal/failure.d.ts.map +1 -1
  6. package/dist/adapters/banal/failure.js +5 -7
  7. package/dist/adapters/banal/failure.js.map +1 -1
  8. package/dist/adapters/banal/index.d.ts.map +1 -1
  9. package/dist/adapters/banal/index.js +4 -3
  10. package/dist/adapters/banal/index.js.map +1 -1
  11. package/dist/adapters/banal/install.d.ts.map +1 -1
  12. package/dist/adapters/banal/install.js +4 -3
  13. package/dist/adapters/banal/install.js.map +1 -1
  14. package/dist/adapters/banal/invocation.d.ts +10 -5
  15. package/dist/adapters/banal/invocation.d.ts.map +1 -1
  16. package/dist/adapters/banal/invocation.js +13 -1
  17. package/dist/adapters/banal/invocation.js.map +1 -1
  18. package/dist/adapters/banal/locate.d.ts +12 -9
  19. package/dist/adapters/banal/locate.d.ts.map +1 -1
  20. package/dist/adapters/banal/locate.js +19 -12
  21. package/dist/adapters/banal/locate.js.map +1 -1
  22. package/dist/adapters/banal/run.d.ts +10 -1
  23. package/dist/adapters/banal/run.d.ts.map +1 -1
  24. package/dist/adapters/banal/run.js +8 -4
  25. package/dist/adapters/banal/run.js.map +1 -1
  26. package/dist/adapters/banal/settings.d.ts +1 -1
  27. package/dist/adapters/banal/settings.d.ts.map +1 -1
  28. package/dist/adapters/banal/settings.js +2 -1
  29. package/dist/adapters/banal/settings.js.map +1 -1
  30. package/dist/adapters/memory/index.d.ts.map +1 -1
  31. package/dist/adapters/memory/index.js +8 -2
  32. package/dist/adapters/memory/index.js.map +1 -1
  33. package/dist/adapters/node/files.io.d.ts.map +1 -1
  34. package/dist/adapters/node/files.io.js +2 -1
  35. package/dist/adapters/node/files.io.js.map +1 -1
  36. package/dist/adapters/node/process.io.d.ts +5 -7
  37. package/dist/adapters/node/process.io.d.ts.map +1 -1
  38. package/dist/adapters/node/process.io.js +4 -3
  39. package/dist/adapters/node/process.io.js.map +1 -1
  40. package/dist/adapters/node/workspace.io.d.ts.map +1 -1
  41. package/dist/adapters/node/workspace.io.js +4 -2
  42. package/dist/adapters/node/workspace.io.js.map +1 -1
  43. package/dist/adapters/pdfjs/fill.d.ts.map +1 -1
  44. package/dist/adapters/pdfjs/fill.js +13 -7
  45. package/dist/adapters/pdfjs/fill.js.map +1 -1
  46. package/dist/adapters/references/index.d.ts.map +1 -1
  47. package/dist/adapters/references/index.js +8 -7
  48. package/dist/adapters/references/index.js.map +1 -1
  49. package/dist/adapters/references/pool.d.ts.map +1 -1
  50. package/dist/adapters/references/pool.js +4 -5
  51. package/dist/adapters/references/pool.js.map +1 -1
  52. package/dist/adapters/references/reach.io.d.ts.map +1 -1
  53. package/dist/adapters/references/reach.io.js +2 -1
  54. package/dist/adapters/references/reach.io.js.map +1 -1
  55. package/dist/build-engine.d.ts +1 -1
  56. package/dist/build-engine.d.ts.map +1 -1
  57. package/dist/build-engine.js +10 -3
  58. package/dist/build-engine.js.map +1 -1
  59. package/dist/build.d.ts +2 -3
  60. package/dist/build.d.ts.map +1 -1
  61. package/dist/build.js +19 -17
  62. package/dist/build.js.map +1 -1
  63. package/dist/caller-path.d.ts +3 -0
  64. package/dist/caller-path.d.ts.map +1 -0
  65. package/dist/caller-path.js +13 -0
  66. package/dist/caller-path.js.map +1 -0
  67. package/dist/cli.d.ts +10 -17
  68. package/dist/cli.d.ts.map +1 -1
  69. package/dist/cli.js +93 -46
  70. package/dist/cli.js.map +1 -1
  71. package/dist/doctor.d.ts +12 -3
  72. package/dist/doctor.d.ts.map +1 -1
  73. package/dist/doctor.js +5 -3
  74. package/dist/doctor.js.map +1 -1
  75. package/dist/domain/lookup-cache.d.ts.map +1 -1
  76. package/dist/domain/lookup-cache.js +10 -8
  77. package/dist/domain/lookup-cache.js.map +1 -1
  78. package/dist/domain/paths.d.ts +8 -2
  79. package/dist/domain/paths.d.ts.map +1 -1
  80. package/dist/domain/paths.js +12 -1
  81. package/dist/domain/paths.js.map +1 -1
  82. package/dist/domain/ready.d.ts +24 -0
  83. package/dist/domain/ready.d.ts.map +1 -0
  84. package/dist/domain/ready.js +11 -0
  85. package/dist/domain/ready.js.map +1 -0
  86. package/dist/domain/record.d.ts +8 -0
  87. package/dist/domain/record.d.ts.map +1 -0
  88. package/dist/domain/record.js +8 -0
  89. package/dist/domain/record.js.map +1 -0
  90. package/dist/domain/sha256.d.ts.map +1 -1
  91. package/dist/domain/sha256.js +4 -2
  92. package/dist/domain/sha256.js.map +1 -1
  93. package/dist/domain/text.d.ts +12 -0
  94. package/dist/domain/text.d.ts.map +1 -1
  95. package/dist/domain/text.js +17 -0
  96. package/dist/domain/text.js.map +1 -1
  97. package/dist/engine.d.ts +23 -22
  98. package/dist/engine.d.ts.map +1 -1
  99. package/dist/engine.js +4 -3
  100. package/dist/engine.js.map +1 -1
  101. package/dist/eslint-rules/bib-reachable-entry.d.ts +22 -0
  102. package/dist/eslint-rules/bib-reachable-entry.d.ts.map +1 -0
  103. package/dist/eslint-rules/bib-reachable-entry.js +75 -0
  104. package/dist/eslint-rules/bib-reachable-entry.js.map +1 -0
  105. package/dist/eslint-rules/install-path-literals.d.ts +80 -0
  106. package/dist/eslint-rules/install-path-literals.d.ts.map +1 -0
  107. package/dist/eslint-rules/install-path-literals.js +105 -0
  108. package/dist/eslint-rules/install-path-literals.js.map +1 -0
  109. package/dist/eslint-rules/latex-language.d.ts +136 -0
  110. package/dist/eslint-rules/latex-language.d.ts.map +1 -0
  111. package/dist/eslint-rules/latex-language.js +639 -0
  112. package/dist/eslint-rules/latex-language.js.map +1 -0
  113. package/dist/eslint-rules/paper-research-question.d.ts +31 -0
  114. package/dist/eslint-rules/paper-research-question.d.ts.map +1 -0
  115. package/dist/eslint-rules/paper-research-question.js +188 -0
  116. package/dist/eslint-rules/paper-research-question.js.map +1 -0
  117. package/dist/eslint-rules/paper-stages.d.ts +83 -0
  118. package/dist/eslint-rules/paper-stages.d.ts.map +1 -0
  119. package/dist/eslint-rules/paper-stages.js +358 -0
  120. package/dist/eslint-rules/paper-stages.js.map +1 -0
  121. package/dist/eslint-rules/paper-typography.d.ts +74 -0
  122. package/dist/eslint-rules/paper-typography.d.ts.map +1 -0
  123. package/dist/eslint-rules/paper-typography.js +404 -0
  124. package/dist/eslint-rules/paper-typography.js.map +1 -0
  125. package/dist/eslint-rules/papers.d.ts +42 -0
  126. package/dist/eslint-rules/papers.d.ts.map +1 -0
  127. package/dist/eslint-rules/papers.js +145 -0
  128. package/dist/eslint-rules/papers.js.map +1 -0
  129. package/dist/eslint-rules/pdf-last-page-balance.d.ts +72 -0
  130. package/dist/eslint-rules/pdf-last-page-balance.d.ts.map +1 -0
  131. package/dist/eslint-rules/pdf-last-page-balance.js +237 -0
  132. package/dist/eslint-rules/pdf-last-page-balance.js.map +1 -0
  133. package/dist/eslint-rules/review-frontmatter.d.ts +57 -0
  134. package/dist/eslint-rules/review-frontmatter.d.ts.map +1 -0
  135. package/dist/eslint-rules/review-frontmatter.js +126 -0
  136. package/dist/eslint-rules/review-frontmatter.js.map +1 -0
  137. package/dist/eslint-rules/review-frontmatter.schema.json +36 -0
  138. package/dist/eslint-rules/rule-context.d.ts +55 -0
  139. package/dist/eslint-rules/rule-context.d.ts.map +1 -0
  140. package/dist/eslint-rules/rule-context.js +9 -0
  141. package/dist/eslint-rules/rule-context.js.map +1 -0
  142. package/dist/eslint-rules/sibling-frontmatter.d.ts +25 -0
  143. package/dist/eslint-rules/sibling-frontmatter.d.ts.map +1 -0
  144. package/dist/eslint-rules/sibling-frontmatter.js +16 -0
  145. package/dist/eslint-rules/sibling-frontmatter.js.map +1 -0
  146. package/dist/eslint-rules/sibling-frontmatter.schema.json +14 -0
  147. package/dist/eslint-rules/temp-root-realpath.d.ts +71 -0
  148. package/dist/eslint-rules/temp-root-realpath.d.ts.map +1 -0
  149. package/dist/eslint-rules/temp-root-realpath.js +139 -0
  150. package/dist/eslint-rules/temp-root-realpath.js.map +1 -0
  151. package/dist/eslint-rules/tex-build.d.ts +35 -0
  152. package/dist/eslint-rules/tex-build.d.ts.map +1 -0
  153. package/dist/eslint-rules/tex-build.js +313 -0
  154. package/dist/eslint-rules/tex-build.js.map +1 -0
  155. package/dist/facts-file.d.ts.map +1 -1
  156. package/dist/facts-file.js +31 -27
  157. package/dist/facts-file.js.map +1 -1
  158. package/dist/hooks-settings.d.ts +1 -1
  159. package/dist/hooks-settings.d.ts.map +1 -1
  160. package/dist/hooks-settings.js +31 -11
  161. package/dist/hooks-settings.js.map +1 -1
  162. package/dist/init.d.ts +8 -9
  163. package/dist/init.d.ts.map +1 -1
  164. package/dist/init.js +33 -15
  165. package/dist/init.js.map +1 -1
  166. package/dist/latex-loop.d.ts +2 -2
  167. package/dist/latex-loop.d.ts.map +1 -1
  168. package/dist/latex-loop.js +2 -2
  169. package/dist/latex-loop.js.map +1 -1
  170. package/dist/lib/agent-cli-version.d.ts +65 -0
  171. package/dist/lib/agent-cli-version.d.ts.map +1 -0
  172. package/dist/lib/agent-cli-version.js +77 -0
  173. package/dist/lib/agent-cli-version.js.map +1 -0
  174. package/dist/lib/check.d.ts +19 -0
  175. package/dist/lib/check.d.ts.map +1 -0
  176. package/dist/lib/check.js +52 -0
  177. package/dist/lib/check.js.map +1 -0
  178. package/dist/lib/markdown.d.ts +180 -0
  179. package/dist/lib/markdown.d.ts.map +1 -0
  180. package/dist/lib/markdown.js +302 -0
  181. package/dist/lib/markdown.js.map +1 -0
  182. package/dist/lib/paper-config.d.ts +84 -0
  183. package/dist/lib/paper-config.d.ts.map +1 -0
  184. package/dist/lib/paper-config.js +138 -0
  185. package/dist/lib/paper-config.js.map +1 -0
  186. package/dist/lib/skill-eval-fixture.d.ts +29 -0
  187. package/dist/lib/skill-eval-fixture.d.ts.map +1 -0
  188. package/dist/lib/skill-eval-fixture.js +62 -0
  189. package/dist/lib/skill-eval-fixture.js.map +1 -0
  190. package/dist/link-skills.d.ts.map +1 -1
  191. package/dist/link-skills.js +5 -4
  192. package/dist/link-skills.js.map +1 -1
  193. package/dist/new-paper.d.ts.map +1 -1
  194. package/dist/new-paper.js +7 -3
  195. package/dist/new-paper.js.map +1 -1
  196. package/dist/paper-files.d.ts +4 -4
  197. package/dist/paper-files.d.ts.map +1 -1
  198. package/dist/paper-files.js +6 -6
  199. package/dist/paper-files.js.map +1 -1
  200. package/dist/paper-settings.d.ts.map +1 -1
  201. package/dist/paper-settings.js +8 -7
  202. package/dist/paper-settings.js.map +1 -1
  203. package/dist/pdf-facts.d.ts +51 -1
  204. package/dist/pdf-facts.d.ts.map +1 -1
  205. package/dist/pdf-facts.js +25 -16
  206. package/dist/pdf-facts.js.map +1 -1
  207. package/dist/pdf-geometry.d.ts.map +1 -1
  208. package/dist/pdf-geometry.js +3 -4
  209. package/dist/pdf-geometry.js.map +1 -1
  210. package/dist/ports/tool-installer.d.ts +2 -14
  211. package/dist/ports/tool-installer.d.ts.map +1 -1
  212. package/dist/presets.d.ts.map +1 -1
  213. package/dist/presets.js +19 -6
  214. package/dist/presets.js.map +1 -1
  215. package/dist/references.d.ts.map +1 -1
  216. package/dist/references.js +15 -10
  217. package/dist/references.js.map +1 -1
  218. package/dist/rules-config.d.ts +26 -1
  219. package/dist/rules-config.d.ts.map +1 -1
  220. package/dist/rules-config.js +15 -12
  221. package/dist/rules-config.js.map +1 -1
  222. package/dist/structure.js +1 -1
  223. package/dist/structure.js.map +1 -1
  224. package/dist/tex-requirements.d.ts +4 -0
  225. package/dist/tex-requirements.d.ts.map +1 -1
  226. package/dist/tex-requirements.js +22 -5
  227. package/dist/tex-requirements.js.map +1 -1
  228. package/dist/toolchain.d.ts.map +1 -1
  229. package/dist/toolchain.js +21 -16
  230. package/dist/toolchain.js.map +1 -1
  231. package/dist/types.d.ts +5 -3
  232. package/dist/types.d.ts.map +1 -1
  233. package/dist/venue-rules.d.ts.map +1 -1
  234. package/dist/venue-rules.js +21 -8
  235. package/dist/venue-rules.js.map +1 -1
  236. package/docs/configuration.md +2 -2
  237. package/docs/e2e.md +7 -7
  238. package/docs/install.md +1 -1
  239. package/docs/prior-art/README.md +1 -1
  240. package/docs/prior-art/package-location.md +5 -5
  241. package/docs/prior-art/repro/README.md +1 -1
  242. package/docs/prior-art/test-tooling.md +2 -2
  243. package/docs/testing.md +11 -8
  244. package/docs/toolchain.md +1 -1
  245. package/eslint-rules/{bib-reachable-entry.mjs → bib-reachable-entry.ts} +5 -4
  246. package/eslint-rules/install-path-literals.harness.mjs +1 -1
  247. package/eslint-rules/{install-path-literals.mjs → install-path-literals.ts} +18 -10
  248. package/eslint-rules/latex-language.harness.mjs +2 -2
  249. package/eslint-rules/latex-language.test.mjs +7 -1
  250. package/eslint-rules/{latex-language.mjs → latex-language.ts} +297 -109
  251. package/eslint-rules/paper-research-question.harness.mjs +3 -3
  252. package/eslint-rules/{paper-research-question.mjs → paper-research-question.ts} +35 -16
  253. package/eslint-rules/paper-stages.harness.mjs +2 -2
  254. package/eslint-rules/paper-stages.test.mjs +8 -2
  255. package/eslint-rules/{paper-stages.mjs → paper-stages.ts} +76 -31
  256. package/eslint-rules/paper-typography.test.mjs +3 -3
  257. package/eslint-rules/{paper-typography.mjs → paper-typography.ts} +134 -56
  258. package/eslint-rules/papers.harness.mjs +2 -2
  259. package/eslint-rules/{papers.mjs → papers.ts} +7 -4
  260. package/eslint-rules/pdf-last-page-balance.harness.mjs +3 -3
  261. package/eslint-rules/{pdf-last-page-balance.mjs → pdf-last-page-balance.ts} +89 -32
  262. package/eslint-rules/review-frontmatter.test.mjs +2 -2
  263. package/eslint-rules/{review-frontmatter.mjs → review-frontmatter.ts} +38 -16
  264. package/eslint-rules/rule-context.ts +62 -0
  265. package/eslint-rules/{sibling-frontmatter.mjs → sibling-frontmatter.ts} +1 -1
  266. package/eslint-rules/temp-root-realpath.harness.mjs +1 -1
  267. package/eslint-rules/{temp-root-realpath.mjs → temp-root-realpath.ts} +20 -7
  268. package/eslint-rules/tex-build.harness.mjs +2 -2
  269. package/eslint-rules/{tex-build.mjs → tex-build.ts} +6 -5
  270. package/eslint.config.mjs +146 -30
  271. package/fixtures/install-path-literals/clean.fixture.mjs +1 -1
  272. package/fixtures/install-path-literals/clean.md +2 -2
  273. package/fixtures/install-path-literals/defect.fixture.mjs +1 -1
  274. package/fixtures/install-path-literals/defect.md +2 -2
  275. package/fixtures/pdf-facts/README.md +2 -2
  276. package/fixtures/real-markdown-paper/{baseline.mjs → baseline.ts} +31 -10
  277. package/hooks/{hooks.harness.mjs → hooks.harness.ts} +51 -34
  278. package/hooks/paper-edit-guard.hook.mjs +1 -1
  279. package/hooks/{paper-edit-guard.test.mjs → paper-edit-guard.test.ts} +3 -7
  280. package/hooks/paper-skills-nudge.hook.mjs +1 -1
  281. package/hooks/{paper-skills-nudge.test.mjs → paper-skills-nudge.test.ts} +2 -6
  282. package/hooks/paper-status-gates.sh +9 -19
  283. package/hooks/{paper-status-gates.test.mjs → paper-status-gates.test.ts} +2 -6
  284. package/lib/agent-cli-version.harness.mjs +1 -1
  285. package/lib/{agent-cli-version.mjs → agent-cli-version.ts} +32 -18
  286. package/lib/check.test.ts +14 -9
  287. package/lib/{check.mjs → check.ts} +22 -9
  288. package/lib/markdown.test.mjs +7 -7
  289. package/lib/{markdown.mjs → markdown.ts} +68 -29
  290. package/lib/paper-config.harness.mjs +9 -9
  291. package/lib/{paper-config.mjs → paper-config.ts} +20 -19
  292. package/lib/skill-checks.mjs +24 -381
  293. package/lib/skill-checks.test.mjs +64 -116
  294. package/lib/skill-corpus.mjs +10 -251
  295. package/lib/skill-corpus.test.mjs +2 -13
  296. package/lib/skill-eval-kit.mjs +1 -1
  297. package/lib/skill-trigger-cases.mjs +1 -1
  298. package/package.json +42 -10
  299. package/scripts/{check.harness.mjs → check.harness.ts} +111 -10
  300. package/scripts/{check.test.mjs → check.test.ts} +13 -10
  301. package/scripts/{check.mjs → check.ts} +116 -42
  302. package/scripts/coverage-config.test.ts +80 -38
  303. package/scripts/eslint-report-guard.test.mjs +1 -1
  304. package/scripts/harness-api.frozen.json +14 -16
  305. package/scripts/harness-api.test.ts +8 -5
  306. package/scripts/layer-legacy-frozen.test.ts +17 -13
  307. package/scripts/{layer-legacy-frozen.mjs → layer-legacy-frozen.ts} +128 -39
  308. package/scripts/release-config.test.ts +12 -4
  309. package/scripts/rules-see-files.test.mjs +1 -1
  310. package/scripts/test.test.ts +78 -0
  311. package/scripts/test.ts +87 -0
  312. package/skills/analyze-sibling-paper/SKILL.md +1 -39
  313. package/skills/analyze-sibling-paper/SKILL.md.spec.ts +0 -38
  314. package/skills/argument-arc/SKILL.md +2 -36
  315. package/skills/argument-arc/SKILL.md.spec.ts +1 -43
  316. package/skills/build-benchmark/SKILL.md +1 -41
  317. package/skills/build-benchmark/SKILL.md.spec.ts +0 -40
  318. package/skills/camera-ready/SKILL.md +1 -34
  319. package/skills/camera-ready/SKILL.md.spec.ts +0 -33
  320. package/skills/cold-read-diff/SKILL.md +1 -35
  321. package/skills/cold-read-diff/SKILL.md.spec.ts +0 -34
  322. package/skills/draft-paper/SKILL.md +2 -41
  323. package/skills/draft-paper/SKILL.md.spec.ts +1 -50
  324. package/skills/extend-paper/SKILL.md +2 -33
  325. package/skills/extend-paper/SKILL.md.spec.ts +1 -42
  326. package/skills/find-venue/SKILL.md +2 -38
  327. package/skills/find-venue/SKILL.md.spec.ts +1 -47
  328. package/skills/grade-paper-writing/SKILL.md +3 -38
  329. package/skills/grade-paper-writing/SKILL.md.spec.ts +2 -47
  330. package/skills/grade-paper-writing/prose-lint.mjs +3 -4
  331. package/skills/grade-paper-writing/{prose-lint.test.mjs → prose-lint.test.ts} +3 -3
  332. package/skills/harden-paper/SKILL.md +1 -39
  333. package/skills/harden-paper/SKILL.md.spec.ts +0 -38
  334. package/skills/map-prior-work/SKILL.md +1 -35
  335. package/skills/map-prior-work/SKILL.md.spec.ts +0 -34
  336. package/skills/osf-artifact-upload/{osf-artifact-upload.harness.mjs → osf-artifact-upload.harness.ts} +3 -4
  337. package/skills/paper-adversarial-review/SKILL.md +2 -35
  338. package/skills/paper-adversarial-review/SKILL.md.spec.ts +1 -43
  339. package/skills/paper-pipeline/SKILL.md +2 -28
  340. package/skills/paper-pipeline/SKILL.md.spec.ts +1 -27
  341. package/skills/paper-pipeline/description-language.eval.mjs +1 -1
  342. package/skills/paper-pipeline/framing-vs-vocabulary.eval.mjs +1 -1
  343. package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +2 -2
  344. package/skills/paper-pipeline/paper-pipeline.harness.mjs +25 -40
  345. package/skills/paper-pipeline/pipeline-firing.eval.mjs +1 -1
  346. package/skills/paper-pipeline/pipeline-language.eval.mjs +1 -1
  347. package/skills/paper-pipeline/references/acceptance-gate.md +1 -6
  348. package/skills/paper-pipeline/references/pipeline-status-template.md +8 -35
  349. package/skills/paper-pipeline/scripts/README.md +30 -225
  350. package/skills/paper-pipeline/scripts/artifact-coverage.harness.mjs +4 -8
  351. package/skills/paper-pipeline/scripts/artifact-coverage.mjs +1 -1
  352. package/skills/paper-pipeline/scripts/{artifact-coverage.test.mjs → artifact-coverage.test.ts} +2 -2
  353. package/skills/paper-pipeline/scripts/consumer.harness.mjs +6 -104
  354. package/skills/paper-pipeline/scripts/consumer.mjs +24 -109
  355. package/skills/paper-pipeline/scripts/consumer.test.mjs +1 -1
  356. package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +1 -1
  357. package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +1 -4
  358. package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +1 -1
  359. package/skills/paper-pipeline/scripts/generated-code.harness.mjs +2 -3
  360. package/skills/paper-pipeline/scripts/{generated-code.test.mjs → generated-code.test.ts} +3 -3
  361. package/skills/paper-pipeline/scripts/population-map.harness.mjs +3 -3
  362. package/skills/paper-pipeline/scripts/population-map.mjs +1 -4
  363. package/skills/paper-pipeline/scripts/population-map.test.mjs +1 -1
  364. package/skills/paper-pipeline/scripts/{round-diff.harness.mjs → round-diff.harness.ts} +45 -18
  365. package/skills/paper-pipeline/scripts/round-diff.mjs +1 -1
  366. package/skills/paper-pipeline/scripts/round-diff.test.mjs +1 -1
  367. package/skills/paper-status/SKILL.md +2 -42
  368. package/skills/paper-status/SKILL.md.spec.ts +1 -41
  369. package/skills/pc-panel-review/SKILL.md +2 -46
  370. package/skills/pc-panel-review/SKILL.md.spec.ts +1 -45
  371. package/skills/plan-paper-timeline/SKILL.md +2 -36
  372. package/skills/plan-paper-timeline/SKILL.md.spec.ts +0 -36
  373. package/skills/plan-paper-timeline/fixtures/{fake-google-calendar.mjs → fake-google-calendar.ts} +101 -70
  374. package/skills/plan-paper-timeline/plan-paper-timeline.effects.harness.mjs +3 -3
  375. package/skills/render-paper/SKILL.md +1 -37
  376. package/skills/render-paper/SKILL.md.spec.ts +0 -36
  377. package/skills/render-paper/{extract-pdf-facts.harness.mjs → extract-pdf-facts.harness.ts} +34 -9
  378. package/skills/render-paper/render-paper.harness.mjs +2 -2
  379. package/skills/research-ideate/SKILL.md +2 -39
  380. package/skills/research-ideate/SKILL.md.spec.ts +1 -47
  381. package/skills/study-accepted-papers/SKILL.md +1 -34
  382. package/skills/study-accepted-papers/SKILL.md.spec.ts +0 -33
  383. package/skills/submit-paper/SKILL.md +1 -33
  384. package/skills/submit-paper/SKILL.md.spec.ts +0 -32
  385. package/skills/sweep-design-space/SKILL.md +1 -38
  386. package/skills/sweep-design-space/SKILL.md.spec.ts +0 -37
  387. package/skills/tighten-paper/SKILL.md +2 -39
  388. package/skills/tighten-paper/SKILL.md.spec.ts +1 -47
  389. package/skills/tighten-paper/structure.mjs +5 -12
  390. package/skills/tighten-paper/{structure.test.mjs → structure.test.ts} +4 -3
  391. package/skills/verify-citations/SKILL.md +2 -38
  392. package/skills/verify-citations/SKILL.md.spec.ts +1 -37
  393. package/skills/verify-citations/scripts/{bib-authors.cli.test.mjs → bib-authors.cli.test.ts} +14 -4
  394. package/skills/verify-citations/scripts/bib-authors.d.mts +63 -0
  395. package/skills/verify-citations/scripts/bib-authors.mjs +1 -4
  396. package/skills/verify-citations/scripts/{verify-cites.cli.test.mjs → verify-cites.cli.test.ts} +32 -9
  397. package/skills/verify-citations/scripts/verify-cites.d.mts +58 -0
  398. package/src/CLAUDE.md +1 -1
  399. package/src/adapters/banal/failure.ts +9 -10
  400. package/src/adapters/banal/index.test.ts +5 -8
  401. package/src/adapters/banal/index.ts +8 -7
  402. package/src/adapters/banal/install.ts +9 -4
  403. package/src/adapters/banal/invocation.test.ts +23 -9
  404. package/src/adapters/banal/invocation.ts +24 -9
  405. package/src/adapters/banal/locate.test.ts +2 -2
  406. package/src/adapters/banal/locate.ts +27 -21
  407. package/src/adapters/banal/output.test.ts +1 -1
  408. package/src/adapters/banal/run.test.ts +5 -5
  409. package/src/adapters/banal/run.ts +16 -10
  410. package/src/adapters/banal/settings.ts +2 -2
  411. package/src/adapters/banal/xml.test.ts +1 -1
  412. package/src/adapters/curl/download.test.ts +8 -6
  413. package/src/adapters/memory/index.ts +4 -3
  414. package/src/adapters/node/files.io.ts +2 -2
  415. package/src/adapters/node/files.test.ts +8 -4
  416. package/src/adapters/node/process.io.ts +13 -5
  417. package/src/adapters/node/process.test.ts +5 -5
  418. package/src/adapters/node/workspace.io.ts +9 -4
  419. package/src/adapters/node/workspace.test.ts +3 -1
  420. package/src/adapters/pdfjs/fill.test.ts +2 -2
  421. package/src/adapters/pdfjs/fill.ts +14 -7
  422. package/src/adapters/references/index.test.ts +31 -38
  423. package/src/adapters/references/index.ts +33 -60
  424. package/src/adapters/references/pool.ts +3 -5
  425. package/src/adapters/references/reach.io.ts +2 -1
  426. package/src/build-engine.harness.mjs +1 -1
  427. package/src/build-engine.test.ts +13 -8
  428. package/src/build-engine.ts +12 -7
  429. package/src/build.harness.mjs +2 -2
  430. package/src/build.test.ts +24 -24
  431. package/src/build.ts +34 -26
  432. package/src/caller-path.ts +13 -0
  433. package/src/cli.harness.mjs +6 -8
  434. package/src/cli.test.ts +31 -13
  435. package/src/cli.ts +117 -58
  436. package/src/doctor.harness.mjs +3 -3
  437. package/src/doctor.test.ts +1 -1
  438. package/src/doctor.ts +17 -7
  439. package/src/domain/lookup-cache.ts +21 -11
  440. package/src/domain/paths.test.ts +15 -0
  441. package/src/domain/paths.ts +20 -1
  442. package/src/domain/ready.ts +31 -0
  443. package/src/domain/record.test.ts +20 -0
  444. package/src/domain/record.ts +12 -0
  445. package/src/domain/result.test.ts +2 -2
  446. package/src/domain/sha256.ts +5 -3
  447. package/src/domain/text.test.ts +32 -0
  448. package/src/domain/text.ts +23 -0
  449. package/src/engine.harness.mjs +2 -2
  450. package/src/engine.ts +34 -5
  451. package/src/facts-file.test.ts +9 -7
  452. package/src/facts-file.ts +45 -29
  453. package/src/{hooks-settings.harness.mjs → hooks-settings.harness.ts} +56 -20
  454. package/src/hooks-settings.ts +48 -13
  455. package/src/init.test.ts +37 -14
  456. package/src/init.ts +48 -24
  457. package/src/{latex-log.harness.mjs → latex-log.harness.ts} +14 -14
  458. package/src/{latex-loop.harness.mjs → latex-loop.harness.ts} +64 -36
  459. package/src/latex-loop.ts +3 -3
  460. package/src/{link-skills.harness.mjs → link-skills.harness.ts} +19 -19
  461. package/src/link-skills.test.ts +3 -3
  462. package/src/link-skills.ts +5 -4
  463. package/src/lint-fix.test.ts +4 -6
  464. package/src/linted-set.test.ts +2 -2
  465. package/src/new-paper.harness.mjs +9 -25
  466. package/src/new-paper.test.ts +8 -10
  467. package/src/new-paper.ts +9 -5
  468. package/src/paper-files.test.ts +25 -24
  469. package/src/paper-files.ts +16 -27
  470. package/src/paper-settings-commands.test.ts +5 -4
  471. package/src/paper-settings.test.ts +7 -7
  472. package/src/paper-settings.ts +13 -14
  473. package/src/{pdf-facts.harness.mjs → pdf-facts.harness.ts} +9 -8
  474. package/src/pdf-facts.test.ts +25 -15
  475. package/src/pdf-facts.ts +81 -23
  476. package/src/{pdf-geometry.harness.mjs → pdf-geometry.harness.ts} +19 -12
  477. package/src/pdf-geometry.ts +8 -4
  478. package/src/ports/tool-installer.ts +2 -17
  479. package/src/presets.ts +31 -16
  480. package/src/references.test.ts +76 -62
  481. package/src/references.ts +18 -12
  482. package/src/{rules-config.harness.mjs → rules-config.harness.ts} +7 -13
  483. package/src/rules-config.ts +46 -14
  484. package/src/rules-off.test.ts +6 -4
  485. package/src/{structure.harness.mjs → structure.harness.ts} +26 -15
  486. package/src/structure.ts +1 -1
  487. package/src/{tex-requirements.harness.mjs → tex-requirements.harness.ts} +50 -22
  488. package/src/tex-requirements.test.ts +21 -0
  489. package/src/tex-requirements.ts +37 -11
  490. package/src/toolchain.harness.mjs +2 -2
  491. package/src/toolchain.test.ts +10 -6
  492. package/src/toolchain.ts +31 -17
  493. package/src/types.ts +5 -3
  494. package/src/venue-rules.test.ts +70 -68
  495. package/src/venue-rules.ts +22 -12
  496. package/tsconfig.json +6 -3
  497. package/tsconfig.pkg.json +10 -0
  498. package/lib/check.d.mts +0 -17
  499. package/lib/paper-config.d.mts +0 -31
  500. package/scripts/layer-legacy-frozen.d.mts +0 -36
  501. package/scripts/rules-are-content-only.harness.mjs +0 -105
  502. package/scripts/rules-are-content-only.mjs +0 -148
  503. package/scripts/rules-are-content-only.test.mjs +0 -61
  504. package/scripts/test.mjs +0 -44
  505. package/scripts/test.test.mjs +0 -52
  506. package/skills/paper-pipeline/scripts/announce.mjs +0 -67
  507. package/skills/paper-pipeline/scripts/announce.test.mjs +0 -102
  508. package/skills/paper-pipeline/scripts/fixtures/clean/PIPELINE-STATUS.md +0 -51
  509. package/skills/paper-pipeline/scripts/fixtures/dirty/PIPELINE-STATUS.md +0 -52
  510. package/skills/paper-pipeline/scripts/fixtures/dirty/paper.md +0 -6
  511. package/skills/paper-pipeline/scripts/fixtures/scorecard.mjs +0 -71
  512. package/skills/paper-pipeline/scripts/ledger.cli.test.mjs +0 -302
  513. package/skills/paper-pipeline/scripts/ledger.mjs +0 -623
  514. package/skills/paper-pipeline/scripts/ledger.test.mjs +0 -254
  515. package/skills/paper-pipeline/scripts/pipeline-check.cli.test.mjs +0 -474
  516. package/skills/paper-pipeline/scripts/pipeline-check.harness.mjs +0 -328
  517. package/skills/paper-pipeline/scripts/pipeline-check.mjs +0 -753
  518. package/skills/paper-pipeline/scripts/pipeline-edges.mjs +0 -169
  519. package/skills/paper-pipeline/scripts/pipeline-edges.test.mjs +0 -73
  520. package/skills/paper-pipeline/scripts/run-mechanical.mjs +0 -648
  521. package/skills/paper-pipeline/scripts/run-mechanical.test.mjs +0 -468
  522. package/skills/paper-pipeline/scripts/status.mjs +0 -295
  523. package/skills/paper-pipeline/scripts/status.test.mjs +0 -232
  524. /package/bin/{paperlint.test.mjs → paperlint.test.ts} +0 -0
  525. /package/lib/{skill-eval-fixture.mjs → skill-eval-fixture.ts} +0 -0
  526. /package/skills/grade-paper-writing/__snapshots__/{prose-lint.test.mjs.snap → prose-lint.test.ts.snap} +0 -0
  527. /package/skills/tighten-paper/__snapshots__/{structure.test.mjs.snap → structure.test.ts.snap} +0 -0
@@ -137,7 +137,7 @@ import {
137
137
  import { paid_measureTriggerRate as measureTriggerRate } from "vigiles/eval";
138
138
  import { existsSync, readFileSync } from "node:fs";
139
139
  import { join } from "node:path";
140
- import { frontmatterBlock } from "../../lib/markdown.mjs";
140
+ import { frontmatterBlock } from "#lib/markdown";
141
141
  import { parseFm } from "../../lib/skill-corpus.mjs";
142
142
  import { installedSkills } from "./scripts/consumer.mjs";
143
143
 
@@ -250,15 +250,13 @@ program is what you can write only when the question is Type A.
250
250
 
251
251
  | unit | question | rung |
252
252
  | ---------------------------------------------------- | ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
253
- | `ledger.mjs` / `status.mjs` | did the gate run, against which bytes, and has it ever said no | A (3) |
254
- | `run-mechanical.mjs` | run every script gate; exit on the worst FACT gate | A (3), wrapping A |
255
253
  | `repro/report-submission.py` | body pages, overfull boxes, unresolved refs, dropped characters | A (3) |
256
254
  | `repro/check-anon.sh`, `check-figure.sh` | deanonymising strings; does the figure fit the column | A (3) |
257
255
  | `structure.mjs`, `prose-lint.mjs` | section weights, unjustified blocks, thresholds with published baselines | A (3) — _measuring_ a B question, which is legitimate and is not the same as answering it |
258
256
  | `check-provenance.mjs` | untraced number; arm word in the path vs condition word in the sentence | A (3) |
259
257
  | `population-map.mjs`, `artifact-coverage.mjs` | which set does this number count; does the bundle hold data for what the paper points at | A (3) |
260
258
  | rules `refs/*` (facts by `extract-ref-facts.mjs`) | does the registry have this record | A (3), **external adjudicator** |
261
- | `pipeline-check.mjs`, `paper-lint.mjs` | scorecard consistency, gate staleness, appendix ratio, shaved passages | A (3) |
259
+ | `paper-lint.mjs` | appendix ratio, shaved passages | A (3) |
262
260
  | `repro/arm_permutation.py`, `repro/delivered_pdf.py` | is the quantity bound to its arm; did it survive typesetting | A (3), metamorphic |
263
261
  | **`repro/paper_numbers.py` + `numbers.tsv`** | — | **rung 1: no PDF exists in which the number is wrong** |
264
262
  | `paper-edit-guard.hook.ts` | writing to a paper from Bash | rung 2 — **leaky**, an interpreter reading its program from stdin still gets through (`papers/CLAUDE.md`, 2026-08-06) |
@@ -320,9 +318,6 @@ questions into programs.
320
318
  ## See also
321
319
 
322
320
  - `review-ratchet.md` — what a caught overclaim costs, and the pass that pays it back.
323
- - `eslint-rules/pipeline-status.mjs` (rule `pipeline/judge-recorded`; until 2026-08-26 in
324
- `../scripts/pipeline-check.mjs`) — `single-family-jury` and `unattributed-verdict`, the mechanical
325
- leg of this document.
326
321
  - `../scripts/round-diff.mjs` — the per-round edit gate: a driving pass may only change what it
327
322
  declared, and the whole document's growth is measured every round.
328
323
  - the papers tree's own `CLAUDE.md` — the four-rung ladder, and the rule that a rule written in prose is
@@ -5,11 +5,9 @@
5
5
  "did everything run?" is a checked fact, not a memory. Chat evaporates; this file rides with the paper
6
6
  to camera-ready.
7
7
 
8
- 🔴 **It is also machine-read.** `../scripts/pipeline-check.mjs` parses this file, so the four section
9
- headings, the row shape, and the ISO dates are a **format contract**, not decoration. Break them and
10
- the checks go quiet — which, as of 2026-08-03, is a failure mode this repo has lived through three
11
- times in one day. Run `node .claude/skills/paper-pipeline/scripts/pipeline-check.mjs <paper-dir>`
12
- after editing to confirm it still parses.
8
+ 🔴 **It is also machine-read.** The status hook prints the `**Readiness verdict:**` line and every
9
+ `☐` gate row each time a paper source is edited, and the rules read the front matter below. Keep
10
+ the verdict line's wording and the `☐`/`☑` marks as they are, or the hook goes quiet.
13
11
 
14
12
  ## The front matter
15
13
 
@@ -41,7 +39,7 @@ match, and the finding will quote exactly what it looked for.
41
39
  ## The row ids, and what each one runs
42
40
 
43
41
  The `id` cell is the **join key** — the same string appears in this template, in every paper's
44
- scorecard, in `Requires` cells, in `../scripts/pipeline-edges.mjs` and in the skills' own prose. So
42
+ scorecard, in `Requires` cells and in the skills' own prose. So
45
43
  the scorecard explains itself where it lives:
46
44
 
47
45
  | id | section | what it is | run by |
@@ -79,27 +77,6 @@ two rows, merged by every consumer — and `Id` (research-ideate) was the header
79
77
  parser's header-row filter silently ate that row on **every** scorecard for as long as the template
80
78
  existed. Neither is expressible now. Keep new ids lowercase, one word, and say what the row is.
81
79
 
82
- ### How `pipeline-check.mjs` reads this file (rewritten 2026-08-09 to parse markdown, not match it)
83
-
84
- Both defects above, plus a third — two rows sharing an id merging in silence — had one cause: a
85
- regular expression guessing at document structure. The parser is now an AST walk (`markdown-it`),
86
- and two rules decide what it reads. They are stated here because the checker's findings point at
87
- them:
88
-
89
- - **A heading names a section iff its FIRST Latin-script word is `SETUP` / `LOOP` / `CONTINUOUS` /
90
- `GATES` / `AFTER`.** Anything after that word is free: `### GATES (re-run 06.08)` is GATES. A
91
- heading with a different first word — or none, like `### Гейты, прогнанные 06.08` — names no
92
- section and CLOSES the previous one. Headings are **not** translated or fuzzy-matched: guessing
93
- what a heading means is the defect, not the fix. Rows in such a region are reported
94
- (`unattributed-row`), never dropped.
95
- - **A table is part of the scorecard iff its header row's first cell is `id`** (also `код`, `code`).
96
- The header comes from the table's real `thead`, so there is nothing to guess and a data row
97
- reading `| Id |` is a row. Prose tables in a scorecard — the panel must-fix table, a ceiling
98
- classification — have no id column and are correctly ignored. The pre-2026-08 single-table format
99
- keys its first column `#`, a row number; it does not parse, and the checker says so by name.
100
-
101
- **A row is never dropped in silence.** Unattributable, unreadable and duplicate ids are findings.
102
-
103
80
  ## Rules
104
81
 
105
82
  - **One row per unit of work; update the row in the SAME commit as the work.** Status + ISO date +
@@ -108,14 +85,10 @@ them:
108
85
  stage 7"). SETUP is done-or-not. LOOP iterates. **CONTINUOUS is never done** — its date is compared
109
86
  against the paper's own mtime, and a `☑` older than the text is a _stale pass_, which reads as
110
87
  green and is not. GATES are blocked on named inputs.
111
- - 🔴 **The `Requires` cells above are not free-form — do not thin them.** The canonical edge set lives
112
- in `../scripts/pipeline-edges.mjs`, with the sentence from each skill's own SKILL.md that admitted
113
- the edge, and the ESLint rule `pipeline/undeclared-input` (`eslint-rules/pipeline-status.mjs`;
114
- until 2026-08-26 `pipeline-check.mjs`) reports any cell that drops one. The cell
115
- was retyped by hand for every paper until 2026-08-08, and `compile-rules-2026` had lost four of
116
- them — including `arc`, which argument-arc's SKILL.md says must precede `tighten-paper`. A dropped
117
- edge is invisible: `gate-missing-input` can only enforce what the cell says. Adding an input the
118
- table does not know about is fine and reports nothing.
88
+ - 🔴 **The `Requires` cells above are not free-form — do not thin them.** The cell was retyped by
89
+ hand for every paper until 2026-08-08, and `compile-rules-2026` had lost four of them — including
90
+ `arc`, which argument-arc's SKILL.md says must precede `tighten-paper`. A dropped edge is
91
+ invisible. Adding an input the table does not know about is fine.
119
92
  - 🔴 **Row `access` is the one that is not about the paper, and it is the one that bites.** _Can you
120
93
  physically upload?_ — account exists, profile is **ACTIVE** rather than "pending moderation", the
121
94
  portal is reachable, you know the form's fields and its artifact-hosting requirement. On
@@ -1,225 +1,30 @@
1
- # paper-pipeline/scripts — did the check run, against what, and what did it say
2
-
3
- Four files record paper-pipeline skill runs and compute the status view that replaced the
4
- hand-maintained `PIPELINE-STATUS.md` table. Alongside them live the harnesses that plant a defect in
5
- each mechanical gate and check the gate says no (see **Harnesses**, added 2026-08-07).
6
-
7
- > **📍 Moved here 2026-08-15 from `.claude/pipeline/`.** The top-level directory was distributed:
8
- > everything with one owner went to it. Here this is the paper-pipeline family —
9
- > `ledger` · `announce` · `status` · `run-mechanical` plus nine checkers that already lay
10
- > here, and **their tests, which until that day lay in another top-level directory**. That is,
11
- > colocation was missing precisely where we preach it: checker in the skill, its harness — outside.
12
- >
13
- > What remained without an owner (shared library of 24 skills, `markdown.mjs`) —
14
- > in [`.claude/lib/`](../../../lib/README.md), also there the breakdown of four forms of reference, from which
15
- > path-based search sees only one.
16
-
17
- | file | what it is for |
18
- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
19
- | `ledger.mjs` | The append-only run ledger. `record()` writes one JSON line carrying a FINDING or an ABSTENTION **plus a hash of the paper and a hash of the skill's own source**. `status()` derives FRESH / STALE-PAPER / STALE-SKILL / NEVER-RUN from those hashes against the bytes on disk. Everything else is derived from this file. |
20
- | `announce.mjs` | `node .claude/skills/paper-pipeline/scripts/announce.mjs <skill> <paper-dir>` — prints a two-line banner to stderr and writes an `ABSTAINED started` row. Called by a skill as its first step. |
21
- | `status.mjs` | The computed status view. No field here can be set by a human. |
22
- | `runs.jsonl` | The ledger itself. Append-only; do not edit by hand. |
23
-
24
- ## The one command
25
-
26
- ```
27
- node .claude/skills/paper-pipeline/scripts/status.mjs <papers-root>/<paper>
28
- ```
29
-
30
- ## Three properties, each one a defect we actually shipped
31
-
32
- 1. **Staleness is computed, never declared.** A run is fresh only if the paper hashes to what it
33
- hashed when the check ran. Nobody ticks a box, so nobody forgets to un-tick one.
34
- 2. **The checker's own source is part of the key.** Improve a skill and its old answers go stale —
35
- they were answers from a different tool. This is what `make` cannot do and why its staleness
36
- answers could not be trusted here.
37
- 3. **A check that has never said no is reported as suspect.** From mutation testing: a test that
38
- kills no mutant is not a test. A tightening pass once returned KEEP on 80 of 81 sections and
39
- still counted as run.
40
-
41
- ## 🔴 There is no `PASS` constructor (2026-08-10)
42
-
43
- Five failures were observed on this pipeline. Three were defects of the CHECKING APPARATUS rather
44
- than of any manuscript, and they are one fact wearing three coats:
45
-
46
- 1. a gate was recorded as passed while the input it needs did not exist;
47
- 2. a check that had never once returned a negative counted as a working check;
48
- 3. five model reviewers from one vendor agreeing was recorded as an acquittal.
49
-
50
- Each is a stored value meaning _nothing was wrong_, written by a process with no evidence for it and
51
- thereafter indistinguishable from one that had some. So the value is gone. **A run records exactly
52
- two things:**
53
-
54
- | constructor | must carry | means |
55
- | ----------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------ |
56
- | `FINDING` | a `<count> ≥ 1` **and a report path that exists** (`--blocking` if it stops the pass) | something is wrong, and here is where to read about it |
57
- | `ABSTAINED` | a `<reason>` from a closed set | no judgement was produced, and here is why |
58
-
59
- Reasons: `started` · `no-witness` · `input-missing` · `blocked` · `crashed`.
60
-
61
- **"Nothing was wrong" is not a value that can be written down.** It is the ABSENCE of findings,
62
- derived at read time by whoever is asking — who then owns the inference. A completed clean run
63
- records `ABSTAINED no-witness`, which is not a pass with better manners: it says the check ran and
64
- has **no witness** for the negative answer.
65
-
66
- ### Prior art, both halves of it
67
-
68
- - **SARIF** (OASIS Standard 2.1.0, §3.27.9) defines `kind: "pass"` verbatim as _"The rule … was
69
- evaluated, and no problem was found"_, and the only REQUIRED property of a `result` is `message` —
70
- `locations` is optional, so a conformant acquittal can name no place, no span and no artifact.
71
- 🔴 **Do not write that industry lacks abstention.** `kind: "open"` sits in the same paragraph
72
- (_"insufficient information to decide whether a problem exists"_) and its NOTE 1 draws exactly our
73
- trichotomy. Our position is **"delete pass"**, never "add abstain". Established `[A]` against the
74
- OASIS text and its JSON schema in
75
- `the author's private research notes` §5.
76
- - **Certifying algorithms** — McConnell, Mehlhorn, Näher & Schweitzer, _Computer Science Review_
77
- 5(2):119–161, 2011 — is the ancestor: BOTH answers carry a witness ("bipartite" → a 2-colouring,
78
- "not bipartite" → an odd cycle). 🔴 **Our discipline is strictly WEAKER than theirs and must say
79
- so rather than borrow the credit:** we witness only the finding. `no-witness` is the honest name
80
- for what we have when a check comes back empty.
81
-
82
- ### What happens to the old rows
83
-
84
- `runs.jsonl` holds real rows carrying `PASS`, `FINDINGS`, `FAIL`, `ABSENT` and `ERROR`. **The file
85
- is not rewritten** — editing history so it agrees with today's design is the same move as ticking a
86
- box in the old status table.
87
-
88
- **Nor are those rows translated.** A silent reinterpretation is precisely the class of defect this
89
- refactor removes: map an old `PASS` onto `ABSTAINED no-witness` and a row asserting "nothing was
90
- wrong" comes back out of the reader wearing the new vocabulary's honesty without having earned it.
91
- So a legacy row keeps a kind of its own, `LEGACY`, and its original word verbatim in
92
- `legacyVerdict`. `LEGACY` is deliberately **not** in `KINDS`: it can be read and never written.
93
-
94
- - it counts as a RUN — someone did run something, and pretending otherwise is its own lie;
95
- - a legacy `FINDINGS`/`FAIL` row still surfaces its count and report path, because those rows carry
96
- evidence and discarding evidence is the destructive direction (the same reasoning that writes a
97
- broken `FINDING` row before failing the run);
98
- - a legacy `PASS`/`ABSENT`/`ERROR` row carries **nothing** forward. The status view prints it as
99
- `legacy:PASS` under a heading that says so. Note that real PASS rows sometimes carry a count
100
- (`sweep-design-space`, `findings: 8`) and that count meant _survivors_, not defects — it is not
101
- read as a finding count. Re-run the check to get an answer in a live vocabulary.
102
-
103
- ## 🔴 One check, one row (2026-08-10)
104
-
105
- **The ledger was keyed on the SKILL, but the thing that produces a verdict is the CHECK.** `status()`
106
- reduces a key's rows with `runs[runs.length - 1]` — correct across repeated runs of one check,
107
- catastrophic across two. Two mechanical checks filed under one skill name took turns being the
108
- answer: a clean run of the second erased a finding of the first from every derived view, while the
109
- ledger file itself still honestly held both lines.
110
-
111
- The key is now `skill` + `check`, written `skill/check` (or just `skill` for a skill with one
112
- check). `EXPECTED_GATES` entries are checks, not skills.
113
-
114
- The cost had been paid in unwired checks: `repro/arm_permutation.py` and `repro/delivered_pdf.py`
115
- were left out of `run-mechanical.mjs` precisely because their natural owner is `build-benchmark` and
116
- `check-provenance.mjs` was already sitting on that row. Both are wired in now, and all three appear
117
- side by side:
118
-
119
- ```
120
- 🟢 build-benchmark/check-provenance FRESH • 13 found
121
- 🟢 build-benchmark/arm-permutation FRESH abstained:no-witness
122
- 🟢 build-benchmark/delivered-pdf FRESH abstained:no-witness
123
- ```
124
-
125
- Legacy rows have no `check`, so they key on the bare skill name and do **not** merge into a new
126
- check's row — another consequence of not reinterpreting them.
127
-
128
- ## How a skill is wired (all 22 paper skills)
129
-
130
- Each `SKILL.md` carries exactly two blocks:
131
-
132
- - **`## Run me`**, right after the title — the `announce.mjs` call, first thing. An advisory pass
133
- cannot be observed failing, because silence is both its error state and its normal state; so
134
- starting is an event and events get written down.
135
- - **`## Record the verdict`**, the last step of the procedure — the `ledger.mjs record` call, in one
136
- shape across all 22 files:
137
-
138
- ```
139
- node .claude/skills/paper-pipeline/scripts/ledger.mjs record <skill> <paper-dir> FINDING <count> <report-path>
140
- node .claude/skills/paper-pipeline/scripts/ledger.mjs record <skill> <paper-dir> ABSTAINED <reason> "<one line>"
141
- ```
142
-
143
- Each block then says, in its own words, what a finding is for that skill and which abstentions it
144
- can honestly reach. `draft-paper` carries the one admission in the suite: it generates rather than
145
- judges, will abstain nearly always, and says so — `skill-checks.mjs` asserts that paragraph is
146
- still there, because an explained limitation deleted becomes an unexplained never-finding check.
147
-
148
- An announce row with no terminal row after it is itself a finding: the skill began and never
149
- finished. Neither row present means it did not run, whatever any status file claims.
150
-
151
- ## Two things to know before you trust the table
152
-
153
- - `EXPECTED_GATES` in `status.mjs` is the table. A check that records rows and is absent there
154
- writes rows nobody sees. Add a new skill AND its mechanical checks; `unlistedGates()` catches the
155
- omission after the fact, and `pipeline-corpus.harness.mjs` catches it before.
156
- - `ledger.test.mjs` and every harness redirect `PIPELINE_LEDGER` to a temp file **before**
157
- importing `ledger.mjs`. Save-and-restore is not isolation; it lost that race on its first day.
158
-
159
- ## Harnesses (2026-08-07)
160
-
161
- Property 3 above says a gate that has never returned a negative verdict is _suspect_. These turn
162
- that suspicion into evidence or into a bug report: each one plants the exact defect a gate claims to
163
- catch, asserts the gate fires, and asserts it stays **quiet on the clean case** — because a checker
164
- that fires on correct text is muted within a day, which is worse than one that misses.
165
-
166
- | harness | gate under test | what is planted |
167
- | ---------------------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
168
- | `gates.harness.mjs` | `structure.mjs`, `prose-lint.mjs`, `ledger.mjs`, `status.mjs`, `run-mechanical.mjs` | unjustified section · a sentence carrying five independent claims · an answer about a rewritten paper · a gate that records nothing · **`PASS` reaching the ledger** · an abstention with a free-text reason · a finding with no evidence, dead evidence, or evidence whose count disagrees · **a sibling check's clean run erasing a finding** · two checks sharing one row key · a status view that prints ✅ or the word PASS · a retired-vocabulary row read as a current answer · **a CLI flag that is advertised and unparsed** |
169
- | `population-map.harness.mjs` | `population-map.mjs` | owns its self-test (13 cases, run as a **subprocess**) + the CLI wiring the self-test never touches |
170
- | `provenance.harness.mjs` | `check-provenance.mjs` | a number from the `annotated` arm cited as "exactly as committed" · a bolded figure with no provenance row |
171
- | `artifact-coverage.harness.mjs` | `artifact-coverage.mjs` | a bundle with no data for the section the abstract leads with · an index promising a path that is not shipped · and, in the REVERSE direction, **a result that exists on disk and that neither the paper nor the released index mentions** — plus the ignore set that decides which of those count: an allowance applied, an allowance printed with its reason, a `ships-as:` claim whose bundle path has gone, a row naming a directory that no longer exists, and **the whole ignore ledger printed on a run with no findings** |
172
- | `generated-code.harness.mjs` | `generated-code.mjs` | an analysis script that draws randomness and never seeds it · one that hard-codes an absolute path · one that reads and then overwrites its own input (literal and `argv` forms). Plus every quiet case, which is where this checker lives or dies: a seed threaded through `--seed`, a lowercase Express route, a third party's relative path, a read-here-write-there script, a variable name reused across two loops — and the released bundle, which `check-anon.sh` cat. 5 owns and this one must not enter |
173
- | `pipeline-check.harness.mjs` | `pipeline-check.mjs` | six defects, **one at a time**, asserting the exact finding set. 🔴 Since 2026-08-26 here only checks whose input is OUTSIDE the file (git · clock · `reviews/` · `process.env`); sixteen others moved to `eslint-rules/pipeline-status.mjs` and are checked by `eslint-rules/pipeline-status.harness.mjs` |
174
- | `paper-lint.harness.mjs` | `paper-lint.mjs` | text moving under an unchanged scorecard · appendix outweighing the body · a shaved passage |
175
- | `round-diff.harness.mjs` | `paper-pipeline/scripts/round-diff.mjs` | a section changed outside the round's declaration (and one added, and one **removed**) · a cite and a bare bibliography entry arriving mid-round · a numeric literal arriving mid-round, with restating an existing one asserted FREE · the body over its budget · **three rounds compounding past the sum of their budgets while the open round is inside its own** · hedge density rising · a `touches:` list covering the paper · a base that does not resolve · edits with every round closed. Plus the clean case: a legal round must produce total silence |
176
- | `delivered-pdf.harness.mjs` | `repro/delivered_pdf.py` | a quantity deleted from the built page · **a superscript minus lost in typesetting, compiled and read back** · a value moved away from its claim · an occurrence whose prose the extractor lost · a missing, unreadable and stale PDF · and all six normalisation rules, one assertion each |
177
- | `bound-numbers.harness.mjs` | `repro/paper_numbers.py`, `md2submission.py`, `latex-build.sh` | a digit in a `\newcommand` name · two registry keys mangling to one macro · an unescaped `%` in the generated values · a converter handling the digits again · a missing `\input` · an unbound name that must stop the build with **no PDF** · a failing pass that leaves its PDF on disk. Plus the CONTROL: a hand-typed literal still compiles, **asserted** so nobody writes that this makes a wrong number unrepresentable |
178
- | `textidote.harness.mjs` | `repro/textidote_check.py` (TeXtidote v0.9) | a misspelling planted into real prose from the paper, live through the pinned jar · a coinage with a committed row staying silent in the same run · the flagged span truncated by one letter · the caret row leaking into the message · each of the three exclusions (`{{macro}}`, `` `code` ``, the reference list) · **an appendix AFTER `## References` still being judged** · a key that stops discriminating by rule, by word, or by case · a tolerated row moved 40 lines down and indented · the tolerated set, its reasons and the stale rows going unprinted · a missing jar or JRE answering with an empty findings array |
179
- | `uncited-refs.harness.mjs` | `repro/uncited_refs.py` (`checkcites`) | a bibliography entry no `\cite` points at, planted into a copy of the real build · a finding that names only the bibtex key and not the work · **`checkcites` absent, asserted against the same fixture that DOES report a finding when it is present** · a paper that was never built · a bibliography that could not be read becoming a finding named after the missing file |
180
- | `<skill>/<skill>.harness.mjs` (×22, colocated) | **one SKILL.md each**, via `skill-checks.mjs` | frontmatter that is not YAML · a `name:` disagreeing with its directory · a wired block naming a script that is not there · a block filing under a **sibling's** name · a Bash command its own `allowed-tools` forbids · a ledger-only skill regressed to bare `Bash` · **a block that documents no FINDING** · a block still instructing a retired constructor · an `ABSTAINED` with no named reason |
181
- | `pipeline-corpus.harness.mjs` | the **SET** of skills | a gate row pointing at a directory that does not exist · a duplicate key in `EXPECTED_GATES` · a wired skill absent from the gate table · an exclusion that has rotted (skill gone, or no longer wired) |
182
-
183
- These two are the only ones aimed at the **skills** rather than the scripts they call, which
184
- is what `vigiles audit`'s `Tested` metric actually counts — "34 surfaces with no vigiles test/eval"
185
- was never about `structure.mjs`. It also fails differently: a script with a bad path crashes, a
186
- **skill** with a bad path is read by a model that quietly does something adjacent and reports success.
187
- Its non-vacuity proof is `lib/skill-checks.test.mjs` — a copy of the shipped skills with defects
188
- planted on the copy, checked against both `checkSkill` (pipeline skills) and `checkProseSkill`
189
- (skills without the pipeline). Run it with `npx vitest run lib/skill-checks.test.mjs`; CI runs it
190
- too.
191
-
192
- > **2026-09-26 — hand-written per-checker mutation batteries are REMOVED (#52).** Do not write a
193
- > `*.mutations.mjs`; see a new test fail before the fix and assert whole values instead (the
194
- > repository's CLAUDE.md, § Testing).
195
-
196
- Five rules, each learned by getting it wrong here:
197
-
198
- 1. **Assertions run at module top level.** `vigiles test` imports the file and treats "did not
199
- throw" as a pass. An exported `tests` object runs nothing and prints ✓ — verified on a file whose
200
- only assertion was `assert.equal(1, 2)`.
201
- 2. **Fixtures live in a temp dir, and the ledger is redirected** with `PIPELINE_LEDGER` _before_
202
- importing `ledger.mjs`. Save-and-restore is not isolation; it loses the race on the first crash.
203
- 3. **Run them by explicit path.** `npx vigiles test` with no arguments finds **nothing** here: its
204
- glob does not descend into dot-directories. Every harness is therefore named explicitly in
205
- the `hooks` job of `.github/workflows/ci.yml` — one not named there does not run, and its
206
- silence looks exactly like having nothing to say. Add the line in the same commit as the file.
207
- 4. **Prove non-vacuity by SEEING IT FAIL, never by reading.** An assertion you cannot see fail is
208
- an assertion you have not tested. `skill-checks.mjs` (then `skills.harness.mjs`) shipped its first draft with a record-block
209
- slicer matching `^#` against an already-sliced string; `^` hit offset 0, every block came back as
210
- the single character `#`, one assertion then fired on all 21 skills while its neighbour checked
211
- nothing at all. Both were invisible on inspection and obvious on the first planted defect.
212
- 5. **A red harness is a finding, not a broken test — and the finding is allowed to close.**
213
- **RED now (2026-08-07):** `skill-checks.mjs` (then `skills.harness.mjs`), on four skills (`tighten-paper`,
214
- `analyze-sibling-paper`, `study-accepted-papers`, `find-venue`) whose frontmatter is not valid
215
- YAML — an unquoted `description:` containing `": "`. Any consumer with a real YAML parser reads
216
- **no** `allowed-tools` on those four, so they inherit every tool: the exact state the 2026-08-03
217
- audit existed to end. Fix: quote the four values.
218
- **CLOSED:** `gates.harness.mjs` was red on `structure.mjs` being silent in `--flags-only` about a
219
- body section with no `carries:` note. `structure.mjs` now pushes that finding and the harness is
220
- green — verified by running it, not by reading it. This paragraph asserted the opposite for a
221
- while after the fix landed, which is the same lie the hand-maintained status table used to tell:
222
- **when this README and a harness disagree, run the harness and believe it.**
223
- The mechanics either way: a failing assertion is deferred to the end of its file so the rest of the
224
- suite still runs, and every step in the CI job carries `if: always()` so a known-red step cannot
225
- mask the harnesses after it.
1
+ # paper-pipeline/scripts — the checkers the paper skills run
2
+
3
+ Each script here answers one mechanical question about a paper directory. The skills name them
4
+ in their instructions; a consumer reaches them through the path in `paperlint.json` → `scripts`
5
+ (default `.claude/skills/paper-pipeline/scripts`, a symlink `paperlint init` makes into the
6
+ installed package — see `consumer.mjs`).
7
+
8
+ | file | what it answers |
9
+ | ----------------------- | ----------------------------------------------------------------------------------------------------- |
10
+ | `artifact-coverage.mjs` | does the released bundle actually contain what the paper points at? |
11
+ | `check-provenance.mjs` | does the paper have a build-time numbers gate, and what does it still not cover? |
12
+ | `extract-ref-facts.mjs` | parse the bibliography, ask the registries, write facts to JSON for the `refs/*` ESLint rules |
13
+ | `generated-code.mjs` | lint the analysis scripts before they produce a number (unseeded randomness, absolute paths, …) |
14
+ | `population-map.mjs` | does the population registry keep its link to the body of the paper? |
15
+ | `round-diff.mjs` | did a review round change only what it declared? |
16
+ | `consumer.mjs` | where the consumer's repository is, and whether a script was executed or imported through the symlink |
17
+
18
+ The checkers with a `--flags-only` mode print nothing on a clean paper and exit 0 — that mode is
19
+ for hooks and pre-commit, where any output means a finding.
20
+
21
+ ## Tests
22
+
23
+ Every script has its tests beside it: `<name>.test.*` (vitest) and, where the check is exercised as
24
+ a process on planted fixtures, `<name>.harness.*` (run by `vigiles test`). Both run under
25
+ `npm test`. Each harness plants the exact defect its checker claims to catch, asserts the checker
26
+ fires, and asserts it stays quiet on the clean case — a checker that fires on correct text is muted
27
+ within a day, which is worse than one that misses.
28
+
29
+ Assertions in a harness run at module top level: `vigiles test` imports the file and treats "did
30
+ not throw" as a pass, so an exported `tests` object would run nothing and still print ✓.
@@ -199,8 +199,7 @@ function fixture(
199
199
  }
200
200
 
201
201
  // ── 5. KNOWN GAP, asserted so it stays visible ─────────────────────────────────────────
202
- // 🔴 A paper with NO bundle at all exits 0 in silence, and `run-mechanical.mjs` records that silence
203
- // as PASS for harden-paper. The checker written because absence is invisible is itself blind to the
202
+ // 🔴 A paper with NO bundle at all exits 0 in silence, which reads as a pass for harden-paper. The checker written because absence is invisible is itself blind to the
204
203
  // largest absence there is. Asserting the CURRENT behaviour: when it learns to say "this paper
205
204
  // releases nothing", the assertion flips and must be updated — that is the point of writing a gap
206
205
  // down rather than leaving it as a thing somebody once noticed.
@@ -412,12 +411,9 @@ const CLEAN_FILES = ["setup/config.json", "runs/gated.tsv"];
412
411
  );
413
412
  }
414
413
 
415
- // ── 13. the header states the finding COUNT, because run-mechanical.mjs parses it ───────────────
416
- // `run-mechanical` reads this check in `flags` mode, where it prefers the check's own stated count
417
- // and falls back to COUNTING OUTPUT LINES. The ledger is a dozen-odd lines that are not findings, so
418
- // without a stated count a clean run would be recorded as a dozen findings. That regression is
419
- // invisible in the check's own output and only shows up in the ledger, which is why it is asserted
420
- // here, from outside, in both directions.
414
+ // ── 13. the header states the finding COUNT ─────────────────────────────────────────────────────
415
+ // The printed ledger is a dozen-odd lines that are not findings, so a caller counting output lines
416
+ // would read a clean run as a dozen findings. Asserted from outside, in both directions.
421
417
  {
422
418
  const clean = run(
423
419
  fixture("count-clean", { index: CLEAN_INDEX, files: CLEAN_FILES }),
@@ -65,7 +65,7 @@ import {
65
65
  headings as mdHeadings,
66
66
  splitSections,
67
67
  requireMarkdown,
68
- } from "../../../lib/markdown.mjs";
68
+ } from "#lib/markdown";
69
69
 
70
70
  // Markup is parsed with a parser (`CLAUDE.md`, 2026-08-11). We fail rather than degrade: without
71
71
  // the parser NOT A SINGLE section would be found in the paper, and the check "every section with a
@@ -8,14 +8,14 @@ import assert from "node:assert/strict";
8
8
  import { dirname, join } from "node:path";
9
9
  import { fileURLToPath } from "node:url";
10
10
  import { test } from "vitest";
11
- import { runNode, useTempDir, writeTree } from "../../../test/support.mjs";
11
+ import { runNode, useTempDir, writeTree } from "../../../test/support.ts";
12
12
 
13
13
  const SCRIPT = join(
14
14
  dirname(fileURLToPath(import.meta.url)),
15
15
  "artifact-coverage.mjs",
16
16
  );
17
17
  const root = useTempDir("artifact-coverage-");
18
- const run = (...args) => runNode(SCRIPT, args);
18
+ const run = (...args: string[]) => runNode(SCRIPT, args);
19
19
 
20
20
  test("no argument prints the usage; a paper with no markdown source is a named skip", () => {
21
21
  assert.deepEqual(run(), {
@@ -1,12 +1,10 @@
1
1
  /**
2
- * consumer.harness.mjs — every rung of `ledgerPath()`, both directions, plus the two path facts
3
- * the move into a package depends on.
2
+ * consumer.harness.mjs — the path facts the move into a package depends on, both directions.
4
3
  *
5
4
  * `npx vigiles test skills/paper-pipeline/scripts/consumer.harness.mjs`
6
5
  *
7
6
  * 🔴 WHY THIS FILE IS NOT OPTIONAL. The resolutions in `consumer.mjs` all fail in the SILENT
8
- * direction when they are wrong: a bad ledger path still accepts appends, a bad main guard still
9
- * exits 0, a bad root still hashes a missing directory to a stable value. None of them produce an
7
+ * direction when they are wrong: a bad main guard still exits 0, a bad root still hashes a missing directory to a stable value. None of them produce an
10
8
  * error, and every one of them produces a run that reads as success. So each is asserted here in
11
9
  * BOTH directions — fires when it should, silent when it should — because an assertion that only
12
10
  * ever sees the good case cannot tell a working resolver from a constant.
@@ -38,7 +36,6 @@ import {
38
36
  insideNodeModules,
39
37
  installedSkills,
40
38
  isMain,
41
- ledgerPath,
42
39
  pipelineScripts,
43
40
  scriptsRoot,
44
41
  PACKAGE_NAME,
@@ -101,94 +98,6 @@ function installedDir(root) {
101
98
  return d;
102
99
  }
103
100
 
104
- // ── I. RUNG 1 — the environment wins, and it wins over a declaration ─────────────────────────
105
- {
106
- const root = fakeConsumer({ ledger: "declared/here.jsonl" });
107
- const env = { PIPELINE_LEDGER: join(root, "from-env.jsonl") };
108
- assert.equal(
109
- ledgerPath(installedDir(root), { env, cwd: root }),
110
- resolve(join(root, "from-env.jsonl")),
111
- "PIPELINE_LEDGER did not win. It must outrank everything: harnesses set it BEFORE importing " +
112
- "the ledger precisely so fixture rows cannot reach real history, and a declaration on disk " +
113
- "that could override it would put them there.",
114
- );
115
- // The other direction: with the variable gone, the same call must move to the next rung.
116
- assert.equal(
117
- ledgerPath(installedDir(root), { env: {}, cwd: root }),
118
- resolve(root, "declared/here.jsonl"),
119
- "without PIPELINE_LEDGER the declaration must be used — otherwise rung 1 is not a rung, it " +
120
- "is the only path, and this assertion pair proves nothing about precedence.",
121
- );
122
- }
123
-
124
- // ── II. RUNG 2 — the consumer's declaration, resolved against the consumer root ───────────────
125
- {
126
- const root = fakeConsumer({ ledger: "docs/pipeline-runs.jsonl" });
127
- assert.equal(
128
- ledgerPath(installedDir(root), { env: {}, cwd: root }),
129
- join(root, "docs/pipeline-runs.jsonl"),
130
- "a declared relative ledger must resolve against the consumer root, not against the cwd of " +
131
- "whatever process happened to start",
132
- );
133
- // 🔴 `null` is a keystroke, not an absence. `?? DEFAULT` would read it as "nothing declared"
134
- // and silently use another file — the same distinction `papersRoot()` makes.
135
- assert.throws(
136
- () =>
137
- ledgerPath(installedDir(root), {
138
- env: {},
139
- cwd: fakeConsumer({ ledger: null }),
140
- }),
141
- /must be a non-empty string/,
142
- 'a `"ledger": null` was accepted. Anything written down must be usable; only ABSENCE may ' +
143
- "fall through to the next rung.",
144
- );
145
- assert.throws(
146
- () =>
147
- ledgerPath(installedDir(root), {
148
- env: {},
149
- cwd: fakeConsumer({ ledger: "" }),
150
- }),
151
- /must be a non-empty string/,
152
- "an empty declared ledger was accepted; it resolves to the consumer root itself",
153
- );
154
- }
155
-
156
- // ── III. RUNG 3 — beside the file, but NEVER inside node_modules ──────────────────────────────
157
- {
158
- const root = fakeConsumer(undefined);
159
- // Developing the package in its own checkout: the default is legitimate.
160
- const own = mkdtempSync(join(TMP, "checkout-"));
161
- assert.equal(
162
- ledgerPath(own, { env: {}, cwd: root }),
163
- join(own, "runs.jsonl"),
164
- "outside node_modules and with nothing declared, the ledger belongs beside the module",
165
- );
166
- // Installed as a dependency with nothing declared: refusing is the whole point.
167
- let err;
168
- try {
169
- ledgerPath(installedDir(root), { env: {}, cwd: root });
170
- } catch (e) {
171
- err = e;
172
- }
173
- assert.ok(
174
- err,
175
- "🔴 ledgerPath RETURNED A PATH INSIDE node_modules. Appending there SUCCEEDS, so the rows " +
176
- "look recorded until the next `npm ci` deletes them — silent data loss with no error at " +
177
- "any point. This must throw.",
178
- );
179
- assert.match(
180
- err.message,
181
- /"ledger"/,
182
- "the refusal must name the key to declare. An error that states the problem without the cure " +
183
- "gets worked around by whoever hits it.",
184
- );
185
- assert.match(
186
- err.message,
187
- /paperlint\.json/,
188
- "the refusal must name the file the key goes in",
189
- );
190
- }
191
-
192
101
  // ── IV. `insideNodeModules` is SEGMENT-WISE, not a substring ──────────────────────────────────
193
102
  assert.equal(
194
103
  insideNodeModules(join("a", "node_modules", "b")),
@@ -199,7 +108,7 @@ assert.equal(
199
108
  insideNodeModules(join("a", "my-node_modules-inspector", "b")),
200
109
  false,
201
110
  "a directory whose NAME merely contains `node_modules` was treated as an install — that " +
202
- "consumer would be refused its own ledger for a naming coincidence",
111
+ "consumer would be refused for a naming coincidence",
203
112
  );
204
113
  assert.equal(
205
114
  insideNodeModules(join("a", "b")),
@@ -433,7 +342,7 @@ assert.equal(
433
342
  );
434
343
  }
435
344
 
436
- // ── X. THE SCRIPT NAMES ARE DERIVED, NOT RESTATED ─────────────────────────────────────────────
345
+ // ── X. THE PREFIX IS DERIVED, NOT RESTATED ─────────────────────────────────────────────
437
346
  // The names belong to this package; a consumer that spelled them out would keep its copy in step
438
347
  // by hand. The trailing slash on `prefix` is load-bearing: without it the prefix also matches a
439
348
  // SIBLING directory whose name merely starts with the root's.
@@ -443,19 +352,17 @@ assert.equal(
443
352
  // `assert` aborts at the first failure, and a prefix that lost its slash died on "must end in a
444
353
  // separator" — leaving the assertion that states WHY the slash matters unreached and unproven.
445
354
  assert.equal(
446
- "a/b-other/ledger.mjs".startsWith(s.prefix),
355
+ "a/b-other/structure.mjs".startsWith(s.prefix),
447
356
  false,
448
357
  "the prefix matched a sibling directory sharing the root's name — drop the trailing slash " +
449
358
  "and every such path is mistaken for a pipeline script",
450
359
  );
451
360
  assert.equal(
452
- "a/b/ledger.mjs".startsWith(s.prefix),
361
+ "a/b/structure.mjs".startsWith(s.prefix),
453
362
  true,
454
363
  "the prefix failed on a real member",
455
364
  );
456
365
  assert.equal(s.prefix, "a/b/", "prefix must end in a separator");
457
- assert.equal(s.announce, "a/b/announce.mjs");
458
- assert.equal(s.ledger, "a/b/ledger.mjs");
459
366
 
460
367
  // EXACTLY ONE slash, whatever the consumer typed. `scriptsRoot()` returns the declaration
461
368
  // verbatim, so a trailing slash in paperlint.json arrives here intact; `${root}/` would then give
@@ -467,11 +374,6 @@ assert.equal(
467
374
  'a declared trailing slash doubled the separator. `"scripts": "tools/pipeline/"` is a normal ' +
468
375
  "thing to write, and the doubled prefix matches no instruction at all.",
469
376
  );
470
- assert.equal(
471
- typedSlash.ledger,
472
- "a/b/ledger.mjs",
473
- "the script paths doubled the separator too",
474
- );
475
377
  }
476
378
 
477
379
  // ── XI. WHICH SKILLS ARE INSTALLED — a link counts, a dangling link is REFUSED (paperlint#62) ────────