paperlint 3.1.3 → 3.2.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 (371) hide show
  1. package/.github/workflows/ci.yml +12 -7
  2. package/CLAUDE.md +3 -3
  3. package/CONTRIBUTING.md +34 -5
  4. package/dist/adapters/banal/index.d.ts.map +1 -1
  5. package/dist/adapters/banal/index.js +9 -5
  6. package/dist/adapters/banal/index.js.map +1 -1
  7. package/dist/adapters/banal/invocation.d.ts +1 -1
  8. package/dist/adapters/node/files.io.d.ts.map +1 -1
  9. package/dist/adapters/node/files.io.js +2 -1
  10. package/dist/adapters/node/files.io.js.map +1 -1
  11. package/dist/adapters/node/process.io.d.ts +0 -5
  12. package/dist/adapters/node/process.io.d.ts.map +1 -1
  13. package/dist/adapters/node/process.io.js +4 -3
  14. package/dist/adapters/node/process.io.js.map +1 -1
  15. package/dist/adapters/pdfjs/fill.d.ts.map +1 -1
  16. package/dist/adapters/pdfjs/fill.js +13 -7
  17. package/dist/adapters/pdfjs/fill.js.map +1 -1
  18. package/dist/adapters/references/index.d.ts.map +1 -1
  19. package/dist/adapters/references/index.js +4 -5
  20. package/dist/adapters/references/index.js.map +1 -1
  21. package/dist/adapters/references/pool.d.ts.map +1 -1
  22. package/dist/adapters/references/pool.js +4 -5
  23. package/dist/adapters/references/pool.js.map +1 -1
  24. package/dist/adapters/references/reach.io.d.ts.map +1 -1
  25. package/dist/adapters/references/reach.io.js +2 -1
  26. package/dist/adapters/references/reach.io.js.map +1 -1
  27. package/dist/build-engine.js +2 -2
  28. package/dist/build-engine.js.map +1 -1
  29. package/dist/build.d.ts.map +1 -1
  30. package/dist/build.js +14 -11
  31. package/dist/build.js.map +1 -1
  32. package/dist/cli.d.ts.map +1 -1
  33. package/dist/cli.js +31 -34
  34. package/dist/cli.js.map +1 -1
  35. package/dist/doctor.d.ts.map +1 -1
  36. package/dist/doctor.js +3 -2
  37. package/dist/doctor.js.map +1 -1
  38. package/dist/domain/lookup-cache.d.ts.map +1 -1
  39. package/dist/domain/lookup-cache.js +10 -8
  40. package/dist/domain/lookup-cache.js.map +1 -1
  41. package/dist/domain/text.d.ts +12 -0
  42. package/dist/domain/text.d.ts.map +1 -1
  43. package/dist/domain/text.js +17 -0
  44. package/dist/domain/text.js.map +1 -1
  45. package/dist/engine.d.ts.map +1 -1
  46. package/dist/engine.js +4 -3
  47. package/dist/engine.js.map +1 -1
  48. package/dist/eslint-rules/bib-reachable-entry.d.ts +22 -0
  49. package/dist/eslint-rules/bib-reachable-entry.d.ts.map +1 -0
  50. package/dist/eslint-rules/bib-reachable-entry.js +75 -0
  51. package/dist/eslint-rules/bib-reachable-entry.js.map +1 -0
  52. package/dist/eslint-rules/install-path-literals.d.ts +80 -0
  53. package/dist/eslint-rules/install-path-literals.d.ts.map +1 -0
  54. package/dist/eslint-rules/install-path-literals.js +105 -0
  55. package/dist/eslint-rules/install-path-literals.js.map +1 -0
  56. package/dist/eslint-rules/latex-language.d.ts +136 -0
  57. package/dist/eslint-rules/latex-language.d.ts.map +1 -0
  58. package/dist/eslint-rules/latex-language.js +639 -0
  59. package/dist/eslint-rules/latex-language.js.map +1 -0
  60. package/dist/eslint-rules/paper-research-question.d.ts +31 -0
  61. package/dist/eslint-rules/paper-research-question.d.ts.map +1 -0
  62. package/dist/eslint-rules/paper-research-question.js +188 -0
  63. package/dist/eslint-rules/paper-research-question.js.map +1 -0
  64. package/dist/eslint-rules/paper-stages.d.ts +83 -0
  65. package/dist/eslint-rules/paper-stages.d.ts.map +1 -0
  66. package/dist/eslint-rules/paper-stages.js +358 -0
  67. package/dist/eslint-rules/paper-stages.js.map +1 -0
  68. package/dist/eslint-rules/paper-typography.d.ts +74 -0
  69. package/dist/eslint-rules/paper-typography.d.ts.map +1 -0
  70. package/dist/eslint-rules/paper-typography.js +404 -0
  71. package/dist/eslint-rules/paper-typography.js.map +1 -0
  72. package/dist/eslint-rules/papers.d.ts +42 -0
  73. package/dist/eslint-rules/papers.d.ts.map +1 -0
  74. package/dist/eslint-rules/papers.js +145 -0
  75. package/dist/eslint-rules/papers.js.map +1 -0
  76. package/dist/eslint-rules/pdf-last-page-balance.d.ts +72 -0
  77. package/dist/eslint-rules/pdf-last-page-balance.d.ts.map +1 -0
  78. package/dist/eslint-rules/pdf-last-page-balance.js +237 -0
  79. package/dist/eslint-rules/pdf-last-page-balance.js.map +1 -0
  80. package/dist/eslint-rules/review-frontmatter.d.ts +57 -0
  81. package/dist/eslint-rules/review-frontmatter.d.ts.map +1 -0
  82. package/dist/eslint-rules/review-frontmatter.js +126 -0
  83. package/dist/eslint-rules/review-frontmatter.js.map +1 -0
  84. package/dist/eslint-rules/review-frontmatter.schema.json +36 -0
  85. package/dist/eslint-rules/rule-context.d.ts +55 -0
  86. package/dist/eslint-rules/rule-context.d.ts.map +1 -0
  87. package/dist/eslint-rules/rule-context.js +9 -0
  88. package/dist/eslint-rules/rule-context.js.map +1 -0
  89. package/dist/eslint-rules/sibling-frontmatter.d.ts +25 -0
  90. package/dist/eslint-rules/sibling-frontmatter.d.ts.map +1 -0
  91. package/dist/eslint-rules/sibling-frontmatter.js +16 -0
  92. package/dist/eslint-rules/sibling-frontmatter.js.map +1 -0
  93. package/dist/eslint-rules/sibling-frontmatter.schema.json +14 -0
  94. package/dist/eslint-rules/temp-root-realpath.d.ts +71 -0
  95. package/dist/eslint-rules/temp-root-realpath.d.ts.map +1 -0
  96. package/dist/eslint-rules/temp-root-realpath.js +139 -0
  97. package/dist/eslint-rules/temp-root-realpath.js.map +1 -0
  98. package/dist/eslint-rules/tex-build.d.ts +35 -0
  99. package/dist/eslint-rules/tex-build.d.ts.map +1 -0
  100. package/dist/eslint-rules/tex-build.js +313 -0
  101. package/dist/eslint-rules/tex-build.js.map +1 -0
  102. package/dist/facts-file.d.ts.map +1 -1
  103. package/dist/facts-file.js +2 -1
  104. package/dist/facts-file.js.map +1 -1
  105. package/dist/hooks-settings.d.ts +1 -1
  106. package/dist/hooks-settings.d.ts.map +1 -1
  107. package/dist/hooks-settings.js +14 -9
  108. package/dist/hooks-settings.js.map +1 -1
  109. package/dist/init.d.ts +5 -5
  110. package/dist/init.d.ts.map +1 -1
  111. package/dist/init.js +15 -7
  112. package/dist/init.js.map +1 -1
  113. package/dist/latex-loop.d.ts +2 -2
  114. package/dist/latex-loop.d.ts.map +1 -1
  115. package/dist/latex-loop.js +2 -2
  116. package/dist/latex-loop.js.map +1 -1
  117. package/dist/lib/agent-cli-version.d.ts +65 -0
  118. package/dist/lib/agent-cli-version.d.ts.map +1 -0
  119. package/dist/lib/agent-cli-version.js +77 -0
  120. package/dist/lib/agent-cli-version.js.map +1 -0
  121. package/dist/lib/check.d.ts +19 -0
  122. package/dist/lib/check.d.ts.map +1 -0
  123. package/dist/lib/check.js +52 -0
  124. package/dist/lib/check.js.map +1 -0
  125. package/dist/lib/markdown.d.ts +180 -0
  126. package/dist/lib/markdown.d.ts.map +1 -0
  127. package/dist/lib/markdown.js +302 -0
  128. package/dist/lib/markdown.js.map +1 -0
  129. package/dist/lib/paper-config.d.ts +84 -0
  130. package/dist/lib/paper-config.d.ts.map +1 -0
  131. package/dist/lib/paper-config.js +142 -0
  132. package/dist/lib/paper-config.js.map +1 -0
  133. package/dist/lib/skill-eval-fixture.d.ts +29 -0
  134. package/dist/lib/skill-eval-fixture.d.ts.map +1 -0
  135. package/dist/lib/skill-eval-fixture.js +62 -0
  136. package/dist/lib/skill-eval-fixture.js.map +1 -0
  137. package/dist/link-skills.d.ts.map +1 -1
  138. package/dist/link-skills.js +5 -4
  139. package/dist/link-skills.js.map +1 -1
  140. package/dist/new-paper.d.ts.map +1 -1
  141. package/dist/new-paper.js +2 -2
  142. package/dist/new-paper.js.map +1 -1
  143. package/dist/paper-settings.d.ts.map +1 -1
  144. package/dist/paper-settings.js +5 -4
  145. package/dist/paper-settings.js.map +1 -1
  146. package/dist/pdf-facts.d.ts.map +1 -1
  147. package/dist/pdf-facts.js +2 -1
  148. package/dist/pdf-facts.js.map +1 -1
  149. package/dist/pdf-geometry.d.ts.map +1 -1
  150. package/dist/pdf-geometry.js +1 -1
  151. package/dist/pdf-geometry.js.map +1 -1
  152. package/dist/presets.d.ts.map +1 -1
  153. package/dist/presets.js +3 -2
  154. package/dist/presets.js.map +1 -1
  155. package/dist/references.d.ts.map +1 -1
  156. package/dist/references.js +11 -6
  157. package/dist/references.js.map +1 -1
  158. package/dist/rules-config.d.ts.map +1 -1
  159. package/dist/rules-config.js +6 -3
  160. package/dist/rules-config.js.map +1 -1
  161. package/dist/structure.js +1 -1
  162. package/dist/structure.js.map +1 -1
  163. package/dist/tex-requirements.d.ts.map +1 -1
  164. package/dist/tex-requirements.js +8 -3
  165. package/dist/tex-requirements.js.map +1 -1
  166. package/dist/toolchain.d.ts.map +1 -1
  167. package/dist/toolchain.js +21 -16
  168. package/dist/toolchain.js.map +1 -1
  169. package/dist/venue-rules.js +1 -1
  170. package/dist/venue-rules.js.map +1 -1
  171. package/docs/e2e.md +7 -7
  172. package/docs/install.md +1 -1
  173. package/docs/prior-art/README.md +1 -1
  174. package/docs/prior-art/package-location.md +5 -5
  175. package/docs/prior-art/repro/README.md +1 -1
  176. package/docs/prior-art/test-tooling.md +2 -2
  177. package/docs/testing.md +11 -8
  178. package/docs/toolchain.md +1 -1
  179. package/eslint-rules/{bib-reachable-entry.mjs → bib-reachable-entry.ts} +4 -3
  180. package/eslint-rules/install-path-literals.harness.mjs +1 -1
  181. package/eslint-rules/{install-path-literals.mjs → install-path-literals.ts} +16 -8
  182. package/eslint-rules/latex-language.harness.mjs +2 -2
  183. package/eslint-rules/latex-language.test.mjs +7 -1
  184. package/eslint-rules/{latex-language.mjs → latex-language.ts} +297 -109
  185. package/eslint-rules/paper-research-question.harness.mjs +3 -3
  186. package/eslint-rules/{paper-research-question.mjs → paper-research-question.ts} +34 -15
  187. package/eslint-rules/paper-stages.harness.mjs +2 -2
  188. package/eslint-rules/paper-stages.test.mjs +8 -2
  189. package/eslint-rules/{paper-stages.mjs → paper-stages.ts} +74 -29
  190. package/eslint-rules/paper-typography.test.mjs +3 -3
  191. package/eslint-rules/{paper-typography.mjs → paper-typography.ts} +132 -54
  192. package/eslint-rules/papers.harness.mjs +2 -2
  193. package/eslint-rules/{papers.mjs → papers.ts} +7 -4
  194. package/eslint-rules/pdf-last-page-balance.harness.mjs +3 -3
  195. package/eslint-rules/{pdf-last-page-balance.mjs → pdf-last-page-balance.ts} +88 -31
  196. package/eslint-rules/review-frontmatter.test.mjs +2 -2
  197. package/eslint-rules/{review-frontmatter.mjs → review-frontmatter.ts} +37 -15
  198. package/eslint-rules/rule-context.ts +62 -0
  199. package/eslint-rules/{sibling-frontmatter.mjs → sibling-frontmatter.ts} +1 -1
  200. package/eslint-rules/temp-root-realpath.harness.mjs +1 -1
  201. package/eslint-rules/{temp-root-realpath.mjs → temp-root-realpath.ts} +19 -6
  202. package/eslint-rules/tex-build.harness.mjs +2 -2
  203. package/eslint-rules/{tex-build.mjs → tex-build.ts} +4 -3
  204. package/eslint.config.mjs +139 -30
  205. package/fixtures/pdf-facts/README.md +2 -2
  206. package/fixtures/real-markdown-paper/{baseline.mjs → baseline.ts} +31 -10
  207. package/hooks/{hooks.harness.mjs → hooks.harness.ts} +51 -34
  208. package/hooks/paper-edit-guard.hook.mjs +1 -1
  209. package/hooks/{paper-edit-guard.test.mjs → paper-edit-guard.test.ts} +3 -7
  210. package/hooks/paper-skills-nudge.hook.mjs +1 -1
  211. package/hooks/{paper-skills-nudge.test.mjs → paper-skills-nudge.test.ts} +2 -6
  212. package/hooks/{paper-status-gates.test.mjs → paper-status-gates.test.ts} +2 -6
  213. package/lib/agent-cli-version.harness.mjs +1 -1
  214. package/lib/{agent-cli-version.mjs → agent-cli-version.ts} +32 -18
  215. package/lib/check.test.ts +14 -9
  216. package/lib/{check.mjs → check.ts} +22 -9
  217. package/lib/markdown.test.mjs +7 -7
  218. package/lib/{markdown.mjs → markdown.ts} +68 -29
  219. package/lib/paper-config.harness.mjs +8 -8
  220. package/lib/{paper-config.mjs → paper-config.ts} +19 -14
  221. package/lib/skill-checks.mjs +1 -1
  222. package/lib/skill-checks.test.mjs +1 -1
  223. package/lib/skill-corpus.mjs +1 -1
  224. package/lib/skill-eval-kit.mjs +1 -1
  225. package/package.json +42 -10
  226. package/scripts/{check.harness.mjs → check.harness.ts} +111 -10
  227. package/scripts/{check.test.mjs → check.test.ts} +13 -10
  228. package/scripts/{check.mjs → check.ts} +116 -42
  229. package/scripts/coverage-config.test.ts +80 -37
  230. package/scripts/eslint-report-guard.test.mjs +1 -1
  231. package/scripts/harness-api.frozen.json +14 -15
  232. package/scripts/harness-api.test.ts +8 -5
  233. package/scripts/layer-legacy-frozen.test.ts +17 -13
  234. package/scripts/{layer-legacy-frozen.mjs → layer-legacy-frozen.ts} +128 -39
  235. package/scripts/release-config.test.ts +12 -4
  236. package/scripts/rules-see-files.test.mjs +1 -1
  237. package/scripts/test.test.ts +78 -0
  238. package/scripts/test.ts +87 -0
  239. package/skills/grade-paper-writing/prose-lint.mjs +1 -1
  240. package/skills/grade-paper-writing/{prose-lint.test.mjs → prose-lint.test.ts} +2 -2
  241. package/skills/paper-pipeline/description-language.eval.mjs +1 -1
  242. package/skills/paper-pipeline/framing-vs-vocabulary.eval.mjs +1 -1
  243. package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +1 -1
  244. package/skills/paper-pipeline/pipeline-firing.eval.mjs +1 -1
  245. package/skills/paper-pipeline/pipeline-language.eval.mjs +1 -1
  246. package/skills/paper-pipeline/scripts/README.md +1 -1
  247. package/skills/paper-pipeline/scripts/announce.test.mjs +1 -1
  248. package/skills/paper-pipeline/scripts/artifact-coverage.mjs +1 -1
  249. package/skills/paper-pipeline/scripts/{artifact-coverage.test.mjs → artifact-coverage.test.ts} +2 -2
  250. package/skills/paper-pipeline/scripts/consumer.mjs +1 -5
  251. package/skills/paper-pipeline/scripts/consumer.test.mjs +1 -1
  252. package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +1 -1
  253. package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +1 -4
  254. package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +1 -1
  255. package/skills/paper-pipeline/scripts/fixtures/{scorecard.mjs → scorecard.ts} +36 -7
  256. package/skills/paper-pipeline/scripts/{generated-code.test.mjs → generated-code.test.ts} +3 -3
  257. package/skills/paper-pipeline/scripts/ledger.cli.test.mjs +1 -1
  258. package/skills/paper-pipeline/scripts/pipeline-check.cli.test.mjs +2 -2
  259. package/skills/paper-pipeline/scripts/pipeline-check.harness.mjs +2 -2
  260. package/skills/paper-pipeline/scripts/pipeline-check.mjs +1 -4
  261. package/skills/paper-pipeline/scripts/population-map.mjs +1 -4
  262. package/skills/paper-pipeline/scripts/population-map.test.mjs +1 -1
  263. package/skills/paper-pipeline/scripts/{round-diff.harness.mjs → round-diff.harness.ts} +45 -18
  264. package/skills/paper-pipeline/scripts/round-diff.mjs +1 -1
  265. package/skills/paper-pipeline/scripts/round-diff.test.mjs +1 -1
  266. package/skills/paper-pipeline/scripts/run-mechanical.test.mjs +1 -1
  267. package/skills/paper-pipeline/scripts/status.test.mjs +1 -1
  268. package/skills/plan-paper-timeline/fixtures/{fake-google-calendar.mjs → fake-google-calendar.ts} +101 -70
  269. package/skills/plan-paper-timeline/plan-paper-timeline.effects.harness.mjs +3 -3
  270. package/skills/render-paper/{extract-pdf-facts.harness.mjs → extract-pdf-facts.harness.ts} +34 -9
  271. package/skills/render-paper/render-paper.harness.mjs +2 -2
  272. package/skills/tighten-paper/structure.mjs +1 -1
  273. package/skills/tighten-paper/{structure.test.mjs → structure.test.ts} +4 -3
  274. package/skills/verify-citations/scripts/{bib-authors.cli.test.mjs → bib-authors.cli.test.ts} +14 -4
  275. package/skills/verify-citations/scripts/bib-authors.d.mts +63 -0
  276. package/skills/verify-citations/scripts/bib-authors.mjs +1 -4
  277. package/skills/verify-citations/scripts/{verify-cites.cli.test.mjs → verify-cites.cli.test.ts} +32 -9
  278. package/skills/verify-citations/scripts/verify-cites.d.mts +58 -0
  279. package/src/CLAUDE.md +1 -1
  280. package/src/adapters/banal/index.test.ts +3 -6
  281. package/src/adapters/banal/index.ts +7 -4
  282. package/src/adapters/banal/invocation.test.ts +3 -1
  283. package/src/adapters/banal/invocation.ts +1 -1
  284. package/src/adapters/banal/output.test.ts +1 -1
  285. package/src/adapters/banal/run.test.ts +3 -3
  286. package/src/adapters/banal/xml.test.ts +1 -1
  287. package/src/adapters/curl/download.test.ts +4 -2
  288. package/src/adapters/node/files.io.ts +2 -2
  289. package/src/adapters/node/files.test.ts +3 -1
  290. package/src/adapters/node/process.io.ts +4 -3
  291. package/src/adapters/node/process.test.ts +1 -1
  292. package/src/adapters/node/workspace.test.ts +3 -1
  293. package/src/adapters/pdfjs/fill.test.ts +2 -2
  294. package/src/adapters/pdfjs/fill.ts +14 -7
  295. package/src/adapters/references/index.test.ts +31 -38
  296. package/src/adapters/references/index.ts +26 -58
  297. package/src/adapters/references/pool.ts +3 -5
  298. package/src/adapters/references/reach.io.ts +2 -1
  299. package/src/build-engine.harness.mjs +1 -1
  300. package/src/build-engine.test.ts +2 -2
  301. package/src/build-engine.ts +2 -2
  302. package/src/build.harness.mjs +2 -2
  303. package/src/build.test.ts +15 -14
  304. package/src/build.ts +28 -19
  305. package/src/cli.harness.mjs +6 -6
  306. package/src/cli.test.ts +13 -6
  307. package/src/cli.ts +39 -37
  308. package/src/doctor.harness.mjs +3 -3
  309. package/src/doctor.ts +3 -2
  310. package/src/domain/lookup-cache.ts +21 -11
  311. package/src/domain/result.test.ts +2 -2
  312. package/src/domain/text.test.ts +32 -0
  313. package/src/domain/text.ts +23 -0
  314. package/src/engine.harness.mjs +2 -2
  315. package/src/engine.ts +4 -3
  316. package/src/facts-file.test.ts +9 -7
  317. package/src/facts-file.ts +2 -1
  318. package/src/{hooks-settings.harness.mjs → hooks-settings.harness.ts} +39 -20
  319. package/src/hooks-settings.ts +20 -9
  320. package/src/init.test.ts +12 -9
  321. package/src/init.ts +22 -12
  322. package/src/{latex-log.harness.mjs → latex-log.harness.ts} +14 -14
  323. package/src/{latex-loop.harness.mjs → latex-loop.harness.ts} +64 -36
  324. package/src/latex-loop.ts +3 -3
  325. package/src/{link-skills.harness.mjs → link-skills.harness.ts} +19 -19
  326. package/src/link-skills.test.ts +3 -3
  327. package/src/link-skills.ts +5 -4
  328. package/src/lint-fix.test.ts +4 -6
  329. package/src/linted-set.test.ts +2 -2
  330. package/src/new-paper.harness.mjs +2 -2
  331. package/src/new-paper.test.ts +8 -10
  332. package/src/new-paper.ts +2 -2
  333. package/src/paper-files.test.ts +3 -5
  334. package/src/paper-settings-commands.test.ts +5 -4
  335. package/src/paper-settings.test.ts +5 -5
  336. package/src/paper-settings.ts +8 -9
  337. package/src/{pdf-facts.harness.mjs → pdf-facts.harness.ts} +9 -8
  338. package/src/pdf-facts.test.ts +10 -11
  339. package/src/pdf-facts.ts +2 -1
  340. package/src/{pdf-geometry.harness.mjs → pdf-geometry.harness.ts} +19 -12
  341. package/src/pdf-geometry.ts +3 -1
  342. package/src/presets.ts +3 -2
  343. package/src/references.test.ts +51 -43
  344. package/src/references.ts +14 -7
  345. package/src/{rules-config.harness.mjs → rules-config.harness.ts} +7 -13
  346. package/src/rules-config.ts +7 -4
  347. package/src/rules-off.test.ts +6 -4
  348. package/src/{structure.harness.mjs → structure.harness.ts} +26 -15
  349. package/src/structure.ts +1 -1
  350. package/src/{tex-requirements.harness.mjs → tex-requirements.harness.ts} +50 -22
  351. package/src/tex-requirements.ts +8 -5
  352. package/src/toolchain.harness.mjs +2 -2
  353. package/src/toolchain.test.ts +1 -1
  354. package/src/toolchain.ts +31 -17
  355. package/src/venue-rules.test.ts +62 -64
  356. package/src/venue-rules.ts +1 -1
  357. package/tsconfig.json +6 -3
  358. package/tsconfig.pkg.json +10 -0
  359. package/lib/check.d.mts +0 -17
  360. package/lib/paper-config.d.mts +0 -31
  361. package/scripts/layer-legacy-frozen.d.mts +0 -36
  362. package/scripts/rules-are-content-only.harness.mjs +0 -105
  363. package/scripts/rules-are-content-only.mjs +0 -148
  364. package/scripts/rules-are-content-only.test.mjs +0 -61
  365. package/scripts/test.mjs +0 -44
  366. package/scripts/test.test.mjs +0 -52
  367. /package/bin/{paperlint.test.mjs → paperlint.test.ts} +0 -0
  368. /package/lib/{skill-eval-fixture.mjs → skill-eval-fixture.ts} +0 -0
  369. /package/skills/grade-paper-writing/__snapshots__/{prose-lint.test.mjs.snap → prose-lint.test.ts.snap} +0 -0
  370. /package/skills/osf-artifact-upload/{osf-artifact-upload.harness.mjs → osf-artifact-upload.harness.ts} +0 -0
  371. /package/skills/tighten-paper/__snapshots__/{structure.test.mjs.snap → structure.test.ts.snap} +0 -0
@@ -1,5 +1,5 @@
1
1
  /**
2
- * hooks.harness.mjs — the three shipped hooks, each asserted in BOTH halves.
2
+ * hooks.harness.ts — the three shipped hooks, each asserted in BOTH halves.
3
3
  *
4
4
  * 🔴 WHY "BOTH HALVES" IS THE ORGANISING RULE HERE AND NOT A STYLE. Two of these three are
5
5
  * ADVISORY: silence is their success state. So "it printed nothing" and "it is dead" look
@@ -20,9 +20,9 @@
20
20
  * `checkHookImports`, over the shipped files directly. That is STRICTER than compiling a twin
21
21
  * `.ts` source would be: a twin can drift from its build, and there is no twin here to drift.
22
22
  *
23
- * Run: `npx vigiles test hooks/hooks.harness.mjs`
23
+ * Run: `npx vigiles test hooks/hooks.harness.ts`
24
24
  */
25
- import { runHook } from "vigiles";
25
+ import { runHook, type HookInput, type HookRunResult } from "vigiles";
26
26
  import { checkHookImports } from "vigiles/hook";
27
27
  import {
28
28
  mkdtempSync,
@@ -37,8 +37,9 @@ import {
37
37
  import { tmpdir } from "node:os";
38
38
  import { dirname, join, resolve } from "node:path";
39
39
  import { fileURLToPath } from "node:url";
40
- import { PAPERS_DIR_FIELD } from "../lib/paper-config.mjs";
41
- import { createChecker } from "../lib/check.mjs";
40
+ import { z } from "zod";
41
+ import { PAPERS_DIR_FIELD } from "../lib/paper-config.ts";
42
+ import { createChecker } from "../lib/check.ts";
42
43
 
43
44
  const HOOKS = dirname(fileURLToPath(import.meta.url));
44
45
  const ROOT = resolve(HOOKS, "..");
@@ -64,7 +65,13 @@ const check = createChecker();
64
65
  * installed rather than adjacent. The `node_modules/<pkg>` entry is a symlink to this very
65
66
  * checkout, which is what `npm install` of a local package does anyway.
66
67
  */
67
- const consumer = (block, { papers = "docs/papers", paper = "alpha" } = {}) => {
68
+ const consumer = (
69
+ block: unknown,
70
+ {
71
+ papers = "docs/papers",
72
+ paper = "alpha",
73
+ }: { papers?: string | null; paper?: string } = {},
74
+ ) => {
68
75
  const dir = realpathSync(mkdtempSync(join(tmpdir(), "paperlint-hooks-")));
69
76
  const nm = join(dir, "node_modules");
70
77
  mkdirSync(nm, { recursive: true });
@@ -84,24 +91,28 @@ const consumer = (block, { papers = "docs/papers", paper = "alpha" } = {}) => {
84
91
  };
85
92
 
86
93
  /** `run-program <hook>` exactly as a consumer's settings block invokes it. */
87
- const program = (name) =>
94
+ const program = (name: string) =>
88
95
  `node ${JSON.stringify(CLI)} hook-runtime run-program ${JSON.stringify(join(HOOKS, name + ".hook.mjs"))}`;
89
96
 
90
- const onBash = (command) => ({
97
+ const onBash = (command: string): HookInput => ({
91
98
  hook_event_name: "PreToolUse",
92
99
  tool_name: "Bash",
93
100
  tool_input: { command },
94
101
  });
95
- const onEdit = (file_path) => ({
102
+ const onEdit = (file_path: string): HookInput => ({
96
103
  hook_event_name: "PostToolUse",
97
104
  tool_name: "Edit",
98
105
  tool_input: { file_path },
99
106
  tool_response: {},
100
107
  });
101
- const STOP = { hook_event_name: "Stop", tool_name: "", tool_input: {} };
108
+ const STOP: HookInput = {
109
+ hook_event_name: "Stop",
110
+ tool_name: "",
111
+ tool_input: {},
112
+ };
102
113
 
103
114
  /** Run a hook AS a consumer: cwd and `CLAUDE_PROJECT_DIR` both point at the fixture repo. */
104
- const at = (dir, name, input) =>
115
+ const at = (dir: string, name: string, input: HookInput): HookRunResult =>
105
116
  runHook(program(name), input, { cwd: dir, env: { CLAUDE_PROJECT_DIR: dir } });
106
117
 
107
118
  /**
@@ -116,23 +127,29 @@ const at = (dir, name, input) =>
116
127
  * live case: a session `cd`s into a subtree, and vigiles runs every provider «via execSync in
117
128
  * the hook's cwd».
118
129
  */
119
- const adrift = (dir, name, input) =>
130
+ const adrift = (dir: string, name: string, input: HookInput): HookRunResult =>
120
131
  runHook(program(name), input, {
121
132
  cwd: realpathSync(mkdtempSync(join(tmpdir(), "drift-"))),
122
133
  env: { CLAUDE_PROJECT_DIR: dir },
123
134
  });
124
135
 
125
136
  /** An injected notice, as the model would receive it — not the returned reaction. */
126
- const injected = (r) => {
137
+ const Injected = z.object({
138
+ hookSpecificOutput: z.object({ additionalContext: z.string().nullish() }),
139
+ });
140
+ const injected = (r: HookRunResult): string => {
127
141
  try {
128
- return JSON.parse(r.stdout).hookSpecificOutput.additionalContext ?? "";
142
+ return (
143
+ Injected.parse(JSON.parse(r.stdout)).hookSpecificOutput
144
+ .additionalContext ?? ""
145
+ );
129
146
  } catch {
130
147
  return "";
131
148
  }
132
149
  };
133
150
 
134
- const tmps = [];
135
- const fixture = (...args) => {
151
+ const tmps: string[] = [];
152
+ const fixture = (...args: Parameters<typeof consumer>) => {
136
153
  const d = consumer(...args);
137
154
  tmps.push(d);
138
155
  return d;
@@ -191,10 +208,10 @@ try {
191
208
  const dir = fixture({ [PAPERS_DIR_FIELD]: "docs/papers" });
192
209
  const P = "docs/papers/alpha/paper.md";
193
210
  const T = "docs/papers/alpha/paper.tex";
194
- const deny = (label, cmd) => {
211
+ const deny = (label: string, cmd: string) => {
195
212
  const r = at(dir, "paper-edit-guard", onBash(cmd));
196
213
  check(
197
- `guard BLOCKS ${label} (rc=${r.exitCode})`,
214
+ `guard BLOCKS ${label} (rc=${String(r.exitCode)})`,
198
215
  r.exitCode === 2 && r.blocked,
199
216
  );
200
217
  check(
@@ -202,10 +219,10 @@ try {
202
219
  /Use Edit or Write/.test(r.stderr),
203
220
  );
204
221
  };
205
- const allow = (label, cmd) => {
222
+ const allow = (label: string, cmd: string) => {
206
223
  const r = at(dir, "paper-edit-guard", onBash(cmd));
207
224
  check(
208
- `guard ALLOWS ${label} (rc=${r.exitCode}, ${r.stderr.length}b err)`,
225
+ `guard ALLOWS ${label} (rc=${String(r.exitCode)}, ${String(r.stderr.length)}b err)`,
209
226
  r.exitCode === 0 && !r.blocked,
210
227
  );
211
228
  };
@@ -292,7 +309,7 @@ try {
292
309
  check("drift · guard still blocks a paper write", write.exitCode === 2);
293
310
  const idle = adrift(dir, "paper-edit-guard", onBash("echo hi"));
294
311
  check(
295
- `drift · guard does NOT deny an unrelated command (rc=${idle.exitCode})`,
312
+ `drift · guard does NOT deny an unrelated command (rc=${String(idle.exitCode)})`,
296
313
  idle.exitCode === 0,
297
314
  );
298
315
  const guarded = adrift(
@@ -318,7 +335,7 @@ try {
318
335
  onEdit(`${dir}/writing/drafts/alpha/PIPELINE-STATUS.md`),
319
336
  );
320
337
  check(
321
- `drift · the status-gates hook still runs (rc=${gates.exitCode})`,
338
+ `drift · the status-gates hook still runs (rc=${String(gates.exitCode)})`,
322
339
  gates.exitCode === 0,
323
340
  );
324
341
  }
@@ -381,7 +398,7 @@ try {
381
398
  const dir = fixture({ [PAPERS_DIR_FIELD]: null });
382
399
  const r = at(dir, "paper-edit-guard", harmless);
383
400
  check(
384
- `carrier: "${PAPERS_DIR_FIELD}": null REFUSES (rc=${r.exitCode})`,
401
+ `carrier: "${PAPERS_DIR_FIELD}": null REFUSES (rc=${String(r.exitCode)})`,
385
402
  r.exitCode === 2,
386
403
  );
387
404
  check(
@@ -394,7 +411,7 @@ try {
394
411
  const dir = fixture({ [PAPERS_DIR_FIELD]: "" });
395
412
  const r = at(dir, "paper-edit-guard", harmless);
396
413
  check(
397
- `carrier: "${PAPERS_DIR_FIELD}": "" REFUSES (rc=${r.exitCode})`,
414
+ `carrier: "${PAPERS_DIR_FIELD}": "" REFUSES (rc=${String(r.exitCode)})`,
398
415
  r.exitCode === 2,
399
416
  );
400
417
  check(
@@ -414,7 +431,7 @@ try {
414
431
  );
415
432
  const r = at(dir, "paper-edit-guard", harmless);
416
433
  check(
417
- `carrier: an unparseable paperlint.json REFUSES (rc=${r.exitCode})`,
434
+ `carrier: an unparseable paperlint.json REFUSES (rc=${String(r.exitCode)})`,
418
435
  r.exitCode === 2,
419
436
  );
420
437
  check(
@@ -442,11 +459,11 @@ try {
442
459
  // ═══════════════════════════════════════════════════════════════════════════
443
460
  {
444
461
  const dir = fixture({ [PAPERS_DIR_FIELD]: "docs/papers" });
445
- const lands = (label, p) => {
462
+ const lands = (label: string, p: string) => {
446
463
  const r = at(dir, "paper-skills-nudge", onEdit(p));
447
464
  const ctx = injected(r);
448
465
  check(
449
- `nudge LANDS on ${label} (${r.stdout.length}b stdout)`,
466
+ `nudge LANDS on ${label} (${String(r.stdout.length)}b stdout)`,
450
467
  ctx.length > 0,
451
468
  );
452
469
  check(
@@ -455,10 +472,10 @@ try {
455
472
  );
456
473
  check(`nudge on ${label} exits 0`, r.exitCode === 0);
457
474
  };
458
- const silent = (label, p) => {
475
+ const silent = (label: string, p: string) => {
459
476
  const r = at(dir, "paper-skills-nudge", onEdit(p));
460
477
  check(
461
- `nudge SILENT on ${label} (${r.stdout.length}b stdout, ${r.stderr.length}b stderr)`,
478
+ `nudge SILENT on ${label} (${String(r.stdout.length)}b stdout, ${String(r.stderr.length)}b stderr)`,
462
479
  r.stdout.length === 0 && r.stderr.length === 0 && r.exitCode === 0,
463
480
  );
464
481
  };
@@ -505,15 +522,15 @@ try {
505
522
  // ═══════════════════════════════════════════════════════════════════════════
506
523
  {
507
524
  const dir = fixture({ [PAPERS_DIR_FIELD]: "docs/papers" });
508
- const fires = (label, p) => {
525
+ const fires = (label: string, p: string) => {
509
526
  const r = at(dir, "paper-status-gates", onEdit(p));
510
527
  check(
511
- `gates FIRES on ${label} (${r.stderr.length}b stderr)`,
528
+ `gates FIRES on ${label} (${String(r.stderr.length)}b stderr)`,
512
529
  /fixture verdict line/.test(r.stderr),
513
530
  );
514
531
  check(`gates on ${label} exits 0`, r.exitCode === 0);
515
532
  };
516
- const silent = (label, p) => {
533
+ const silent = (label: string, p: string) => {
517
534
  const r = at(dir, "paper-status-gates", onEdit(p));
518
535
  check(
519
536
  `gates SILENT on ${label}`,
@@ -593,7 +610,7 @@ try {
593
610
  // ═══════════════════════════════════════════════════════════════════════════
594
611
  {
595
612
  const dir = fixture({ [PAPERS_DIR_FIELD]: "docs/papers" });
596
- const sh = (args) =>
613
+ const sh = (args: string) =>
597
614
  runHook(
598
615
  `bash ${JSON.stringify(join(HOOKS, "paper-status-gates.sh"))} ${args}`,
599
616
  STOP,
@@ -643,7 +660,7 @@ try {
643
660
  );
644
661
  }
645
662
 
646
- console.log(`hooks.harness: ${check.count} assertions passed`);
663
+ console.log(`hooks.harness: ${String(check.count)} assertions passed`);
647
664
  } finally {
648
665
  for (const d of tmps) rmSync(d, { recursive: true, force: true });
649
666
  }
@@ -38,7 +38,7 @@
38
38
  * install reverting it.
39
39
  *
40
40
  * The capability surface is therefore asserted HERE, on the artifact that actually executes:
41
- * `hooks.harness.mjs` runs `checkHookImports` over every shipped `.hook.mjs`. That is a stricter
41
+ * `hooks.harness.ts` runs `checkHookImports` over every shipped `.hook.mjs`. That is a stricter
42
42
  * check than compiling a twin `.ts` source would be, because a twin can drift from its build and
43
43
  * this cannot — there is only one file.
44
44
  *
@@ -7,12 +7,8 @@
7
7
  */
8
8
  import assert from "node:assert/strict";
9
9
  import { test } from "vitest";
10
- import {
11
- hookConsumer,
12
- onBash,
13
- runShippedHook,
14
- } from "../test/hook-consumer.mjs";
15
- import { useTempDir } from "../test/support.mjs";
10
+ import { hookConsumer, onBash, runShippedHook } from "../test/hook-consumer.ts";
11
+ import { useTempDir } from "../test/support.ts";
16
12
 
17
13
  const dir = hookConsumer(useTempDir("paper-edit-guard-"), {
18
14
  "papers/alpha/PIPELINE-STATUS.md": "# S\n",
@@ -22,7 +18,7 @@ const GT = String.fromCharCode(62);
22
18
  const P = "papers/alpha/paper.md";
23
19
 
24
20
  /** The guard's exit code for one Bash command: 2 denies, 0 lets it through. */
25
- const guard = (command) =>
21
+ const guard = (command: string): number =>
26
22
  runShippedHook("paper-edit-guard", onBash(command), dir).exitCode;
27
23
 
28
24
  test("a `>` inside quotes, after an escaped quote, or escaped itself is not a redirection", () => {
@@ -28,7 +28,7 @@
28
28
  * load at all, and a consumer-side thin spec that imports this logic is refused by
29
29
  * `vigiles compile` and can therefore never be re-stamped. The full reasoning, with both
30
30
  * measurements, is in that file's header; the capability surface of this one is asserted by
31
- * `hooks.harness.mjs` running `checkHookImports` over the shipped artifact.
31
+ * `hooks.harness.ts` running `checkHookImports` over the shipped artifact.
32
32
  */
33
33
  import {
34
34
  experimental_defineReact,
@@ -5,12 +5,8 @@
5
5
  import assert from "node:assert/strict";
6
6
  import { join } from "node:path";
7
7
  import { test } from "vitest";
8
- import {
9
- hookConsumer,
10
- onEdit,
11
- runShippedHook,
12
- } from "../test/hook-consumer.mjs";
13
- import { useTempDir } from "../test/support.mjs";
8
+ import { hookConsumer, onEdit, runShippedHook } from "../test/hook-consumer.ts";
9
+ import { useTempDir } from "../test/support.ts";
14
10
 
15
11
  const root = useTempDir("paper-skills-nudge-");
16
12
 
@@ -5,12 +5,8 @@
5
5
  import assert from "node:assert/strict";
6
6
  import { join } from "node:path";
7
7
  import { test } from "vitest";
8
- import {
9
- hookConsumer,
10
- onEdit,
11
- runShippedHook,
12
- } from "../test/hook-consumer.mjs";
13
- import { useTempDir } from "../test/support.mjs";
8
+ import { hookConsumer, onEdit, runShippedHook } from "../test/hook-consumer.ts";
9
+ import { useTempDir } from "../test/support.ts";
14
10
 
15
11
  const root = useTempDir("paper-status-gates-");
16
12
  const STATUS = "**Readiness verdict:** ✅ fixture verdict line\n";
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import assert from "node:assert/strict";
10
10
  import { recordCheck } from "vigiles";
11
- import { observeAgentCli, CHARACTERIZED_CLI } from "./agent-cli-version.mjs";
11
+ import { observeAgentCli, CHARACTERIZED_CLI } from "./agent-cli-version.ts";
12
12
 
13
13
  /** A spawner that records how it was called, so the CALL SHAPE is assertable, not just the result. */
14
14
  const spawner = (result) => {
@@ -39,28 +39,42 @@ export const CHARACTERIZED_CLI = "2.1.227";
39
39
  /** What `claude --version` prints: `2.1.273 (Claude Code)`. */
40
40
  const VERSION_IN = /\b(\d+\.\d+\.\d+)\b/;
41
41
 
42
- /**
43
- * @typedef {object} CliObservation
44
- * @property {boolean} present the binary answered `--version` with status 0
45
- * @property {string} raw the full line it printed, trimmed
46
- * @property {string} version the dotted version parsed out of it
47
- * @property {string} characterized the version the assertions were written against
48
- * @property {boolean} drifted observed and characterized differ
49
- * @property {string} note one line naming BOTH numbers, for the report and for failures
50
- */
42
+ /** What one `--version` probe of the agent CLI observed. */
43
+ export interface CliObservation {
44
+ /** The binary answered `--version` with status 0. */
45
+ present: boolean;
46
+ /** The full line it printed, trimmed. */
47
+ raw: string;
48
+ /** The dotted version parsed out of it. */
49
+ version: string;
50
+ /** The version the assertions were written against. */
51
+ characterized: string;
52
+ /** Observed and characterized differ. */
53
+ drifted: boolean;
54
+ /** One line naming BOTH numbers, for the report and for failures. */
55
+ note: string;
56
+ }
57
+
58
+ /** The one process call the probe makes — injected, so every case is stageable. */
59
+ export type SpawnVersion = (
60
+ program: string,
61
+ args: readonly string[],
62
+ options: { encoding: "utf8" },
63
+ ) => {
64
+ readonly status: number | null;
65
+ readonly stdout?: string | null;
66
+ readonly stderr?: string | null;
67
+ };
51
68
 
52
- /**
53
- * @param {object} deps
54
- * @param {Function} deps.spawnSync injected, so every case below is stageable
55
- * @param {string} [deps.program]
56
- * @param {string} [deps.characterized]
57
- * @returns {CliObservation}
58
- */
59
69
  export function observeAgentCli({
60
70
  spawnSync,
61
71
  program = "claude",
62
72
  characterized = CHARACTERIZED_CLI,
63
- }) {
73
+ }: {
74
+ spawnSync: SpawnVersion;
75
+ program?: string;
76
+ characterized?: string;
77
+ }): CliObservation {
64
78
  // 🔴 `encoding: "utf8"` IS THE FIX. With `stdio: "ignore"` — the shape this replaces — the
65
79
  // child's output goes nowhere and `probe.stdout` comes back null, so everything downstream
66
80
  // would be describing a string that was never read.
@@ -85,7 +99,7 @@ export function observeAgentCli({
85
99
  if (version === "")
86
100
  throw new Error(
87
101
  `\`${program} --version\` exited 0 but printed no version on stdout (got ${JSON.stringify(raw)}` +
88
- `${probe.stderr ? `, stderr ${JSON.stringify(String(probe.stderr).trim().slice(0, 200))}` : ""}). ` +
102
+ `${probe.stderr ? `, stderr ${JSON.stringify(probe.stderr.trim().slice(0, 200))}` : ""}). ` +
89
103
  `A presence probe is not an observation: this is the \`stdio: "ignore"\` shape that issue #7 ` +
90
104
  `is about, and a version nobody read must not be reported as one.`,
91
105
  );
package/lib/check.test.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { test } from "vitest";
3
- import { createChecker, failureMessage, renderDetail } from "./check.mjs";
3
+ import { createChecker, failureMessage, renderDetail } from "./check.ts";
4
4
 
5
5
  test("renderDetail: a string verbatim, nothing for empty, a value inspected, a thunk called", () => {
6
6
  assert.deepEqual(
@@ -32,7 +32,7 @@ test("createChecker: counts every call, stays silent on a pass", () => {
32
32
  const printed: string[] = [];
33
33
  const check = createChecker({ log: (s) => printed.push(s) });
34
34
  check("a", true);
35
- check("b", 1 === 1, "unused detail");
35
+ check("b", true, "unused detail");
36
36
  assert.deepEqual({ count: check.count, printed }, { count: 2, printed: [] });
37
37
  });
38
38
 
@@ -40,10 +40,15 @@ test("createChecker: a failure throws AND prints the label with its detail, and
40
40
  const printed: string[] = [];
41
41
  const check = createChecker({ log: (s) => printed.push(s) });
42
42
  // Guards: the detail reaches the reader — the drift this helper replaced dropped it.
43
- assert.throws(() => check("the label", false, { got: 3 }), {
44
- name: "AssertionError",
45
- message: "the label — { got: 3 }",
46
- });
43
+ assert.throws(
44
+ () => {
45
+ check("the label", false, { got: 3 });
46
+ },
47
+ {
48
+ name: "AssertionError",
49
+ message: "the label — { got: 3 }",
50
+ },
51
+ );
47
52
  assert.deepEqual(
48
53
  { count: check.count, printed },
49
54
  { count: 1, printed: ["✗ the label — { got: 3 }"] },
@@ -57,11 +62,11 @@ test("createChecker: a detail thunk is only evaluated on failure", () => {
57
62
  calls++;
58
63
  return "x";
59
64
  });
60
- assert.throws(() =>
65
+ assert.throws(() => {
61
66
  check("fail", false, () => {
62
67
  calls++;
63
68
  return "x";
64
- }),
65
- );
69
+ });
70
+ });
66
71
  assert.equal(calls, 1);
67
72
  });
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The one assertion helper every `*.harness.mjs` uses.
2
+ * The one assertion helper every harness uses.
3
3
  *
4
4
  * Until #52 each harness defined its own `check()`, and the copies had drifted: some printed a
5
5
  * detail on failure and some dropped it, some joined it with " — " and some with a newline, and two
@@ -17,15 +17,27 @@ import assert from "node:assert/strict";
17
17
  import { inspect } from "node:util";
18
18
  import { recordCheck } from "vigiles";
19
19
 
20
+ /** A failure detail: text, any value (rendered with `util.inspect`), or a thunk producing one. */
21
+ export type Detail = unknown;
22
+
23
+ /** `check(label, cond, detail?)`, and how many times it has been called. */
24
+ export interface Check {
25
+ (label: string, cond: unknown, detail?: Detail): void;
26
+ readonly count: number;
27
+ }
28
+
29
+ /** A detail given as a function, so it is only built on failure. */
30
+ const isThunk = (d: Detail): d is () => unknown => typeof d === "function";
31
+
20
32
  /** A failure detail as text: strings verbatim, other values through `util.inspect`, a thunk called. */
21
- export function renderDetail(detail) {
22
- const value = typeof detail === "function" ? detail() : detail;
33
+ export function renderDetail(detail: Detail): string {
34
+ const value: unknown = isThunk(detail) ? detail() : detail;
23
35
  if (value === undefined || value === null || value === "") return "";
24
36
  return typeof value === "string" ? value : inspect(value, { depth: 6 });
25
37
  }
26
38
 
27
39
  /** The label, then the detail: on the same line when it is one line, below it when it is many. */
28
- export function failureMessage(label, detail) {
40
+ export function failureMessage(label: string, detail: Detail): string {
29
41
  const shown = renderDetail(detail);
30
42
  if (!shown) return label;
31
43
  return shown.includes("\n") ? `${label}\n${shown}` : `${label} — ${shown}`;
@@ -35,16 +47,17 @@ export function failureMessage(label, detail) {
35
47
  * A fresh `check(label, cond, detail?)`. `check.count` is how many times it has been called.
36
48
  * `log` is where a failure is printed — stderr unless this module's own test injects one.
37
49
  */
38
- export function createChecker({ log = console.error } = {}) {
39
- let count = 0;
40
- const check = (label, cond, detail) => {
41
- count++;
50
+ export function createChecker({
51
+ log = console.error,
52
+ }: { log?: (line: string) => void } = {}): Check {
53
+ const check = (label: string, cond: unknown, detail?: Detail): void => {
54
+ check.count++;
42
55
  recordCheck();
43
56
  if (cond) return;
44
57
  const message = failureMessage(label, detail);
45
58
  log(`✗ ${message}`);
46
59
  assert.fail(message);
47
60
  };
48
- Object.defineProperty(check, "count", { get: () => count });
61
+ check.count = 0;
49
62
  return check;
50
63
  }
@@ -10,7 +10,7 @@ import { copyFileSync, mkdirSync } from "node:fs";
10
10
  import { dirname, join } from "node:path";
11
11
  import { fileURLToPath } from "node:url";
12
12
  import { test } from "vitest";
13
- import { runNode, useTempDir, writeTree } from "../test/support.mjs";
13
+ import { runNode, useTempDir, writeTree } from "../test/support.ts";
14
14
  import {
15
15
  blankFrontmatter,
16
16
  fences,
@@ -22,7 +22,7 @@ import {
22
22
  stripFences,
23
23
  stripFrontmatter,
24
24
  tables,
25
- } from "./markdown.mjs";
25
+ } from "./markdown.ts";
26
26
 
27
27
  const DOC = [
28
28
  "---", // 0
@@ -157,13 +157,13 @@ writeTree(root, {
157
157
  "import { register } from 'node:module';\n" +
158
158
  "register('data:text/javascript,' + encodeURIComponent(\"export async function resolve(s, c, next) { if (s === 'markdown-it') throw Object.assign(new Error('gone'), { code: 'ERR_MODULE_NOT_FOUND' }); return next(s, c); }\"));\n",
159
159
  "probe.mjs":
160
- `const m = await import(${JSON.stringify(join(dirname(fileURLToPath(import.meta.url)), "markdown.mjs"))});\n` +
160
+ `const m = await import(${JSON.stringify(join(dirname(fileURLToPath(import.meta.url)), "markdown.ts"))});\n` +
161
161
  "const t = '# H\\n\\n```\\nx\\n```\\n';\n" +
162
162
  "console.log(JSON.stringify([m.MD_AVAILABLE(), m.headings(t), m.fences(t), m.stripFences(t), m.paragraphs(t), m.tables(t)]));\n" +
163
163
  "m.requireMarkdown();\n" +
164
164
  "console.log('not reached');\n",
165
165
  "probe-plugin.mjs":
166
- `const m = await import(${JSON.stringify(join(dirname(fileURLToPath(import.meta.url)), "markdown.mjs"))});\n` +
166
+ `const m = await import(${JSON.stringify(join(dirname(fileURLToPath(import.meta.url)), "markdown.ts"))});\n` +
167
167
  'm.requireMarkdown({ from: "file:///home/u/.claude/plugins/cache/paperlint/lib/markdown.mjs" });\n',
168
168
  });
169
169
 
@@ -223,12 +223,12 @@ test("a copy running from a plugin cache without markdown-it is told to install
223
223
  const lib = join(root, ".claude", "plugins", "paperlint", "lib");
224
224
  mkdirSync(lib, { recursive: true });
225
225
  copyFileSync(
226
- join(dirname(fileURLToPath(import.meta.url)), "markdown.mjs"),
227
- join(lib, "markdown.mjs"),
226
+ join(dirname(fileURLToPath(import.meta.url)), "markdown.ts"),
227
+ join(lib, "markdown.ts"),
228
228
  );
229
229
  writeTree(lib, {
230
230
  "probe.mjs":
231
- 'const m = await import("./markdown.mjs");\nm.requireMarkdown();\n',
231
+ 'const m = await import("./markdown.ts");\nm.requireMarkdown();\n',
232
232
  });
233
233
  const r = runNode(join(lib, "probe.mjs"), []);
234
234
  assert.deepEqual(