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
@@ -21,7 +21,7 @@ import {
21
21
  ROOT_ONLY_KEYS,
22
22
  SETTINGS_KEYS,
23
23
  findProjectRoot,
24
- } from "../lib/paper-config.mjs";
24
+ } from "#lib/paper-config";
25
25
  import type { AbsolutePath } from "./domain/paths.ts";
26
26
  import { err, ok, type Result } from "./domain/result.ts";
27
27
  import type { Files } from "./ports/files.ts";
@@ -30,6 +30,7 @@ import {
30
30
  type Parsed,
31
31
  type RuleEntry,
32
32
  } from "./rules-config.ts";
33
+ import { messageOf } from "./domain/text.ts";
33
34
 
34
35
  /** What one paper declares. Every absent field is null. */
35
36
  export interface PaperSettings {
@@ -92,11 +93,13 @@ export function parsePaperSettings(
92
93
  if (!isObject(json)) return err("must be a JSON object");
93
94
  const unknown = Object.keys(json).filter((k) => !KNOWN.includes(k));
94
95
  if (unknown.length > 0)
95
- return err(`unknown key "${unknown[0]}" — known keys: ${KNOWN.join(", ")}`);
96
+ return err(
97
+ `unknown key "${String(unknown[0])}" — known keys: ${KNOWN.join(", ")}`,
98
+ );
96
99
  const project = Object.keys(json).filter((k) => ROOT_ONLY_KEYS.includes(k));
97
100
  if (project.length > 0)
98
101
  return err(
99
- `"${project[0]}" is a project setting — set it in the root ${CONFIG_FILE}, not in a paper's`,
102
+ `"${String(project[0])}" is a project setting — set it in the root ${CONFIG_FILE}, not in a paper's`,
100
103
  );
101
104
  const fields = stringFields(json);
102
105
  if (!fields.ok) return fields;
@@ -123,7 +126,7 @@ function readJson(
123
126
  try {
124
127
  return ok(JSON.parse(new TextDecoder().decode(bytes)));
125
128
  } catch (e) {
126
- return err({ kind: "broken", why: `not JSON (${(e as Error).message})` });
129
+ return err({ kind: "broken", why: `not JSON (${messageOf(e)})` });
127
130
  }
128
131
  }
129
132
 
@@ -200,9 +203,5 @@ export function paperRules(
200
203
  if (settings.rules === null || Array.isArray(settings.rules))
201
204
  return { ok: true, value: null };
202
205
  const where = `${join(paperDir, CONFIG_FILE)} → "rules"`;
203
- return parseRuleEntries(
204
- settings.rules as Readonly<Record<string, unknown>>,
205
- where,
206
- shipped,
207
- );
206
+ return parseRuleEntries(settings.rules, where, shipped);
208
207
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * `pdf-facts.ts` — pdf.js reading REAL PDFs, committed under `fixtures/pdf-facts/`.
3
3
  *
4
- * The pure decisions are tested in `pdf-geometry.harness.mjs` with hand-written words. This file is
4
+ * The pure decisions are tested in `pdf-geometry.harness.ts` with hand-written words. This file is
5
5
  * the other half: that the shell asks pdf.js the right questions, with the right options, and maps
6
6
  * what comes back. The expected numbers are the ones poppler reported for the same files in the
7
7
  * 2026-09-24 measurement (issue #61), so a pass here is agreement with the tool this replaced.
@@ -11,23 +11,24 @@ import { writeFileSync, mkdtempSync, realpathSync, rmSync } from "node:fs";
11
11
  import { tmpdir } from "node:os";
12
12
  import { dirname, join, resolve } from "node:path";
13
13
  import { fileURLToPath } from "node:url";
14
- import { createChecker } from "../lib/check.mjs";
14
+ import { createChecker } from "../lib/check.ts";
15
+ import type { PdfFacts } from "./pdf-facts.ts";
15
16
 
16
17
  const HERE = dirname(fileURLToPath(import.meta.url));
17
18
  const FIX = resolve(HERE, "..", "fixtures", "pdf-facts");
18
- const { readPdf } = await import(join(HERE, "pdf-facts.ts"));
19
- const { classifyLastPage } = await import(join(HERE, "pdf-geometry.ts"));
19
+ const { readPdf } = await import("./pdf-facts.ts");
20
+ const { classifyLastPage } = await import("./pdf-geometry.ts");
20
21
 
21
22
  const check = createChecker();
22
23
 
23
- const read = async (name) => {
24
+ const read = async (name: string): Promise<PdfFacts> => {
24
25
  const r = await readPdf(join(FIX, name));
25
26
  assert.ok(r.ok, `${name} must read: ${JSON.stringify(r)}`);
26
27
  return r.facts;
27
28
  };
28
- const kinds = (facts) =>
29
+ const kinds = (facts: PdfFacts): string[] =>
29
30
  facts.fonts.kind === "drawn" ? facts.fonts.list.map((f) => f.kind) : [];
30
- const named = (facts, name) =>
31
+ const named = (facts: PdfFacts, name: string) =>
31
32
  facts.fonts.kind === "drawn"
32
33
  ? facts.fonts.list.find((f) => f.name === name)
33
34
  : undefined;
@@ -139,7 +140,7 @@ try {
139
140
  const boxes = f.layout.flatMap((p, i) =>
140
141
  p.boxes.map((b) => ({ ...b, page: i + 1 })),
141
142
  );
142
- const chars = (pred) =>
143
+ const chars = (pred: (b: (typeof boxes)[number]) => boolean) =>
143
144
  boxes.filter(pred).reduce((sum, b) => sum + b.text.length, 0);
144
145
  check(
145
146
  "hidden-text: one layout per page, each US letter",
@@ -8,6 +8,7 @@ import assert from "node:assert/strict";
8
8
  import { getResolvedPDFJS } from "unpdf";
9
9
  import { test } from "vitest";
10
10
  import { factsOf, failureOf, rawFontOf } from "./pdf-facts.ts";
11
+ import { present } from "../test/support.ts";
11
12
 
12
13
  const lib = await getResolvedPDFJS();
13
14
  const H = 792;
@@ -23,8 +24,8 @@ interface FakeItem {
23
24
  function page(items: FakeItem[], fonts: Record<string, unknown>) {
24
25
  const objs = new Map(Object.entries(fonts));
25
26
  return {
26
- getOperatorList: async () => ({ fnArray: [], argsArray: [] }),
27
- getTextContent: async () => ({ items }),
27
+ getOperatorList: () => Promise.resolve({ fnArray: [], argsArray: [] }),
28
+ getTextContent: () => Promise.resolve({ items }),
28
29
  // Iterable like pdf.js's `commonObjs`, with its `has`/`get`.
29
30
  commonObjs: {
30
31
  has: (id: string) => objs.has(id),
@@ -42,7 +43,7 @@ function page(items: FakeItem[], fonts: Record<string, unknown>) {
42
43
  const doc = (pages: ReturnType<typeof page>[]) =>
43
44
  ({
44
45
  numPages: pages.length,
45
- getPage: async (i: number) => pages[i - 1],
46
+ getPage: (i: number) => Promise.resolve(pages[i - 1]),
46
47
  }) as never;
47
48
  const item = (
48
49
  fontName: string,
@@ -89,7 +90,7 @@ test("a document with no pages is `empty`; text drawn with no font is refused",
89
90
  lib,
90
91
  );
91
92
  assert.equal(r.ok, false);
92
- assert.equal(!r.ok && r.reason, "zero-fonts-on-text");
93
+ assert.equal(r.reason, "zero-fonts-on-text");
93
94
  });
94
95
 
95
96
  test("metrics, sizes and names fall back when pdf.js gives none", async () => {
@@ -117,7 +118,7 @@ test("metrics, sizes and names fall back when pdf.js gives none", async () => {
117
118
  lib,
118
119
  );
119
120
  assert.equal(r.ok, true);
120
- const boxes = r.ok ? r.facts.layout[0]!.boxes : [];
121
+ const boxes = present(r.facts.layout[0], "a first page").boxes;
121
122
  assert.deepEqual(
122
123
  boxes.map((b) => [b.font, b.size, b.fill]),
123
124
  [
@@ -127,12 +128,10 @@ test("metrics, sizes and names fall back when pdf.js gives none", async () => {
127
128
  ["gone", 10, { kind: "unknown" }],
128
129
  ],
129
130
  );
130
- assert.deepEqual(r.ok ? r.facts.last.words.map((w) => w.text) : [], [
131
- "word",
132
- "word",
133
- "word",
134
- "word",
135
- ]);
131
+ assert.deepEqual(
132
+ r.facts.last.words.map((w) => w.text),
133
+ ["word", "word", "word", "word"],
134
+ );
136
135
  });
137
136
 
138
137
  test("a failure that is not an Error is named `Error` with its text", () => {
package/src/pdf-facts.ts CHANGED
@@ -34,6 +34,7 @@ import {
34
34
  import type { PageLayout, TextBox } from "./domain/page-layout.ts";
35
35
  // eslint-disable-next-line boundaries/dependencies -- legacy layer, moves behind a port in #76
36
36
  import { fillsFor, isUpright } from "./adapters/pdfjs/fill.ts";
37
+ import { messageOf } from "./domain/text.ts";
37
38
 
38
39
  type PdfJs = Awaited<ReturnType<typeof import("unpdf").getResolvedPDFJS>>;
39
40
  type Doc = Awaited<ReturnType<PdfJs["getDocument"]>["promise"]>;
@@ -303,7 +304,7 @@ export const readPdf: PdfReader = async (path) => {
303
304
  try {
304
305
  data = new Uint8Array(readFileSync(path));
305
306
  } catch (e) {
306
- return fail("unreadable", (e as Error).message);
307
+ return fail("unreadable", messageOf(e));
307
308
  }
308
309
  const lib = await loadPdfJs();
309
310
  const task = lib.getDocument({ data, ...PDFJS_OPTIONS });
@@ -6,11 +6,9 @@
6
6
  * on 2026-08-29 (the midpoint cut, the review build, years mistaken for line numbers), ported from
7
7
  * the harness of the function this replaces.
8
8
  */
9
- import { dirname, join } from "node:path";
10
- import { fileURLToPath } from "node:url";
11
- import { createChecker } from "../lib/check.mjs";
9
+ import { createChecker } from "../lib/check.ts";
10
+ import type { RawFont } from "./pdf-geometry.ts";
12
11
 
13
- const HERE = dirname(fileURLToPath(import.meta.url));
14
12
  const {
15
13
  classifyLastPage,
16
14
  wordsOf,
@@ -19,14 +17,23 @@ const {
19
17
  programOf,
20
18
  popplerType,
21
19
  STUB_WORDS,
22
- } = await import(join(HERE, "pdf-geometry.ts"));
20
+ } = await import("./pdf-geometry.ts");
23
21
 
24
22
  const check = createChecker();
25
23
 
24
+ /** A row of stacked words: `n` of them from `top` downwards, `step` apart. */
25
+ interface Row {
26
+ x: number;
27
+ top: number;
28
+ n?: number;
29
+ step?: number;
30
+ text?: string;
31
+ }
32
+
26
33
  const W = 612; // US letter, points
27
34
 
28
35
  /** Words from rows `{x, top, n, step, text}`: `n` words stacked downwards, each 10 pt tall. */
29
- const words = (rows) =>
36
+ const words = (rows: readonly Row[]) =>
30
37
  rows.flatMap(({ x, top, n: count = 1, step = 10, text = "word" }) =>
31
38
  Array.from({ length: count }, (_, i) => ({
32
39
  x0: x,
@@ -36,7 +43,7 @@ const words = (rows) =>
36
43
  text,
37
44
  })),
38
45
  );
39
- const page = (rows, widthPt = W) => ({
46
+ const page = (rows: readonly Row[], widthPt = W) => ({
40
47
  widthPt,
41
48
  heightPt: 792,
42
49
  words: words(rows),
@@ -164,19 +171,19 @@ check(
164
171
  check(
165
172
  "a run splits at whitespace into words with proportional x",
166
173
  ws.length === 2 &&
167
- ws[0].text === "ab" &&
174
+ ws[0]?.text === "ab" &&
168
175
  ws[0].x0 === 100 &&
169
176
  ws[0].x1 === 120 &&
170
- ws[1].x0 === 140,
177
+ ws[1]?.x0 === 140,
171
178
  );
172
179
  check(
173
180
  "a word spans ascent above the baseline to descent below it",
174
- ws[0].y0 === 192 && ws[0].y1 === 202,
181
+ ws[0]?.y0 === 192 && ws[0].y1 === 202,
175
182
  );
176
183
  }
177
184
 
178
185
  // ── fonts ───────────────────────────────────────────────────────────────────────────────
179
- const font = (over) => ({
186
+ const font = (over: Partial<RawFont>): RawFont => ({
180
187
  id: "f1",
181
188
  name: "ABCDEF+LinLibertineT",
182
189
  type: "Type1",
@@ -227,7 +234,7 @@ check(
227
234
  ]);
228
235
  check(
229
236
  "fonts: each font once, in the order pages first drew them",
230
- r.kind === "drawn" && r.list.length === 2 && r.list[1].name === "B",
237
+ r.kind === "drawn" && r.list.length === 2 && r.list[1]?.name === "B",
231
238
  );
232
239
  }
233
240
  {
@@ -89,7 +89,9 @@ function isLineNumber(w: Word, widthPt: number): boolean {
89
89
  */
90
90
  export function classifyLastPage(p: PageText): LastPage {
91
91
  if (!(p.widthPt > 0))
92
- throw new RangeError(`page width must be positive, got ${p.widthPt}`);
92
+ throw new RangeError(
93
+ `page width must be positive, got ${String(p.widthPt)}`,
94
+ );
93
95
  if (p.words.length < STUB_WORDS)
94
96
  return { kind: "stub", words: p.words.length };
95
97
  const lineNumbers = p.words.filter((w) => isLineNumber(w, p.widthPt)).length;
package/src/presets.ts CHANGED
@@ -47,7 +47,8 @@ import {
47
47
  type PaperSettings,
48
48
  type SettingsProblem,
49
49
  } from "./paper-settings.ts";
50
- import { CONFIG_FILE } from "../lib/paper-config.mjs";
50
+ import { CONFIG_FILE } from "#lib/paper-config";
51
+ import { messageOf } from "./domain/text.ts";
51
52
 
52
53
  /** The prefix of a shipped preset's spec. */
53
54
  export const SHIPPED_PREFIX = "paperlint:";
@@ -132,7 +133,7 @@ function readPreset(
132
133
  return err({
133
134
  kind: "broken",
134
135
  file: where.value,
135
- why: (e as Error).message.split("\n").join(" "),
136
+ why: messageOf(e).split("\n").join(" "),
136
137
  });
137
138
  }
138
139
  }
@@ -32,8 +32,8 @@ import {
32
32
  type LookupCache,
33
33
  } from "./domain/lookup-cache.ts";
34
34
  import { referenceRules, REFERENCE_RULE_LEVELS } from "./reference-rules.ts";
35
- // @ts-expect-error — an ESLint language in .mjs, it has no types
36
- import { texLanguage } from "../eslint-rules/latex-language.mjs";
35
+ import { texLanguage } from "../eslint-rules/latex-language.ts";
36
+ import { present } from "../test/support.ts";
37
37
 
38
38
  const PAPER = "/work/papers/p";
39
39
  const TEX = (entries: string) =>
@@ -63,14 +63,16 @@ const verdict = (
63
63
 
64
64
  const checker =
65
65
  (entries: readonly EntryVerdict[]): CheckReferences =>
66
- async (_bib, cache) => ({ check: { kind: "checked", entries }, cache });
67
- const offline: CheckReferences = async (_bib, cache) => ({
68
- check: {
69
- kind: "not-checked",
70
- why: "the citation services cannot be reached (fetch failed)",
71
- },
72
- cache,
73
- });
66
+ (_bib, cache) =>
67
+ Promise.resolve({ check: { kind: "checked", entries }, cache });
68
+ const offline: CheckReferences = (_bib, cache) =>
69
+ Promise.resolve({
70
+ check: {
71
+ kind: "not-checked",
72
+ why: "the citation services cannot be reached (fetch failed)",
73
+ },
74
+ cache,
75
+ });
74
76
 
75
77
  /** The step's context over `files`, with every port it does not use stubbed. */
76
78
  const ctx = (files: ReturnType<typeof memoryFiles>) => ({
@@ -122,7 +124,7 @@ async function lint(files: ReturnType<typeof memoryFiles>, tex: string) {
122
124
  const [res] = await eslint.lintText(tex, {
123
125
  filePath: join(PAPER, "paper.tex"),
124
126
  });
125
- return res!.messages;
127
+ return present(res, "one lint result").messages;
126
128
  }
127
129
 
128
130
  describe("the references build step", () => {
@@ -135,7 +137,9 @@ describe("the references build step", () => {
135
137
  expect(out.ok).toBe(true);
136
138
  const doc = readReferences(files, PAPER);
137
139
  expect(doc?.status).toBe("checked");
138
- expect(doc?.bib.sha256).toBe(bibHash(bibliographyOf(files, PAPER)!));
140
+ expect(doc?.bib.sha256).toBe(
141
+ bibHash(present(bibliographyOf(files, PAPER), "the bibliography")),
142
+ );
139
143
  expect(await lint(files, tex)).toEqual([]);
140
144
  });
141
145
 
@@ -148,13 +152,13 @@ describe("the references build step", () => {
148
152
  expect(msgs.map((m) => [m.ruleId, m.severity])).toEqual([
149
153
  ["paper/refs-checked", 1],
150
154
  ]);
151
- expect(msgs[0]!.message).toMatch(/cannot be reached/);
155
+ expect(msgs[0]?.message).toMatch(/cannot be reached/);
152
156
  });
153
157
 
154
158
  it("a checker that throws is recorded as not checked, not as a failed build", async () => {
155
- const { files, out } = await build(TEX(ENTRIES), async () => {
156
- throw new Error("DBLP exploded");
157
- });
159
+ const { files, out } = await build(TEX(ENTRIES), () =>
160
+ Promise.reject(new Error("DBLP exploded")),
161
+ );
158
162
  expect(out.ok).toBe(true);
159
163
  expect(readReferences(files, PAPER)?.why).toMatch(/DBLP exploded/);
160
164
  });
@@ -167,9 +171,9 @@ const ANSWER: CachedResponse = {
167
171
  /** A checker that fetches `key` unless the cache has it, recording the cache it was handed. */
168
172
  function fetching(key: string) {
169
173
  const seen: LookupCache[] = [];
170
- const check: CheckReferences = async (_bib, cache) => {
174
+ const check: CheckReferences = (_bib, cache) => {
171
175
  seen.push(cache);
172
- return {
176
+ return Promise.resolve({
173
177
  check: { kind: "checked", entries: [verdict("schick2023")] },
174
178
  cache: cache.citations.has(key)
175
179
  ? cache
@@ -177,7 +181,7 @@ function fetching(key: string) {
177
181
  citations: new Map([...cache.citations, [key, ANSWER]]),
178
182
  dblp: cache.dblp,
179
183
  },
180
- };
184
+ });
181
185
  };
182
186
  return { check, seen };
183
187
  }
@@ -210,23 +214,26 @@ describe("the lookup cache — <paper>/repro/references-cache.json", () => {
210
214
  note: "references: 1 checked, 0 failing → _build/references.json; nothing fetched",
211
215
  });
212
216
  // The second build was handed exactly what the first one wrote.
213
- expect(parseLookupCache(written!)).toEqual(ok(f.seen[1]));
217
+ expect(parseLookupCache(present(written, "the written cache"))).toEqual(
218
+ ok(f.seen[1]),
219
+ );
214
220
  expect(cacheText(files)).toBe(written);
215
221
  });
216
222
  });
217
223
 
218
224
  describe("the lookup cache — counting and refusing", () => {
219
225
  it("two new answers are counted as two", async () => {
220
- const two: CheckReferences = async (_bib, cache) => ({
221
- check: { kind: "checked", entries: [verdict("schick2023")] },
222
- cache: {
223
- citations: new Map([
224
- ["a", ANSWER],
225
- ["b", ANSWER],
226
- ]),
227
- dblp: cache.dblp,
228
- },
229
- });
226
+ const two: CheckReferences = (_bib, cache) =>
227
+ Promise.resolve({
228
+ check: { kind: "checked", entries: [verdict("schick2023")] },
229
+ cache: {
230
+ citations: new Map([
231
+ ["a", ANSWER],
232
+ ["b", ANSWER],
233
+ ]),
234
+ dblp: cache.dblp,
235
+ },
236
+ });
230
237
  const { out } = await build(TEX(ENTRIES), two);
231
238
  expect(out).toEqual({
232
239
  ok: true,
@@ -271,8 +278,8 @@ describe("the reference rules", () => {
271
278
  );
272
279
  const msgs = await lint(files, tex);
273
280
  expect(msgs.map((m) => m.ruleId)).toEqual(["paper/author-list"]);
274
- expect(msgs[0]!.line).toBe(3);
275
- expect(msgs[0]!.message).toMatch(/`schick2023`.*missing hambro/);
281
+ expect(msgs[0]?.line).toBe(3);
282
+ expect(msgs[0]?.message).toMatch(/`schick2023`.*missing hambro/);
276
283
  });
277
284
 
278
285
  it("an identifier that provably fails → paper/cite-exists on that entry", async () => {
@@ -311,7 +318,7 @@ describe("the reference rules", () => {
311
318
  const files = memoryFiles({});
312
319
  const msgs = await lint(files, TEX(ENTRIES));
313
320
  expect(msgs.map((m) => m.ruleId)).toEqual(["paper/refs-checked"]);
314
- expect(msgs[0]!.message).toMatch(/npx paperlint build/);
321
+ expect(msgs[0]?.message).toMatch(/npx paperlint build/);
315
322
  });
316
323
 
317
324
  it("a paper with no bibliography gets nothing", async () => {
@@ -354,7 +361,7 @@ const record = (
354
361
  files: ReturnType<typeof memoryFiles>,
355
362
  body: Record<string, unknown>,
356
363
  ) => {
357
- const bib = bibliographyOf(files, PAPER)!;
364
+ const bib = present(bibliographyOf(files, PAPER), "the bibliography");
358
365
  files.writeAtomic(
359
366
  referencesPath(PAPER) as AbsolutePath,
360
367
  new TextEncoder().encode(
@@ -374,7 +381,7 @@ describe("the reference rules over a record written by hand", () => {
374
381
  record(files, { status: "not-checked", entries: [] });
375
382
  const msgs = await lint(files, tex);
376
383
  expect(msgs.map((m) => m.ruleId)).toEqual(["paper/refs-checked"]);
377
- expect(msgs[0]!.message).toMatch(/no reason recorded/);
384
+ expect(msgs[0]?.message).toMatch(/no reason recorded/);
378
385
  });
379
386
 
380
387
  it("a verdict without a reason, and one for a key the bibliography lacks, still report", async () => {
@@ -410,7 +417,7 @@ describe("the reference rules over refs.bib, and on other files", () => {
410
417
  expect(msgs.map((m) => [m.ruleId, m.line])).toEqual([
411
418
  ["paper/author-list", 1],
412
419
  ]);
413
- expect(msgs[0]!.message).toMatch(/schick2023/);
420
+ expect(msgs[0]?.message).toMatch(/schick2023/);
414
421
  });
415
422
 
416
423
  it("only paper.tex is judged", () => {
@@ -430,13 +437,14 @@ describe("the lookup cache — refreshed answers", () => {
430
437
  const OLD: CachedResponse = { ...ANSWER, fetched: "2026-08-01" };
431
438
  const put =
432
439
  (answer: CachedResponse): CheckReferences =>
433
- async (_bib, cache) => ({
434
- check: { kind: "checked", entries: [verdict("schick2023")] },
435
- cache: {
436
- citations: new Map([...cache.citations, ["a", answer]]),
437
- dblp: cache.dblp,
438
- },
439
- });
440
+ (_bib, cache) =>
441
+ Promise.resolve({
442
+ check: { kind: "checked", entries: [verdict("schick2023")] },
443
+ cache: {
444
+ citations: new Map([...cache.citations, ["a", answer]]),
445
+ dblp: cache.dblp,
446
+ },
447
+ });
440
448
  const { files } = await build(TEX(ENTRIES), put(OLD));
441
449
  const again = await referencesStep.run({
442
450
  ...ctx(files),
package/src/references.ts CHANGED
@@ -44,8 +44,8 @@ import type {
44
44
  ReferencesCheck,
45
45
  ReferencesRun,
46
46
  } from "./ports/check-references.ts";
47
- // @ts-expect-error — an ESLint rule module in .mjs, it has no types
48
- import { bibRange } from "../eslint-rules/paper-typography.mjs";
47
+ import { bibRange } from "#eslint-rules/paper-typography";
48
+ import { messageOf } from "./domain/text.ts";
49
49
 
50
50
  export const REFERENCES_SCHEMA = 1;
51
51
  export const REFERENCES_FILE = "references.json";
@@ -109,6 +109,15 @@ export const documentOf = (
109
109
  : { status: "not-checked" as const, why: check.why, entries: [] }),
110
110
  });
111
111
 
112
+ /** A parsed `references.json` of this schema, its entries a list. */
113
+ const isReferencesDocument = (d: unknown): d is ReferencesDocument =>
114
+ typeof d === "object" &&
115
+ d !== null &&
116
+ "schema" in d &&
117
+ d.schema === REFERENCES_SCHEMA &&
118
+ "entries" in d &&
119
+ Array.isArray(d.entries);
120
+
112
121
  /** The recorded verdicts, or null when there are none or they do not parse. */
113
122
  export function readReferences(
114
123
  files: Files,
@@ -117,10 +126,8 @@ export function readReferences(
117
126
  const raw = text(files, referencesPath(paperDir));
118
127
  if (raw === null) return null;
119
128
  try {
120
- const d = JSON.parse(raw) as ReferencesDocument;
121
- return d?.schema === REFERENCES_SCHEMA && Array.isArray(d.entries)
122
- ? d
123
- : null;
129
+ const d: unknown = JSON.parse(raw);
130
+ return isReferencesDocument(d) ? d : null;
124
131
  } catch {
125
132
  return null;
126
133
  }
@@ -159,7 +166,7 @@ async function run(
159
166
  try {
160
167
  return await check(bib, cache);
161
168
  } catch (e) {
162
- return { check: { kind: "not-checked", why: (e as Error).message }, cache };
169
+ return { check: { kind: "not-checked", why: messageOf(e) }, cache };
163
170
  }
164
171
  }
165
172
 
@@ -3,24 +3,18 @@
3
3
  * paperlint ships. The end-to-end path (a project's paperlint.json → `paperlint lint`) is in `cli.harness.mjs`;
4
4
  * this pins the parser's output shape and each refusal.
5
5
  */
6
- import { dirname, join } from "node:path";
7
- import { fileURLToPath } from "node:url";
8
- import { createChecker } from "../lib/check.mjs";
6
+ import { createChecker } from "../lib/check.ts";
9
7
 
10
- const HERE = dirname(fileURLToPath(import.meta.url));
11
- const { parseRuleBlocks, shippedRuleIds, unknownKeys } = await import(
12
- join(HERE, "rules-config.ts")
13
- );
8
+ const { parseRuleBlocks, shippedRuleIds, unknownKeys } =
9
+ await import("./rules-config.ts");
14
10
 
15
11
  const check = createChecker();
16
12
 
17
13
  const SHIPPED = new Set(["pdf/last-page-balance", "paper/typography"]);
18
- const parse = (raw) => parseRuleBlocks(raw, "S", SHIPPED, "/consumer");
14
+ const parse = (raw: unknown) => parseRuleBlocks(raw, "S", SHIPPED, "/consumer");
19
15
 
20
- check(
21
- "no `rules` key: no blocks",
22
- parse(undefined).ok && parse(undefined).value.length === 0,
23
- );
16
+ const none = parse(undefined);
17
+ check("no `rules` key: no blocks", none.ok && none.value.length === 0);
24
18
  {
25
19
  const r = parse([
26
20
  {
@@ -62,7 +56,7 @@ check(
62
56
  parse([{ rules: { "paper/typography": 0, "pdf/last-page-balance": "warn" } }])
63
57
  .ok,
64
58
  );
65
- const refusals = [
59
+ const refusals: [unknown, string][] = [
66
60
  ["pdf/last-page-balance", 'S → "rules" must be { "<rule>": "<severity>" }'],
67
61
  [{ a: 1 }, '"a" is not a rule paperlint ships'],
68
62
  [[42], "S → rules[0] must be an object"],
@@ -25,7 +25,7 @@
25
25
  * would need that plugin, which JSON cannot carry. Which rules paperlint ships is read off its own
26
26
  * config (`buildConfig`), not listed a second time.
27
27
  */
28
- import { SETTINGS_KEYS } from "../lib/paper-config.mjs";
28
+ import { SETTINGS_KEYS } from "#lib/paper-config";
29
29
 
30
30
  /** ESLint's severities, in both of its spellings. */
31
31
  export type Severity = "off" | "warn" | "error" | 0 | 1 | 2;
@@ -61,7 +61,7 @@ const isStringList = (v: unknown): v is string[] =>
61
61
 
62
62
  /** A severity, or `[severity, ...options]`. */
63
63
  function parseEntry(v: unknown): RuleEntry | null {
64
- const sev = Array.isArray(v) ? v[0] : v;
64
+ const sev: unknown = Array.isArray(v) ? v[0] : v;
65
65
  if (!SEVERITIES.includes(sev)) return null;
66
66
  return v as RuleEntry;
67
67
  }
@@ -126,7 +126,7 @@ function parseBlock(
126
126
  const extra = Object.keys(o).filter((k) => !BLOCK_KEYS.has(k));
127
127
  if (extra.length)
128
128
  return bad(
129
- `${at}: unknown key "${extra[0]}" — a block takes "files", "ignores" and "rules"`,
129
+ `${at}: unknown key "${String(extra[0])}" — a block takes "files", "ignores" and "rules"`,
130
130
  );
131
131
  const globs = badGlobs(o, at);
132
132
  if (globs) return bad(globs);
@@ -167,7 +167,10 @@ export function parseRuleBlocks(
167
167
  );
168
168
  const out: RuleBlock[] = [];
169
169
  for (const [i, v] of raw.entries()) {
170
- const b = parseBlock(v, `${where} → rules[${i}]`, { shipped, baseDir });
170
+ const b = parseBlock(v, `${where} → rules[${String(i)}]`, {
171
+ shipped,
172
+ baseDir,
173
+ });
171
174
  if (!b.ok) return b;
172
175
  out.push(b.value);
173
176
  }
@@ -9,9 +9,9 @@
9
9
  */
10
10
  import { ESLint, type Linter } from "eslint";
11
11
  import { describe, expect, it } from "vitest";
12
- // @ts-expect-error — the module is .mjs and has no types.
13
- import { texLanguage } from "../eslint-rules/latex-language.mjs";
12
+ import { texLanguage } from "../eslint-rules/latex-language.ts";
14
13
  import { rulePlugins, rulesOff, SHIPPED_RULES } from "./cli.ts";
14
+ import { present } from "../test/support.ts";
15
15
 
16
16
  const PAPER = [
17
17
  "\\documentclass{article}",
@@ -45,7 +45,7 @@ async function lintAsConsumer(
45
45
  const [res] = await eslint.lintText(PAPER, {
46
46
  filePath: "papers/a/paper.tex",
47
47
  });
48
- return res!.messages;
48
+ return present(res, "one lint result").messages;
49
49
  }
50
50
 
51
51
  describe("rulesOff — paperlint's rules, registered and off, for a consumer's own ESLint run", () => {
@@ -100,7 +100,9 @@ describe("rulesOff — paperlint's rules, registered and off, for a consumer's o
100
100
  "// eslint-disable-next-line paper/leading-zero -- nothing to silence\nconst x = 1;\n",
101
101
  { filePath: "src/x.js" },
102
102
  );
103
- expect(res!.messages.map((m) => m.message)).toEqual([
103
+ expect(
104
+ present(res, "one lint result").messages.map((m) => m.message),
105
+ ).toEqual([
104
106
  "Unused eslint-disable directive (no problems were reported from 'paper/leading-zero').",
105
107
  ]);
106
108
  });