paperlint 3.2.0 → 4.1.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 (442) hide show
  1. package/.github/workflows/ci.yml +16 -23
  2. package/CLAUDE.md +33 -13
  3. package/CONTRIBUTING.md +23 -7
  4. package/README.md +11 -8
  5. package/dist/adapters/banal/failure.d.ts +1 -7
  6. package/dist/adapters/banal/failure.d.ts.map +1 -1
  7. package/dist/adapters/banal/failure.js +5 -7
  8. package/dist/adapters/banal/failure.js.map +1 -1
  9. package/dist/adapters/banal/index.d.ts.map +1 -1
  10. package/dist/adapters/banal/index.js +8 -11
  11. package/dist/adapters/banal/index.js.map +1 -1
  12. package/dist/adapters/banal/install.d.ts.map +1 -1
  13. package/dist/adapters/banal/install.js +4 -3
  14. package/dist/adapters/banal/install.js.map +1 -1
  15. package/dist/adapters/banal/invocation.d.ts +10 -5
  16. package/dist/adapters/banal/invocation.d.ts.map +1 -1
  17. package/dist/adapters/banal/invocation.js +13 -1
  18. package/dist/adapters/banal/invocation.js.map +1 -1
  19. package/dist/adapters/banal/locate.d.ts +12 -9
  20. package/dist/adapters/banal/locate.d.ts.map +1 -1
  21. package/dist/adapters/banal/locate.js +19 -12
  22. package/dist/adapters/banal/locate.js.map +1 -1
  23. package/dist/adapters/banal/run.d.ts +10 -1
  24. package/dist/adapters/banal/run.d.ts.map +1 -1
  25. package/dist/adapters/banal/run.js +8 -4
  26. package/dist/adapters/banal/run.js.map +1 -1
  27. package/dist/adapters/banal/settings.d.ts +1 -1
  28. package/dist/adapters/banal/settings.d.ts.map +1 -1
  29. package/dist/adapters/banal/settings.js +2 -1
  30. package/dist/adapters/banal/settings.js.map +1 -1
  31. package/dist/adapters/latex/document-class.d.ts +22 -0
  32. package/dist/adapters/latex/document-class.d.ts.map +1 -0
  33. package/dist/adapters/latex/document-class.js +102 -0
  34. package/dist/adapters/latex/document-class.js.map +1 -0
  35. package/dist/adapters/latex/hidden.d.ts +16 -0
  36. package/dist/adapters/latex/hidden.d.ts.map +1 -0
  37. package/dist/adapters/latex/hidden.js +49 -0
  38. package/dist/adapters/latex/hidden.js.map +1 -0
  39. package/dist/adapters/latex/index.d.ts +29 -0
  40. package/dist/adapters/latex/index.d.ts.map +1 -0
  41. package/dist/adapters/latex/index.js +18 -0
  42. package/dist/adapters/latex/index.js.map +1 -0
  43. package/dist/adapters/latex/nodes.d.ts +54 -0
  44. package/dist/adapters/latex/nodes.d.ts.map +1 -0
  45. package/dist/adapters/latex/nodes.js +93 -0
  46. package/dist/adapters/latex/nodes.js.map +1 -0
  47. package/dist/adapters/latex/outline.d.ts +10 -0
  48. package/dist/adapters/latex/outline.d.ts.map +1 -0
  49. package/dist/adapters/latex/outline.js +39 -0
  50. package/dist/adapters/latex/outline.js.map +1 -0
  51. package/dist/adapters/latex/parse.d.ts +42 -0
  52. package/dist/adapters/latex/parse.d.ts.map +1 -0
  53. package/dist/adapters/latex/parse.js +94 -0
  54. package/dist/adapters/latex/parse.js.map +1 -0
  55. package/dist/adapters/latex/rendered.d.ts +10 -0
  56. package/dist/adapters/latex/rendered.d.ts.map +1 -0
  57. package/dist/adapters/latex/rendered.js +39 -0
  58. package/dist/adapters/latex/rendered.js.map +1 -0
  59. package/dist/adapters/memory/index.d.ts.map +1 -1
  60. package/dist/adapters/memory/index.js +8 -2
  61. package/dist/adapters/memory/index.js.map +1 -1
  62. package/dist/adapters/node/process.io.d.ts +6 -3
  63. package/dist/adapters/node/process.io.d.ts.map +1 -1
  64. package/dist/adapters/node/process.io.js.map +1 -1
  65. package/dist/adapters/node/workspace.io.d.ts.map +1 -1
  66. package/dist/adapters/node/workspace.io.js +4 -2
  67. package/dist/adapters/node/workspace.io.js.map +1 -1
  68. package/dist/adapters/pdfjs/fill.d.ts.map +1 -1
  69. package/dist/adapters/pdfjs/fill.js +12 -12
  70. package/dist/adapters/pdfjs/fill.js.map +1 -1
  71. package/dist/adapters/references/index.d.ts.map +1 -1
  72. package/dist/adapters/references/index.js +4 -2
  73. package/dist/adapters/references/index.js.map +1 -1
  74. package/dist/build-engine.d.ts +1 -1
  75. package/dist/build-engine.d.ts.map +1 -1
  76. package/dist/build-engine.js +8 -1
  77. package/dist/build-engine.js.map +1 -1
  78. package/dist/build.d.ts +2 -3
  79. package/dist/build.d.ts.map +1 -1
  80. package/dist/build.js +9 -10
  81. package/dist/build.js.map +1 -1
  82. package/dist/caller-path.d.ts +3 -0
  83. package/dist/caller-path.d.ts.map +1 -0
  84. package/dist/caller-path.js +13 -0
  85. package/dist/caller-path.js.map +1 -0
  86. package/dist/cli.d.ts +10 -17
  87. package/dist/cli.d.ts.map +1 -1
  88. package/dist/cli.js +91 -17
  89. package/dist/cli.js.map +1 -1
  90. package/dist/doctor.d.ts +12 -3
  91. package/dist/doctor.d.ts.map +1 -1
  92. package/dist/doctor.js +2 -1
  93. package/dist/doctor.js.map +1 -1
  94. package/dist/domain/paths.d.ts +8 -2
  95. package/dist/domain/paths.d.ts.map +1 -1
  96. package/dist/domain/paths.js +12 -1
  97. package/dist/domain/paths.js.map +1 -1
  98. package/dist/domain/ready.d.ts +24 -0
  99. package/dist/domain/ready.d.ts.map +1 -0
  100. package/dist/domain/ready.js +11 -0
  101. package/dist/domain/ready.js.map +1 -0
  102. package/dist/domain/record.d.ts +8 -0
  103. package/dist/domain/record.d.ts.map +1 -0
  104. package/dist/domain/record.js +8 -0
  105. package/dist/domain/record.js.map +1 -0
  106. package/dist/domain/sha256.d.ts.map +1 -1
  107. package/dist/domain/sha256.js +4 -2
  108. package/dist/domain/sha256.js.map +1 -1
  109. package/dist/domain/tex-document.d.ts +84 -0
  110. package/dist/domain/tex-document.d.ts.map +1 -0
  111. package/dist/domain/tex-document.js +43 -0
  112. package/dist/domain/tex-document.js.map +1 -0
  113. package/dist/engine.d.ts +23 -22
  114. package/dist/engine.d.ts.map +1 -1
  115. package/dist/engine.js.map +1 -1
  116. package/dist/eslint-rules/bib-reachable-entry.d.ts +1 -1
  117. package/dist/eslint-rules/bib-reachable-entry.js.map +1 -1
  118. package/dist/eslint-rules/install-path-literals.d.ts +2 -2
  119. package/dist/eslint-rules/install-path-literals.js.map +1 -1
  120. package/dist/eslint-rules/paper-research-question.d.ts +1 -1
  121. package/dist/eslint-rules/paper-research-question.js.map +1 -1
  122. package/dist/eslint-rules/paper-stages.d.ts +2 -2
  123. package/dist/eslint-rules/paper-stages.js.map +1 -1
  124. package/dist/eslint-rules/paper-typography.d.ts +6 -6
  125. package/dist/eslint-rules/paper-typography.js.map +1 -1
  126. package/dist/eslint-rules/pdf-last-page-balance.d.ts +1 -1
  127. package/dist/eslint-rules/pdf-last-page-balance.js.map +1 -1
  128. package/dist/eslint-rules/review-frontmatter.d.ts +2 -2
  129. package/dist/eslint-rules/review-frontmatter.js.map +1 -1
  130. package/dist/eslint-rules/sibling-frontmatter.d.ts +1 -1
  131. package/dist/eslint-rules/sibling-frontmatter.d.ts.map +1 -1
  132. package/dist/eslint-rules/temp-root-realpath.d.ts +1 -1
  133. package/dist/eslint-rules/temp-root-realpath.js.map +1 -1
  134. package/dist/eslint-rules/tex-build.d.ts +2 -2
  135. package/dist/eslint-rules/tex-build.js.map +1 -1
  136. package/dist/facts-file.d.ts.map +1 -1
  137. package/dist/facts-file.js +31 -28
  138. package/dist/facts-file.js.map +1 -1
  139. package/dist/hooks-settings.d.ts.map +1 -1
  140. package/dist/hooks-settings.js +17 -2
  141. package/dist/hooks-settings.js.map +1 -1
  142. package/dist/init.d.ts +3 -4
  143. package/dist/init.d.ts.map +1 -1
  144. package/dist/init.js +18 -8
  145. package/dist/init.js.map +1 -1
  146. package/dist/lib/paper-config.d.ts.map +1 -1
  147. package/dist/lib/paper-config.js +1 -5
  148. package/dist/lib/paper-config.js.map +1 -1
  149. package/dist/new-paper.d.ts +5 -0
  150. package/dist/new-paper.d.ts.map +1 -1
  151. package/dist/new-paper.js +8 -1
  152. package/dist/new-paper.js.map +1 -1
  153. package/dist/package-dirs.d.ts +5 -0
  154. package/dist/package-dirs.d.ts.map +1 -0
  155. package/dist/package-dirs.js +14 -0
  156. package/dist/package-dirs.js.map +1 -0
  157. package/dist/paper-files.d.ts +4 -4
  158. package/dist/paper-files.d.ts.map +1 -1
  159. package/dist/paper-files.js +6 -6
  160. package/dist/paper-files.js.map +1 -1
  161. package/dist/paper-settings.js +3 -3
  162. package/dist/paper-settings.js.map +1 -1
  163. package/dist/pdf-facts.d.ts +51 -1
  164. package/dist/pdf-facts.d.ts.map +1 -1
  165. package/dist/pdf-facts.js +23 -15
  166. package/dist/pdf-facts.js.map +1 -1
  167. package/dist/pdf-geometry.d.ts.map +1 -1
  168. package/dist/pdf-geometry.js +2 -3
  169. package/dist/pdf-geometry.js.map +1 -1
  170. package/dist/ports/latex.d.ts +24 -0
  171. package/dist/ports/latex.d.ts.map +1 -0
  172. package/dist/ports/latex.js +2 -0
  173. package/dist/ports/latex.js.map +1 -0
  174. package/dist/ports/tool-installer.d.ts +2 -14
  175. package/dist/ports/tool-installer.d.ts.map +1 -1
  176. package/dist/presets.d.ts +15 -2
  177. package/dist/presets.d.ts.map +1 -1
  178. package/dist/presets.js +35 -10
  179. package/dist/presets.js.map +1 -1
  180. package/dist/references.d.ts.map +1 -1
  181. package/dist/references.js +4 -4
  182. package/dist/references.js.map +1 -1
  183. package/dist/rules-config.d.ts +26 -1
  184. package/dist/rules-config.d.ts.map +1 -1
  185. package/dist/rules-config.js +9 -9
  186. package/dist/rules-config.js.map +1 -1
  187. package/dist/tex-requirements.d.ts +15 -0
  188. package/dist/tex-requirements.d.ts.map +1 -1
  189. package/dist/tex-requirements.js +28 -10
  190. package/dist/tex-requirements.js.map +1 -1
  191. package/dist/tex-venue-rules.d.ts +83 -0
  192. package/dist/tex-venue-rules.d.ts.map +1 -0
  193. package/dist/tex-venue-rules.js +254 -0
  194. package/dist/tex-venue-rules.js.map +1 -0
  195. package/dist/types.d.ts +5 -3
  196. package/dist/types.d.ts.map +1 -1
  197. package/dist/venue-rules.d.ts +1 -1
  198. package/dist/venue-rules.d.ts.map +1 -1
  199. package/dist/venue-rules.js +20 -7
  200. package/dist/venue-rules.js.map +1 -1
  201. package/docs/configuration.md +5 -4
  202. package/docs/e2e.md +69 -40
  203. package/docs/install.md +1 -1
  204. package/docs/prior-art/README.md +1 -1
  205. package/docs/prior-art/package-location.md +5 -5
  206. package/docs/prior-art/repro/README.md +1 -1
  207. package/docs/prior-art/test-tooling.md +21 -10
  208. package/docs/rules/tex/required-section.md +73 -0
  209. package/docs/rules/tex/template.md +62 -0
  210. package/docs/rules/tex/venue-leftover.md +71 -0
  211. package/docs/rules.md +70 -48
  212. package/docs/testing.md +17 -4
  213. package/docs/texlive-install-decision.md +1 -1
  214. package/docs/toolchain.md +2 -2
  215. package/eslint-rules/bib-reachable-entry.ts +1 -1
  216. package/eslint-rules/install-path-literals.ts +2 -2
  217. package/eslint-rules/paper-research-question.ts +1 -1
  218. package/eslint-rules/paper-stages.ts +2 -2
  219. package/eslint-rules/paper-typography.ts +2 -2
  220. package/eslint-rules/pdf-last-page-balance.ts +1 -1
  221. package/eslint-rules/review-frontmatter.ts +1 -1
  222. package/eslint-rules/temp-root-realpath.ts +1 -1
  223. package/eslint-rules/tex-build.ts +2 -2
  224. package/eslint.config.mjs +162 -15
  225. package/fixtures/build-e2e/acmart/expect.json +26 -0
  226. package/fixtures/build-e2e/aidc/PIPELINE-STATUS.md +3 -0
  227. package/fixtures/build-e2e/aidc/expect.json +14 -0
  228. package/fixtures/build-e2e/aidc/paper.tex +46 -0
  229. package/fixtures/build-e2e/aidc/paperlint.json +1 -0
  230. package/fixtures/build-e2e/broken/expect.json +11 -0
  231. package/fixtures/build-e2e/cite/expect.json +27 -0
  232. package/fixtures/build-e2e/empty/expect.json +12 -0
  233. package/fixtures/build-e2e/fallback/expect.json +53 -0
  234. package/fixtures/build-e2e/guards/expect.json +6 -0
  235. package/fixtures/build-e2e/no-source/expect.json +10 -0
  236. package/fixtures/build-e2e/unbalanced/expect.json +60 -0
  237. package/fixtures/install-path-literals/clean.fixture.mjs +1 -1
  238. package/fixtures/install-path-literals/clean.md +2 -2
  239. package/fixtures/install-path-literals/defect.fixture.mjs +1 -1
  240. package/fixtures/install-path-literals/defect.md +2 -2
  241. package/fixtures/pdf-facts/README.md +1 -1
  242. package/fixtures/real-markdown-paper/baseline.ts +1 -1
  243. package/fixtures/render-paper/build-clean.sh +3 -5
  244. package/hooks/paper-status-gates.sh +9 -19
  245. package/lib/paper-config.harness.mjs +1 -1
  246. package/lib/paper-config.ts +1 -5
  247. package/lib/skill-checks.mjs +24 -381
  248. package/lib/skill-checks.test.mjs +63 -115
  249. package/lib/skill-corpus.mjs +10 -251
  250. package/lib/skill-corpus.test.mjs +2 -13
  251. package/lib/skill-trigger-cases.mjs +1 -1
  252. package/package.json +13 -2
  253. package/{skills/submit-paper/references/venues → presets}/acm-sigconf.jsonc +4 -1
  254. package/{skills/submit-paper/references/venues → presets}/agenticdev.jsonc +4 -0
  255. package/presets/aidc.jsonc +73 -0
  256. package/{skills/submit-paper/references/venues → presets}/aisec.jsonc +4 -0
  257. package/presets/ieee-conference.jsonc +66 -0
  258. package/{skills/submit-paper/references/venues → presets}/realm.jsonc +8 -1
  259. package/{skills/submit-paper/references/venues → presets/tex}/paper-guards.tex +1 -1
  260. package/{skills/submit-paper/references/venues → presets}/venue-profile.schema.json +30 -2
  261. package/scripts/check.harness.ts +64 -10
  262. package/scripts/check.test.ts +60 -13
  263. package/scripts/check.ts +83 -30
  264. package/scripts/coverage-config.test.ts +0 -1
  265. package/scripts/harness-api.frozen.json +0 -1
  266. package/scripts/test.test.ts +1 -1
  267. package/scripts/test.ts +1 -1
  268. package/skills/analyze-sibling-paper/SKILL.md +1 -39
  269. package/skills/analyze-sibling-paper/SKILL.md.spec.ts +0 -38
  270. package/skills/argument-arc/SKILL.md +2 -36
  271. package/skills/argument-arc/SKILL.md.spec.ts +1 -43
  272. package/skills/build-benchmark/SKILL.md +1 -41
  273. package/skills/build-benchmark/SKILL.md.spec.ts +0 -40
  274. package/skills/camera-ready/SKILL.md +1 -34
  275. package/skills/camera-ready/SKILL.md.spec.ts +0 -33
  276. package/skills/cold-read-diff/SKILL.md +1 -35
  277. package/skills/cold-read-diff/SKILL.md.spec.ts +0 -34
  278. package/skills/draft-paper/SKILL.md +2 -41
  279. package/skills/draft-paper/SKILL.md.spec.ts +1 -50
  280. package/skills/extend-paper/SKILL.md +2 -33
  281. package/skills/extend-paper/SKILL.md.spec.ts +1 -42
  282. package/skills/find-venue/SKILL.md +2 -38
  283. package/skills/find-venue/SKILL.md.spec.ts +1 -47
  284. package/skills/grade-paper-writing/SKILL.md +3 -38
  285. package/skills/grade-paper-writing/SKILL.md.spec.ts +2 -47
  286. package/skills/grade-paper-writing/prose-lint.mjs +2 -3
  287. package/skills/grade-paper-writing/prose-lint.test.ts +1 -1
  288. package/skills/harden-paper/SKILL.md +1 -39
  289. package/skills/harden-paper/SKILL.md.spec.ts +0 -38
  290. package/skills/map-prior-work/SKILL.md +1 -35
  291. package/skills/map-prior-work/SKILL.md.spec.ts +0 -34
  292. package/skills/osf-artifact-upload/osf-artifact-upload.harness.ts +3 -4
  293. package/skills/paper-adversarial-review/SKILL.md +2 -35
  294. package/skills/paper-adversarial-review/SKILL.md.spec.ts +1 -43
  295. package/skills/paper-pipeline/PIPELINE-MAP.md +2 -2
  296. package/skills/paper-pipeline/SKILL.md +3 -29
  297. package/skills/paper-pipeline/SKILL.md.spec.ts +2 -28
  298. package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +1 -1
  299. package/skills/paper-pipeline/paper-pipeline.harness.mjs +25 -40
  300. package/skills/paper-pipeline/references/acceptance-gate.md +1 -6
  301. package/skills/paper-pipeline/references/pipeline-status-template.md +8 -35
  302. package/skills/paper-pipeline/scripts/README.md +30 -225
  303. package/skills/paper-pipeline/scripts/artifact-coverage.harness.mjs +4 -8
  304. package/skills/paper-pipeline/scripts/consumer.d.mts +0 -2
  305. package/skills/paper-pipeline/scripts/consumer.harness.mjs +6 -104
  306. package/skills/paper-pipeline/scripts/consumer.mjs +24 -119
  307. package/skills/paper-pipeline/scripts/generated-code.harness.mjs +2 -3
  308. package/skills/paper-pipeline/scripts/population-map.harness.mjs +3 -3
  309. package/skills/paper-status/SKILL.md +2 -42
  310. package/skills/paper-status/SKILL.md.spec.ts +1 -41
  311. package/skills/pc-panel-review/SKILL.md +2 -46
  312. package/skills/pc-panel-review/SKILL.md.spec.ts +1 -45
  313. package/skills/plan-paper-timeline/SKILL.md +2 -36
  314. package/skills/plan-paper-timeline/SKILL.md.spec.ts +0 -36
  315. package/skills/render-paper/SKILL.md +2 -38
  316. package/skills/render-paper/SKILL.md.spec.ts +1 -37
  317. package/skills/render-paper/check-render.sh +16 -63
  318. package/skills/render-paper/render-paper.harness.mjs +17 -59
  319. package/skills/research-ideate/SKILL.md +2 -39
  320. package/skills/research-ideate/SKILL.md.spec.ts +1 -47
  321. package/skills/study-accepted-papers/SKILL.md +1 -34
  322. package/skills/study-accepted-papers/SKILL.md.spec.ts +0 -33
  323. package/skills/submit-paper/SKILL.md +1 -33
  324. package/skills/submit-paper/SKILL.md.spec.ts +0 -32
  325. package/skills/sweep-design-space/SKILL.md +1 -38
  326. package/skills/sweep-design-space/SKILL.md.spec.ts +0 -37
  327. package/skills/tighten-paper/SKILL.md +2 -39
  328. package/skills/tighten-paper/SKILL.md.spec.ts +1 -47
  329. package/skills/tighten-paper/structure.mjs +4 -11
  330. package/skills/verify-citations/SKILL.md +2 -38
  331. package/skills/verify-citations/SKILL.md.spec.ts +1 -37
  332. package/src/adapters/banal/failure.ts +9 -10
  333. package/src/adapters/banal/index.test.ts +3 -3
  334. package/src/adapters/banal/index.ts +8 -10
  335. package/src/adapters/banal/install.ts +9 -4
  336. package/src/adapters/banal/invocation.test.ts +20 -8
  337. package/src/adapters/banal/invocation.ts +24 -9
  338. package/src/adapters/banal/locate.test.ts +2 -2
  339. package/src/adapters/banal/locate.ts +27 -21
  340. package/src/adapters/banal/output.test.ts +1 -1
  341. package/src/adapters/banal/run.test.ts +3 -3
  342. package/src/adapters/banal/run.ts +16 -10
  343. package/src/adapters/banal/settings.ts +2 -2
  344. package/src/adapters/banal/xml.test.ts +1 -1
  345. package/src/adapters/curl/download.test.ts +4 -4
  346. package/src/adapters/latex/document-class.ts +147 -0
  347. package/src/adapters/latex/hidden.ts +70 -0
  348. package/src/adapters/latex/index.test.ts +361 -0
  349. package/src/adapters/latex/index.ts +55 -0
  350. package/src/adapters/latex/nodes.ts +141 -0
  351. package/src/adapters/latex/outline.ts +64 -0
  352. package/src/adapters/latex/parse.ts +136 -0
  353. package/src/adapters/latex/rendered.ts +59 -0
  354. package/src/adapters/memory/index.ts +4 -3
  355. package/src/adapters/node/files.test.ts +5 -3
  356. package/src/adapters/node/process.io.ts +9 -2
  357. package/src/adapters/node/process.test.ts +4 -4
  358. package/src/adapters/node/workspace.io.ts +9 -4
  359. package/src/adapters/pdfjs/fill.test.ts +1 -1
  360. package/src/adapters/pdfjs/fill.ts +18 -10
  361. package/src/adapters/references/index.ts +7 -2
  362. package/src/build-engine.test.ts +11 -6
  363. package/src/build-engine.ts +10 -5
  364. package/src/build.harness.mjs +5 -7
  365. package/src/build.test.ts +10 -11
  366. package/src/build.ts +12 -13
  367. package/src/caller-path.ts +13 -0
  368. package/src/cli.harness.mjs +1 -3
  369. package/src/cli.test.ts +21 -16
  370. package/src/cli.ts +115 -33
  371. package/src/doctor.test.ts +1 -1
  372. package/src/doctor.ts +14 -5
  373. package/src/domain/paths.test.ts +15 -0
  374. package/src/domain/paths.ts +20 -1
  375. package/src/domain/ready.ts +31 -0
  376. package/src/domain/record.test.ts +20 -0
  377. package/src/domain/record.ts +12 -0
  378. package/src/domain/sha256.ts +5 -3
  379. package/src/domain/tex-document.test.ts +80 -0
  380. package/src/domain/tex-document.ts +110 -0
  381. package/src/engine.harness.mjs +1 -1
  382. package/src/engine.ts +30 -2
  383. package/src/facts-file.test.ts +4 -7
  384. package/src/facts-file.ts +45 -30
  385. package/src/hooks-settings.harness.ts +17 -0
  386. package/src/hooks-settings.ts +28 -4
  387. package/src/init.test.ts +25 -5
  388. package/src/init.ts +26 -12
  389. package/src/latex-loop.harness.ts +1 -1
  390. package/src/new-paper.harness.mjs +7 -23
  391. package/src/new-paper.test.ts +28 -3
  392. package/src/new-paper.ts +15 -3
  393. package/src/package-dirs.ts +17 -0
  394. package/src/paper-files.test.ts +22 -19
  395. package/src/paper-files.ts +16 -27
  396. package/src/paper-settings-commands.test.ts +6 -1
  397. package/src/paper-settings.test.ts +2 -2
  398. package/src/paper-settings.ts +5 -5
  399. package/src/pdf-facts.test.ts +16 -5
  400. package/src/pdf-facts.ts +79 -22
  401. package/src/pdf-geometry.ts +5 -3
  402. package/src/ports/latex.ts +29 -0
  403. package/src/ports/tool-installer.ts +2 -17
  404. package/src/presets.test.ts +65 -4
  405. package/src/presets.ts +65 -28
  406. package/src/references.test.ts +25 -19
  407. package/src/references.ts +4 -5
  408. package/src/rule-cases.test.ts +474 -0
  409. package/src/rule-docs.test.ts +159 -0
  410. package/src/rules-config.ts +40 -11
  411. package/src/tex-requirements.harness.ts +13 -23
  412. package/src/tex-requirements.test.ts +21 -0
  413. package/src/tex-requirements.ts +64 -14
  414. package/src/tex-venue-rules.test.ts +659 -0
  415. package/src/tex-venue-rules.ts +429 -0
  416. package/src/toolchain.harness.mjs +1 -1
  417. package/src/toolchain.test.ts +9 -5
  418. package/src/toolchain.ts +2 -2
  419. package/src/types.ts +5 -3
  420. package/src/venue-rules.test.ts +104 -8
  421. package/src/venue-rules.ts +22 -12
  422. package/templates/paper/paper.tex +2 -1
  423. package/skills/paper-pipeline/scripts/announce.mjs +0 -67
  424. package/skills/paper-pipeline/scripts/announce.test.mjs +0 -102
  425. package/skills/paper-pipeline/scripts/fixtures/clean/PIPELINE-STATUS.md +0 -51
  426. package/skills/paper-pipeline/scripts/fixtures/dirty/PIPELINE-STATUS.md +0 -52
  427. package/skills/paper-pipeline/scripts/fixtures/dirty/paper.md +0 -6
  428. package/skills/paper-pipeline/scripts/fixtures/scorecard.ts +0 -100
  429. package/skills/paper-pipeline/scripts/ledger.cli.test.mjs +0 -302
  430. package/skills/paper-pipeline/scripts/ledger.mjs +0 -623
  431. package/skills/paper-pipeline/scripts/ledger.test.mjs +0 -254
  432. package/skills/paper-pipeline/scripts/pipeline-check.cli.test.mjs +0 -474
  433. package/skills/paper-pipeline/scripts/pipeline-check.harness.mjs +0 -328
  434. package/skills/paper-pipeline/scripts/pipeline-check.mjs +0 -750
  435. package/skills/paper-pipeline/scripts/pipeline-edges.mjs +0 -169
  436. package/skills/paper-pipeline/scripts/pipeline-edges.test.mjs +0 -73
  437. package/skills/paper-pipeline/scripts/run-mechanical.mjs +0 -648
  438. package/skills/paper-pipeline/scripts/run-mechanical.test.mjs +0 -468
  439. package/skills/paper-pipeline/scripts/status.mjs +0 -295
  440. package/skills/paper-pipeline/scripts/status.test.mjs +0 -232
  441. /package/{skills/submit-paper/references/venues → presets/tex}/agenticdev.tex +0 -0
  442. /package/{skills/submit-paper/references/venues → presets}/tex-base.jsonc +0 -0
@@ -1,12 +1,10 @@
1
1
  /**
2
- * consumer.harness.mjs — every rung of `ledgerPath()`, both directions, plus the two path facts
3
- * the move into a package depends on.
2
+ * consumer.harness.mjs — the path facts the move into a package depends on, both directions.
4
3
  *
5
4
  * `npx vigiles test skills/paper-pipeline/scripts/consumer.harness.mjs`
6
5
  *
7
6
  * 🔴 WHY THIS FILE IS NOT OPTIONAL. The resolutions in `consumer.mjs` all fail in the SILENT
8
- * direction when they are wrong: a bad ledger path still accepts appends, a bad main guard still
9
- * exits 0, a bad root still hashes a missing directory to a stable value. None of them produce an
7
+ * direction when they are wrong: a bad main guard still exits 0, a bad root still hashes a missing directory to a stable value. None of them produce an
10
8
  * error, and every one of them produces a run that reads as success. So each is asserted here in
11
9
  * BOTH directions — fires when it should, silent when it should — because an assertion that only
12
10
  * ever sees the good case cannot tell a working resolver from a constant.
@@ -38,7 +36,6 @@ import {
38
36
  insideNodeModules,
39
37
  installedSkills,
40
38
  isMain,
41
- ledgerPath,
42
39
  pipelineScripts,
43
40
  scriptsRoot,
44
41
  PACKAGE_NAME,
@@ -101,94 +98,6 @@ function installedDir(root) {
101
98
  return d;
102
99
  }
103
100
 
104
- // ── I. RUNG 1 — the environment wins, and it wins over a declaration ─────────────────────────
105
- {
106
- const root = fakeConsumer({ ledger: "declared/here.jsonl" });
107
- const env = { PIPELINE_LEDGER: join(root, "from-env.jsonl") };
108
- assert.equal(
109
- ledgerPath(installedDir(root), { env, cwd: root }),
110
- resolve(join(root, "from-env.jsonl")),
111
- "PIPELINE_LEDGER did not win. It must outrank everything: harnesses set it BEFORE importing " +
112
- "the ledger precisely so fixture rows cannot reach real history, and a declaration on disk " +
113
- "that could override it would put them there.",
114
- );
115
- // The other direction: with the variable gone, the same call must move to the next rung.
116
- assert.equal(
117
- ledgerPath(installedDir(root), { env: {}, cwd: root }),
118
- resolve(root, "declared/here.jsonl"),
119
- "without PIPELINE_LEDGER the declaration must be used — otherwise rung 1 is not a rung, it " +
120
- "is the only path, and this assertion pair proves nothing about precedence.",
121
- );
122
- }
123
-
124
- // ── II. RUNG 2 — the consumer's declaration, resolved against the consumer root ───────────────
125
- {
126
- const root = fakeConsumer({ ledger: "docs/pipeline-runs.jsonl" });
127
- assert.equal(
128
- ledgerPath(installedDir(root), { env: {}, cwd: root }),
129
- join(root, "docs/pipeline-runs.jsonl"),
130
- "a declared relative ledger must resolve against the consumer root, not against the cwd of " +
131
- "whatever process happened to start",
132
- );
133
- // 🔴 `null` is a keystroke, not an absence. `?? DEFAULT` would read it as "nothing declared"
134
- // and silently use another file — the same distinction `papersRoot()` makes.
135
- assert.throws(
136
- () =>
137
- ledgerPath(installedDir(root), {
138
- env: {},
139
- cwd: fakeConsumer({ ledger: null }),
140
- }),
141
- /must be a non-empty string/,
142
- 'a `"ledger": null` was accepted. Anything written down must be usable; only ABSENCE may ' +
143
- "fall through to the next rung.",
144
- );
145
- assert.throws(
146
- () =>
147
- ledgerPath(installedDir(root), {
148
- env: {},
149
- cwd: fakeConsumer({ ledger: "" }),
150
- }),
151
- /must be a non-empty string/,
152
- "an empty declared ledger was accepted; it resolves to the consumer root itself",
153
- );
154
- }
155
-
156
- // ── III. RUNG 3 — beside the file, but NEVER inside node_modules ──────────────────────────────
157
- {
158
- const root = fakeConsumer(undefined);
159
- // Developing the package in its own checkout: the default is legitimate.
160
- const own = mkdtempSync(join(TMP, "checkout-"));
161
- assert.equal(
162
- ledgerPath(own, { env: {}, cwd: root }),
163
- join(own, "runs.jsonl"),
164
- "outside node_modules and with nothing declared, the ledger belongs beside the module",
165
- );
166
- // Installed as a dependency with nothing declared: refusing is the whole point.
167
- let err;
168
- try {
169
- ledgerPath(installedDir(root), { env: {}, cwd: root });
170
- } catch (e) {
171
- err = e;
172
- }
173
- assert.ok(
174
- err,
175
- "🔴 ledgerPath RETURNED A PATH INSIDE node_modules. Appending there SUCCEEDS, so the rows " +
176
- "look recorded until the next `npm ci` deletes them — silent data loss with no error at " +
177
- "any point. This must throw.",
178
- );
179
- assert.match(
180
- err.message,
181
- /"ledger"/,
182
- "the refusal must name the key to declare. An error that states the problem without the cure " +
183
- "gets worked around by whoever hits it.",
184
- );
185
- assert.match(
186
- err.message,
187
- /paperlint\.json/,
188
- "the refusal must name the file the key goes in",
189
- );
190
- }
191
-
192
101
  // ── IV. `insideNodeModules` is SEGMENT-WISE, not a substring ──────────────────────────────────
193
102
  assert.equal(
194
103
  insideNodeModules(join("a", "node_modules", "b")),
@@ -199,7 +108,7 @@ assert.equal(
199
108
  insideNodeModules(join("a", "my-node_modules-inspector", "b")),
200
109
  false,
201
110
  "a directory whose NAME merely contains `node_modules` was treated as an install — that " +
202
- "consumer would be refused its own ledger for a naming coincidence",
111
+ "consumer would be refused for a naming coincidence",
203
112
  );
204
113
  assert.equal(
205
114
  insideNodeModules(join("a", "b")),
@@ -433,7 +342,7 @@ assert.equal(
433
342
  );
434
343
  }
435
344
 
436
- // ── X. THE SCRIPT NAMES ARE DERIVED, NOT RESTATED ─────────────────────────────────────────────
345
+ // ── X. THE PREFIX IS DERIVED, NOT RESTATED ─────────────────────────────────────────────
437
346
  // The names belong to this package; a consumer that spelled them out would keep its copy in step
438
347
  // by hand. The trailing slash on `prefix` is load-bearing: without it the prefix also matches a
439
348
  // SIBLING directory whose name merely starts with the root's.
@@ -443,19 +352,17 @@ assert.equal(
443
352
  // `assert` aborts at the first failure, and a prefix that lost its slash died on "must end in a
444
353
  // separator" — leaving the assertion that states WHY the slash matters unreached and unproven.
445
354
  assert.equal(
446
- "a/b-other/ledger.mjs".startsWith(s.prefix),
355
+ "a/b-other/structure.mjs".startsWith(s.prefix),
447
356
  false,
448
357
  "the prefix matched a sibling directory sharing the root's name — drop the trailing slash " +
449
358
  "and every such path is mistaken for a pipeline script",
450
359
  );
451
360
  assert.equal(
452
- "a/b/ledger.mjs".startsWith(s.prefix),
361
+ "a/b/structure.mjs".startsWith(s.prefix),
453
362
  true,
454
363
  "the prefix failed on a real member",
455
364
  );
456
365
  assert.equal(s.prefix, "a/b/", "prefix must end in a separator");
457
- assert.equal(s.announce, "a/b/announce.mjs");
458
- assert.equal(s.ledger, "a/b/ledger.mjs");
459
366
 
460
367
  // EXACTLY ONE slash, whatever the consumer typed. `scriptsRoot()` returns the declaration
461
368
  // verbatim, so a trailing slash in paperlint.json arrives here intact; `${root}/` would then give
@@ -467,11 +374,6 @@ assert.equal(
467
374
  'a declared trailing slash doubled the separator. `"scripts": "tools/pipeline/"` is a normal ' +
468
375
  "thing to write, and the doubled prefix matches no instruction at all.",
469
376
  );
470
- assert.equal(
471
- typedSlash.ledger,
472
- "a/b/ledger.mjs",
473
- "the script paths doubled the separator too",
474
- );
475
377
  }
476
378
 
477
379
  // ── XI. WHICH SKILLS ARE INSTALLED — a link counts, a dangling link is REFUSED (paperlint#62) ────────
@@ -7,11 +7,9 @@
7
7
  *
8
8
  * 1. where is the consumer's repository root, now that walking `..` from here lands in
9
9
  * `node_modules/` instead;
10
- * 2. where does the run ledger live, now that the directory next to this file is wiped by
11
- * `npm ci`;
12
- * 3. was this file executed, or merely imported — a question whose usual answer stops
10
+ * 2. was this file executed, or merely imported — a question whose usual answer stops
13
11
  * working the moment the file is reached through a symlink;
14
- * 4. by what path a SKILL'S PROSE names these scripts, so that a checker can tell an
12
+ * 3. by what path a SKILL'S PROSE names these scripts, so that a checker can tell an
15
13
  * instruction that runs them from one that runs something else. See the fourth-carrier
16
14
  * section at the bottom of this file.
17
15
  *
@@ -23,7 +21,7 @@
23
21
  * paper-pipeline/scripts
24
22
  *
25
23
  * so every command a skill's prose already names — `node .claude/skills/paper-pipeline/scripts/
26
- * ledger.mjs …` — keeps working unchanged. That is the whole reason the symlink exists: the
24
+ * <script>.mjs …` — keeps working unchanged. That is the whole reason the symlink exists: the
27
25
  * first consumer names those paths 306 times across 129 files, almost all of it in the PROSE of
28
26
  * skills, which no refactor can rewrite.
29
27
  *
@@ -35,12 +33,11 @@
35
33
  * process.cwd() /…/<the consumer repository root>
36
34
  *
37
35
  * Node resolves the entry point to its realpath (`--preserve-symlinks-main` is off by default)
38
- * but leaves `argv[1]` as typed. Three consequences, each one a function here:
36
+ * but leaves `argv[1]` as typed. Two consequences, each one a function here:
39
37
  *
40
38
  * 🔴 `import.meta.url === \`file://${process.argv[1]}\`` — the main guard nine scripts in this
41
39
  * directory used — is `false` through the symlink. NOT an error: the process exits 0 having
42
- * done nothing. `ledger.mjs record …` would silently record nothing, `announce.mjs` would
43
- * silently announce nothing, and every caller would read success. `isMain()` compares
40
+ * done nothing, and every caller reads success. `isMain()` compares
44
41
  * against the REALPATH of `argv[1]` instead, which is true through both routes.
45
42
  *
46
43
  * 🔴 `resolve(import.meta.dirname, "..", "..", "..", "..")` — the repository root, correct
@@ -48,30 +45,9 @@
48
45
  * by `process.cwd()`, which is the consumer root because every one of those 306 invocations
49
46
  * is `node .claude/skills/…` typed from the repository root. `consumerRoot()`.
50
47
  *
51
- * 🔴 `join(import.meta.dirname, "runs.jsonl")` — the ledger's default home — now points inside
52
- * `node_modules`, which `npm ci` deletes. Appending there is SILENT DATA LOSS: the write
53
- * succeeds, the rows are real, and they vanish at the next install with no error anywhere.
54
- * `ledgerPath()` refuses to return that path at all.
55
- *
56
- * ── THE LEDGER, THREE RUNGS ─────────────────────────────────────────────────
57
- * 1. `PIPELINE_LEDGER` in the environment — always wins. This is what test harnesses set to
58
- * keep fixture rows out of real history, so it must outrank a declaration on disk.
59
- * 2. `{ "ledger": "…" }` in the CONSUMER's root `paperlint.json`, read
60
- * from `process.cwd()`, resolved relative to it.
61
- * 3. `runs.jsonl` beside this file — ONLY when this file is not inside `node_modules`, i.e.
62
- * when the package is being developed in its own checkout. Inside `node_modules` with
63
- * nothing declared, this THROWS.
64
- *
65
- * 🔴 WHY RUNG 3 THROWS RATHER THAN GUESSING. Every other candidate default is worse in the same
66
- * direction. Writing beside the file loses data at the next `npm ci`. Writing to
67
- * `<cwd>/runs.jsonl` puts an append-only journal at the top of someone's repository without
68
- * asking. Writing nowhere makes `record()` a no-op, which is the stored "nothing was wrong" this
69
- * ledger exists to abolish. A throw is the only one of the four that cannot be mistaken for
70
- * having worked — and it carries the cure, naming the key and the file to put it in.
71
- *
72
- * ⚠️ THE EXAMPLES HERE ARE DELIBERATELY GENERIC (`docs/pipeline-runs.jsonl`). The first
73
- * consumer's own directory names are absent by the same rule `papers.harness.mjs` part IV
74
- * enforces for `papers.mjs`: a package that spells out one user's private tree has one user.
48
+ * ⚠️ THE EXAMPLES HERE ARE DELIBERATELY GENERIC. The first consumer's own directory names are
49
+ * absent by the same rule `papers.harness.mjs` part IV enforces for `papers.mjs`: a package that
50
+ * spells out one user's private tree has one user.
75
51
  */
76
52
  import {
77
53
  existsSync,
@@ -81,7 +57,7 @@ import {
81
57
  statSync,
82
58
  } from "node:fs";
83
59
  import { join, resolve, sep } from "node:path";
84
- import { fileURLToPath, pathToFileURL } from "node:url";
60
+ import { pathToFileURL } from "node:url";
85
61
 
86
62
  // Re-exported, not re-declared — see the note in `lib/paper-config.mjs`. This file is not a hook
87
63
  // and never needed its own copy.
@@ -122,20 +98,6 @@ export function consumerRoot({ env = process.env, cwd = process.cwd() } = {}) {
122
98
  return env.CLAUDE_PROJECT_DIR || cwd;
123
99
  }
124
100
 
125
- /**
126
- * The directory of venue TeX files THIS PACKAGE ships — `paper-guards.tex` and each venue's
127
- * `<venue>.tex` — which a paper's preamble `\input`s and `paperlint build` puts on `TEXINPUTS`.
128
- *
129
- * Resolved from this file's own location, never from the caller's cwd: the directory travels
130
- * with the package, wherever the package is installed (own checkout, `node_modules`, a plugin
131
- * cache). `import.meta.url` is the realpath, so a symlinked skills directory changes nothing.
132
- */
133
- export function packageVenuesDir() {
134
- return fileURLToPath(
135
- new URL("../../submit-paper/references/venues", import.meta.url),
136
- );
137
- }
138
-
139
101
  /**
140
102
  * The package's npm name — the directory it lands in under `node_modules`, and the name Node
141
103
  * resolves it by. Every path into the installed package is built from this, never re-spelled;
@@ -246,68 +208,22 @@ function readlinkOr(entry, readlink) {
246
208
  * True when `dir` lies inside a `node_modules` directory.
247
209
  *
248
210
  * Segment-wise, not `includes("node_modules")`: a repository legitimately named
249
- * `my-node_modules-inspector` is not an installed package, and a substring test would refuse to
250
- * write its ledger. Cheap to get right, and the failure it prevents is a throw in someone's face.
211
+ * `my-node_modules-inspector` is not an installed package, and a substring test would refuse it.
212
+ * Cheap to get right, and the failure it prevents is a throw in someone's face.
251
213
  */
252
214
  export function insideNodeModules(dir) {
253
215
  return dir.split(sep).includes("node_modules");
254
216
  }
255
217
 
256
- /**
257
- * Where the run ledger lives. Three rungs; see the docblock.
258
- *
259
- * @param hereDir the directory of the module that owns the ledger — pass `import.meta.dirname`.
260
- * @returns an absolute path. Never a path inside `node_modules`.
261
- */
262
- export function ledgerPath(
263
- hereDir,
264
- { env = process.env, cwd = process.cwd() } = {},
265
- ) {
266
- // Rung 1 — the environment. Deliberately first: harnesses redirect the ledger BEFORE importing
267
- // it, and a declaration on disk that could override that would put fixture rows in real history.
268
- if (env.PIPELINE_LEDGER) return resolve(env.PIPELINE_LEDGER);
269
-
270
- // Rung 2 — the consumer's declaration.
271
- const root = consumerRoot({ env, cwd });
272
- const declared = settingsOf(consumerRoot({ env, cwd }))?.ledger;
273
- // 🔴 `declared === undefined`, NOT `declared ?? default` — the same distinction `papersRoot()`
274
- // makes and for the same reason: `"ledger": null` is a keystroke, not an absence, and reading
275
- // it as "nothing was declared" would silently pick a different file than the one asked for.
276
- if (declared !== undefined) {
277
- if (typeof declared !== "string" || declared.length === 0)
278
- throw new TypeError(
279
- `${CONFIG_KEY}: "ledger" must be a non-empty string, got ${JSON.stringify(declared)}`,
280
- );
281
- return resolve(root, declared);
282
- }
283
-
284
- // Rung 3 — beside this file, but only in the package's own checkout.
285
- if (insideNodeModules(hereDir))
286
- throw new Error(
287
- `${CONFIG_KEY}: no ledger location is declared, and the default (a file beside this ` +
288
- `module) is inside node_modules, which \`npm ci\` deletes.\n` +
289
- `Declare where the ledger lives, in ${join(root, CONFIG_FILE)}:\n` +
290
- ` { "ledger": "docs/pipeline-runs.jsonl" }\n` +
291
- `or set PIPELINE_LEDGER for a single run.\n` +
292
- `This is thrown rather than defaulted on purpose: appending to a path under ` +
293
- `node_modules succeeds, so the rows would look recorded right up until the next ` +
294
- `install removed them, with no error at any point.`,
295
- );
296
- return join(hereDir, "runs.jsonl");
297
- }
298
-
299
218
  // ═════════════════════════════════════════════════════════════════════════════════════════════
300
- // THE FOURTH CARRIER — WHERE A SKILL'S PROSE NAMES THESE SCRIPTS
219
+ // WHERE A SKILL'S PROSE NAMES THESE SCRIPTS
301
220
  //
302
- // `papersRoot()` answers "where does the consumer keep its papers"; `ledgerPath()` answers
303
- // "where does the run journal live"; `citeChecks` (read in `run-mechanical.mjs`) answers "where
304
- // are the consumer's own citation checkers". This answers the fourth: BY WHAT PATH does a skill
305
- // instruct the model to run `announce.mjs` and `ledger.mjs`.
221
+ // `papersRoot()` answers "where does the consumer keep its papers". This answers: BY WHAT PATH
222
+ // does a skill instruct the model to run this package's scripts.
306
223
  //
307
- // 🔴 IT IS A DIFFERENT KIND OF PATH FROM THE OTHER THREE, and the difference decides the API.
308
- // The other three are read by CODE and may be absolute. This one is compared against text a
309
- // human wrote inside a SKILL.md — `node .claude/skills/paper-pipeline/scripts/ledger.mjs record
310
- // …` — so it must stay relative to the consumer root and spelled with `/`, exactly as the prose
224
+ // 🔴 IT IS A DIFFERENT KIND OF PATH, and the difference decides the API. `papersRoot()` is read by
225
+ // CODE and may be absolute. This one is compared against text a human wrote inside a SKILL.md —
226
+ // `node .claude/skills/paper-pipeline/scripts/<script>.mjs …` — so it must stay relative to the consumer root and spelled with `/`, exactly as the prose
311
227
  // spells it. Returning an absolute path here would make every `startsWith` test false and every
312
228
  // check that depends on it pass over an empty set.
313
229
  //
@@ -346,7 +262,7 @@ export function scriptsRoot({ env = process.env, cwd = process.cwd() } = {}) {
346
262
  const root = consumerRoot({ env, cwd });
347
263
  const declared = settingsOf(consumerRoot({ env, cwd }))?.scripts;
348
264
  // 🔴 `declared === undefined`, NOT `declared ?? DEFAULT` — the same distinction `papersRoot()`
349
- // and `ledgerPath()` make, for the same reason: `"scripts": null` is a keystroke, not an
265
+ // makes, for the same reason: `"scripts": null` is a keystroke, not an
350
266
  // absence, and silently substituting the default for it hides a typo behind a working run.
351
267
  const rel = declared === undefined ? DEFAULT_SCRIPTS_ROOT : declared;
352
268
  if (typeof rel !== "string" || rel.length === 0)
@@ -385,12 +301,11 @@ export function scriptsRoot({ env = process.env, cwd = process.cwd() } = {}) {
385
301
  }
386
302
 
387
303
  // ═════════════════════════════════════════════════════════════════════════════════════════════
388
- // THE SIXTH CARRIER — WHICH WALL CLOCK THE CONSUMER SCHEDULES AGAINST
304
+ // WHICH WALL CLOCK THE CONSUMER SCHEDULES AGAINST
389
305
  //
390
- // `papersRoot()` answers "where are the papers", `ledgerPath()` "where is the journal",
391
- // `citeChecks` "where are the consumer's citation checkers", `scriptsRoot()` "by what path does
392
- // prose name these scripts", `triggerCases` "where are the consumer's own trigger cases". This
393
- // answers the sixth: IN WHAT TIME ZONE does a deadline anchor get written.
306
+ // `papersRoot()` answers "where are the papers", `scriptsRoot()` "by what path does prose name
307
+ // these scripts", `triggerCases` "where are the consumer's own trigger cases". This answers: IN
308
+ // WHAT TIME ZONE does a deadline anchor get written.
394
309
  //
395
310
  // 🔴 WHY IT IS A DECLARATION AND NOT A CONSTANT. A deadline is the one quantity in this pipeline
396
311
  // that is meaningless without a zone, and the zone belongs to the PERSON, not to the pipeline —
@@ -446,7 +361,7 @@ export function consumerTimezone({
446
361
  }
447
362
 
448
363
  // ═════════════════════════════════════════════════════════════════════════════════════════════
449
- // THE SEVENTH CARRIER — WHOM THE SCHOLARLY APIs SHOULD CONTACT
364
+ // WHOM THE SCHOLARLY APIs SHOULD CONTACT
450
365
  //
451
366
  // Crossref and friends run a "polite pool": send a `mailto:` in the User-Agent and you get the
452
367
  // faster, more reliable tier, plus a warning by email instead of a silent block if your usage
@@ -485,13 +400,7 @@ export function consumerContactEmail({
485
400
  return declared;
486
401
  }
487
402
 
488
- /**
489
- * The individual script paths, derived from one root.
490
- *
491
- * 🔴 THE NAMES LIVE HERE, NOT IN THE CONSUMER — the same half that keeps `paperFiles()` honest.
492
- * `announce.mjs` and `ledger.mjs` are this package's contract; a consumer that spelled them out
493
- * itself would keep its copy of the list in step by hand, and a hand-kept list rots silently.
494
- */
403
+ /** The prefix every prose path to this package's scripts starts with, derived from one root. */
495
404
  export function pipelineScripts(root) {
496
405
  // 🔴 ONE TRAILING SLASH, NOT THE ONE THAT WAS TYPED. `scriptsRoot()` returns the declaration
497
406
  // verbatim — it must, the value is compared against prose — so `"scripts": "tools/pipeline/"`
@@ -504,9 +413,5 @@ export function pipelineScripts(root) {
504
413
  return {
505
414
  /** What every prose path under this root starts with. Trailing slash, for `startsWith`. */
506
415
  prefix: `${base}/`,
507
- /** Announces that a gate has started. */
508
- announce: `${base}/announce.mjs`,
509
- /** Appends the verdict row. */
510
- ledger: `${base}/ledger.mjs`,
511
416
  };
512
417
  }
@@ -317,9 +317,8 @@ function fixture({ scripts = {}, allow = null, bundle = {}, at = "repro" }) {
317
317
  }
318
318
 
319
319
  // ── 11. the ledger prints on a CLEAN run, and the header states the count ───────────────────────
320
- // The count matters beyond tidiness: `run-mechanical.mjs` reads this check in `flags` mode, where
321
- // it prefers a stated count and otherwise COUNTS OUTPUT LINES. The ledger is ~20 lines that are not
322
- // findings, so without the stated count a clean paper would be recorded as twenty findings.
320
+ // The count matters beyond tidiness: the ledger is ~20 lines that are not findings, so a caller
321
+ // counting output lines would read a clean paper as twenty findings.
323
322
  {
324
323
  const r = run(
325
324
  fixture({
@@ -11,7 +11,7 @@
11
11
  * The findings themselves are planted in `population-map.test.mjs` (vitest; until #52 a
12
12
  * self-test script this harness spawned). This file keeps what only the command shows: finding
13
13
  * paper.md and repro/populations.tsv, and `--flags-only` staying silent on a clean paper — the
14
- * wiring the CI step and `run-mechanical.mjs` actually invoke.
14
+ * wiring the CI step actually invokes.
15
15
  */
16
16
  import assert from "node:assert/strict";
17
17
  import { execFileSync } from "node:child_process";
@@ -107,8 +107,8 @@ const REG = [
107
107
  }
108
108
 
109
109
  // ── 4. KNOWN GAP, asserted so it stays visible ─────────────────────────────────────────
110
- // 🔴 A paper with NO repro/populations.tsv exits 0 in silence, and `run-mechanical.mjs` records
111
- // that silence as PASS for draft-paper. So a paper that never declared its populations scores
110
+ // 🔴 A paper with NO repro/populations.tsv exits 0 in silence, which reads as a pass for
111
+ // draft-paper. So a paper that never declared its populations scores
112
112
  // identically to one that declared them and tied every one. Absence is invisible again — the same
113
113
  // shape artifact-coverage.mjs was written to close for the released bundle.
114
114
  //
@@ -4,21 +4,10 @@ description: Answer "what's the status of the paper?" in one pass — MEASURE wh
4
4
  allowed-tools: [Bash, Read, Grep, Glob]
5
5
  ---
6
6
 
7
- <!-- vigiles:sha256:51bc50b2485782e4 compiled from skills/paper-status/SKILL.md.spec.ts -->
7
+ <!-- vigiles:sha256:cdf5e1fcefdb916d compiled from skills/paper-status/SKILL.md.spec.ts -->
8
8
 
9
9
  # paper-status — state, measured before it is narrated
10
10
 
11
- ## Run me
12
-
13
- 🔴 FIRST, before any other step:
14
-
15
- ```
16
- node .claude/skills/paper-pipeline/scripts/announce.mjs paper-status <paper-dir>
17
- ```
18
-
19
- An advisory pass cannot be observed failing — silence is both its error state and its normal
20
- state — so starting is an event, and events get written down.
21
-
22
11
  The author asks "what's the status there?" constantly, and the answer keeps coming from the wrong place: my
23
12
  memory of the session, which drifts, or a 200-line scorecard he cannot skim. Both fail the same way —
24
13
  **they narrate state instead of measuring it.**
@@ -49,18 +38,13 @@ So on any turn that touched a paper, the reply ends with **three lines, unprompt
49
38
  Three failures make this a rule rather than a preference, all in one session:
50
39
  - I finished a page-fit cut and stopped, so he asked what the status was;
51
40
  - I left artifact hosting on his side of the split for a day and a half while the credential to clear
52
- it sat in the environment (see `credential-available` in `pipeline-check.mjs`);
41
+ it sat in the environment;
53
42
  - three consecutive panels returned Weak Accept and the ceiling they named was never the next task,
54
43
  because nothing turned "here is the score" into "here is what to do about it".
55
44
 
56
45
  **Never end a paper turn on a completed sub-task alone.** "Fits in 8 pages" is a measurement, not a
57
46
  status; the status is what that measurement makes possible next.
58
47
 
59
- **Enforcement leg.** `pipeline-check.mjs` prints its banner **unconditionally**, including when it has
60
- zero findings — deadline countdown, verdict row, and `➡️ NEXT`. It runs from `paper-status-gates.sh`
61
- on every edit to a paper source, so the state arrives without anyone pulling it. If that banner and
62
- this section ever disagree, the banner is a measurement and this is prose: believe the banner.
63
-
64
48
  ## How to run it
65
49
 
66
50
  ### 1. Measure (no reading yet)
@@ -90,8 +74,6 @@ gate's verdict **does not describe the current paper** — say so explicitly, an
90
74
  An accept-probability from a superseded draft is not evidence about this one; carrying it forward is
91
75
  the single most common way a status report lies.
92
76
 
93
- If the project ships a checker (`.claude/skills/paper-pipeline/scripts/pipeline-check.mjs`), run it.
94
-
95
77
  ### 3. Read the judgement rows — from BOTH checklists, they answer different questions
96
78
 
97
79
  The project already carries the checklists this skill should report against. Do not invent a new list
@@ -126,28 +108,6 @@ Short. He is asking because he wants to decide what to do next, not to read a re
126
108
  5. **What moved since he last asked**, three lines maximum.
127
109
  6. **The deadline, in his timezone**, and what is on the critical path to it.
128
110
 
129
- ## Record the verdict
130
-
131
- 🔴 LAST step, once the deliverable exists:
132
-
133
- ```
134
- node .claude/skills/paper-pipeline/scripts/ledger.mjs record paper-status <paper-dir> FINDING <count> <report-path>
135
- node .claude/skills/paper-pipeline/scripts/ledger.mjs record paper-status <paper-dir> ABSTAINED <reason> "<one line>"
136
- ```
137
-
138
- This skill reports state and does not judge the paper, so what it records is about the **status run**:
139
- **FINDING** — `<count>` blockers printed, `<report-path>` the status output.
140
- **ABSTAINED** — `no-witness`: the status run measured everything it can and printed zero blockers.
141
- `crashed`: the build did not run, so nothing was measured.
142
-
143
- 🔴 `crashed` is the one outcome that must never be narrated as a status. This skill exists because
144
- state was being narrated instead of measured; a status report built on a build that did not happen
145
- is that same failure with a fresh timestamp on it. Under the old vocabulary this was `ERROR`, which
146
- sat in the same column as a verdict about the paper — it does not any more.
147
-
148
- 🔴 **There is no PASS**, so "zero blockers" cannot be stored. Print it, and let the reader draw the
149
- conclusion from an empty blocker list they can see.
150
-
151
111
  ## Rules
152
112
 
153
113
  - **Never report a gate as run because I intended to run it.** Predicting a background agent's result,
@@ -14,17 +14,6 @@ export default experimental_skill({
14
14
  body: `
15
15
  # paper-status — state, measured before it is narrated
16
16
 
17
- ## Run me
18
-
19
- 🔴 FIRST, before any other step:
20
-
21
- \`\`\`
22
- node .claude/skills/paper-pipeline/scripts/announce.mjs paper-status <paper-dir>
23
- \`\`\`
24
-
25
- An advisory pass cannot be observed failing — silence is both its error state and its normal
26
- state — so starting is an event, and events get written down.
27
-
28
17
  The author asks "what's the status there?" constantly, and the answer keeps coming from the wrong place: my
29
18
  memory of the session, which drifts, or a 200-line scorecard he cannot skim. Both fail the same way —
30
19
  **they narrate state instead of measuring it.**
@@ -55,18 +44,13 @@ So on any turn that touched a paper, the reply ends with **three lines, unprompt
55
44
  Three failures make this a rule rather than a preference, all in one session:
56
45
  - I finished a page-fit cut and stopped, so he asked what the status was;
57
46
  - I left artifact hosting on his side of the split for a day and a half while the credential to clear
58
- it sat in the environment (see \`credential-available\` in \`pipeline-check.mjs\`);
47
+ it sat in the environment;
59
48
  - three consecutive panels returned Weak Accept and the ceiling they named was never the next task,
60
49
  because nothing turned "here is the score" into "here is what to do about it".
61
50
 
62
51
  **Never end a paper turn on a completed sub-task alone.** "Fits in 8 pages" is a measurement, not a
63
52
  status; the status is what that measurement makes possible next.
64
53
 
65
- **Enforcement leg.** \`pipeline-check.mjs\` prints its banner **unconditionally**, including when it has
66
- zero findings — deadline countdown, verdict row, and \`➡️ NEXT\`. It runs from \`paper-status-gates.sh\`
67
- on every edit to a paper source, so the state arrives without anyone pulling it. If that banner and
68
- this section ever disagree, the banner is a measurement and this is prose: believe the banner.
69
-
70
54
  ## How to run it
71
55
 
72
56
  ### 1. Measure (no reading yet)
@@ -96,8 +80,6 @@ gate's verdict **does not describe the current paper** — say so explicitly, an
96
80
  An accept-probability from a superseded draft is not evidence about this one; carrying it forward is
97
81
  the single most common way a status report lies.
98
82
 
99
- If the project ships a checker (\`.claude/skills/paper-pipeline/scripts/pipeline-check.mjs\`), run it.
100
-
101
83
  ### 3. Read the judgement rows — from BOTH checklists, they answer different questions
102
84
 
103
85
  The project already carries the checklists this skill should report against. Do not invent a new list
@@ -132,28 +114,6 @@ Short. He is asking because he wants to decide what to do next, not to read a re
132
114
  5. **What moved since he last asked**, three lines maximum.
133
115
  6. **The deadline, in his timezone**, and what is on the critical path to it.
134
116
 
135
- ## Record the verdict
136
-
137
- 🔴 LAST step, once the deliverable exists:
138
-
139
- \`\`\`
140
- node .claude/skills/paper-pipeline/scripts/ledger.mjs record paper-status <paper-dir> FINDING <count> <report-path>
141
- node .claude/skills/paper-pipeline/scripts/ledger.mjs record paper-status <paper-dir> ABSTAINED <reason> "<one line>"
142
- \`\`\`
143
-
144
- This skill reports state and does not judge the paper, so what it records is about the **status run**:
145
- **FINDING** — \`<count>\` blockers printed, \`<report-path>\` the status output.
146
- **ABSTAINED** — \`no-witness\`: the status run measured everything it can and printed zero blockers.
147
- \`crashed\`: the build did not run, so nothing was measured.
148
-
149
- 🔴 \`crashed\` is the one outcome that must never be narrated as a status. This skill exists because
150
- state was being narrated instead of measured; a status report built on a build that did not happen
151
- is that same failure with a fresh timestamp on it. Under the old vocabulary this was \`ERROR\`, which
152
- sat in the same column as a verdict about the paper — it does not any more.
153
-
154
- 🔴 **There is no PASS**, so "zero blockers" cannot be stored. Print it, and let the reader draw the
155
- conclusion from an empty blocker list they can see.
156
-
157
117
  ## Rules
158
118
 
159
119
  - **Never report a gate as run because I intended to run it.** Predicting a background agent's result,