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
- * round-diff.harness.mjs — plant the defect each check in `round-diff.mjs` claims to catch, assert
2
+ * round-diff.harness.ts — plant the defect each check in `round-diff.mjs` claims to catch, assert
3
3
  * it says no with the RIGHT WORDS, and assert it stays silent on the clean case. `npx vigiles test`.
4
4
  *
5
5
  * WHAT IS UNDER TEST. `.claude/skills/paper-pipeline/scripts/round-diff.mjs`: a review round may only
@@ -49,6 +49,7 @@ import {
49
49
  import { tmpdir } from "node:os";
50
50
  import { join, dirname } from "node:path";
51
51
  import { fileURLToPath } from "node:url";
52
+ import { z } from "zod";
52
53
  const HERE = dirname(fileURLToPath(import.meta.url));
53
54
 
54
55
  const SCRIPT = join(HERE, "round-diff.mjs");
@@ -60,7 +61,9 @@ const tmp = realpathSync(mkdtempSync(join(tmpdir(), "round-diff-harness-")));
60
61
  // numbered body, a `## References` boundary and an appendix after it. The boundary is load-bearing
61
62
  // — the ratchet must not charge for words moved into an appendix.
62
63
 
63
- const P = (o = {}) => `---
64
+ type Section =
65
+ "abstract" | "s1" | "s2" | "s21" | "s3" | "lim" | "refs" | "appendix";
66
+ const P = (o: Partial<Record<Section, string>> = {}) => `---
64
67
  title: "A fixture"
65
68
  ---
66
69
 
@@ -108,10 +111,16 @@ function fixture({
108
111
  now = null,
109
112
  rounds = {},
110
113
  commitRounds = false,
114
+ }: {
115
+ base?: string;
116
+ now?: string | null;
117
+ rounds?: Record<string, string>;
118
+ commitRounds?: boolean;
111
119
  } = {}) {
112
- const dir = join(tmp, `f${++n}`, "paper-x");
120
+ const dir = join(tmp, `f${String(++n)}`, "paper-x");
113
121
  mkdirSync(dir, { recursive: true });
114
- const g = (...a) => spawnSync("git", a, { cwd: dir, encoding: "utf8" });
122
+ const g = (...a: string[]) =>
123
+ spawnSync("git", a, { cwd: dir, encoding: "utf8" });
115
124
  g("init", "-q", "-b", "main");
116
125
  g("config", "user.email", "h@example.com");
117
126
  g("config", "user.name", "harness");
@@ -150,18 +159,34 @@ const M = ({
150
159
  hedge,
151
160
  closed,
152
161
  base = "__BASE__",
162
+ }: {
163
+ round?: number;
164
+ touches?: readonly string[];
165
+ allows?: readonly string[];
166
+ budget?: number;
167
+ hedge?: number;
168
+ closed?: string;
169
+ base?: string;
153
170
  } = {}) =>
154
171
  `---
155
- round: ${round}
172
+ round: ${String(round)}
156
173
  opened: 2026-08-10
157
174
  base: ${base}
158
175
  touches: [${touches.map((t) => `"${t}"`).join(", ")}]
159
- ${allows ? `allows: [${allows.join(", ")}]\n` : ""}${budget !== undefined ? `budget: ${budget}\n` : ""}${hedge !== undefined ? `hedge-budget: ${hedge}\n` : ""}${closed ? `closed: ${closed}\n` : ""}---
176
+ ${allows ? `allows: [${allows.join(", ")}]\n` : ""}${budget !== undefined ? `budget: ${String(budget)}\n` : ""}${hedge !== undefined ? `hedge-budget: ${String(hedge)}\n` : ""}${closed ? `closed: ${closed}\n` : ""}---
160
177
 
161
- Round ${round}.
178
+ Round ${String(round)}.
162
179
  `;
163
180
 
164
- function run(dir, ...args) {
181
+ /** One finding of the gate's `--json` output, with the two fields these checks read. */
182
+ const Findings = z.array(z.looseObject({ kind: z.string(), msg: z.string() }));
183
+ type Run = {
184
+ findings: z.infer<typeof Findings>;
185
+ kinds: string[];
186
+ text: string;
187
+ };
188
+
189
+ function run(dir: string, ...args: string[]): Run {
165
190
  const json = spawnSync("node", [SCRIPT, dir, "--json", ...args], {
166
191
  encoding: "utf8",
167
192
  });
@@ -169,11 +194,11 @@ function run(dir, ...args) {
169
194
  assert.equal(
170
195
  json.status,
171
196
  0,
172
- `the gate must always exit 0 (advisory); it exited ${json.status}:\n${json.stderr}`,
197
+ `the gate must always exit 0 (advisory); it exited ${String(json.status)}:\n${json.stderr}`,
173
198
  );
174
- let findings;
199
+ let findings: z.infer<typeof Findings>;
175
200
  try {
176
- findings = JSON.parse(json.stdout);
201
+ findings = Findings.parse(JSON.parse(json.stdout));
177
202
  } catch {
178
203
  throw new Error(
179
204
  `--json did not produce JSON.\nstdout:\n${json.stdout}\nstderr:\n${json.stderr}`,
@@ -187,15 +212,16 @@ function run(dir, ...args) {
187
212
  }
188
213
 
189
214
  /** Exactly these kinds, in any order — never `.includes`, which is how a deleted check stays green. */
190
- const only = (r, expected, why) =>
215
+ const only = (r: Run, expected: readonly string[], why: string) => {
191
216
  assert.deepEqual(
192
217
  [...r.kinds].sort(),
193
218
  [...expected].sort(),
194
219
  `${why}\n got: ${JSON.stringify(r.findings, null, 2)}`,
195
220
  );
221
+ };
196
222
 
197
223
  /** The finding of this kind must say this. A kind alone does not prove which branch produced it. */
198
- const msg = (r, kind, needle, why) => {
224
+ const msg = (r: Run, kind: string, needle: string, why: string) => {
199
225
  const f = r.findings.find((x) => x.kind === kind);
200
226
  assert.ok(
201
227
  f,
@@ -452,7 +478,7 @@ const msg = (r, kind, needle, why) => {
452
478
  // paid it back — which is exactly the failure the cumulative check exists for, and exactly what a
453
479
  // per-round gate forgets the moment the round is closed.
454
480
  {
455
- const grow = (k) =>
481
+ const grow = (k: number) =>
456
482
  P({
457
483
  s3:
458
484
  "The direction holds across all four rules. " +
@@ -460,7 +486,8 @@ const msg = (r, kind, needle, why) => {
460
486
  });
461
487
  const dir = join(tmp, "cumulative", "paper-x");
462
488
  mkdirSync(dir, { recursive: true });
463
- const g = (...a) => spawnSync("git", a, { cwd: dir, encoding: "utf8" });
489
+ const g = (...a: string[]) =>
490
+ spawnSync("git", a, { cwd: dir, encoding: "utf8" });
464
491
  g("init", "-q", "-b", "main");
465
492
  g("config", "user.email", "h@e.com");
466
493
  g("config", "user.name", "h");
@@ -468,20 +495,20 @@ const msg = (r, kind, needle, why) => {
468
495
  for (const k of [0, 10, 16]) {
469
496
  writeFileSync(join(dir, "paper.md"), grow(k));
470
497
  g("add", "-A");
471
- g("commit", "-qm", `r${k}`);
498
+ g("commit", "-qm", `r${String(k)}`);
472
499
  shas.push(g("rev-parse", "HEAD").stdout.trim());
473
500
  }
474
501
  writeFileSync(join(dir, "paper.md"), grow(22));
475
502
  mkdirSync(join(dir, "rounds"), { recursive: true });
476
503
  for (const [i, sha] of shas.entries()) {
477
504
  writeFileSync(
478
- join(dir, "rounds", `0${i + 1}.md`),
505
+ join(dir, "rounds", `0${String(i + 1)}.md`),
479
506
  M({
480
507
  round: i + 1,
481
508
  touches: ["3"],
482
509
  budget: 20,
483
510
  base: sha,
484
- closed: i < 2 ? "2026-08-1" + i : undefined,
511
+ ...(i < 2 ? { closed: `2026-08-1${String(i)}` } : {}),
485
512
  }),
486
513
  );
487
514
  }
@@ -66,7 +66,7 @@
66
66
  import { readFileSync, existsSync, readdirSync } from "node:fs";
67
67
  import { spawnSync } from "node:child_process";
68
68
  import { basename, join, resolve, relative } from "node:path";
69
- import { stripFrontmatter, frontmatterBlock } from "../../../lib/markdown.mjs";
69
+ import { stripFrontmatter, frontmatterBlock } from "#lib/markdown";
70
70
  import { isMain } from "./consumer.mjs";
71
71
 
72
72
  // ── the lexicon ──────────────────────────────────────────────────────────────────────────────
@@ -10,7 +10,7 @@ import { chmodSync } 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 { census, check, covers, parseManifest } from "./round-diff.mjs";
15
15
 
16
16
  const SCRIPT = join(dirname(fileURLToPath(import.meta.url)), "round-diff.mjs");
@@ -18,7 +18,7 @@ import {
18
18
  import { dirname, join } from "node:path";
19
19
  import { fileURLToPath } from "node:url";
20
20
  import { afterEach, test, vi } from "vitest";
21
- import { runNode, useTempDir, writeTree } from "../../../test/support.mjs";
21
+ import { runNode, useTempDir, writeTree } from "../../../test/support.ts";
22
22
 
23
23
  const HERE = dirname(fileURLToPath(import.meta.url));
24
24
  const SCRIPT = join(HERE, "run-mechanical.mjs");
@@ -9,7 +9,7 @@ import { rmSync, writeFileSync } from "node:fs";
9
9
  import { dirname, join } from "node:path";
10
10
  import { fileURLToPath } from "node:url";
11
11
  import { afterEach, test, vi } from "vitest";
12
- import { runNode, useTempDir, writeTree } from "../../../test/support.mjs";
12
+ import { runNode, useTempDir, writeTree } from "../../../test/support.ts";
13
13
 
14
14
  const HERE = dirname(fileURLToPath(import.meta.url));
15
15
  const root = useTempDir("status-");
@@ -34,6 +34,20 @@
34
34
  * Calls are appended as JSONL to $FAKE_MCP_LOG.
35
35
  */
36
36
  import { appendFileSync } from "node:fs";
37
+ /** One tool as the real server declares it: which arguments it requires and which it knows. */
38
+ interface ToolSpec {
39
+ readonly description: string;
40
+ readonly required: readonly string[];
41
+ readonly known: readonly string[];
42
+ }
43
+
44
+ /**
45
+ * A JSON object. The narrowing is written out rather than done with zod on purpose: zod's import
46
+ * costs ~45 ms at startup, and the effects harness calls a tool as soon as the session opens — with
47
+ * zod loaded the server was measured still connecting on every run, without it ready on every run.
48
+ */
49
+ type Msg = Readonly<Record<string, unknown>>;
50
+ const isObject = (v: unknown): v is Msg => typeof v === "object" && v !== null;
37
51
 
38
52
  const LOG = process.env.FAKE_MCP_LOG;
39
53
  if (!LOG) {
@@ -44,12 +58,14 @@ if (!LOG) {
44
58
  );
45
59
  process.exit(1);
46
60
  }
61
+ /** The log, known to be set from here on. */
62
+ const LOG_FILE: string = LOG;
47
63
 
48
64
  /**
49
65
  * Transcribed from the live tool definitions, 2026-08-17. `required` is the field
50
66
  * that carries the weight — everything else is here so an unknown key is detectable.
51
67
  */
52
- const TOOLS = {
68
+ const TOOLS: Readonly<Record<string, ToolSpec>> = {
53
69
  create_event: {
54
70
  description: "Creates an event on the given calendar.",
55
71
  required: ["summary", "startTime", "endTime"],
@@ -138,7 +154,7 @@ const TOOLS = {
138
154
  },
139
155
  };
140
156
 
141
- const schemaOf = (spec) => ({
157
+ const schemaOf = (spec: ToolSpec) => ({
142
158
  type: "object",
143
159
  properties: Object.fromEntries(spec.known.map((k) => [k, {}])),
144
160
  required: spec.required,
@@ -147,9 +163,12 @@ const schemaOf = (spec) => ({
147
163
  let created = 0;
148
164
 
149
165
  /** @returns {string|null} the reason this call would be rejected, or null if it is well-formed. */
150
- function reject(name, args) {
151
- const spec = TOOLS[name];
152
- if (!spec) return `no such tool: ${name}`;
166
+ function reject(
167
+ name: unknown,
168
+ args: Readonly<Record<string, unknown>> | null | undefined,
169
+ ): string | null {
170
+ const spec = TOOLS[String(name)];
171
+ if (!spec) return `no such tool: ${String(name)}`;
153
172
  const a = args ?? {};
154
173
  const missing = spec.required.filter(
155
174
  (k) => a[k] === undefined || a[k] === "",
@@ -158,82 +177,94 @@ function reject(name, args) {
158
177
  return `missing required argument(s): ${missing.join(", ")} — the real Google Calendar API rejects this call`;
159
178
  const unknown = Object.keys(a).filter((k) => !spec.known.includes(k));
160
179
  if (unknown.length)
161
- return `unknown argument(s): ${unknown.join(", ")} — not fields of ${name} on the real server`;
180
+ return `unknown argument(s): ${unknown.join(", ")} — not fields of ${String(name)} on the real server`;
162
181
  return null;
163
182
  }
164
183
 
184
+ /** A `tools/call`: logged whatever its fate, then answered with an error or a fake event id. */
185
+ function call(msg: Msg): Record<string, unknown> {
186
+ const params = isObject(msg.params) ? msg.params : undefined;
187
+ const name = params?.name;
188
+ const args = isObject(params?.arguments) ? params.arguments : {};
189
+ const why = reject(name, args);
190
+ // Every call is logged, accepted or not. A rejected call that left no trace
191
+ // would be indistinguishable from a call that never happened.
192
+ appendFileSync(
193
+ LOG_FILE,
194
+ JSON.stringify({ name, args, ok: why === null, why }) + "\n",
195
+ );
196
+ if (why)
197
+ return {
198
+ result: {
199
+ isError: true,
200
+ content: [{ type: "text", text: `INVALID CALL: ${why}` }],
201
+ },
202
+ };
203
+ created += 1;
204
+ return {
205
+ result: {
206
+ content: [
207
+ {
208
+ type: "text",
209
+ text: JSON.stringify({
210
+ id: `evt_fake_${String(created)}`,
211
+ status: "confirmed",
212
+ }),
213
+ },
214
+ ],
215
+ },
216
+ };
217
+ }
218
+
219
+ /** The reply to one message, or `null` for a notification (no id) this server does not handle. */
220
+ function answer(msg: Msg): Record<string, unknown> | null {
221
+ if (msg.method === "initialize")
222
+ return {
223
+ result: {
224
+ protocolVersion: "2024-11-05",
225
+ capabilities: { tools: {} },
226
+ serverInfo: { name: "fake-google-calendar", version: "1.0.0" },
227
+ },
228
+ };
229
+ if (msg.method === "tools/list")
230
+ return {
231
+ result: {
232
+ tools: Object.entries(TOOLS).map(([name, spec]) => ({
233
+ name,
234
+ description: spec.description,
235
+ inputSchema: schemaOf(spec),
236
+ })),
237
+ },
238
+ };
239
+ if (msg.method === "tools/call") return call(msg);
240
+ return msg.id !== undefined ? { result: {} } : null;
241
+ }
242
+
243
+ /** One line of input, parsed; `null` for a line that is not a JSON-RPC message. */
244
+ function parseLine(line: string): Msg | null {
245
+ let json: unknown;
246
+ try {
247
+ json = JSON.parse(line);
248
+ } catch {
249
+ return null;
250
+ }
251
+ return isObject(json) ? json : null;
252
+ }
253
+
165
254
  let buf = "";
166
- process.stdin.on("data", (chunk) => {
167
- buf += chunk;
255
+ process.stdin.on("data", (chunk: Buffer | string) => {
256
+ buf += String(chunk);
168
257
  let nl;
169
258
  while ((nl = buf.indexOf("\n")) >= 0) {
170
259
  const line = buf.slice(0, nl).trim();
171
260
  buf = buf.slice(nl + 1);
172
261
  if (!line) continue;
173
- let msg;
174
- try {
175
- msg = JSON.parse(line);
176
- } catch {
177
- continue;
178
- }
179
- const send = (payload) =>
262
+ const msg = parseLine(line);
263
+ if (msg === null) continue;
264
+ const payload = answer(msg);
265
+ if (payload)
180
266
  process.stdout.write(
181
267
  JSON.stringify({ jsonrpc: "2.0", id: msg.id, ...payload }) + "\n",
182
268
  );
183
-
184
- if (msg.method === "initialize") {
185
- send({
186
- result: {
187
- protocolVersion: "2024-11-05",
188
- capabilities: { tools: {} },
189
- serverInfo: { name: "fake-google-calendar", version: "1.0.0" },
190
- },
191
- });
192
- } else if (msg.method === "tools/list") {
193
- send({
194
- result: {
195
- tools: Object.entries(TOOLS).map(([name, spec]) => ({
196
- name,
197
- description: spec.description,
198
- inputSchema: schemaOf(spec),
199
- })),
200
- },
201
- });
202
- } else if (msg.method === "tools/call") {
203
- const name = msg.params?.name;
204
- const args = msg.params?.arguments ?? {};
205
- const why = reject(name, args);
206
- // Every call is logged, accepted or not. A rejected call that left no trace
207
- // would be indistinguishable from a call that never happened.
208
- appendFileSync(
209
- LOG,
210
- JSON.stringify({ name, args, ok: why === null, why }) + "\n",
211
- );
212
- if (why) {
213
- send({
214
- result: {
215
- isError: true,
216
- content: [{ type: "text", text: `INVALID CALL: ${why}` }],
217
- },
218
- });
219
- } else {
220
- created += 1;
221
- send({
222
- result: {
223
- content: [
224
- {
225
- type: "text",
226
- text: JSON.stringify({
227
- id: `evt_fake_${String(created)}`,
228
- status: "confirmed",
229
- }),
230
- },
231
- ],
232
- },
233
- });
234
- }
235
- } else if (msg.id !== undefined) {
236
- send({ result: {} });
237
- }
238
269
  }
239
270
  });
@@ -60,7 +60,7 @@ import { tmpdir } from "node:os";
60
60
  import { dirname, join, resolve } from "node:path";
61
61
  import { fileURLToPath } from "node:url";
62
62
  import { runHarnessTest, skip } from "vigiles";
63
- import { observeAgentCli } from "../../lib/agent-cli-version.mjs";
63
+ import { observeAgentCli } from "#lib/agent-cli-version";
64
64
  import { DEFAULT_TIMEZONE } from "../paper-pipeline/scripts/consumer.mjs";
65
65
 
66
66
  const HERE = dirname(fileURLToPath(import.meta.url));
@@ -225,7 +225,7 @@ try {
225
225
  // Run A above pins that the calendar surface is absent from a headless session, which
226
226
  // is true and worth knowing and also means the skill's CENTRAL STEP has never been
227
227
  // checked by anything. Run B supplies the surface with a stand-in
228
- // (`fixtures/fake-google-calendar.mjs`) carrying the REAL API's required-field sets,
228
+ // (`fixtures/fake-google-calendar.ts`) carrying the REAL API's required-field sets,
229
229
  // so the step becomes executable for the first time.
230
230
  //
231
231
  // ── WHAT IS UNDER TEST, AND WHY IT IS NOT THE MODEL ─────────────────────────────
@@ -312,7 +312,7 @@ try {
312
312
  mcpServers: {
313
313
  Google_Calendar: {
314
314
  command: process.execPath,
315
- args: [join(HERE, "fixtures", "fake-google-calendar.mjs")],
315
+ args: [join(HERE, "fixtures", "fake-google-calendar.ts")],
316
316
  env: { FAKE_MCP_LOG: callLog },
317
317
  },
318
318
  },
@@ -24,7 +24,29 @@ import {
24
24
  import { tmpdir } from "node:os";
25
25
  import { dirname, join, resolve } from "node:path";
26
26
  import { fileURLToPath } from "node:url";
27
- import { createChecker } from "../../lib/check.mjs";
27
+ import { z } from "zod";
28
+ import { createChecker } from "../../lib/check.ts";
29
+ import type { ScriptResult } from "../../test/support.ts";
30
+
31
+ /** The facts file, as far as these checks read it. */
32
+ const Facts = z.looseObject({
33
+ schema: z.unknown().optional(),
34
+ pdf: z.unknown().optional(),
35
+ venue: z.unknown().optional(),
36
+ kind: z.unknown().optional(),
37
+ geometry_source: z.unknown().optional(),
38
+ columns: z.unknown().optional(),
39
+ fonts: z.unknown().optional(),
40
+ });
41
+ const Fonts = z.array(
42
+ z.looseObject({
43
+ name: z.unknown().optional(),
44
+ embedded: z.unknown().optional(),
45
+ type: z.unknown().optional(),
46
+ }),
47
+ );
48
+ const readFacts = (file: string) =>
49
+ Facts.parse(JSON.parse(readFileSync(file, "utf8")));
28
50
 
29
51
  const HERE = dirname(fileURLToPath(import.meta.url));
30
52
  const SHIM = join(HERE, "extract-pdf-facts.mjs");
@@ -37,7 +59,7 @@ const root = realpathSync(mkdtempSync(join(tmpdir(), "paperlint-extract-")));
37
59
  * Run the shim from `root`, with no banal anywhere unless `env` names one. `HOME` is the temp root:
38
60
  * without it Node falls back to the account's home, where `paperlint toolchain` may have installed banal.
39
61
  */
40
- const shim = (args, env = {}) =>
62
+ const shim = (args: readonly string[], env: Record<string, string> = {}) =>
41
63
  spawnSync(process.execPath, [SHIM, ...args], {
42
64
  cwd: root,
43
65
  encoding: "utf8",
@@ -58,7 +80,8 @@ const shim = (args, env = {}) =>
58
80
  ...env,
59
81
  },
60
82
  });
61
- const said = (r) => `${r.stdout}${r.stderr}`;
83
+ const said = (r: Pick<ScriptResult, "stdout" | "stderr">) =>
84
+ `${r.stdout}${r.stderr}`;
62
85
 
63
86
  try {
64
87
  const paper = join(root, "papers", "mixed");
@@ -92,7 +115,9 @@ try {
92
115
  /banal not found/.test(local.stderr),
93
116
  said(local),
94
117
  );
95
- const facts = JSON.parse(readFileSync(factsFile, "utf8"));
118
+ const facts = readFacts(factsFile);
119
+ const fonts = Fonts.safeParse(facts.fonts);
120
+ const fontList = fonts.success ? fonts.data : [];
96
121
  check(
97
122
  "the facts are schema 2, about paper.pdf, for the venue paperlint.json declares",
98
123
  facts.schema === 2 &&
@@ -103,10 +128,10 @@ try {
103
128
  );
104
129
  check(
105
130
  "a text PDF has fonts",
106
- Array.isArray(facts.fonts) && facts.fonts.length === 4,
131
+ fonts.success && fontList.length === 4,
107
132
  JSON.stringify(facts.fonts),
108
133
  );
109
- const times = facts.fonts.find((f) => f.name === "Times-Roman");
134
+ const times = fontList.find((f) => f.name === "Times-Roman");
110
135
  check(
111
136
  "🔴 Times-Roman — pdffonts `Type 1 Custom no no yes` — is embedded: false (the old reader said true)",
112
137
  times?.embedded === false && times.type === "Type 1",
@@ -114,7 +139,7 @@ try {
114
139
  );
115
140
  check(
116
141
  "the Type 3 font keeps poppler's type spelling, which consumers match on",
117
- facts.fonts.some((f) => f.type === "Type 3"),
142
+ fontList.some((f) => f.type === "Type 3"),
118
143
  );
119
144
 
120
145
  const fake = join(root, "banal.pl");
@@ -123,7 +148,7 @@ try {
123
148
  `print '{"papersize":[792,612],"columns":2,"bodyfontsize":9,"pages":[{}]}';\n`,
124
149
  );
125
150
  const withBanal = shim([paper, "--strict"], { BANAL: fake });
126
- const g = JSON.parse(readFileSync(factsFile, "utf8"));
151
+ const g = readFacts(factsFile);
127
152
  check(
128
153
  "--strict with banal ($BANAL): exit 0, and the geometry is banal's",
129
154
  withBanal.status === 0 && g.geometry_source === "banal" && g.columns === 2,
@@ -176,7 +201,7 @@ try {
176
201
  direct.stdout.startsWith(
177
202
  "✅ draft.pdf → papers/loose/_build/paper.facts.json (",
178
203
  ) &&
179
- JSON.parse(readFileSync(looseFacts, "utf8")).venue === null,
204
+ readFacts(looseFacts).venue === null,
180
205
  said(direct),
181
206
  );
182
207
  // The project directory IS the facts file (nonsense, but an environment can say it): the
@@ -34,7 +34,7 @@ import { spawnSync } from "node:child_process";
34
34
  import { fileURLToPath } from "node:url";
35
35
  import { dirname, join } from "node:path";
36
36
  import { checkSkill } from "../../lib/skill-checks.mjs";
37
- import { papersRoot } from "../../eslint-rules/papers.mjs";
37
+ import { papersRoot } from "#eslint-rules/papers";
38
38
  import {
39
39
  consumerRoot,
40
40
  settingsOf,
@@ -44,7 +44,7 @@ await checkSkill("render-paper");
44
44
 
45
45
  // The TeX package pins that stood here (texlive-fonts-extra, texlive-plain-generic in
46
46
  // ensure-toolchain.sh) moved with the list itself: the venue profiles declare the packages, and
47
- // src/tex-requirements.harness.mjs asserts that every acmart venue declares libertine, inconsolata,
47
+ // src/tex-requirements.harness.ts asserts that every acmart venue declares libertine, inconsolata,
48
48
  // newtx, kastrup (binhex.tex) and fancyhdr — each proved by the file whose absence broke a build.
49
49
 
50
50
  // ── TEXINPUTS: WHERE THE SCRIPTS LOOK FOR `paper-guards.tex` ─────────────────
@@ -97,7 +97,7 @@ import {
97
97
  stripFences,
98
98
  requireMarkdown,
99
99
  stripFrontmatter,
100
- } from "../../lib/markdown.mjs";
100
+ } from "#lib/markdown";
101
101
 
102
102
  // Markup is parsed with a PARSER (`CLAUDE.md`, 2026-08-11). We fail rather than degrade: without a
103
103
  // parser not one section would be found in the paper, `--flags-only` would return 0 findings and
@@ -6,11 +6,12 @@ import assert from "node:assert/strict";
6
6
  import { dirname, join } from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import { expect, test } from "vitest";
9
- import { runNode, useTempDir, writeTree } from "../../test/support.mjs";
9
+ import { runNode, useTempDir, writeTree } from "../../test/support.ts";
10
10
 
11
11
  const SCRIPT = join(dirname(fileURLToPath(import.meta.url)), "structure.mjs");
12
12
  const root = useTempDir("structure-");
13
- const words = (n) => Array.from({ length: n }, (_, i) => `w${i}`).join(" ");
13
+ const words = (n: number) =>
14
+ Array.from({ length: n }, (_, i) => `w${String(i)}`).join(" ");
14
15
 
15
16
  writeTree(root, {
16
17
  // Every row shape: a sub-section before any section, carries notes with a score and a verdict,
@@ -69,7 +70,7 @@ ${words(30)}
69
70
  "body-only.md": `## 1 Intro\n${words(8)}\n\n## 2 Rest\n${words(4)}\n\n## 3 Empty\n`,
70
71
  });
71
72
 
72
- const run = (...args) => {
73
+ const run = (...args: string[]) => {
73
74
  const r = runNode(
74
75
  SCRIPT,
75
76
  args.map((a) => (a.startsWith("--") ? a : join(root, a))),
@@ -9,7 +9,12 @@ import { join } from "node:path";
9
9
  import { dirname } from "node:path";
10
10
  import { fileURLToPath } from "node:url";
11
11
  import { test } from "vitest";
12
- import { runNode, useTempDir, writeTree } from "../../../test/support.mjs";
12
+ import {
13
+ parseJson,
14
+ runNode,
15
+ useTempDir,
16
+ writeTree,
17
+ } from "../../../test/support.ts";
13
18
 
14
19
  const SCRIPT = join(dirname(fileURLToPath(import.meta.url)), "bib-authors.mjs");
15
20
  const root = useTempDir("bib-authors-cli-");
@@ -19,7 +24,12 @@ const DBLP_ANSWERS = {
19
24
  "Swapped Order": [["Ada Lovelace", "Alan Turing"]],
20
25
  "All Good": [["Ada Lovelace"]],
21
26
  };
22
- const bib = (key, author, title, venue = "booktitle = {Proc. X}") =>
27
+ const bib = (
28
+ key: string,
29
+ author: string,
30
+ title: string,
31
+ venue = "booktitle = {Proc. X}",
32
+ ) =>
23
33
  `@inproceedings{${key}, author = {${author}}, title = {${title}}, ${venue}}\n`;
24
34
 
25
35
  writeTree(root, {
@@ -54,7 +64,7 @@ writeTree(root, {
54
64
  "empty/.keep": "",
55
65
  });
56
66
 
57
- const run = (...args) => {
67
+ const run = (...args: string[]) => {
58
68
  const r = runNode(SCRIPT, args, {
59
69
  nodeArgs: ["--import", join(root, "dblp.mjs")],
60
70
  });
@@ -126,7 +136,7 @@ test("a paper directory: the canonical paper.bib is read, and each difference is
126
136
  test("two .tex files and no paper.tex: the first by name, said out loud; its filecontents is read", () => {
127
137
  const r = run(join(root, "twotex"), "--json");
128
138
  assert.deepEqual(
129
- { status: r.status, stderr: r.stderr, report: JSON.parse(r.stdout) },
139
+ { status: r.status, stderr: r.stderr, report: parseJson(r.stdout) },
130
140
  {
131
141
  status: 0,
132
142
  stderr: