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
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * skill-checks.mjs's two refusing halves, which the per-skill harnesses (all green on the real
3
- * corpus) never reach: `checkSkill` on an unknown name and on a pipeline skill with a planted
4
- * defect, and `checkProseSkill` — the check for skills WITHOUT the pipeline, which the package
3
+ * corpus) never reach: `checkSkill` on an unknown name and on a skill with a planted defect, and `checkProseSkill` — the check for skills WITHOUT the pipeline, which the package
5
4
  * exports for a consumer's own skills and no harness here calls.
6
5
  *
7
6
  * The consumer is a temp root holding a COPY of the shipped skills, so a planted defect edits the
@@ -12,7 +11,7 @@ import { cpSync, readFileSync, writeFileSync } from "node:fs";
12
11
  import { dirname, join } from "node:path";
13
12
  import { fileURLToPath } from "node:url";
14
13
  import { test } from "vitest";
15
- import { useTempDir, writeTree } from "../test/support.mjs";
14
+ import { useTempDir, writeTree } from "../test/support.ts";
16
15
 
17
16
  const SHIPPED = join(dirname(fileURLToPath(import.meta.url)), "..", "skills");
18
17
  const consumer = useTempDir("skill-checks-");
@@ -33,55 +32,13 @@ async function thrown(fn) {
33
32
  return null;
34
33
  }
35
34
 
36
- test("checkSkill passes a real shipped skill and refuses a name that is not a wired skill", async () => {
35
+ test("checkSkill passes a real shipped skill and refuses a name with no SKILL.md", async () => {
37
36
  assert.deepEqual(
38
37
  [
39
38
  await checkSkill("cold-read-diff"),
40
39
  await thrown(() => checkSkill("no-such-skill")),
41
40
  ],
42
- [
43
- 1,
44
- `"no-such-skill" is not a wired pipeline skill. Known: ${(await import("./skill-corpus.mjs")).SKILLS.join(", ")}`,
45
- ],
46
- );
47
- });
48
-
49
- test("checkSkill reports EVERY finding of a broken pipeline skill at once", async () => {
50
- const md = join(SKILLS, "cold-read-diff", "SKILL.md");
51
- const clean = readFileSync(md, "utf8");
52
- writeFileSync(
53
- md,
54
- clean
55
- .replace("name: cold-read-diff", "name: renamed")
56
- .replace("## Run me", "## Not run"),
57
- );
58
- try {
59
- const first = await thrown(() => checkSkill("cold-read-diff"));
60
- assert.equal(
61
- first,
62
- '2 assertion(s) failed for pipeline skill "cold-read-diff":',
63
- );
64
- } finally {
65
- writeFileSync(md, clean);
66
- }
67
- });
68
-
69
- test("a pipeline skill whose record block is gone: ONE finding naming the heading — the ledger checks that read the block skip it", async () => {
70
- const md = join(SKILLS, "cold-read-diff", "SKILL.md");
71
- const clean = readFileSync(md, "utf8");
72
- writeFileSync(md, clean.replaceAll("## Record the verdict", "## Afterwards"));
73
- let message = "";
74
- try {
75
- await checkSkill("cold-read-diff");
76
- } catch (e) {
77
- message = e.message;
78
- } finally {
79
- writeFileSync(md, clean);
80
- }
81
- assert.equal(
82
- message,
83
- '1 assertion(s) failed for pipeline skill "cold-read-diff":\n\n' +
84
- ' [1] cold-read-diff: no "Record the verdict" heading. A run with an announce row and no verdict row is indistinguishable from a skill that started and died.',
41
+ [1, `"no-such-skill" has no SKILL.md under ${SKILLS}`],
85
42
  );
86
43
  });
87
44
 
@@ -203,95 +160,86 @@ async function brokenBy(name, edit) {
203
160
  await checkSkill(name);
204
161
  return null;
205
162
  } catch (e) {
206
- // The header, then the first 90 characters of each numbered finding.
163
+ // The header, then each numbered finding.
207
164
  const lines = e.message.split("\n");
208
165
  return [
209
166
  lines[0],
210
- ...lines
211
- .filter((l) => /^ \[\d+\] /.test(l))
212
- .map((l) => l.trim().slice(0, 90)),
167
+ ...lines.filter((l) => /^ \[\d+\] /.test(l)).map((l) => l.trim()),
213
168
  ];
214
169
  } finally {
215
170
  writeFileSync(md, clean);
216
171
  }
217
172
  }
218
- const recordSection = /## Record the verdict[\s\S]*?(?=\n## |\n# |$)/;
219
-
220
- test("checkSkill on the defects its own corpus never has: no description, no record block, a record block naming no kind", async () => {
221
- assert.deepEqual(
222
- [
223
- await brokenBy("cold-read-diff", (s) =>
224
- s.replace(/^description: .*\n/m, ""),
225
- ),
226
- await brokenBy("argument-arc", (s) =>
227
- s.replace(
228
- recordSection,
229
- "## Record the verdict\n\nNothing named here.\n",
230
- ),
231
- ),
232
- await brokenBy("draft-paper", (s) =>
233
- s.replace(
234
- recordSection,
235
- "## Record the verdict\n\n**ABSTAINED** — and no reason is named.\n",
236
- ),
237
- ),
238
- ],
239
- [
240
- [
241
- '1 assertion(s) failed for pipeline skill "cold-read-diff":',
242
- "[1] cold-read-diff: description is missing or trivially short (0 chars). The description i",
243
- ],
244
- [
245
- '3 assertion(s) failed for pipeline skill "argument-arc":',
246
- '[1] argument-arc: the "Record the verdict" block instructs no `node .claude/skills/paper-p',
247
- "[2] argument-arc: its record block names [nothing] and not FINDING, and does not admit tha",
248
- "[3] argument-arc: its record block names no abstention reason from [started, no-witness, i",
249
- ],
250
- [
251
- '5 assertion(s) failed for pipeline skill "draft-paper":',
252
- '[1] draft-paper: the "Record the verdict" block instructs no `node .claude/skills/paper-pi',
253
- "[2] draft-paper: its record block names [ABSTAINED] and not FINDING, and does not admit th",
254
- "[3] draft-paper: its record block names no abstention reason from [started, no-witness, in",
255
- "[4] draft-paper: the honest admission is gone from its record block. It is the one skill i",
256
- "[5] ABSTAINED needs a reason from started|no-witness|input-missing|blocked|crashed, got nu",
257
- ],
258
- ],
173
+ test("checkSkill reports EVERY finding at once: a renamed skill with no description", async () => {
174
+ const lines = await brokenBy("cold-read-diff", (s) =>
175
+ s
176
+ .replace("name: cold-read-diff", "name: renamed")
177
+ .replace(/^description: .*\n/m, ""),
259
178
  );
179
+ assert.deepEqual(lines?.slice(0, 1), [
180
+ '2 assertion(s) failed for skill "cold-read-diff":',
181
+ ]);
182
+ assert.match(lines?.[1] ?? "", /frontmatter name is "renamed"/);
183
+ assert.match(lines?.[2] ?? "", /description is missing/);
260
184
  });
261
185
 
262
- test("a skill may show `ledger.mjs status` — only a `record` call must file under the skill", async () => {
186
+ test("checkSkill refuses an instructed script that does not exist", async () => {
187
+ const lines = await brokenBy("verify-citations", (s) =>
188
+ s.replaceAll("scripts/extract-ref-facts.mjs", "scripts/missing.mjs"),
189
+ );
263
190
  assert.equal(
264
- await brokenBy("cold-read-diff", (s) =>
265
- s.replace(
266
- "node .claude/skills/paper-pipeline/scripts/announce.mjs cold-read-diff <paper-dir>",
267
- "node .claude/skills/paper-pipeline/scripts/announce.mjs cold-read-diff <paper-dir>\n" +
268
- "node .claude/skills/paper-pipeline/scripts/ledger.mjs status <paper-dir>",
269
- ),
270
- ),
271
- null,
191
+ lines?.[0],
192
+ '2 assertion(s) failed for skill "verify-citations":',
193
+ );
194
+ assert.match(
195
+ lines?.[1] ?? "",
196
+ /instructs `node .*missing\.mjs.*` but .*missing\.mjs does not exist/,
272
197
  );
273
198
  });
274
199
 
275
- test("a skill whose only ledger call is `status` records nothing, and is told so", async () => {
276
- assert.deepEqual(
277
- await brokenBy("cold-read-diff", (s) =>
278
- s.replaceAll(
279
- /ledger\.mjs record cold-read-diff <paper-dir> \w+[^\n]*/g,
280
- "ledger.mjs status <paper-dir>",
281
- ),
200
+ test("checkSkill refuses a pipeline script the skill's allowed-tools do not permit", async () => {
201
+ const lines = await brokenBy("verify-citations", (s) =>
202
+ s.replace(
203
+ /^allowed-tools: \[(.*)\]$/m,
204
+ (_, list) =>
205
+ `allowed-tools: [${list
206
+ .split(", ")
207
+ .filter((t) => t !== "Bash")
208
+ .join(", ")}]`,
282
209
  ),
283
- [
284
- '1 assertion(s) failed for pipeline skill "cold-read-diff":',
285
- '[1] cold-read-diff: the "Record the verdict" block instructs no `node .claude/skills/paper',
286
- ],
210
+ );
211
+ assert.equal(
212
+ lines?.[0],
213
+ '2 assertion(s) failed for skill "verify-citations":',
214
+ );
215
+ assert.match(
216
+ lines?.[1] ?? "",
217
+ /extract-ref-facts\.mjs.* permit no such Bash command/,
287
218
  );
288
219
  });
289
220
 
290
- test("the record CALL is the contract, not the heading: a renamed record section still passes", async () => {
221
+ test("a Bash entry narrowed to the instructed script permits it, and one narrowed elsewhere does not", async () => {
222
+ const narrowTo = (script) => (s) =>
223
+ s.replace(
224
+ /^allowed-tools: \[(.*)\]$/m,
225
+ (_, list) =>
226
+ `allowed-tools: [${list
227
+ .split(", ")
228
+ .map((t) =>
229
+ t === "Bash"
230
+ ? `"Bash(node .claude/skills/paper-pipeline/scripts/${script}:*)"`
231
+ : t,
232
+ )
233
+ .join(", ")}]`,
234
+ );
291
235
  assert.equal(
292
- await brokenBy("camera-ready", (s) =>
293
- s.replace("## Record the verdict", "## Something else"),
294
- ),
236
+ await brokenBy("verify-citations", narrowTo("extract-ref-facts.mjs")),
295
237
  null,
296
238
  );
239
+ const lines = await brokenBy("verify-citations", narrowTo("round-diff.mjs"));
240
+ assert.equal(
241
+ lines?.[0],
242
+ '2 assertion(s) failed for skill "verify-citations":',
243
+ );
244
+ assert.match(lines?.[1] ?? "", /permit no such Bash command/);
297
245
  });
@@ -1,50 +1,18 @@
1
1
  /**
2
- * skill-corpus.mjs — WHAT the pipeline's skills ARE. Derivation and helpers, no assertions.
3
- *
4
- * 🔴 THIS FILE MAKES NO CLAIMS AND RUNS NO TESTS. Importing it must be free of verdicts —
5
- * that is the whole reason it was split out of `skills.harness.mjs` on 2026-08-11. That file
6
- * held three things under one name: this derivation, the per-skill assertions, and the
7
- * corpus-wide ones. It answered to no surface (there is no skill called `skills`), so the
8
- * 21 colocated per-skill harnesses could only reach it by setting an env var and importing
9
- * a script for its SIDE EFFECTS — 21 wrappers around one bag.
10
- *
11
- * Now: this module derives, `skill-checks.mjs` asserts about ONE skill, and
12
- * `pipeline-corpus.harness.mjs` asserts about the SET. Each colocated file calls a
13
- * function with its own name in it, and there is no env-var side channel.
14
- *
15
- * The ledger is redirected to a scratch path at import time, BEFORE `ledger.mjs` loads, so
16
- * that an assertion which calls `record()` cannot leave fixture rows in real history. That
17
- * import order is load-bearing and is the reason this is a module rather than a plain
18
- * export list. Save-and-restore is not isolation — it lost that race on its first day.
2
+ * skill-corpus.mjs — what a skill IS, read from its directory: the frontmatter, the tool contract,
3
+ * the commands its text instructs. Derivation and helpers, no assertions — `skill-checks.mjs`
4
+ * asserts about one skill by calling these.
19
5
  */
20
6
  import assert from "node:assert/strict";
21
- // ⚠️ 2026-08-28: `rmSync` and `writeFileSync` were removed from here — nothing called them.
22
- // `rmSync` is NOT a harmless finding here, though: `tmp` below is a `mkdtempSync` that is never
23
- // removed, so every import of this library leaves a directory behind in `/tmp` (two dozen per
24
- // full sweep of the harnesses). Cleanup is DELIBERATELY not added in this edit: it would change
25
- // the lifecycle of the directory that holds `PIPELINE_LEDGER` for every child run, and today's
26
- // edit is about turning the lint on, not about harness behavior. Written down as an open item
27
- // in the report, not swept under an import-that-does-something-silently.
28
- import { existsSync, readFileSync, mkdtempSync, realpathSync } from "node:fs";
29
- import { tmpdir } from "node:os";
7
+ import { existsSync, readFileSync } from "node:fs";
30
8
  import { join, dirname } from "node:path";
31
9
  import { fileURLToPath } from "node:url";
32
10
  import * as yaml from "js-yaml";
33
- import {
34
- consumerRoot,
35
- installedSkills,
36
- } from "../skills/paper-pipeline/scripts/consumer.mjs";
37
- import {
38
- headings as mdHeadings,
39
- requireMarkdown,
40
- frontmatterBlock,
41
- } from "./markdown.mjs";
11
+ import { consumerRoot } from "../skills/paper-pipeline/scripts/consumer.mjs";
12
+ import { requireMarkdown, frontmatterBlock } from "#lib/markdown";
42
13
 
43
- // Markup is parsed with a real parser (`CLAUDE.md`, 2026-08-11). Here we THROW rather than
44
- // degrade: this module answers the question "what does the skill corpus contain", and an empty
45
- // heading list with no parser would read as "not one skill has a `Record the verdict` block" —
46
- // i.e. as a finding about the corpus. A confident zero has already been mistaken for a fact in
47
- // this repo four times.
14
+ // Markup is parsed with a real parser. Here we THROW rather than degrade: with no parser every
15
+ // frontmatter would read as absent, which is a finding about the corpus, not about the install.
48
16
  requireMarkdown();
49
17
 
50
18
  export const HERE = dirname(fileURLToPath(import.meta.url));
@@ -54,122 +22,10 @@ export const HERE = dirname(fileURLToPath(import.meta.url));
54
22
  // below it — the skills directory above all — would be a directory that does not exist. That
55
23
  // failure is silent in the worst way: `readdirSync` throws while this module is still LOADING, and
56
24
  // a harness whose module fails to load is reported by `vigiles test` as SKIPPED with exit code 0.
57
- // `consumerRoot()` is the carrier the package already uses for the ledger and the papers root.
25
+ // `consumerRoot()` is the carrier the package already uses for the papers root.
58
26
  export const ROOT = consumerRoot();
59
27
  export const SKILLS_DIR = join(ROOT, ".claude", "skills");
60
28
 
61
- // Redirected BEFORE ledger.mjs is imported, so a crash cannot leave fixture rows in real history.
62
- // Save-and-restore is not isolation — it lost that race on its first day (see ledger.mjs's comment).
63
- export const tmp = realpathSync(mkdtempSync(join(tmpdir(), "skills-harness-")));
64
- process.env.PIPELINE_LEDGER = join(tmp, "runs.jsonl");
65
- const { KINDS, RETIRED, ABSTENTIONS, record, parseGate } =
66
- await import("../skills/paper-pipeline/scripts/ledger.mjs");
67
- export { KINDS, RETIRED, ABSTENTIONS, record, parseGate };
68
- const { EXPECTED_GATES } =
69
- await import("../skills/paper-pipeline/scripts/status.mjs");
70
- export { EXPECTED_GATES };
71
- /** The skills EXPECTED_GATES names, with the `skill/check` rows collapsed onto their skill. */
72
- export const GATE_SKILLS = new Set(
73
- EXPECTED_GATES.map((g) => parseGate(g).skill),
74
- );
75
-
76
- // The paper-pipeline skills, DERIVED FROM DISK — a skill is one iff its SKILL.md instructs
77
- // `node .claude/skills/paper-pipeline/scripts/announce.mjs <its own name>`. Minus EXCLUDED below.
78
- //
79
- // 🔴 THIS WAS A PINNED ARRAY UNTIL 2026-08-07, AND THE PIN MADE ASSERTION 12b VACUOUS IN THE ONE
80
- // CASE IT EXISTS FOR. That assertion reads "<skill> is a wired pipeline skill but is absent from
81
- // status.mjs EXPECTED_GATES" — and it iterated the pinned array. So it could only ever catch a skill
82
- // present in the array and missing from EXPECTED_GATES. A skill wired on disk and absent from BOTH
83
- // lists — the actual "I added a skill and forgot" failure — was structurally invisible.
84
- //
85
- // Measured, not theorised: `paper-status` is in exactly that state right now. It carries an announce
86
- // command and is in neither list. That is DELIBERATE (see EXCLUDED), but nothing checked that the
87
- // deliberateness was still true, and the old comment asserting it was the only thing holding it.
88
- //
89
- // A defect that removes a skill from EXPECTED_GATES while leaving it in the pinned array passed
90
- // silently the whole time. A check proving a narrower property than its own error message claims
91
- // is exactly the vacuity this file was built to hunt, sitting in this file.
92
- //
93
- // The old comment's objection to globbing was real and is answered rather than ignored: `.claude/skills/`
94
- // does hold daily skills (search-krisha, food-log-entry, …) with no pipeline contract, so the filter
95
- // cannot be "every directory". But it need not be a guess either — carrying an announce command that
96
- // names YOURSELF is the pipeline contract, checked below by assertion 5c, and it is a fact on disk.
97
- export const PIPELINE_MARKER =
98
- /^\s*node\s+\.claude\/skills\/paper-pipeline\/scripts\/announce\.mjs\s+([a-z0-9-]+)/m;
99
-
100
- /**
101
- * Skills wired into the pipeline but deliberately NOT graded as gates, each with the reason.
102
- * A reason is required because these are the entries that rot: an exclusion with no argument is
103
- * indistinguishable from an omission, and the previous version of this file held both in prose.
104
- *
105
- * 🔴 UNTIL 2026-08-11 THIS SUBTRACTED FROM THE CHECKED SET, WHICH IS NOT WHAT ITS OWN REASON SAYS.
106
- * The recorded reason for `paper-status` is "it has no gate row" — a statement about ONE assertion
107
- * (12b: every checked skill must appear in EXPECTED_GATES). The implementation filtered it out of
108
- * `SKILLS` entirely, so it received NONE of the structural checks either: not the strict-YAML
109
- * frontmatter parse, not the tool contract, not "your wiring points at scripts that exist", not
110
- * "you announce under your OWN identity". A skill was silently unexamined because it lacked a
111
- * grade — and this file's own governing comment, four lines below, says a defect that shrinks the
112
- * checked set is worse than the defect itself.
113
- *
114
- * Found by the coverage sweep of 2026-08-11: `paper-status` showed as untested, and the reason was
115
- * not a missing test file but this line. Now the exclusion means what it says — skipped at 12b,
116
- * checked everywhere else.
117
- */
118
- export const EXCLUDED = new Map([
119
- [
120
- "paper-status",
121
- "reports on the pipeline; produces no verdict ABOUT the paper, so it has no gate row",
122
- ],
123
- ]);
124
-
125
- // 🔴 MEMBERSHIP IS THE UNION OF TWO SOURCES, AND THAT IS THE WHOLE POINT. A skill is checked if the
126
- // gate table names it OR its SKILL.md carries pipeline wiring. Either alone is corruptible by the very
127
- // defects this file hunts, and a defect that shrinks the checked set is worse than the defect itself:
128
- // the harness goes green by no longer looking.
129
- //
130
- // Both narrower versions were written and both failed within minutes, on the same two planted
131
- // defects:
132
- //
133
- // filter `announce.mjs <own name>` -> a typo in the announced NAME dropped harden-paper out
134
- // of scope; harness printed "20 pipeline skills", said nothing
135
- // filter `announce.mjs` path -> a typo in the announce PATH dropped camera-ready the
136
- // same way
137
- //
138
- // Each time, the same planted defect that used to turn the harness red went GREEN because it
139
- // erased its own subject. Scope decided by a property the defect corrupts is not scope.
140
- //
141
- // The union cannot be shrunk by a single-point edit: break the wiring and the gate table still holds
142
- // the skill; forget the gate table and the disk still does. Only removing it from BOTH — which is
143
- // what actually leaving the pipeline looks like — takes it out of scope, and EXCLUDED makes that
144
- // deliberate and reasoned rather than silent.
145
- const wiredOnDisk = installedSkills(SKILLS_DIR).filter((d) =>
146
- PIPELINE_MARKER.test(readFileSync(join(SKILLS_DIR, d, "SKILL.md"), "utf-8")),
147
- );
148
-
149
- // EXCLUDED deliberately does NOT filter here — see its comment. An entry means "no gate row",
150
- // and that is applied at assertion 12b alone; everything structural still looks at it.
151
- export const SKILLS = [...new Set([...GATE_SKILLS, ...wiredOnDisk])]
152
- .filter((d) => existsSync(join(SKILLS_DIR, d, "SKILL.md"))) // 12a reports a gate with no directory
153
- .sort();
154
-
155
- // The nine that gained Bash ONLY so they could write the ledger, per
156
- // `.claude/skills/ALLOWED-TOOLS-AUDIT-2026-08-03.md`. PINNED because the property is not derivable:
157
- // from the frontmatter alone a plain `Bash` on `render-paper` (pdflatex, bibtex, apt-get — it needs
158
- // it) is indistinguishable from a plain `Bash` on `argument-arc` (which would be a regression). The
159
- // audit decided that per skill by reading each one's text; this list is that decision, made checkable.
160
- // A skill moving off this list is a deliberate act and should require editing this line.
161
- export const LEDGER_ONLY_BASH = new Set([
162
- "argument-arc",
163
- "tighten-paper",
164
- "grade-paper-writing",
165
- "paper-adversarial-review",
166
- "draft-paper",
167
- "extend-paper",
168
- "research-ideate",
169
- "find-venue",
170
- "plan-paper-timeline",
171
- ]);
172
-
173
29
  // ── parsing ────────────────────────────────────────────────────────────────────────────────────
174
30
 
175
31
  /** Raw text between the opening `---` and the next `---`. Null if there is no frontmatter at all. */
@@ -233,7 +89,7 @@ export function parseFm(fmText, where = "frontmatter") {
233
89
  * round trip (vigiles #107). This split understood only the scalar, so on 2026-08-17
234
90
  * adopting five pipeline skills turned four harnesses red: the checker read the whole
235
91
  * bracketed list as a single entry named `[Bash` and reported that the skill's own
236
- * `Bash(node …announce.mjs:*)` instruction was denied. The skills were fine; the reader
92
+ * narrowed `Bash(node …:*)` instruction was denied. The skills were fine; the reader
237
93
  * was not.
238
94
  *
239
95
  * Entries themselves carry no commas (`Bash(node …:*)` has none), so a plain split is
@@ -287,74 +143,6 @@ export function commands(src) {
287
143
  }));
288
144
  }
289
145
 
290
- /**
291
- * The `Record the verdict` block: from its heading to the next heading of the same or higher level.
292
- *
293
- * 🔴 The first version of this sliced `rest.slice(1).search(/^#{1,3} /m)` and `^` matched at offset 0
294
- * of the slice, so every block came back as the single character "#". Assertions 10 and 11 then read
295
- * an empty block, found no verdicts, and 10 fired on all 21 skills while 11 checked nothing at all —
296
- * a checker firing on clean input and a checker that is vacuous, from one off-by-one. Caught by
297
- * running it, not by reading it. Matching on `\n#` rather than `^#` is the fix.
298
- *
299
- * 2026-08-11 — switched to a real parser (`CLAUDE.md`: "markdown is parsed with a parser"). The
300
- * same off-by-one is now INEXPRESSIBLE: a heading from the parser already carries the offset of
301
- * its line start, so the block boundary no longer has to be computed from a newline's position.
302
- * Along with it went two defects the old expression carried silently: `#{1,3}` counted a hash
303
- * inside a ``` block as a heading (a skill QUOTING someone else's `## Run me` in an example cut
304
- * its own block short at the quote), and `\n#{1,3} ` did not see a heading indented by 1-3
305
- * spaces, which CommonMark still counts as a heading. The block boundary is preserved byte for
306
- * byte: end = start of the next heading.
307
- */
308
- export function recordBlock(src) {
309
- const hs = mdHeadings(src);
310
- const i = hs.findIndex(
311
- (h) => h.depth <= 3 && /record the verdict/i.test(h.text),
312
- );
313
- if (i < 0) return null;
314
- const next = hs.slice(i + 1).find((h) => h.depth <= 3);
315
- return src.slice(hs[i].offset, next ? next.offset : src.length);
316
- }
317
-
318
- // Three extractors, because they answer different questions and a single one would be wrong for one.
319
- //
320
- // `allBold` — every all-caps bolded token, whatever it is. The raw material for the other two.
321
- // Bold-only on purpose: several blocks write a retired word in prose ("There is no
322
- // PASS") or in backticks, and a looser match would read those DENIALS as claims —
323
- // which is the exact reading error that would make the turned-around assertion below
324
- // fire on every correct file.
325
- // `namedKinds` — bolded tokens that are live constructors. Used to ask "can this check report a
326
- // finding at all?".
327
- // `namedRetired` — bolded tokens from the DELETED vocabulary. Used to ask "does this block still
328
- // instruct something `record()` refuses?".
329
- export const allBold = (blk) => [
330
- ...new Set(
331
- [...blk.matchAll(/\*\*`?([A-Z][A-Z-]{2,})`?\*\*/g)].map((m) => m[1]),
332
- ),
333
- ];
334
- export const namedKinds = (blk) =>
335
- allBold(blk).filter((v) => KINDS.includes(v));
336
- export const namedRetired = (blk) => allBold(blk).filter((v) => RETIRED.has(v));
337
- export const definedKinds = (blk) => [
338
- ...new Set(
339
- [...blk.matchAll(/\*\*`?([A-Z][A-Z-]{2,})`?\*\*\s*(?=[—(:]| \()/g)].map(
340
- (m) => m[1],
341
- ),
342
- ),
343
- ];
344
-
345
- // The abstention reasons a block names, as backticked tokens: `no-witness`, `blocked`, …
346
- export const namedReasons = (blk) =>
347
- [
348
- ...new Set([...blk.matchAll(/`([a-z][a-z-]{3,})`/g)].map((m) => m[1])),
349
- ].filter((r) => ABSTENTIONS.has(r));
350
-
351
- // The honest-admission escape hatch, for a skill whose nature is to generate rather than judge.
352
- // `draft-paper` is the designed case: it will abstain nearly always and its text says so, in the
353
- // terms status.mjs uses. The regex matches that ARGUMENT, not a keyword, so an admission cannot be
354
- // satisfied by pasting the word "smell".
355
- export const ADMISSION =
356
- /(almost always record|never returned a (negative|finding)|a generator is not a gate|only honest negative)/i;
357
-
358
146
  export const load = (name) => {
359
147
  const path = join(SKILLS_DIR, name, "SKILL.md");
360
148
  assert.ok(
@@ -370,32 +158,3 @@ export const load = (name) => {
370
158
  const fm = parseFm(fmText, name);
371
159
  return { name, path, src, fmText, fm, tools: toolList(fm["allowed-tools"]) };
372
160
  };
373
-
374
- // One-skill mode, for the COLOCATED per-skill harness files that sit next to each
375
- // SKILL.md. Coverage is decided by placement (vigiles, 2026-08-11: a test that only
376
- // NAMES a surface no longer counts), so each skill needs a file inside its own
377
- // directory. Rather than copy these assertions 21 times — the drift this file was
378
- // built to avoid — each colocated file sets ONLY and imports this one.
379
- //
380
- // 🔴 2026-08-28: THERE USED TO BE TWO MORE LINES BELOW, AND THEY WERE DEAD LOGIC, NOT CLUTTER.
381
- //
382
- // const skills = (ONLY ? [ONLY] : SKILLS).map(load);
383
- // const skillDirs = new Set(readdirSync(SKILLS_DIR).filter((d) => existsSync(…)));
384
- //
385
- // Both were computed and read by NOBODY — not here, not on export. This is a leftover from the
386
- // 2026-08-11 split, when the cross-skill checks moved into `pipeline-corpus.harness.mjs`: the
387
- // bodies of the checks were carried over, and their inputs were forgotten. The paragraph above
388
- // about "cross-skill assertions below" described exactly that block, now gone; after the move
389
- // there is NOT A SINGLE CHECK below it, i.e. the comment promised a mechanism that does not
390
- // exist — exactly what this base's "prose isn't policy" rule exists to catch.
391
- //
392
- // The second, less obvious cost: `.map(load)` is not an inert computation. `load()` carries an
393
- // `assert.ok`, so this line was an UNDECLARED check of the whole corpus, run as a side effect of
394
- // `import` and costing a read of all 22 `SKILL.md` files on every import of the library. This is
395
- // exactly the kind of "via an import's side effect" coupling the file was moving away from (see
396
- // the header of `skill-checks.mjs`). The real, declared carrier of this check is `checkSkill()`,
397
- // which does it one skill at a time and with a legible message.
398
- //
399
- // `PIPELINE_SKILL_ONLY` used to stay here, validated against SKILLS. Nothing read the variable
400
- // any more — the validation was all that was left of the feature — so it was removed with it
401
- // (2026-09-27).
@@ -1,11 +1,10 @@
1
1
  /**
2
2
  * skill-corpus.mjs's pure readers on the inputs the shipped corpus never contains: frontmatter
3
- * that is not a mapping or not YAML, an `allowed-tools` that is absent, a skill with no record
4
- * block and one whose record block ends the file.
3
+ * that is not a mapping or not YAML, and an `allowed-tools` that is absent.
5
4
  */
6
5
  import assert from "node:assert/strict";
7
6
  import { test } from "vitest";
8
- import { parseFm, recordBlock, toolList } from "./skill-corpus.mjs";
7
+ import { parseFm, toolList } from "./skill-corpus.mjs";
9
8
 
10
9
  const refusal = (fn) => {
11
10
  try {
@@ -42,13 +41,3 @@ test("toolList of nothing is an empty contract, and a flow list or a comma scala
42
41
  [[], ["Bash", "Read"], ["Read", "Write"], ["Read"]],
43
42
  );
44
43
  });
45
-
46
- test("recordBlock: none without the heading; to the end of the file when it is the last section", () => {
47
- assert.deepEqual(
48
- [
49
- recordBlock("# S\n\n## Run me\n\nx\n"),
50
- recordBlock("# S\n\n## Record the verdict\n\nlast\n"),
51
- ],
52
- [null, "## Record the verdict\n\nlast\n"],
53
- );
54
- });
@@ -54,7 +54,7 @@ import { resolve } from "node:path";
54
54
  import { consumerRoot } from "../skills/paper-pipeline/scripts/consumer.mjs";
55
55
 
56
56
  import { BY_SKILL, irrelevantFor } from "./skill-trigger-cases.mjs";
57
- import { FIXTURE } from "./skill-eval-fixture.mjs";
57
+ import { FIXTURE } from "#lib/skill-eval-fixture";
58
58
 
59
59
  // The CONSUMER's root — the live harness being measured is the consumer's, not this package's.
60
60
  const REPO = consumerRoot();
@@ -330,7 +330,7 @@ const PACKAGE_CASES = [
330
330
  //
331
331
  // 🔴 AND THEY CANNOT SIMPLY BE CARRIED HERE: the first consumer's cases name private skills and
332
332
  // quote private prose. A package that ships one user's private vocabulary has one possible user —
333
- // the same argument that moved `papersRoot()`, `ledgerPath()`, `citeChecks` and `scriptsRoot()`
333
+ // the same argument that moved `papersRoot()` and `scriptsRoot()`
334
334
  // out of the code and into a declaration. This is the fifth of those carriers and reads the same
335
335
  // key from the same file.
336
336
  //