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
@@ -253,17 +253,19 @@ function judgePages(g: FlatGeometry, resolved: Resolved): Finding[] {
253
253
  [kind.limits.bodyPagesMax, g.body_pages, "body pages"],
254
254
  [kind.limits.refPagesMax, g.ref_pages, "reference pages"],
255
255
  ];
256
- return pages
257
- .filter(([max, got]) => max !== null && got > max)
258
- .map(([max, got, what]) =>
259
- finding("pages", {
260
- what,
261
- got,
262
- max: max ?? 0,
263
- venue: resolved.venue,
264
- kind: kind.name,
265
- }),
266
- );
256
+ return pages.flatMap(([max, got, what]) =>
257
+ max !== null && got > max
258
+ ? [
259
+ finding("pages", {
260
+ what,
261
+ got,
262
+ max,
263
+ venue: resolved.venue,
264
+ kind: kind.name,
265
+ }),
266
+ ]
267
+ : [],
268
+ );
267
269
  }
268
270
 
269
271
  function judgeRefPt(g: FlatGeometry, resolved: Resolved): Finding[] {
@@ -430,7 +432,7 @@ const META: Readonly<Record<VenueRuleName, Meta>> = {
430
432
  },
431
433
  messages: {
432
434
  unbuilt:
433
- "this paper names the venue `{{venue}}`, but {{file}} does not exist, so its page limit, fonts and format were NOT checked — run `paperlint build` before `paperlint lint`",
435
+ "not built yet, so `{{venue}}`'s page limit, fonts and format are not checked — run `paperlint build`",
434
436
  noPreset:
435
437
  'this paper names no venue preset yet, so its page limit, fonts and format are not checked — set "extends" in {{file}} (e.g. "paperlint:agenticdev"; see docs/rules.md)',
436
438
  noGeometry:
@@ -1,4 +1,4 @@
1
1
  {
2
- "$comment": "Name this paper's venue preset in \"extends\", e.g. \"paperlint:agenticdev\" (shipped: acm-sigconf, agenticdev, aisec, realm — see paperlint's docs/rules.md) or \"./venues/my-venue.jsonc\"; add \"kind\" for its page limit. Until then the page limit, fonts and format are not checked.",
2
+ "$comment": "Name this paper's venue preset in \"extends\": \"paperlint:<name>\" for a shipped one, e.g. \"paperlint:agenticdev\" (`npx paperlint --help` lists them), or a path to your own preset relative to THIS file, e.g. \"../../venues/my-venue.jsonc\"; add \"kind\" for its page limit. `npx paperlint new <name> --venue <preset>` writes both for a new paper. Until then the page limit, fonts and format are not checked.",
3
3
  "extends": null
4
4
  }
@@ -1,162 +0,0 @@
1
- /**
2
- * Mutation battery over `action.yml` and `scripts/eslint-report-guard.mjs`.
3
- * Run: `node action.mutations.mjs` (not `vigiles test` — this is not a harness).
4
- *
5
- * 🔴 EVERY MUTATION CARRIES A "THE PATCH LANDED" ASSERTION: the target must occur EXACTLY ONCE,
6
- * the mutant must be present in the file re-read from disk, the original text must be GONE, and
7
- * after the rollback the contents must return byte for byte. In this corpus a green mutation has
8
- * three times meant not "the defence holds" but "the patch never applied".
9
- *
10
- * A GREEN HARNESS UNDER A MUTATION is a finding about the TEST, not a conclusion about the defence.
11
- * Rank the causes: the wrong test · the patch did not land · the set is too narrow · THE RUN WAS
12
- * ALREADY RED BEFORE THE MUTATION. The last is why the baseline runs FIRST and its failure stops
13
- * the battery.
14
- *
15
- * ⚠️ Mutation 1 is the load-bearing one and it is deliberately asymmetric: it sends the step back
16
- * to calling `npx eslint` directly while LEAVING the words `paperlint lint` in the comments above. A
17
- * harness that grepped the file would stay green; this one parses the YAML and addresses the step
18
- * as a node, so it dies. That is the whole argument for the parser, made executable.
19
- *
20
- * It replaced a mutation over `--no-config-lookup`, which stopped having a target on 2026-09-18:
21
- * the flag went away with the eslint call, and the guarantee it carried moved INTO the CLI
22
- * (`overrideConfigFile: true`). The bypass it protected against is the same one — a consumer's
23
- * nested config deciding the rule set — so the case moved rather than disappeared.
24
- */
25
- import { readFileSync, writeFileSync } from "node:fs";
26
- import { execFileSync } from "node:child_process";
27
-
28
- const HARNESS = "action.harness.mjs";
29
-
30
- if (process.env.MUTATIONS_REPORT_COVERAGE) {
31
- console.log(`MUTATION-COVERS\t${HARNESS}`);
32
- process.exit(0);
33
- }
34
-
35
- const ACTION = "action.yml";
36
- const GUARD = "scripts/eslint-report-guard.mjs";
37
- const PRISTINE = new Map(
38
- [ACTION, GUARD].map((f) => [f, readFileSync(f, "utf8")]),
39
- );
40
- const restoreAll = () => {
41
- for (const [f, body] of PRISTINE) writeFileSync(f, body);
42
- };
43
-
44
- const M = [
45
- [
46
- ACTION,
47
- "the step goes back around the CLI to eslint (the words `paperlint lint` left in the comments)",
48
- "reinstate the bypass: the settings unread, the directory-structure check absent in CI, and a " +
49
- "consumer's nested config free to decide the rule set — while a grep still finds `paperlint lint`",
50
- "npx paperlint lint $PAPERLINT_PATHS",
51
- 'npx eslint --config "$PAPERLINT_CONFIG" $PAPERLINT_PATHS',
52
- ],
53
- [
54
- ACTION,
55
- "the guard is no longer called",
56
- "keep the lint run and drop the green-zero check — the job passes loudest when it measured nothing",
57
- 'node "$GITHUB_ACTION_PATH/scripts/eslint-report-guard.mjs" \\',
58
- "true # MUT \\",
59
- ],
60
- [
61
- ACTION,
62
- "texcount probed by the installer's exit code instead of by the binary",
63
- "trust an installer that can put nothing in place and report success",
64
- "command -v texcount >/dev/null || {",
65
- "true || {",
66
- ],
67
- [
68
- GUARD,
69
- "a report of ZERO linted files treated as clean",
70
- "reinstate the exact indistinguishability the guard exists to break",
71
- " if (res.length === 0) {",
72
- " if (false) { /* MUT */",
73
- ],
74
- [
75
- GUARD,
76
- "ESLint's return code masked",
77
- "swallow a real ESLint failure and report success",
78
- " return { code: eslintRc, lines };",
79
- " return { code: 0, lines }; /* MUT */",
80
- ],
81
- [
82
- GUARD,
83
- "an unparsable report treated as clean",
84
- "let a missing or corrupt report pass — nothing was measured either way",
85
- " lines.push(\n `::error::ESLint wrote no parsable report to ${reportPath} (rc=${eslintRc}). Nothing was measured.`,\n );\n return { code: 1, lines };",
86
- " return { code: 0, lines }; /* MUT */",
87
- ],
88
- ];
89
-
90
- const run = () => {
91
- try {
92
- return {
93
- ok: true,
94
- out: execFileSync("npx", ["vigiles", "test", HARNESS], {
95
- encoding: "utf8",
96
- stdio: "pipe",
97
- }),
98
- };
99
- } catch (e) {
100
- return { ok: false, out: `${e.stdout ?? ""}${e.stderr ?? ""}` };
101
- }
102
- };
103
-
104
- const base = run();
105
- if (!base.ok) {
106
- console.log(
107
- "❌ THE HARNESS IS RED BEFORE ANY MUTATION — the battery cannot tell a killed mutation from that:\n" +
108
- base.out.slice(-1200),
109
- );
110
- process.exit(1);
111
- }
112
-
113
- let ok = 0;
114
- let bad = 0;
115
- const rows = [];
116
-
117
- for (const [file, label, what, from, to] of M) {
118
- const pristine = PRISTINE.get(file);
119
- const hits = pristine.split(from).length - 1;
120
- if (hits !== 1) {
121
- console.log(
122
- `❌ ${label}: TARGET ${hits === 0 ? "NOT FOUND" : `NOT UNIQUE (${hits})`} in ${file}`,
123
- );
124
- bad++;
125
- continue;
126
- }
127
- writeFileSync(file, pristine.replace(from, to));
128
- const on = readFileSync(file, "utf8");
129
- if (!on.includes(to) || on.includes(from)) {
130
- console.log(
131
- `❌ ${label}: THE MUTATION DID NOT LAND (checked by re-reading ${file})`,
132
- );
133
- bad++;
134
- restoreAll();
135
- continue;
136
- }
137
- const res = run();
138
- restoreAll();
139
- if (readFileSync(file, "utf8") !== pristine) {
140
- console.log(`❌ ${label}: THE ROLLBACK FAILED on ${file}`);
141
- bad++;
142
- continue;
143
- }
144
- if (!res.ok) {
145
- ok++;
146
- const why =
147
- (res.out.match(/AssertionError[^\n]*?: ([^\n]*)/) ?? [])[1] ??
148
- "(the harness died)";
149
- rows.push([label, what, why.trim().slice(0, 100)]);
150
- } else {
151
- console.log(
152
- `🔴 ${label}: THE HARNESS IS GREEN UNDER THE MUTATION — a finding about the TEST, not a conclusion about the defence`,
153
- );
154
- bad++;
155
- }
156
- }
157
-
158
- console.log("\n| property | mutation | what the harness died on |");
159
- console.log("|---|---|---|");
160
- for (const r of rows) console.log(`| ${r[0]} | ${r[1]} | ${r[2]} |`);
161
- console.log(`\n${ok} mutation(s) killed, ${bad} problem(s)`);
162
- process.exit(bad === 0 ? 0 : 1);
@@ -1,62 +0,0 @@
1
- /**
2
- * Battery on the two `port/*-install-path` rules: four mutations, each removing one
3
- * load-bearing property — the detection itself, the markdown carriers, the template form on
4
- * the code side, and the ratchet.
5
- *
6
- * There is no fourth mutation here anymore — for the ratchet: the ratchet itself was removed.
7
- */
8
- import { fileURLToPath } from "node:url";
9
- import { dirname, join, resolve } from "node:path";
10
- import { runMutations } from "../lib/mutation-driver.mjs";
11
-
12
- const HERE = dirname(fileURLToPath(import.meta.url));
13
- const ROOT = resolve(HERE, "..");
14
- const RULE = join(HERE, "install-path-literals.mjs");
15
- const HARNESS = join(HERE, "install-path-literals.harness.mjs");
16
-
17
- process.exit(
18
- runMutations({
19
- root: ROOT,
20
- runner: "node",
21
- cases: [
22
- {
23
- name: "the detector stops finding anything",
24
- harness: HARNESS,
25
- expect: "expected at least three findings",
26
- disables:
27
- "the check itself — an install-specific path passes silently in both languages",
28
- edits: [
29
- [
30
- RULE,
31
- "for (const p of CHANNEL_PREFIXES) if (s.includes(p)) return p;",
32
- "void CHANNEL_PREFIXES;",
33
- ],
34
- ],
35
- },
36
- {
37
- name: "markdown stops seeing frontmatter and code",
38
- harness: HARNESS,
39
- expect: "expected at least three findings",
40
- disables:
41
- "the carriers in a skill — only prose is left, while the command lives in a fence and in allowed-tools",
42
- edits: [
43
- [
44
- RULE,
45
- "return { yaml: check, code: check, inlineCode: check, text: check };",
46
- "return { text: check };",
47
- ],
48
- ],
49
- },
50
- {
51
- name: "code stops seeing the template string",
52
- harness: HARNESS,
53
- expect: "expected one plain and one template finding",
54
- disables:
55
- "the second literal form — a path assembled by a template stays a path",
56
- edits: [
57
- [RULE, "TemplateElement(node) {", "TemplateElement_disabled(node) {"],
58
- ],
59
- },
60
- ],
61
- }),
62
- );
@@ -1,196 +0,0 @@
1
- /**
2
- * Mutation battery for the LaTeX language `eslint-rules/latex-language.mjs`.
3
- * Run: `node eslint-rules/latex-language.mutations.mjs` (not `vigiles test` — not a harness).
4
- *
5
- * 🔴 EVERY MUTATION CARRIES A "THE PATCH LANDED" ASSERTION: the file is re-read from disk, the
6
- * mutant must be in it, the original text must be GONE (otherwise the target was not unique),
7
- * and after the rollback the contents must return byte for byte. In the repository this came
8
- * from, a green mutation has three times meant not "the defence holds" but "the patch never
9
- * applied".
10
- *
11
- * A GREEN HARNESS UNDER A MUTATION is a finding about the TEST, not a conclusion about the
12
- * defence. Rank the causes:
13
- * 1. the wrong test — it pins a side effect rather than the property itself;
14
- * 2. the patch did not land — removed by the assertion above;
15
- * 3. the set is too narrow — the property is observable in no fixture;
16
- * 4. 🔴 THE RUN WAS ALREADY RED BEFORE THE MUTATION — then "killed" and "was failing anyway"
17
- * are indistinguishable.
18
- * Cause 4 is why the baseline run comes FIRST and why its failure stops the battery.
19
- *
20
- * ⚠️ WHAT IS NOT HERE ANY MORE. The original battery had a second group of three mutations
21
- * against the `case "html"` patch in three consumer rule modules — the patch that taught them to
22
- * recognise a `%` comment, not only an `<!--` one. Those modules were not extracted, so the
23
- * mutations have no target. The property itself did NOT disappear with them: the language still
24
- * trims both edges of a comment node, and the two mutations named
25
- * `comment/RIGHT edge` and `comment/LEFT edge` below cover the language's half of it. What is
26
- * genuinely gone is the evidence that a CONSUMER acts on it.
27
- */
28
- import { readFileSync, writeFileSync } from "node:fs";
29
- import { execFileSync } from "node:child_process";
30
-
31
- const HARNESS = "eslint-rules/latex-language.harness.mjs";
32
-
33
- // Coverage mode — see `scripts/run-mutations.mjs` guard 2 and the block in
34
- // `lib/mutation-driver.mjs`. Says what this battery can kill, without touching a file.
35
- if (process.env.MUTATIONS_REPORT_COVERAGE) {
36
- console.log(`MUTATION-COVERS\t${HARNESS}`);
37
- process.exit(0);
38
- }
39
-
40
- const LANG = "eslint-rules/latex-language.mjs";
41
- const PRISTINE = readFileSync(LANG, "utf8");
42
-
43
- const M = [
44
- [
45
- "synthesis of `## References` from `\\bibliography{}`",
46
- "stop declaring the bibliography a heading",
47
- ' if (node.content === "bibliography" && pos) {',
48
- " if (false && pos) { /* MUT */",
49
- ],
50
- [
51
- "synthesis of `## Abstract` from the environment",
52
- "stop declaring the abstract a heading",
53
- ' if (env === "abstract" && pos) {',
54
- " if (false && pos) { /* MUT */",
55
- ],
56
- [
57
- "preamble cut-off during synthesis",
58
- "treat preamble constructs (e.g. `\\AtBeginDocument{\\bibliography{}}`) as nodes of the document",
59
- ' if (pos && pos.start.offset < preEnd) {\n for (const k of ["content", "args"]) if (node[k]) walk(node[k]);\n return;\n }',
60
- " if (false) { return; } /* MUT */",
61
- ],
62
- [
63
- "preamble blanking",
64
- "leave the preamble (package list, author macros) visible as prose",
65
- " if (preEnd) {\n blank(0, preEnd);",
66
- " if (preEnd) {\n void 0; /* MUT */",
67
- ],
68
- [
69
- "comment/RIGHT edge trim",
70
- "leave the trailing `\\n` inside the comment node",
71
- ' while (\n ce > pos.start.offset &&\n (src[ce - 1] === "\\n" || src[ce - 1] === "\\r")\n )\n ce--;',
72
- " /* MUT */",
73
- ],
74
- [
75
- "comment/LEFT edge trim",
76
- "leave the preceding `\\n` inside the comment node",
77
- ' while (cs < ce && src[cs] !== "%") cs++;',
78
- " /* MUT */",
79
- ],
80
- [
81
- "`**` written in place of `\\te`",
82
- "write the `**` before the content, leaving the line to start with spaces",
83
- ' while (pendingStrong.length) {\n const o = pendingStrong.pop();\n chars[o] = "*";\n chars[o + 1] = "*";\n }',
84
- " while (pendingStrong.length) { pendingStrong.pop(); } /* MUT */",
85
- ],
86
- [
87
- "`\\texttt{X}` → `` `X` ``",
88
- "stop reproducing the one markdown markup the projection can express",
89
- ' if (node.content === "texttt" || node.content === "lstinline") {',
90
- " if (false) { /* MUT */",
91
- ],
92
- [
93
- "heading projected to ATX",
94
- "stop writing `#` where `\\section` was",
95
- ' for (let i = 0; i < depth; i++) chars[pos.start.offset + i] = "#";',
96
- ' for (let i = 0; i < 0; i++) chars[pos.start.offset + i] = "#"; /* MUT */',
97
- ],
98
- [
99
- "OPAQUE macro blanking",
100
- "leave `\\cite` / `\\label` / `\\input` as prose",
101
- " if (OPAQUE.test(node.content)) {\n blank(pos.start.offset, argEnd(node).offset + 1);\n return;\n }",
102
- " if (false) { return; } /* MUT */",
103
- ],
104
- [
105
- "math blanking",
106
- "leave formulas as prose",
107
- ' if (\n (node.type === "inlinemath" ||\n node.type === "displaymath" ||\n node.type === "verbatim") &&\n pos\n ) {',
108
- " if (false) { /* MUT */",
109
- ],
110
- [
111
- "float prose kept (caption / footnote)",
112
- "swallow the caption together with the float again — the double lock returns",
113
- "const FLOAT_PROSE = /^(caption|footnote)$/;",
114
- "const FLOAT_PROSE = /^(?!)$/; /* MUT */",
115
- ],
116
- ];
117
-
118
- const runHarness = () => {
119
- try {
120
- execFileSync("npx", ["vigiles", "test", HARNESS], {
121
- encoding: "utf8",
122
- stdio: ["ignore", "pipe", "pipe"],
123
- });
124
- return { failed: false, out: "" };
125
- } catch (e) {
126
- return { failed: true, out: (e.stdout ?? "") + (e.stderr ?? "") };
127
- }
128
- };
129
-
130
- // 🔴 A BASELINE RUN BEFORE ANY MUTATION — see cause 4 in the header.
131
- {
132
- const base = runHarness();
133
- if (base.failed) {
134
- console.log(
135
- "❌ THE HARNESS IS RED BEFORE ANY MUTATION — the battery cannot tell a killed mutation " +
136
- "from that:\n" +
137
- base.out.slice(-1500),
138
- );
139
- process.exit(1);
140
- }
141
- }
142
-
143
- let ok = 0;
144
- let bad = 0;
145
- const rows = [];
146
- for (const [label, what, from, to] of M) {
147
- const hits = PRISTINE.split(from).length - 1;
148
- if (hits !== 1) {
149
- console.log(
150
- `❌ ${label}: TARGET ${hits === 0 ? "NOT FOUND" : `NOT UNIQUE (${hits})`} — ${from.slice(0, 70)}`,
151
- );
152
- bad++;
153
- continue;
154
- }
155
- writeFileSync(LANG, PRISTINE.replace(from, to));
156
- const on = readFileSync(LANG, "utf8");
157
- if (!on.includes(to) || on.includes(from)) {
158
- console.log(
159
- `❌ ${label}: THE MUTATION DID NOT LAND (checked by re-reading the file)`,
160
- );
161
- bad++;
162
- writeFileSync(LANG, PRISTINE);
163
- continue;
164
- }
165
- const { failed, out } = runHarness();
166
- writeFileSync(LANG, PRISTINE);
167
- if (readFileSync(LANG, "utf8") !== PRISTINE) {
168
- console.log(`❌ ${label}: THE ROLLBACK FAILED`);
169
- bad++;
170
- continue;
171
- }
172
- const at = (out.match(/latex-language\.harness\.mjs:(\d+)/) ?? [])[1];
173
- const msg =
174
- (out.match(/AssertionError[^:]*: ([^\n]+)/) ?? [])[1] ??
175
- (out.match(/((?:Error|ENOENT)[^\n]*)/) ?? [])[1] ??
176
- (out.match(
177
- /([^\n]*(?:must|drifted|was lost|is stale|leaked|swallowed)[^\n]*)/,
178
- ) ?? [])[1] ??
179
- "";
180
- const where = (at ? `harness:${at} · ` : "") + msg;
181
- if (failed) {
182
- ok++;
183
- rows.push([label, what, where.trim().slice(0, 130)]);
184
- } else {
185
- console.log(
186
- `🔴 ${label}: THE HARNESS IS GREEN UNDER THE MUTATION — a finding about the TEST, not a conclusion about the defence`,
187
- );
188
- bad++;
189
- }
190
- }
191
- console.log("\n| property | mutation | what the harness died on |");
192
- console.log("|---|---|---|");
193
- for (const [a, b, c] of rows)
194
- console.log(`| \`${a}\` | ${b} | ${c.replace(/\|/g, "\\|")} |`);
195
- console.log(`\n${ok} mutation(s) killed, ${bad} problem(s)`);
196
- process.exit(bad ? 1 : 0);
@@ -1,127 +0,0 @@
1
- /**
2
- * Battery for `paper/research-question` — five mutations, each removes ITS OWN load-bearing
3
- * property.
4
- *
5
- * 🔴 Why the battery matters here specifically. The rule's success state is SILENCE, and on the
6
- * live corpus it only produces two findings out of four papers. "It passed" and "it cannot fire"
7
- * look identical from the outside, and only this tells them apart.
8
- *
9
- * Two mutations do not target the finding but the SCOPE and the LANGUAGE — the halves a test
10
- * forgets. Without the stage gate the rule scolds every draft; without markdown it loses
11
- * `compile-rules`, i.e. half of the real findings, while staying green.
12
- */
13
- import { fileURLToPath } from "node:url";
14
- import { dirname, join, resolve } from "node:path";
15
- import { runMutations } from "../lib/mutation-driver.mjs";
16
-
17
- const HERE = dirname(fileURLToPath(import.meta.url));
18
- const ROOT = resolve(HERE, "..");
19
- const RULE = join(HERE, "paper-research-question.mjs");
20
- const HARNESS = join(HERE, "paper-research-question.harness.mjs");
21
-
22
- process.exit(
23
- runMutations({
24
- root: ROOT,
25
- runner: "node",
26
- cases: [
27
- {
28
- name: "the rule stops noticing that nothing was declared",
29
- harness: HARNESS,
30
- expect: "shipped and no question — a finding",
31
- disables:
32
- "the subject itself: the rule stays silent across the whole corpus, and silence is " +
33
- "its success state — from the outside a disabled rule is indistinguishable from a clean paper",
34
- edits: [[RULE, 'if (question.trim() === "") {', "if (false) {"]],
35
- },
36
- {
37
- // 🔴 The half that did not exist before 2026-09-19, and the reason the redesign happened.
38
- // Without this case the second step could be deleted and the battery would stay green on
39
- // the strength of the first — which is how a two-step check quietly becomes a one-step one.
40
- name: "the CONTENT check is dropped — a declaration nobody carries passes",
41
- harness: HARNESS,
42
- expect:
43
- "declared but absent from the paper — a finding, where the old pattern was silent",
44
- disables:
45
- "step two. The scorecard would only have to SAY a question exists, never to have it in " +
46
- "the paper — which is the checklist the `bytes` field exists to not be. The predecessor " +
47
- "failed the same way from the other side: it matched «we leave the research question to " +
48
- "future work» and reported a paper that states no question as clean",
49
- edits: [
50
- [
51
- RULE,
52
- "if (flatten(raw).includes(flatten(question))) return;",
53
- "return;",
54
- ],
55
- ],
56
- },
57
- {
58
- name: "the STAGE GATE is removed — drafts get scolded too",
59
- harness: HARNESS,
60
- expect:
61
- "a draft (no stages) — silent, even though it has no question either",
62
- disables:
63
- "the scope. The rule does not ask 'is there a question', it asks 'is there a question " +
64
- "FOR SOMETHING SHIPPED'. Without the gate every draft gets a finding — and a rule that " +
65
- "scolds drafts gets turned off within a week, at which point both real findings disappear too",
66
- edits: [
67
- [
68
- RULE,
69
- "if (stages.length === 0) return; // not shipped — owes nothing",
70
- "",
71
- ],
72
- ],
73
- },
74
- {
75
- name: "the LANGUAGE narrows to LaTeX — a markdown paper becomes invisible",
76
- harness: HARNESS,
77
- expect: "a paper in markdown is checked the same way",
78
- disables:
79
- "the second half of the language. For the `tex/latex` language the text sits in `raw`, " +
80
- "for markdown it sits in `text`. The mutation keeps only the first, and " +
81
- "`compile-rules-2026` (paper written in markdown, no question) stops being found — HALF " +
82
- "of the live corpus's findings disappear silently, and the run stays green",
83
- edits: [
84
- [
85
- RULE,
86
- "const raw = context.sourceCode.raw ?? context.sourceCode.text;",
87
- "const raw = context.sourceCode.raw;",
88
- ],
89
- ],
90
- },
91
- {
92
- name: "the stage list is taken from somewhere OTHER than the field again",
93
- harness: HARNESS,
94
- expect:
95
- "the stage list in the message comes from the field and carries BOTH",
96
- disables:
97
- "the whole reason the move was made. The predecessor derived the stage with a regex " +
98
- "over the scorecard's prose and on agenticdev printed `submitted` where `submitted, " +
99
- "camera-ready` was declared. The mutation does not change the set of findings — only " +
100
- "the TEXT lies, and without its own assert the regression would have passed silently",
101
- edits: [
102
- [
103
- RULE,
104
- 'data: { stages: stages.join("/") }',
105
- 'data: { stages: "submitted" }',
106
- ],
107
- ],
108
- },
109
- {
110
- name: "the scorecard's name stops arriving as an option",
111
- harness: HARNESS,
112
- expect:
113
- "and with a nonexistent scorecard a shipped paper is also silent — the stage gate is load-bearing",
114
- disables:
115
- "the boundary 'the mechanism goes in the package, the data stays with the consumer'. " +
116
- "`PIPELINE-STATUS.md` is ONE repository's convention, and hardcoding it makes the rule unusable for everyone else",
117
- edits: [
118
- [
119
- RULE,
120
- 'const statusName =\n context.options?.[0]?.statusFile ?? "PIPELINE-STATUS.md";',
121
- 'const statusName =\n "PIPELINE-STATUS.md";',
122
- ],
123
- ],
124
- },
125
- ],
126
- }),
127
- );
@@ -1,81 +0,0 @@
1
- /**
2
- * Battery for `paper/stages` and `paper/source` — four mutations, chosen so that each one
3
- * removes ITS OWN load-bearing property, rather than just breaking the file.
4
- *
5
- * 🔴 Why the battery matters here specifically. For both rules the success state is SILENCE, and
6
- * the harness catches them on fixtures that it lays out itself. "It passed" and "it cannot fail"
7
- * look identical from the outside; the battery is the only thing that tells them apart.
8
- *
9
- * The first two mutations hit the TWO DIRECTIONS of `paper/stages`, and this is not symmetry for
10
- * its own sake: without the second direction the rule turns itself off by deleting the
11
- * frontmatter — no declarations, so no mismatches, so green. The `declared-without-bytes`
12
- * mutation and the `bytes-without-declaration` mutation must die on DIFFERENT asserts, or only
13
- * one half is proven.
14
- */
15
- import { fileURLToPath } from "node:url";
16
- import { dirname, join, resolve } from "node:path";
17
- import { runMutations } from "../lib/mutation-driver.mjs";
18
-
19
- const HERE = dirname(fileURLToPath(import.meta.url));
20
- const ROOT = resolve(HERE, "..");
21
- const RULE = join(HERE, "paper-stages.mjs");
22
- const HARNESS = join(HERE, "paper-stages.harness.mjs");
23
-
24
- process.exit(
25
- runMutations({
26
- root: ROOT,
27
- runner: "node",
28
- cases: [
29
- {
30
- name: "the DECLARED→BYTES direction stops checking",
31
- harness: HARNESS,
32
- expect: "wrong byte count is reported",
33
- disables:
34
- "the check of declared `bytes` against the file on disk — a stage could declare any " +
35
- "number, and the pdf underneath it could be anything",
36
- edits: [[RULE, "if (got !== Number(rec.bytes)) {", "if (false) {"]],
37
- },
38
- {
39
- name: "the BYTES→DECLARED direction stops checking",
40
- harness: HARNESS,
41
- expect: "a frozen version nobody declared is reported",
42
- disables:
43
- "the second half — the one without which the rule is muted by deleting the " +
44
- "frontmatter: no declarations ⇒ no mismatches ⇒ green",
45
- edits: [
46
- [
47
- RULE,
48
- "if (records.some((r) => r.stage === f.stage && r.date === f.date))\n continue;",
49
- "if (true)\n continue;",
50
- ],
51
- ],
52
- },
53
- {
54
- name: "`paper/source` stops checking the source's size",
55
- harness: HARNESS,
56
- expect: "a byte mismatch on the source is reported",
57
- disables:
58
- "the IDENTITY check on a frozen source: `existsSync` answers 'a file exists', " +
59
- "and only the size answers 'this is that exact file'",
60
- edits: [
61
- [RULE, "if (Number.isFinite(want) && got !== want)", "if (false)"],
62
- ],
63
- },
64
- {
65
- name: "acknowledging a loss starts EXEMPTING instead of recording",
66
- harness: HARNESS,
67
- expect: "an acknowledged loss is still reported, not silenced",
68
- disables:
69
- "the decision that `sourceLost` is a RECORD, not an indulgence: the rule must keep " +
70
- "speaking, because the state is still defective, merely unfixable today",
71
- edits: [
72
- [
73
- RULE,
74
- "if (rec?.sourceLost === true) {",
75
- "if (rec?.sourceLost === true && false) {",
76
- ],
77
- ],
78
- },
79
- ],
80
- }),
81
- );