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
@@ -20,7 +20,9 @@ import { afterAll as after, test } from "vitest";
20
20
  import { tmpWorkspace } from "./workspace.io.ts";
21
21
 
22
22
  const root = realpathSync(mkdtempSync(join(tmpdir(), "paperlint-ws-test-")));
23
- after(() => rmSync(root, { recursive: true, force: true }));
23
+ after(() => {
24
+ rmSync(root, { recursive: true, force: true });
25
+ });
24
26
 
25
27
  test("the directory exists inside the scope and is gone after it", () => {
26
28
  const ws = tmpWorkspace(root);
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * `fill.ts` — what is recovered from pdf.js's exports for each text item: whether it is upright, and
3
3
  * the fill it was drawn in (the operator-list walk). Operator lists here are written by hand; the
4
- * real-PDF half runs in `pdf-facts.harness.mjs` and `test/e2e/banal.mjs`.
4
+ * real-PDF half runs in `pdf-facts.harness.ts` and `test/e2e/banal.ts`.
5
5
  */
6
6
  import assert from "node:assert/strict";
7
7
  import { test } from "vitest";
@@ -34,7 +34,7 @@ const OPS = {
34
34
  showText: 5,
35
35
  showSpacedText: 6,
36
36
  };
37
- const glyphs = (s: string) => [[...s].map((unicode) => ({ unicode }))];
37
+ const glyphs = (s: string) => [Array.from(s, (unicode) => ({ unicode }))];
38
38
  const ops = (...pairs: [number, unknown?][]): OperatorList => ({
39
39
  fnArray: pairs.map((p) => p[0]),
40
40
  argsArray: pairs.map((p) => p[1] ?? null),
@@ -33,9 +33,12 @@ export interface OperatorList {
33
33
  /** Text as the walk compares it: normalised, with no whitespace (items and glyphs split spaces differently). */
34
34
  const squeeze = (s: string): string => s.normalize("NFKC").replace(/\s+/g, "");
35
35
 
36
+ /** An operator's argument list; pdf.js types it `any`, and a malformed one reads as empty. */
37
+ const listOf = (v: unknown): readonly unknown[] => (Array.isArray(v) ? v : []);
38
+
36
39
  /** `setFillRGBColor`'s argument: a `#rrggbb` string in current pdf.js, three 0-255 numbers in older ones. */
37
40
  export function hexOf(args: unknown): string {
38
- const a = Array.isArray(args) ? (args as unknown[]) : [];
41
+ const a = listOf(args);
39
42
  if (typeof a[0] === "string") return a[0].toLowerCase();
40
43
  const byte = (v: unknown): string =>
41
44
  Math.round(typeof v === "number" ? v : 0)
@@ -46,12 +49,13 @@ export function hexOf(args: unknown): string {
46
49
 
47
50
  /** The characters a show-text operator draws, from its glyph objects. */
48
51
  function glyphText(args: unknown): string {
49
- const a = Array.isArray(args) ? (args as unknown[]) : [];
50
- const glyphs = Array.isArray(a[0]) ? (a[0] as unknown[]) : [];
52
+ const glyphs = listOf(listOf(args)[0]);
51
53
  return glyphs
52
54
  .map((g) =>
53
55
  typeof g === "object" && g !== null && "unicode" in g
54
- ? String((g as { unicode: unknown }).unicode ?? "")
56
+ ? typeof g.unicode === "string"
57
+ ? g.unicode
58
+ : ""
55
59
  : "",
56
60
  )
57
61
  .join("");
@@ -76,7 +80,7 @@ function applyState(
76
80
  else if (fn === ops.setFillRGBColor)
77
81
  s.cur.fill = { kind: "rgb", hex: hexOf(args) };
78
82
  else if (fn === ops.setTextRenderingMode)
79
- s.cur.mode = Number((args as unknown[] | undefined)?.[0] ?? 0);
83
+ s.cur.mode = Number(listOf(args)[0] ?? 0);
80
84
  else return false;
81
85
  return true;
82
86
  }
@@ -89,7 +93,10 @@ interface Drawn {
89
93
 
90
94
  /** Every drawn character, in content order, with the fill it was drawn in. */
91
95
  export function drawnCharacters(ops: ColourOps, list: OperatorList): Drawn[] {
92
- const s = { cur: { fill: { kind: "unknown" } as Fill, mode: 0 }, stack: [] };
96
+ const s: { cur: State; stack: State[] } = {
97
+ cur: { fill: { kind: "unknown" }, mode: 0 },
98
+ stack: [],
99
+ };
93
100
  const drawn: Drawn[] = [];
94
101
  list.fnArray.forEach((fn, i) => {
95
102
  const args = list.argsArray[i];
@@ -149,7 +156,7 @@ export function fillsFor<T>(
149
156
  const drawn = drawnCharacters(ops, list);
150
157
  let at = 0;
151
158
  const fillOf = (text: string): Fill => {
152
- const want = [...squeeze(text)];
159
+ const want = Array.from(squeeze(text));
153
160
  if (want.length === 0) return { kind: "unknown" };
154
161
  const hit = findRun(drawn, want, at);
155
162
  if (hit === null) return { kind: "unknown" };
@@ -10,7 +10,6 @@ import {
10
10
  EMPTY_LOOKUP_CACHE,
11
11
  type LookupCache,
12
12
  } from "../../domain/lookup-cache.ts";
13
- // @ts-expect-error — a skill script in .mjs, it has no types
14
13
  import * as cites from "../../../skills/verify-citations/scripts/verify-cites.mjs";
15
14
 
16
15
  /** The verdicts of a cold run — an empty cache, the adapter the CLI wires. */
@@ -46,29 +45,29 @@ const dblp = (title: string, authors: string[]) =>
46
45
  /** Route by URL prefix; the longest prefix wins. */
47
46
  function fakeFetch(routes: Record<string, () => Response>): string[] {
48
47
  const calls: string[] = [];
49
- vi.stubGlobal("fetch", async (url: string) => {
48
+ vi.stubGlobal("fetch", (url: string) => {
50
49
  calls.push(url);
51
- const hit = Object.keys(routes)
52
- .filter((p) => url.startsWith(p))
53
- .sort((a, b) => b.length - a.length)[0];
54
- if (hit === undefined) return json(404);
55
- return routes[hit]!();
50
+ const hit = Object.entries(routes)
51
+ .filter(([p]) => url.startsWith(p))
52
+ .sort(([a], [b]) => b.length - a.length)[0];
53
+ return Promise.resolve(hit === undefined ? json(404) : hit[1]());
56
54
  });
57
55
  return calls;
58
56
  }
59
57
 
60
58
  /** Await `p` while fake time runs, so DBLP's pauses between requests elapse at once. */
61
59
  async function settle<T>(p: Promise<T>): Promise<T> {
62
- let done = false;
63
- const out = p.finally(() => (done = true));
64
- while (!done) await vi.advanceTimersByTimeAsync(1000);
60
+ // A field, not a `let`: the flag is set in a callback, which flow analysis cannot see.
61
+ const state = { done: false };
62
+ const out = p.finally(() => (state.done = true));
63
+ while (!state.done) await vi.advanceTimersByTimeAsync(1000);
65
64
  return out;
66
65
  }
67
66
 
68
67
  test("no service answers: not-checked, with the reason", async () => {
69
- vi.stubGlobal("fetch", async () => {
70
- throw new Error("getaddrinfo ENOTFOUND api.crossref.org");
71
- });
68
+ vi.stubGlobal("fetch", () =>
69
+ Promise.reject(new Error("getaddrinfo ENOTFOUND api.crossref.org")),
70
+ );
72
71
  assert.deepEqual(await checkOnly("@misc{k, title={T}}"), {
73
72
  kind: "not-checked",
74
73
  why: "the citation services cannot be reached (getaddrinfo ENOTFOUND api.crossref.org)",
@@ -104,19 +103,19 @@ test("each entry gets its existence and its authors: confirmed, fabricated, mism
104
103
  const r = await settle(checkOnly(bib));
105
104
  assert.equal(r.kind, "checked");
106
105
  const byKey = Object.fromEntries(
107
- (r.kind === "checked" ? r.entries : []).map((e) => [
108
- e.key,
109
- [e.exists, e.authors, e.why ?? ""],
110
- ]),
106
+ r.entries.map((e) => [e.key, [e.exists, e.authors, e.why ?? ""]]),
111
107
  );
112
108
  assert.deepEqual(
113
109
  {
114
- good: byKey["good"]!.slice(0, 2),
115
- fake: byKey["fake"]!.slice(0, 2),
116
- drift: byKey["drift"]!.slice(0, 2),
117
- driftWhy: String(byKey["drift"]![2]).split("; ").pop(),
118
- flaky: [byKey["flaky"]![1], String(byKey["flaky"]![2]).split("; ").pop()],
119
- pre: byKey["pre"]!.slice(0, 2),
110
+ good: byKey["good"]?.slice(0, 2),
111
+ fake: byKey["fake"]?.slice(0, 2),
112
+ drift: byKey["drift"]?.slice(0, 2),
113
+ driftWhy: String(byKey["drift"]?.[2]).split("; ").pop(),
114
+ flaky: [
115
+ byKey["flaky"]?.[1],
116
+ String(byKey["flaky"]?.[2]).split("; ").pop(),
117
+ ],
118
+ pre: byKey["pre"]?.slice(0, 2),
120
119
  },
121
120
  {
122
121
  good: ["true", "match"],
@@ -209,20 +208,14 @@ test(
209
208
  slowFetch(40);
210
209
  const bib = manyBib(20);
211
210
  const serial: string[] = [];
212
- for (const c of cites.parseBib(bib) as { id: string }[])
213
- serial.push(
214
- (
215
- (await cites.verifyCitationLive(c, { cache: {} })) as {
216
- verdict: string;
217
- }
218
- ).verdict,
219
- );
211
+ for (const c of cites.parseBib(bib))
212
+ serial.push((await cites.verifyCitationLive(c, { cache: {} })).verdict);
220
213
  const t0 = Date.now();
221
214
  const r = await checkOnly(bib);
222
215
  const ms = Date.now() - t0;
223
216
  assert.equal(r.kind, "checked");
224
217
  assert.deepEqual(
225
- r.kind === "checked" ? r.entries.map((e) => e.exists) : [],
218
+ r.entries.map((e) => e.exists),
226
219
  serial,
227
220
  );
228
221
  assert.ok(ms < (20 * 200) / 2, `${String(ms)} ms for 20 lookups`);
@@ -249,7 +242,7 @@ const PAPERS = [
249
242
  "@inproceedings{good, author={Ada Lovelace}, title={Good Paper}, booktitle={ICSE}, doi={10.1/good}}",
250
243
  "@inproceedings{other, author={Ada Lovelace}, title={Other Paper}, booktitle={ICSE}}",
251
244
  "@misc{pre, author={Ada Lovelace}, title={A Preprint}, journal={arXiv preprint}}",
252
- ];
245
+ ] as const;
253
246
  /** Every service answers; DBLP knows "Good Paper" and nothing else. */
254
247
  const answering = () =>
255
248
  fakeFetch({
@@ -414,7 +407,7 @@ test("a failed lookup is not cached: the next run asks it again", async () => {
414
407
  "https://api.crossref.org/": () => json(200, { message: { items: [] } }),
415
408
  "https://dblp.org/": () => json(500),
416
409
  });
417
- const bib = PAPERS[1]!;
410
+ const bib = PAPERS[1];
418
411
  const r = await settle(cached(bib, EMPTY_LOOKUP_CACHE));
419
412
  const failed: LookupCache = r.cache;
420
413
  assert.deepEqual(
@@ -427,10 +420,10 @@ test("a failed lookup is not cached: the next run asks it again", async () => {
427
420
  });
428
421
 
429
422
  test("offline with an incomplete cache: not-checked, and the cache comes back unchanged", async () => {
430
- vi.stubGlobal("fetch", async () => {
431
- throw new Error("getaddrinfo ENOTFOUND api.crossref.org");
432
- });
433
- const r = await cached(PAPERS[0]!, EMPTY_LOOKUP_CACHE);
423
+ vi.stubGlobal("fetch", () =>
424
+ Promise.reject(new Error("getaddrinfo ENOTFOUND api.crossref.org")),
425
+ );
426
+ const r = await cached(PAPERS[0], EMPTY_LOOKUP_CACHE);
434
427
  assert.deepEqual(r, {
435
428
  check: {
436
429
  kind: "not-checked",
@@ -35,29 +35,16 @@ import { sleep, todayUtc } from "./clock.io.ts";
35
35
 
36
36
  /** Citations looked up at once (#107). */
37
37
  const LOOKUPS_IN_FLIGHT = 6;
38
- // @ts-expect-error — a skill script in .mjs, it has no types
39
38
  import * as cites from "../../../skills/verify-citations/scripts/verify-cites.mjs";
40
- // @ts-expect-error — a skill script in .mjs, it has no types
39
+ import type { CiteVerdict } from "../../../skills/verify-citations/scripts/verify-cites.mjs";
41
40
  import * as authors from "../../../skills/verify-citations/scripts/bib-authors.mjs";
42
-
43
- interface CiteResult {
44
- readonly id: string;
45
- readonly verdict: "true" | "false" | "unresolvable";
46
- readonly reason?: string;
47
- /** "<service>: <reason>" for each question a refusing service did not answer (#120). */
48
- readonly refused?: readonly string[];
49
- }
50
- interface AuthorFinding {
51
- readonly key: string;
52
- readonly venue: string;
53
- readonly missing: readonly string[];
54
- readonly extra: readonly string[];
55
- readonly orderDiffers: boolean;
56
- }
57
- interface KeyWhy {
58
- readonly key: string;
59
- readonly why: string;
60
- }
41
+ import type {
42
+ AuthorBuckets,
43
+ AuthorFinding,
44
+ BibEntry,
45
+ } from "../../../skills/verify-citations/scripts/bib-authors.mjs";
46
+ import { messageOf } from "../../domain/text.ts";
47
+ import { isRecord } from "../../domain/record.ts";
61
48
 
62
49
  const describeAuthors = (f: AuthorFinding): string =>
63
50
  [
@@ -68,13 +55,6 @@ const describeAuthors = (f: AuthorFinding): string =>
68
55
  .filter(Boolean)
69
56
  .join("; ") + ` (DBLP: ${f.venue})`;
70
57
 
71
- interface AuthorBuckets {
72
- readonly findings: readonly AuthorFinding[];
73
- readonly skipped: readonly KeyWhy[];
74
- readonly unchecked: readonly KeyWhy[];
75
- readonly matched: readonly string[];
76
- }
77
-
78
58
  const authorsOf = (key: string, a: AuthorBuckets): EntryVerdict["authors"] =>
79
59
  a.findings.some((f) => f.key === key)
80
60
  ? "mismatch"
@@ -90,7 +70,7 @@ const authorsOf = (key: string, a: AuthorBuckets): EntryVerdict["authors"] =>
90
70
  * confirmed work: a refusal after a confirmation changed nothing.
91
71
  */
92
72
  const unconfirmed = (
93
- c: CiteResult | undefined,
73
+ c: CiteVerdict | undefined,
94
74
  ): readonly (string | undefined)[] =>
95
75
  c && c.verdict !== "true"
96
76
  ? [c.reason, ...(c.refused ?? []).map((r) => `not asked: ${r}`)]
@@ -99,7 +79,7 @@ const unconfirmed = (
99
79
  /** One entry's verdict from the two checkers' answers. */
100
80
  function entryVerdict(
101
81
  key: string,
102
- found: readonly CiteResult[],
82
+ found: readonly CiteVerdict[],
103
83
  a: AuthorBuckets,
104
84
  ): EntryVerdict {
105
85
  const c = found.find((x) => x.id === key);
@@ -119,27 +99,21 @@ function entryVerdict(
119
99
  };
120
100
  }
121
101
 
122
- interface BibAuthorsEntry {
123
- readonly key: string;
124
- readonly title: string;
125
- readonly author: string;
126
- }
127
-
128
102
  /** Whether bib-authors will ask DBLP about this entry — the filter its `checkAuthors` applies. */
129
- const asksDblp = (e: BibAuthorsEntry): boolean =>
103
+ const asksDblp = (e: BibEntry): boolean =>
130
104
  Boolean(e.title && e.author) &&
131
- (authors.claimsPublished(e) as boolean) &&
132
- !(authors.truncated(e.author) as boolean);
105
+ authors.claimsPublished(e) &&
106
+ !authors.truncated(e.author);
133
107
 
134
108
  /** Is every question this bibliography would ask already answered in `cache`? */
135
109
  async function fullyCached(
136
- citations: readonly object[],
137
- entries: readonly BibAuthorsEntry[],
110
+ citations: readonly cites.Citation[],
111
+ entries: readonly BibEntry[],
138
112
  cache: LookupCache,
139
113
  store: Readonly<Record<string, unknown>>,
140
114
  ): Promise<boolean> {
141
115
  const asks = await Promise.all(
142
- citations.map((c) => cites.wouldAsk(c, store) as Promise<boolean>),
116
+ citations.map((c) => cites.wouldAsk(c, store)),
143
117
  );
144
118
  return (
145
119
  !asks.includes(true) &&
@@ -185,11 +159,11 @@ function cachedDblp(cache: LookupCache, today: () => string) {
185
159
  if (refused !== null)
186
160
  throw new Error(`DBLP refused earlier in this run (${refused})`);
187
161
  try {
188
- const hits = (await authors.dblpHits(title)) as DblpHit[];
162
+ const hits = await authors.dblpHits(title);
189
163
  dblp.set(dblpTitleKey(title), { fetched: today(), title, hits });
190
164
  return hits;
191
165
  } catch (e) {
192
- refused = (e as Error).message;
166
+ refused = messageOf(e);
193
167
  throw new Error(`DBLP refused earlier in this run (${refused})`);
194
168
  }
195
169
  },
@@ -215,12 +189,16 @@ function grown(
215
189
  ): LookupCache {
216
190
  const { cache, usable } = loaded;
217
191
  const { store, dblp } = run;
218
- const fresh = Object.entries(store).filter(([k]) => !usable.citations.has(k));
192
+ // verify-cites stores each answer as an object; only such a value is a response the cache can hold.
193
+ const fresh = Object.entries(store).filter(
194
+ (e): e is [string, Readonly<Record<string, unknown>>] =>
195
+ !usable.citations.has(e[0]) && isRecord(e[1]),
196
+ );
219
197
  const freshDblp = [...dblp].filter(([k]) => !usable.dblp.has(k));
220
198
  if (fresh.length === 0 && freshDblp.length === 0) return cache;
221
199
  const dated = fresh.map(([k, response]): [string, CachedResponse] => [
222
200
  k,
223
- { fetched: today(), response: response as CachedResponse["response"] },
201
+ { fetched: today(), response },
224
202
  ]);
225
203
  return {
226
204
  citations: new Map([...cache.citations, ...dated]),
@@ -234,10 +212,8 @@ export const referencesChecker =
234
212
  async (bib, cache) => {
235
213
  // Answers past MAX_AGE_DAYS are left out, so the run asks them again (and `grown` re-dates them).
236
214
  const usable = freshPart(cache, today());
237
- const citations = (cites.parseBib(bib) as { id?: string }[]).filter(
238
- (c) => c.id,
239
- );
240
- const parsed = authors.parseBib(bib) as BibAuthorsEntry[];
215
+ const citations = cites.parseBib(bib);
216
+ const parsed = authors.parseBib(bib);
241
217
  const store: Record<string, unknown> = Object.fromEntries(
242
218
  [...usable.citations].map(([k, v]) => [k, v.response]),
243
219
  );
@@ -252,22 +228,19 @@ export const referencesChecker =
252
228
  // answer.
253
229
  // One breaker for the run: a service that refuses is not asked again, not once per citation
254
230
  // (#120). Requests already in flight when it starts refusing still land — at most six.
255
- const breaker = cites.createBreaker() as unknown;
231
+ const breaker = cites.createBreaker();
256
232
  const d = cachedDblp(usable, today);
257
233
  const [found, a] = await Promise.all([
258
- mapLimit(
259
- citations,
260
- LOOKUPS_IN_FLIGHT,
261
- (c) =>
262
- cites.verifyCitationLive(c, {
263
- cache: store,
264
- breaker,
265
- }) as Promise<CiteResult>,
234
+ mapLimit(citations, LOOKUPS_IN_FLIGHT, (c) =>
235
+ cites.verifyCitationLive(c, {
236
+ cache: store,
237
+ breaker,
238
+ }),
266
239
  ),
267
240
  authors.checkAuthors(parsed, {
268
241
  lookup: d.lookup,
269
242
  pause: d.pause,
270
- }) as Promise<AuthorBuckets>,
243
+ }),
271
244
  ]);
272
245
  const keys = new Set([
273
246
  ...found.map((c) => c.id),
@@ -10,12 +10,10 @@ export async function mapLimit<T, R>(
10
10
  fn: (item: T) => Promise<R>,
11
11
  ): Promise<R[]> {
12
12
  const out: R[] = new Array<R>(items.length);
13
- let next = 0;
13
+ // ONE iterator shared by every worker: each `next()` hands out the next index exactly once.
14
+ const queue = items.entries();
14
15
  const worker = async (): Promise<void> => {
15
- while (next < items.length) {
16
- const i = next++;
17
- out[i] = await fn(items[i] as T);
18
- }
16
+ for (const [i, item] of queue) out[i] = await fn(item);
19
17
  };
20
18
  await Promise.all(
21
19
  Array.from({ length: Math.min(limit, items.length) }, worker),
@@ -1,3 +1,4 @@
1
+ import { messageOf } from "../../domain/text.ts";
1
2
  /** Whether the citation services answer at all — the one request that decides "not checked". */
2
3
  export async function unreachable(): Promise<string | null> {
3
4
  try {
@@ -7,6 +8,6 @@ export async function unreachable(): Promise<string | null> {
7
8
  });
8
9
  return null;
9
10
  } catch (e) {
10
- return `the citation services cannot be reached (${(e as Error).message})`;
11
+ return `the citation services cannot be reached (${messageOf(e)})`;
11
12
  }
12
13
  }
@@ -19,7 +19,7 @@ import {
19
19
  import { tmpdir } from "node:os";
20
20
  import { basename, delimiter, dirname, join } from "node:path";
21
21
  import { fileURLToPath } from "node:url";
22
- import { createChecker } from "../lib/check.mjs";
22
+ import { createChecker } from "../lib/check.ts";
23
23
 
24
24
  const HERE = dirname(fileURLToPath(import.meta.url));
25
25
  const E = await import(join(HERE, "build-engine.ts"));
@@ -7,8 +7,9 @@ import assert from "node:assert/strict";
7
7
  import { chmodSync, mkdirSync, writeFileSync } from "node:fs";
8
8
  import { dirname, join } from "node:path";
9
9
  import { test } from "vitest";
10
- import { useTempDir } from "../test/support.mjs";
10
+ import { useTempDir } from "../test/support.ts";
11
11
  import { prepareEngine, refusal } from "./build-engine.ts";
12
+ import type { Runner } from "./engine.ts";
12
13
 
13
14
  const work = useTempDir("paperlint-build-engine-test-");
14
15
  const TEX = { packages: { acmart: ["acmart.cls"] }, tools: {} };
@@ -20,11 +21,12 @@ const texAt = (dir: string): string => {
20
21
  return dir;
21
22
  };
22
23
  /** kpsewhich answers per bin directory. */
23
- const fakeRun = (found: Record<string, string>) =>
24
- ((cmd: string) => ({
24
+ const fakeRun =
25
+ (found: Record<string, string>): Runner =>
26
+ (cmd) => ({
25
27
  stdout: found[dirname(cmd)] ?? "",
26
28
  status: 0,
27
- })) as never;
29
+ });
28
30
 
29
31
  async function go(o: Parameters<typeof prepareEngine>[0]) {
30
32
  const out: string[] = [];
@@ -34,7 +36,7 @@ async function go(o: Parameters<typeof prepareEngine>[0]) {
34
36
  err: (l) => err.push(l),
35
37
  platform: "linux",
36
38
  interactive: true,
37
- ask: async () => "y",
39
+ ask: () => Promise.resolve("y"),
38
40
  ...o,
39
41
  });
40
42
  return { r, out, err };
@@ -101,7 +103,10 @@ test("a complete cache is used from an environment with no PATH at all", async (
101
103
 
102
104
  test("the refusal line for a decision to use a tree names nothing missing", () => {
103
105
  assert.equal(
104
- refusal({ kind: "use-cache" } as never, { system: null } as never),
106
+ refusal(
107
+ { kind: "use-cache", tree: { label: "cache", bin: "/b", missing: [] } },
108
+ { system: null },
109
+ ),
105
110
  "✗ paperlint build: no TeX Live with every package these papers need — run `npx paperlint toolchain` (missing: 0 package(s): )",
106
111
  );
107
112
  });
@@ -122,14 +127,14 @@ test("without an ask or an install given: Enter is yes, and the default install
122
127
  PAPERLINT_CTAN_MIRROR: "https://mirror.invalid/tlnet",
123
128
  PATH: join(work, "nothing"),
124
129
  },
125
- run: ((cmd: string) => {
130
+ run: (cmd) => {
126
131
  calls.push(cmd.split("/").pop() ?? cmd);
127
132
  return {
128
133
  status: 22,
129
134
  stdout: "",
130
135
  stderr: "curl: (6) could not resolve\n",
131
136
  };
132
- }) as never,
137
+ },
133
138
  });
134
139
  assert.deepEqual(r, { ok: false, code: 1 });
135
140
  assert.ok(calls.includes("curl"), calls.join(","));
@@ -62,16 +62,18 @@ type Resolved = Required<Omit<EngineOptions, "install">> & {
62
62
  };
63
63
 
64
64
  /** The options a caller actually set — `undefined` must still get its default, so no plain spread. */
65
- const defined = <T extends object>(o: T): Partial<T> =>
66
- Object.fromEntries(
67
- Object.entries(o).filter(([, v]) => v !== undefined),
68
- ) as Partial<T>;
65
+ function defined<T extends object>(o: T): Partial<T> {
66
+ const out: Partial<T> = {};
67
+ // `for…in` hands out `o`'s keys typed as its keys; an options literal has no inherited ones.
68
+ for (const k in o) if (o[k] !== undefined) out[k] = o[k];
69
+ return out;
70
+ }
69
71
 
70
72
  function withDefaults(o: EngineOptions): Resolved {
71
73
  const r = {
72
74
  dryRun: false,
73
75
  interactive: false,
74
- ask: async () => "",
76
+ ask: () => Promise.resolve(""),
75
77
  log: console.log,
76
78
  err: console.error,
77
79
  // eslint-disable-next-line no-restricted-globals -- legacy I/O, moves behind a port in #76
@@ -130,10 +132,13 @@ export function gatherFacts(o: Resolved): EngineFacts {
130
132
  }
131
133
 
132
134
  const names = (missing: readonly string[]): string =>
133
- `${missing.length} package(s): ${missing.join(", ")}`;
135
+ `${String(missing.length)} package(s): ${missing.join(", ")}`;
134
136
 
135
137
  /** The one line a run without a human gets. It names the command and what is missing. */
136
- export function refusal(d: EngineDecision, facts: EngineFacts): string {
138
+ export function refusal(
139
+ d: EngineDecision,
140
+ facts: Pick<EngineFacts, "system">,
141
+ ): string {
137
142
  const missing = d.kind === "refuse" || d.kind === "ask" ? d.missing : [];
138
143
  const sys =
139
144
  facts.system && facts.system.missing.length
@@ -4,7 +4,7 @@
4
4
  * The process runner is replaced by a fake that writes what pdflatex and bibtex would write, so
5
5
  * these assertions check the SHELL's decisions: what is run, with which environment, in which
6
6
  * order, and what is left on disk. Whether a real pdflatex produces a real PDF is the other half,
7
- * `test/e2e/build.mjs`.
7
+ * `test/e2e/build.ts`.
8
8
  *
9
9
  * 🔴 THE TWO THINGS PINNED DOWN HERE THAT A RETURNED OBJECT CANNOT SHOW: a paper-supplied
10
10
  * `build.sh` is never executed (checked by the trace it would leave on disk and by the list of
@@ -22,7 +22,7 @@ import {
22
22
  import { tmpdir } from "node:os";
23
23
  import { join, dirname, delimiter } from "node:path";
24
24
  import { fileURLToPath } from "node:url";
25
- import { createChecker } from "../lib/check.mjs";
25
+ import { createChecker } from "../lib/check.ts";
26
26
 
27
27
  const HERE = dirname(fileURLToPath(import.meta.url));
28
28
  const {
package/src/build.test.ts CHANGED
@@ -8,7 +8,7 @@ import assert from "node:assert/strict";
8
8
  import { existsSync, writeFileSync } from "node:fs";
9
9
  import { join } from "node:path";
10
10
  import { test } from "vitest";
11
- import { useTempDir, writeTree } from "../test/support.mjs";
11
+ import { present, useTempDir, writeTree } from "../test/support.ts";
12
12
  import {
13
13
  bibInput,
14
14
  buildPaper,
@@ -62,18 +62,18 @@ function bibtexRun(f: Fake, cwd: string) {
62
62
  ? { status: f.bibtex }
63
63
  : { status: f.bibtex, stdout: f.bibtexOut };
64
64
  }
65
- function fakeRun(f: Fake): BuildOptions["run"] {
65
+ function fakeRun(f: Fake): NonNullable<BuildOptions["run"]> {
66
66
  let pass = 0;
67
- return ((bin: string, _args: string[], opts: { cwd: string }) => {
67
+ return (bin, _args, opts) => {
68
68
  if (bin === f.missing)
69
69
  return {
70
70
  error: Object.assign(new Error("spawn ENOENT"), { code: "ENOENT" }),
71
71
  status: null,
72
72
  };
73
- return bin === "pdflatex"
74
- ? latexPass(f, opts.cwd, ++pass)
75
- : bibtexRun(f, opts.cwd);
76
- }) as never;
73
+ // The build always runs a pass in the paper's directory.
74
+ const cwd = present(opts.cwd, "the pass's cwd");
75
+ return bin === "pdflatex" ? latexPass(f, cwd, ++pass) : bibtexRun(f, cwd);
76
+ };
77
77
  }
78
78
 
79
79
  const NO_GEOMETRY: Geometry = {
@@ -103,18 +103,19 @@ const page = (words: number, numbered = 0) => ({
103
103
  });
104
104
  const reader =
105
105
  (last = page(80)): PdfReader =>
106
- async () => ({
107
- ok: true,
108
- facts: {
109
- pages: 1,
110
- fonts: {
111
- kind: "drawn",
112
- list: [{ kind: "embedded", name: "CMR10", program: "Type1" }],
106
+ () =>
107
+ Promise.resolve({
108
+ ok: true,
109
+ facts: {
110
+ pages: 1,
111
+ fonts: {
112
+ kind: "drawn",
113
+ list: [{ kind: "embedded", name: "CMR10", program: "Type1" }],
114
+ },
115
+ last,
116
+ layout: [],
113
117
  },
114
- last,
115
- layout: [],
116
- },
117
- });
118
+ });
118
119
  const quiet = () => {};
119
120
  /** buildPaper with fakes; the verdict line is what the command would print for it. */
120
121
  async function build(dir: string, f: Fake = {}, over: BuildOptions = {}) {
@@ -145,11 +146,10 @@ test("documentclass: an empty name is no class; a macro inside the name contribu
145
146
  // `\documentclass` came back bare has no name to read, and is no class.
146
147
  assert.equal(
147
148
  parseDocumentclass("\\documentclass", {
148
- parse: () =>
149
- ({
150
- type: "root",
151
- content: [{ type: "macro", content: "documentclass" }],
152
- }) as never,
149
+ parse: () => ({
150
+ type: "root",
151
+ content: [{ type: "macro", content: "documentclass" }],
152
+ }),
153
153
  }),
154
154
  null,
155
155
  );
@@ -332,7 +332,7 @@ test("paths: a paper that IS the working directory is named by its full path", a
332
332
  const run = await buildPapers([dir], {
333
333
  cwd: dir,
334
334
  log: (l) => lines.push(l),
335
- engine: async () => null,
335
+ engine: () => Promise.resolve(null),
336
336
  });
337
337
  assert.deepEqual(
338
338
  [run, lines],