paperlint 7.2.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 (156) hide show
  1. package/dist/build.d.ts.map +1 -1
  2. package/dist/build.js +1 -6
  3. package/dist/build.js.map +1 -1
  4. package/dist/cli.d.ts +3 -4
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +13 -42
  7. package/dist/cli.js.map +1 -1
  8. package/dist/eslint-rules/latex-language.d.ts.map +1 -1
  9. package/dist/eslint-rules/latex-language.js +10 -13
  10. package/dist/eslint-rules/latex-language.js.map +1 -1
  11. package/dist/eslint-rules/paper-context.js +2 -2
  12. package/dist/eslint-rules/paper-typography.d.ts.map +1 -1
  13. package/dist/eslint-rules/paper-typography.js +22 -91
  14. package/dist/eslint-rules/paper-typography.js.map +1 -1
  15. package/dist/eslint-rules/papers.d.ts +1 -3
  16. package/dist/eslint-rules/papers.d.ts.map +1 -1
  17. package/dist/eslint-rules/papers.js +2 -4
  18. package/dist/eslint-rules/papers.js.map +1 -1
  19. package/dist/eslint-rules/tex-build.d.ts.map +1 -1
  20. package/dist/eslint-rules/tex-build.js +8 -25
  21. package/dist/eslint-rules/tex-build.js.map +1 -1
  22. package/dist/init.d.ts +1 -4
  23. package/dist/init.d.ts.map +1 -1
  24. package/dist/init.js +3 -9
  25. package/dist/init.js.map +1 -1
  26. package/dist/lib/markdown.d.ts +5 -81
  27. package/dist/lib/markdown.d.ts.map +1 -1
  28. package/dist/lib/markdown.js +5 -116
  29. package/dist/lib/markdown.js.map +1 -1
  30. package/dist/lib/paper-config.d.ts.map +1 -1
  31. package/dist/lib/paper-config.js +0 -2
  32. package/dist/lib/paper-config.js.map +1 -1
  33. package/dist/lib/skill-eval-fixture.d.ts +6 -4
  34. package/dist/lib/skill-eval-fixture.d.ts.map +1 -1
  35. package/dist/lib/skill-eval-fixture.js +25 -18
  36. package/dist/lib/skill-eval-fixture.js.map +1 -1
  37. package/dist/new-paper.d.ts +5 -11
  38. package/dist/new-paper.d.ts.map +1 -1
  39. package/dist/new-paper.js +12 -23
  40. package/dist/new-paper.js.map +1 -1
  41. package/dist/paper-files.d.ts +4 -4
  42. package/dist/paper-prose.d.ts +17 -0
  43. package/dist/paper-prose.d.ts.map +1 -0
  44. package/dist/paper-prose.js +46 -0
  45. package/dist/paper-prose.js.map +1 -0
  46. package/dist/structure.d.ts.map +1 -1
  47. package/dist/structure.js +4 -3
  48. package/dist/structure.js.map +1 -1
  49. package/dist/types.d.ts +0 -2
  50. package/dist/types.d.ts.map +1 -1
  51. package/docs/configuration.md +7 -9
  52. package/docs/e2e.md +4 -6
  53. package/docs/prior-art/paper-folder-scaffolding.md +24 -29
  54. package/docs/rules/tex/claim-provenance.md +0 -1
  55. package/docs/rules.md +2 -6
  56. package/eslint-rules/latex-language.harness.mjs +3 -3
  57. package/eslint-rules/latex-language.ts +10 -13
  58. package/eslint-rules/paper-context.ts +2 -2
  59. package/eslint-rules/paper-typography.test.mjs +17 -85
  60. package/eslint-rules/paper-typography.ts +22 -98
  61. package/eslint-rules/papers.harness.mjs +1 -1
  62. package/eslint-rules/papers.ts +2 -4
  63. package/eslint-rules/tex-build.harness.mjs +2 -2
  64. package/eslint-rules/tex-build.ts +8 -25
  65. package/eslint.config.mjs +0 -1
  66. package/hooks/hooks.harness.ts +15 -15
  67. package/hooks/paper-edit-guard.hook.mjs +5 -8
  68. package/hooks/paper-edit-guard.test.ts +12 -3
  69. package/hooks/paper-skills-nudge.hook.mjs +2 -3
  70. package/hooks/paper-skills-nudge.test.ts +13 -3
  71. package/hooks/paper-status-gates.hook.mjs +2 -2
  72. package/hooks/paper-status-gates.test.ts +14 -2
  73. package/lib/markdown.test.mjs +5 -60
  74. package/lib/markdown.ts +9 -136
  75. package/lib/paper-config.ts +0 -2
  76. package/lib/skill-checks.test.mjs +4 -1
  77. package/lib/skill-eval-fixture.ts +25 -18
  78. package/package.json +1 -3
  79. package/presets/realm.jsonc +1 -1
  80. package/scripts/harness-api.frozen.json +0 -5
  81. package/skills/cold-read-diff/SKILL.md +3 -3
  82. package/skills/cold-read-diff/SKILL.md.spec.ts +2 -2
  83. package/skills/grade-paper-writing/SKILL.md +14 -22
  84. package/skills/grade-paper-writing/SKILL.md.spec.ts +13 -21
  85. package/skills/grade-paper-writing/__snapshots__/prose-lint.test.ts.snap +40 -26
  86. package/skills/grade-paper-writing/prose-lint.mjs +108 -278
  87. package/skills/grade-paper-writing/prose-lint.test.ts +184 -81
  88. package/skills/paper-adversarial-review/SKILL.md +2 -2
  89. package/skills/paper-adversarial-review/SKILL.md.spec.ts +1 -1
  90. package/skills/paper-pipeline/SKILL.md +9 -12
  91. package/skills/paper-pipeline/SKILL.md.spec.ts +8 -11
  92. package/skills/paper-pipeline/description-language.eval.mjs +25 -15
  93. package/skills/paper-pipeline/framing-vs-vocabulary.eval.mjs +25 -15
  94. package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +25 -15
  95. package/skills/paper-pipeline/pipeline-firing.eval.mjs +27 -16
  96. package/skills/paper-pipeline/pipeline-language.eval.mjs +25 -15
  97. package/skills/paper-pipeline/references/acceptance-gate.md +16 -20
  98. package/skills/paper-pipeline/references/artifact-checklist.md +4 -5
  99. package/skills/paper-pipeline/references/pipeline-status-template.md +1 -1
  100. package/skills/paper-pipeline/scripts/README.md +0 -4
  101. package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +8 -72
  102. package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +48 -156
  103. package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +81 -75
  104. package/skills/paper-pipeline/scripts/generated-code.harness.mjs +3 -3
  105. package/skills/paper-status/SKILL.md +2 -2
  106. package/skills/paper-status/SKILL.md.spec.ts +1 -1
  107. package/skills/render-paper/SKILL.md +2 -2
  108. package/skills/render-paper/SKILL.md.spec.ts +1 -1
  109. package/skills/render-paper/extract-pdf-facts.mjs +2 -2
  110. package/skills/tighten-paper/SKILL.md +29 -42
  111. package/skills/tighten-paper/SKILL.md.spec.ts +28 -41
  112. package/skills/verify-citations/scripts/bib-authors.check.test.mjs +0 -13
  113. package/skills/verify-citations/scripts/bib-authors.cli.test.ts +23 -11
  114. package/skills/verify-citations/scripts/bib-authors.mjs +8 -93
  115. package/skills/verify-citations/scripts/bib-authors.test.mjs +1 -44
  116. package/src/build.harness.mjs +2 -2
  117. package/src/build.test.ts +1 -1
  118. package/src/build.ts +1 -6
  119. package/src/cli.harness.mjs +26 -46
  120. package/src/cli.test.ts +32 -40
  121. package/src/cli.ts +14 -54
  122. package/src/folder-venue-rule.test.ts +1 -1
  123. package/src/init.test.ts +6 -7
  124. package/src/init.ts +3 -22
  125. package/src/lint-fix.test.ts +10 -4
  126. package/src/new-paper.harness.mjs +9 -28
  127. package/src/new-paper.test.ts +6 -6
  128. package/src/new-paper.ts +11 -30
  129. package/src/paper-files.test.ts +5 -4
  130. package/src/paper-files.ts +4 -4
  131. package/src/paper-prose.test.ts +49 -0
  132. package/src/paper-prose.ts +67 -0
  133. package/src/parse-args.test.ts +0 -1
  134. package/src/structure.harness.ts +5 -11
  135. package/src/structure.ts +4 -3
  136. package/src/types.ts +0 -2
  137. package/fixtures/real-markdown-paper/baseline.json +0 -22
  138. package/fixtures/real-markdown-paper/baseline.ts +0 -70
  139. package/skills/paper-pipeline/PIPELINE-MAP.md +0 -381
  140. package/skills/paper-pipeline/pipeline-firing.baseline.json +0 -270
  141. package/skills/paper-pipeline/references/sweep-2026-08-09-ideal-pipeline.md +0 -585
  142. package/skills/paper-pipeline/scripts/artifact-coverage.harness.mjs +0 -492
  143. package/skills/paper-pipeline/scripts/artifact-coverage.mjs +0 -397
  144. package/skills/paper-pipeline/scripts/artifact-coverage.test.ts +0 -86
  145. package/skills/paper-pipeline/scripts/check-provenance.mjs +0 -184
  146. package/skills/paper-pipeline/scripts/population-map.harness.mjs +0 -135
  147. package/skills/paper-pipeline/scripts/population-map.mjs +0 -188
  148. package/skills/paper-pipeline/scripts/population-map.test.mjs +0 -154
  149. package/skills/paper-pipeline/scripts/provenance.harness.mjs +0 -266
  150. package/skills/paper-pipeline/scripts/round-diff.harness.ts +0 -907
  151. package/skills/paper-pipeline/scripts/round-diff.mjs +0 -583
  152. package/skills/paper-pipeline/scripts/round-diff.test.mjs +0 -159
  153. package/skills/tighten-paper/__snapshots__/structure.test.ts.snap +0 -133
  154. package/skills/tighten-paper/structure.mjs +0 -364
  155. package/skills/tighten-paper/structure.test.ts +0 -117
  156. package/templates/paper/paper.md +0 -4
@@ -1,135 +0,0 @@
1
- /**
2
- * population-map.harness.mjs โ€” the population gate, made visible to the metric, plus the layer
3
- * its own self-test cannot reach. `npx vigiles test .claude/skills/paper-pipeline/scripts/population-map.harness.mjs`.
4
- *
5
- * ๐Ÿ”ด SCOPE NARROWED 2026-08-26. Two findings out of four (`untied`, `undeclared`) moved to ESLint
6
- * rules โ€” `paper/population-untied` and `paper/population-undeclared`, tests for them in
7
- * `eslint-rules/paper-registry.harness.mjs`. Two remain here: `stale` and `badref`, both are about
8
- * THE REGISTRY ITSELF (`repro/populations.tsv`), both have no address in the paper, and so cannot be expressed by ESLint,
9
- * which reports only to the linted file.
10
- *
11
- * The findings themselves are planted in `population-map.test.mjs` (vitest; until #52 a
12
- * self-test script this harness spawned). This file keeps what only the command shows: finding
13
- * paper.md and repro/populations.tsv, and `--flags-only` staying silent on a clean paper โ€” the
14
- * wiring the CI step actually invokes.
15
- */
16
- import assert from "node:assert/strict";
17
- import { execFileSync } from "node:child_process";
18
- import {
19
- mkdtempSync,
20
- mkdirSync,
21
- writeFileSync,
22
- rmSync,
23
- realpathSync,
24
- } from "node:fs";
25
- import { tmpdir } from "node:os";
26
- import { join, dirname } from "node:path";
27
- import { fileURLToPath } from "node:url";
28
- import { consumerRoot } from "./consumer.mjs";
29
- const HERE = dirname(fileURLToPath(import.meta.url));
30
-
31
- const ROOT = consumerRoot();
32
- const SCRIPT = join(HERE, "population-map.mjs");
33
- const tmp = realpathSync(mkdtempSync(join(tmpdir(), "popmap-harness-")));
34
-
35
- /** Run a node script; never throws on a non-zero exit. */
36
- function run(argv) {
37
- try {
38
- return {
39
- code: 0,
40
- out: execFileSync("node", argv, {
41
- cwd: ROOT,
42
- encoding: "utf8",
43
- stdio: ["ignore", "pipe", "pipe"],
44
- }),
45
- };
46
- } catch (e) {
47
- return { code: e.status ?? 1, out: (e.stdout || "") + (e.stderr || "") };
48
- }
49
- }
50
-
51
- /** A paper dir carrying `body`, and optionally a population registry. */
52
- function paper(name, body, tsv) {
53
- const d = join(tmp, name);
54
- mkdirSync(join(d, "repro"), { recursive: true });
55
- writeFileSync(
56
- join(d, "paper.md"),
57
- `## Abstract\n\n${body}\n\n## References\n\n[1] x.\n`,
58
- );
59
- if (tsv !== undefined)
60
- writeFileSync(join(d, "repro", "populations.tsv"), tsv);
61
- return d;
62
- }
63
-
64
- const REG = [
65
- "id\tprinted\trelation\trelated_to\tgloss",
66
- "broad\t1,921\troot\t\tthe corpus",
67
- "census\t134\tdisjoint\tbroad\ta separate sample",
68
- ].join("\n");
69
-
70
- // โ”€โ”€ 2. the CLI finds the paper and the registry, and fires on a stale registry row โ”€โ”€โ”€โ”€โ”€
71
- // ๐Ÿ”ด FIXTURE REWRITTEN 2026-08-26. Before that it planted an UNLINKED POPULATION, and that
72
- // finding moved to `paper/population-untied` โ€” meaning the old ยซdirtyยป input became clean, and
73
- // the block would silently stop checking the argv โ†’ findings() wiring while staying green. Exactly that
74
- // class of failure this file was written for.
75
- {
76
- const dirty = paper(
77
- "cli-stale",
78
- "The resolver ran over all 1,921 repositories at pinned commits.",
79
- REG,
80
- );
81
- const r = run([SCRIPT, dirty, "--flags-only"]);
82
- assert.match(
83
- r.out,
84
- /134 \(census\) is declared and the body no longer prints it/,
85
- "the CLI did not report a stale registry row โ€” the self-test proves findings() sees it, so what " +
86
- "is broken is the path from argv to findings():\n" +
87
- r.out,
88
- );
89
- }
90
-
91
- // โ”€โ”€ 3. โ€ฆand says NOTHING when every declared population is printed โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
92
- // The direction that matters more. A gate that fires on a paper which already obeys the rule gets
93
- // muted the same week, and a muted gate is indistinguishable from an absent one.
94
- {
95
- const clean = paper(
96
- "cli-clean",
97
- "The resolver ran over all 1,921 repositories at pinned commits, and a census of 134\nrepositories is a separate sample from those 1,921, overlapping in seven.",
98
- REG,
99
- );
100
- const r = run([SCRIPT, clean, "--flags-only"]);
101
- assert.equal(
102
- r.out.trim(),
103
- "",
104
- "--flags-only printed something about a paper whose registry matches its body:\n" +
105
- r.out,
106
- );
107
- }
108
-
109
- // โ”€โ”€ 4. KNOWN GAP, asserted so it stays visible โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
110
- // ๐Ÿ”ด A paper with NO repro/populations.tsv exits 0 in silence, which reads as a pass for
111
- // draft-paper. So a paper that never declared its populations scores
112
- // identically to one that declared them and tied every one. Absence is invisible again โ€” the same
113
- // shape artifact-coverage.mjs was written to close for the released bundle.
114
- //
115
- // This asserts the CURRENT behaviour. When population-map learns to say "this paper has no
116
- // registry", this assertion flips and must be updated; writing the gap down is how it stops being
117
- // mistaken for coverage.
118
- {
119
- const bare = paper(
120
- "cli-no-registry",
121
- "We measured 1,921 repositories and a census of 134 repositories.",
122
- undefined,
123
- );
124
- const r = run([SCRIPT, bare, "--flags-only"]);
125
- assert.equal(
126
- r.out.trim(),
127
- "",
128
- "population-map now says something about a paper with no registry โ€” good; update this assertion",
129
- );
130
- }
131
-
132
- rmSync(tmp, { recursive: true, force: true });
133
- console.log(
134
- "โœ“ population-map: self-test owned (7 cases), CLI wiring fires on a stale row and stays quiet, 1 known gap recorded",
135
- );
@@ -1,188 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * population-map.mjs โ€” does the population registry keep its link to the body of the paper?
4
- *
5
- * (Before 2026-08-26 this file answered the question "can the reader tell WHICH SET each number
6
- * counts" in full; half of the answer now lives in ESLint rules โ€” see below.)
7
- *
8
- * node population-map.mjs <paper-dir> report
9
- * node population-map.mjs <paper-dir> --flags-only for hooks and pre-commit
10
- *
11
- * ๐Ÿ”ด WHY THIS EXISTS. `papers/CLAUDE.md` has carried this as writing rule #3 since 2026-08-05:
12
- *
13
- * "A number arrives together with what it counts, and out of what. [โ€ฆ] If two numbers come
14
- * from different experiments โ€” say so IN THE SAME SENTENCE."
15
- *
16
- * It was written after a cold reader gave up tracking the abstract's populations halfway through.
17
- * It is prose. Nothing enforced it. On 2026-08-06 the author read the finished paper and said he
18
- * could not follow his own ยง4 โ€” and the cause was not the count of measurements, it was that ยง4
19
- * moves between TWO SAMPLES THAT BARELY OVERLAP (a targeted census of 134 repositories and a broad
20
- * corpus of 1,921; seven repositories are in both) and never says so. ยง4.1's rate and ยง4.3's rate
21
- * read as if one replicated the other. They cannot: they are different populations.
22
- *
23
- * That is this repository's own thesis turned on its own manuscript โ€” a rule written in a rules
24
- * file, with no mechanism behind it, that therefore did nothing for a year of edits. So the rule
25
- * gets a leg.
26
- *
27
- * ๐Ÿ”ด TWO OF THE FOUR FINDINGS MOVED INTO ESLint RULES ON 2026-08-26. What is left here is only
28
- * what speaks about THE REGISTRY ITSELF, not about the paper:
29
- *
30
- * stale a registry row for a quantity the body NO LONGER PRINTS. The registry shrinks together
31
- * with the paper and does not rot.
32
- * badref a row points at a `related_to` that is not a row anywhere.
33
- *
34
- * Both are inseparable from the file `repro/populations.tsv` and HAVE NO ADDRESS IN THE PAPER:
35
- * `stale` by definition speaks about a number that is not in the paper, and `badref` about two
36
- * fields of one TSV row. An ESLint rule can only report into the file being linted, so moving them
37
- * would mean pointing a finger at the paper while talking about a different file.
38
- *
39
- * MOVED INTO `eslint-rules/paper-registry.mjs` (the rules `paper/population-untied` and
40
- * `paper/population-undeclared`):
41
- * untied โ€” a population is not named IN THE SAME SENTENCE as the one it is derived from;
42
- * undeclared โ€” a number is printed in the shape of a population, and no registry row declares it.
43
- * Both speak about a place IN THE PAPER, and for them the registry is configuration, exactly like a
44
- * venue profile for `pdf/profile`. Parity was proved before the deletion (the real paper + 17
45
- * fixtures), the write-up is in `the author's private research notes`.
46
- *
47
- * Advisory.
48
- */
49
- import { readFileSync, existsSync } from "node:fs";
50
- import { join } from "node:path";
51
- import { headings as mdHeadings, requireMarkdown } from "#lib/markdown";
52
- import { isMain } from "./consumer.mjs";
53
-
54
- // Markup is parsed with a parser (`CLAUDE.md`, 2026-08-11). We fail rather than degrade: without
55
- // the body boundaries `bodyOf()` would return an empty string, and out of an empty string this
56
- // check concludes "not a single registry number is printed", that is, ALL rows stale โ€” a confident
57
- // list of findings about a paper nobody read.
58
- requireMarkdown();
59
-
60
- /** The body is everything before the bibliography; appendices sit after it in this paper and are
61
- * not under the page limit, so a relation stated only there does not reach the reader who stops
62
- * at the References. That asymmetry is the whole point of checking the body separately. */
63
- export function bodyOf(md) {
64
- const stripped = md.replace(/<!--[\s\S]*?-->/g, "");
65
- // 2026-08-11: both body boundaries come from the parser. `/^##\s+References\s*$/m` opened and
66
- // closed the body on hashes at the start of a line โ€” including hashes inside a ```-block, and this
67
- // paper quotes whole chunks of other people's papers. The `-1` convention and the `< 0`
68
- // comparisons are left as they were.
69
- const at = (re) => {
70
- const h = mdHeadings(stripped).find(
71
- (x) => x.depth === 2 && re.test(x.text),
72
- );
73
- return h ? h.offset : -1;
74
- };
75
- const cut = at(/^References$/u);
76
- const start = at(/^Abstract$/u);
77
- return stripped.slice(start < 0 ? 0 : start, cut < 0 ? stripped.length : cut);
78
- }
79
-
80
- const asLiteral = (n) => n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
81
- /** A printed quantity, not a substring of a longer one: 189 must not match inside 1,189 or 1893.
82
- * The trailing guard must still allow ORDINARY PUNCTUATION after the number โ€” "of the 1,921, we
83
- * could enumerate 189" and a sentence-final "...over all 1,921." are both the number being
84
- * printed. Forbidding any following `.` or `,` silently loses exactly the positions a summary
85
- * figure lands in; only a DIGIT after the separator makes it part of a longer quantity. */
86
- const printed = (n) => new RegExp(`(?<![\\d,.])${asLiteral(n)}(?!\\d|[,.]\\d)`);
87
-
88
- export function parseRegistry(tsv) {
89
- const rows = [];
90
- for (const line of tsv.split("\n")) {
91
- if (!line.trim() || line.startsWith("#")) continue;
92
- const [id, print, relation, relatedTo, gloss] = line
93
- .split("\t")
94
- .map((c) => c.trim()); // split yields strings; a missing column is `undefined` in the destructuring
95
- if (!id || id === "id") continue;
96
- rows.push({ id, print, relation, relatedTo, gloss });
97
- }
98
- return rows;
99
- }
100
-
101
- export function findings(md, tsv) {
102
- const body = bodyOf(md);
103
- const reg = parseRegistry(tsv);
104
- const byId = new Map(reg.map((r) => [r.id, r]));
105
- const out = [];
106
-
107
- for (const row of reg) {
108
- // A row with no printed quantity has nothing to look for in the body. It used to reach
109
- // `printed(undefined)` and crash the checker; it is a defect of the TSV, reported as one.
110
- if (!row.print) {
111
- out.push({ kind: "incomplete", row });
112
- continue;
113
- }
114
- const appears = printed(row.print).test(body);
115
- if (!appears) {
116
- // A registry row for a quantity the body no longer prints. Same discipline as the
117
- // grandfathered numbers list: the registry shrinks when the paper does, and never rots.
118
- if (row.relation !== "retired") out.push({ kind: "stale", row });
119
- continue;
120
- }
121
- if (row.relation === "root" || row.relation === "external") continue;
122
- // The order is preserved from the original: `untied` was computed AFTER this check and moved
123
- // into `paper/population-untied`; the dangling reference stayed here because it is about a TSV
124
- // row.
125
- if (!byId.get(row.relatedTo)) out.push({ kind: "badref", row });
126
- }
127
- return out;
128
- }
129
-
130
- /* Importable above this line; the CLI only runs when this file is the entry point, so the tests
131
- * can exercise findings() without the argv handling firing. */
132
- if (!isMain(import.meta.url)) {
133
- // imported โ€” nothing to do
134
- } else main();
135
-
136
- function main() {
137
- const args = process.argv.slice(2);
138
- const dir = args.find((a) => !a.startsWith("--"));
139
- const flagsOnly = args.includes("--flags-only");
140
- if (!dir) {
141
- console.error("usage: node population-map.mjs <paper-dir> [--flags-only]");
142
- process.exit(0);
143
- }
144
-
145
- const paperPath = ["paper.md", "draft.md"]
146
- .map((f) => join(dir, f))
147
- .find(existsSync);
148
- const regPath = join(dir, "repro", "populations.tsv");
149
- // Same silent-skip class as artifact-coverage.mjs โ€” see the note there (measured 2026-08-25).
150
- if (!paperPath || !existsSync(regPath)) {
151
- const why = !paperPath
152
- ? "no paper.md/draft.md โ€” this checker reads markdown only, a .tex paper is NOT covered"
153
- : `no registry at ${regPath}`;
154
- console.error(`โญ๏ธ population map SKIPPED for ${dir} โ€” ${why}.`);
155
- process.exit(0);
156
- }
157
-
158
- const found = findings(
159
- readFileSync(paperPath, "utf8"),
160
- readFileSync(regPath, "utf8"),
161
- );
162
-
163
- if (!found.length) {
164
- if (!flagsOnly)
165
- console.log(
166
- "population map: the registry matches the body โ€” no stale rows, no dangling related_to.",
167
- );
168
- process.exit(0);
169
- }
170
-
171
- console.log(`population map โ€” ${found.length} finding(s) in ${paperPath}`);
172
- for (const f of found) {
173
- if (f.kind === "stale") {
174
- console.log(
175
- ` ยท ${f.row.print} (${f.row.id}) is declared and the body no longer prints it โ€” drop the row or mark it retired`,
176
- );
177
- } else if (f.kind === "badref") {
178
- console.log(
179
- ` ๐Ÿ”ด ${f.row.id} says it relates to "${f.row.relatedTo}", which is not a row in populations.tsv`,
180
- );
181
- } else {
182
- console.log(
183
- ` ๐Ÿ”ด ${f.row.id} has no printed quantity โ€” the row cannot be checked against the body`,
184
- );
185
- }
186
- }
187
- process.exit(0);
188
- }
@@ -1,154 +0,0 @@
1
- /**
2
- * population-map.mjs โ€” the registry's own findings (`stale`, `badref`), planted in both directions,
3
- * and the command around them. Until #52 the seven cases below were `population-map.selftest.mjs`,
4
- * a script the harness spawned and whose "N passed" line it parsed; they are vitest cases now.
5
- *
6
- * ๐Ÿ”ด ONE CASE WAS DELETED BEFORE THE MOVE, NOT MOVED, and that is worth remembering: it ran a
7
- * HAND-COPIED COPY of the checker's regex over a string. A change to the real `printed()` cannot
8
- * touch such a test by construction. The property is pinned by behaviour instead (case 6).
9
- */
10
- import assert from "node:assert/strict";
11
- import { dirname, join } from "node:path";
12
- import { fileURLToPath } from "node:url";
13
- import { test } from "vitest";
14
- import { runNode, useTempDir, writeTree } from "../../../test/support.ts";
15
- import { bodyOf, findings } from "./population-map.mjs";
16
-
17
- const SCRIPT = join(
18
- dirname(fileURLToPath(import.meta.url)),
19
- "population-map.mjs",
20
- );
21
- const doc = (bodyLines) =>
22
- `## Abstract\n\n${bodyLines}\n\n## References\n\n[1] x.\n`;
23
- const kinds = (md, reg) =>
24
- findings(md, reg).map((f) => `${f.kind}:${f.row.print}`);
25
- const REG = [
26
- "id\tprinted\trelation\trelated_to\tgloss",
27
- "broad\t1,921\troot\t\tthe corpus",
28
- "sub\t1,836\tsubset\tbroad\tthose it could read",
29
- ].join("\n");
30
- const HEAD = "id\tprinted\trelation\trelated_to\tgloss\n";
31
-
32
- // 1. The direction that matters more: a checker firing on a paper that already obeys gets muted.
33
- test("a registry that matches the body is silent", () => {
34
- assert.deepEqual(
35
- kinds(
36
- doc(
37
- "The resolver ran over all 1,921 repositories, and read 1,836 of them.",
38
- ),
39
- REG,
40
- ),
41
- [],
42
- );
43
- });
44
-
45
- // 2. A row for a quantity the paper no longer prints is a claim nobody can check.
46
- test("a stale registry row fires", () => {
47
- assert.deepEqual(
48
- kinds(doc("The resolver ran over all 1,921 repositories."), REG),
49
- ["stale:1,836"],
50
- );
51
- });
52
-
53
- // 3. `retired` is what keeps a set declared after it was cut from the paper.
54
- test("a retired row does not fire", () => {
55
- assert.deepEqual(
56
- kinds(
57
- doc("The resolver ran over all 1,921 repositories, and read 1,836."),
58
- `${REG}\ngold\t7,310\tretired\t\ta public gold set`,
59
- ),
60
- [],
61
- );
62
- });
63
-
64
- // 4. A relation pointing at an id that is not a row โ€” a defect of the TSV alone.
65
- test("a dangling related_to fires", () => {
66
- assert.deepEqual(
67
- kinds(
68
- doc("We read 1,836 repositories."),
69
- `${HEAD}sub\t1,836\tsubset\tnosuch\tx`,
70
- ),
71
- ["badref:1,836"],
72
- );
73
- });
74
-
75
- // 5. A row whose number is absent is `stale`, never also `badref`.
76
- test("a row that is absent is stale, not badref", () => {
77
- assert.deepEqual(
78
- kinds(doc("Nothing numeric here."), `${HEAD}sub\t1,836\tsubset\tnosuch\tx`),
79
- ["stale:1,836"],
80
- );
81
- });
82
-
83
- // 6. `printed()` boundaries through behaviour: 189 is not printed inside 1,189.
84
- test('189 is not "printed" inside 1,189', () => {
85
- assert.deepEqual(
86
- kinds(
87
- doc("A follow-up read covered 1,189 records, of all 1,921."),
88
- `${HEAD}broad\t1,921\troot\t\tthe corpus\nspread\t189\tsubset\tbroad\tenumerable`,
89
- ),
90
- ["stale:189"],
91
- );
92
- });
93
-
94
- // 7. The body stops at the bibliography; with no Abstract it starts at the top, with no
95
- // References it runs to the end.
96
- test("bodyOf: from Abstract to References, or the whole text when either is missing", () => {
97
- assert.deepEqual(
98
- [
99
- bodyOf(doc("nothing here")).includes("[1] x."),
100
- bodyOf("pre\n\n## References\n\n[1] x.\n"),
101
- bodyOf("## Abstract\n\nall of it\n"),
102
- ],
103
- [false, "pre\n\n", "## Abstract\n\nall of it\n"],
104
- );
105
- });
106
-
107
- test("a registry row with no printed quantity is reported, not a crash", () => {
108
- assert.deepEqual(
109
- findings(doc("x"), `${HEAD}short\n`).map((f) => [f.kind, f.row.id]),
110
- [["incomplete", "short"]],
111
- );
112
- });
113
-
114
- // โ”€โ”€ the command โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
115
- const root = useTempDir("population-map-");
116
- const paper = (name, body, tsv) =>
117
- writeTree(join(root, name), {
118
- "paper.md": doc(body),
119
- ...(tsv === undefined ? {} : { "repro/populations.tsv": tsv }),
120
- });
121
-
122
- test("the command: usage, a paper without markdown, a clean registry, and every finding kind", () => {
123
- assert.deepEqual(runNode(SCRIPT, []), {
124
- status: 0,
125
- stdout: "",
126
- stderr: "usage: node population-map.mjs <paper-dir> [--flags-only]\n",
127
- });
128
- const tex = writeTree(join(root, "tex"), { "paper.tex": "x" });
129
- assert.match(
130
- runNode(SCRIPT, [tex]).stderr,
131
- /population map SKIPPED for .* โ€” no paper\.md\/draft\.md โ€” this checker reads markdown only/,
132
- );
133
- const clean = paper(
134
- "clean",
135
- "All 1,921 of them.",
136
- `${HEAD}broad\t1,921\troot\t\tx`,
137
- );
138
- assert.equal(
139
- runNode(SCRIPT, [clean]).stdout,
140
- "population map: the registry matches the body โ€” no stale rows, no dangling related_to.\n",
141
- );
142
- assert.equal(runNode(SCRIPT, [clean, "--flags-only"]).stdout, "");
143
- const bad = paper(
144
- "bad",
145
- "We read 1,836.",
146
- `${HEAD}sub\t1,836\tsubset\tnosuch\tx\nold\t7,310\tsubset\tsub\tx\nshort\n`,
147
- );
148
- assert.deepEqual(runNode(SCRIPT, [bad]).stdout.split("\n").slice(1), [
149
- ' ๐Ÿ”ด sub says it relates to "nosuch", which is not a row in populations.tsv',
150
- " ยท 7,310 (old) is declared and the body no longer prints it โ€” drop the row or mark it retired",
151
- " ๐Ÿ”ด short has no printed quantity โ€” the row cannot be checked against the body",
152
- "",
153
- ]);
154
- });