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
@@ -13,12 +13,9 @@ import { describe, expect, it } from "vitest";
13
13
  import { memoryFiles } from "./adapters/memory/index.ts";
14
14
  import { sha256Hex } from "./domain/sha256.ts";
15
15
  import { packageVenuesDir } from "../skills/paper-pipeline/scripts/consumer.mjs";
16
- import {
17
- VENUE_RULE_LEVELS,
18
- venueRules,
19
- type VenueRuleModule,
20
- } from "./venue-rules.ts";
16
+ import { VENUE_RULE_LEVELS, venueRules } from "./venue-rules.ts";
21
17
  import { buildConfig, OPTIONAL_RULES, SHIPPED_RULES } from "./cli.ts";
18
+ import { present } from "../test/support.ts";
22
19
 
23
20
  const VENUES = packageVenuesDir();
24
21
  const PAPER = "/work/papers/p";
@@ -34,9 +31,18 @@ const shipped = Object.fromEntries(
34
31
  );
35
32
 
36
33
  type Json = Record<string, unknown>;
34
+ interface Font {
35
+ name: string;
36
+ type: string;
37
+ embedded: boolean;
38
+ program: string;
39
+ }
40
+ /** The facts file as these tests edit it: free-form, except the fonts they reach into. */
41
+ type Facts = Json & { fonts: Font[] };
42
+ type Patch = (f: Facts) => void;
37
43
 
38
44
  /** The facts of a short agenticdev paper that meets every number in its profile. */
39
- function goodFacts(): Json {
45
+ function goodFacts(): Facts {
40
46
  return {
41
47
  schema: 2,
42
48
  pdf: "paper.pdf",
@@ -118,10 +124,7 @@ function lint(
118
124
  const files = memoryFiles(paperFiles(p));
119
125
  const rules = venueRules({ files, venuesDir: VENUES });
120
126
  const out: Finding[] = [];
121
- for (const [name, rule] of Object.entries(rules) as [
122
- string,
123
- VenueRuleModule,
124
- ][]) {
127
+ for (const [name, rule] of Object.entries(rules)) {
125
128
  const visitor = rule.create({
126
129
  filename,
127
130
  ...(cwd === undefined ? {} : { cwd }),
@@ -147,7 +150,13 @@ function lint(
147
150
  const DECL = { extends: "paperlint:agenticdev", kind: "short" };
148
151
  const ids = (fs: readonly Finding[]) =>
149
152
  fs.map((f) => `${f.rule}:${f.messageId}`).sort();
150
- const withFacts = (patch: (f: Json) => void): Json => {
153
+ /** A patch that changes one field set of the `i`-th font. */
154
+ const font =
155
+ (i: number, change: Partial<Font>): Patch =>
156
+ (f) => {
157
+ f.fonts = f.fonts.map((x, j) => (j === i ? { ...x, ...change } : x));
158
+ };
159
+ const withFacts = (patch: Patch): Facts => {
151
160
  const f = goodFacts();
152
161
  patch(f);
153
162
  return f;
@@ -260,7 +269,7 @@ describe("pdf/profile — the kind", () => {
260
269
  // the fonts rule still runs with an unresolved kind
261
270
  const fonts = lint({
262
271
  venue,
263
- facts: withFacts((f) => ((f.fonts as Json[])[0]!.embedded = false)),
272
+ facts: withFacts(font(0, { embedded: false })),
264
273
  });
265
274
  expect(ids(fonts)).toEqual([
266
275
  `pdf/fonts:notEmbedded`,
@@ -338,7 +347,7 @@ describe("pdf/measured — a paper that names a venue and was not measured says
338
347
  "pages_by_type",
339
348
  ])
340
349
  f[k] = null;
341
- (f.fonts as Json[])[0]!.program = "Type3";
350
+ font(0, { program: "Type3" })(f);
342
351
  });
343
352
  expect(ids(lint({ venue: DECL, facts: g }))).toEqual([
344
353
  "pdf/fonts:type3",
@@ -348,7 +357,7 @@ describe("pdf/measured — a paper that names a venue and was not measured says
348
357
  });
349
358
 
350
359
  describe("pdf/fresh — facts about another build are not judged", () => {
351
- it.each([
360
+ it.each<[string, Paper, string]>([
352
361
  [
353
362
  "a changed PDF",
354
363
  { pdf: new TextEncoder().encode("another build") },
@@ -364,55 +373,48 @@ describe("pdf/fresh — facts about another build are not judged", () => {
364
373
  ],
365
374
  [
366
375
  "a font entry of the wrong shape",
367
- { facts: withFacts((f) => (f.fonts = [{ name: 3 }])) },
376
+ { facts: { ...goodFacts(), fonts: [{ name: 3 }] } },
368
377
  "factsBroken",
369
378
  ],
370
- ] as [string, Paper, string][])(
371
- "%s: one finding, from pdf/fresh only",
372
- (_, p, messageId) => {
373
- // Everything else planted wrong too — none of it may be reported over stale facts.
374
- const facts =
375
- p.facts ?? withFacts((f) => ((f.body_pages = 99), (f.columns = 1)));
376
- expect(ids(lint({ venue: DECL, ...p, facts }))).toEqual([
377
- `pdf/fresh:${messageId}`,
378
- ]);
379
- },
380
- );
379
+ ])("%s: one finding, from pdf/fresh only", (_, p, messageId) => {
380
+ // Everything else planted wrong too — none of it may be reported over stale facts.
381
+ const facts =
382
+ p.facts ?? withFacts((f) => ((f.body_pages = 99), (f.columns = 1)));
383
+ expect(ids(lint({ venue: DECL, ...p, facts }))).toEqual([
384
+ `pdf/fresh:${messageId}`,
385
+ ]);
386
+ });
381
387
  });
382
388
 
383
389
  describe("pdf/fonts", () => {
384
- it.each([
385
- [
386
- "a Type 3 font",
387
- (f: Json) => ((f.fonts as Json[])[0]!.program = "Type3"),
388
- ["pdf/fonts:type3"],
389
- ],
390
+ it.each<[string, (f: Facts) => void, string[]]>([
391
+ ["a Type 3 font", font(0, { program: "Type3" }), ["pdf/fonts:type3"]],
390
392
  [
391
393
  "a font not embedded",
392
- (f: Json) => ((f.fonts as Json[])[1]!.embedded = false),
394
+ font(1, { embedded: false }),
393
395
  ["pdf/fonts:notEmbedded"],
394
396
  ],
395
397
  [
396
398
  "the text family missing — acmart fell back to Computer Modern",
397
- (f: Json) => ((f.fonts as Json[])[0]!.name = "CMR10"),
399
+ font(0, { name: "CMR10" }),
398
400
  ["pdf/fonts:noFamily"],
399
401
  ],
400
402
  [
401
403
  "both families missing: one finding per family",
402
- (f: Json) =>
404
+ (f: Facts) =>
403
405
  (f.fonts = [
404
406
  { name: "CMR10", type: "Type 1", embedded: true, program: "Type1" },
405
407
  ]),
406
408
  ["pdf/fonts:noFamily", "pdf/fonts:noFamily"],
407
409
  ],
408
- ] as [string, (f: Json) => void, string[]][])("%s", (_, patch, want) => {
410
+ ])("%s", (_, patch, want) => {
409
411
  expect(ids(lint({ venue: DECL, facts: withFacts(patch) }))).toEqual(want);
410
412
  });
411
413
 
412
414
  it("names the family and the fonts the PDF does have", () => {
413
415
  const [f] = lint({
414
416
  venue: DECL,
415
- facts: withFacts((x) => ((x.fonts as Json[])[0]!.name = "CMR10")),
417
+ facts: withFacts(font(0, { name: "CMR10" })),
416
418
  });
417
419
  expect(f?.message).toMatch(/LinLibertine/);
418
420
  expect(f?.message).toMatch(/CMR10/);
@@ -420,28 +422,28 @@ describe("pdf/fonts", () => {
420
422
  });
421
423
 
422
424
  describe("pdf/geometry", () => {
423
- it.each([
425
+ it.each<[string, (f: Facts) => void, string[]]>([
424
426
  [
425
427
  "A4 instead of letter",
426
- (f: Json) => ((f.page_w_in = 8.27), (f.page_h_in = 11.69)),
428
+ (f: Facts) => ((f.page_w_in = 8.27), (f.page_h_in = 11.69)),
427
429
  ["pdf/geometry:dim", "pdf/geometry:dim"],
428
430
  ],
429
431
  [
430
432
  "one column instead of two",
431
- (f: Json) => (f.columns = 1),
433
+ (f: Facts) => (f.columns = 1),
432
434
  ["pdf/geometry:columns"],
433
435
  ],
434
436
  [
435
437
  "a page size banal could not read",
436
- (f: Json) => (f.page_w_in = null),
438
+ (f: Facts) => (f.page_w_in = null),
437
439
  ["pdf/geometry:dimMissing"],
438
440
  ],
439
441
  [
440
442
  "0.03 in off is inside the default tolerance",
441
- (f: Json) => (f.page_w_in = 8.53),
443
+ (f: Facts) => (f.page_w_in = 8.53),
442
444
  [],
443
445
  ],
444
- ] as [string, (f: Json) => void, string[]][])("%s", (_, patch, want) => {
446
+ ])("%s", (_, patch, want) => {
445
447
  expect(ids(lint({ venue: DECL, facts: withFacts(patch) }))).toEqual(want);
446
448
  });
447
449
 
@@ -455,46 +457,43 @@ describe("pdf/geometry", () => {
455
457
  });
456
458
 
457
459
  describe("pdf/limits", () => {
458
- it.each([
460
+ it.each<[string, (f: Facts) => void, string[], RegExp | null]>([
459
461
  [
460
462
  "one body page over the short-paper limit",
461
- (f: Json) => (f.body_pages = 6),
463
+ (f: Facts) => (f.body_pages = 6),
462
464
  ["pdf/limits:pages"],
463
465
  /body pages: 6, over the limit 5 for agenticdev\/short/,
464
466
  ],
465
467
  [
466
468
  "one reference page over",
467
- (f: Json) => (f.ref_pages = 3),
469
+ (f: Facts) => (f.ref_pages = 3),
468
470
  ["pdf/limits:pages"],
469
471
  /reference pages: 3, over the limit 2/,
470
472
  ],
471
- ["exactly at the limit", (f: Json) => (f.body_pages = 5), [], null],
473
+ ["exactly at the limit", (f: Facts) => (f.body_pages = 5), [], null],
472
474
  [
473
475
  "a reference font below the range",
474
- (f: Json) => (f.ref_pt = 6),
476
+ (f: Facts) => (f.ref_pt = 6),
475
477
  ["pdf/limits:refPt"],
476
478
  /6 pt/,
477
479
  ],
478
480
  [
479
481
  "6.8 pt is inside the range once the measuring drift (body_pt_tol) is allowed",
480
- (f: Json) => (f.ref_pt = 6.8),
482
+ (f: Facts) => (f.ref_pt = 6.8),
481
483
  [],
482
484
  null,
483
485
  ],
484
486
  [
485
487
  "no reference font measured (no bibliography) is not a finding",
486
- (f: Json) => (f.ref_pt = null),
488
+ (f: Facts) => (f.ref_pt = null),
487
489
  [],
488
490
  null,
489
491
  ],
490
- ] as [string, (f: Json) => void, string[], RegExp | null][])(
491
- "%s",
492
- (_, patch, want, text) => {
493
- const fs = lint({ venue: DECL, facts: withFacts(patch) });
494
- expect(ids(fs)).toEqual(want);
495
- if (text) expect(fs[0]?.message).toMatch(text);
496
- },
497
- );
492
+ ])("%s", (_, patch, want, text) => {
493
+ const fs = lint({ venue: DECL, facts: withFacts(patch) });
494
+ expect(ids(fs)).toEqual(want);
495
+ if (text) expect(fs[0]?.message).toMatch(text);
496
+ });
498
497
 
499
498
  it("a longer kind of the same venue passes the same page count", () => {
500
499
  const facts = withFacts((f) => (f.body_pages = 9));
@@ -506,19 +505,19 @@ describe("pdf/limits", () => {
506
505
  });
507
506
 
508
507
  describe("pdf/body-size", () => {
509
- it.each([
508
+ it.each<[string, (f: Facts) => void, string[]]>([
510
509
  [
511
510
  "10.2 pt against 9 ± 0.5",
512
- (f: Json) => (f.body_pt = 10.2),
511
+ (f: Facts) => (f.body_pt = 10.2),
513
512
  ["pdf/body-size:body"],
514
513
  ],
515
- ["9.4 pt is inside the tolerance", (f: Json) => (f.body_pt = 9.4), []],
514
+ ["9.4 pt is inside the tolerance", (f: Facts) => (f.body_pt = 9.4), []],
516
515
  [
517
516
  "no body size measured",
518
- (f: Json) => (f.body_pt = null),
517
+ (f: Facts) => (f.body_pt = null),
519
518
  ["pdf/body-size:bodyMissing"],
520
519
  ],
521
- ] as [string, (f: Json) => void, string[]][])("%s", (_, patch, want) => {
520
+ ])("%s", (_, patch, want) => {
522
521
  expect(ids(lint({ venue: DECL, facts: withFacts(patch) }))).toEqual(want);
523
522
  });
524
523
  });
@@ -531,12 +530,15 @@ describe("where a finding points", () => {
531
530
  });
532
531
 
533
532
  describe("paperlint's own config turns the venue rules on for every paper.tex", () => {
534
- const texBlock = buildConfig({}, { sentinel: "tex language" }).find((b) =>
535
- ((b as { files?: string[] }).files ?? []).includes("**/paper.tex"),
536
- ) as { rules: Record<string, unknown> };
533
+ const texRules = present(
534
+ buildConfig({}, { sentinel: "tex language" }).find((b) =>
535
+ (b.files ?? []).includes("**/paper.tex"),
536
+ )?.rules,
537
+ "the paper.tex block's rules",
538
+ );
537
539
 
538
540
  it.each(Object.entries(VENUE_RULE_LEVELS))("%s is on at %s", (id, level) => {
539
- expect(texBlock.rules[id]).toBe(level);
541
+ expect(texRules[id]).toBe(level);
540
542
  expect(SHIPPED_RULES.has(id)).toBe(true);
541
543
  expect(OPTIONAL_RULES.has(id)).toBe(false);
542
544
  });
@@ -567,7 +569,7 @@ describe("a project's own preset, and the facts' other spellings", () => {
567
569
  format: { columns: 2, body_pt: 9, ref_pt_min: 7, ref_pt_max: 8 },
568
570
  }),
569
571
  };
570
- const house = (patch: (f: Json) => void = () => {}) =>
572
+ const house = (patch: (f: Facts) => void = () => {}) =>
571
573
  lint({
572
574
  venue: { extends: "./house.jsonc" },
573
575
  facts: withFacts(patch),
@@ -45,10 +45,11 @@ import {
45
45
  import type { KindLimits, VenueFormat } from "./tex-requirements.ts";
46
46
  import { paperPreset, presetProblemText } from "./presets.ts";
47
47
  import type { FlatGeometry } from "./domain/geometry.ts";
48
- import type { AbsolutePath } from "./domain/paths.ts";
48
+ import { callerPath } from "./caller-path.ts";
49
49
  import { sha256Hex } from "./domain/sha256.ts";
50
+ import { fieldOf } from "./domain/record.ts";
50
51
  import type { Files } from "./ports/files.ts";
51
- import { CONFIG_FILE } from "../lib/paper-config.mjs";
52
+ import { CONFIG_FILE } from "#lib/paper-config";
52
53
 
53
54
  // ── the verdict's vocabulary ─────────────────────────────────────────────────────────
54
55
 
@@ -92,9 +93,8 @@ export interface VenueRuleDeps {
92
93
  readonly venuesDir: string;
93
94
  }
94
95
 
95
- const at = (p: string): AbsolutePath => p as AbsolutePath;
96
96
  const text = (files: Files, p: string): string | null => {
97
- const b = files.readBytes(at(p));
97
+ const b = files.readBytes(callerPath(p));
98
98
  return b === null ? null : new TextDecoder().decode(b);
99
99
  };
100
100
 
@@ -138,7 +138,7 @@ function freshFacts(
138
138
  : finding("factsBroken", { why: parsed.error.why });
139
139
  const f = parsed.value;
140
140
  const pdf = files.readBytes(
141
- at(isAbsolute(f.pdf) ? f.pdf : join(paperDir, f.pdf)),
141
+ callerPath(isAbsolute(f.pdf) ? f.pdf : join(paperDir, f.pdf)),
142
142
  );
143
143
  if (pdf === null) return finding("pdfMissing", { pdf: f.pdf });
144
144
  return sha256Hex(pdf) === f.sha ? f : finding("stale", { pdf: f.pdf });
@@ -373,8 +373,7 @@ const JUDGES: Readonly<Record<VenueRuleName, Judge>> = {
373
373
  a.facts.geometry,
374
374
  a.venue.format,
375
375
  a.venue.venue,
376
- (options[0] as { dimTol?: number } | undefined)?.dimTol ??
377
- DEFAULT_DIM_TOL_IN,
376
+ dimTolOf(options[0]),
378
377
  )
379
378
  : [],
380
379
  limits: (a) =>
@@ -390,6 +389,12 @@ const JUDGES: Readonly<Record<VenueRuleName, Judge>> = {
390
389
  /** How far, in inches, a measured page dimension may be from the preset's. */
391
390
  export const DEFAULT_DIM_TOL_IN = 0.05;
392
391
 
392
+ /** `geometry`'s `dimTol` option in inches, or the default when the rule was given none. */
393
+ function dimTolOf(options: unknown): number {
394
+ const tol = fieldOf(options, "dimTol");
395
+ return typeof tol === "number" ? tol : DEFAULT_DIM_TOL_IN;
396
+ }
397
+
393
398
  type Meta = Pick<VenueRuleModule["meta"], "docs" | "messages"> & {
394
399
  readonly type?: "suggestion";
395
400
  readonly schema?: readonly object[];
@@ -565,9 +570,14 @@ function rule(name: VenueRuleName, deps: VenueRuleDeps): VenueRuleModule {
565
570
  export function venueRules(
566
571
  deps: VenueRuleDeps,
567
572
  ): Record<VenueRuleName, VenueRuleModule> {
568
- const names = Object.keys(META) as VenueRuleName[];
569
- return Object.fromEntries(names.map((n) => [n, rule(n, deps)])) as Record<
570
- VenueRuleName,
571
- VenueRuleModule
572
- >;
573
+ // Every name listed: a record missing one is a compile error.
574
+ return {
575
+ fresh: rule("fresh", deps),
576
+ profile: rule("profile", deps),
577
+ measured: rule("measured", deps),
578
+ fonts: rule("fonts", deps),
579
+ geometry: rule("geometry", deps),
580
+ limits: rule("limits", deps),
581
+ "body-size": rule("body-size", deps),
582
+ };
573
583
  }
package/tsconfig.json CHANGED
@@ -11,13 +11,16 @@
11
11
  "sourceMap": true,
12
12
  "strict": true,
13
13
  "noUncheckedIndexedAccess": true,
14
- "exactOptionalPropertyTypes": false,
14
+ "exactOptionalPropertyTypes": true,
15
15
  "skipLibCheck": true,
16
16
  "verbatimModuleSyntax": false,
17
17
  "types": ["node"],
18
18
  "allowImportingTsExtensions": true,
19
- "rewriteRelativeImportExtensions": true
19
+ "rewriteRelativeImportExtensions": true,
20
+ "noImplicitOverride": true,
21
+ "noImplicitReturns": true,
22
+ "noFallthroughCasesInSwitch": true
20
23
  },
21
24
  "include": ["src/**/*.ts"],
22
- "exclude": ["src/**/*.test.ts"]
25
+ "exclude": ["src/**/*.test.ts", "src/**/*.harness.ts"]
23
26
  }
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": ".",
5
+ "outDir": "dist",
6
+ "resolveJsonModule": true
7
+ },
8
+ "include": ["lib/*.ts", "eslint-rules/*.ts", "eslint-rules/*.json"],
9
+ "exclude": ["**/*.test.ts", "**/*.harness.ts", "**/*.d.ts"]
10
+ }
package/lib/check.d.mts DELETED
@@ -1,17 +0,0 @@
1
- // Types for lib/check.mjs — the shared harness assertion helper (see the module).
2
-
3
- /** A failure detail: text, any value (rendered with `util.inspect`), or a thunk producing one. */
4
- export type Detail = unknown;
5
-
6
- export declare function renderDetail(detail: Detail): string;
7
- export declare function failureMessage(label: string, detail: Detail): string;
8
-
9
- export interface Check {
10
- (label: string, cond: unknown, detail?: Detail): void;
11
- /** How many times this checker has been called. */
12
- readonly count: number;
13
- }
14
-
15
- export declare function createChecker(options?: {
16
- log?: (line: string) => void;
17
- }): Check;
@@ -1,31 +0,0 @@
1
- // Types for lib/paper-config.mjs, which `src/*.ts` imports. Without this file TypeScript sees
2
- // `any` and each import needs a `@ts-expect-error`, which covers only the next line and stops
3
- // working as soon as a formatter wraps the import across several lines.
4
- //
5
- // The field names are typed as `string`, not as their values, so that the value is written
6
- // once, in paper-config.mjs.
7
-
8
- /** The tool's name — its settings file is named after it. */
9
- export declare const CONFIG_KEY: string;
10
- /** `paperlint.json`: the settings file, at the project root and in each paper. */
11
- export declare const CONFIG_FILE: string;
12
- /** The files that make a directory a paper. */
13
- export declare const PAPER_MARKERS: readonly string[];
14
-
15
- /** The project root, walking up from `startDir` (see the module). */
16
- export declare function findProjectRoot(
17
- startDir: string,
18
- isFile?: (path: string) => boolean,
19
- ): string;
20
-
21
- /** The root `paperlint.json` of `root` — undefined when absent, unparsable or not an object. */
22
- export declare function settingsOf(
23
- root: string,
24
- ): Readonly<Record<string, unknown>> | undefined;
25
- export declare const DEFAULT_PAPERS_ROOT: string;
26
- export declare const PAPERS_DIR_FIELD: string;
27
-
28
- /** Every key a `paperlint.json` may hold, at either level, mapped to who reads it. */
29
- export declare const SETTINGS_KEYS: Readonly<Record<string, string>>;
30
- /** The keys refused in a paper's `paperlint.json`. */
31
- export declare const ROOT_ONLY_KEYS: readonly string[];
@@ -1,36 +0,0 @@
1
- /** Types for `layer-legacy-frozen.mjs`, for its TypeScript test. */
2
- export interface Suppressed {
3
- readonly filePath: string;
4
- readonly suppressedMessages?: readonly {
5
- readonly ruleId: string | null;
6
- readonly line: number;
7
- readonly suppressions?: readonly { readonly justification?: string }[];
8
- }[];
9
- }
10
- export type Counts = Record<string, Record<string, number>>;
11
- export declare const FROZEN_FILE: string;
12
- export declare const LAYER_RULES: readonly string[];
13
- export declare const NO_LEGACY_UNDER: readonly string[];
14
- export declare const LEGACY: RegExp;
15
- export declare function tally(results: readonly Suppressed[]): {
16
- counts: Counts;
17
- unexplained: string[];
18
- };
19
- export declare function judge(o: {
20
- counts: Counts;
21
- unexplained: readonly string[];
22
- frozen: Counts;
23
- }): string[];
24
- export declare function checkFrozen(
25
- root?: string,
26
- options?: { lint?: (cwd: string) => Promise<unknown[]> },
27
- ): Promise<{
28
- problems: string[];
29
- frozen: Counts;
30
- counts: Counts;
31
- }>;
32
- export declare function main(options?: {
33
- check?: () => Promise<{ problems: string[]; frozen: Counts; counts: Counts }>;
34
- log?: (line: string) => void;
35
- err?: (line: string) => void;
36
- }): Promise<number>;
@@ -1,105 +0,0 @@
1
- /**
2
- * Colocated test for `scripts/rules-are-content-only.mjs` — a rule may not ask git.
3
- * Run: `npx vigiles test scripts/rules-are-content-only.harness.mjs`
4
- *
5
- * 🔴 BOTH HALVES. Like its sibling, this guard's success state is silence, so silence alone is
6
- * no evidence it works:
7
- * I. QUIET on this repository's real `eslint-rules/`, and the count is asserted non-zero —
8
- * a guard that scanned nothing is also quiet, and that is the reading to rule out first.
9
- * II. FIRES on each form the defect takes: static `import`, `require()`, dynamic `import()`,
10
- * and the `node:` prefix — the prefix matters because a set membership test that lists
11
- * only the bare specifier passes a file that writes the modern spelling.
12
- * III. QUIET on sources that merely TALK about the banned module — a comment and a string
13
- * literal. This is the half a grep cannot have, and the reason the check parses.
14
- */
15
- import { dirname, join } from "node:path";
16
- import { fileURLToPath } from "node:url";
17
- import { createChecker } from "../lib/check.mjs";
18
-
19
- const HERE = dirname(fileURLToPath(import.meta.url));
20
- const { rulesAreContentOnly, processImports } = await import(
21
- join(HERE, "rules-are-content-only.mjs")
22
- );
23
-
24
- const check = createChecker();
25
-
26
- // ── I. quiet on the real corpus, and it actually looked ───────────────────────
27
- {
28
- const { checked, findings } = rulesAreContentOnly({ cwd: join(HERE, "..") });
29
- check(
30
- `scanned something (${checked.length} rule sources)`,
31
- checked.length > 0,
32
- );
33
- check(
34
- `quiet on the real corpus (${findings.length} findings)`,
35
- findings.length === 0,
36
- );
37
- }
38
-
39
- // ── II. fires on every form the defect has taken ────────────────────────────
40
- for (const [label, src] of [
41
- [
42
- "execFileSync with a git argv",
43
- `import { execFileSync } from "node:child_process";
44
- execFileSync("git", ["cat-file", "-e", sha]);`,
45
- ],
46
- [
47
- "a namespaced call",
48
- `import cp from "node:child_process";
49
- cp.spawnSync("git", ["rev-parse", ref]);`,
50
- ],
51
- [
52
- "execSync with a git shell line",
53
- `import { execSync } from "child_process";
54
- execSync("git log --oneline -1");`,
55
- ],
56
- ["exec, async form", `exec("git rev-list --all", cb);`],
57
- ]) {
58
- check(`FIRES on ${label}`, processImports(src).length === 1);
59
- }
60
-
61
- // ── III. the FALSE POSITIVE that forced the narrowing, asserted so it cannot return ──
62
- // 🔴 `paper-texcount.mjs` in the consumer shells out to `texcount` to count words in a .tex.
63
- // The first version of this check keyed on importing `child_process` at all, so it claimed that
64
- // rule on its first run — a false positive on an `error` gate, which is worse than a miss
65
- // because the gate that cannot be cleared gets switched off rather than fixed.
66
- for (const [label, src] of [
67
- [
68
- "a rule spawning texcount",
69
- `import { execFileSync } from "node:child_process";
70
- execFileSync("texcount", ["-brief", file]);`,
71
- ],
72
- [
73
- "a rule spawning pdflatex",
74
- `import { spawnSync } from "node:child_process";
75
- spawnSync("pdflatex", [tex]);`,
76
- ],
77
- [
78
- "importing child_process without running git",
79
- `import { execFileSync } from "node:child_process";
80
- export default {};`,
81
- ],
82
- ]) {
83
- check(`quiet on ${label}`, processImports(src).length === 0);
84
- }
85
-
86
- // ── IV. quiet on sources that only MENTION it — what a grep cannot do ───────
87
- for (const [label, src] of [
88
- [
89
- "a comment naming git",
90
- `// never reach for git cat-file here\nexport default {};`,
91
- ],
92
- ["a string literal", `export const why = "git cat-file is banned in rules";`],
93
- [
94
- "a reported message",
95
- `export default { meta: { messages: { m: "run git cat-file? no" } } };`,
96
- ],
97
- [
98
- "a program merely NAMED git-something",
99
- `execFileSync("git-lfs-helper", []);`,
100
- ],
101
- ]) {
102
- check(`quiet on ${label}`, processImports(src).length === 0);
103
- }
104
-
105
- console.log(`rules-are-content-only.harness: ${check.count} assertions passed`);