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
package/src/init.test.ts CHANGED
@@ -34,7 +34,7 @@ import {
34
34
  reportTexLive,
35
35
  reportWorkflow,
36
36
  } from "./init.ts";
37
- import { runNode } from "../test/support.mjs";
37
+ import { runNode } from "../test/support.ts";
38
38
 
39
39
  const dirs: string[] = [];
40
40
  afterEach(() => {
@@ -70,7 +70,7 @@ describe("paperlint init — a declared papers directory", () => {
70
70
  err: (s: string) => out.push(s),
71
71
  interactive: false,
72
72
  hooks: false,
73
- run: (() => ({ status: 0 })) as never,
73
+ run: () => ({ status: 0 }),
74
74
  link: () => ({ ok: false, error: "not linked in this test" }),
75
75
  });
76
76
  const text = out.join("\n");
@@ -81,7 +81,9 @@ describe("paperlint init — a declared papers directory", () => {
81
81
  expect(code).toBe(0);
82
82
  expect(section).toMatch(/✓ papers — declared in paperlint\.json/);
83
83
  expect(section).not.toMatch(/candidates|eslint-rules|drafts/);
84
- const cfg = JSON.parse(readFileSync(join(root, "paperlint.json"), "utf8"));
84
+ const cfg: unknown = JSON.parse(
85
+ readFileSync(join(root, "paperlint.json"), "utf8"),
86
+ );
85
87
  expect(cfg).toEqual({ papersDir: "papers" });
86
88
  });
87
89
 
@@ -93,9 +95,9 @@ describe("paperlint init — a declared papers directory", () => {
93
95
  log: () => {},
94
96
  err: () => {},
95
97
  interactive: true,
96
- ask: async (q) => (asked.push(q), "n"),
98
+ ask: (q) => Promise.resolve((asked.push(q), "n")),
97
99
  hooks: false,
98
- run: (() => ({ status: 0 })) as never,
100
+ run: () => ({ status: 0 }),
99
101
  link: () => ({ ok: false, error: "not linked in this test" }),
100
102
  });
101
103
  expect(asked.filter((q) => /papers roots/.test(q))).toEqual([]);
@@ -114,7 +116,7 @@ describe("paperlint init — writes paperlint.json only when something differs f
114
116
  err: () => {},
115
117
  interactive: false,
116
118
  hooks: false,
117
- run: (() => ({ status: 0 })) as never,
119
+ run: () => ({ status: 0 }),
118
120
  link: () => ({ ok: false as const, error: "not linked in this test" }),
119
121
  };
120
122
  const fresh = (files: Record<string, string>): string => {
@@ -165,9 +167,10 @@ const texRun = async (
165
167
  log: (s: string) => out.push(s),
166
168
  err: () => {},
167
169
  interactive,
168
- ask: async (q) => (asked.push(q), /TeX Live/.test(q) ? answer : ""),
170
+ ask: (q) =>
171
+ Promise.resolve((asked.push(q), /TeX Live/.test(q) ? answer : "")),
169
172
  hooks: false,
170
- run: (() => ({ status: 0 })) as never,
173
+ run: () => ({ status: 0 }),
171
174
  link: () => ({ ok: false as const, error: "not linked in this test" }),
172
175
  tex,
173
176
  });
@@ -246,7 +249,7 @@ async function initRun(
246
249
  err: (s: string) => err.push(s),
247
250
  interactive: false,
248
251
  hooks: false,
249
- run: (() => ({ status: 0 })) as never,
252
+ run: () => ({ status: 0 }),
250
253
  link: () => ({ ok: false, error: "not linked in this test" }),
251
254
  ...opts,
252
255
  });
@@ -277,6 +280,26 @@ describe("paperlint init — a paperlint.json it cannot use stops it before anyt
277
280
  ]);
278
281
  });
279
282
 
283
+ it("🔴 a paperlint.json holding JSON that is not an object is refused, not read as settings", async () => {
284
+ // Guards: `null` threw a TypeError reading the papers field; `[1]` was spread into a settings
285
+ // object and the consumer's file rewritten as `{ "0": 1, "papersDir": … }`.
286
+ for (const json of ["null\n", "[1]\n"]) {
287
+ const root = tree({
288
+ "paperlint.json": json,
289
+ "drafts/a/paper.md": "# a\n",
290
+ });
291
+ const decl = await declarePapers(root, () =>
292
+ choosePapers(root, { interactive: false }),
293
+ );
294
+ expect(decl).toEqual({
295
+ status: "unparsable",
296
+ path: join(root, "paperlint.json"),
297
+ reason: "not a JSON object",
298
+ });
299
+ expect(readFileSync(join(root, "paperlint.json"), "utf8")).toBe(json);
300
+ }
301
+ });
302
+
280
303
  it("a file without a trailing newline keeps having none", async () => {
281
304
  const root = tree({
282
305
  "paperlint.json": '{"x":1}',
@@ -301,7 +324,7 @@ describe("paperlint init — how several candidate directories were decided, sai
301
324
  it("picked by a human", async () => {
302
325
  const r = await initRun(several(), {
303
326
  interactive: true,
304
- ask: async () => "2",
327
+ ask: () => Promise.resolve("2"),
305
328
  });
306
329
  expect(section(r.out)).toEqual([
307
330
  " ✓ writing — you picked it out of 2 candidates",
@@ -319,7 +342,7 @@ describe("paperlint init — how several candidate directories were decided, sai
319
342
  it("an answer that picks nothing: the first, and said so", async () => {
320
343
  const r = await initRun(several(), {
321
344
  interactive: true,
322
- ask: async () => "nine",
345
+ ask: () => Promise.resolve("nine"),
323
346
  });
324
347
  expect(section(r.out)[0]).toBe(
325
348
  " ✓ drafts — 2 candidates, no answer was given, so the first one was taken",
@@ -433,7 +456,7 @@ describe("paperlint init — TeX Live and hooks failures, whole", () => {
433
456
  it("TeX Live: a failed install says to run it again", async () => {
434
457
  const t = await offerTexLive(
435
458
  { installed: () => false, install: () => 1 },
436
- { interactive: true, ask: async () => "yes" },
459
+ { interactive: true, ask: () => Promise.resolve("yes") },
437
460
  );
438
461
  expect([t, reportTexLive(t, "")]).toEqual([
439
462
  "failed",
@@ -462,8 +485,8 @@ describe("paperlint init — a first paper", () => {
462
485
  const made: string[] = [];
463
486
  const r = await initRun(tree({}), {
464
487
  interactive: true,
465
- ask: async () => "",
466
- createPaper: async (_root, name) => (made.push(name), 0),
488
+ ask: () => Promise.resolve(""),
489
+ createPaper: (_root, name) => Promise.resolve((made.push(name), 0)),
467
490
  });
468
491
  expect(made).toEqual([]);
469
492
  expect(r.out[r.out.indexOf("first paper") + 1]).toBe(
package/src/init.ts CHANGED
@@ -46,7 +46,13 @@ import {
46
46
  // eslint-disable-next-line boundaries/dependencies -- legacy I/O, moves behind a port in #76
47
47
  import { spawnSync } from "node:child_process";
48
48
  import { dirname, join, relative, resolve } from "node:path";
49
- import { doctor, detectPapers, found, PROGRAMS } from "./doctor.ts";
49
+ import {
50
+ doctor,
51
+ detectPapers,
52
+ found,
53
+ PROGRAMS,
54
+ type ProgramProbe,
55
+ } from "./doctor.ts";
50
56
  import { PAPER_MARKERS, papersIn } from "./build.ts";
51
57
  import { linkSkills, SKILLS_HOME, type LinkReport } from "./link-skills.ts";
52
58
  import { actionRef } from "./action-ref.ts";
@@ -64,13 +70,16 @@ import {
64
70
  nameProblem,
65
71
  type PaperFormat,
66
72
  } from "./new-paper.ts";
67
- // The one source for the consumer's config key lives in the .mjs half of the package (the ESLint
68
- // rules and the skill scripts import it too); its types are in lib/paper-config.d.mts.
73
+ // The one source for the consumer's config key lives in lib/ (the ESLint rules and the skill scripts
74
+ // import it too). It is imported compiled, from dist/, the one path that resolves the same from src/
75
+ // and from dist/ — see CONTRIBUTING.md.
69
76
  import {
70
77
  CONFIG_FILE,
71
78
  DEFAULT_PAPERS_ROOT,
72
79
  PAPERS_DIR_FIELD,
73
- } from "../lib/paper-config.mjs";
80
+ } from "#lib/paper-config";
81
+ import { messageOf } from "./domain/text.ts";
82
+ import { isRecord } from "./domain/record.ts";
74
83
 
75
84
  /** How the papers directory was arrived at. Printed, because a guess must not read as a fact. */
76
85
  export type PapersHow =
@@ -122,13 +131,21 @@ export function interactivity({
122
131
  return { interactive: true, why: "a terminal on both ends" };
123
132
  }
124
133
 
134
+ /**
135
+ * Whether a standard stream is a terminal. Node types `isTTY` as `boolean`, but on a stream that
136
+ * is NOT a terminal (a pipe, a file) the property is absent — `undefined` — so it is read as the
137
+ * optional it really is.
138
+ */
139
+ const isTty = (stream: { readonly isTTY?: boolean }): boolean =>
140
+ stream.isTTY === true;
141
+
125
142
  /** The mode of THIS process — the one place `process` is read for it. */
126
143
  export const processInteractivity = (yes: boolean) =>
127
144
  interactivity({
128
145
  // eslint-disable-next-line no-restricted-globals -- legacy I/O, moves behind a port in #76
129
- stdinTTY: Boolean(process.stdin.isTTY),
146
+ stdinTTY: isTty(process.stdin),
130
147
  // eslint-disable-next-line no-restricted-globals -- legacy I/O, moves behind a port in #76
131
- stdoutTTY: Boolean(process.stdout.isTTY),
148
+ stdoutTTY: isTty(process.stdout),
132
149
  // eslint-disable-next-line no-restricted-globals -- legacy I/O, moves behind a port in #76
133
150
  env: process.env,
134
151
  yes,
@@ -217,13 +234,17 @@ export async function declarePapers(
217
234
  ): Promise<DeclarationResult> {
218
235
  const path = join(root, CONFIG_FILE);
219
236
  const raw = existsSync(path) ? readFileSync(path, "utf8") : null;
220
- let settings: Record<string, unknown> = {};
237
+ let settings: Readonly<Record<string, unknown>> = {};
221
238
  if (raw !== null) {
239
+ let parsed: unknown;
222
240
  try {
223
- settings = JSON.parse(raw) as Record<string, unknown>;
241
+ parsed = JSON.parse(raw);
224
242
  } catch (e) {
225
- return { status: "unparsable", path, reason: (e as Error).message };
243
+ return { status: "unparsable", path, reason: messageOf(e) };
226
244
  }
245
+ if (!isRecord(parsed))
246
+ return { status: "unparsable", path, reason: "not a JSON object" };
247
+ settings = parsed;
227
248
  const existing = settings[PAPERS_DIR_FIELD];
228
249
  if (existing !== undefined)
229
250
  return { status: "kept", path, papers: existing };
@@ -289,7 +310,7 @@ export async function offerWorkflow(
289
310
  ask?: (q: string) => Promise<string>;
290
311
  interactive: boolean;
291
312
  /** The running package's version; see `InitOptions.version`. */
292
- version?: string;
313
+ version?: string | undefined;
293
314
  },
294
315
  ): Promise<WorkflowResult> {
295
316
  const path = join(root, WORKFLOW_PATH);
@@ -317,7 +338,7 @@ export function reportWorkflow(
317
338
  version,
318
339
  papersDir,
319
340
  why,
320
- }: { version?: string; papersDir: string; why: string },
341
+ }: { version?: string | undefined; papersDir: string; why: string },
321
342
  ): string[] {
322
343
  const ref = actionRef(version);
323
344
  const out: string[] = [];
@@ -352,7 +373,7 @@ export function reportWorkflow(
352
373
  * passing checker produce the same silence.
353
374
  */
354
375
  export function missingPrograms(
355
- run = spawnSync,
376
+ run: ProgramProbe = spawnSync,
356
377
  ): readonly (typeof PROGRAMS)[number][] {
357
378
  return PROGRAMS.filter((p) => !found(p.bin, run));
358
379
  }
@@ -406,7 +427,7 @@ export async function offerHooks(
406
427
  hooks: boolean;
407
428
  interactive: boolean;
408
429
  ask?: (q: string) => Promise<string>;
409
- merge?: Merge;
430
+ merge?: Merge | undefined;
410
431
  },
411
432
  ): Promise<HooksOutcome> {
412
433
  if (!hooks) return { status: "skipped" };
@@ -438,7 +459,7 @@ export async function offerHooks(
438
459
  }
439
460
  return wireHooks(root, m, shippedWiring());
440
461
  } catch (e) {
441
- return { status: "failed", reason: (e as Error).message };
462
+ return { status: "failed", reason: messageOf(e) };
442
463
  }
443
464
  }
444
465
 
@@ -560,7 +581,7 @@ export interface InitOptions {
560
581
  /** `false` is `--no-hooks`. */
561
582
  hooks?: boolean;
562
583
  /** vigiles' merge. Injected only so a test can observe or replace it. */
563
- merge?: Merge;
584
+ merge?: Merge | undefined;
564
585
  /** `--paper <name>`: create this paper, even without a terminal. */
565
586
  paper?: string | null;
566
587
  /** `--format tex|md` for that paper. */
@@ -574,7 +595,7 @@ export interface InitOptions {
574
595
  name: string,
575
596
  format: PaperFormat,
576
597
  ) => Promise<number>;
577
- run?: typeof spawnSync;
598
+ run?: ProgramProbe;
578
599
  /**
579
600
  * What `paperlint lint` would resolve from the declaration, asked of the CLI's OWN reader. A second
580
601
  * implementation here would be a second source of truth — the very defect `doctor` reports.
@@ -586,7 +607,7 @@ export interface InitOptions {
586
607
  * The version of the running package, read by the CLI from its own `package.json`. The CI
587
608
  * workflow is pinned to its release tag (`actionRef`); absent or unreleased, the placeholder.
588
609
  */
589
- version?: string;
610
+ version?: string | undefined;
590
611
  /**
591
612
  * TeX Live for `paperlint build`: whether paperlint's own tree is installed, and how to install
592
613
  * it (`paperlint toolchain`). Passed in by the CLI; without them the step only names the command.
@@ -739,21 +760,28 @@ export async function init(
739
760
  for (const line of papersLines(decl, why)) log(line);
740
761
  log(``);
741
762
  log(`settings`);
742
- if (decl.status === "written")
763
+ // Every step below uses the DECLARED directory. A kept declaration outranks what init
764
+ // measured or guessed: otherwise the first paper and the workflow would land in the
765
+ // guessed directory while lint and the hooks keep reading the declared one.
766
+ let papersDir: string;
767
+ if (decl.status === "written") {
768
+ papersDir = decl.papers;
743
769
  log(
744
770
  ` ✓ ${here(decl.path)} → { "${PAPERS_DIR_FIELD}": ${JSON.stringify(decl.papers)} }`,
745
771
  );
746
- else if (decl.status === "default")
772
+ } else if (decl.status === "default") {
773
+ papersDir = decl.papers;
747
774
  log(
748
775
  ` ✓ nothing to write — "${decl.papers}" is the default, so no ${CONFIG_FILE} is needed`,
749
776
  );
750
- else if (decl.status === "kept") {
777
+ } else if (decl.status === "kept") {
751
778
  if (typeof decl.papers !== "string") {
752
779
  err(
753
780
  ` ✗ ${here(decl.path)} declares ${PAPERS_DIR_FIELD} = ${JSON.stringify(decl.papers)} — it must be a directory path (a string)`,
754
781
  );
755
782
  return 2;
756
783
  }
784
+ papersDir = decl.papers;
757
785
  log(
758
786
  ` ✓ ${here(decl.path)} already declares ${PAPERS_DIR_FIELD} = ${JSON.stringify(decl.papers)} — kept, nothing overwritten`,
759
787
  );
@@ -767,10 +795,6 @@ export async function init(
767
795
  );
768
796
  return 2;
769
797
  }
770
- // Every step below uses the DECLARED directory. A kept declaration outranks what init
771
- // measured or guessed: otherwise the first paper and the workflow would land in the
772
- // guessed directory while lint and the hooks keep reading the declared one.
773
- const papersDir = decl.papers as string;
774
798
 
775
799
  // ── 3. the skills, linked where Claude Code looks for them ─────────────────────────────
776
800
  for (const line of reportSkillLinks(link(root), here)) log(line);
@@ -5,11 +5,9 @@
5
5
  * memory: the two error spellings `-file-line-error` produces, the missing-package form, and a
6
6
  * marker that TeX broke at column 79.
7
7
  */
8
- import { dirname, join } from "node:path";
9
- import { fileURLToPath } from "node:url";
10
- import { createChecker } from "../lib/check.mjs";
8
+ import { createChecker } from "../lib/check.ts";
9
+ import type { LogMarker } from "./latex-log.ts";
11
10
 
12
- const HERE = dirname(fileURLToPath(import.meta.url));
13
11
  const {
14
12
  unwrapLog,
15
13
  logMarkers,
@@ -18,7 +16,7 @@ const {
18
16
  bibtexExcerpt,
19
17
  auxBib,
20
18
  MAX_PRINT_LINE,
21
- } = await import(join(HERE, "latex-log.ts"));
19
+ } = await import("./latex-log.ts");
22
20
 
23
21
  const check = createChecker();
24
22
 
@@ -78,13 +76,15 @@ const firstPass = [
78
76
  const m = logMarkers(firstPass);
79
77
  check(
80
78
  "every documented marker is recognised",
81
- [
82
- "rerun-requested",
83
- "labels-changed",
84
- "rerunfilecheck",
85
- "undefined-references",
86
- "undefined-citations",
87
- ].every((k) => m.includes(k)),
79
+ (
80
+ [
81
+ "rerun-requested",
82
+ "labels-changed",
83
+ "rerunfilecheck",
84
+ "undefined-references",
85
+ "undefined-citations",
86
+ ] satisfies LogMarker[]
87
+ ).every((k) => m.includes(k)),
88
88
  );
89
89
  check(
90
90
  "a clean log carries no markers",
@@ -96,7 +96,7 @@ check(
96
96
  // Guards: matching the documented summary line rather than the word 'undefined'.
97
97
  check(
98
98
  "a single reference warning is not the end-of-run summary",
99
- !logMarkers([firstPass[0]]).includes("undefined-references"),
99
+ !logMarkers(firstPass.slice(0, 1)).includes("undefined-references"),
100
100
  );
101
101
 
102
102
  // ── error excerpt ───────────────────────────────────────────────────────────────────────
@@ -114,7 +114,7 @@ check(
114
114
  "-file-line-error form: the error line, the l.NNN line and the rest of the source line",
115
115
  ex[0] === "./paper.tex:4: Undefined control sequence." &&
116
116
  ex[1] === "l.4 \\foo" &&
117
- ex[2].includes("bar baz"),
117
+ (ex[2] ?? "").includes("bar baz"),
118
118
  );
119
119
  // Guards: the end of the excerpt — without it the excerpt runs into TeX's memory statistics and
120
120
  // buries the line the author needs.
@@ -6,30 +6,43 @@
6
6
  * A defect in `nextStep` fails in table 1, one in `summarize` in table 2, before table 3 runs.
7
7
  *
8
8
  * The decision is pure, so none of this needs TeX. The real-pdflatex half lives in
9
- * `test/e2e/build.mjs`.
9
+ * `test/e2e/build.ts`.
10
10
  */
11
- import { dirname, join } from "node:path";
12
- import { fileURLToPath } from "node:url";
13
- import { createChecker } from "../lib/check.mjs";
11
+ import { createChecker } from "../lib/check.ts";
12
+ import type {
13
+ BibInput,
14
+ Hashes,
15
+ Observation,
16
+ State,
17
+ Step,
18
+ } from "./latex-loop.ts";
14
19
 
15
- const HERE = dirname(fileURLToPath(import.meta.url));
16
- const { nextStep, summarize, changedFiles, MAX_PASSES } = await import(
17
- join(HERE, "latex-loop.ts")
18
- );
20
+ const { nextStep, summarize, changedFiles, MAX_PASSES } =
21
+ await import("./latex-loop.ts");
19
22
 
20
23
  const check = createChecker();
21
24
 
22
25
  // ── builders ────────────────────────────────────────────────────────────────────────────
23
- const H = (aux, toc = null, out = null, bbl = null) => ({ aux, toc, out, bbl });
24
- const NO_BIB = { kind: "none" };
25
- const bib = (citations, bibHash = "b1") => ({
26
+ type LatexPass = Extract<Observation, { step: "latex" }>;
27
+ type BibtexPass = Extract<Observation, { step: "bibtex" }>;
28
+ const H = (
29
+ aux: string | null,
30
+ toc: string | null = null,
31
+ out: string | null = null,
32
+ bbl: string | null = null,
33
+ ): Hashes => ({ aux, toc, out, bbl });
34
+ const NO_BIB: BibInput = { kind: "none" };
35
+ const bib = (
36
+ citations: readonly string[],
37
+ bibHash: string | null = "b1",
38
+ ): BibInput => ({
26
39
  kind: "needed",
27
40
  citations,
28
41
  databases: ["refs"],
29
42
  style: "plain",
30
43
  bibHash,
31
44
  });
32
- const latex = (o = {}) => ({
45
+ const latex = (o: Partial<LatexPass> = {}): LatexPass => ({
33
46
  step: "latex",
34
47
  final: false,
35
48
  exitCode: 0,
@@ -40,7 +53,7 @@ const latex = (o = {}) => ({
40
53
  errorLines: [],
41
54
  ...o,
42
55
  });
43
- const bibtex = (o = {}) => ({
56
+ const bibtex = (o: Partial<BibtexPass> = {}): BibtexPass => ({
44
57
  step: "bibtex",
45
58
  exitCode: 0,
46
59
  before: H("a1"),
@@ -49,11 +62,11 @@ const bibtex = (o = {}) => ({
49
62
  errorLines: [],
50
63
  ...o,
51
64
  });
52
- const kind = (s) =>
65
+ const kind = (s: Step) =>
53
66
  s.kind === "latex" ? `latex${s.final ? "-final" : ""}` : s.kind;
54
67
 
55
68
  // ── 1. nextStep over a State ─────────────────────────────────────────────────────────────
56
- const S = (o = {}) => ({
69
+ const S = (o: Partial<State> = {}): State => ({
57
70
  failed: null,
58
71
  bibOutdated: false,
59
72
  unsettled: [],
@@ -62,12 +75,12 @@ const S = (o = {}) => ({
62
75
  warnings: [],
63
76
  ...o,
64
77
  });
65
- const exitFail = {
78
+ const exitFail: NonNullable<State["failed"]> = {
66
79
  step: "bibtex",
67
80
  cause: { kind: "exit", code: 2 },
68
81
  lines: ["I couldn't open database file nope.bib"],
69
82
  };
70
- const STATES = [
83
+ const STATES: [string, State, string][] = [
71
84
  // Guards: the only moment paper-guards can speak — its error comes on the final pass, so testing
72
85
  // 'final' before 'exit code' would ship the undefined reference green.
73
86
  [
@@ -96,7 +109,7 @@ const STATES = [
96
109
  // Guards: the stop on a document that never settles — without it the loop runs forever; with a
97
110
  // silent cap it ships a PDF with stale cross-references.
98
111
  [
99
- `Q3 cap: ${MAX_PASSES} passes and still unsettled — fail`,
112
+ `Q3 cap: ${String(MAX_PASSES)} passes and still unsettled — fail`,
100
113
  S({ unsettled: ["paper.aux"], latexPasses: MAX_PASSES }),
101
114
  "fail",
102
115
  ],
@@ -115,20 +128,29 @@ for (const [label, state, expected] of STATES) {
115
128
  const q1 = nextStep(S({ failed: exitFail }));
116
129
  check(
117
130
  "Q1 passes the failure through: the step, the cause and the lines verbatim",
118
- q1.step === "bibtex" && q1.cause.code === 2 && q1.lines === exitFail.lines,
131
+ q1.kind === "fail" &&
132
+ q1.step === "bibtex" &&
133
+ q1.cause.kind === "exit" &&
134
+ q1.cause.code === 2 &&
135
+ q1.lines === exitFail.lines,
119
136
  );
120
137
  const q3cap = nextStep(
121
138
  S({ unsettled: ["paper.aux", "labels-changed"], latexPasses: MAX_PASSES }),
122
139
  );
123
140
  check(
124
141
  "the cap's failure is no-convergence and NAMES what stayed unsettled",
125
- q3cap.cause.kind === "no-convergence" &&
126
- q3cap.lines[0].includes("paper.aux, labels-changed"),
142
+ q3cap.kind === "fail" &&
143
+ q3cap.cause.kind === "no-convergence" &&
144
+ (q3cap.lines[0] ?? "").includes("paper.aux, labels-changed"),
127
145
  );
128
146
  check(
129
147
  "done carries the state's warnings",
130
- nextStep(S({ finalDone: true, warnings: ["undefined-citations"] }))
131
- .warnings[0] === "undefined-citations",
148
+ (() => {
149
+ const done = nextStep(
150
+ S({ finalDone: true, warnings: ["undefined-citations"] }),
151
+ );
152
+ return done.kind === "done" && done.warnings[0] === "undefined-citations";
153
+ })(),
132
154
  );
133
155
 
134
156
  // ── 2. summarize over histories ─────────────────────────────────────────────────────────
@@ -152,7 +174,10 @@ check(
152
174
  (() => {
153
175
  const f = sum([latex({ exitCode: 1, errorLines: ["l.4 \\foo"] })]).failed;
154
176
  return (
155
- f?.step === "latex" && f.cause.code === 1 && f.lines[0] === "l.4 \\foo"
177
+ f?.step === "latex" &&
178
+ f.cause.kind === "exit" &&
179
+ f.cause.code === 1 &&
180
+ f.lines[0] === "l.4 \\foo"
156
181
  );
157
182
  })(),
158
183
  );
@@ -162,22 +187,22 @@ check(
162
187
  );
163
188
  check(
164
189
  "summarize: bibOutdated when the aux names a bibliography bibtex has not run on",
165
- sum([latex({ bib: bib(["k"]) })]).bibOutdated === true,
190
+ sum([latex({ bib: bib(["k"]) })]).bibOutdated,
166
191
  );
167
192
  check(
168
193
  "summarize: bibOutdated is false once bibtex ran on exactly that input",
169
- sum([latex({ bib: bib(["k"]) }), bibtex()]).bibOutdated === false,
194
+ !sum([latex({ bib: bib(["k"]) }), bibtex()]).bibOutdated,
170
195
  );
171
196
  // Guards: the .bib hash as an input — a corrected author or year in refs.bib would keep the old
172
197
  // .bbl, and the PDF would print the entry as it was before the fix.
173
198
  check(
174
199
  "summarize: the .bib content changed since bibtex ran — bibOutdated",
175
200
  sum([latex({ bib: bib(["k"]) }), bibtex(), latex({ bib: bib(["k"], "b2") })])
176
- .bibOutdated === true,
201
+ .bibOutdated,
177
202
  );
178
203
  check(
179
204
  "summarize: no bibliography at all — never bibOutdated",
180
- sum([latex()]).bibOutdated === false,
205
+ !sum([latex()]).bibOutdated,
181
206
  );
182
207
  check(
183
208
  "summarize: unsettled names every tracked file the last pass changed",
@@ -232,10 +257,10 @@ check(
232
257
  );
233
258
 
234
259
  // ── 3. composed: nextStep(summarize(history)) ───────────────────────────────────────────
235
- const step = (history) => nextStep(summarize(history));
260
+ const step = (history: readonly Observation[]) => nextStep(summarize(history));
236
261
 
237
262
  // ── the histories the loop sees ───────────────────────────────────────────────────────────────────────────
238
- const TABLE = [
263
+ const TABLE: [string, Observation[], string][] = [
239
264
  ["nothing ran yet — the first step is a plain pass", [], "latex"],
240
265
  [
241
266
  "a pass that created the aux — rerun",
@@ -364,7 +389,8 @@ const latexFail = step([
364
389
  ]);
365
390
  check(
366
391
  "a latex fail names the step, the exit code and the log excerpt verbatim",
367
- latexFail.step === "latex" &&
392
+ latexFail.kind === "fail" &&
393
+ latexFail.step === "latex" &&
368
394
  latexFail.cause.kind === "exit" &&
369
395
  latexFail.cause.code === 1 &&
370
396
  latexFail.lines[1] === "l.4 \\foo",
@@ -380,14 +406,16 @@ check(
380
406
  "a bibtex fail names BIBTEX, not latex",
381
407
  bibFail.kind === "fail" &&
382
408
  bibFail.step === "bibtex" &&
409
+ bibFail.cause.kind === "exit" &&
383
410
  bibFail.cause.code === 2,
384
411
  );
385
412
 
386
413
  // ── the cap ─────────────────────────────────────────────────────────────────────────────
387
- const moving = (i) => latex({ before: H(`a${i}`), after: H(`a${i + 1}`) });
414
+ const moving = (i: number) =>
415
+ latex({ before: H(`a${String(i)}`), after: H(`a${String(i + 1)}`) });
388
416
  const underCap = Array.from({ length: MAX_PASSES - 1 }, (_, i) => moving(i));
389
417
  check(
390
- `the cap is ${MAX_PASSES} and it is not hit early: after ${MAX_PASSES - 1} moving passes, one more`,
418
+ `the cap is ${String(MAX_PASSES)} and it is not hit early: after ${String(MAX_PASSES - 1)} moving passes, one more`,
391
419
  MAX_PASSES === 5 && kind(step(underCap)) === "latex",
392
420
  );
393
421
  const atCap = Array.from({ length: MAX_PASSES }, (_, i) => moving(i));
@@ -398,7 +426,7 @@ check(
398
426
  );
399
427
  check(
400
428
  "the non-convergence failure NAMES the file that kept changing",
401
- capped.kind === "fail" && capped.lines[0].includes("paper.aux"),
429
+ capped.kind === "fail" && (capped.lines[0] ?? "").includes("paper.aux"),
402
430
  );
403
431
  const cappedMarker = step(
404
432
  Array.from({ length: MAX_PASSES }, () =>
@@ -408,7 +436,7 @@ const cappedMarker = step(
408
436
  check(
409
437
  "a log that keeps asking for a rerun hits the same cap, and names the marker",
410
438
  cappedMarker.kind === "fail" &&
411
- cappedMarker.lines[0].includes("labels-changed"),
439
+ (cappedMarker.lines[0] ?? "").includes("labels-changed"),
412
440
  );
413
441
  const convergedAtCap = [...atCap.slice(0, -1), latex()];
414
442
  check(
@@ -446,5 +474,5 @@ check(
446
474
  );
447
475
 
448
476
  console.log(
449
- `✓ ${String(check.count)} assertions passed — latex-loop: nextStep over State, summarize over histories, and the two composed, including the ${MAX_PASSES}-pass cap`,
477
+ `✓ ${String(check.count)} assertions passed — latex-loop: nextStep over State, summarize over histories, and the two composed, including the ${String(MAX_PASSES)}-pass cap`,
450
478
  );
package/src/latex-loop.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * PURE. No disk, no process, no clock. The shell in `build.ts` runs a step, records what it saw
5
5
  * as an `Observation`, appends it to the history and asks `nextStep(summarize(history))` again.
6
- * Both halves are table tests (`latex-loop.harness.mjs`), not scenarios that need TeX.
6
+ * Both halves are table tests (`latex-loop.harness.ts`), not scenarios that need TeX.
7
7
  *
8
8
  * ── THE LOOP: `summarize` reduces the history to a `State`, and `nextStep` asks four questions ──
9
9
  * 1. did the last program fail? → fail, with its error lines
@@ -118,7 +118,7 @@ export type Step =
118
118
  };
119
119
 
120
120
  /** Non-final pdflatex passes allowed before the build is declared non-converging. */
121
- export const MAX_PASSES = 5;
121
+ export const MAX_PASSES: number = 5;
122
122
 
123
123
  /** The history, reduced to the four questions `nextStep` asks. */
124
124
  export type State = {
@@ -152,7 +152,7 @@ const noConvergence = (unsettled: readonly string[]): Step => ({
152
152
  step: "latex",
153
153
  cause: { kind: "no-convergence", unsettled },
154
154
  lines: [
155
- `the build does not converge: after ${MAX_PASSES} pdflatex passes, the last one still changed or asked to rerun: ${unsettled.join(", ")}.`,
155
+ `the build does not converge: after ${String(MAX_PASSES)} pdflatex passes, the last one still changed or asked to rerun: ${unsettled.join(", ")}.`,
156
156
  `A document whose aux keeps changing on every pass never settles; stopping here instead of`,
157
157
  `shipping a PDF with stale cross-references.`,
158
158
  ],