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
@@ -9,11 +9,9 @@
9
9
  * a result, and the coupling was an environment variable. The owner asked three times why the
10
10
  * file survived; the answer was that I had wrapped it instead of splitting it.
11
11
  *
12
- * WHAT IS CHECKED HERE vs NEXT DOOR. Anything decidable from one SKILL.md: strict-YAML
13
- * frontmatter, the declared tool contract, every script path it instructs, whether it is
14
- * PERMITTED to run what it instructs, whether it files under its OWN identity, and its verdict
15
- * vocabulary. Anything about the SET — name uniqueness, the gate table, the exclusion list — is
16
- * `pipeline-corpus.harness.mjs`, because a per-skill run structurally cannot see it.
12
+ * WHAT IS CHECKED HERE. Anything decidable from one SKILL.md: strict-YAML frontmatter, the
13
+ * declared tool contract, every script path it instructs, and whether it is PERMITTED to run what
14
+ * it instructs.
17
15
  *
18
16
  * 🔴 KNOWN-RED ASSERTIONS ARE DEFERRED, NOT WEAKENED, and the deferral list is now PER CALL.
19
17
  * A throw would abort the rest, making one open finding silently skip every check after it —
@@ -21,108 +19,29 @@
21
19
  * a full sweep could attribute one skill's failures to another's line in the report.
22
20
  */
23
21
  import assert from "node:assert/strict";
24
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
22
+ import { existsSync, readFileSync } from "node:fs";
25
23
  import { join } from "node:path";
26
24
  import { recordCheck } from "vigiles";
27
25
 
28
- // Markup is parsed with a parser (`CLAUDE.md`, 2026-08-11). `requireMarkdown()` is called by
29
- // `skill-corpus.mjs` on the import below, so by the time anything is parsed here the parser
30
- // either exists or the process does not.
31
- import { headings as mdHeadings } from "./markdown.mjs";
26
+ import { SKILLS_DIR, ROOT, load, commands, permits } from "./skill-corpus.mjs";
32
27
 
33
- // 🔴 2026-08-28: nine names were removed from this list, and that is NOT a style cleanup — they
34
- // had drifted apart from what the file does. `EXCLUDED PIPELINE_MARKER GATE_SKILLS EXPECTED_GATES
35
- // KINDS parseGate` are about the SET of skills (name uniqueness, the gate table, the exclusion
36
- // list); the file's own header says that is `pipeline-corpus.harness.mjs`'s job, and it was
37
- // verified by grep — all six really are used there, no logic was lost. `frontmatter` and
38
- // `toolList` are called INSIDE `load()` in `skill-corpus.mjs`, and the direct import was a
39
- // second path to the same data. `allBold` is the raw material for `namedKinds`/`namedRetired`,
40
- // which are imported right beside it.
28
+ // Where the pipeline's own scripts live is asked of the package, not spelled here. The resolver
29
+ // refuses a root that is not on disk, because every use below is a FILTER: a wrong prefix would
30
+ // match nothing and report a clean skill that was never examined.
41
31
  import {
42
- SKILLS,
43
- SKILLS_DIR,
44
- RETIRED,
45
- ABSTENTIONS,
46
- record,
47
- ROOT,
48
- LEDGER_ONLY_BASH,
49
- recordBlock,
50
- namedKinds,
51
- namedRetired,
52
- load,
53
- commands,
54
- permits,
55
- definedKinds,
56
- namedReasons,
57
- ADMISSION,
58
- tmp,
59
- } from "./skill-corpus.mjs";
60
-
61
- // ═════════════════════════════════════════════════════════════════════════════════════════════
62
- // WHERE THE PIPELINE'S SCRIPTS ARE — ASKED, NOT SPELLED (2026-09-12)
63
- //
64
- // Until today this file carried the string `.claude/skills/paper-pipeline/scripts/` in SEVEN
65
- // places: two announce/record templates, two `indexOf` ordering probes, one `startsWith` filter
66
- // and two regular expressions. Every one of them is this consumer's own layout, and a mechanism
67
- // that spells one consumer's layout has exactly one possible user — the thing the extraction
68
- // exists to undo. Nine colocated harnesses of wave ① import THIS module and nothing else from
69
- // `lib/`, so until the seven literals left, none of them could be built anywhere but here.
70
- //
71
- // The value now comes from the package, as the fourth carrier beside `papersRoot()` (where the
72
- // papers are), `ledgerPath()` (where the journal is) and `citeChecks` (where the consumer's own
73
- // citation checkers are). One declaration in `paperlint.json`, default
74
- // `.claude/skills/paper-pipeline/scripts`, and the resolver REFUSES a root that is not on disk
75
- // or that resolves inside `node_modules`.
76
- //
77
- // 🔴 WHY THE REFUSAL IS THE POINT AND NOT DEFENSIVENESS. Six of the seven uses are FILTERS —
78
- // `startsWith`, `indexOf`, `RegExp.test`. A wrong prefix does not make them wrong; it makes them
79
- // match nothing, so every loop body below goes unentered and `checkSkill` reports a clean skill
80
- // it never examined. That failure is byte-identical to success, and a throw while this module is
81
- // still LOADING is the only place it can be made visible.
82
- //
83
- // `{ env: {}, cwd: ROOT }` rather than the defaults: `ROOT` above already resolved
84
- // `CLAUDE_PROJECT_DIR`, and letting the resolver consult it again would make the two disagree on
85
- // exactly one input (an empty-string variable — `??` keeps it, `||` does not).
86
- import {
87
- installedSkills,
88
32
  pipelineScripts,
89
33
  scriptsRoot,
90
34
  } from "../skills/paper-pipeline/scripts/consumer.mjs";
91
35
 
92
36
  const PIPELINE = pipelineScripts(scriptsRoot({ env: {}, cwd: ROOT }));
93
37
 
94
- /**
95
- * A literal path, embedded in a RegExp. Not a parser dodge — the subject here IS a string (a
96
- * path, which has no grammar to ask), and this is the standard way to put one inside a pattern.
97
- */
98
- const reEscape = (t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
99
-
100
- /** `Bash(node <pipeline scripts>/<name>.mjs:*)` — the narrowed entry the ledger-only skills get. */
101
- const LEDGER_BASH_RE = new RegExp(
102
- `^Bash\\(node ${reEscape(PIPELINE.prefix)}[a-z-]+\\.mjs:\\*\\)$`,
103
- );
104
-
105
- /**
106
- * A command that is one of the two ledger calls.
107
- *
108
- * ⚠️ DELIBERATELY NOT ANCHORED AT THE START, because the literal it replaced was not either. The
109
- * anchored form would be tighter and is probably what anyone would write today, but tightening it
110
- * in the same change that parameterises it would mean a `before = after` comparison could no
111
- * longer tell a successful parameterisation from a changed rule.
112
- */
113
- const LEDGER_SCRIPT_RE = new RegExp(
114
- `${reEscape(PIPELINE.prefix)}(announce|ledger)\\.mjs$`,
115
- );
116
-
117
38
  /**
118
39
  * Assert everything single-skill about `name`. Throws with EVERY finding, not the first.
119
40
  * Returns the number of skills checked (1) so a caller can report what it actually did.
120
41
  */
121
42
  export async function checkSkill(name) {
122
- if (!SKILLS.includes(name))
123
- throw new Error(
124
- `"${name}" is not a wired pipeline skill. Known: ${SKILLS.join(", ")}`,
125
- );
43
+ if (!existsSync(join(SKILLS_DIR, name, "SKILL.md")))
44
+ throw new Error(`"${name}" has no SKILL.md under ${SKILLS_DIR}`);
126
45
  const deferred = [];
127
46
  const soft = (fn) => {
128
47
  try {
@@ -132,7 +51,6 @@ export async function checkSkill(name) {
132
51
  }
133
52
  };
134
53
  const skills = [load(name)];
135
- const skillDirs = new Set(installedSkills(SKILLS_DIR));
136
54
 
137
55
  // ═══════════════════════════════════════════════════════════════════════════════════════════════
138
56
  // 1. GONE, AND THAT IS THE RESULT (2026-08-19). This slot held a strict-YAML assertion whose whole
@@ -150,19 +68,15 @@ export async function checkSkill(name) {
150
68
 
151
69
  // ═══════════════════════════════════════════════════════════════════════════════════════════════
152
70
  // 2-4. Identity and contract fields, read from the strict parse — there is no other kind now, and
153
- // nothing above can fail while leaving these reachable. `name` is what the ledger and `skillHash()`
154
- // key on: a name that disagrees
155
- // with its directory means every verdict this skill records hashes against a directory that is not
156
- // its own, and staleness detection quietly stops working for it.
71
+ // nothing above can fail while leaving these reachable. `name` is what Claude Code lists and
72
+ // matches the skill by, so a name that disagrees with its directory is two identities for one skill.
157
73
  // ═══════════════════════════════════════════════════════════════════════════════════════════════
158
74
  for (const s of skills) {
159
75
  soft(() =>
160
76
  assert.equal(
161
77
  s.fm.name,
162
78
  s.name,
163
- `${s.name}: frontmatter name is ${JSON.stringify(s.fm.name)} but the directory is "${s.name}". ` +
164
- `skillHash() resolves by DIRECTORY and the ledger stores whatever the SKILL.md text passes — ` +
165
- `disagreement here splits one skill's history across two keys and neither is ever fresh.`,
79
+ `${s.name}: frontmatter name is ${JSON.stringify(s.fm.name)} but the directory is "${s.name}".`,
166
80
  ),
167
81
  );
168
82
 
@@ -186,92 +100,7 @@ export async function checkSkill(name) {
186
100
  }
187
101
 
188
102
  // ═══════════════════════════════════════════════════════════════════════════════════════════════
189
- // 5-6. The two wired blocks: `## Run me` (announce, first) and `Record the verdict` (ledger, last).
190
- // The README calls these the whole wiring contract. Each is checked three ways — the block exists,
191
- // the script it names exists on disk, and the SKILL NAME IT PASSES IS ITS OWN. The third is the
192
- // copy-paste defect: these blocks differ from their siblings' by one token, and `announce.mjs`
193
- // validates nothing, so a stale token records a real verdict under another skill's name.
194
- // ═══════════════════════════════════════════════════════════════════════════════════════════════
195
- for (const s of skills) {
196
- const cmds = commands(s.src);
197
-
198
- // 2026-08-11: "is there a `## Run me` block" is a question about a HEADING, therefore about
199
- // level and text, not about the number of hashes. The former `/^#{1,3} Run me\s*$/m` also
200
- // counted a `## Run me` line inside a ```-block as a heading: a skill SHOWING someone else's
201
- // block in an example passed the check without having one of its own. Silence is a soft
202
- // assertion's success state, so there was nothing to notice this with.
203
- soft(() =>
204
- assert.ok(
205
- mdHeadings(s.src).some((h) => h.depth <= 3 && h.text === "Run me"),
206
- `${s.name}: no "## Run me" block. Without it the skill leaves no trace of having started, and ` +
207
- `an advisory pass that leaves no trace cannot be observed failing — silence is both its error ` +
208
- `state and its normal state. Three hooks in this repo died exactly that way.`,
209
- ),
210
- );
211
-
212
- for (const [label, script, want] of [
213
- ["announce", PIPELINE.announce, "Run me"],
214
- ["record", PIPELINE.ledger, "Record the verdict"],
215
- ]) {
216
- // For the ledger, only a `record` call is the wired call: `ledger.mjs status <dir>` is a
217
- // read, files under nothing, and until 2026-09-27 was misreported as "files under status".
218
- const hits = cmds.filter(
219
- (c) =>
220
- c.script === script && (label !== "record" || c.args[0] === "record"),
221
- );
222
- soft(() =>
223
- assert.ok(
224
- hits.length > 0,
225
- `${s.name}: the "${want}" block instructs no \`node ${script}\` command. The block may be present ` +
226
- `and empty — the README's contract is the CALL, not the heading.`,
227
- ),
228
- );
229
- for (const h of hits) {
230
- soft(() =>
231
- assert.ok(
232
- existsSync(join(ROOT, h.script)),
233
- `${s.name}: instructs \`${h.cmd}\` and ${h.script} DOES NOT EXIST. A skill naming a path that ` +
234
- `is not there is a dead instruction: the model reads it, does something adjacent, and reports ` +
235
- `success. Prose cannot notice this; nothing else in this repo was looking.`,
236
- ),
237
- );
238
- // arg[0] for announce is the skill; for ledger.mjs it is the literal `record`, then the skill.
239
- const passed = label === "announce" ? h.args[0] : h.args[1];
240
- soft(() =>
241
- assert.equal(
242
- passed,
243
- s.name,
244
- `${s.name}: its ${label} command files under ${JSON.stringify(passed)}, not "${s.name}" — ` +
245
- `\`${h.cmd}\`. These blocks are copy-paste templates differing in one token and announce.mjs ` +
246
- `validates nothing, so this records a real run under a SIBLING'S identity: the wrong gate ` +
247
- `reads FRESH in status.mjs and the right one reads NEVER-RUN.`,
248
- ),
249
- );
250
- }
251
- }
252
-
253
- soft(() => {
254
- const a = s.src.indexOf(PIPELINE.announce);
255
- const r = s.src.indexOf(`${PIPELINE.ledger} record`);
256
- if (a >= 0 && r >= 0)
257
- assert.ok(
258
- a < r,
259
- `${s.name}: the ledger record call appears BEFORE the announce call. The contract is announce ` +
260
- `first / record last; a verdict written before the run started is a verdict about nothing.`,
261
- );
262
- });
263
-
264
- soft(() =>
265
- assert.ok(
266
- recordBlock(s.src),
267
- `${s.name}: no "Record the verdict" heading. A run with an announce row and no verdict row is ` +
268
- `indistinguishable from a skill that started and died.`,
269
- ),
270
- );
271
- }
272
-
273
- // ═══════════════════════════════════════════════════════════════════════════════════════════════
274
- // 7. EVERY script path the skill instructs resolves — not only the two ledger calls.
103
+ // 7. EVERY script path the skill instructs resolves.
275
104
  //
276
105
  // Scoped to paths that are unambiguously resolvable: `.claude/…` is repo-root-relative and
277
106
  // `scripts/…` is skill-relative (both conventions are used consistently across the corpus). Paths
@@ -298,209 +127,27 @@ export async function checkSkill(name) {
298
127
 
299
128
  // ═══════════════════════════════════════════════════════════════════════════════════════════════
300
129
  // 8. A skill is PERMITTED TO RUN what it instructs. The inverse-direction risk of narrowing: over-
301
- // narrow an allowed-tools entry and the skill's own first instruction is denied at runtime, which
302
- // looks like the skill silently declining to announce — the same invisible failure as a dead hook,
303
- // arrived at from the opposite direction. Nothing else checks the two halves against each other.
130
+ // narrow an allowed-tools entry and the skill's own instruction is denied at runtime, which looks
131
+ // like the skill silently declining a step — the same invisible failure as a dead hook, arrived at
132
+ // from the opposite direction. Nothing else checks the two halves against each other.
304
133
  // ═══════════════════════════════════════════════════════════════════════════════════════════════
305
134
  for (const s of skills) {
306
135
  for (const c of commands(s.src)) {
307
- // The calls the pipeline contract owns. Was `.claude/pipeline/` until 2026-08-15, when that
308
- // directory was dissolved and `ledger.mjs`/`announce.mjs` went home to the skill they serve.
136
+ // The pipeline's own scripts; any other command is the consumer's business.
309
137
  if (!c.script.startsWith(PIPELINE.prefix)) continue;
310
138
  soft(() =>
311
139
  assert.ok(
312
140
  s.tools.some((t) => permits(t, c.cmd)),
313
141
  `${s.name}: instructs \`${c.cmd}\` but its allowed-tools [${s.tools.join(", ")}] permit no such ` +
314
- `Bash command. The instruction is denied at runtime and the skill simply does not announce or ` +
315
- `record — invisible, because a gate that never wrote a row looks exactly like one nobody ran.`,
142
+ `Bash command. The instruction is denied at runtime and the step silently does not happen.`,
316
143
  ),
317
144
  );
318
145
  }
319
146
  }
320
147
 
321
- // ═══════════════════════════════════════════════════════════════════════════════════════════════
322
- // 9. The nine ledger-only skills keep their Bash NARROWED.
323
- //
324
- // A plain `Bash` on one of these is the regression that took this repo's Safety score from 70 to 0
325
- // in the audit's own arithmetic: Read + Bash + WebFetch is the lethal trifecta, and these nine are
326
- // analysis skills that read the knowledge base. `argument-arc` grading a paper does not need a
327
- // shell; it needs to append one line to a JSONL. Construction beats prose — with the narrow entry
328
- // the capability is ABSENT, and absence cannot be talked around by injected text in a fetched CFP.
329
- // ═══════════════════════════════════════════════════════════════════════════════════════════════
330
- for (const s of skills.filter((s) => LEDGER_ONLY_BASH.has(s.name))) {
331
- const bash = s.tools.filter((t) => t === "Bash" || t.startsWith("Bash("));
332
- soft(() =>
333
- assert.ok(
334
- bash.length > 0,
335
- `${s.name}: listed as ledger-only-Bash but declares no Bash entry at all — it cannot run its own ` +
336
- `announce/record calls. Either add the narrow entries or remove it from LEDGER_ONLY_BASH here.`,
337
- ),
338
- );
339
- for (const b of bash) {
340
- soft(() =>
341
- assert.match(
342
- b,
343
- LEDGER_BASH_RE,
344
- `${s.name}: Bash entry ${JSON.stringify(b)} is not narrowed to the ledger. This skill gained Bash ` +
345
- `ONLY to append a run row; a bare \`Bash\` hands a knowledge-base reader a shell and completes ` +
346
- `the lethal trifecta. See ALLOWED-TOOLS-AUDIT-2026-08-03.md §2 for why this one was narrowed.`,
347
- ),
348
- );
349
- }
350
- }
351
-
352
- // ═══════════════════════════════════════════════════════════════════════════════════════════════
353
- // 10. THE VOCABULARY, TURNED AROUND ON 2026-08-10.
354
- //
355
- // WHAT THIS USED TO ASSERT, and why it had to change rather than be deleted. The old text was:
356
- // "the vocabulary contains a REACHABLE NEGATIVE — or the text admits it does not", because a gate
357
- // documented as PASS-only cannot say no and no amount of running would reveal it. With `PASS`
358
- // deleted from the ledger, the literal form of that assertion is VACUOUS: `FINDING` and
359
- // `ABSTAINED` are the only constructors, so every conforming block names a negative by
360
- // construction and the check would pass on every file forever while reading as though it still
361
- // guarded something. A vacuous assertion is worse than a missing one — it is a green light with
362
- // nothing behind it, which is this directory's whole subject.
363
- //
364
- // So the property is carried over rather than the wording. It was never really "names a negative";
365
- // it was **"this check is capable of reporting that something is wrong."** Under the new
366
- // vocabulary that is: the block names FINDING, the evidence-carrying constructor. A block that
367
- // documents only how to abstain describes a check that cannot fail — the same defect, wearing the
368
- // new words.
369
- //
370
- // 10b is genuinely new and is the migration guard: a block still instructing a RETIRED constructor
371
- // tells the model to run a command that now exits 2. Prose survives refactors that code does not,
372
- // and 22 files were rewritten by hand today.
373
- //
374
- // 10c requires a named abstention REASON, because `ABSTAINED` with no reason is the deleted `PASS`
375
- // with a longer name — the closed reason vocabulary is the entire mechanism separating "ran and
376
- // found nothing" from "could not run".
377
- //
378
- // The escape hatch on 10a is deliberate and is the honest case, not a loophole: `draft-paper`
379
- // GENERATES, will abstain nearly always, and its text says so in status.mjs's own terms. That
380
- // admission is load-bearing documentation — delete it and an explained limitation becomes an
381
- // unexplained never-finding check — so it is asserted directly, not merely permitted.
382
- // ═══════════════════════════════════════════════════════════════════════════════════════════════
383
- for (const s of skills) {
384
- const blk = recordBlock(s.src);
385
- if (!blk) continue;
386
-
387
- soft(() =>
388
- assert.ok(
389
- namedKinds(blk).includes("FINDING") || ADMISSION.test(blk),
390
- `${s.name}: its record block names [${namedKinds(blk).join(", ") || "nothing"}] and not FINDING, ` +
391
- `and does not admit that it cannot produce one. FINDING is the only constructor that carries ` +
392
- `evidence; a block documenting only how to ABSTAIN describes a check that can never report that ` +
393
- `anything is wrong. Either name the finding this skill can return, or state plainly that it ` +
394
- `generates rather than judges — as draft-paper does.`,
395
- ),
396
- );
397
-
398
- soft(() =>
399
- assert.deepEqual(
400
- namedRetired(blk),
401
- [],
402
- `${s.name}: its record block still instructs the RETIRED constructor(s) ` +
403
- `[${namedRetired(blk).join(", ")}]. ${[...RETIRED.keys()]
404
- .filter((k) => namedRetired(blk).includes(k))
405
- .map((k) => `${k} ${RETIRED.get(k)}`)
406
- .join(" ")} ` +
407
- `A model following this block runs a command that exits 2 at the last step of finished work.`,
408
- ),
409
- );
410
-
411
- soft(() =>
412
- assert.ok(
413
- namedReasons(blk).length > 0,
414
- `${s.name}: its record block names no abstention reason from [${[...ABSTENTIONS.keys()].join(", ")}]. ` +
415
- `An ABSTAINED with an unnamed reason is the deleted PASS with more characters: the closed reason ` +
416
- `set is the only thing separating "ran and found nothing" from "could not run", and a block that ` +
417
- `does not say which one applies leaves the model to invent a string record() will refuse.`,
418
- ),
419
- );
420
- }
421
- // Guarded for one-skill mode: this assertion is ABOUT draft-paper, so it only runs
422
- // when draft-paper is in scope. Silently skipping it in a full sweep would be the
423
- // vacuity this file hunts, so the guard is the membership test, not a try/catch.
424
- const draftPaper = skills.find((s) => s.name === "draft-paper");
425
- if (draftPaper)
426
- soft(() =>
427
- assert.match(
428
- recordBlock(draftPaper.src),
429
- ADMISSION,
430
- `draft-paper: the honest admission is gone from its record block. It is the one skill in the ` +
431
- `pipeline that will almost always abstain, and the paragraph saying so — in status.mjs's own ` +
432
- `terms, "a generator is not a gate" — is what keeps that flag readable as a known limitation ` +
433
- `rather than an unexplained never-finding check someone later "fixes" by faking one.`,
434
- ),
435
- );
436
-
437
- // ═══════════════════════════════════════════════════════════════════════════════════════════════
438
- // 11. No skill instructs something `record()` will throw on — proven by CALLING IT, not by reading
439
- // the vocabulary list. `record()` validates its argument and throws; a skill telling the model to
440
- // record `GREEN`, `OK` or `PASS` produces a crash at the last step of a completed pass, after the
441
- // work is done.
442
- //
443
- // Each constructor is called in its VALID shape, because the shape is part of the contract: a
444
- // FINDING needs a count and an existing report, an ABSTENTION needs a reason from the closed set.
445
- // Calling `record` with a bare kind would prove only that the kind string is spelled right.
446
- // ═══════════════════════════════════════════════════════════════════════════════════════════════
447
- {
448
- writeFileSync(
449
- join(tmp, "fixture-report.md"),
450
- "---\nfindings: 1\n---\n\nharness fixture\n",
451
- );
452
- for (const s of skills) {
453
- const blk = recordBlock(s.src);
454
- if (!blk) continue;
455
- for (const v of definedKinds(blk)) {
456
- soft(() =>
457
- record(
458
- v === "ABSTAINED"
459
- ? {
460
- skill: s.name,
461
- paper: tmp,
462
- kind: v,
463
- reason: namedReasons(blk)[0] ?? null,
464
- note: "harness fixture",
465
- }
466
- : {
467
- skill: s.name,
468
- paper: tmp,
469
- kind: v,
470
- findings: 1,
471
- report: "fixture-report.md",
472
- note: "harness fixture",
473
- },
474
- ),
475
- );
476
- }
477
- }
478
- }
479
- // Every skill name any of these files hands to announce.mjs or ledger.mjs must resolve to a real
480
- // skill, including names inside worked examples. announce.mjs does NOT validate: an unknown name
481
- // hashes to `????????` and the row is written anyway.
482
- for (const s of skills) {
483
- for (const c of commands(s.src)) {
484
- if (!LEDGER_SCRIPT_RE.test(c.script)) continue;
485
- const passed = c.script.endsWith("announce.mjs")
486
- ? c.args[0]
487
- : c.args[0] === "record"
488
- ? c.args[1]
489
- : null;
490
- if (!passed || passed.startsWith("<")) continue;
491
- soft(() =>
492
- assert.ok(
493
- skillDirs.has(passed),
494
- `${s.name}: instructs \`${c.cmd}\`, naming skill "${passed}", which has no directory under ` +
495
- `.claude/skills/. announce.mjs validates nothing — it hashes the unknown name to "????????" ` +
496
- `and writes the row, so the typo is permanent and silent in the ledger.`,
497
- ),
498
- );
499
- }
500
- }
501
148
  if (deferred.length) {
502
149
  const e = new Error(
503
- `${deferred.length} assertion(s) failed for pipeline skill "${name}":\n\n` +
150
+ `${deferred.length} assertion(s) failed for skill "${name}":\n\n` +
504
151
  deferred.map((d, i) => ` [${i + 1}] ${d.message}`).join("\n\n"),
505
152
  );
506
153
  e.stack = e.message;
@@ -509,20 +156,16 @@ export async function checkSkill(name) {
509
156
  // Reported so `vigiles test` can tell a run that verified this skill from a file that merely
510
157
  // sits beside it — the distinction colocation cannot make (see vigiles check-count.ts).
511
158
  recordCheck();
512
- console.log(
513
- `✓ ${name}: frontmatter, wiring, script paths, tool contract, verdict vocabulary`,
514
- );
159
+ console.log(`✓ ${name}: frontmatter, script paths, tool contract`);
515
160
  return 1;
516
161
  }
517
162
 
518
163
  // ═════════════════════════════════════════════════════════════════════════════════════════════════
519
164
  // checkProseSkill — for skills WITHOUT the pipeline (2026-08-27)
520
165
  //
521
- // WHY A SEPARATE FUNCTION AND NOT A FLAG ON checkSkill. `checkSkill` requires `## Run me`, a call
522
- // to `announce.mjs` and a "Record the verdict" block — those are paper-pipeline conventions, and a
523
- // skill like `avito-posting` neither has them nor should. Running it through checkSkill means
524
- // getting three red assertions about the absence of what it never promised. A check that is red by
525
- // construction lives one day, after which it gets silenced — that has already happened in this repo.
166
+ // WHY A SEPARATE FUNCTION. A prose skill only writes text, so Bash, Agent and Task are banned
167
+ // unless an exemption names a reason; a pipeline skill runs its scripts through narrowed Bash
168
+ // entries and is checked instead for whether those scripts exist and are permitted.
526
169
  //
527
170
  // 🔴 BOTH HALVES ARE BUILT IN, NOT WRITTEN BY HAND. The `CLAUDE.md` rule requires two halves of
528
171
  // every check: it fires on a planted defect AND it stays silent on clean input. The "fires" half is