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
@@ -21,8 +21,8 @@ import {
21
21
  ROOT_ONLY_KEYS,
22
22
  SETTINGS_KEYS,
23
23
  findProjectRoot,
24
- } from "../lib/paper-config.mjs";
25
- import type { AbsolutePath } from "./domain/paths.ts";
24
+ } from "#lib/paper-config";
25
+ import { callerPath } from "./caller-path.ts";
26
26
  import { err, ok, type Result } from "./domain/result.ts";
27
27
  import type { Files } from "./ports/files.ts";
28
28
  import {
@@ -30,6 +30,7 @@ import {
30
30
  type Parsed,
31
31
  type RuleEntry,
32
32
  } from "./rules-config.ts";
33
+ import { messageOf } from "./domain/text.ts";
33
34
 
34
35
  /** What one paper declares. Every absent field is null. */
35
36
  export interface PaperSettings {
@@ -92,11 +93,13 @@ export function parsePaperSettings(
92
93
  if (!isObject(json)) return err("must be a JSON object");
93
94
  const unknown = Object.keys(json).filter((k) => !KNOWN.includes(k));
94
95
  if (unknown.length > 0)
95
- return err(`unknown key "${unknown[0]}" — known keys: ${KNOWN.join(", ")}`);
96
+ return err(
97
+ `unknown key "${String(unknown[0])}" — known keys: ${KNOWN.join(", ")}`,
98
+ );
96
99
  const project = Object.keys(json).filter((k) => ROOT_ONLY_KEYS.includes(k));
97
100
  if (project.length > 0)
98
101
  return err(
99
- `"${project[0]}" is a project setting — set it in the root ${CONFIG_FILE}, not in a paper's`,
102
+ `"${String(project[0])}" is a project setting — set it in the root ${CONFIG_FILE}, not in a paper's`,
100
103
  );
101
104
  const fields = stringFields(json);
102
105
  if (!fields.ok) return fields;
@@ -111,19 +114,17 @@ export function parsePaperSettings(
111
114
  });
112
115
  }
113
116
 
114
- const at = (p: string): AbsolutePath => p as AbsolutePath;
115
-
116
117
  /** A JSON file through `files`: undefined when absent, the problem when it does not parse. */
117
118
  function readJson(
118
119
  files: Files,
119
120
  path: string,
120
121
  ): Result<unknown, SettingsProblem> {
121
- const bytes = files.readBytes(at(path));
122
+ const bytes = files.readBytes(callerPath(path));
122
123
  if (bytes === null) return ok(undefined);
123
124
  try {
124
125
  return ok(JSON.parse(new TextDecoder().decode(bytes)));
125
126
  } catch (e) {
126
- return err({ kind: "broken", why: `not JSON (${(e as Error).message})` });
127
+ return err({ kind: "broken", why: `not JSON (${messageOf(e)})` });
127
128
  }
128
129
  }
129
130
 
@@ -136,7 +137,9 @@ function rootDefaults(
136
137
  files: Files,
137
138
  paperDir: string,
138
139
  ): Result<Defaults | null, SettingsProblem> {
139
- const root = findProjectRoot(dirname(paperDir), (p) => files.isFile(at(p)));
140
+ const root = findProjectRoot(dirname(paperDir), (p) =>
141
+ files.isFile(callerPath(p)),
142
+ );
140
143
  const json = readJson(files, join(root, CONFIG_FILE));
141
144
  if (!json.ok) return json;
142
145
  if (!isObject(json.value)) return ok(null);
@@ -200,9 +203,5 @@ export function paperRules(
200
203
  if (settings.rules === null || Array.isArray(settings.rules))
201
204
  return { ok: true, value: null };
202
205
  const where = `${join(paperDir, CONFIG_FILE)} → "rules"`;
203
- return parseRuleEntries(
204
- settings.rules as Readonly<Record<string, unknown>>,
205
- where,
206
- shipped,
207
- );
206
+ return parseRuleEntries(settings.rules, where, shipped);
208
207
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * `pdf-facts.ts` — pdf.js reading REAL PDFs, committed under `fixtures/pdf-facts/`.
3
3
  *
4
- * The pure decisions are tested in `pdf-geometry.harness.mjs` with hand-written words. This file is
4
+ * The pure decisions are tested in `pdf-geometry.harness.ts` with hand-written words. This file is
5
5
  * the other half: that the shell asks pdf.js the right questions, with the right options, and maps
6
6
  * what comes back. The expected numbers are the ones poppler reported for the same files in the
7
7
  * 2026-09-24 measurement (issue #61), so a pass here is agreement with the tool this replaced.
@@ -11,23 +11,24 @@ import { writeFileSync, mkdtempSync, realpathSync, rmSync } from "node:fs";
11
11
  import { tmpdir } from "node:os";
12
12
  import { dirname, join, resolve } from "node:path";
13
13
  import { fileURLToPath } from "node:url";
14
- import { createChecker } from "../lib/check.mjs";
14
+ import { createChecker } from "../lib/check.ts";
15
+ import type { PdfFacts } from "./pdf-facts.ts";
15
16
 
16
17
  const HERE = dirname(fileURLToPath(import.meta.url));
17
18
  const FIX = resolve(HERE, "..", "fixtures", "pdf-facts");
18
- const { readPdf } = await import(join(HERE, "pdf-facts.ts"));
19
- const { classifyLastPage } = await import(join(HERE, "pdf-geometry.ts"));
19
+ const { readPdf } = await import("./pdf-facts.ts");
20
+ const { classifyLastPage } = await import("./pdf-geometry.ts");
20
21
 
21
22
  const check = createChecker();
22
23
 
23
- const read = async (name) => {
24
+ const read = async (name: string): Promise<PdfFacts> => {
24
25
  const r = await readPdf(join(FIX, name));
25
26
  assert.ok(r.ok, `${name} must read: ${JSON.stringify(r)}`);
26
27
  return r.facts;
27
28
  };
28
- const kinds = (facts) =>
29
+ const kinds = (facts: PdfFacts): string[] =>
29
30
  facts.fonts.kind === "drawn" ? facts.fonts.list.map((f) => f.kind) : [];
30
- const named = (facts, name) =>
31
+ const named = (facts: PdfFacts, name: string) =>
31
32
  facts.fonts.kind === "drawn"
32
33
  ? facts.fonts.list.find((f) => f.name === name)
33
34
  : undefined;
@@ -139,7 +140,7 @@ try {
139
140
  const boxes = f.layout.flatMap((p, i) =>
140
141
  p.boxes.map((b) => ({ ...b, page: i + 1 })),
141
142
  );
142
- const chars = (pred) =>
143
+ const chars = (pred: (b: (typeof boxes)[number]) => boolean) =>
143
144
  boxes.filter(pred).reduce((sum, b) => sum + b.text.length, 0);
144
145
  check(
145
146
  "hidden-text: one layout per page, each US letter",
@@ -8,6 +8,7 @@ import assert from "node:assert/strict";
8
8
  import { getResolvedPDFJS } from "unpdf";
9
9
  import { test } from "vitest";
10
10
  import { factsOf, failureOf, rawFontOf } from "./pdf-facts.ts";
11
+ import { present } from "../test/support.ts";
11
12
 
12
13
  const lib = await getResolvedPDFJS();
13
14
  const H = 792;
@@ -23,8 +24,8 @@ interface FakeItem {
23
24
  function page(items: FakeItem[], fonts: Record<string, unknown>) {
24
25
  const objs = new Map(Object.entries(fonts));
25
26
  return {
26
- getOperatorList: async () => ({ fnArray: [], argsArray: [] }),
27
- getTextContent: async () => ({ items }),
27
+ getOperatorList: () => Promise.resolve({ fnArray: [], argsArray: [] }),
28
+ getTextContent: () => Promise.resolve({ items }),
28
29
  // Iterable like pdf.js's `commonObjs`, with its `has`/`get`.
29
30
  commonObjs: {
30
31
  has: (id: string) => objs.has(id),
@@ -39,11 +40,11 @@ function page(items: FakeItem[], fonts: Record<string, unknown>) {
39
40
  }),
40
41
  };
41
42
  }
42
- const doc = (pages: ReturnType<typeof page>[]) =>
43
- ({
44
- numPages: pages.length,
45
- getPage: async (i: number) => pages[i - 1],
46
- }) as never;
43
+ const doc = (pages: ReturnType<typeof page>[]) => ({
44
+ numPages: pages.length,
45
+ getPage: (i: number) =>
46
+ Promise.resolve(present(pages[i - 1], `page ${String(i)}`)),
47
+ });
47
48
  const item = (
48
49
  fontName: string,
49
50
  transform: number[],
@@ -89,7 +90,7 @@ test("a document with no pages is `empty`; text drawn with no font is refused",
89
90
  lib,
90
91
  );
91
92
  assert.equal(r.ok, false);
92
- assert.equal(!r.ok && r.reason, "zero-fonts-on-text");
93
+ assert.equal(r.reason, "zero-fonts-on-text");
93
94
  });
94
95
 
95
96
  test("metrics, sizes and names fall back when pdf.js gives none", async () => {
@@ -117,7 +118,7 @@ test("metrics, sizes and names fall back when pdf.js gives none", async () => {
117
118
  lib,
118
119
  );
119
120
  assert.equal(r.ok, true);
120
- const boxes = r.ok ? r.facts.layout[0]!.boxes : [];
121
+ const boxes = present(r.facts.layout[0], "a first page").boxes;
121
122
  assert.deepEqual(
122
123
  boxes.map((b) => [b.font, b.size, b.fill]),
123
124
  [
@@ -127,12 +128,10 @@ test("metrics, sizes and names fall back when pdf.js gives none", async () => {
127
128
  ["gone", 10, { kind: "unknown" }],
128
129
  ],
129
130
  );
130
- assert.deepEqual(r.ok ? r.facts.last.words.map((w) => w.text) : [], [
131
- "word",
132
- "word",
133
- "word",
134
- "word",
135
- ]);
131
+ assert.deepEqual(
132
+ r.facts.last.words.map((w) => w.text),
133
+ ["word", "word", "word", "word"],
134
+ );
136
135
  });
137
136
 
138
137
  test("a failure that is not an Error is named `Error` with its text", () => {
@@ -142,3 +141,14 @@ test("a failure that is not an Error is named `Error` with its text", () => {
142
141
  detail: "Error: boom",
143
142
  });
144
143
  });
144
+
145
+ test("🔴 a rejection with no value at all is a failure too, not a TypeError out of `readPdf`", () => {
146
+ // Guards: `readPdf` ends in `.catch(failureOf)`, and reading `.name` off `null` threw from inside it.
147
+ assert.deepEqual(
148
+ [failureOf(null), failureOf(undefined)],
149
+ [
150
+ { ok: false, reason: "unreadable", detail: "Error: null" },
151
+ { ok: false, reason: "unreadable", detail: "Error: undefined" },
152
+ ],
153
+ );
154
+ });
package/src/pdf-facts.ts CHANGED
@@ -34,12 +34,68 @@ import {
34
34
  import type { PageLayout, TextBox } from "./domain/page-layout.ts";
35
35
  // eslint-disable-next-line boundaries/dependencies -- legacy layer, moves behind a port in #76
36
36
  import { fillsFor, isUpright } from "./adapters/pdfjs/fill.ts";
37
+ import { messageOf } from "./domain/text.ts";
38
+ import { fieldOf, isRecord, numbersOf } from "./domain/record.ts";
37
39
 
38
40
  type PdfJs = Awaited<ReturnType<typeof import("unpdf").getResolvedPDFJS>>;
39
- type Doc = Awaited<ReturnType<PdfJs["getDocument"]>["promise"]>;
40
- type Page = Awaited<ReturnType<Doc["getPage"]>>;
41
- type Item = Awaited<ReturnType<Page["getTextContent"]>>["items"][number];
42
- type TextItem = Extract<Item, { str: string }>;
41
+
42
+ /** A text item as this module reads it; pdf.js's `TextItem` is one. */
43
+ interface TextItem {
44
+ readonly str: string;
45
+ readonly fontName: string;
46
+ /** pdf.js types it `any[]`; `numbersOf` reads it. */
47
+ readonly transform: readonly unknown[];
48
+ readonly width: number;
49
+ readonly height: number;
50
+ }
51
+ /** What else pdf.js lists among a page's items: a marked-content boundary, with no text. */
52
+ interface MarkedContent {
53
+ readonly type: string;
54
+ readonly str?: undefined;
55
+ }
56
+ type Item = TextItem | MarkedContent;
57
+
58
+ /** A page's operators, as `fillsFor` walks them; pdf.js's operator list is one. */
59
+ interface OperatorList {
60
+ readonly fnArray: readonly number[];
61
+ readonly argsArray: readonly unknown[];
62
+ }
63
+
64
+ /** A viewport as this module reads it; pdf.js's `PageViewport` is one. */
65
+ interface Viewport {
66
+ readonly width: number;
67
+ readonly height: number;
68
+ readonly transform: readonly number[];
69
+ convertToViewportPoint(x: number, y: number): readonly unknown[];
70
+ }
71
+
72
+ /**
73
+ * A page as this module reads it. pdf.js's `PDFPageProxy` is one, and so is a test's fake page: the
74
+ * module depends on these five members, not on the rest of pdf.js's page.
75
+ */
76
+ interface Page {
77
+ getOperatorList(): Promise<OperatorList>;
78
+ getTextContent(): Promise<{ readonly items: readonly Item[] }>;
79
+ /** pdf.js's `commonObjs`: loaded objects by id, iterable as `[id, object]` pairs. */
80
+ readonly commonObjs: {
81
+ has(id: string): boolean;
82
+ get(id: string): unknown;
83
+ [Symbol.iterator](): Iterator<readonly unknown[]>;
84
+ };
85
+ getViewport(params: { scale: number }): Viewport;
86
+ }
87
+
88
+ /** A document as this module reads it; pdf.js's `PDFDocumentProxy` is one. */
89
+ interface Doc {
90
+ readonly numPages: number;
91
+ getPage(pageNumber: number): Promise<Page>;
92
+ }
93
+
94
+ /** A viewport point, which pdf.js types as `any[]`: the two numbers it holds. */
95
+ const pointOf = (xy: readonly unknown[]): [number, number] => [
96
+ Number(xy[0]),
97
+ Number(xy[1]),
98
+ ];
43
99
 
44
100
  /**
45
101
  * What `readPdf` measured. `last` is the last page's words; `classifyLastPage` reads it. `layout` is
@@ -94,8 +150,8 @@ const fail = (reason: PdfReadFailure, detail: string): PdfRead => ({
94
150
 
95
151
  /** A font object pdf.js left in `commonObjs`, read into a `RawFont`, or null when it is not one. */
96
152
  export function rawFontOf(id: string, obj: unknown): RawFont | null {
97
- if (typeof obj !== "object" || obj === null) return null;
98
- const o = obj as Record<string, unknown>;
153
+ if (!isRecord(obj)) return null;
154
+ const o = obj;
99
155
  if (!("loadedName" in o) && !("isType3Font" in o)) return null;
100
156
  const str = (v: unknown): string | undefined =>
101
157
  typeof v === "string" ? v : undefined;
@@ -110,7 +166,7 @@ export function rawFontOf(id: string, obj: unknown): RawFont | null {
110
166
  }
111
167
 
112
168
  const isText = (it: Item): it is TextItem =>
113
- "str" in it && it.str.trim() !== "";
169
+ typeof it.str === "string" && it.str.trim() !== "";
114
170
 
115
171
  /** The font pdf.js resolved for `id`, if it did. `get` throws on an unresolved id; `has` does not. */
116
172
  function resolved(page: Page, id: string): unknown {
@@ -123,9 +179,10 @@ function resolved(page: Page, id: string): unknown {
123
179
  */
124
180
  function loadedFonts(page: Page): Map<string, RawFont> {
125
181
  const out = new Map<string, RawFont>();
126
- for (const [id, obj] of page.commonObjs as Iterable<[string, unknown]>) {
127
- const f = rawFontOf(id, obj);
128
- if (f) out.set(id, f);
182
+ // pdf.js yields `[id, object]` pairs, typed `any[]`.
183
+ for (const entry of page.commonObjs) {
184
+ const f = rawFontOf(String(entry[0]), entry[1]);
185
+ if (f) out.set(f.id, f);
129
186
  }
130
187
  return out;
131
188
  }
@@ -166,7 +223,7 @@ async function readPage(
166
223
  * Both are far below any balance tolerance, so the simpler rule stays.
167
224
  */
168
225
  function metricsOf(font: unknown): { ascent: number; descent: number } {
169
- const f = (font ?? {}) as Record<string, unknown>;
226
+ const f = isRecord(font) ? font : {};
170
227
  const ascent = f["ascent"];
171
228
  if (typeof ascent !== "number" || !(ascent > 0))
172
229
  return { ascent: DEFAULT_ASCENT, descent: DEFAULT_DESCENT };
@@ -178,8 +235,8 @@ function metricsOf(font: unknown): { ascent: number; descent: number } {
178
235
  function pageText(page: Page, items: readonly TextItem[]): PageText {
179
236
  const vp = page.getViewport({ scale: 1 });
180
237
  const runs: TextRun[] = items.map((it) => {
181
- const [a = 0, b = 0, , d = 0, e = 0, f = 0] = it.transform as number[];
182
- const [x, baseline] = vp.convertToViewportPoint(e, f) as [number, number];
238
+ const [a = 0, b = 0, , d = 0, e = 0, f = 0] = numbersOf(it.transform);
239
+ const [x, baseline] = pointOf(vp.convertToViewportPoint(e, f));
183
240
  const size = Math.hypot(a, b) || it.height || Math.abs(d);
184
241
  return {
185
242
  text: it.str,
@@ -199,7 +256,7 @@ function pageText(page: Page, items: readonly TextItem[]): PageText {
199
256
 
200
257
  /** The font name pdf.js resolved for an item, else its internal id. */
201
258
  function fontNameOf(font: unknown, id: string): string {
202
- const name = (font as { name?: unknown } | null)?.name;
259
+ const name = fieldOf(font, "name");
203
260
  return typeof name === "string" && name ? name : id;
204
261
  }
205
262
 
@@ -210,12 +267,12 @@ function fontNameOf(font: unknown, id: string): string {
210
267
  */
211
268
  function boxOf(
212
269
  page: Page,
213
- vp: ReturnType<Page["getViewport"]>,
270
+ vp: Viewport,
214
271
  it: TextItem,
215
272
  lib: PdfJs,
216
273
  ): Omit<TextBox, "fill"> {
217
- const [a = 0, b = 0, c = 0, d = 0, e = 0, f = 0] = it.transform as number[];
218
- const [x, baseline] = vp.convertToViewportPoint(e, f) as [number, number];
274
+ const [a = 0, b = 0, c = 0, d = 0, e = 0, f = 0] = numbersOf(it.transform);
275
+ const [x, baseline] = pointOf(vp.convertToViewportPoint(e, f));
219
276
  const size = Math.hypot(c, d) || Math.hypot(a, b) || it.height;
220
277
  const font = resolved(page, it.fontName);
221
278
  const m = metricsOf(font);
@@ -228,7 +285,7 @@ function boxOf(
228
285
  font: fontNameOf(font, it.fontName),
229
286
  text: it.str,
230
287
  upright: isUpright(
231
- lib.Util.transform(vp.transform, it.transform) as number[],
288
+ numbersOf(lib.Util.transform(vp.transform, it.transform)),
232
289
  ),
233
290
  };
234
291
  }
@@ -237,7 +294,7 @@ function boxOf(
237
294
  function layoutOf(
238
295
  page: Page,
239
296
  items: readonly TextItem[],
240
- ops: Awaited<ReturnType<Page["getOperatorList"]>>,
297
+ ops: OperatorList,
241
298
  lib: PdfJs,
242
299
  ): PageLayout {
243
300
  const vp = page.getViewport({ scale: 1 });
@@ -289,10 +346,11 @@ export async function factsOf(doc: Doc, lib: PdfJs): Promise<PdfRead> {
289
346
 
290
347
  /** pdf.js's own exception names for the two conditions a caller can act on. */
291
348
  export function failureOf(e: unknown): PdfRead {
292
- const err = e as { name?: string; message?: string };
293
- const detail = `${err.name ?? "Error"}: ${err.message ?? String(e)}`;
349
+ const name = fieldOf(e, "name");
350
+ const message = fieldOf(e, "message");
351
+ const detail = `${typeof name === "string" ? name : "Error"}: ${typeof message === "string" ? message : String(e)}`;
294
352
  return fail(
295
- err.name === "PasswordException" ? "encrypted" : "unreadable",
353
+ name === "PasswordException" ? "encrypted" : "unreadable",
296
354
  detail,
297
355
  );
298
356
  }
@@ -303,7 +361,7 @@ export const readPdf: PdfReader = async (path) => {
303
361
  try {
304
362
  data = new Uint8Array(readFileSync(path));
305
363
  } catch (e) {
306
- return fail("unreadable", (e as Error).message);
364
+ return fail("unreadable", messageOf(e));
307
365
  }
308
366
  const lib = await loadPdfJs();
309
367
  const task = lib.getDocument({ data, ...PDFJS_OPTIONS });
@@ -6,11 +6,9 @@
6
6
  * on 2026-08-29 (the midpoint cut, the review build, years mistaken for line numbers), ported from
7
7
  * the harness of the function this replaces.
8
8
  */
9
- import { dirname, join } from "node:path";
10
- import { fileURLToPath } from "node:url";
11
- import { createChecker } from "../lib/check.mjs";
9
+ import { createChecker } from "../lib/check.ts";
10
+ import type { RawFont } from "./pdf-geometry.ts";
12
11
 
13
- const HERE = dirname(fileURLToPath(import.meta.url));
14
12
  const {
15
13
  classifyLastPage,
16
14
  wordsOf,
@@ -19,14 +17,23 @@ const {
19
17
  programOf,
20
18
  popplerType,
21
19
  STUB_WORDS,
22
- } = await import(join(HERE, "pdf-geometry.ts"));
20
+ } = await import("./pdf-geometry.ts");
23
21
 
24
22
  const check = createChecker();
25
23
 
24
+ /** A row of stacked words: `n` of them from `top` downwards, `step` apart. */
25
+ interface Row {
26
+ x: number;
27
+ top: number;
28
+ n?: number;
29
+ step?: number;
30
+ text?: string;
31
+ }
32
+
26
33
  const W = 612; // US letter, points
27
34
 
28
35
  /** Words from rows `{x, top, n, step, text}`: `n` words stacked downwards, each 10 pt tall. */
29
- const words = (rows) =>
36
+ const words = (rows: readonly Row[]) =>
30
37
  rows.flatMap(({ x, top, n: count = 1, step = 10, text = "word" }) =>
31
38
  Array.from({ length: count }, (_, i) => ({
32
39
  x0: x,
@@ -36,7 +43,7 @@ const words = (rows) =>
36
43
  text,
37
44
  })),
38
45
  );
39
- const page = (rows, widthPt = W) => ({
46
+ const page = (rows: readonly Row[], widthPt = W) => ({
40
47
  widthPt,
41
48
  heightPt: 792,
42
49
  words: words(rows),
@@ -164,19 +171,19 @@ check(
164
171
  check(
165
172
  "a run splits at whitespace into words with proportional x",
166
173
  ws.length === 2 &&
167
- ws[0].text === "ab" &&
174
+ ws[0]?.text === "ab" &&
168
175
  ws[0].x0 === 100 &&
169
176
  ws[0].x1 === 120 &&
170
- ws[1].x0 === 140,
177
+ ws[1]?.x0 === 140,
171
178
  );
172
179
  check(
173
180
  "a word spans ascent above the baseline to descent below it",
174
- ws[0].y0 === 192 && ws[0].y1 === 202,
181
+ ws[0]?.y0 === 192 && ws[0].y1 === 202,
175
182
  );
176
183
  }
177
184
 
178
185
  // ── fonts ───────────────────────────────────────────────────────────────────────────────
179
- const font = (over) => ({
186
+ const font = (over: Partial<RawFont>): RawFont => ({
180
187
  id: "f1",
181
188
  name: "ABCDEF+LinLibertineT",
182
189
  type: "Type1",
@@ -227,7 +234,7 @@ check(
227
234
  ]);
228
235
  check(
229
236
  "fonts: each font once, in the order pages first drew them",
230
- r.kind === "drawn" && r.list.length === 2 && r.list[1].name === "B",
237
+ r.kind === "drawn" && r.list.length === 2 && r.list[1]?.name === "B",
231
238
  );
232
239
  }
233
240
  {
@@ -89,7 +89,9 @@ function isLineNumber(w: Word, widthPt: number): boolean {
89
89
  */
90
90
  export function classifyLastPage(p: PageText): LastPage {
91
91
  if (!(p.widthPt > 0))
92
- throw new RangeError(`page width must be positive, got ${p.widthPt}`);
92
+ throw new RangeError(
93
+ `page width must be positive, got ${String(p.widthPt)}`,
94
+ );
93
95
  if (p.words.length < STUB_WORDS)
94
96
  return { kind: "stub", words: p.words.length };
95
97
  const lineNumbers = p.words.filter((w) => isLineNumber(w, p.widthPt)).length;
@@ -158,6 +160,10 @@ export type FontProgram =
158
160
  /** A program pdf.js named but this module does not know — kept verbatim, never guessed. */
159
161
  export type UnknownProgram = `unknown:${string}`;
160
162
 
163
+ const isUnknownProgram = (
164
+ p: FontProgram | UnknownProgram,
165
+ ): p is UnknownProgram => p.startsWith("unknown:");
166
+
161
167
  /**
162
168
  * One font the PDF draws text with.
163
169
  *
@@ -294,7 +300,5 @@ export const POPPLER_TYPE: Readonly<Record<FontProgram, string>> = {
294
300
  /** The type column for any fact, the unknown ones verbatim. */
295
301
  export function popplerType(f: FontFact): string {
296
302
  if (f.kind === "type3") return POPPLER_TYPE.Type3;
297
- return f.program.startsWith("unknown:")
298
- ? f.program
299
- : POPPLER_TYPE[f.program as FontProgram];
303
+ return isUnknownProgram(f.program) ? f.program : POPPLER_TYPE[f.program];
300
304
  }
@@ -2,26 +2,11 @@
2
2
  * The port `paperlint toolchain` installs and checks an external program through. One port, many
3
3
  * programs: banal implements it now, TeX Live in #76.
4
4
  */
5
- import type { Opaque } from "ts-essentials";
6
5
  import type { Result } from "../domain/result.ts";
7
6
  import type { Lines } from "../domain/text.ts";
7
+ import type { Ready } from "../domain/ready.ts";
8
8
 
9
- /**
10
- * Present, verified, running. BRANDED: a literal `{ where, fresh, verified }` is not a `Ready`, so
11
- * only an installer's adapter — one minting function each (banal's is `ready` in
12
- * `adapters/banal/index.ts`) — can make one, and a ready line cannot be printed without it.
13
- */
14
- export type Ready = Opaque<
15
- {
16
- /** Where it is: the program's path or its bin directory. */
17
- readonly where: string;
18
- /** This run installed it. */
19
- readonly fresh: boolean;
20
- /** What was checked before calling it ready, as a person reads it. */
21
- readonly verified: string;
22
- },
23
- "Ready"
24
- >;
9
+ export type { Ready };
25
10
 
26
11
  export interface ToolInstaller {
27
12
  /** The program as a person reads it: `banal 1.2 (HotCRP f3e4352)`. */
package/src/presets.ts CHANGED
@@ -39,7 +39,7 @@ import {
39
39
  type TexRequirements,
40
40
  type VenueFormat,
41
41
  } from "./tex-requirements.ts";
42
- import type { AbsolutePath } from "./domain/paths.ts";
42
+ import { callerPath } from "./caller-path.ts";
43
43
  import { err, ok, type Result } from "./domain/result.ts";
44
44
  import type { Files } from "./ports/files.ts";
45
45
  import {
@@ -47,7 +47,8 @@ import {
47
47
  type PaperSettings,
48
48
  type SettingsProblem,
49
49
  } from "./paper-settings.ts";
50
- import { CONFIG_FILE } from "../lib/paper-config.mjs";
50
+ import { CONFIG_FILE } from "#lib/paper-config";
51
+ import { messageOf } from "./domain/text.ts";
51
52
 
52
53
  /** The prefix of a shipped preset's spec. */
53
54
  export const SHIPPED_PREFIX = "paperlint:";
@@ -119,7 +120,7 @@ function readPreset(
119
120
  ): Result<{ file: string; preset: PresetFile }, PresetProblem> {
120
121
  const where = fileOf(spec, from, deps.venuesDir);
121
122
  if (!where.ok) return where;
122
- const bytes = deps.files.readBytes(where.value as AbsolutePath);
123
+ const bytes = deps.files.readBytes(callerPath(where.value));
123
124
  if (bytes === null)
124
125
  return err({ kind: "not-found", spec, file: where.value, shipped: [] });
125
126
  try {
@@ -132,7 +133,7 @@ function readPreset(
132
133
  return err({
133
134
  kind: "broken",
134
135
  file: where.value,
135
- why: (e as Error).message.split("\n").join(" "),
136
+ why: messageOf(e).split("\n").join(" "),
136
137
  });
137
138
  }
138
139
  }
@@ -165,14 +166,22 @@ export function mergeFormat(
165
166
  parent: VenueFormat,
166
167
  child: VenueFormat,
167
168
  ): VenueFormat {
168
- const keys = Object.keys(NO_FORMAT).filter((k) => k !== "kinds") as Exclude<
169
- keyof VenueFormat,
170
- "kinds"
171
- >[];
172
- const scalars = Object.fromEntries(
173
- keys.map((k) => [k, child[k] ?? parent[k]]),
174
- ) as Omit<VenueFormat, "kinds">;
175
- return { ...scalars, kinds: new Map([...parent.kinds, ...child.kinds]) };
169
+ // Every field listed: a VenueFormat literal missing one is a compile error.
170
+ const pick = <K extends Exclude<keyof VenueFormat, "kinds">>(
171
+ k: K,
172
+ ): VenueFormat[K] => child[k] ?? parent[k];
173
+ return {
174
+ pageWidthIn: pick("pageWidthIn"),
175
+ pageHeightIn: pick("pageHeightIn"),
176
+ columns: pick("columns"),
177
+ bodyPt: pick("bodyPt"),
178
+ bodyPtTol: pick("bodyPtTol"),
179
+ refPtMin: pick("refPtMin"),
180
+ refPtMax: pick("refPtMax"),
181
+ fontsText: pick("fontsText"),
182
+ fontsTitle: pick("fontsTitle"),
183
+ kinds: new Map([...parent.kinds, ...child.kinds]),
184
+ };
176
185
  }
177
186
 
178
187
  /** The chain, root first, merged into one preset. Pure. */
@@ -181,12 +190,18 @@ function merged(
181
190
  rootFirst: readonly PresetFile[],
182
191
  files: readonly string[],
183
192
  ): Preset {
184
- const base = {
185
- name: null as string | null,
186
- template: null as string | null,
193
+ const base: {
194
+ name: string | null;
195
+ template: string | null;
196
+ tex: TexRequirements;
197
+ format: VenueFormat;
198
+ rules: Readonly<Record<string, unknown>>;
199
+ } = {
200
+ name: null,
201
+ template: null,
187
202
  tex: NO_REQUIREMENTS,
188
203
  format: NO_FORMAT,
189
- rules: {} as Readonly<Record<string, unknown>>,
204
+ rules: {},
190
205
  };
191
206
  const m = rootFirst.reduce(
192
207
  (acc, p) => ({