paperlint 3.0.0 → 3.1.1

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 (319) hide show
  1. package/.github/workflows/ci.yml +28 -41
  2. package/.github/workflows/dependabot-automerge.yml +1 -1
  3. package/CLAUDE.md +51 -56
  4. package/CONTRIBUTING.md +30 -18
  5. package/README.md +179 -180
  6. package/bin/paperlint.test.mjs +45 -0
  7. package/dist/adapters/node/files.io.d.ts +2 -0
  8. package/dist/adapters/node/files.io.d.ts.map +1 -1
  9. package/dist/adapters/node/files.io.js +3 -2
  10. package/dist/adapters/node/files.io.js.map +1 -1
  11. package/dist/adapters/pdfjs/fill.d.ts +15 -4
  12. package/dist/adapters/pdfjs/fill.d.ts.map +1 -1
  13. package/dist/adapters/pdfjs/fill.js +31 -21
  14. package/dist/adapters/pdfjs/fill.js.map +1 -1
  15. package/dist/adapters/references/index.d.ts.map +1 -1
  16. package/dist/adapters/references/index.js +14 -5
  17. package/dist/adapters/references/index.js.map +1 -1
  18. package/dist/adapters/references/pool.d.ts +8 -0
  19. package/dist/adapters/references/pool.d.ts.map +1 -0
  20. package/dist/adapters/references/pool.js +19 -0
  21. package/dist/adapters/references/pool.js.map +1 -0
  22. package/dist/build-engine.d.ts.map +1 -1
  23. package/dist/build-engine.js +6 -2
  24. package/dist/build-engine.js.map +1 -1
  25. package/dist/build.d.ts +10 -2
  26. package/dist/build.d.ts.map +1 -1
  27. package/dist/build.js +29 -11
  28. package/dist/build.js.map +1 -1
  29. package/dist/cli.d.ts +93 -6
  30. package/dist/cli.d.ts.map +1 -1
  31. package/dist/cli.js +296 -80
  32. package/dist/cli.js.map +1 -1
  33. package/dist/doctor.d.ts +27 -1
  34. package/dist/doctor.d.ts.map +1 -1
  35. package/dist/doctor.js +4 -3
  36. package/dist/doctor.js.map +1 -1
  37. package/dist/domain/result.d.ts +0 -2
  38. package/dist/domain/result.d.ts.map +1 -1
  39. package/dist/domain/result.js +0 -4
  40. package/dist/domain/result.js.map +1 -1
  41. package/dist/engine.d.ts +9 -0
  42. package/dist/engine.d.ts.map +1 -1
  43. package/dist/engine.js +27 -0
  44. package/dist/engine.js.map +1 -1
  45. package/dist/hooks-settings.d.ts +8 -5
  46. package/dist/hooks-settings.d.ts.map +1 -1
  47. package/dist/hooks-settings.js +32 -32
  48. package/dist/hooks-settings.js.map +1 -1
  49. package/dist/init.d.ts +8 -0
  50. package/dist/init.d.ts.map +1 -1
  51. package/dist/init.js +4 -4
  52. package/dist/init.js.map +1 -1
  53. package/dist/latex-log.d.ts +0 -2
  54. package/dist/latex-log.d.ts.map +1 -1
  55. package/dist/latex-log.js +7 -13
  56. package/dist/latex-log.js.map +1 -1
  57. package/dist/link-skills.d.ts +15 -2
  58. package/dist/link-skills.d.ts.map +1 -1
  59. package/dist/link-skills.js +36 -20
  60. package/dist/link-skills.js.map +1 -1
  61. package/dist/new-paper.d.ts +8 -1
  62. package/dist/new-paper.d.ts.map +1 -1
  63. package/dist/new-paper.js +50 -15
  64. package/dist/new-paper.js.map +1 -1
  65. package/dist/paper-files.d.ts +30 -8
  66. package/dist/paper-files.d.ts.map +1 -1
  67. package/dist/paper-files.js +59 -30
  68. package/dist/paper-files.js.map +1 -1
  69. package/dist/pdf-facts.d.ts +11 -0
  70. package/dist/pdf-facts.d.ts.map +1 -1
  71. package/dist/pdf-facts.js +16 -18
  72. package/dist/pdf-facts.js.map +1 -1
  73. package/dist/pdf-geometry.js +1 -1
  74. package/dist/pdf-geometry.js.map +1 -1
  75. package/dist/presets.d.ts +2 -0
  76. package/dist/presets.d.ts.map +1 -1
  77. package/dist/presets.js +3 -1
  78. package/dist/presets.js.map +1 -1
  79. package/dist/references.js +2 -2
  80. package/dist/references.js.map +1 -1
  81. package/dist/structure.js +1 -1
  82. package/dist/structure.js.map +1 -1
  83. package/dist/tex-requirements.d.ts +8 -0
  84. package/dist/tex-requirements.d.ts.map +1 -1
  85. package/dist/tex-requirements.js +6 -5
  86. package/dist/tex-requirements.js.map +1 -1
  87. package/dist/toolchain.d.ts +8 -3
  88. package/dist/toolchain.d.ts.map +1 -1
  89. package/dist/toolchain.js +26 -10
  90. package/dist/toolchain.js.map +1 -1
  91. package/dist/types.d.ts +6 -0
  92. package/dist/types.d.ts.map +1 -1
  93. package/dist/venue-rules.d.ts.map +1 -1
  94. package/dist/venue-rules.js +12 -10
  95. package/dist/venue-rules.js.map +1 -1
  96. package/docs/configuration.md +48 -4
  97. package/docs/prior-art/README.md +1 -1
  98. package/docs/prior-art/test-tooling.md +7 -30
  99. package/docs/rules.md +29 -25
  100. package/docs/skills.md +80 -0
  101. package/eslint-rules/install-path-literals.harness.mjs +17 -1
  102. package/eslint-rules/latex-language.harness.mjs +3 -3
  103. package/eslint-rules/latex-language.mjs +12 -9
  104. package/eslint-rules/latex-language.test.mjs +200 -0
  105. package/eslint-rules/paper-research-question.harness.mjs +73 -7
  106. package/eslint-rules/paper-stages.harness.mjs +9 -14
  107. package/eslint-rules/paper-stages.test.mjs +135 -0
  108. package/eslint-rules/paper-typography.mjs +56 -9
  109. package/eslint-rules/paper-typography.test.mjs +186 -1
  110. package/eslint-rules/pdf-last-page-balance.harness.mjs +63 -7
  111. package/eslint-rules/review-frontmatter.mjs +2 -1
  112. package/eslint-rules/review-frontmatter.schema.json +1 -1
  113. package/eslint-rules/review-frontmatter.test.mjs +37 -1
  114. package/eslint-rules/temp-root-realpath.harness.mjs +49 -0
  115. package/eslint-rules/tex-build.mjs +13 -13
  116. package/eslint.config.mjs +6 -2
  117. package/fixtures/toolchain-mirror/catalog.txt +1 -0
  118. package/fixtures/toolchain-mirror/install-tl +4 -1
  119. package/fixtures/toolchain-mirror/stub-fmtutil-sys +4 -0
  120. package/fixtures/toolchain-mirror/stub-tlmgr +15 -0
  121. package/hooks/hooks.harness.mjs +6 -11
  122. package/hooks/paper-edit-guard.hook.mjs +2 -2
  123. package/hooks/paper-edit-guard.test.mjs +61 -0
  124. package/hooks/paper-skills-nudge.hook.mjs +2 -2
  125. package/hooks/paper-skills-nudge.test.mjs +38 -0
  126. package/hooks/paper-status-gates.test.mjs +42 -0
  127. package/lib/agent-cli-version.harness.mjs +7 -0
  128. package/lib/check.d.mts +17 -0
  129. package/lib/check.mjs +50 -0
  130. package/lib/check.test.ts +67 -0
  131. package/lib/markdown.mjs +16 -69
  132. package/lib/markdown.test.mjs +246 -0
  133. package/lib/paper-config.harness.mjs +28 -9
  134. package/lib/paper-config.mjs +7 -2
  135. package/lib/skill-checks.mjs +18 -46
  136. package/lib/skill-checks.test.mjs +297 -0
  137. package/lib/skill-corpus.mjs +11 -13
  138. package/lib/skill-corpus.test.mjs +54 -0
  139. package/lib/skill-trigger-cases.harness.mjs +57 -4
  140. package/package.json +6 -5
  141. package/scripts/check.harness.mjs +4 -10
  142. package/scripts/check.mjs +45 -42
  143. package/scripts/check.test.mjs +104 -0
  144. package/scripts/coverage-config.test.ts +406 -0
  145. package/scripts/eslint-report-guard.test.mjs +48 -0
  146. package/scripts/harness-api.frozen.json +0 -2
  147. package/scripts/layer-legacy-frozen.d.mts +9 -1
  148. package/scripts/layer-legacy-frozen.mjs +28 -11
  149. package/scripts/layer-legacy-frozen.test.ts +143 -1
  150. package/scripts/rules-are-content-only.harness.mjs +3 -11
  151. package/scripts/rules-are-content-only.mjs +29 -19
  152. package/scripts/rules-are-content-only.test.mjs +61 -0
  153. package/scripts/rules-see-files.mjs +11 -9
  154. package/scripts/rules-see-files.test.mjs +71 -0
  155. package/scripts/test.mjs +44 -0
  156. package/scripts/test.test.mjs +52 -0
  157. package/skills/grade-paper-writing/__snapshots__/prose-lint.test.mjs.snap +57 -0
  158. package/skills/grade-paper-writing/prose-lint.mjs +31 -12
  159. package/skills/grade-paper-writing/prose-lint.test.mjs +153 -0
  160. package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +3 -3
  161. package/skills/paper-pipeline/scripts/README.md +13 -132
  162. package/skills/paper-pipeline/scripts/announce.test.mjs +102 -0
  163. package/skills/paper-pipeline/scripts/artifact-coverage.test.mjs +86 -0
  164. package/skills/paper-pipeline/scripts/consumer.d.mts +8 -1
  165. package/skills/paper-pipeline/scripts/consumer.harness.mjs +3 -3
  166. package/skills/paper-pipeline/scripts/consumer.mjs +13 -25
  167. package/skills/paper-pipeline/scripts/consumer.test.mjs +113 -0
  168. package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +9 -4
  169. package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +380 -0
  170. package/skills/paper-pipeline/scripts/fixtures/scorecard.mjs +71 -0
  171. package/skills/paper-pipeline/scripts/generated-code.harness.mjs +4 -4
  172. package/skills/paper-pipeline/scripts/generated-code.mjs +3 -2
  173. package/skills/paper-pipeline/scripts/generated-code.test.mjs +62 -0
  174. package/skills/paper-pipeline/scripts/ledger.cli.test.mjs +302 -0
  175. package/skills/paper-pipeline/scripts/ledger.mjs +1 -1
  176. package/skills/paper-pipeline/scripts/{ledger.selftest.mjs → ledger.test.mjs} +44 -76
  177. package/skills/paper-pipeline/scripts/pipeline-check.cli.test.mjs +474 -0
  178. package/skills/paper-pipeline/scripts/pipeline-check.harness.mjs +2 -63
  179. package/skills/paper-pipeline/scripts/pipeline-check.mjs +37 -21
  180. package/skills/paper-pipeline/scripts/pipeline-edges.mjs +2 -2
  181. package/skills/paper-pipeline/scripts/pipeline-edges.test.mjs +73 -0
  182. package/skills/paper-pipeline/scripts/population-map.harness.mjs +4 -47
  183. package/skills/paper-pipeline/scripts/population-map.mjs +12 -2
  184. package/skills/paper-pipeline/scripts/population-map.test.mjs +154 -0
  185. package/skills/paper-pipeline/scripts/provenance.harness.mjs +26 -0
  186. package/skills/paper-pipeline/scripts/round-diff.harness.mjs +1 -2
  187. package/skills/paper-pipeline/scripts/round-diff.mjs +13 -6
  188. package/skills/paper-pipeline/scripts/round-diff.test.mjs +159 -0
  189. package/skills/paper-pipeline/scripts/run-mechanical.mjs +30 -6
  190. package/skills/paper-pipeline/scripts/run-mechanical.test.mjs +468 -0
  191. package/skills/paper-pipeline/scripts/status.mjs +2 -2
  192. package/skills/paper-pipeline/scripts/status.test.mjs +232 -0
  193. package/skills/plan-paper-timeline/plan-paper-timeline.effects.harness.mjs +2 -2
  194. package/skills/render-paper/extract-pdf-facts.harness.mjs +56 -7
  195. package/skills/tighten-paper/__snapshots__/structure.test.mjs.snap +133 -0
  196. package/skills/tighten-paper/structure.test.mjs +116 -0
  197. package/skills/verify-citations/SKILL.md +5 -5
  198. package/skills/verify-citations/SKILL.md.spec.ts +4 -4
  199. package/skills/verify-citations/scripts/__snapshots__/verify-cites.net.test.mjs.snap +235 -0
  200. package/skills/verify-citations/scripts/bib-authors.check.test.mjs +274 -0
  201. package/skills/verify-citations/scripts/bib-authors.cli.test.mjs +178 -0
  202. package/skills/verify-citations/scripts/bib-authors.mjs +2 -1
  203. package/skills/verify-citations/scripts/verify-cites.cli.test.mjs +160 -0
  204. package/skills/verify-citations/scripts/verify-cites.mjs +96 -96
  205. package/skills/verify-citations/scripts/verify-cites.net.test.mjs +287 -0
  206. package/skills/verify-citations/scripts/verify-cites.test.mjs +149 -5
  207. package/src/adapters/banal/failure.test.ts +70 -1
  208. package/src/adapters/banal/index.test.ts +16 -0
  209. package/src/adapters/banal/output.test.ts +5 -0
  210. package/src/adapters/curl/download.test.ts +43 -1
  211. package/src/adapters/memory/index.test.ts +25 -0
  212. package/src/adapters/node/files.io.ts +3 -2
  213. package/src/adapters/node/files.test.ts +13 -1
  214. package/src/adapters/node/process.test.ts +38 -0
  215. package/src/adapters/pdfjs/fill.test.ts +34 -0
  216. package/src/adapters/pdfjs/fill.ts +44 -25
  217. package/src/adapters/references/index.test.ts +228 -0
  218. package/src/adapters/references/index.ts +21 -6
  219. package/src/adapters/references/pool.test.ts +31 -0
  220. package/src/adapters/references/pool.ts +24 -0
  221. package/src/build-engine.harness.mjs +3 -7
  222. package/src/build-engine.test.ts +137 -0
  223. package/src/build-engine.ts +6 -1
  224. package/src/build.harness.mjs +51 -11
  225. package/src/build.test.ts +362 -0
  226. package/src/build.ts +40 -14
  227. package/src/cli.harness.mjs +12 -19
  228. package/src/cli.test.ts +584 -0
  229. package/src/cli.ts +409 -94
  230. package/src/doctor.harness.mjs +3 -8
  231. package/src/doctor.test.ts +64 -0
  232. package/src/doctor.ts +12 -4
  233. package/src/domain/result.test.ts +4 -0
  234. package/src/domain/result.ts +0 -5
  235. package/src/engine.harness.mjs +77 -7
  236. package/src/engine.ts +35 -0
  237. package/src/facts-file.test.ts +110 -0
  238. package/src/hooks-settings.harness.mjs +43 -13
  239. package/src/hooks-settings.ts +41 -35
  240. package/src/init.test.ts +308 -5
  241. package/src/init.ts +4 -4
  242. package/src/latex-log.harness.mjs +32 -7
  243. package/src/latex-log.ts +6 -13
  244. package/src/latex-loop.harness.mjs +8 -7
  245. package/src/link-skills.harness.mjs +3 -8
  246. package/src/link-skills.test.ts +105 -0
  247. package/src/link-skills.ts +75 -21
  248. package/src/linted-set.test.ts +118 -0
  249. package/src/new-paper.harness.mjs +5 -7
  250. package/src/new-paper.test.ts +203 -2
  251. package/src/new-paper.ts +84 -17
  252. package/src/paper-files.test.ts +122 -5
  253. package/src/paper-files.ts +99 -39
  254. package/src/paper-settings-commands.test.ts +41 -5
  255. package/src/paper-settings.test.ts +20 -0
  256. package/src/parse-args.test.ts +48 -0
  257. package/src/pdf-facts.harness.mjs +3 -6
  258. package/src/pdf-facts.test.ts +144 -0
  259. package/src/pdf-facts.ts +17 -19
  260. package/src/pdf-geometry.harness.mjs +3 -7
  261. package/src/pdf-geometry.ts +1 -1
  262. package/src/presets.test.ts +2 -0
  263. package/src/presets.ts +7 -1
  264. package/src/references.test.ts +97 -0
  265. package/src/references.ts +2 -2
  266. package/src/rules-config.harness.mjs +10 -8
  267. package/src/rules-off.test.ts +111 -0
  268. package/src/structure.harness.mjs +5 -9
  269. package/src/structure.ts +1 -1
  270. package/src/tex-requirements.harness.mjs +34 -7
  271. package/src/tex-requirements.ts +12 -6
  272. package/src/toolchain.harness.mjs +44 -13
  273. package/src/toolchain.test.ts +280 -0
  274. package/src/toolchain.ts +37 -12
  275. package/src/types.ts +6 -0
  276. package/src/venue-rules.test.ts +63 -0
  277. package/src/venue-rules.ts +14 -12
  278. package/templates/paper/paperlint.json +1 -1
  279. package/action.mutations.mjs +0 -162
  280. package/eslint-rules/install-path-literals.mutations.mjs +0 -62
  281. package/eslint-rules/latex-language.mutations.mjs +0 -196
  282. package/eslint-rules/paper-research-question.mutations.mjs +0 -127
  283. package/eslint-rules/paper-stages.mutations.mjs +0 -81
  284. package/eslint-rules/papers.mutations.mjs +0 -186
  285. package/eslint-rules/temp-root-realpath.mutations.mjs +0 -99
  286. package/eslint-rules/tex-build.mutations.mjs +0 -258
  287. package/hooks/hooks.mutations.mjs +0 -337
  288. package/lib/agent-cli-version.mutations.mjs +0 -109
  289. package/lib/mutation-driver.harness.mjs +0 -227
  290. package/lib/mutation-driver.mjs +0 -397
  291. package/lib/mutation-driver.mutations.mjs +0 -68
  292. package/lib/paper-config.mutations.mjs +0 -143
  293. package/lib/skill-trigger-cases.mutations.mjs +0 -65
  294. package/scripts/check.mutations.mjs +0 -110
  295. package/scripts/exclusive.mjs +0 -138
  296. package/scripts/mutation-batteries-frozen.harness.mjs +0 -204
  297. package/scripts/mutation-batteries-frozen.mjs +0 -238
  298. package/scripts/mutation-batteries.frozen.json +0 -108
  299. package/scripts/rules-are-content-only.mutations.mjs +0 -81
  300. package/scripts/rules-see-files.mutations.mjs +0 -131
  301. package/scripts/run-mutations.mjs +0 -100
  302. package/skills/paper-pipeline/scripts/artifact-coverage.mutations.mjs +0 -218
  303. package/skills/paper-pipeline/scripts/consumer.mutations.mjs +0 -190
  304. package/skills/paper-pipeline/scripts/extract-ref-facts.mutations.mjs +0 -54
  305. package/skills/paper-pipeline/scripts/generated-code.mutations.mjs +0 -254
  306. package/skills/paper-pipeline/scripts/pipeline-check.mutations.mjs +0 -54
  307. package/skills/paper-pipeline/scripts/population-map.mutations.mjs +0 -65
  308. package/skills/paper-pipeline/scripts/population-map.selftest.mjs +0 -122
  309. package/skills/paper-pipeline/scripts/provenance.mutations.mjs +0 -59
  310. package/skills/paper-pipeline/scripts/round-diff.mutations.mjs +0 -276
  311. package/skills/plan-paper-timeline/plan-paper-timeline.effects.mutations.mjs +0 -65
  312. package/skills/skill-contract.mutations.mjs +0 -179
  313. package/src/build.mutations.mjs +0 -196
  314. package/src/cli.mutations.mjs +0 -414
  315. package/src/doctor.mutations.mjs +0 -175
  316. package/src/hooks-settings.mutations.mjs +0 -99
  317. package/src/link-skills.mutations.mjs +0 -140
  318. package/src/new-paper.mutations.mjs +0 -79
  319. package/src/structure.mutations.mjs +0 -83
package/src/build.ts CHANGED
@@ -187,11 +187,16 @@ function argText(arg: LatexArgument | undefined): string {
187
187
  .join("");
188
188
  }
189
189
 
190
- /** A unified-latex AST, or null when the text does not parse. */
191
- function parseTex(tex: string): LatexRoot | null {
190
+ /** A unified-latex AST, or null when the text does not parse. `parse` is unified-latex's parser. */
191
+ function parseTex(
192
+ tex: string,
193
+ parse: (tex: string) => LatexRoot = (t) => getParser().parse(t),
194
+ ): LatexRoot | null {
192
195
  try {
193
- return getParser().parse(tex);
196
+ return parse(tex);
194
197
  } catch {
198
+ // The parser recurses per group: ~20 000 nested `{` exhaust the stack (RangeError). Such a
199
+ // file has no class to report, and must not fail the build at `facts` for it.
195
200
  return null;
196
201
  }
197
202
  }
@@ -200,8 +205,11 @@ function parseTex(tex: string): LatexRoot | null {
200
205
  * `\documentclass[opts]{name}`, read by the unified-latex parser the lint rules already use —
201
206
  * so a `\documentclass` inside a comment is a comment, not a class.
202
207
  */
203
- export function parseDocumentclass(tex: string): PaperFacts["documentclass"] {
204
- return documentclassOf(parseTex(tex));
208
+ export function parseDocumentclass(
209
+ tex: string,
210
+ { parse }: { parse?: (tex: string) => LatexRoot } = {},
211
+ ): PaperFacts["documentclass"] {
212
+ return documentclassOf(parseTex(tex, parse));
205
213
  }
206
214
 
207
215
  function documentclassOf(ast: LatexRoot | null): PaperFacts["documentclass"] {
@@ -354,9 +362,8 @@ function latexPass(
354
362
  const r = ctx.run("pdflatex", pdflatexArgs(final), opts);
355
363
  if (r.error) return null;
356
364
  const exitCode = r.status ?? 1;
357
- const log = unwrapLog(
358
- readOr(join(ctx.paperDir, `${JOB}.log`), "latin1") ?? "",
359
- );
365
+ const written = readOr(join(ctx.paperDir, `${JOB}.log`), "latin1");
366
+ const log = unwrapLog(written ?? "");
360
367
  return {
361
368
  step: "latex",
362
369
  final,
@@ -365,10 +372,30 @@ function latexPass(
365
372
  after: hashes(ctx.paperDir),
366
373
  markers: logMarkers(log),
367
374
  bib: bibInput(ctx.paperDir),
368
- errorLines: exitCode === 0 ? [] : errorExcerpt(log).map(fromLatin1),
375
+ errorLines:
376
+ exitCode === 0
377
+ ? []
378
+ : written === null
379
+ ? printed(r)
380
+ : errorExcerpt(log).map(fromLatin1),
369
381
  };
370
382
  }
371
383
 
384
+ /**
385
+ * The last lines pdflatex printed, for the run that wrote no log — it died before opening one (a
386
+ * format it cannot load, a missing TeX Live file). Marked by NO_LOG, which the report keys on.
387
+ */
388
+ function printed(r: { stdout?: unknown; stderr?: unknown }): string[] {
389
+ const lines = fromLatin1(
390
+ `${String(r.stdout ?? "")}\n${String(r.stderr ?? "")}`,
391
+ )
392
+ .split("\n")
393
+ .filter((l) => l.trim());
394
+ return [NO_LOG, ...lines.slice(-8).map((l) => ` ${l}`)];
395
+ }
396
+
397
+ const NO_LOG = `pdflatex wrote no ${JOB}.log — the last lines it printed:`;
398
+
372
399
  /** One bibtex run, on the input the `.aux` names BEFORE it runs, or null when it could not start. */
373
400
  function bibtexPass(ctx: BuildContext, opts: SpawnOptions): Observation | null {
374
401
  const before = hashes(ctx.paperDir);
@@ -395,6 +422,9 @@ export function compile(ctx: BuildContext): {
395
422
  } {
396
423
  const history: Observation[] = [];
397
424
  const opts = spawnOptions(ctx);
425
+ // Every log this loop reads is one its own pdflatex wrote. A pdflatex that dies before opening
426
+ // paper.log would otherwise have an earlier build's error quoted as this one's.
427
+ rmSync(join(ctx.paperDir, `${JOB}.log`), { force: true });
398
428
  const runs = { latex: 0, bibtex: 0 };
399
429
  for (;;) {
400
430
  const step = nextStep(summarize(history));
@@ -450,7 +480,7 @@ export const compileStep: BuildStep = {
450
480
  ...(end.lines.length
451
481
  ? end.lines
452
482
  : ["(no error line found in the log)"]),
453
- `full log: ${log}`,
483
+ ...(end.lines[0] === NO_LOG ? [] : [`full log: ${log}`]),
454
484
  ],
455
485
  };
456
486
  }
@@ -828,10 +858,6 @@ function formatRefusal(r: BuildResult): string {
828
858
  return r.staleRemoved ? `${refused}\n ${PDF_REMOVED}` : refused;
829
859
  }
830
860
 
831
- export function formatResults(results: readonly BuildResult[]): string {
832
- return results.map(formatResult).join("\n");
833
- }
834
-
835
861
  /**
836
862
  * 🔴 "Nothing to compile" COUNTS AS A REFUSAL on a par with a failed build. Failing to tell these
837
863
  * two apart is what once produced a green run over a paper no job had built.
@@ -11,8 +11,6 @@
11
11
  * leaked out of the depths of eslint-helpers.
12
12
  * Both are pinned down by the assertions below so there is no going back.
13
13
  */
14
- import assert from "node:assert/strict";
15
- import { recordCheck } from "vigiles";
16
14
  import { load as yamlLoad } from "js-yaml";
17
15
  import {
18
16
  mkdtempSync,
@@ -31,6 +29,7 @@ import { tmpdir } from "node:os";
31
29
  import { join, dirname } from "node:path";
32
30
  import { fileURLToPath } from "node:url";
33
31
  import { PAPERS_DIR_FIELD, findProjectRoot } from "../lib/paper-config.mjs";
32
+ import { createChecker } from "../lib/check.mjs";
34
33
 
35
34
  const HERE = dirname(fileURLToPath(import.meta.url));
36
35
  const { run, parseArgs, buildConfig, nextSteps, toPaths, runHook } =
@@ -39,14 +38,9 @@ const { init, choosePapers, offerWorkflow, missingPrograms, WORKFLOW_PATH } =
39
38
  await import(join(HERE, "init.ts"));
40
39
  const { PROGRAMS } = await import(join(HERE, "doctor.ts"));
41
40
 
42
- let n = 0;
43
41
  // Counted by vigiles too: `init` now loads `vigiles/claude-code` for its merge, and a harness that
44
42
  // loads vigiles without recording a check is reported as having verified nothing.
45
- const check = (label, cond) => {
46
- assert.ok(cond, label);
47
- recordCheck(label);
48
- n++;
49
- };
43
+ const check = createChecker();
50
44
 
51
45
  /** Runs the utility with output captured — quieter and faster than spawning a process. */
52
46
  async function cli(args, cwd) {
@@ -73,10 +67,9 @@ async function cli(args, cwd) {
73
67
  };
74
68
  } catch (e) {
75
69
  // 🔴 A LEAKED EXCEPTION IS A PROPERTY THAT MUST BE ASSERTED, NOT CAUGHT BY LETTING THE
76
- // HARNESS CRASH. The mutation that removes the catch around ESLint used to bring the
77
- // harness down with a stack trace, and the driver — under its own strict rule "killed only
78
- // by ITS OWN assertion" — refused to count that as a kill and printed "survived". I.e. the
79
- // real defect looked like a weak test.
70
+ // HARNESS CRASH. Removing the catch around ESLint brings the whole harness down with a
71
+ // stack trace instead of a clean, named assertion failure — i.e. the real defect would
72
+ // look like a broken test, not a caught one.
80
73
  return {
81
74
  code: 99,
82
75
  out: `THREW: ${e?.message ?? e}`,
@@ -537,8 +530,8 @@ check(
537
530
  // declaration had already been written — i.e. the install both succeeded and looked like
538
531
  // a crash.
539
532
  // 🔴 THE `.catch` HERE IS LOAD-BEARING, NOT CAUTION. A leaked exception is a PROPERTY that
540
- // gets asserted; caught by letting the harness crash it reads to the battery's driver as
541
- // "the mutation survived", i.e. the real defect would look like a hole in the test.
533
+ // gets asserted; without it, the harness crashes instead of failing a named assertion,
534
+ // i.e. the real defect would look like a hole in the test rather than a caught one.
542
535
  const aborted = await offerWorkflow(dir, "writing", {
543
536
  interactive: true,
544
537
  ask: async () => {
@@ -599,7 +592,7 @@ check(
599
592
  });
600
593
  // 🔴 JUDGE ONLY init's OWN REPORT, BEFORE doctor's banner. The first version of these
601
594
  // three assertions looked at the WHOLE output — and doctor also prints `✗ pdflatex` and
602
- // the same install command. The mutation that stripped the remedy OUT of init stayed
595
+ // the same install command. A version of init with the remedy stripped OUT still stayed
603
596
  // green: the assertion found a line printed by a different command and reported coverage
604
597
  // that did not exist.
605
598
  const own = out.text().split("── paperlint doctor")[0];
@@ -996,7 +989,7 @@ check(
996
989
  );
997
990
  check(
998
991
  "and the finding is printed before the ESLint report, with its consequence",
999
- /missing `PIPELINE-STATUS\.md`/.test(r.out) && /is checked/.test(r.out),
992
+ /missing `PIPELINE-STATUS\.md`/.test(r.out) && /are skipped/.test(r.out),
1000
993
  );
1001
994
  check(
1002
995
  'and it does NOT print "no findings" over something that was found',
@@ -1260,7 +1253,7 @@ check(
1260
1253
  }
1261
1254
 
1262
1255
  console.log(
1263
- `✓ ${String(n)} assertions passed — paperlint lint, one command instead of hand-rolled config`,
1256
+ `✓ ${String(check.count)} assertions passed — paperlint lint, one command instead of hand-rolled config`,
1264
1257
  );
1265
1258
 
1266
1259
  // ─────────────────────────────────────────────────────────────────────────────
@@ -1783,7 +1776,7 @@ console.log(
1783
1776
  "🔴 an optional rule turned on for a glob that reaches no paper.tex FAILS the run — not a green zero",
1784
1777
  silent.code === 1 &&
1785
1778
  silent.out.includes(
1786
- 'pdf/last-page-balance is turned on in "rules", but no linted paper.tex gets it',
1779
+ 'pdf/last-page-balance is turned on in "rules", but no paper.tex of the project gets it',
1787
1780
  ),
1788
1781
  );
1789
1782
  settings({
@@ -1799,5 +1792,5 @@ console.log(
1799
1792
  }
1800
1793
 
1801
1794
  console.log(
1802
- `✓ ${String(n)} assertions in total, including init's hooks and paperlint new`,
1795
+ `✓ ${String(check.count)} assertions in total, including init's hooks and paperlint new`,
1803
1796
  );