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
@@ -35,6 +35,23 @@
35
35
  import { existsSync, readFileSync } from "node:fs";
36
36
  import { join, dirname } from "node:path";
37
37
  import { load } from "js-yaml";
38
+ import { z } from "zod";
39
+ import type { RuleContext } from "./rule-context.ts";
40
+
41
+ /**
42
+ * The two scorecard fields this rule reads, parsed rather than trusted: a field of the wrong shape
43
+ * reads as absent, the way `data?.stages` on a string or a number always did.
44
+ */
45
+ const Scorecard = z
46
+ .looseObject({
47
+ stages: z.array(z.unknown()).catch([]),
48
+ researchQuestion: z.string().catch(""),
49
+ })
50
+ .catch({ stages: [], researchQuestion: "" });
51
+ const StageEntry = z.looseObject({ stage: z.unknown().optional() }).catch({});
52
+ const RuleOptions = z
53
+ .object({ statusFile: z.string().default("PIPELINE-STATUS.md") })
54
+ .default({ statusFile: "PIPELINE-STATUS.md" });
38
55
 
39
56
  /**
40
57
  * 🔴 THE REGEX IS GONE, AND THE REASON IS NOT "regexes are bad" — IT IS THAT IT ANSWERED THE
@@ -76,18 +93,21 @@ import { load } from "js-yaml";
76
93
  */
77
94
 
78
95
  /** Whitespace is the only thing normalised: a sentence wrapped across lines is the same sentence. */
79
- const flatten = (t) => t.replace(/\s+/g, " ").trim().toLowerCase();
96
+ const flatten = (t: string) => t.replace(/\s+/g, " ").trim().toLowerCase();
80
97
 
81
98
  /**
82
99
  * Both facts come out of ONE parse of the scorecard's front matter: the stages (has this paper
83
100
  * shipped?) and the declared question. Reading them separately would invite the two to be taken
84
101
  * from different revisions of the same file.
85
102
  */
86
- function scorecard(dir, statusName) {
103
+ function scorecard(
104
+ dir: string,
105
+ statusName: string,
106
+ ): { stages: unknown[]; question: string } {
87
107
  const p = join(dir, statusName);
88
108
  const none = { stages: [], question: "" };
89
109
  if (!existsSync(p)) return none;
90
- let text;
110
+ let text: string;
91
111
  try {
92
112
  text = readFileSync(p, "utf-8");
93
113
  } catch {
@@ -95,25 +115,23 @@ function scorecard(dir, statusName) {
95
115
  }
96
116
  const m = /^---\n([\s\S]*?)\n---/.exec(text);
97
117
  if (!m) return none;
98
- let data;
118
+ const [, front = ""] = m;
119
+ let data: unknown;
99
120
  try {
100
- data = load(m[1]);
121
+ data = load(front);
101
122
  } catch {
102
123
  return none; // the unreadable YAML is already reported by `paper/stages`, on its own file
103
124
  }
104
- const raw = data?.stages;
105
- const stages = Array.isArray(raw)
106
- ? raw.map((r) => r?.stage).filter(Boolean)
107
- : [];
108
- const q = data?.researchQuestion;
109
- return { stages, question: typeof q === "string" ? q : "" };
125
+ const { stages: raw, researchQuestion: question } = Scorecard.parse(data);
126
+ const stages = raw.map((r) => StageEntry.parse(r).stage).filter(Boolean);
127
+ return { stages, question };
110
128
  }
111
129
 
112
130
  export default {
113
131
  rules: {
114
132
  "research-question": {
115
133
  meta: {
116
- type: "suggestion",
134
+ type: "suggestion" as const,
117
135
  docs: {
118
136
  description:
119
137
  "a paper that declares a stage states its research question explicitly — otherwise the reviewer reconstructs it themselves, and says so",
@@ -137,14 +155,15 @@ export default {
137
155
  "the scorecard declares a research question the paper does not contain. Looked for «{{needle}}» with whitespace collapsed, and the source has no such run of text. Either the paper dropped it or the declaration drifted from what was written — and which of the two it is, only you know",
138
156
  },
139
157
  },
140
- create(context) {
141
- const statusName =
142
- context.options?.[0]?.statusFile ?? "PIPELINE-STATUS.md";
158
+ create(context: RuleContext) {
159
+ const { statusFile: statusName } = RuleOptions.parse(
160
+ context.options[0],
161
+ );
143
162
  return {
144
163
  // `root:exit` exists for both markdown and the `tex/latex` language — the same place
145
164
  // `paper/typography` lives, and for the same reason: a paper in this corpus can be
146
165
  // either.
147
- "root:exit"(node) {
166
+ "root:exit"(node: object) {
148
167
  const raw = context.sourceCode.raw ?? context.sourceCode.text;
149
168
  if (typeof raw !== "string") return;
150
169
  const { stages, question } = scorecard(
@@ -15,8 +15,8 @@ import { join, dirname } from "node:path";
15
15
  import { fileURLToPath } from "node:url";
16
16
  import { Linter } from "eslint";
17
17
  import markdown from "@eslint/markdown";
18
- import stages from "./paper-stages.mjs";
19
- import { createChecker } from "../lib/check.mjs";
18
+ import stages from "./paper-stages.ts";
19
+ import { createChecker } from "../lib/check.ts";
20
20
 
21
21
  const FIX = join(
22
22
  dirname(fileURLToPath(import.meta.url)),
@@ -8,8 +8,8 @@ import { join } from "node:path";
8
8
  import markdown from "@eslint/markdown";
9
9
  import { Linter } from "eslint";
10
10
  import { test } from "vitest";
11
- import { useTempDir, writeTree } from "../test/support.mjs";
12
- import stages from "./paper-stages.mjs";
11
+ import { useTempDir, writeTree } from "../test/support.ts";
12
+ import stages from "./paper-stages.ts";
13
13
 
14
14
  const root = useTempDir("paper-stages-");
15
15
  // Flat config matches `files` against paths under the linter's cwd; the temp papers live outside
@@ -93,6 +93,12 @@ test("each entry: an unknown or missing stage, missing fields, a date that is no
93
93
  );
94
94
  });
95
95
 
96
+ test("a stage written as a mapping is named as written, not as [object Object]", () => {
97
+ assert.deepEqual(lint("mapping", "stages:\n - stage: {name: submitted}"), [
98
+ 'unknown stage «{"name":"submitted"}» — the vocabulary is: submitted · camera-ready · arxiv',
99
+ ]);
100
+ });
101
+
96
102
  test("paper/source skips a `stages` that is not a list, and entries whose stage is unknown or missing", () => {
97
103
  assert.deepEqual(
98
104
  lint("src-scalar", "stages: submitted", { rule: "source" }),
@@ -48,30 +48,69 @@
48
48
  */
49
49
  import { existsSync, statSync, readdirSync } from "node:fs";
50
50
  import { join, dirname } from "node:path";
51
+ import assert from "node:assert/strict";
51
52
  import { load } from "js-yaml";
53
+ import { z } from "zod";
54
+ import type { RuleContext } from "./rule-context.ts";
52
55
 
53
- const STAGES = ["submitted", "camera-ready", "arxiv"];
56
+ /** The frontmatter's `stages` field, whatever it holds; anything that is not a mapping has none. */
57
+ const Frontmatter = z.looseObject({ stages: z.unknown().optional() }).catch({});
58
+ const StageList = z.array(z.unknown());
59
+ /** One `stages` entry. A field of the wrong shape is read as it is written, then judged below. */
60
+ const StageRecord = z
61
+ .looseObject({
62
+ stage: z.unknown().optional(),
63
+ date: z.unknown().optional(),
64
+ pdf: z.unknown().optional(),
65
+ bytes: z.unknown().optional(),
66
+ source: z.unknown().optional(),
67
+ sourceBytes: z.unknown().optional(),
68
+ sourceLost: z.unknown().optional(),
69
+ })
70
+ .catch({});
71
+ const REQUIRED = ["date", "pdf", "bytes"] satisfies readonly (
72
+ "date" | "pdf" | "bytes"
73
+ )[];
74
+
75
+ /** A stage the frontmatter declares, with the fields every check below needs. */
76
+ interface Declared {
77
+ readonly stage: string;
78
+ readonly date: string;
79
+ readonly pdf: string;
80
+ readonly bytes: number;
81
+ }
82
+
83
+ const STAGES: readonly string[] = ["submitted", "camera-ready", "arxiv"];
54
84
 
55
85
  /**
56
86
  * A YAML date without quotes parses to a `Date`, not a string — js-yaml honours YAML 1.1
57
87
  * timestamps. Comparing `Date === "2026-08-29"` is silently false, so every date is
58
88
  * normalised before it is compared with one taken from a filename.
59
89
  */
60
- function isoDate(v) {
90
+ /**
91
+ * A YAML value as the text a message shows: a scalar as written, nothing as "", and a mapping or
92
+ * list as its JSON (as a number is) — `String()` would print `[object Object]` for a mapping.
93
+ */
94
+ const scalarText = (v: unknown): string =>
95
+ typeof v === "string" ? v : v === undefined ? "" : JSON.stringify(v);
96
+
97
+ function isoDate(v: unknown): string {
61
98
  if (v instanceof Date) return v.toISOString().slice(0, 10);
62
99
  if (typeof v === "string") return /^\d{4}-\d{2}-\d{2}/.exec(v)?.[0] ?? "";
63
100
  return "";
64
101
  }
65
102
 
66
103
  /** Frozen versions on disk, as `{name, date, stage}`. */
67
- function frozenPdfs(versionsDir) {
68
- let names;
104
+ function frozenPdfs(
105
+ versionsDir: string,
106
+ ): { name: string; date: string; stage: string }[] {
107
+ let names: string[];
69
108
  try {
70
109
  names = readdirSync(versionsDir);
71
110
  } catch {
72
111
  return [];
73
112
  }
74
- const out = [];
113
+ const out: { name: string; date: string; stage: string }[] = [];
75
114
  for (const name of names) {
76
115
  if (!name.endsWith(".pdf")) continue;
77
116
  // ⚠️ A name carrying `STALE` is a version WITHDRAWN on purpose — one such file records
@@ -80,8 +119,9 @@ function frozenPdfs(versionsDir) {
80
119
  if (name.includes("STALE")) continue;
81
120
  const m = /^(\d{4}-\d{2}-\d{2})-(.+)\.pdf$/.exec(name);
82
121
  if (!m) continue;
83
- if (!STAGES.includes(m[2])) continue;
84
- out.push({ name, date: m[1], stage: m[2] });
122
+ const [, date = "", stage = ""] = m;
123
+ if (!STAGES.includes(stage)) continue;
124
+ out.push({ name, date, stage });
85
125
  }
86
126
  return out;
87
127
  }
@@ -90,7 +130,7 @@ export default {
90
130
  rules: {
91
131
  stages: {
92
132
  meta: {
93
- type: "problem",
133
+ type: "problem" as const,
94
134
  docs: {
95
135
  description:
96
136
  "a paper's stage is declared as a FIELD, and every declaration is checked against the bytes on disk in both directions",
@@ -112,26 +152,29 @@ export default {
112
152
  "`versions/{{file}}` is frozen, but no «{{stage}}» stage on {{date}} is declared in `stages` — the artefact ran ahead of the declaration",
113
153
  },
114
154
  },
115
- create(context) {
155
+ create(context: RuleContext) {
116
156
  const dir = dirname(context.filename);
117
- let declared = null; // null = there was no frontmatter at all
157
+ let declared: Declared[] | null = null; // null = there was no frontmatter at all
118
158
 
119
159
  return {
120
- yaml(node) {
121
- let data;
160
+ yaml(node: { readonly value?: string }) {
161
+ let data: unknown;
122
162
  try {
123
163
  data = load(node.value ?? "");
124
164
  } catch (e) {
165
+ // js-yaml throws its own `YAMLException`, an `Error`, and nothing else.
166
+ assert.ok(e instanceof Error);
125
167
  context.report({
126
168
  node,
127
169
  messageId: "badYaml",
128
- data: { reason: String(e.message) },
170
+ data: { reason: e.message },
129
171
  });
130
172
  return;
131
173
  }
132
- const raw = data?.stages;
174
+ const { stages: raw } = Frontmatter.parse(data);
133
175
  if (raw === undefined) return;
134
- if (!Array.isArray(raw)) {
176
+ const list = StageList.safeParse(raw);
177
+ if (!list.success) {
135
178
  context.report({
136
179
  node,
137
180
  messageId: "notAList",
@@ -140,8 +183,9 @@ export default {
140
183
  return;
141
184
  }
142
185
  declared = [];
143
- for (const rec of raw) {
144
- const stage = String(rec?.stage ?? "");
186
+ for (const entry of list.data) {
187
+ const rec = StageRecord.parse(entry);
188
+ const stage = scalarText(rec.stage);
145
189
  if (!STAGES.includes(stage)) {
146
190
  context.report({
147
191
  node,
@@ -151,7 +195,7 @@ export default {
151
195
  continue;
152
196
  }
153
197
  let complete = true;
154
- for (const key of ["date", "pdf", "bytes"]) {
198
+ for (const key of REQUIRED) {
155
199
  if (rec[key] === undefined) {
156
200
  context.report({
157
201
  node,
@@ -207,7 +251,7 @@ export default {
207
251
  // ── direction two: bytes owe their declaration ────────────────────────────────
208
252
  // 🔴 On `root:exit` rather than inside `yaml`, because the case worth catching is
209
253
  // precisely the one with NO frontmatter at all — where the `yaml` visitor never runs.
210
- "root:exit"(node) {
254
+ "root:exit"(node: object) {
211
255
  const records = declared ?? [];
212
256
  for (const f of frozenPdfs(join(dir, "versions"))) {
213
257
  if (records.some((r) => r.stage === f.stage && r.date === f.date))
@@ -256,7 +300,7 @@ export default {
256
300
  */
257
301
  source: {
258
302
  meta: {
259
- type: "problem",
303
+ type: "problem" as const,
260
304
  docs: {
261
305
  description:
262
306
  "a declared stage freezes its source beside the pdf and is checked by bytes — not by a commit reference",
@@ -273,26 +317,27 @@ export default {
273
317
  "stage «{{stage}}» ({{date}}): the source is declared LOST. There is nothing left to match a build against a reviewer's line — if a copy turns up, put it in versions/ and clear the flag",
274
318
  },
275
319
  },
276
- create(context) {
320
+ create(context: RuleContext) {
277
321
  const dir = dirname(context.filename);
278
322
  return {
279
- yaml(node) {
280
- let data;
323
+ yaml(node: { readonly value?: string }) {
324
+ let data: unknown;
281
325
  try {
282
326
  data = load(node.value ?? "");
283
327
  } catch {
284
328
  return; // `paper/stages` has already reported the unreadable YAML
285
329
  }
286
- const raw = data?.stages;
287
- if (!Array.isArray(raw)) return;
288
- for (const rec of raw) {
289
- const stage = String(rec?.stage ?? "");
330
+ const list = StageList.safeParse(Frontmatter.parse(data).stages);
331
+ if (!list.success) return;
332
+ for (const entry of list.data) {
333
+ const rec = StageRecord.parse(entry);
334
+ const stage = scalarText(rec.stage);
290
335
  if (!STAGES.includes(stage)) continue;
291
- const date = isoDate(rec?.date);
336
+ const date = isoDate(rec.date);
292
337
 
293
338
  // Acknowledging the loss is a RECORD, not an exemption: the rule keeps speaking,
294
339
  // because the state stays defective, just unfixable today.
295
- if (rec?.sourceLost === true) {
340
+ if (rec.sourceLost === true) {
296
341
  context.report({
297
342
  node,
298
343
  messageId: "lostAcknowledged",
@@ -300,7 +345,7 @@ export default {
300
345
  });
301
346
  continue;
302
347
  }
303
- const src = rec?.source === undefined ? "" : String(rec.source);
348
+ const src = scalarText(rec.source);
304
349
  if (src === "") {
305
350
  context.report({
306
351
  node,
@@ -319,7 +364,7 @@ export default {
319
364
  continue;
320
365
  }
321
366
  const got = statSync(abs).size;
322
- const want = Number(rec?.sourceBytes);
367
+ const want = Number(rec.sourceBytes);
323
368
  if (Number.isFinite(want) && got !== want)
324
369
  context.report({
325
370
  node,
@@ -15,9 +15,9 @@ import markdown from "@eslint/markdown";
15
15
  import { readFileSync } from "node:fs";
16
16
  import { dirname, join } from "node:path";
17
17
  import { fileURLToPath } from "node:url";
18
- import { texLanguage } from "./latex-language.mjs";
19
- import typography, { texVisibleRuns } from "./paper-typography.mjs";
20
- import bib from "./bib-reachable-entry.mjs";
18
+ import { texLanguage } from "./latex-language.ts";
19
+ import typography, { texVisibleRuns } from "./paper-typography.ts";
20
+ import bib from "./bib-reachable-entry.ts";
21
21
 
22
22
  const HERE = dirname(fileURLToPath(import.meta.url));
23
23
  const FIX = join(HERE, "..", "fixtures", "paper-typography");