paperlint 3.1.3 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (527) hide show
  1. package/.github/workflows/ci.yml +12 -7
  2. package/CLAUDE.md +5 -5
  3. package/CONTRIBUTING.md +34 -5
  4. package/dist/adapters/banal/failure.d.ts +1 -7
  5. package/dist/adapters/banal/failure.d.ts.map +1 -1
  6. package/dist/adapters/banal/failure.js +5 -7
  7. package/dist/adapters/banal/failure.js.map +1 -1
  8. package/dist/adapters/banal/index.d.ts.map +1 -1
  9. package/dist/adapters/banal/index.js +4 -3
  10. package/dist/adapters/banal/index.js.map +1 -1
  11. package/dist/adapters/banal/install.d.ts.map +1 -1
  12. package/dist/adapters/banal/install.js +4 -3
  13. package/dist/adapters/banal/install.js.map +1 -1
  14. package/dist/adapters/banal/invocation.d.ts +10 -5
  15. package/dist/adapters/banal/invocation.d.ts.map +1 -1
  16. package/dist/adapters/banal/invocation.js +13 -1
  17. package/dist/adapters/banal/invocation.js.map +1 -1
  18. package/dist/adapters/banal/locate.d.ts +12 -9
  19. package/dist/adapters/banal/locate.d.ts.map +1 -1
  20. package/dist/adapters/banal/locate.js +19 -12
  21. package/dist/adapters/banal/locate.js.map +1 -1
  22. package/dist/adapters/banal/run.d.ts +10 -1
  23. package/dist/adapters/banal/run.d.ts.map +1 -1
  24. package/dist/adapters/banal/run.js +8 -4
  25. package/dist/adapters/banal/run.js.map +1 -1
  26. package/dist/adapters/banal/settings.d.ts +1 -1
  27. package/dist/adapters/banal/settings.d.ts.map +1 -1
  28. package/dist/adapters/banal/settings.js +2 -1
  29. package/dist/adapters/banal/settings.js.map +1 -1
  30. package/dist/adapters/memory/index.d.ts.map +1 -1
  31. package/dist/adapters/memory/index.js +8 -2
  32. package/dist/adapters/memory/index.js.map +1 -1
  33. package/dist/adapters/node/files.io.d.ts.map +1 -1
  34. package/dist/adapters/node/files.io.js +2 -1
  35. package/dist/adapters/node/files.io.js.map +1 -1
  36. package/dist/adapters/node/process.io.d.ts +5 -7
  37. package/dist/adapters/node/process.io.d.ts.map +1 -1
  38. package/dist/adapters/node/process.io.js +4 -3
  39. package/dist/adapters/node/process.io.js.map +1 -1
  40. package/dist/adapters/node/workspace.io.d.ts.map +1 -1
  41. package/dist/adapters/node/workspace.io.js +4 -2
  42. package/dist/adapters/node/workspace.io.js.map +1 -1
  43. package/dist/adapters/pdfjs/fill.d.ts.map +1 -1
  44. package/dist/adapters/pdfjs/fill.js +13 -7
  45. package/dist/adapters/pdfjs/fill.js.map +1 -1
  46. package/dist/adapters/references/index.d.ts.map +1 -1
  47. package/dist/adapters/references/index.js +8 -7
  48. package/dist/adapters/references/index.js.map +1 -1
  49. package/dist/adapters/references/pool.d.ts.map +1 -1
  50. package/dist/adapters/references/pool.js +4 -5
  51. package/dist/adapters/references/pool.js.map +1 -1
  52. package/dist/adapters/references/reach.io.d.ts.map +1 -1
  53. package/dist/adapters/references/reach.io.js +2 -1
  54. package/dist/adapters/references/reach.io.js.map +1 -1
  55. package/dist/build-engine.d.ts +1 -1
  56. package/dist/build-engine.d.ts.map +1 -1
  57. package/dist/build-engine.js +10 -3
  58. package/dist/build-engine.js.map +1 -1
  59. package/dist/build.d.ts +2 -3
  60. package/dist/build.d.ts.map +1 -1
  61. package/dist/build.js +19 -17
  62. package/dist/build.js.map +1 -1
  63. package/dist/caller-path.d.ts +3 -0
  64. package/dist/caller-path.d.ts.map +1 -0
  65. package/dist/caller-path.js +13 -0
  66. package/dist/caller-path.js.map +1 -0
  67. package/dist/cli.d.ts +10 -17
  68. package/dist/cli.d.ts.map +1 -1
  69. package/dist/cli.js +93 -46
  70. package/dist/cli.js.map +1 -1
  71. package/dist/doctor.d.ts +12 -3
  72. package/dist/doctor.d.ts.map +1 -1
  73. package/dist/doctor.js +5 -3
  74. package/dist/doctor.js.map +1 -1
  75. package/dist/domain/lookup-cache.d.ts.map +1 -1
  76. package/dist/domain/lookup-cache.js +10 -8
  77. package/dist/domain/lookup-cache.js.map +1 -1
  78. package/dist/domain/paths.d.ts +8 -2
  79. package/dist/domain/paths.d.ts.map +1 -1
  80. package/dist/domain/paths.js +12 -1
  81. package/dist/domain/paths.js.map +1 -1
  82. package/dist/domain/ready.d.ts +24 -0
  83. package/dist/domain/ready.d.ts.map +1 -0
  84. package/dist/domain/ready.js +11 -0
  85. package/dist/domain/ready.js.map +1 -0
  86. package/dist/domain/record.d.ts +8 -0
  87. package/dist/domain/record.d.ts.map +1 -0
  88. package/dist/domain/record.js +8 -0
  89. package/dist/domain/record.js.map +1 -0
  90. package/dist/domain/sha256.d.ts.map +1 -1
  91. package/dist/domain/sha256.js +4 -2
  92. package/dist/domain/sha256.js.map +1 -1
  93. package/dist/domain/text.d.ts +12 -0
  94. package/dist/domain/text.d.ts.map +1 -1
  95. package/dist/domain/text.js +17 -0
  96. package/dist/domain/text.js.map +1 -1
  97. package/dist/engine.d.ts +23 -22
  98. package/dist/engine.d.ts.map +1 -1
  99. package/dist/engine.js +4 -3
  100. package/dist/engine.js.map +1 -1
  101. package/dist/eslint-rules/bib-reachable-entry.d.ts +22 -0
  102. package/dist/eslint-rules/bib-reachable-entry.d.ts.map +1 -0
  103. package/dist/eslint-rules/bib-reachable-entry.js +75 -0
  104. package/dist/eslint-rules/bib-reachable-entry.js.map +1 -0
  105. package/dist/eslint-rules/install-path-literals.d.ts +80 -0
  106. package/dist/eslint-rules/install-path-literals.d.ts.map +1 -0
  107. package/dist/eslint-rules/install-path-literals.js +105 -0
  108. package/dist/eslint-rules/install-path-literals.js.map +1 -0
  109. package/dist/eslint-rules/latex-language.d.ts +136 -0
  110. package/dist/eslint-rules/latex-language.d.ts.map +1 -0
  111. package/dist/eslint-rules/latex-language.js +639 -0
  112. package/dist/eslint-rules/latex-language.js.map +1 -0
  113. package/dist/eslint-rules/paper-research-question.d.ts +31 -0
  114. package/dist/eslint-rules/paper-research-question.d.ts.map +1 -0
  115. package/dist/eslint-rules/paper-research-question.js +188 -0
  116. package/dist/eslint-rules/paper-research-question.js.map +1 -0
  117. package/dist/eslint-rules/paper-stages.d.ts +83 -0
  118. package/dist/eslint-rules/paper-stages.d.ts.map +1 -0
  119. package/dist/eslint-rules/paper-stages.js +358 -0
  120. package/dist/eslint-rules/paper-stages.js.map +1 -0
  121. package/dist/eslint-rules/paper-typography.d.ts +74 -0
  122. package/dist/eslint-rules/paper-typography.d.ts.map +1 -0
  123. package/dist/eslint-rules/paper-typography.js +404 -0
  124. package/dist/eslint-rules/paper-typography.js.map +1 -0
  125. package/dist/eslint-rules/papers.d.ts +42 -0
  126. package/dist/eslint-rules/papers.d.ts.map +1 -0
  127. package/dist/eslint-rules/papers.js +145 -0
  128. package/dist/eslint-rules/papers.js.map +1 -0
  129. package/dist/eslint-rules/pdf-last-page-balance.d.ts +72 -0
  130. package/dist/eslint-rules/pdf-last-page-balance.d.ts.map +1 -0
  131. package/dist/eslint-rules/pdf-last-page-balance.js +237 -0
  132. package/dist/eslint-rules/pdf-last-page-balance.js.map +1 -0
  133. package/dist/eslint-rules/review-frontmatter.d.ts +57 -0
  134. package/dist/eslint-rules/review-frontmatter.d.ts.map +1 -0
  135. package/dist/eslint-rules/review-frontmatter.js +126 -0
  136. package/dist/eslint-rules/review-frontmatter.js.map +1 -0
  137. package/dist/eslint-rules/review-frontmatter.schema.json +36 -0
  138. package/dist/eslint-rules/rule-context.d.ts +55 -0
  139. package/dist/eslint-rules/rule-context.d.ts.map +1 -0
  140. package/dist/eslint-rules/rule-context.js +9 -0
  141. package/dist/eslint-rules/rule-context.js.map +1 -0
  142. package/dist/eslint-rules/sibling-frontmatter.d.ts +25 -0
  143. package/dist/eslint-rules/sibling-frontmatter.d.ts.map +1 -0
  144. package/dist/eslint-rules/sibling-frontmatter.js +16 -0
  145. package/dist/eslint-rules/sibling-frontmatter.js.map +1 -0
  146. package/dist/eslint-rules/sibling-frontmatter.schema.json +14 -0
  147. package/dist/eslint-rules/temp-root-realpath.d.ts +71 -0
  148. package/dist/eslint-rules/temp-root-realpath.d.ts.map +1 -0
  149. package/dist/eslint-rules/temp-root-realpath.js +139 -0
  150. package/dist/eslint-rules/temp-root-realpath.js.map +1 -0
  151. package/dist/eslint-rules/tex-build.d.ts +35 -0
  152. package/dist/eslint-rules/tex-build.d.ts.map +1 -0
  153. package/dist/eslint-rules/tex-build.js +313 -0
  154. package/dist/eslint-rules/tex-build.js.map +1 -0
  155. package/dist/facts-file.d.ts.map +1 -1
  156. package/dist/facts-file.js +31 -27
  157. package/dist/facts-file.js.map +1 -1
  158. package/dist/hooks-settings.d.ts +1 -1
  159. package/dist/hooks-settings.d.ts.map +1 -1
  160. package/dist/hooks-settings.js +31 -11
  161. package/dist/hooks-settings.js.map +1 -1
  162. package/dist/init.d.ts +8 -9
  163. package/dist/init.d.ts.map +1 -1
  164. package/dist/init.js +33 -15
  165. package/dist/init.js.map +1 -1
  166. package/dist/latex-loop.d.ts +2 -2
  167. package/dist/latex-loop.d.ts.map +1 -1
  168. package/dist/latex-loop.js +2 -2
  169. package/dist/latex-loop.js.map +1 -1
  170. package/dist/lib/agent-cli-version.d.ts +65 -0
  171. package/dist/lib/agent-cli-version.d.ts.map +1 -0
  172. package/dist/lib/agent-cli-version.js +77 -0
  173. package/dist/lib/agent-cli-version.js.map +1 -0
  174. package/dist/lib/check.d.ts +19 -0
  175. package/dist/lib/check.d.ts.map +1 -0
  176. package/dist/lib/check.js +52 -0
  177. package/dist/lib/check.js.map +1 -0
  178. package/dist/lib/markdown.d.ts +180 -0
  179. package/dist/lib/markdown.d.ts.map +1 -0
  180. package/dist/lib/markdown.js +302 -0
  181. package/dist/lib/markdown.js.map +1 -0
  182. package/dist/lib/paper-config.d.ts +84 -0
  183. package/dist/lib/paper-config.d.ts.map +1 -0
  184. package/dist/lib/paper-config.js +138 -0
  185. package/dist/lib/paper-config.js.map +1 -0
  186. package/dist/lib/skill-eval-fixture.d.ts +29 -0
  187. package/dist/lib/skill-eval-fixture.d.ts.map +1 -0
  188. package/dist/lib/skill-eval-fixture.js +62 -0
  189. package/dist/lib/skill-eval-fixture.js.map +1 -0
  190. package/dist/link-skills.d.ts.map +1 -1
  191. package/dist/link-skills.js +5 -4
  192. package/dist/link-skills.js.map +1 -1
  193. package/dist/new-paper.d.ts.map +1 -1
  194. package/dist/new-paper.js +7 -3
  195. package/dist/new-paper.js.map +1 -1
  196. package/dist/paper-files.d.ts +4 -4
  197. package/dist/paper-files.d.ts.map +1 -1
  198. package/dist/paper-files.js +6 -6
  199. package/dist/paper-files.js.map +1 -1
  200. package/dist/paper-settings.d.ts.map +1 -1
  201. package/dist/paper-settings.js +8 -7
  202. package/dist/paper-settings.js.map +1 -1
  203. package/dist/pdf-facts.d.ts +51 -1
  204. package/dist/pdf-facts.d.ts.map +1 -1
  205. package/dist/pdf-facts.js +25 -16
  206. package/dist/pdf-facts.js.map +1 -1
  207. package/dist/pdf-geometry.d.ts.map +1 -1
  208. package/dist/pdf-geometry.js +3 -4
  209. package/dist/pdf-geometry.js.map +1 -1
  210. package/dist/ports/tool-installer.d.ts +2 -14
  211. package/dist/ports/tool-installer.d.ts.map +1 -1
  212. package/dist/presets.d.ts.map +1 -1
  213. package/dist/presets.js +19 -6
  214. package/dist/presets.js.map +1 -1
  215. package/dist/references.d.ts.map +1 -1
  216. package/dist/references.js +15 -10
  217. package/dist/references.js.map +1 -1
  218. package/dist/rules-config.d.ts +26 -1
  219. package/dist/rules-config.d.ts.map +1 -1
  220. package/dist/rules-config.js +15 -12
  221. package/dist/rules-config.js.map +1 -1
  222. package/dist/structure.js +1 -1
  223. package/dist/structure.js.map +1 -1
  224. package/dist/tex-requirements.d.ts +4 -0
  225. package/dist/tex-requirements.d.ts.map +1 -1
  226. package/dist/tex-requirements.js +22 -5
  227. package/dist/tex-requirements.js.map +1 -1
  228. package/dist/toolchain.d.ts.map +1 -1
  229. package/dist/toolchain.js +21 -16
  230. package/dist/toolchain.js.map +1 -1
  231. package/dist/types.d.ts +5 -3
  232. package/dist/types.d.ts.map +1 -1
  233. package/dist/venue-rules.d.ts.map +1 -1
  234. package/dist/venue-rules.js +21 -8
  235. package/dist/venue-rules.js.map +1 -1
  236. package/docs/configuration.md +2 -2
  237. package/docs/e2e.md +7 -7
  238. package/docs/install.md +1 -1
  239. package/docs/prior-art/README.md +1 -1
  240. package/docs/prior-art/package-location.md +5 -5
  241. package/docs/prior-art/repro/README.md +1 -1
  242. package/docs/prior-art/test-tooling.md +2 -2
  243. package/docs/testing.md +11 -8
  244. package/docs/toolchain.md +1 -1
  245. package/eslint-rules/{bib-reachable-entry.mjs → bib-reachable-entry.ts} +5 -4
  246. package/eslint-rules/install-path-literals.harness.mjs +1 -1
  247. package/eslint-rules/{install-path-literals.mjs → install-path-literals.ts} +18 -10
  248. package/eslint-rules/latex-language.harness.mjs +2 -2
  249. package/eslint-rules/latex-language.test.mjs +7 -1
  250. package/eslint-rules/{latex-language.mjs → latex-language.ts} +297 -109
  251. package/eslint-rules/paper-research-question.harness.mjs +3 -3
  252. package/eslint-rules/{paper-research-question.mjs → paper-research-question.ts} +35 -16
  253. package/eslint-rules/paper-stages.harness.mjs +2 -2
  254. package/eslint-rules/paper-stages.test.mjs +8 -2
  255. package/eslint-rules/{paper-stages.mjs → paper-stages.ts} +76 -31
  256. package/eslint-rules/paper-typography.test.mjs +3 -3
  257. package/eslint-rules/{paper-typography.mjs → paper-typography.ts} +134 -56
  258. package/eslint-rules/papers.harness.mjs +2 -2
  259. package/eslint-rules/{papers.mjs → papers.ts} +7 -4
  260. package/eslint-rules/pdf-last-page-balance.harness.mjs +3 -3
  261. package/eslint-rules/{pdf-last-page-balance.mjs → pdf-last-page-balance.ts} +89 -32
  262. package/eslint-rules/review-frontmatter.test.mjs +2 -2
  263. package/eslint-rules/{review-frontmatter.mjs → review-frontmatter.ts} +38 -16
  264. package/eslint-rules/rule-context.ts +62 -0
  265. package/eslint-rules/{sibling-frontmatter.mjs → sibling-frontmatter.ts} +1 -1
  266. package/eslint-rules/temp-root-realpath.harness.mjs +1 -1
  267. package/eslint-rules/{temp-root-realpath.mjs → temp-root-realpath.ts} +20 -7
  268. package/eslint-rules/tex-build.harness.mjs +2 -2
  269. package/eslint-rules/{tex-build.mjs → tex-build.ts} +6 -5
  270. package/eslint.config.mjs +146 -30
  271. package/fixtures/install-path-literals/clean.fixture.mjs +1 -1
  272. package/fixtures/install-path-literals/clean.md +2 -2
  273. package/fixtures/install-path-literals/defect.fixture.mjs +1 -1
  274. package/fixtures/install-path-literals/defect.md +2 -2
  275. package/fixtures/pdf-facts/README.md +2 -2
  276. package/fixtures/real-markdown-paper/{baseline.mjs → baseline.ts} +31 -10
  277. package/hooks/{hooks.harness.mjs → hooks.harness.ts} +51 -34
  278. package/hooks/paper-edit-guard.hook.mjs +1 -1
  279. package/hooks/{paper-edit-guard.test.mjs → paper-edit-guard.test.ts} +3 -7
  280. package/hooks/paper-skills-nudge.hook.mjs +1 -1
  281. package/hooks/{paper-skills-nudge.test.mjs → paper-skills-nudge.test.ts} +2 -6
  282. package/hooks/paper-status-gates.sh +9 -19
  283. package/hooks/{paper-status-gates.test.mjs → paper-status-gates.test.ts} +2 -6
  284. package/lib/agent-cli-version.harness.mjs +1 -1
  285. package/lib/{agent-cli-version.mjs → agent-cli-version.ts} +32 -18
  286. package/lib/check.test.ts +14 -9
  287. package/lib/{check.mjs → check.ts} +22 -9
  288. package/lib/markdown.test.mjs +7 -7
  289. package/lib/{markdown.mjs → markdown.ts} +68 -29
  290. package/lib/paper-config.harness.mjs +9 -9
  291. package/lib/{paper-config.mjs → paper-config.ts} +20 -19
  292. package/lib/skill-checks.mjs +24 -381
  293. package/lib/skill-checks.test.mjs +64 -116
  294. package/lib/skill-corpus.mjs +10 -251
  295. package/lib/skill-corpus.test.mjs +2 -13
  296. package/lib/skill-eval-kit.mjs +1 -1
  297. package/lib/skill-trigger-cases.mjs +1 -1
  298. package/package.json +42 -10
  299. package/scripts/{check.harness.mjs → check.harness.ts} +111 -10
  300. package/scripts/{check.test.mjs → check.test.ts} +13 -10
  301. package/scripts/{check.mjs → check.ts} +116 -42
  302. package/scripts/coverage-config.test.ts +80 -38
  303. package/scripts/eslint-report-guard.test.mjs +1 -1
  304. package/scripts/harness-api.frozen.json +14 -16
  305. package/scripts/harness-api.test.ts +8 -5
  306. package/scripts/layer-legacy-frozen.test.ts +17 -13
  307. package/scripts/{layer-legacy-frozen.mjs → layer-legacy-frozen.ts} +128 -39
  308. package/scripts/release-config.test.ts +12 -4
  309. package/scripts/rules-see-files.test.mjs +1 -1
  310. package/scripts/test.test.ts +78 -0
  311. package/scripts/test.ts +87 -0
  312. package/skills/analyze-sibling-paper/SKILL.md +1 -39
  313. package/skills/analyze-sibling-paper/SKILL.md.spec.ts +0 -38
  314. package/skills/argument-arc/SKILL.md +2 -36
  315. package/skills/argument-arc/SKILL.md.spec.ts +1 -43
  316. package/skills/build-benchmark/SKILL.md +1 -41
  317. package/skills/build-benchmark/SKILL.md.spec.ts +0 -40
  318. package/skills/camera-ready/SKILL.md +1 -34
  319. package/skills/camera-ready/SKILL.md.spec.ts +0 -33
  320. package/skills/cold-read-diff/SKILL.md +1 -35
  321. package/skills/cold-read-diff/SKILL.md.spec.ts +0 -34
  322. package/skills/draft-paper/SKILL.md +2 -41
  323. package/skills/draft-paper/SKILL.md.spec.ts +1 -50
  324. package/skills/extend-paper/SKILL.md +2 -33
  325. package/skills/extend-paper/SKILL.md.spec.ts +1 -42
  326. package/skills/find-venue/SKILL.md +2 -38
  327. package/skills/find-venue/SKILL.md.spec.ts +1 -47
  328. package/skills/grade-paper-writing/SKILL.md +3 -38
  329. package/skills/grade-paper-writing/SKILL.md.spec.ts +2 -47
  330. package/skills/grade-paper-writing/prose-lint.mjs +3 -4
  331. package/skills/grade-paper-writing/{prose-lint.test.mjs → prose-lint.test.ts} +3 -3
  332. package/skills/harden-paper/SKILL.md +1 -39
  333. package/skills/harden-paper/SKILL.md.spec.ts +0 -38
  334. package/skills/map-prior-work/SKILL.md +1 -35
  335. package/skills/map-prior-work/SKILL.md.spec.ts +0 -34
  336. package/skills/osf-artifact-upload/{osf-artifact-upload.harness.mjs → osf-artifact-upload.harness.ts} +3 -4
  337. package/skills/paper-adversarial-review/SKILL.md +2 -35
  338. package/skills/paper-adversarial-review/SKILL.md.spec.ts +1 -43
  339. package/skills/paper-pipeline/SKILL.md +2 -28
  340. package/skills/paper-pipeline/SKILL.md.spec.ts +1 -27
  341. package/skills/paper-pipeline/description-language.eval.mjs +1 -1
  342. package/skills/paper-pipeline/framing-vs-vocabulary.eval.mjs +1 -1
  343. package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +2 -2
  344. package/skills/paper-pipeline/paper-pipeline.harness.mjs +25 -40
  345. package/skills/paper-pipeline/pipeline-firing.eval.mjs +1 -1
  346. package/skills/paper-pipeline/pipeline-language.eval.mjs +1 -1
  347. package/skills/paper-pipeline/references/acceptance-gate.md +1 -6
  348. package/skills/paper-pipeline/references/pipeline-status-template.md +8 -35
  349. package/skills/paper-pipeline/scripts/README.md +30 -225
  350. package/skills/paper-pipeline/scripts/artifact-coverage.harness.mjs +4 -8
  351. package/skills/paper-pipeline/scripts/artifact-coverage.mjs +1 -1
  352. package/skills/paper-pipeline/scripts/{artifact-coverage.test.mjs → artifact-coverage.test.ts} +2 -2
  353. package/skills/paper-pipeline/scripts/consumer.harness.mjs +6 -104
  354. package/skills/paper-pipeline/scripts/consumer.mjs +24 -109
  355. package/skills/paper-pipeline/scripts/consumer.test.mjs +1 -1
  356. package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +1 -1
  357. package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +1 -4
  358. package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +1 -1
  359. package/skills/paper-pipeline/scripts/generated-code.harness.mjs +2 -3
  360. package/skills/paper-pipeline/scripts/{generated-code.test.mjs → generated-code.test.ts} +3 -3
  361. package/skills/paper-pipeline/scripts/population-map.harness.mjs +3 -3
  362. package/skills/paper-pipeline/scripts/population-map.mjs +1 -4
  363. package/skills/paper-pipeline/scripts/population-map.test.mjs +1 -1
  364. package/skills/paper-pipeline/scripts/{round-diff.harness.mjs → round-diff.harness.ts} +45 -18
  365. package/skills/paper-pipeline/scripts/round-diff.mjs +1 -1
  366. package/skills/paper-pipeline/scripts/round-diff.test.mjs +1 -1
  367. package/skills/paper-status/SKILL.md +2 -42
  368. package/skills/paper-status/SKILL.md.spec.ts +1 -41
  369. package/skills/pc-panel-review/SKILL.md +2 -46
  370. package/skills/pc-panel-review/SKILL.md.spec.ts +1 -45
  371. package/skills/plan-paper-timeline/SKILL.md +2 -36
  372. package/skills/plan-paper-timeline/SKILL.md.spec.ts +0 -36
  373. package/skills/plan-paper-timeline/fixtures/{fake-google-calendar.mjs → fake-google-calendar.ts} +101 -70
  374. package/skills/plan-paper-timeline/plan-paper-timeline.effects.harness.mjs +3 -3
  375. package/skills/render-paper/SKILL.md +1 -37
  376. package/skills/render-paper/SKILL.md.spec.ts +0 -36
  377. package/skills/render-paper/{extract-pdf-facts.harness.mjs → extract-pdf-facts.harness.ts} +34 -9
  378. package/skills/render-paper/render-paper.harness.mjs +2 -2
  379. package/skills/research-ideate/SKILL.md +2 -39
  380. package/skills/research-ideate/SKILL.md.spec.ts +1 -47
  381. package/skills/study-accepted-papers/SKILL.md +1 -34
  382. package/skills/study-accepted-papers/SKILL.md.spec.ts +0 -33
  383. package/skills/submit-paper/SKILL.md +1 -33
  384. package/skills/submit-paper/SKILL.md.spec.ts +0 -32
  385. package/skills/sweep-design-space/SKILL.md +1 -38
  386. package/skills/sweep-design-space/SKILL.md.spec.ts +0 -37
  387. package/skills/tighten-paper/SKILL.md +2 -39
  388. package/skills/tighten-paper/SKILL.md.spec.ts +1 -47
  389. package/skills/tighten-paper/structure.mjs +5 -12
  390. package/skills/tighten-paper/{structure.test.mjs → structure.test.ts} +4 -3
  391. package/skills/verify-citations/SKILL.md +2 -38
  392. package/skills/verify-citations/SKILL.md.spec.ts +1 -37
  393. package/skills/verify-citations/scripts/{bib-authors.cli.test.mjs → bib-authors.cli.test.ts} +14 -4
  394. package/skills/verify-citations/scripts/bib-authors.d.mts +63 -0
  395. package/skills/verify-citations/scripts/bib-authors.mjs +1 -4
  396. package/skills/verify-citations/scripts/{verify-cites.cli.test.mjs → verify-cites.cli.test.ts} +32 -9
  397. package/skills/verify-citations/scripts/verify-cites.d.mts +58 -0
  398. package/src/CLAUDE.md +1 -1
  399. package/src/adapters/banal/failure.ts +9 -10
  400. package/src/adapters/banal/index.test.ts +5 -8
  401. package/src/adapters/banal/index.ts +8 -7
  402. package/src/adapters/banal/install.ts +9 -4
  403. package/src/adapters/banal/invocation.test.ts +23 -9
  404. package/src/adapters/banal/invocation.ts +24 -9
  405. package/src/adapters/banal/locate.test.ts +2 -2
  406. package/src/adapters/banal/locate.ts +27 -21
  407. package/src/adapters/banal/output.test.ts +1 -1
  408. package/src/adapters/banal/run.test.ts +5 -5
  409. package/src/adapters/banal/run.ts +16 -10
  410. package/src/adapters/banal/settings.ts +2 -2
  411. package/src/adapters/banal/xml.test.ts +1 -1
  412. package/src/adapters/curl/download.test.ts +8 -6
  413. package/src/adapters/memory/index.ts +4 -3
  414. package/src/adapters/node/files.io.ts +2 -2
  415. package/src/adapters/node/files.test.ts +8 -4
  416. package/src/adapters/node/process.io.ts +13 -5
  417. package/src/adapters/node/process.test.ts +5 -5
  418. package/src/adapters/node/workspace.io.ts +9 -4
  419. package/src/adapters/node/workspace.test.ts +3 -1
  420. package/src/adapters/pdfjs/fill.test.ts +2 -2
  421. package/src/adapters/pdfjs/fill.ts +14 -7
  422. package/src/adapters/references/index.test.ts +31 -38
  423. package/src/adapters/references/index.ts +33 -60
  424. package/src/adapters/references/pool.ts +3 -5
  425. package/src/adapters/references/reach.io.ts +2 -1
  426. package/src/build-engine.harness.mjs +1 -1
  427. package/src/build-engine.test.ts +13 -8
  428. package/src/build-engine.ts +12 -7
  429. package/src/build.harness.mjs +2 -2
  430. package/src/build.test.ts +24 -24
  431. package/src/build.ts +34 -26
  432. package/src/caller-path.ts +13 -0
  433. package/src/cli.harness.mjs +6 -8
  434. package/src/cli.test.ts +31 -13
  435. package/src/cli.ts +117 -58
  436. package/src/doctor.harness.mjs +3 -3
  437. package/src/doctor.test.ts +1 -1
  438. package/src/doctor.ts +17 -7
  439. package/src/domain/lookup-cache.ts +21 -11
  440. package/src/domain/paths.test.ts +15 -0
  441. package/src/domain/paths.ts +20 -1
  442. package/src/domain/ready.ts +31 -0
  443. package/src/domain/record.test.ts +20 -0
  444. package/src/domain/record.ts +12 -0
  445. package/src/domain/result.test.ts +2 -2
  446. package/src/domain/sha256.ts +5 -3
  447. package/src/domain/text.test.ts +32 -0
  448. package/src/domain/text.ts +23 -0
  449. package/src/engine.harness.mjs +2 -2
  450. package/src/engine.ts +34 -5
  451. package/src/facts-file.test.ts +9 -7
  452. package/src/facts-file.ts +45 -29
  453. package/src/{hooks-settings.harness.mjs → hooks-settings.harness.ts} +56 -20
  454. package/src/hooks-settings.ts +48 -13
  455. package/src/init.test.ts +37 -14
  456. package/src/init.ts +48 -24
  457. package/src/{latex-log.harness.mjs → latex-log.harness.ts} +14 -14
  458. package/src/{latex-loop.harness.mjs → latex-loop.harness.ts} +64 -36
  459. package/src/latex-loop.ts +3 -3
  460. package/src/{link-skills.harness.mjs → link-skills.harness.ts} +19 -19
  461. package/src/link-skills.test.ts +3 -3
  462. package/src/link-skills.ts +5 -4
  463. package/src/lint-fix.test.ts +4 -6
  464. package/src/linted-set.test.ts +2 -2
  465. package/src/new-paper.harness.mjs +9 -25
  466. package/src/new-paper.test.ts +8 -10
  467. package/src/new-paper.ts +9 -5
  468. package/src/paper-files.test.ts +25 -24
  469. package/src/paper-files.ts +16 -27
  470. package/src/paper-settings-commands.test.ts +5 -4
  471. package/src/paper-settings.test.ts +7 -7
  472. package/src/paper-settings.ts +13 -14
  473. package/src/{pdf-facts.harness.mjs → pdf-facts.harness.ts} +9 -8
  474. package/src/pdf-facts.test.ts +25 -15
  475. package/src/pdf-facts.ts +81 -23
  476. package/src/{pdf-geometry.harness.mjs → pdf-geometry.harness.ts} +19 -12
  477. package/src/pdf-geometry.ts +8 -4
  478. package/src/ports/tool-installer.ts +2 -17
  479. package/src/presets.ts +31 -16
  480. package/src/references.test.ts +76 -62
  481. package/src/references.ts +18 -12
  482. package/src/{rules-config.harness.mjs → rules-config.harness.ts} +7 -13
  483. package/src/rules-config.ts +46 -14
  484. package/src/rules-off.test.ts +6 -4
  485. package/src/{structure.harness.mjs → structure.harness.ts} +26 -15
  486. package/src/structure.ts +1 -1
  487. package/src/{tex-requirements.harness.mjs → tex-requirements.harness.ts} +50 -22
  488. package/src/tex-requirements.test.ts +21 -0
  489. package/src/tex-requirements.ts +37 -11
  490. package/src/toolchain.harness.mjs +2 -2
  491. package/src/toolchain.test.ts +10 -6
  492. package/src/toolchain.ts +31 -17
  493. package/src/types.ts +5 -3
  494. package/src/venue-rules.test.ts +70 -68
  495. package/src/venue-rules.ts +22 -12
  496. package/tsconfig.json +6 -3
  497. package/tsconfig.pkg.json +10 -0
  498. package/lib/check.d.mts +0 -17
  499. package/lib/paper-config.d.mts +0 -31
  500. package/scripts/layer-legacy-frozen.d.mts +0 -36
  501. package/scripts/rules-are-content-only.harness.mjs +0 -105
  502. package/scripts/rules-are-content-only.mjs +0 -148
  503. package/scripts/rules-are-content-only.test.mjs +0 -61
  504. package/scripts/test.mjs +0 -44
  505. package/scripts/test.test.mjs +0 -52
  506. package/skills/paper-pipeline/scripts/announce.mjs +0 -67
  507. package/skills/paper-pipeline/scripts/announce.test.mjs +0 -102
  508. package/skills/paper-pipeline/scripts/fixtures/clean/PIPELINE-STATUS.md +0 -51
  509. package/skills/paper-pipeline/scripts/fixtures/dirty/PIPELINE-STATUS.md +0 -52
  510. package/skills/paper-pipeline/scripts/fixtures/dirty/paper.md +0 -6
  511. package/skills/paper-pipeline/scripts/fixtures/scorecard.mjs +0 -71
  512. package/skills/paper-pipeline/scripts/ledger.cli.test.mjs +0 -302
  513. package/skills/paper-pipeline/scripts/ledger.mjs +0 -623
  514. package/skills/paper-pipeline/scripts/ledger.test.mjs +0 -254
  515. package/skills/paper-pipeline/scripts/pipeline-check.cli.test.mjs +0 -474
  516. package/skills/paper-pipeline/scripts/pipeline-check.harness.mjs +0 -328
  517. package/skills/paper-pipeline/scripts/pipeline-check.mjs +0 -753
  518. package/skills/paper-pipeline/scripts/pipeline-edges.mjs +0 -169
  519. package/skills/paper-pipeline/scripts/pipeline-edges.test.mjs +0 -73
  520. package/skills/paper-pipeline/scripts/run-mechanical.mjs +0 -648
  521. package/skills/paper-pipeline/scripts/run-mechanical.test.mjs +0 -468
  522. package/skills/paper-pipeline/scripts/status.mjs +0 -295
  523. package/skills/paper-pipeline/scripts/status.test.mjs +0 -232
  524. /package/bin/{paperlint.test.mjs → paperlint.test.ts} +0 -0
  525. /package/lib/{skill-eval-fixture.mjs → skill-eval-fixture.ts} +0 -0
  526. /package/skills/grade-paper-writing/__snapshots__/{prose-lint.test.mjs.snap → prose-lint.test.ts.snap} +0 -0
  527. /package/skills/tighten-paper/__snapshots__/{structure.test.mjs.snap → structure.test.ts.snap} +0 -0
@@ -9,10 +9,11 @@
9
9
  */
10
10
  import { describe, expect, it } from "vitest";
11
11
  import { ESLint } from "eslint";
12
+ import { eslintConfig } from "./cli.ts";
12
13
  import { join } from "node:path";
13
14
  import { memoryFiles } from "./adapters/memory/index.ts";
14
15
  import { ok } from "./domain/result.ts";
15
- import type { AbsolutePath } from "./domain/paths.ts";
16
+ import { absolutePath } from "./domain/paths.ts";
16
17
  import type {
17
18
  CheckReferences,
18
19
  EntryVerdict,
@@ -32,8 +33,8 @@ import {
32
33
  type LookupCache,
33
34
  } from "./domain/lookup-cache.ts";
34
35
  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";
36
+ import { texLanguage } from "../eslint-rules/latex-language.ts";
37
+ import { present } from "../test/support.ts";
37
38
 
38
39
  const PAPER = "/work/papers/p";
39
40
  const TEX = (entries: string) =>
@@ -63,22 +64,28 @@ const verdict = (
63
64
 
64
65
  const checker =
65
66
  (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
- });
67
+ (_bib, cache) =>
68
+ Promise.resolve({ check: { kind: "checked", entries }, cache });
69
+ const offline: CheckReferences = (_bib, cache) =>
70
+ Promise.resolve({
71
+ check: {
72
+ kind: "not-checked",
73
+ why: "the citation services cannot be reached (fetch failed)",
74
+ },
75
+ cache,
76
+ });
74
77
 
75
78
  /** The step's context over `files`, with every port it does not use stubbed. */
76
79
  const ctx = (files: ReturnType<typeof memoryFiles>) => ({
77
80
  paperDir: PAPER,
78
81
  env: {},
79
- run: (() => ({})) as never,
80
- readPdf: (() => null) as never,
81
- measure: (() => null) as never,
82
+ run: () => ({ status: 0 }),
83
+ readPdf: () => Promise.reject(new Error("the references step reads no PDF")),
84
+ measure: {
85
+ measure: () => {
86
+ throw new Error("the references step measures nothing");
87
+ },
88
+ },
82
89
  files,
83
90
  });
84
91
 
@@ -92,37 +99,31 @@ async function build(tex: string, check: CheckReferences) {
92
99
  return { files, out };
93
100
  }
94
101
 
95
- /**
96
- * The rules as an ESLint plugin. Their context type is the narrow one they read (a custom
97
- * language's SourceCode), which ESLint's JS-centred plugin type does not describe.
98
- */
99
- const asPlugin = (rules: object): ESLint.Plugin => ({ rules }) as ESLint.Plugin;
100
-
101
102
  /** Lint `tex` as the paper's paper.tex against what `files` recorded. */
102
103
  async function lint(files: ReturnType<typeof memoryFiles>, tex: string) {
103
104
  files.writeAtomic(
104
- `${PAPER}/paper.tex` as AbsolutePath,
105
+ absolutePath(`${PAPER}/paper.tex`),
105
106
  new TextEncoder().encode(tex),
106
107
  );
107
108
  const eslint = new ESLint({
108
109
  cwd: "/work",
109
110
  overrideConfigFile: true,
110
- overrideConfig: [
111
+ overrideConfig: eslintConfig([
111
112
  {
112
113
  files: ["**/paper.tex"],
113
114
  plugins: {
114
115
  tex: { languages: { latex: texLanguage } },
115
- paper: asPlugin(referenceRules({ files })),
116
+ paper: { rules: referenceRules({ files }) },
116
117
  },
117
118
  language: "tex/latex",
118
119
  rules: REFERENCE_RULE_LEVELS,
119
120
  },
120
- ],
121
+ ]),
121
122
  });
122
123
  const [res] = await eslint.lintText(tex, {
123
124
  filePath: join(PAPER, "paper.tex"),
124
125
  });
125
- return res!.messages;
126
+ return present(res, "one lint result").messages;
126
127
  }
127
128
 
128
129
  describe("the references build step", () => {
@@ -135,7 +136,9 @@ describe("the references build step", () => {
135
136
  expect(out.ok).toBe(true);
136
137
  const doc = readReferences(files, PAPER);
137
138
  expect(doc?.status).toBe("checked");
138
- expect(doc?.bib.sha256).toBe(bibHash(bibliographyOf(files, PAPER)!));
139
+ expect(doc?.bib.sha256).toBe(
140
+ bibHash(present(bibliographyOf(files, PAPER), "the bibliography")),
141
+ );
139
142
  expect(await lint(files, tex)).toEqual([]);
140
143
  });
141
144
 
@@ -148,13 +151,13 @@ describe("the references build step", () => {
148
151
  expect(msgs.map((m) => [m.ruleId, m.severity])).toEqual([
149
152
  ["paper/refs-checked", 1],
150
153
  ]);
151
- expect(msgs[0]!.message).toMatch(/cannot be reached/);
154
+ expect(msgs[0]?.message).toMatch(/cannot be reached/);
152
155
  });
153
156
 
154
157
  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
- });
158
+ const { files, out } = await build(TEX(ENTRIES), () =>
159
+ Promise.reject(new Error("DBLP exploded")),
160
+ );
158
161
  expect(out.ok).toBe(true);
159
162
  expect(readReferences(files, PAPER)?.why).toMatch(/DBLP exploded/);
160
163
  });
@@ -167,9 +170,9 @@ const ANSWER: CachedResponse = {
167
170
  /** A checker that fetches `key` unless the cache has it, recording the cache it was handed. */
168
171
  function fetching(key: string) {
169
172
  const seen: LookupCache[] = [];
170
- const check: CheckReferences = async (_bib, cache) => {
173
+ const check: CheckReferences = (_bib, cache) => {
171
174
  seen.push(cache);
172
- return {
175
+ return Promise.resolve({
173
176
  check: { kind: "checked", entries: [verdict("schick2023")] },
174
177
  cache: cache.citations.has(key)
175
178
  ? cache
@@ -177,12 +180,12 @@ function fetching(key: string) {
177
180
  citations: new Map([...cache.citations, [key, ANSWER]]),
178
181
  dblp: cache.dblp,
179
182
  },
180
- };
183
+ });
181
184
  };
182
185
  return { check, seen };
183
186
  }
184
187
  const cacheText = (files: ReturnType<typeof memoryFiles>): string | null => {
185
- const b = files.readBytes(lookupCachePath(PAPER) as AbsolutePath);
188
+ const b = files.readBytes(absolutePath(lookupCachePath(PAPER)));
186
189
  return b === null ? null : new TextDecoder().decode(b);
187
190
  };
188
191
 
@@ -210,23 +213,26 @@ describe("the lookup cache — <paper>/repro/references-cache.json", () => {
210
213
  note: "references: 1 checked, 0 failing → _build/references.json; nothing fetched",
211
214
  });
212
215
  // The second build was handed exactly what the first one wrote.
213
- expect(parseLookupCache(written!)).toEqual(ok(f.seen[1]));
216
+ expect(parseLookupCache(present(written, "the written cache"))).toEqual(
217
+ ok(f.seen[1]),
218
+ );
214
219
  expect(cacheText(files)).toBe(written);
215
220
  });
216
221
  });
217
222
 
218
223
  describe("the lookup cache — counting and refusing", () => {
219
224
  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
- });
225
+ const two: CheckReferences = (_bib, cache) =>
226
+ Promise.resolve({
227
+ check: { kind: "checked", entries: [verdict("schick2023")] },
228
+ cache: {
229
+ citations: new Map([
230
+ ["a", ANSWER],
231
+ ["b", ANSWER],
232
+ ]),
233
+ dblp: cache.dblp,
234
+ },
235
+ });
230
236
  const { out } = await build(TEX(ENTRIES), two);
231
237
  expect(out).toEqual({
232
238
  ok: true,
@@ -271,8 +277,8 @@ describe("the reference rules", () => {
271
277
  );
272
278
  const msgs = await lint(files, tex);
273
279
  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/);
280
+ expect(msgs[0]?.line).toBe(3);
281
+ expect(msgs[0]?.message).toMatch(/`schick2023`.*missing hambro/);
276
282
  });
277
283
 
278
284
  it("an identifier that provably fails → paper/cite-exists on that entry", async () => {
@@ -311,7 +317,7 @@ describe("the reference rules", () => {
311
317
  const files = memoryFiles({});
312
318
  const msgs = await lint(files, TEX(ENTRIES));
313
319
  expect(msgs.map((m) => m.ruleId)).toEqual(["paper/refs-checked"]);
314
- expect(msgs[0]!.message).toMatch(/npx paperlint build/);
320
+ expect(msgs[0]?.message).toMatch(/npx paperlint build/);
315
321
  });
316
322
 
317
323
  it("a paper with no bibliography gets nothing", async () => {
@@ -354,9 +360,9 @@ const record = (
354
360
  files: ReturnType<typeof memoryFiles>,
355
361
  body: Record<string, unknown>,
356
362
  ) => {
357
- const bib = bibliographyOf(files, PAPER)!;
363
+ const bib = present(bibliographyOf(files, PAPER), "the bibliography");
358
364
  files.writeAtomic(
359
- referencesPath(PAPER) as AbsolutePath,
365
+ absolutePath(referencesPath(PAPER)),
360
366
  new TextEncoder().encode(
361
367
  JSON.stringify({
362
368
  schema: 1,
@@ -374,7 +380,7 @@ describe("the reference rules over a record written by hand", () => {
374
380
  record(files, { status: "not-checked", entries: [] });
375
381
  const msgs = await lint(files, tex);
376
382
  expect(msgs.map((m) => m.ruleId)).toEqual(["paper/refs-checked"]);
377
- expect(msgs[0]!.message).toMatch(/no reason recorded/);
383
+ expect(msgs[0]?.message).toMatch(/no reason recorded/);
378
384
  });
379
385
 
380
386
  it("a verdict without a reason, and one for a key the bibliography lacks, still report", async () => {
@@ -410,16 +416,23 @@ describe("the reference rules over refs.bib, and on other files", () => {
410
416
  expect(msgs.map((m) => [m.ruleId, m.line])).toEqual([
411
417
  ["paper/author-list", 1],
412
418
  ]);
413
- expect(msgs[0]!.message).toMatch(/schick2023/);
419
+ expect(msgs[0]?.message).toMatch(/schick2023/);
414
420
  });
415
421
 
416
422
  it("only paper.tex is judged", () => {
417
423
  const files = memoryFiles({});
418
424
  const rules = referenceRules({ files });
419
425
  for (const rule of Object.values(rules))
420
- expect(rule.create({ filename: `${PAPER}/notes.tex` } as never)).toEqual(
421
- {},
422
- );
426
+ expect(
427
+ rule.create({
428
+ filename: `${PAPER}/notes.tex`,
429
+ cwd: "/work",
430
+ sourceCode: { getLocFromIndex: () => ({ line: 1, column: 0 }) },
431
+ report: () => {
432
+ throw new Error("a file that is not paper.tex is never reported");
433
+ },
434
+ }),
435
+ ).toEqual({});
423
436
  });
424
437
  });
425
438
 
@@ -430,13 +443,14 @@ describe("the lookup cache — refreshed answers", () => {
430
443
  const OLD: CachedResponse = { ...ANSWER, fetched: "2026-08-01" };
431
444
  const put =
432
445
  (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
- });
446
+ (_bib, cache) =>
447
+ Promise.resolve({
448
+ check: { kind: "checked", entries: [verdict("schick2023")] },
449
+ cache: {
450
+ citations: new Map([...cache.citations, ["a", answer]]),
451
+ dblp: cache.dblp,
452
+ },
453
+ });
440
454
  const { files } = await build(TEX(ENTRIES), put(OLD));
441
455
  const again = await referencesStep.run({
442
456
  ...ctx(files),
package/src/references.ts CHANGED
@@ -28,7 +28,7 @@
28
28
  * a pass would be the counter that counts what it never looked at.
29
29
  */
30
30
  import { join } from "node:path";
31
- import type { AbsolutePath } from "./domain/paths.ts";
31
+ import { callerPath } from "./caller-path.ts";
32
32
  import { sha256Hex } from "./domain/sha256.ts";
33
33
  import type { Files } from "./ports/files.ts";
34
34
  import {
@@ -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";
@@ -60,9 +60,8 @@ export const lookupCachePath = (paperDir: string): string =>
60
60
  export const referencesPath = (paperDir: string): string =>
61
61
  join(paperDir, "_build", REFERENCES_FILE);
62
62
 
63
- const at = (p: string): AbsolutePath => p as AbsolutePath;
64
63
  const text = (files: Files, p: string): string | null => {
65
- const b = files.readBytes(at(p));
64
+ const b = files.readBytes(callerPath(p));
66
65
  return b === null ? null : new TextDecoder().decode(b);
67
66
  };
68
67
 
@@ -109,6 +108,15 @@ export const documentOf = (
109
108
  : { status: "not-checked" as const, why: check.why, entries: [] }),
110
109
  });
111
110
 
111
+ /** A parsed `references.json` of this schema, its entries a list. */
112
+ const isReferencesDocument = (d: unknown): d is ReferencesDocument =>
113
+ typeof d === "object" &&
114
+ d !== null &&
115
+ "schema" in d &&
116
+ d.schema === REFERENCES_SCHEMA &&
117
+ "entries" in d &&
118
+ Array.isArray(d.entries);
119
+
112
120
  /** The recorded verdicts, or null when there are none or they do not parse. */
113
121
  export function readReferences(
114
122
  files: Files,
@@ -117,10 +125,8 @@ export function readReferences(
117
125
  const raw = text(files, referencesPath(paperDir));
118
126
  if (raw === null) return null;
119
127
  try {
120
- const d = JSON.parse(raw) as ReferencesDocument;
121
- return d?.schema === REFERENCES_SCHEMA && Array.isArray(d.entries)
122
- ? d
123
- : null;
128
+ const d: unknown = JSON.parse(raw);
129
+ return isReferencesDocument(d) ? d : null;
124
130
  } catch {
125
131
  return null;
126
132
  }
@@ -159,7 +165,7 @@ async function run(
159
165
  try {
160
166
  return await check(bib, cache);
161
167
  } catch (e) {
162
- return { check: { kind: "not-checked", why: (e as Error).message }, cache };
168
+ return { check: { kind: "not-checked", why: messageOf(e) }, cache };
163
169
  }
164
170
  }
165
171
 
@@ -179,7 +185,7 @@ export async function recordReferences(
179
185
  const record = (c: ReferencesCheck): ReferencesDocument => {
180
186
  const doc = documentOf(bib, c);
181
187
  files.writeAtomic(
182
- at(referencesPath(paperDir)),
188
+ callerPath(referencesPath(paperDir)),
183
189
  new TextEncoder().encode(`${JSON.stringify(doc, null, 2)}\n`),
184
190
  );
185
191
  return doc;
@@ -199,7 +205,7 @@ export async function recordReferences(
199
205
  const fetched = newAnswers(cache.value, result.cache);
200
206
  if (fetched > 0)
201
207
  files.writeAtomic(
202
- at(lookupCachePath(paperDir)),
208
+ callerPath(lookupCachePath(paperDir)),
203
209
  new TextEncoder().encode(serializeLookupCache(result.cache)),
204
210
  );
205
211
  const bad = doc.entries.filter(
@@ -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;
@@ -39,11 +39,40 @@ export interface RuleBlock {
39
39
  readonly rules: Readonly<Record<string, RuleEntry>>;
40
40
  }
41
41
 
42
+ /**
43
+ * A plugin as a config block registers it, typed to what paperlint reads of it: its rules, and the
44
+ * languages it brings (`tex` brings LaTeX).
45
+ */
46
+ export interface ConfigPlugin {
47
+ readonly rules?: Readonly<Record<string, unknown>>;
48
+ readonly languages?: Readonly<Record<string, unknown>>;
49
+ }
50
+
51
+ /**
52
+ * An ESLint config block as paperlint builds and reads one. Not `Linter.Config`: ESLint types a
53
+ * plugin's rule as one that runs on ANY language, and paperlint's rules are written for the language
54
+ * they read (LaTeX, markdown), so none of them is assignable to it. ESLint checks a block when it
55
+ * loads it; `eslintConfig` in cli.ts is the one place these blocks become `Linter.Config`.
56
+ */
57
+ export interface ConfigBlock {
58
+ readonly name?: string;
59
+ readonly basePath?: string;
60
+ readonly files?: readonly (string | readonly string[])[];
61
+ readonly ignores?: readonly string[];
62
+ readonly plugins?: Readonly<Record<string, ConfigPlugin>>;
63
+ readonly language?: string;
64
+ readonly languageOptions?: Readonly<Record<string, unknown>>;
65
+ readonly linterOptions?: Readonly<Record<string, unknown>>;
66
+ readonly rules?: Readonly<Record<string, unknown>>;
67
+ }
68
+
42
69
  export type Parsed<T> =
43
70
  | { readonly ok: true; readonly value: T }
44
71
  | { readonly ok: false; readonly error: string };
45
72
 
46
- const SEVERITIES: readonly unknown[] = ["off", "warn", "error", 0, 1, 2];
73
+ const SEVERITIES: readonly Severity[] = ["off", "warn", "error", 0, 1, 2];
74
+ const isSeverity = (v: unknown): v is Severity =>
75
+ SEVERITIES.some((s) => s === v);
47
76
 
48
77
  const BLOCK_KEYS = new Set(["files", "ignores", "rules"]);
49
78
 
@@ -61,9 +90,10 @@ const isStringList = (v: unknown): v is string[] =>
61
90
 
62
91
  /** A severity, or `[severity, ...options]`. */
63
92
  function parseEntry(v: unknown): RuleEntry | null {
64
- const sev = Array.isArray(v) ? v[0] : v;
65
- if (!SEVERITIES.includes(sev)) return null;
66
- return v as RuleEntry;
93
+ const entry: readonly unknown[] = Array.isArray(v) ? v : [v];
94
+ const [sev, ...options] = entry;
95
+ if (!isSeverity(sev)) return null;
96
+ return Array.isArray(v) ? [sev, ...options] : sev;
67
97
  }
68
98
 
69
99
  /**
@@ -105,8 +135,8 @@ function badGlobs(o: Record<string, unknown>, at: string): string | null {
105
135
 
106
136
  /** The block's `files`/`ignores`, present only when given. */
107
137
  const globsOf = (o: Record<string, unknown>) => ({
108
- ...(o["files"] ? { files: o["files"] as string[] } : {}),
109
- ...(o["ignores"] ? { ignores: o["ignores"] as string[] } : {}),
138
+ ...(isStringList(o["files"]) ? { files: o["files"] } : {}),
139
+ ...(isStringList(o["ignores"]) ? { ignores: o["ignores"] } : {}),
110
140
  });
111
141
 
112
142
  const isRecord = (v: unknown): v is Record<string, unknown> =>
@@ -126,7 +156,7 @@ function parseBlock(
126
156
  const extra = Object.keys(o).filter((k) => !BLOCK_KEYS.has(k));
127
157
  if (extra.length)
128
158
  return bad(
129
- `${at}: unknown key "${extra[0]}" — a block takes "files", "ignores" and "rules"`,
159
+ `${at}: unknown key "${String(extra[0])}" — a block takes "files", "ignores" and "rules"`,
130
160
  );
131
161
  const globs = badGlobs(o, at);
132
162
  if (globs) return bad(globs);
@@ -167,7 +197,10 @@ export function parseRuleBlocks(
167
197
  );
168
198
  const out: RuleBlock[] = [];
169
199
  for (const [i, v] of raw.entries()) {
170
- const b = parseBlock(v, `${where} → rules[${i}]`, { shipped, baseDir });
200
+ const b = parseBlock(v, `${where} → rules[${String(i)}]`, {
201
+ shipped,
202
+ baseDir,
203
+ });
171
204
  if (!b.ok) return b;
172
205
  out.push(b.value);
173
206
  }
@@ -179,16 +212,15 @@ export function parseRuleBlocks(
179
212
  * `foreign` (a dependency's plugin, such as `@eslint/markdown`) are not paperlint's and are skipped.
180
213
  */
181
214
  export function shippedRuleIds(
182
- config: readonly unknown[],
215
+ config: readonly ConfigBlock[],
183
216
  foreign: readonly unknown[],
184
217
  ): Set<string> {
185
218
  const ids = new Set<string>();
186
219
  for (const block of config) {
187
- const plugins = (block as { plugins?: Record<string, unknown> }).plugins;
188
- for (const [name, plugin] of Object.entries(plugins ?? {})) {
220
+ for (const [name, plugin] of Object.entries(block.plugins ?? {})) {
189
221
  if (foreign.includes(plugin)) continue;
190
- const rules = (plugin as { rules?: Record<string, unknown> }).rules;
191
- for (const rule of Object.keys(rules ?? {})) ids.add(`${name}/${rule}`);
222
+ for (const rule of Object.keys(plugin.rules ?? {}))
223
+ ids.add(`${name}/${rule}`);
192
224
  }
193
225
  }
194
226
  return ids;
@@ -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
  });
@@ -15,18 +15,27 @@ import {
15
15
  } from "node:fs";
16
16
  import { tmpdir } from "node:os";
17
17
  import { join, dirname } from "node:path";
18
- import { fileURLToPath } from "node:url";
19
- import { createChecker } from "../lib/check.mjs";
18
+ import { z } from "zod";
19
+ import { createChecker } from "../lib/check.ts";
20
20
 
21
- const HERE = dirname(fileURLToPath(import.meta.url));
21
+ /** `asEslintResults` promises only `unknown[]`; these are the fields the checks read. */
22
+ const EslintResults = z.array(
23
+ z.object({
24
+ errorCount: z.number(),
25
+ warningCount: z.number(),
26
+ messages: z.array(
27
+ z.object({ ruleId: z.string().nullable(), severity: z.number() }),
28
+ ),
29
+ }),
30
+ );
22
31
  const { checkStructure, formatStructure, asEslintResults, STRUCTURE_DEFAULTS } =
23
- await import(join(HERE, "structure.ts"));
32
+ await import("./structure.ts");
24
33
 
25
34
  const check = createChecker();
26
35
 
27
36
  const root = realpathSync(mkdtempSync(join(tmpdir(), "paperlint-struct-")));
28
37
  const papers = join(root, "papers");
29
- const paper = (name, files) => {
38
+ const paper = (name: string, files: readonly string[]) => {
30
39
  const dir = join(papers, name);
31
40
  mkdirSync(dir, { recursive: true });
32
41
  for (const f of files) {
@@ -50,7 +59,7 @@ try {
50
59
  writeFileSync(join(papers, ".hidden", "paper.tex"), "x");
51
60
 
52
61
  const f = checkStructure([papers], undefined, { cwd: root });
53
- const at = (name) => f.filter((x) => x.file.endsWith(name));
62
+ const at = (name: string) => f.filter((x) => x.file.endsWith(name));
54
63
 
55
64
  check(
56
65
  "a complete paper directory — NOT A SINGLE finding",
@@ -63,24 +72,26 @@ try {
63
72
  check(
64
73
  "a missing scorecard — a finding",
65
74
  at("no-scorecard").length === 1 &&
66
- /missing `PIPELINE-STATUS\.md`/.test(at("no-scorecard")[0].message),
75
+ /missing `PIPELINE-STATUS\.md`/.test(
76
+ at("no-scorecard")[0]?.message ?? "",
77
+ ),
67
78
  );
68
79
  check(
69
80
  "and the message names the CONSEQUENCE, not a restatement of the condition",
70
- /are skipped/.test(at("no-scorecard")[0].message) &&
81
+ /are skipped/.test(at("no-scorecard")[0]?.message ?? "") &&
71
82
  /stage, source and research-question checks/.test(
72
- at("no-scorecard")[0].message,
83
+ at("no-scorecard")[0]?.message ?? "",
73
84
  ),
74
85
  );
75
86
  check(
76
87
  "and the consequence names THIS directory by name",
77
- /no-scorecard/.test(at("no-scorecard")[0].message),
88
+ /no-scorecard/.test(at("no-scorecard")[0]?.message ?? ""),
78
89
  );
79
90
  check(
80
91
  "a directory with no source — a finding, and BOTH accepted forms are listed",
81
92
  at("no-source").length === 1 &&
82
- /`paper\.tex`/.test(at("no-source")[0].message) &&
83
- /`paper\.md`/.test(at("no-source")[0].message),
93
+ /`paper\.tex`/.test(at("no-source")[0]?.message ?? "") &&
94
+ /`paper\.md`/.test(at("no-source")[0]?.message ?? ""),
84
95
  );
85
96
 
86
97
  // 🔴 THE PAIRED HALF: detection is GENEROUS. Without this the check would scream about every
@@ -136,7 +147,7 @@ try {
136
147
  );
137
148
 
138
149
  // ── one schema for both halves ────────────────────────────────────────────────────────
139
- const asResults = asEslintResults(f);
150
+ const asResults = EslintResults.parse(asEslintResults(f));
140
151
  check(
141
152
  "findings come back shaped like an ESLint result — `--json` stays one array",
142
153
  asResults.length === 2 &&
@@ -144,7 +155,7 @@ try {
144
155
  (r) =>
145
156
  r.errorCount === 1 &&
146
157
  r.warningCount === 0 &&
147
- r.messages[0].ruleId === "structure/required-file" &&
158
+ r.messages[0]?.ruleId === "structure/required-file" &&
148
159
  r.messages[0].severity === 2,
149
160
  ),
150
161
  );
@@ -156,7 +167,7 @@ try {
156
167
  { require: ["PIPELINE-STATUS.md", "refs.bib"] },
157
168
  { cwd: root },
158
169
  ).filter((x) => x.file.endsWith("no-source"));
159
- return asEslintResults(two)[0].errorCount === 2;
170
+ return EslintResults.parse(asEslintResults(two))[0]?.errorCount === 2;
160
171
  })(),
161
172
  );
162
173
  check(