paperlint 7.1.0 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. package/.github/workflows/ci.yml +3 -0
  2. package/.github/workflows/deadlines.yml +100 -0
  3. package/README.md +6 -0
  4. package/dist/adapters/hotcrp/deadlines.d.ts +8 -0
  5. package/dist/adapters/hotcrp/deadlines.d.ts.map +1 -0
  6. package/dist/adapters/hotcrp/deadlines.io.d.ts +6 -0
  7. package/dist/adapters/hotcrp/deadlines.io.d.ts.map +1 -0
  8. package/dist/adapters/hotcrp/deadlines.io.js +26 -0
  9. package/dist/adapters/hotcrp/deadlines.io.js.map +1 -0
  10. package/dist/adapters/hotcrp/deadlines.js +134 -0
  11. package/dist/adapters/hotcrp/deadlines.js.map +1 -0
  12. package/dist/adapters/hotcrp/index.d.ts +2 -0
  13. package/dist/adapters/hotcrp/index.d.ts.map +1 -1
  14. package/dist/adapters/hotcrp/index.js +2 -0
  15. package/dist/adapters/hotcrp/index.js.map +1 -1
  16. package/dist/build.d.ts.map +1 -1
  17. package/dist/build.js +1 -6
  18. package/dist/build.js.map +1 -1
  19. package/dist/cli.d.ts +3 -4
  20. package/dist/cli.d.ts.map +1 -1
  21. package/dist/cli.js +53 -47
  22. package/dist/cli.js.map +1 -1
  23. package/dist/cycle-rules.d.ts +24 -0
  24. package/dist/cycle-rules.d.ts.map +1 -0
  25. package/dist/cycle-rules.js +212 -0
  26. package/dist/cycle-rules.js.map +1 -0
  27. package/dist/domain/cycle.d.ts +148 -0
  28. package/dist/domain/cycle.d.ts.map +1 -0
  29. package/dist/domain/cycle.js +281 -0
  30. package/dist/domain/cycle.js.map +1 -0
  31. package/dist/domain/deadline.d.ts +128 -0
  32. package/dist/domain/deadline.d.ts.map +1 -0
  33. package/dist/domain/deadline.js +281 -0
  34. package/dist/domain/deadline.js.map +1 -0
  35. package/dist/eslint-rules/cycle-stage.d.ts +39 -0
  36. package/dist/eslint-rules/cycle-stage.d.ts.map +1 -0
  37. package/dist/eslint-rules/cycle-stage.js +119 -0
  38. package/dist/eslint-rules/cycle-stage.js.map +1 -0
  39. package/dist/eslint-rules/latex-language.d.ts.map +1 -1
  40. package/dist/eslint-rules/latex-language.js +10 -13
  41. package/dist/eslint-rules/latex-language.js.map +1 -1
  42. package/dist/eslint-rules/paper-context.js +2 -2
  43. package/dist/eslint-rules/paper-typography.d.ts.map +1 -1
  44. package/dist/eslint-rules/paper-typography.js +22 -91
  45. package/dist/eslint-rules/paper-typography.js.map +1 -1
  46. package/dist/eslint-rules/papers.d.ts +1 -3
  47. package/dist/eslint-rules/papers.d.ts.map +1 -1
  48. package/dist/eslint-rules/papers.js +2 -4
  49. package/dist/eslint-rules/papers.js.map +1 -1
  50. package/dist/eslint-rules/tex-build.d.ts.map +1 -1
  51. package/dist/eslint-rules/tex-build.js +8 -25
  52. package/dist/eslint-rules/tex-build.js.map +1 -1
  53. package/dist/init.d.ts +1 -4
  54. package/dist/init.d.ts.map +1 -1
  55. package/dist/init.js +3 -9
  56. package/dist/init.js.map +1 -1
  57. package/dist/lib/markdown.d.ts +5 -81
  58. package/dist/lib/markdown.d.ts.map +1 -1
  59. package/dist/lib/markdown.js +5 -116
  60. package/dist/lib/markdown.js.map +1 -1
  61. package/dist/lib/paper-config.d.ts.map +1 -1
  62. package/dist/lib/paper-config.js +2 -2
  63. package/dist/lib/paper-config.js.map +1 -1
  64. package/dist/lib/skill-eval-fixture.d.ts +6 -4
  65. package/dist/lib/skill-eval-fixture.d.ts.map +1 -1
  66. package/dist/lib/skill-eval-fixture.js +25 -18
  67. package/dist/lib/skill-eval-fixture.js.map +1 -1
  68. package/dist/new-paper.d.ts +5 -11
  69. package/dist/new-paper.d.ts.map +1 -1
  70. package/dist/new-paper.js +12 -23
  71. package/dist/new-paper.js.map +1 -1
  72. package/dist/paper-files.d.ts +4 -4
  73. package/dist/paper-prose.d.ts +17 -0
  74. package/dist/paper-prose.d.ts.map +1 -0
  75. package/dist/paper-prose.js +46 -0
  76. package/dist/paper-prose.js.map +1 -0
  77. package/dist/paper-settings.d.ts +7 -0
  78. package/dist/paper-settings.d.ts.map +1 -1
  79. package/dist/paper-settings.js +61 -8
  80. package/dist/paper-settings.js.map +1 -1
  81. package/dist/ports/venue-deadlines.d.ts +14 -0
  82. package/dist/ports/venue-deadlines.d.ts.map +1 -0
  83. package/dist/ports/venue-deadlines.js +2 -0
  84. package/dist/ports/venue-deadlines.js.map +1 -0
  85. package/dist/presets.d.ts +12 -0
  86. package/dist/presets.d.ts.map +1 -1
  87. package/dist/presets.js +19 -11
  88. package/dist/presets.js.map +1 -1
  89. package/dist/structure.d.ts.map +1 -1
  90. package/dist/structure.js +4 -3
  91. package/dist/structure.js.map +1 -1
  92. package/dist/submission.d.ts +8 -0
  93. package/dist/submission.d.ts.map +1 -1
  94. package/dist/submission.js +41 -10
  95. package/dist/submission.js.map +1 -1
  96. package/dist/tex-requirements.d.ts +3 -0
  97. package/dist/tex-requirements.d.ts.map +1 -1
  98. package/dist/tex-requirements.js +19 -0
  99. package/dist/tex-requirements.js.map +1 -1
  100. package/dist/tex-venue-rules.d.ts +19 -0
  101. package/dist/tex-venue-rules.d.ts.map +1 -1
  102. package/dist/tex-venue-rules.js +69 -0
  103. package/dist/tex-venue-rules.js.map +1 -1
  104. package/dist/types.d.ts +0 -2
  105. package/dist/types.d.ts.map +1 -1
  106. package/dist/venue-rules.d.ts +10 -0
  107. package/dist/venue-rules.d.ts.map +1 -1
  108. package/dist/venue-rules.js +26 -10
  109. package/dist/venue-rules.js.map +1 -1
  110. package/docs/configuration.md +146 -20
  111. package/docs/e2e.md +4 -6
  112. package/docs/prior-art/paper-folder-scaffolding.md +24 -29
  113. package/docs/rules/cycle/evidence.md +87 -0
  114. package/docs/rules/cycle/port-done.md +75 -0
  115. package/docs/rules/cycle/record.md +152 -0
  116. package/docs/rules/cycle/stage.md +85 -0
  117. package/docs/rules/tex/claim-provenance.md +0 -1
  118. package/docs/rules.md +48 -47
  119. package/docs/submission.md +5 -0
  120. package/eslint-rules/cycle-stage.test.mjs +131 -0
  121. package/eslint-rules/cycle-stage.ts +141 -0
  122. package/eslint-rules/latex-language.harness.mjs +3 -3
  123. package/eslint-rules/latex-language.ts +10 -13
  124. package/eslint-rules/paper-context.ts +2 -2
  125. package/eslint-rules/paper-typography.test.mjs +17 -85
  126. package/eslint-rules/paper-typography.ts +22 -98
  127. package/eslint-rules/papers.harness.mjs +1 -1
  128. package/eslint-rules/papers.ts +2 -4
  129. package/eslint-rules/tex-build.harness.mjs +2 -2
  130. package/eslint-rules/tex-build.ts +8 -25
  131. package/eslint.config.mjs +41 -2
  132. package/hooks/hooks.harness.ts +15 -15
  133. package/hooks/paper-edit-guard.hook.mjs +5 -8
  134. package/hooks/paper-edit-guard.test.ts +12 -3
  135. package/hooks/paper-skills-nudge.hook.mjs +2 -3
  136. package/hooks/paper-skills-nudge.test.ts +13 -3
  137. package/hooks/paper-status-gates.hook.mjs +2 -2
  138. package/hooks/paper-status-gates.test.ts +14 -2
  139. package/lib/markdown.test.mjs +5 -60
  140. package/lib/markdown.ts +9 -136
  141. package/lib/paper-config.ts +3 -2
  142. package/lib/skill-checks.test.mjs +4 -1
  143. package/lib/skill-eval-fixture.ts +25 -18
  144. package/package.json +1 -3
  145. package/presets/aidc.jsonc +24 -3
  146. package/presets/aidc.md +6 -3
  147. package/presets/msr.jsonc +25 -1
  148. package/presets/msr.md +5 -1
  149. package/presets/realm.jsonc +1 -1
  150. package/presets/venue-profile.schema.json +39 -0
  151. package/scripts/check.ts +4 -0
  152. package/scripts/harness-api.frozen.json +0 -5
  153. package/scripts/refresh-deadlines.test.ts +431 -0
  154. package/scripts/refresh-deadlines.ts +387 -0
  155. package/skills/cold-read-diff/SKILL.md +3 -3
  156. package/skills/cold-read-diff/SKILL.md.spec.ts +2 -2
  157. package/skills/grade-paper-writing/SKILL.md +14 -22
  158. package/skills/grade-paper-writing/SKILL.md.spec.ts +13 -21
  159. package/skills/grade-paper-writing/__snapshots__/prose-lint.test.ts.snap +40 -26
  160. package/skills/grade-paper-writing/prose-lint.mjs +108 -278
  161. package/skills/grade-paper-writing/prose-lint.test.ts +184 -81
  162. package/skills/paper-adversarial-review/SKILL.md +2 -2
  163. package/skills/paper-adversarial-review/SKILL.md.spec.ts +1 -1
  164. package/skills/paper-pipeline/SKILL.md +9 -12
  165. package/skills/paper-pipeline/SKILL.md.spec.ts +8 -11
  166. package/skills/paper-pipeline/description-language.eval.mjs +25 -15
  167. package/skills/paper-pipeline/framing-vs-vocabulary.eval.mjs +25 -15
  168. package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +25 -15
  169. package/skills/paper-pipeline/pipeline-firing.eval.mjs +27 -16
  170. package/skills/paper-pipeline/pipeline-language.eval.mjs +25 -15
  171. package/skills/paper-pipeline/references/acceptance-gate.md +16 -20
  172. package/skills/paper-pipeline/references/artifact-checklist.md +4 -5
  173. package/skills/paper-pipeline/references/pipeline-status-template.md +1 -1
  174. package/skills/paper-pipeline/scripts/README.md +0 -4
  175. package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +8 -72
  176. package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +48 -156
  177. package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +81 -75
  178. package/skills/paper-pipeline/scripts/generated-code.harness.mjs +3 -3
  179. package/skills/paper-status/SKILL.md +2 -2
  180. package/skills/paper-status/SKILL.md.spec.ts +1 -1
  181. package/skills/render-paper/SKILL.md +2 -2
  182. package/skills/render-paper/SKILL.md.spec.ts +1 -1
  183. package/skills/render-paper/extract-pdf-facts.mjs +2 -2
  184. package/skills/tighten-paper/SKILL.md +29 -42
  185. package/skills/tighten-paper/SKILL.md.spec.ts +28 -41
  186. package/skills/verify-citations/scripts/bib-authors.check.test.mjs +0 -13
  187. package/skills/verify-citations/scripts/bib-authors.cli.test.ts +23 -11
  188. package/skills/verify-citations/scripts/bib-authors.mjs +8 -93
  189. package/skills/verify-citations/scripts/bib-authors.test.mjs +1 -44
  190. package/src/adapters/hotcrp/cassette/README.md +22 -0
  191. package/src/adapters/hotcrp/cassette/deadlines-aidc26.html +17 -0
  192. package/src/adapters/hotcrp/cassette/deadlines-fse2027.html +24 -0
  193. package/src/adapters/hotcrp/cassette/deadlines-icse2027.html +25 -0
  194. package/src/adapters/hotcrp/cassette/deadlines-msr2027.html +25 -0
  195. package/src/adapters/hotcrp/cassette/deadlines-nosuch.html +18 -0
  196. package/src/adapters/hotcrp/cassette/deadlines-sosp26.html +26 -0
  197. package/src/adapters/hotcrp/deadlines.io.test.ts +86 -0
  198. package/src/adapters/hotcrp/deadlines.io.ts +27 -0
  199. package/src/adapters/hotcrp/deadlines.test.ts +170 -0
  200. package/src/adapters/hotcrp/deadlines.ts +161 -0
  201. package/src/adapters/hotcrp/index.ts +2 -0
  202. package/src/build.harness.mjs +2 -2
  203. package/src/build.test.ts +1 -1
  204. package/src/build.ts +1 -6
  205. package/src/cli.harness.mjs +26 -46
  206. package/src/cli.test.ts +41 -40
  207. package/src/cli.ts +57 -58
  208. package/src/cycle-rules.test.ts +278 -0
  209. package/src/cycle-rules.ts +273 -0
  210. package/src/domain/cycle.test.ts +456 -0
  211. package/src/domain/cycle.ts +432 -0
  212. package/src/domain/deadline.test.ts +386 -0
  213. package/src/domain/deadline.ts +472 -0
  214. package/src/folder-venue-rule.test.ts +1 -1
  215. package/src/init.test.ts +6 -7
  216. package/src/init.ts +3 -22
  217. package/src/lint-fix.test.ts +10 -4
  218. package/src/new-paper.harness.mjs +9 -28
  219. package/src/new-paper.test.ts +7 -6
  220. package/src/new-paper.ts +11 -30
  221. package/src/paper-files.test.ts +5 -4
  222. package/src/paper-files.ts +4 -4
  223. package/src/paper-prose.test.ts +49 -0
  224. package/src/paper-prose.ts +67 -0
  225. package/src/paper-settings.test.ts +167 -0
  226. package/src/paper-settings.ts +85 -11
  227. package/src/parse-args.test.ts +0 -1
  228. package/src/ports/venue-deadlines.ts +14 -0
  229. package/src/presets.test.ts +105 -0
  230. package/src/presets.ts +43 -10
  231. package/src/rule-cases.test.ts +144 -0
  232. package/src/structure.harness.ts +5 -11
  233. package/src/structure.ts +4 -3
  234. package/src/submission.test.ts +105 -1
  235. package/src/submission.ts +73 -12
  236. package/src/tex-requirements.ts +31 -0
  237. package/src/tex-venue-rules.test.ts +76 -0
  238. package/src/tex-venue-rules.ts +85 -0
  239. package/src/types.ts +0 -2
  240. package/src/venue-rules.test.ts +82 -0
  241. package/src/venue-rules.ts +44 -12
  242. package/fixtures/real-markdown-paper/baseline.json +0 -22
  243. package/fixtures/real-markdown-paper/baseline.ts +0 -70
  244. package/skills/paper-pipeline/PIPELINE-MAP.md +0 -381
  245. package/skills/paper-pipeline/pipeline-firing.baseline.json +0 -270
  246. package/skills/paper-pipeline/references/sweep-2026-08-09-ideal-pipeline.md +0 -585
  247. package/skills/paper-pipeline/scripts/artifact-coverage.harness.mjs +0 -492
  248. package/skills/paper-pipeline/scripts/artifact-coverage.mjs +0 -397
  249. package/skills/paper-pipeline/scripts/artifact-coverage.test.ts +0 -86
  250. package/skills/paper-pipeline/scripts/check-provenance.mjs +0 -184
  251. package/skills/paper-pipeline/scripts/population-map.harness.mjs +0 -135
  252. package/skills/paper-pipeline/scripts/population-map.mjs +0 -188
  253. package/skills/paper-pipeline/scripts/population-map.test.mjs +0 -154
  254. package/skills/paper-pipeline/scripts/provenance.harness.mjs +0 -266
  255. package/skills/paper-pipeline/scripts/round-diff.harness.ts +0 -907
  256. package/skills/paper-pipeline/scripts/round-diff.mjs +0 -583
  257. package/skills/paper-pipeline/scripts/round-diff.test.mjs +0 -159
  258. package/skills/tighten-paper/__snapshots__/structure.test.ts.snap +0 -133
  259. package/skills/tighten-paper/structure.mjs +0 -364
  260. package/skills/tighten-paper/structure.test.ts +0 -117
  261. package/templates/paper/paper.md +0 -4
@@ -1,266 +0,0 @@
1
- /**
2
- * provenance.harness.mjs — what remains from `check-provenance.mjs` after the move, verified from
3
- * both sides. `npx vigiles test .claude/skills/paper-pipeline/scripts/provenance.harness.mjs`.
4
- *
5
- * 🔴 SCOPE NARROWED 2026-08-26. Three findings out of five moved to ESLint rules:
6
- * arm-mismatch → `paper/number-arm-mismatch`
7
- * untraced-number → `paper/number-untraced`
8
- * arm-unlabelled → `paper/number-arm-unlabelled`
9
- * Their cases (including the literal defect from 2026-08-05 and "prediction named its own arm — stay quiet")
10
- * moved to `eslint-rules/paper-registry.harness.mjs`, where they stand alongside runs on real
11
- * papers. Two checks remain here, which do NOT READ the paper BODY AT ALL — they are given only
12
- * a directory, and so cannot be lint rules:
13
- *
14
- * no-numbers-gate missing `repro/paper_numbers.py`;
15
- * numbers-coverage how many quantities are sourced against the ratchet `numbers-grandfathered.txt`.
16
- * This is NOT a finding, but a coverage measurement — and it must always print.
17
- *
18
- * 🔴 WHY GUARDS ARE CHECKED SEPARATELY. Both remaining checks have two exit conditions,
19
- * inherited from the deleted half: no `paper.md` ⇒ silent, no non-empty
20
- * provenance file ⇒ silent. After the move they look unmotivated ("what does prose have to do with it
21
- * if we look at a directory?") — and the first person to "remove redundancy" will change behavior. Assertions
22
- * below pin them as contract.
23
- *
24
- * 🔴 Assertions run at MODULE TOP LEVEL. `vigiles test` imports the file and treats "did not throw"
25
- * as a pass, so a `tests` export or a describe block would report ✓ having run nothing — verified in
26
- * this repo on a file containing only `assert.equal(1, 2)` inside an exported function.
27
- *
28
- * Every fixture is a throwaway in a temp dir. Nothing here reads or writes a real paper.
29
- */
30
- import assert from "node:assert/strict";
31
- import { execFileSync } from "node:child_process";
32
- import {
33
- mkdtempSync,
34
- mkdirSync,
35
- writeFileSync,
36
- rmSync,
37
- realpathSync,
38
- } from "node:fs";
39
- import { tmpdir } from "node:os";
40
- import { join, dirname } from "node:path";
41
- import { fileURLToPath } from "node:url";
42
- import { consumerRoot } from "./consumer.mjs";
43
- const HERE = dirname(fileURLToPath(import.meta.url));
44
-
45
- const ROOT = consumerRoot();
46
- const SCRIPT = join(HERE, "check-provenance.mjs");
47
- const tmp = realpathSync(mkdtempSync(join(tmpdir(), "prov-harness-")));
48
-
49
- /** Run the gate over a fixture and return the set of finding kinds it reported. */
50
- function kinds(dir) {
51
- let out;
52
- try {
53
- out = execFileSync("node", [SCRIPT, dir], {
54
- cwd: ROOT,
55
- encoding: "utf8",
56
- stdio: ["ignore", "pipe", "pipe"],
57
- });
58
- } catch (e) {
59
- out = (e.stdout || "") + (e.stderr || "");
60
- }
61
- return {
62
- set: new Set([...out.matchAll(/^\s*\[([a-z-]+)\]/gm)].map((m) => m[1])),
63
- out,
64
- };
65
- }
66
-
67
- /**
68
- * A paper dir with a body, a provenance file, and (unless `gate: false`) the per-paper numbers gate.
69
- * The gate file must exist or `no-numbers-gate` fires on every fixture and drowns the signal — which
70
- * is itself a finding worth having, and is asserted on its own below.
71
- */
72
- function paper(
73
- name,
74
- { body, notes, gate = true, numbers, grandfathered, noPaper = false },
75
- ) {
76
- const d = join(tmp, name);
77
- mkdirSync(join(d, "repro"), { recursive: true });
78
- if (!noPaper)
79
- writeFileSync(
80
- join(d, "paper.md"),
81
- `## Abstract\n\n${body}\n\n## References\n\n[1] x.\n`,
82
- );
83
- if (notes !== undefined) writeFileSync(join(d, "NOTES.md"), notes);
84
- if (gate)
85
- writeFileSync(
86
- join(d, "repro", "paper_numbers.py"),
87
- "# the per-paper numbers gate\n",
88
- );
89
- if (numbers !== undefined)
90
- writeFileSync(join(d, "repro", "numbers.tsv"), numbers);
91
- if (grandfathered !== undefined)
92
- writeFileSync(join(d, "repro", "numbers-grandfathered.txt"), grandfathered);
93
- return d;
94
- }
95
-
96
- // The provenance rows of the real 2026-08-05 defect: the count of failures is recorded against the
97
- // ANNOTATED arm, in which the extractor's guesses were written in as if an author had authored them.
98
- const NOTES = [
99
- "# where each number comes from",
100
- "- `.all.strict.compiled` = 147 — rules files that compiled",
101
- "- `.all.annotated.failedContradicted` = 22 — files failing admission in the annotated arm",
102
- ].join("\n");
103
-
104
- // ── 1. QUIET ON CORRECT: paper has gate and registry — only coverage measurement, zero findings ──
105
- // First this half: a check that cries on correct input is worse than no check.
106
- {
107
- const d = paper("covered", {
108
- body: "The headline rate is **15.0%** across the corpus.",
109
- notes: NOTES,
110
- numbers: "# id\tvalue\n a\t1\n b\t2\n",
111
- grandfathered: "# still raw\n42\n",
112
- });
113
- const { set, out } = kinds(d);
114
- assert.ok(
115
- !set.has("no-numbers-gate"),
116
- "paper with gate got `no-numbers-gate`:\n" + out,
117
- );
118
- assert.ok(
119
- set.has("numbers-coverage"),
120
- "coverage measurement must always print:\n" + out,
121
- );
122
- assert.match(
123
- out,
124
- /2 quantities are sourced and guarded, 1 are still raw/,
125
- "numbers in coverage measurement do not match — counted registry lines and ratchet lines:\n" +
126
- out,
127
- );
128
- assert.equal(
129
- set.size,
130
- 1,
131
- "after three checks moved, this input must give EXACTLY the coverage measurement:\n" +
132
- out,
133
- );
134
- }
135
-
136
- // ── 2. a paper with no numbers gate at all is told so ──────────────────────────────────
137
- // The coverage question, not a defect in the prose: without `repro/paper_numbers.py` every printed
138
- // figure is a hand-typed digit whose only link to its data file is somebody's memory.
139
- {
140
- const d = paper("no-gate", {
141
- body: "Nothing bolded here.",
142
- notes: NOTES,
143
- gate: false,
144
- });
145
- const { set, out } = kinds(d);
146
- assert.ok(
147
- set.has("no-numbers-gate"),
148
- "a paper with no repro/paper_numbers.py was not told:\n" + out,
149
- );
150
- assert.ok(
151
- !set.has("numbers-coverage"),
152
- "no gate — nothing to cover, measurement must not print:\n" + out,
153
- );
154
- }
155
-
156
- // ── 3. gate exists, no `numbers.tsv` registry — silence, not zero coverage ─────────────────
157
- // The distinction is semantic: «gate was copied but no quantity has been registered in it yet» is NOT the same
158
- // as «0 of 0 sourced», and printing the latter would mean reporting coverage that nobody measured.
159
- {
160
- const d = paper("gate-no-registry", {
161
- body: "Nothing bolded here.",
162
- notes: NOTES,
163
- });
164
- const { set, out } = kinds(d);
165
- assert.equal(set.size, 0, "gate without registry must stay silent:\n" + out);
166
- }
167
-
168
- // ── 4. GUARDS inherited from the deleted half — contract, not a leftover ──────────────────
169
- // 🔴 After the move both look unmotivated: the check looks at a directory, what has prose to do with it?
170
- // But removing them means changing behaviour on papers that have no provenance file at all (most of them)
171
- // — and `no-numbers-gate` will start firing on each one.
172
- {
173
- const d = paper("no-prov", {
174
- body: "Nothing bolded here.",
175
- notes: undefined,
176
- gate: false,
177
- });
178
- const { set, out } = kinds(d);
179
- assert.equal(
180
- set.size,
181
- 0,
182
- "no provenance file at all — script must stay completely silent:\n" + out,
183
- );
184
- // 🔴 Stay silent — yes, but NOT with the voice of success. Before 2026-08-26 both guards printed `clean`, and on
185
- // the real corpus that gave confident green on three papers out of four (all three in .tex).
186
- assert.match(
187
- out,
188
- /SKIPPED/,
189
- "skip must name itself as skip, not as a clean run:\n" + out,
190
- );
191
- assert.doesNotMatch(
192
- out,
193
- /clean/,
194
- "skip must not print the word `clean`:\n" + out,
195
- );
196
- }
197
- {
198
- const d = paper("no-paper", {
199
- body: "",
200
- notes: NOTES,
201
- gate: false,
202
- noPaper: true,
203
- });
204
- const { set, out } = kinds(d);
205
- assert.equal(
206
- set.size,
207
- 0,
208
- "no paper.md/draft.md — script must stay completely silent:\n" + out,
209
- );
210
- assert.match(out, /SKIPPED/, "skip must name itself as skip:\n" + out);
211
- assert.match(
212
- out,
213
- /\.tex/,
214
- "the reason must name WHAT exactly is not covered — otherwise the reader\n" +
215
- "will think it is a breakage:\n" +
216
- out,
217
- );
218
- assert.doesNotMatch(
219
- out,
220
- /clean/,
221
- "skip must not print the word `clean`:\n" + out,
222
- );
223
- }
224
-
225
- // ── 5. clean run SPEAKS that it is clean ──────────────────────────────────────────────────
226
- // Silence and success must not look the same — this is the same class as «0 checks» vs
227
- // «checks passed» in the PR interface.
228
- {
229
- const d = paper("clean-voice", {
230
- body: "Nothing bolded here.",
231
- notes: NOTES,
232
- });
233
- const { out } = kinds(d);
234
- assert.match(out, /clean/, "clean run did not print a verdict:\n" + out);
235
- }
236
-
237
- // ── 6. a registry with no ratchet file: every registered quantity sourced, nothing still raw ──
238
- {
239
- const d = paper("no-ratchet", {
240
- body: "Nothing bolded here.",
241
- notes: NOTES,
242
- numbers: "# id\tvalue\n a\t1\n",
243
- });
244
- const { out } = kinds(d);
245
- assert.match(
246
- out,
247
- /1 quantities are sourced and guarded, 0 are still raw/,
248
- "no numbers-grandfathered.txt must count as zero still raw:\n" + out,
249
- );
250
- }
251
-
252
- // ── 7. no argument: the directory it is run from ──────────────────────────────────────────
253
- {
254
- const d = paper("from-cwd", { body: "Nothing bolded here.", notes: NOTES });
255
- const out = execFileSync("node", [SCRIPT], { cwd: d, encoding: "utf8" });
256
- assert.match(
257
- out,
258
- /clean/,
259
- "run with no argument must judge its cwd:\n" + out,
260
- );
261
- }
262
-
263
- rmSync(tmp, { recursive: true, force: true });
264
- console.log(
265
- "✓ check-provenance: no-numbers-gate fires, coverage is measured, both guards are pinned, clean run speaks",
266
- );