paperlint 3.1.2 → 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 (374) 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/skills/verify-citations/scripts/verify-cites.mjs +11 -1
  280. package/skills/verify-citations/scripts/verify-cites.net.test.mjs +35 -0
  281. package/skills/verify-citations/scripts/verify-cites.test.mjs +14 -0
  282. package/src/CLAUDE.md +1 -1
  283. package/src/adapters/banal/index.test.ts +3 -6
  284. package/src/adapters/banal/index.ts +7 -4
  285. package/src/adapters/banal/invocation.test.ts +3 -1
  286. package/src/adapters/banal/invocation.ts +1 -1
  287. package/src/adapters/banal/output.test.ts +1 -1
  288. package/src/adapters/banal/run.test.ts +3 -3
  289. package/src/adapters/banal/xml.test.ts +1 -1
  290. package/src/adapters/curl/download.test.ts +4 -2
  291. package/src/adapters/node/files.io.ts +2 -2
  292. package/src/adapters/node/files.test.ts +3 -1
  293. package/src/adapters/node/process.io.ts +4 -3
  294. package/src/adapters/node/process.test.ts +1 -1
  295. package/src/adapters/node/workspace.test.ts +3 -1
  296. package/src/adapters/pdfjs/fill.test.ts +2 -2
  297. package/src/adapters/pdfjs/fill.ts +14 -7
  298. package/src/adapters/references/index.test.ts +31 -38
  299. package/src/adapters/references/index.ts +26 -58
  300. package/src/adapters/references/pool.ts +3 -5
  301. package/src/adapters/references/reach.io.ts +2 -1
  302. package/src/build-engine.harness.mjs +1 -1
  303. package/src/build-engine.test.ts +2 -2
  304. package/src/build-engine.ts +2 -2
  305. package/src/build.harness.mjs +2 -2
  306. package/src/build.test.ts +15 -14
  307. package/src/build.ts +28 -19
  308. package/src/cli.harness.mjs +6 -6
  309. package/src/cli.test.ts +13 -6
  310. package/src/cli.ts +39 -37
  311. package/src/doctor.harness.mjs +3 -3
  312. package/src/doctor.ts +3 -2
  313. package/src/domain/lookup-cache.ts +21 -11
  314. package/src/domain/result.test.ts +2 -2
  315. package/src/domain/text.test.ts +32 -0
  316. package/src/domain/text.ts +23 -0
  317. package/src/engine.harness.mjs +2 -2
  318. package/src/engine.ts +4 -3
  319. package/src/facts-file.test.ts +9 -7
  320. package/src/facts-file.ts +2 -1
  321. package/src/{hooks-settings.harness.mjs → hooks-settings.harness.ts} +39 -20
  322. package/src/hooks-settings.ts +20 -9
  323. package/src/init.test.ts +12 -9
  324. package/src/init.ts +22 -12
  325. package/src/{latex-log.harness.mjs → latex-log.harness.ts} +14 -14
  326. package/src/{latex-loop.harness.mjs → latex-loop.harness.ts} +64 -36
  327. package/src/latex-loop.ts +3 -3
  328. package/src/{link-skills.harness.mjs → link-skills.harness.ts} +19 -19
  329. package/src/link-skills.test.ts +3 -3
  330. package/src/link-skills.ts +5 -4
  331. package/src/lint-fix.test.ts +4 -6
  332. package/src/linted-set.test.ts +2 -2
  333. package/src/new-paper.harness.mjs +2 -2
  334. package/src/new-paper.test.ts +8 -10
  335. package/src/new-paper.ts +2 -2
  336. package/src/paper-files.test.ts +3 -5
  337. package/src/paper-settings-commands.test.ts +5 -4
  338. package/src/paper-settings.test.ts +5 -5
  339. package/src/paper-settings.ts +8 -9
  340. package/src/{pdf-facts.harness.mjs → pdf-facts.harness.ts} +9 -8
  341. package/src/pdf-facts.test.ts +10 -11
  342. package/src/pdf-facts.ts +2 -1
  343. package/src/{pdf-geometry.harness.mjs → pdf-geometry.harness.ts} +19 -12
  344. package/src/pdf-geometry.ts +3 -1
  345. package/src/presets.ts +3 -2
  346. package/src/references.test.ts +51 -43
  347. package/src/references.ts +14 -7
  348. package/src/{rules-config.harness.mjs → rules-config.harness.ts} +7 -13
  349. package/src/rules-config.ts +7 -4
  350. package/src/rules-off.test.ts +6 -4
  351. package/src/{structure.harness.mjs → structure.harness.ts} +26 -15
  352. package/src/structure.ts +1 -1
  353. package/src/{tex-requirements.harness.mjs → tex-requirements.harness.ts} +50 -22
  354. package/src/tex-requirements.ts +8 -5
  355. package/src/toolchain.harness.mjs +2 -2
  356. package/src/toolchain.test.ts +1 -1
  357. package/src/toolchain.ts +31 -17
  358. package/src/venue-rules.test.ts +62 -64
  359. package/src/venue-rules.ts +1 -1
  360. package/tsconfig.json +6 -3
  361. package/tsconfig.pkg.json +10 -0
  362. package/lib/check.d.mts +0 -17
  363. package/lib/paper-config.d.mts +0 -31
  364. package/scripts/layer-legacy-frozen.d.mts +0 -36
  365. package/scripts/rules-are-content-only.harness.mjs +0 -105
  366. package/scripts/rules-are-content-only.mjs +0 -148
  367. package/scripts/rules-are-content-only.test.mjs +0 -61
  368. package/scripts/test.mjs +0 -44
  369. package/scripts/test.test.mjs +0 -52
  370. /package/bin/{paperlint.test.mjs → paperlint.test.ts} +0 -0
  371. /package/lib/{skill-eval-fixture.mjs → skill-eval-fixture.ts} +0 -0
  372. /package/skills/grade-paper-writing/__snapshots__/{prose-lint.test.mjs.snap → prose-lint.test.ts.snap} +0 -0
  373. /package/skills/osf-artifact-upload/{osf-artifact-upload.harness.mjs → osf-artifact-upload.harness.ts} +0 -0
  374. /package/skills/tighten-paper/__snapshots__/{structure.test.mjs.snap → structure.test.ts.snap} +0 -0
@@ -25,11 +25,21 @@ import {
25
25
  writeFileSync,
26
26
  } from "node:fs";
27
27
  import { tmpdir } from "node:os";
28
- import { dirname, join } from "node:path";
29
- import { fileURLToPath } from "node:url";
30
- import { createChecker } from "../lib/check.mjs";
28
+ import { join } from "node:path";
29
+ import { createChecker } from "../lib/check.ts";
30
+ import { isDeepStrictEqual } from "node:util";
31
+ import { z } from "zod";
32
+ import type { Merge, Settings } from "./hooks-settings.ts";
33
+
34
+ /** Settings with a PreToolUse entry — the shape the "wired twice" case edits. */
35
+ const PreToolUseSettings = z.looseObject({
36
+ hooks: z.looseObject({
37
+ PreToolUse: z.array(
38
+ z.looseObject({ hooks: z.array(z.looseObject({ command: z.string() })) }),
39
+ ),
40
+ }),
41
+ });
31
42
 
32
- const HERE = dirname(fileURLToPath(import.meta.url));
33
43
  const {
34
44
  hookRun,
35
45
  shippedWiring,
@@ -39,16 +49,17 @@ const {
39
49
  readSettings,
40
50
  MANAGED_BY,
41
51
  SETTINGS_PATH,
42
- } = await import(join(HERE, "hooks-settings.ts"));
52
+ } = await import("./hooks-settings.ts");
43
53
  const { claudeCodeHookProtocol } = await import("vigiles/claude-code");
44
- const merge = (e, c, m) => claudeCodeHookProtocol.mergeRegistrations(e, c, m);
54
+ const merge: Merge = (e, c, m) =>
55
+ claudeCodeHookProtocol.mergeRegistrations(e, c, m);
45
56
 
46
57
  const check = createChecker();
47
58
 
48
59
  // ── the contract with vigiles, checked before anything relies on it ──────────────────────
49
60
  check(
50
61
  "🔴 vigiles/claude-code exports claudeCodeHookProtocol.mergeRegistrations — the merge init calls",
51
- typeof claudeCodeHookProtocol?.mergeRegistrations === "function",
62
+ typeof claudeCodeHookProtocol.mergeRegistrations === "function",
52
63
  );
53
64
 
54
65
  // ── the one source: plugin/hooks/hooks.json ─────────────────────────────────────────────
@@ -82,7 +93,7 @@ check(
82
93
  try {
83
94
  shippedWiring(file);
84
95
  } catch (e) {
85
- thrown = e.message;
96
+ thrown = e instanceof Error ? e.message : String(e);
86
97
  }
87
98
  rmSync(file);
88
99
  check(
@@ -109,7 +120,7 @@ try {
109
120
 
110
121
  // ── which spelling runs which hook ─────────────────────────────────────────────────────
111
122
  const ours = `node "\${CLAUDE_PROJECT_DIR}/${MANAGED_BY}" hook paper-edit-guard`;
112
- const cases = [
123
+ const cases: [string, { name: string; ours: boolean } | null][] = [
113
124
  [ours, { name: "paper-edit-guard", ours: true }],
114
125
  [
115
126
  `node "$CLAUDE_PROJECT_DIR/${MANAGED_BY}" hook paper-status-gates`,
@@ -140,7 +151,7 @@ try {
140
151
  JSON.stringify(hookRun(cmd)) === JSON.stringify(want),
141
152
  );
142
153
 
143
- const project = (name, settings) => {
154
+ const project = (name: string, settings?: Settings | string) => {
144
155
  const dir = join(work, name);
145
156
  mkdirSync(join(dir, ".claude"), { recursive: true });
146
157
  if (settings !== undefined)
@@ -152,14 +163,19 @@ try {
152
163
  );
153
164
  return dir;
154
165
  };
155
- const text = (dir) => readFileSync(join(dir, SETTINGS_PATH), "utf8");
166
+ const text = (dir: string) => readFileSync(join(dir, SETTINGS_PATH), "utf8");
167
+ const settingsIn = (dir: string): Settings => {
168
+ const r = readSettings(dir);
169
+ if (r.status === "unparsable") throw new Error(r.reason);
170
+ return r.settings;
171
+ };
156
172
 
157
173
  // ── a fresh project ────────────────────────────────────────────────────────────────────
158
174
  {
159
175
  const dir = join(work, "fresh");
160
176
  mkdirSync(dir);
161
177
  const r = wireHooks(dir, merge, wiring);
162
- const s = JSON.parse(text(dir));
178
+ const s = settingsIn(dir);
163
179
  check(
164
180
  "no settings file → it is created with the three hooks",
165
181
  r.status === "written" &&
@@ -189,14 +205,14 @@ try {
189
205
  },
190
206
  });
191
207
  wireHooks(dir, merge, wiring);
192
- const s = JSON.parse(text(dir));
208
+ const s = settingsIn(dir);
193
209
  check(
194
210
  "🔴 the user's own hook in the SAME matcher survives",
195
211
  JSON.stringify(s).includes("node my-own-lint.mjs"),
196
212
  );
197
213
  check(
198
214
  "and every other key is kept (permissions)",
199
- s.permissions?.allow?.[0] === "Bash(ls:*)",
215
+ isDeepStrictEqual(s["permissions"], { allow: ["Bash(ls:*)"] }),
200
216
  );
201
217
  check(
202
218
  "and ours are there, once each",
@@ -243,11 +259,14 @@ try {
243
259
  );
244
260
  check(
245
261
  "the report names the hook and the command it found",
246
- r.found.length === 1 && r.found[0].name === "paper-edit-guard",
262
+ r.status === "foreign" &&
263
+ r.found.length === 1 &&
264
+ r.found[0]?.name === "paper-edit-guard",
247
265
  );
248
266
  check(
249
267
  "and names the hooks that are not wired in ANY form",
250
- r.missing.join() === "paper-skills-nudge,paper-status-gates",
268
+ r.status === "foreign" &&
269
+ r.missing.join() === "paper-skills-nudge,paper-status-gates",
251
270
  );
252
271
  // The control: without the check, vigiles' merge WOULD add a second copy. This is the
253
272
  // reason the check exists, measured rather than asserted.
@@ -257,7 +276,7 @@ try {
257
276
  );
258
277
  check(
259
278
  "(control) vigiles' merge alone would wire paper-edit-guard TWICE here",
260
- c.ours === 1 && c.other === 1,
279
+ c?.ours === 1 && c.other === 1,
261
280
  );
262
281
  }
263
282
 
@@ -276,7 +295,7 @@ try {
276
295
  }
277
296
 
278
297
  // ── doctor's section ───────────────────────────────────────────────────────────────────
279
- const doc = (settings) => {
298
+ const doc = (settings?: Settings | string) => {
280
299
  const dir = project(`doc-${String(check.count)}`, settings);
281
300
  return doctorHooks(dir, wiring).join("\n");
282
301
  };
@@ -291,8 +310,8 @@ try {
291
310
  "nothing wired → ⚠ not wired, with the remedy",
292
311
  /⚠ not wired — none of the 3 hooks/.test(doc({})),
293
312
  );
294
- const twice = JSON.parse(JSON.stringify(wiredOnce));
295
- twice.hooks.PreToolUse[0].hooks.push({
313
+ const twice = PreToolUseSettings.parse(structuredClone(wiredOnce));
314
+ twice.hooks.PreToolUse[0]?.hooks.push({
296
315
  type: "command",
297
316
  command: "npx paperlint hook paper-edit-guard",
298
317
  });
@@ -30,6 +30,7 @@ import {
30
30
  BIN_FILE,
31
31
  PACKAGE_NAME,
32
32
  } from "../skills/paper-pipeline/scripts/consumer.mjs";
33
+ import { messageOf } from "./domain/text.ts";
33
34
 
34
35
  /** The committed, shared settings file — husky's `.husky/` analogue, except git carries it. */
35
36
  export const SETTINGS_PATH = join(".claude", "settings.json");
@@ -133,17 +134,23 @@ export function hookRun(command: string): {
133
134
  export function commandsIn(
134
135
  settings: Settings,
135
136
  ): { readonly event: string; readonly command: string }[] {
136
- const hooks = settings["hooks"];
137
- if (!hooks || typeof hooks !== "object") return [];
138
137
  const out: { event: string; command: string }[] = [];
139
- for (const [event, entries] of Object.entries(hooks as HooksMap))
140
- for (const entry of Array.isArray(entries) ? entries : [])
141
- for (const h of Array.isArray(entry?.hooks) ? entry.hooks : [])
142
- if (typeof h?.command === "string")
143
- out.push({ event, command: h.command });
138
+ for (const [event, entries] of Object.entries(prop(settings["hooks"]) ?? {}))
139
+ for (const entry of listOf(entries))
140
+ for (const h of listOf(prop(entry)?.["hooks"])) {
141
+ const command = prop(h)?.["command"];
142
+ if (typeof command === "string") out.push({ event, command });
143
+ }
144
144
  return out;
145
145
  }
146
146
 
147
+ /** The settings file is the user's: any level may be missing or of the wrong kind. */
148
+ const listOf = (v: unknown): readonly unknown[] => (Array.isArray(v) ? v : []);
149
+ const prop = (v: unknown): Readonly<Record<string, unknown>> | undefined =>
150
+ typeof v === "object" && v !== null
151
+ ? Object.fromEntries(Object.entries(v))
152
+ : undefined;
153
+
147
154
  export interface Wiring {
148
155
  readonly compiled: HooksMap;
149
156
  /** Derived from the commands, in order. Never empty — an empty set throws. */
@@ -195,7 +202,11 @@ export function wiredCounts(
195
202
  const total = (c: HookCount): number => c.ours + c.other;
196
203
 
197
204
  export type SettingsRead =
198
- | { readonly status: "absent"; readonly path: string; readonly settings: {} }
205
+ | {
206
+ readonly status: "absent";
207
+ readonly path: string;
208
+ readonly settings: Settings;
209
+ }
199
210
  | {
200
211
  readonly status: "read";
201
212
  readonly path: string;
@@ -218,7 +229,7 @@ export function readSettings(root: string): SettingsRead {
218
229
  return { status: "unparsable", path, reason: "not a JSON object" };
219
230
  return { status: "read", path, settings: parsed as Settings, raw };
220
231
  } catch (e) {
221
- return { status: "unparsable", path, reason: (e as Error).message };
232
+ return { status: "unparsable", path, reason: messageOf(e) };
222
233
  }
223
234
  }
224
235
 
package/src/init.test.ts CHANGED
@@ -34,7 +34,7 @@ import {
34
34
  reportTexLive,
35
35
  reportWorkflow,
36
36
  } from "./init.ts";
37
- import { runNode } from "../test/support.mjs";
37
+ import { runNode } from "../test/support.ts";
38
38
 
39
39
  const dirs: string[] = [];
40
40
  afterEach(() => {
@@ -81,7 +81,9 @@ describe("paperlint init — a declared papers directory", () => {
81
81
  expect(code).toBe(0);
82
82
  expect(section).toMatch(/✓ papers — declared in paperlint\.json/);
83
83
  expect(section).not.toMatch(/candidates|eslint-rules|drafts/);
84
- const cfg = JSON.parse(readFileSync(join(root, "paperlint.json"), "utf8"));
84
+ const cfg: unknown = JSON.parse(
85
+ readFileSync(join(root, "paperlint.json"), "utf8"),
86
+ );
85
87
  expect(cfg).toEqual({ papersDir: "papers" });
86
88
  });
87
89
 
@@ -93,7 +95,7 @@ describe("paperlint init — a declared papers directory", () => {
93
95
  log: () => {},
94
96
  err: () => {},
95
97
  interactive: true,
96
- ask: async (q) => (asked.push(q), "n"),
98
+ ask: (q) => Promise.resolve((asked.push(q), "n")),
97
99
  hooks: false,
98
100
  run: (() => ({ status: 0 })) as never,
99
101
  link: () => ({ ok: false, error: "not linked in this test" }),
@@ -165,7 +167,8 @@ const texRun = async (
165
167
  log: (s: string) => out.push(s),
166
168
  err: () => {},
167
169
  interactive,
168
- ask: async (q) => (asked.push(q), /TeX Live/.test(q) ? answer : ""),
170
+ ask: (q) =>
171
+ Promise.resolve((asked.push(q), /TeX Live/.test(q) ? answer : "")),
169
172
  hooks: false,
170
173
  run: (() => ({ status: 0 })) as never,
171
174
  link: () => ({ ok: false as const, error: "not linked in this test" }),
@@ -301,7 +304,7 @@ describe("paperlint init — how several candidate directories were decided, sai
301
304
  it("picked by a human", async () => {
302
305
  const r = await initRun(several(), {
303
306
  interactive: true,
304
- ask: async () => "2",
307
+ ask: () => Promise.resolve("2"),
305
308
  });
306
309
  expect(section(r.out)).toEqual([
307
310
  " ✓ writing — you picked it out of 2 candidates",
@@ -319,7 +322,7 @@ describe("paperlint init — how several candidate directories were decided, sai
319
322
  it("an answer that picks nothing: the first, and said so", async () => {
320
323
  const r = await initRun(several(), {
321
324
  interactive: true,
322
- ask: async () => "nine",
325
+ ask: () => Promise.resolve("nine"),
323
326
  });
324
327
  expect(section(r.out)[0]).toBe(
325
328
  " ✓ drafts — 2 candidates, no answer was given, so the first one was taken",
@@ -433,7 +436,7 @@ describe("paperlint init — TeX Live and hooks failures, whole", () => {
433
436
  it("TeX Live: a failed install says to run it again", async () => {
434
437
  const t = await offerTexLive(
435
438
  { installed: () => false, install: () => 1 },
436
- { interactive: true, ask: async () => "yes" },
439
+ { interactive: true, ask: () => Promise.resolve("yes") },
437
440
  );
438
441
  expect([t, reportTexLive(t, "")]).toEqual([
439
442
  "failed",
@@ -462,8 +465,8 @@ describe("paperlint init — a first paper", () => {
462
465
  const made: string[] = [];
463
466
  const r = await initRun(tree({}), {
464
467
  interactive: true,
465
- ask: async () => "",
466
- createPaper: async (_root, name) => (made.push(name), 0),
468
+ ask: () => Promise.resolve(""),
469
+ createPaper: (_root, name) => Promise.resolve((made.push(name), 0)),
467
470
  });
468
471
  expect(made).toEqual([]);
469
472
  expect(r.out[r.out.indexOf("first paper") + 1]).toBe(
package/src/init.ts CHANGED
@@ -64,13 +64,15 @@ import {
64
64
  nameProblem,
65
65
  type PaperFormat,
66
66
  } from "./new-paper.ts";
67
- // The one source for the consumer's config key lives in the .mjs half of the package (the ESLint
68
- // rules and the skill scripts import it too); its types are in lib/paper-config.d.mts.
67
+ // The one source for the consumer's config key lives in lib/ (the ESLint rules and the skill scripts
68
+ // import it too). It is imported compiled, from dist/, the one path that resolves the same from src/
69
+ // and from dist/ — see CONTRIBUTING.md.
69
70
  import {
70
71
  CONFIG_FILE,
71
72
  DEFAULT_PAPERS_ROOT,
72
73
  PAPERS_DIR_FIELD,
73
- } from "../lib/paper-config.mjs";
74
+ } from "#lib/paper-config";
75
+ import { messageOf } from "./domain/text.ts";
74
76
 
75
77
  /** How the papers directory was arrived at. Printed, because a guess must not read as a fact. */
76
78
  export type PapersHow =
@@ -122,13 +124,21 @@ export function interactivity({
122
124
  return { interactive: true, why: "a terminal on both ends" };
123
125
  }
124
126
 
127
+ /**
128
+ * Whether a standard stream is a terminal. Node types `isTTY` as `boolean`, but on a stream that
129
+ * is NOT a terminal (a pipe, a file) the property is absent — `undefined` — so it is read as the
130
+ * optional it really is.
131
+ */
132
+ const isTty = (stream: { readonly isTTY?: boolean }): boolean =>
133
+ stream.isTTY === true;
134
+
125
135
  /** The mode of THIS process — the one place `process` is read for it. */
126
136
  export const processInteractivity = (yes: boolean) =>
127
137
  interactivity({
128
138
  // eslint-disable-next-line no-restricted-globals -- legacy I/O, moves behind a port in #76
129
- stdinTTY: Boolean(process.stdin.isTTY),
139
+ stdinTTY: isTty(process.stdin),
130
140
  // eslint-disable-next-line no-restricted-globals -- legacy I/O, moves behind a port in #76
131
- stdoutTTY: Boolean(process.stdout.isTTY),
141
+ stdoutTTY: isTty(process.stdout),
132
142
  // eslint-disable-next-line no-restricted-globals -- legacy I/O, moves behind a port in #76
133
143
  env: process.env,
134
144
  yes,
@@ -222,7 +232,7 @@ export async function declarePapers(
222
232
  try {
223
233
  settings = JSON.parse(raw) as Record<string, unknown>;
224
234
  } catch (e) {
225
- return { status: "unparsable", path, reason: (e as Error).message };
235
+ return { status: "unparsable", path, reason: messageOf(e) };
226
236
  }
227
237
  const existing = settings[PAPERS_DIR_FIELD];
228
238
  if (existing !== undefined)
@@ -289,7 +299,7 @@ export async function offerWorkflow(
289
299
  ask?: (q: string) => Promise<string>;
290
300
  interactive: boolean;
291
301
  /** The running package's version; see `InitOptions.version`. */
292
- version?: string;
302
+ version?: string | undefined;
293
303
  },
294
304
  ): Promise<WorkflowResult> {
295
305
  const path = join(root, WORKFLOW_PATH);
@@ -317,7 +327,7 @@ export function reportWorkflow(
317
327
  version,
318
328
  papersDir,
319
329
  why,
320
- }: { version?: string; papersDir: string; why: string },
330
+ }: { version?: string | undefined; papersDir: string; why: string },
321
331
  ): string[] {
322
332
  const ref = actionRef(version);
323
333
  const out: string[] = [];
@@ -406,7 +416,7 @@ export async function offerHooks(
406
416
  hooks: boolean;
407
417
  interactive: boolean;
408
418
  ask?: (q: string) => Promise<string>;
409
- merge?: Merge;
419
+ merge?: Merge | undefined;
410
420
  },
411
421
  ): Promise<HooksOutcome> {
412
422
  if (!hooks) return { status: "skipped" };
@@ -438,7 +448,7 @@ export async function offerHooks(
438
448
  }
439
449
  return wireHooks(root, m, shippedWiring());
440
450
  } catch (e) {
441
- return { status: "failed", reason: (e as Error).message };
451
+ return { status: "failed", reason: messageOf(e) };
442
452
  }
443
453
  }
444
454
 
@@ -560,7 +570,7 @@ export interface InitOptions {
560
570
  /** `false` is `--no-hooks`. */
561
571
  hooks?: boolean;
562
572
  /** vigiles' merge. Injected only so a test can observe or replace it. */
563
- merge?: Merge;
573
+ merge?: Merge | undefined;
564
574
  /** `--paper <name>`: create this paper, even without a terminal. */
565
575
  paper?: string | null;
566
576
  /** `--format tex|md` for that paper. */
@@ -586,7 +596,7 @@ export interface InitOptions {
586
596
  * The version of the running package, read by the CLI from its own `package.json`. The CI
587
597
  * workflow is pinned to its release tag (`actionRef`); absent or unreleased, the placeholder.
588
598
  */
589
- version?: string;
599
+ version?: string | undefined;
590
600
  /**
591
601
  * TeX Live for `paperlint build`: whether paperlint's own tree is installed, and how to install
592
602
  * it (`paperlint toolchain`). Passed in by the CLI; without them the step only names the command.
@@ -5,11 +5,9 @@
5
5
  * memory: the two error spellings `-file-line-error` produces, the missing-package form, and a
6
6
  * marker that TeX broke at column 79.
7
7
  */
8
- import { dirname, join } from "node:path";
9
- import { fileURLToPath } from "node:url";
10
- import { createChecker } from "../lib/check.mjs";
8
+ import { createChecker } from "../lib/check.ts";
9
+ import type { LogMarker } from "./latex-log.ts";
11
10
 
12
- const HERE = dirname(fileURLToPath(import.meta.url));
13
11
  const {
14
12
  unwrapLog,
15
13
  logMarkers,
@@ -18,7 +16,7 @@ const {
18
16
  bibtexExcerpt,
19
17
  auxBib,
20
18
  MAX_PRINT_LINE,
21
- } = await import(join(HERE, "latex-log.ts"));
19
+ } = await import("./latex-log.ts");
22
20
 
23
21
  const check = createChecker();
24
22
 
@@ -78,13 +76,15 @@ const firstPass = [
78
76
  const m = logMarkers(firstPass);
79
77
  check(
80
78
  "every documented marker is recognised",
81
- [
82
- "rerun-requested",
83
- "labels-changed",
84
- "rerunfilecheck",
85
- "undefined-references",
86
- "undefined-citations",
87
- ].every((k) => m.includes(k)),
79
+ (
80
+ [
81
+ "rerun-requested",
82
+ "labels-changed",
83
+ "rerunfilecheck",
84
+ "undefined-references",
85
+ "undefined-citations",
86
+ ] satisfies LogMarker[]
87
+ ).every((k) => m.includes(k)),
88
88
  );
89
89
  check(
90
90
  "a clean log carries no markers",
@@ -96,7 +96,7 @@ check(
96
96
  // Guards: matching the documented summary line rather than the word 'undefined'.
97
97
  check(
98
98
  "a single reference warning is not the end-of-run summary",
99
- !logMarkers([firstPass[0]]).includes("undefined-references"),
99
+ !logMarkers(firstPass.slice(0, 1)).includes("undefined-references"),
100
100
  );
101
101
 
102
102
  // ── error excerpt ───────────────────────────────────────────────────────────────────────
@@ -114,7 +114,7 @@ check(
114
114
  "-file-line-error form: the error line, the l.NNN line and the rest of the source line",
115
115
  ex[0] === "./paper.tex:4: Undefined control sequence." &&
116
116
  ex[1] === "l.4 \\foo" &&
117
- ex[2].includes("bar baz"),
117
+ (ex[2] ?? "").includes("bar baz"),
118
118
  );
119
119
  // Guards: the end of the excerpt — without it the excerpt runs into TeX's memory statistics and
120
120
  // buries the line the author needs.