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.
- package/.github/workflows/ci.yml +3 -0
- package/.github/workflows/deadlines.yml +100 -0
- package/README.md +6 -0
- package/dist/adapters/hotcrp/deadlines.d.ts +8 -0
- package/dist/adapters/hotcrp/deadlines.d.ts.map +1 -0
- package/dist/adapters/hotcrp/deadlines.io.d.ts +6 -0
- package/dist/adapters/hotcrp/deadlines.io.d.ts.map +1 -0
- package/dist/adapters/hotcrp/deadlines.io.js +26 -0
- package/dist/adapters/hotcrp/deadlines.io.js.map +1 -0
- package/dist/adapters/hotcrp/deadlines.js +134 -0
- package/dist/adapters/hotcrp/deadlines.js.map +1 -0
- package/dist/adapters/hotcrp/index.d.ts +2 -0
- package/dist/adapters/hotcrp/index.d.ts.map +1 -1
- package/dist/adapters/hotcrp/index.js +2 -0
- package/dist/adapters/hotcrp/index.js.map +1 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +1 -6
- package/dist/build.js.map +1 -1
- package/dist/cli.d.ts +3 -4
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +53 -47
- package/dist/cli.js.map +1 -1
- package/dist/cycle-rules.d.ts +24 -0
- package/dist/cycle-rules.d.ts.map +1 -0
- package/dist/cycle-rules.js +212 -0
- package/dist/cycle-rules.js.map +1 -0
- package/dist/domain/cycle.d.ts +148 -0
- package/dist/domain/cycle.d.ts.map +1 -0
- package/dist/domain/cycle.js +281 -0
- package/dist/domain/cycle.js.map +1 -0
- package/dist/domain/deadline.d.ts +128 -0
- package/dist/domain/deadline.d.ts.map +1 -0
- package/dist/domain/deadline.js +281 -0
- package/dist/domain/deadline.js.map +1 -0
- package/dist/eslint-rules/cycle-stage.d.ts +39 -0
- package/dist/eslint-rules/cycle-stage.d.ts.map +1 -0
- package/dist/eslint-rules/cycle-stage.js +119 -0
- package/dist/eslint-rules/cycle-stage.js.map +1 -0
- package/dist/eslint-rules/latex-language.d.ts.map +1 -1
- package/dist/eslint-rules/latex-language.js +10 -13
- package/dist/eslint-rules/latex-language.js.map +1 -1
- package/dist/eslint-rules/paper-context.js +2 -2
- package/dist/eslint-rules/paper-typography.d.ts.map +1 -1
- package/dist/eslint-rules/paper-typography.js +22 -91
- package/dist/eslint-rules/paper-typography.js.map +1 -1
- package/dist/eslint-rules/papers.d.ts +1 -3
- package/dist/eslint-rules/papers.d.ts.map +1 -1
- package/dist/eslint-rules/papers.js +2 -4
- package/dist/eslint-rules/papers.js.map +1 -1
- package/dist/eslint-rules/tex-build.d.ts.map +1 -1
- package/dist/eslint-rules/tex-build.js +8 -25
- package/dist/eslint-rules/tex-build.js.map +1 -1
- package/dist/init.d.ts +1 -4
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +3 -9
- package/dist/init.js.map +1 -1
- package/dist/lib/markdown.d.ts +5 -81
- package/dist/lib/markdown.d.ts.map +1 -1
- package/dist/lib/markdown.js +5 -116
- package/dist/lib/markdown.js.map +1 -1
- package/dist/lib/paper-config.d.ts.map +1 -1
- package/dist/lib/paper-config.js +2 -2
- package/dist/lib/paper-config.js.map +1 -1
- package/dist/lib/skill-eval-fixture.d.ts +6 -4
- package/dist/lib/skill-eval-fixture.d.ts.map +1 -1
- package/dist/lib/skill-eval-fixture.js +25 -18
- package/dist/lib/skill-eval-fixture.js.map +1 -1
- package/dist/new-paper.d.ts +5 -11
- package/dist/new-paper.d.ts.map +1 -1
- package/dist/new-paper.js +12 -23
- package/dist/new-paper.js.map +1 -1
- package/dist/paper-files.d.ts +4 -4
- package/dist/paper-prose.d.ts +17 -0
- package/dist/paper-prose.d.ts.map +1 -0
- package/dist/paper-prose.js +46 -0
- package/dist/paper-prose.js.map +1 -0
- package/dist/paper-settings.d.ts +7 -0
- package/dist/paper-settings.d.ts.map +1 -1
- package/dist/paper-settings.js +61 -8
- package/dist/paper-settings.js.map +1 -1
- package/dist/ports/venue-deadlines.d.ts +14 -0
- package/dist/ports/venue-deadlines.d.ts.map +1 -0
- package/dist/ports/venue-deadlines.js +2 -0
- package/dist/ports/venue-deadlines.js.map +1 -0
- package/dist/presets.d.ts +12 -0
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +19 -11
- package/dist/presets.js.map +1 -1
- package/dist/structure.d.ts.map +1 -1
- package/dist/structure.js +4 -3
- package/dist/structure.js.map +1 -1
- package/dist/submission.d.ts +8 -0
- package/dist/submission.d.ts.map +1 -1
- package/dist/submission.js +41 -10
- package/dist/submission.js.map +1 -1
- package/dist/tex-requirements.d.ts +3 -0
- package/dist/tex-requirements.d.ts.map +1 -1
- package/dist/tex-requirements.js +19 -0
- package/dist/tex-requirements.js.map +1 -1
- package/dist/tex-venue-rules.d.ts +19 -0
- package/dist/tex-venue-rules.d.ts.map +1 -1
- package/dist/tex-venue-rules.js +69 -0
- package/dist/tex-venue-rules.js.map +1 -1
- package/dist/types.d.ts +0 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/venue-rules.d.ts +10 -0
- package/dist/venue-rules.d.ts.map +1 -1
- package/dist/venue-rules.js +26 -10
- package/dist/venue-rules.js.map +1 -1
- package/docs/configuration.md +146 -20
- package/docs/e2e.md +4 -6
- package/docs/prior-art/paper-folder-scaffolding.md +24 -29
- package/docs/rules/cycle/evidence.md +87 -0
- package/docs/rules/cycle/port-done.md +75 -0
- package/docs/rules/cycle/record.md +152 -0
- package/docs/rules/cycle/stage.md +85 -0
- package/docs/rules/tex/claim-provenance.md +0 -1
- package/docs/rules.md +48 -47
- package/docs/submission.md +5 -0
- package/eslint-rules/cycle-stage.test.mjs +131 -0
- package/eslint-rules/cycle-stage.ts +141 -0
- package/eslint-rules/latex-language.harness.mjs +3 -3
- package/eslint-rules/latex-language.ts +10 -13
- package/eslint-rules/paper-context.ts +2 -2
- package/eslint-rules/paper-typography.test.mjs +17 -85
- package/eslint-rules/paper-typography.ts +22 -98
- package/eslint-rules/papers.harness.mjs +1 -1
- package/eslint-rules/papers.ts +2 -4
- package/eslint-rules/tex-build.harness.mjs +2 -2
- package/eslint-rules/tex-build.ts +8 -25
- package/eslint.config.mjs +41 -2
- package/hooks/hooks.harness.ts +15 -15
- package/hooks/paper-edit-guard.hook.mjs +5 -8
- package/hooks/paper-edit-guard.test.ts +12 -3
- package/hooks/paper-skills-nudge.hook.mjs +2 -3
- package/hooks/paper-skills-nudge.test.ts +13 -3
- package/hooks/paper-status-gates.hook.mjs +2 -2
- package/hooks/paper-status-gates.test.ts +14 -2
- package/lib/markdown.test.mjs +5 -60
- package/lib/markdown.ts +9 -136
- package/lib/paper-config.ts +3 -2
- package/lib/skill-checks.test.mjs +4 -1
- package/lib/skill-eval-fixture.ts +25 -18
- package/package.json +1 -3
- package/presets/aidc.jsonc +24 -3
- package/presets/aidc.md +6 -3
- package/presets/msr.jsonc +25 -1
- package/presets/msr.md +5 -1
- package/presets/realm.jsonc +1 -1
- package/presets/venue-profile.schema.json +39 -0
- package/scripts/check.ts +4 -0
- package/scripts/harness-api.frozen.json +0 -5
- package/scripts/refresh-deadlines.test.ts +431 -0
- package/scripts/refresh-deadlines.ts +387 -0
- package/skills/cold-read-diff/SKILL.md +3 -3
- package/skills/cold-read-diff/SKILL.md.spec.ts +2 -2
- package/skills/grade-paper-writing/SKILL.md +14 -22
- package/skills/grade-paper-writing/SKILL.md.spec.ts +13 -21
- package/skills/grade-paper-writing/__snapshots__/prose-lint.test.ts.snap +40 -26
- package/skills/grade-paper-writing/prose-lint.mjs +108 -278
- package/skills/grade-paper-writing/prose-lint.test.ts +184 -81
- package/skills/paper-adversarial-review/SKILL.md +2 -2
- package/skills/paper-adversarial-review/SKILL.md.spec.ts +1 -1
- package/skills/paper-pipeline/SKILL.md +9 -12
- package/skills/paper-pipeline/SKILL.md.spec.ts +8 -11
- package/skills/paper-pipeline/description-language.eval.mjs +25 -15
- package/skills/paper-pipeline/framing-vs-vocabulary.eval.mjs +25 -15
- package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +25 -15
- package/skills/paper-pipeline/pipeline-firing.eval.mjs +27 -16
- package/skills/paper-pipeline/pipeline-language.eval.mjs +25 -15
- package/skills/paper-pipeline/references/acceptance-gate.md +16 -20
- package/skills/paper-pipeline/references/artifact-checklist.md +4 -5
- package/skills/paper-pipeline/references/pipeline-status-template.md +1 -1
- package/skills/paper-pipeline/scripts/README.md +0 -4
- package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +8 -72
- package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +48 -156
- package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +81 -75
- package/skills/paper-pipeline/scripts/generated-code.harness.mjs +3 -3
- package/skills/paper-status/SKILL.md +2 -2
- package/skills/paper-status/SKILL.md.spec.ts +1 -1
- package/skills/render-paper/SKILL.md +2 -2
- package/skills/render-paper/SKILL.md.spec.ts +1 -1
- package/skills/render-paper/extract-pdf-facts.mjs +2 -2
- package/skills/tighten-paper/SKILL.md +29 -42
- package/skills/tighten-paper/SKILL.md.spec.ts +28 -41
- package/skills/verify-citations/scripts/bib-authors.check.test.mjs +0 -13
- package/skills/verify-citations/scripts/bib-authors.cli.test.ts +23 -11
- package/skills/verify-citations/scripts/bib-authors.mjs +8 -93
- package/skills/verify-citations/scripts/bib-authors.test.mjs +1 -44
- package/src/adapters/hotcrp/cassette/README.md +22 -0
- package/src/adapters/hotcrp/cassette/deadlines-aidc26.html +17 -0
- package/src/adapters/hotcrp/cassette/deadlines-fse2027.html +24 -0
- package/src/adapters/hotcrp/cassette/deadlines-icse2027.html +25 -0
- package/src/adapters/hotcrp/cassette/deadlines-msr2027.html +25 -0
- package/src/adapters/hotcrp/cassette/deadlines-nosuch.html +18 -0
- package/src/adapters/hotcrp/cassette/deadlines-sosp26.html +26 -0
- package/src/adapters/hotcrp/deadlines.io.test.ts +86 -0
- package/src/adapters/hotcrp/deadlines.io.ts +27 -0
- package/src/adapters/hotcrp/deadlines.test.ts +170 -0
- package/src/adapters/hotcrp/deadlines.ts +161 -0
- package/src/adapters/hotcrp/index.ts +2 -0
- package/src/build.harness.mjs +2 -2
- package/src/build.test.ts +1 -1
- package/src/build.ts +1 -6
- package/src/cli.harness.mjs +26 -46
- package/src/cli.test.ts +41 -40
- package/src/cli.ts +57 -58
- package/src/cycle-rules.test.ts +278 -0
- package/src/cycle-rules.ts +273 -0
- package/src/domain/cycle.test.ts +456 -0
- package/src/domain/cycle.ts +432 -0
- package/src/domain/deadline.test.ts +386 -0
- package/src/domain/deadline.ts +472 -0
- package/src/folder-venue-rule.test.ts +1 -1
- package/src/init.test.ts +6 -7
- package/src/init.ts +3 -22
- package/src/lint-fix.test.ts +10 -4
- package/src/new-paper.harness.mjs +9 -28
- package/src/new-paper.test.ts +7 -6
- package/src/new-paper.ts +11 -30
- package/src/paper-files.test.ts +5 -4
- package/src/paper-files.ts +4 -4
- package/src/paper-prose.test.ts +49 -0
- package/src/paper-prose.ts +67 -0
- package/src/paper-settings.test.ts +167 -0
- package/src/paper-settings.ts +85 -11
- package/src/parse-args.test.ts +0 -1
- package/src/ports/venue-deadlines.ts +14 -0
- package/src/presets.test.ts +105 -0
- package/src/presets.ts +43 -10
- package/src/rule-cases.test.ts +144 -0
- package/src/structure.harness.ts +5 -11
- package/src/structure.ts +4 -3
- package/src/submission.test.ts +105 -1
- package/src/submission.ts +73 -12
- package/src/tex-requirements.ts +31 -0
- package/src/tex-venue-rules.test.ts +76 -0
- package/src/tex-venue-rules.ts +85 -0
- package/src/types.ts +0 -2
- package/src/venue-rules.test.ts +82 -0
- package/src/venue-rules.ts +44 -12
- package/fixtures/real-markdown-paper/baseline.json +0 -22
- package/fixtures/real-markdown-paper/baseline.ts +0 -70
- package/skills/paper-pipeline/PIPELINE-MAP.md +0 -381
- package/skills/paper-pipeline/pipeline-firing.baseline.json +0 -270
- package/skills/paper-pipeline/references/sweep-2026-08-09-ideal-pipeline.md +0 -585
- package/skills/paper-pipeline/scripts/artifact-coverage.harness.mjs +0 -492
- package/skills/paper-pipeline/scripts/artifact-coverage.mjs +0 -397
- package/skills/paper-pipeline/scripts/artifact-coverage.test.ts +0 -86
- package/skills/paper-pipeline/scripts/check-provenance.mjs +0 -184
- package/skills/paper-pipeline/scripts/population-map.harness.mjs +0 -135
- package/skills/paper-pipeline/scripts/population-map.mjs +0 -188
- package/skills/paper-pipeline/scripts/population-map.test.mjs +0 -154
- package/skills/paper-pipeline/scripts/provenance.harness.mjs +0 -266
- package/skills/paper-pipeline/scripts/round-diff.harness.ts +0 -907
- package/skills/paper-pipeline/scripts/round-diff.mjs +0 -583
- package/skills/paper-pipeline/scripts/round-diff.test.mjs +0 -159
- package/skills/tighten-paper/__snapshots__/structure.test.ts.snap +0 -133
- package/skills/tighten-paper/structure.mjs +0 -364
- package/skills/tighten-paper/structure.test.ts +0 -117
- package/templates/paper/paper.md +0 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AN ESLint LANGUAGE FOR `.tex` — so that
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* AN ESLint LANGUAGE FOR `.tex` — so that prose rules read a paper's LaTeX source. The spike it
|
|
3
|
+
* grew out of lives in the source corpus as a coverage measurement plus an executable prototype
|
|
4
|
+
* kept as history.
|
|
5
5
|
*
|
|
6
6
|
* ── CONSTRUCTION: two legs ──────────────────────────────────────────────────
|
|
7
7
|
* 1. AST — a flat mdast-like root (heading / code / html / strong) with REAL positions taken
|
|
@@ -9,9 +9,8 @@
|
|
|
9
9
|
* 2. TEXT — a projection of `.tex` into a markdown-like view THAT PRESERVES LENGTHS AND
|
|
10
10
|
* OFFSETS: comments, the preamble, math, service macros and code environments are blanked
|
|
11
11
|
* with spaces; `\section{X}` projects to `## X `, `\texttt{X}` to `` `X` ``.
|
|
12
|
-
* The point: a rule
|
|
13
|
-
*
|
|
14
|
-
* rewritten for LaTeX.
|
|
12
|
+
* The point: a rule reads a plain, heading-marked text and KNOWS NOTHING about LaTeX, while
|
|
13
|
+
* the position of a finding points into the real `.tex`.
|
|
15
14
|
*
|
|
16
15
|
* ── HEADING SYNTHESIS: not decoration, but the repair of seven silent rules ──
|
|
17
16
|
* Measured 2026-08-26 across three real papers: NONE of them has a `Limitations` /
|
|
@@ -47,10 +46,8 @@
|
|
|
47
46
|
* `95 of it, costing 5 and A B`. This matters: a word splitter counts `%` as part of
|
|
48
47
|
* a word, and a plain-language rule counts NUMBERS — so the percent sign detaches from
|
|
49
48
|
* its number. Not fixed: three exceptions would have to be written and pinned.
|
|
50
|
-
* 3.
|
|
51
|
-
*
|
|
52
|
-
* blanking and 1208 after — a 2.5% discrepancy. The discrepancy is ACCEPTED deliberately;
|
|
53
|
-
* the thresholds were not recalibrated.
|
|
49
|
+
* 3. MACRO BLANKING SHIFTS WORD COUNTS. The same place gave 1239 words before macro blanking
|
|
50
|
+
* and 1208 after — a 2.5% discrepancy, ACCEPTED deliberately.
|
|
54
51
|
* 4. ONE FILE AT A TIME. `\input` / `\include` are in OPAQUE: the projection of `paper.tex`
|
|
55
52
|
* does not hold what they pull in. `paperlint lint` hands ESLint each file the paper includes
|
|
56
53
|
* from its body as a file of its own (src/paper-includes.ts), and a file with no `document`
|
|
@@ -546,9 +543,9 @@ export function texToMdast(
|
|
|
546
543
|
// 🔴 AND SYMMETRICALLY ON THE LEFT (found by a run on 2026-08-27, not by reading). A
|
|
547
544
|
// comment that follows a line of text directly, rather than a blank line, comes back from
|
|
548
545
|
// unified-latex with a position starting at the PREVIOUS newline instead of at `%`.
|
|
549
|
-
// Consumers recognise a comment by its first character (
|
|
550
|
-
//
|
|
551
|
-
//
|
|
546
|
+
// Consumers recognise a comment by its first character (`%`), so such a node was not a
|
|
547
|
+
// comment to them: notes above bold appendix lead-ins never bound, and the block-note /
|
|
548
|
+
// block-verdict rules could NEVER fire. Measured:
|
|
552
549
|
// 2 out of 2 blocks were reported as "no note AT ALL" while the notes were right there.
|
|
553
550
|
let cs = pos.start.offset;
|
|
554
551
|
while (cs < ce && src[cs] !== "%") cs++;
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* them under `settings.paperlint.paper`
|
|
9
9
|
* for `paper.tex` and for every file it includes. A rule reads the other files from disk.
|
|
10
10
|
*
|
|
11
|
-
* No setting — a file linted outside `paperlint lint
|
|
12
|
-
*
|
|
11
|
+
* No setting — a file linted outside `paperlint lint` — means a paper of one file: every rule
|
|
12
|
+
* decides on the file it was handed, as it did before.
|
|
13
13
|
*/
|
|
14
14
|
import { readFileSync } from "node:fs";
|
|
15
15
|
import { z } from "zod";
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { describe, expect, it } from "vitest";
|
|
13
13
|
import { ESLint } from "eslint";
|
|
14
|
-
import markdown from "@eslint/markdown";
|
|
15
14
|
import { readFileSync } from "node:fs";
|
|
16
15
|
import { dirname, join } from "node:path";
|
|
17
16
|
import { fileURLToPath } from "node:url";
|
|
@@ -49,16 +48,6 @@ const eslint = (fix = false) =>
|
|
|
49
48
|
linterOptions: { reportUnusedDisableDirectives: "error" },
|
|
50
49
|
rules: RULES,
|
|
51
50
|
},
|
|
52
|
-
{
|
|
53
|
-
files: ["**/*.md"],
|
|
54
|
-
plugins: { markdown, paper: typography },
|
|
55
|
-
language: "markdown/gfm",
|
|
56
|
-
languageOptions: { frontmatter: "yaml" },
|
|
57
|
-
rules: {
|
|
58
|
-
"paper/section-word": "warn",
|
|
59
|
-
"paper/leading-zero": "warn",
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
51
|
],
|
|
63
52
|
});
|
|
64
53
|
|
|
@@ -164,14 +153,6 @@ describe("paper/section-word", () => {
|
|
|
164
153
|
"\\documentclass{article}\n\\crefname{section}{§}{§§}\n\\begin{document}\nSee §5.\n\\end{document}\n";
|
|
165
154
|
expect(ids(await lint(src), "paper/section-word")).toHaveLength(1);
|
|
166
155
|
});
|
|
167
|
-
|
|
168
|
-
it("markdown: `§5` → `Section 5`, code untouched", async () => {
|
|
169
|
-
const src = "See §5 and `§3` here.\n";
|
|
170
|
-
expect(ids(await lint(src, "paper.md"), "paper/section-word")).toHaveLength(
|
|
171
|
-
1,
|
|
172
|
-
);
|
|
173
|
-
expect(await fixed(src, "paper.md")).toBe("See Section 5 and `§3` here.\n");
|
|
174
|
-
});
|
|
175
156
|
});
|
|
176
157
|
|
|
177
158
|
describe("paper/leading-zero", () => {
|
|
@@ -237,17 +218,6 @@ describe("paper/leading-zero", () => {
|
|
|
237
218
|
doc("At $p < 0.05$ and $d=0.21$; cell & 0.037 \\\\ and .5 or 0.25."),
|
|
238
219
|
);
|
|
239
220
|
});
|
|
240
|
-
|
|
241
|
-
it("markdown: prose and a table cell are fixed, code is not", async () => {
|
|
242
|
-
const src =
|
|
243
|
-
"Significant at p<.05.\n\n| task | p |\n| --- | --- |\n| bugfix | .002 |\n\nRun `.25`.\n";
|
|
244
|
-
expect(ids(await lint(src, "paper.md"), "paper/leading-zero")).toHaveLength(
|
|
245
|
-
2,
|
|
246
|
-
);
|
|
247
|
-
expect(await fixed(src, "paper.md")).toBe(
|
|
248
|
-
"Significant at p<0.05.\n\n| task | p |\n| --- | --- |\n| bugfix | 0.002 |\n\nRun `.25`.\n",
|
|
249
|
-
);
|
|
250
|
-
});
|
|
251
221
|
});
|
|
252
222
|
|
|
253
223
|
describe("a finding after a heading written over several lines", () => {
|
|
@@ -290,15 +260,11 @@ describe("paper/leading-zero — a number after ¶ or § is not a decimal (#102)
|
|
|
290
260
|
expect(await fixed(doc(CITE))).toBe(doc(CITE));
|
|
291
261
|
});
|
|
292
262
|
|
|
293
|
-
it("…while a real decimal still fixes,
|
|
263
|
+
it("…while a real decimal still fixes, even beside a cited paragraph", async () => {
|
|
294
264
|
const body = `${CITE}\n\nWe use a threshold of .05 throughout.`;
|
|
295
265
|
expect(await fixed(doc(body))).toBe(
|
|
296
266
|
doc(`${CITE}\n\nWe use a threshold of 0.05 throughout.`),
|
|
297
267
|
);
|
|
298
|
-
const md = "See ¶¶.42 and .44.\n\nSignificant at p<.05.\n";
|
|
299
|
-
expect(await fixed(md, "paper.md")).toBe(
|
|
300
|
-
"See ¶¶.42 and .44.\n\nSignificant at p<0.05.\n",
|
|
301
|
-
);
|
|
302
268
|
});
|
|
303
269
|
});
|
|
304
270
|
|
|
@@ -467,48 +433,6 @@ describe("where visible text comes from — the walk's other paths", () => {
|
|
|
467
433
|
});
|
|
468
434
|
});
|
|
469
435
|
|
|
470
|
-
describe("markdown: offsets that cannot be followed, and rules that are LaTeX-only", () => {
|
|
471
|
-
const mdLint = async (text, rules) => {
|
|
472
|
-
const [res] = await new ESLint({
|
|
473
|
-
cwd: FIX,
|
|
474
|
-
overrideConfigFile: true,
|
|
475
|
-
overrideConfig: [
|
|
476
|
-
{
|
|
477
|
-
files: ["**/*.md"],
|
|
478
|
-
plugins: { markdown, paper: typography },
|
|
479
|
-
language: "markdown/gfm",
|
|
480
|
-
rules,
|
|
481
|
-
},
|
|
482
|
-
],
|
|
483
|
-
}).lintText(text, { filePath: join(FIX, "inline", "paper.md") });
|
|
484
|
-
expect(res.messages.filter((m) => m.fatal)).toEqual([]);
|
|
485
|
-
return res;
|
|
486
|
-
};
|
|
487
|
-
|
|
488
|
-
it("`§` before prose is reported without a fix", async () => {
|
|
489
|
-
const src = "As § above.\n";
|
|
490
|
-
const res = await mdLint(src, { "paper/section-word": "warn" });
|
|
491
|
-
expect(res.messages.map((m) => [m.ruleId, m.column, m.fix])).toEqual([
|
|
492
|
-
["paper/section-word", 4, undefined],
|
|
493
|
-
]);
|
|
494
|
-
});
|
|
495
|
-
|
|
496
|
-
it("a sign or a dot written as a character reference has no source offset: nothing is reported", async () => {
|
|
497
|
-
const res = await mdLint("See § 5 and .25.\n", {
|
|
498
|
-
"paper/section-word": "warn",
|
|
499
|
-
"paper/leading-zero": "warn",
|
|
500
|
-
});
|
|
501
|
-
expect(res.messages).toEqual([]);
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
it("figure-ref-style is LaTeX-only: silent on markdown", async () => {
|
|
505
|
-
const res = await mdLint("Figure~\\ref{a} and Fig.~\\ref{b}.\n", {
|
|
506
|
-
"paper/figure-ref-style": "warn",
|
|
507
|
-
});
|
|
508
|
-
expect(res.messages).toEqual([]);
|
|
509
|
-
});
|
|
510
|
-
});
|
|
511
|
-
|
|
512
436
|
describe("a source code the rules did not expect — the defaults hold", () => {
|
|
513
437
|
const reports = (ruleId, sourceCode) => {
|
|
514
438
|
const out = [];
|
|
@@ -530,12 +454,20 @@ describe("a source code the rules did not expect — the defaults hold", () => {
|
|
|
530
454
|
]);
|
|
531
455
|
});
|
|
532
456
|
|
|
533
|
-
it("
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
457
|
+
it.each(["section-word", "leading-zero", "figure-ref-style"])(
|
|
458
|
+
"%s reads LaTeX only: a source without `raw` (any other language) is not reported",
|
|
459
|
+
(ruleId) => {
|
|
460
|
+
const ast = {
|
|
461
|
+
type: "root",
|
|
462
|
+
children: [
|
|
463
|
+
{
|
|
464
|
+
type: "text",
|
|
465
|
+
value: "§5 and .25",
|
|
466
|
+
position: { start: { offset: 0 }, end: { offset: 10 } },
|
|
467
|
+
},
|
|
468
|
+
],
|
|
469
|
+
};
|
|
470
|
+
expect(reports(ruleId, { text: "§5 and .25", ast })).toEqual([]);
|
|
471
|
+
},
|
|
472
|
+
);
|
|
541
473
|
});
|
|
@@ -41,11 +41,9 @@
|
|
|
41
41
|
* IEEE Editorial Style Manual ("0.25, not .25") require the zero. A regex over the raw source
|
|
42
42
|
* found 22 decimals on a real corpus and none was a defect: every one sat in markup (an option,
|
|
43
43
|
* a column spec, a comment, a listing, a tikz coordinate). So this walks the TREE and keeps only
|
|
44
|
-
* what the reader sees as a number:
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* comments, verbatim/listings/`\lstinline`, tikz, the preamble, the bibliography.
|
|
48
|
-
* Markdown text nodes, table cells included. Not: code, inline code, html, front matter.
|
|
44
|
+
* what the reader sees as a number: prose, inline and display math, table cells, and the text
|
|
45
|
+
* arguments of a known set of text macros. Not: any other macro's arguments, environment
|
|
46
|
+
* arguments, comments, verbatim/listings/`\lstinline`, tikz, the preamble, the bibliography.
|
|
49
47
|
* Each character of a run keeps its source offset, so the finding points at the dot and the fix
|
|
50
48
|
* inserts the zero there. The arXiv lookbehind stays: in `2310.05736` the dot follows a digit.
|
|
51
49
|
* A text macro outside the known set (`\hl{.05}`) is read as a parameter and NOT reported — a
|
|
@@ -62,7 +60,6 @@
|
|
|
62
60
|
import assert from "node:assert/strict";
|
|
63
61
|
import { getParser } from "@unified-latex/unified-latex-util-parse";
|
|
64
62
|
import { takeWhile } from "remeda";
|
|
65
|
-
import type { Nodes } from "mdast";
|
|
66
63
|
import {
|
|
67
64
|
texToMdast,
|
|
68
65
|
type TexArg,
|
|
@@ -108,7 +105,7 @@ export function bibRange(text: string): BibRange | null {
|
|
|
108
105
|
return { start: m.index, end: m.index + whole.length, bodyStart, body };
|
|
109
106
|
}
|
|
110
107
|
|
|
111
|
-
/** A node of
|
|
108
|
+
/** A node of the `.tex` language's projection, as far as its place goes. */
|
|
112
109
|
interface ProjectedNode {
|
|
113
110
|
readonly type: string;
|
|
114
111
|
readonly position?:
|
|
@@ -372,66 +369,6 @@ export function texVisibleRuns(
|
|
|
372
369
|
return texRuns(document ? document.content : root, false, raw, []);
|
|
373
370
|
}
|
|
374
371
|
|
|
375
|
-
// Markdown node types that are not prose.
|
|
376
|
-
const MD_HIDDEN = new Set([
|
|
377
|
-
"code",
|
|
378
|
-
"inlineCode",
|
|
379
|
-
"html",
|
|
380
|
-
"yaml",
|
|
381
|
-
"toml",
|
|
382
|
-
"math",
|
|
383
|
-
"inlineMath",
|
|
384
|
-
]);
|
|
385
|
-
/**
|
|
386
|
-
* The source offset of each character of a text node's value. The value is the source with
|
|
387
|
-
* markdown's escapes (`\*`) and character references (`<`) resolved, so the two are walked
|
|
388
|
-
* side by side; a character whose source cannot be followed gets null (reported, not fixed).
|
|
389
|
-
*/
|
|
390
|
-
function mdOffsets(value: string, src: string, start: number) {
|
|
391
|
-
const offs: (number | null)[] = [];
|
|
392
|
-
let j = start;
|
|
393
|
-
for (const ch of value) {
|
|
394
|
-
if (src[j] === ch) offs.push(j++);
|
|
395
|
-
else if (src[j] === "\\" && src[j + 1] === ch) {
|
|
396
|
-
offs.push(j + 1);
|
|
397
|
-
j += 2;
|
|
398
|
-
} else if (src[j] === "&" && /^&#?\w{1,8};/.test(src.slice(j, j + 10))) {
|
|
399
|
-
offs.push(null);
|
|
400
|
-
j = src.indexOf(";", j) + 1;
|
|
401
|
-
} else {
|
|
402
|
-
offs.push(null);
|
|
403
|
-
j++;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
return offs;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
function mdVisibleRuns(
|
|
410
|
-
node: Nodes | undefined,
|
|
411
|
-
src: string,
|
|
412
|
-
out: Run[] = [],
|
|
413
|
-
): Run[] {
|
|
414
|
-
if (!node || MD_HIDDEN.has(node.type)) return out;
|
|
415
|
-
if (node.type === "text") {
|
|
416
|
-
const start = node.position?.start.offset;
|
|
417
|
-
out.push({
|
|
418
|
-
text: node.value,
|
|
419
|
-
offs:
|
|
420
|
-
typeof start === "number"
|
|
421
|
-
? mdOffsets(node.value, src, start)
|
|
422
|
-
: Array.from(node.value, () => null),
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
|
-
for (const c of "children" in node ? node.children : [])
|
|
426
|
-
mdVisibleRuns(c, src, out);
|
|
427
|
-
return out;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
const visibleRuns = (sourceCode: RuleSourceCode): Run[] =>
|
|
431
|
-
isTex(sourceCode)
|
|
432
|
-
? texVisibleRuns(sourceCode.raw)
|
|
433
|
-
: mdVisibleRuns(sourceCode.ast, sourceCode.text);
|
|
434
|
-
|
|
435
372
|
/** A report at `[from, to)` of the file. */
|
|
436
373
|
const at = (
|
|
437
374
|
context: RuleContext,
|
|
@@ -455,36 +392,22 @@ const SECTION_GLYPH = /§/g;
|
|
|
455
392
|
|
|
456
393
|
function sectionWord(context: RuleContext) {
|
|
457
394
|
const sc = context.sourceCode;
|
|
458
|
-
if (isTex(sc))
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
}
|
|
469
|
-
// A glyph before neither a reference nor a number: reported, not rewritten — there is no
|
|
470
|
-
// single right word for a doubled sign or a sign before prose.
|
|
471
|
-
for (const m of markupMatches(sc, SECTION_GLYPH))
|
|
472
|
-
if (!seen.has(m.index))
|
|
473
|
-
at(context, m.index, m.index + 1, { messageId: "sign" });
|
|
474
|
-
return;
|
|
395
|
+
if (!isTex(sc)) return;
|
|
396
|
+
const seen = new Set();
|
|
397
|
+
for (const m of markupMatches(sc, SECTION_TEX)) {
|
|
398
|
+
seen.add(m.index);
|
|
399
|
+
const word = m[3] ? "Section~" : "Section ";
|
|
400
|
+
const range: [number, number] = [m.index, m.index + m[0].length];
|
|
401
|
+
at(context, range[0], range[1], {
|
|
402
|
+
messageId: "sign",
|
|
403
|
+
fix: (f) => f.replaceTextRange(range, word),
|
|
404
|
+
});
|
|
475
405
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
const [, gap = ""] = m;
|
|
482
|
-
const range: [number, number] = [from, from + 1 + gap.length];
|
|
483
|
-
at(context, range[0], range[1], {
|
|
484
|
-
messageId: "sign",
|
|
485
|
-
...(m[2] ? { fix: (f) => f.replaceTextRange(range, "Section ") } : {}),
|
|
486
|
-
});
|
|
487
|
-
}
|
|
406
|
+
// A glyph before neither a reference nor a number: reported, not rewritten — there is no
|
|
407
|
+
// single right word for a doubled sign or a sign before prose.
|
|
408
|
+
for (const m of markupMatches(sc, SECTION_GLYPH))
|
|
409
|
+
if (!seen.has(m.index))
|
|
410
|
+
at(context, m.index, m.index + 1, { messageId: "sign" });
|
|
488
411
|
}
|
|
489
412
|
|
|
490
413
|
// ── paper/leading-zero ─────────────────────────────────────────────────────────────────
|
|
@@ -508,8 +431,9 @@ const paragraphBefore = (src: string, i: number) => {
|
|
|
508
431
|
|
|
509
432
|
function leadingZero(context: RuleContext) {
|
|
510
433
|
const sc = context.sourceCode;
|
|
511
|
-
|
|
512
|
-
|
|
434
|
+
if (!isTex(sc)) return;
|
|
435
|
+
const src = sc.raw;
|
|
436
|
+
for (const r of texVisibleRuns(src))
|
|
513
437
|
for (const m of r.text.matchAll(BARE_DECIMAL)) {
|
|
514
438
|
const dot = r.offs[m.index];
|
|
515
439
|
if (dot === null || dot === undefined) continue;
|
|
@@ -170,7 +170,7 @@ for (const bad of ["", null, 0, false, [], {}, 42])
|
|
|
170
170
|
const names = Object.keys(sets).sort();
|
|
171
171
|
assert.deepEqual(
|
|
172
172
|
names,
|
|
173
|
-
["all", "
|
|
173
|
+
["all", "pdfFacts", "refFacts", "status", "tex", "venue"],
|
|
174
174
|
"the glob set changed — a consumer's config enumerates these by name, and a renamed or " +
|
|
175
175
|
"dropped key is a block that silently lints nothing",
|
|
176
176
|
);
|
package/eslint-rules/papers.ts
CHANGED
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
*
|
|
42
42
|
* A FILENAME MARKER, no root at all (`**\/paper.tex`).
|
|
43
43
|
* Measured on the first consumer: `paper.tex` ×4 with one outside the corpus,
|
|
44
|
-
* `PIPELINE-STATUS.md` ×6 with two of them test fixtures
|
|
44
|
+
* `PIPELINE-STATUS.md` ×6 with two of them test fixtures.
|
|
45
45
|
* The guard that would consume this is a `deny` at `error` severity; a denial on writing
|
|
46
46
|
* a fixture gets the hook switched off the same day.
|
|
47
47
|
*
|
|
@@ -134,15 +134,13 @@ export function papersRoot(
|
|
|
134
134
|
* The glob sets a consumer declares its blocks on, derived from one root.
|
|
135
135
|
*
|
|
136
136
|
* 🔴 THE SHAPES LIVE HERE, NOT IN THE CONSUMER, and that is the half that keeps a rule from
|
|
137
|
-
* going blind later. `paper.
|
|
137
|
+
* going blind later. `paper.tex`, `_build/*.facts.json`, `PIPELINE-STATUS.md` are
|
|
138
138
|
* this pipeline's CONVENTION; if a rule starts reading a new file, the glob for it must arrive
|
|
139
139
|
* with the rule, in the same package, rather than as a line every consumer has to add by hand
|
|
140
140
|
* to keep up. A hand-maintained list of paths is the thing that rots, and rots silently.
|
|
141
141
|
*/
|
|
142
142
|
export function paperFiles(root: string) {
|
|
143
143
|
return {
|
|
144
|
-
/** Markdown drafts — the two names a paper's prose is allowed to have. */
|
|
145
|
-
md: [`${root}/*/paper.md`, `${root}/*/draft.md`],
|
|
146
144
|
/** The LaTeX source of a paper (not its figures, not its vendored drafts). */
|
|
147
145
|
tex: [`${root}/*/paper.tex`],
|
|
148
146
|
/** The per-paper stage ledger. */
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
* (`284:will be released`, `284:Upon acceptance` + `285:will be made publicly available`).
|
|
36
36
|
*
|
|
37
37
|
* 🔴 AND ONE DISAGREEMENT, WHICH IS NOT IN THE LOGIC BUT IN THE INPUT — and it is the whole
|
|
38
|
-
* gain. The old implementation picked its file by
|
|
39
|
-
*
|
|
38
|
+
* gain. The old implementation picked its file by taking the FIRST `.tex` in `readdirSync`
|
|
39
|
+
* order. Measured 2026-09-07:
|
|
40
40
|
* paper A → a 536-BYTE file of nine `\def`s with venue numbers. The check, added
|
|
41
41
|
* specifically BECAUSE of paper A, was not reading paper A;
|
|
42
42
|
* paper B → a four-month-old draft, one of seven `.tex` files in that directory;
|
|
@@ -3,27 +3,11 @@
|
|
|
3
3
|
* how the prose is written.
|
|
4
4
|
*
|
|
5
5
|
* ── WHY ITS OWN NAMESPACE, AND NOT `paper/*` ────────────────────────────────
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* `paper/future-promise: "off"` on markdown would be an exemption justified by "there is no
|
|
12
|
-
* subject", and that same parity assertion requires the rule to be ALIVE on the other side —
|
|
13
|
-
* i.e. the machinery exists but is built for the opposite direction (off on `.tex`, alive on
|
|
14
|
-
* `.md`).
|
|
15
|
-
*
|
|
16
|
-
* Hence a namespace of its own, the same way an external word-counter gets one: it has a word
|
|
17
|
-
* count markdown does not have, we have a preamble markdown does not have. Where the config
|
|
18
|
-
* enumerates non-`paper/*` rules on `.tex`, the list is CLOSED and `tex/future-promise` is
|
|
19
|
-
* named explicitly, so that a new namespace arrives by decision rather than as a side effect
|
|
20
|
-
* of editing a config.
|
|
21
|
-
*
|
|
22
|
-
* ⚠️ THE MEASUREMENT THIS DECISION RESTS ON (source corpus, 2026-09-06 → 2026-09-07): had the
|
|
23
|
-
* rule been declared on `paper.md` it would produce ZERO there TODAY — not one match of
|
|
24
|
-
* `FUTURE_PROMISE_RE` in the markdown draft of the one paper that has one. So the choice is
|
|
25
|
-
* about semantics, not noise: markdown has no input distinguishing a draft from a shipped
|
|
26
|
-
* build, and the rule would fire on a draft's honest promise.
|
|
6
|
+
* The subject here is the BUILD MODE (`\documentclass[review]`, `printacmref=false`), not the
|
|
7
|
+
* prose the `paper/*` rules read. Hence a namespace of its own, the same way an external
|
|
8
|
+
* word-counter gets one. Where the config enumerates non-`paper/*` rules on `.tex`, the list is
|
|
9
|
+
* CLOSED and `tex/future-promise` is named explicitly, so that a new namespace arrives by
|
|
10
|
+
* decision rather than as a side effect of editing a config.
|
|
27
11
|
*
|
|
28
12
|
* ── THE RULE READS THE FILE FROM DISK, NOT THE PROJECTION. This is load-bearing ──
|
|
29
13
|
* The LaTeX language in `latex-language.mjs` hands rules a PROJECTION of `.tex` into a
|
|
@@ -37,9 +21,8 @@
|
|
|
37
21
|
* run (and in CI) there is no such difference.
|
|
38
22
|
*
|
|
39
23
|
* ── WHAT THE MOVE TO A LINT RULE FIXED BESIDES THE FORM (measured 2026-09-07) ──
|
|
40
|
-
* 🔴 The previous implementation, a script, picked its input like this:
|
|
41
|
-
* `
|
|
42
|
-
* FIRST `.tex` in directory order. Measured across the three papers of the source corpus:
|
|
24
|
+
* 🔴 The previous implementation, a script, picked its input like this:
|
|
25
|
+
* `readdirSync(dir).find((f) => f.endsWith(".tex"))` — the FIRST `.tex` in directory order. Measured across the three papers of the source corpus:
|
|
43
26
|
* paper A → a 536-BYTE file holding nine `\def`s with venue numbers. The check, added
|
|
44
27
|
* specifically BECAUSE of paper A, was not reading paper A;
|
|
45
28
|
* paper B → a four-month-old draft, one of SEVEN `.tex` files in that directory (the real
|
|
@@ -121,7 +104,7 @@ const FRONTMATTER_OVERRIDES = [
|
|
|
121
104
|
// is looking.
|
|
122
105
|
//
|
|
123
106
|
// The scope is DELIBERATELY NARROW, because a noisy gate is a silenced gate:
|
|
124
|
-
// - `.tex` only
|
|
107
|
+
// - `.tex` only: the build mode is read from the preamble;
|
|
125
108
|
// - a review-mode build is EXEMPT, and that is the whole point: during peer review
|
|
126
109
|
// "at camera-ready" is a true and ordinary promise. The defect is only a promise that
|
|
127
110
|
// outlived its own delivery;
|
package/eslint.config.mjs
CHANGED
|
@@ -413,6 +413,40 @@ export const IO_GLOBALS = {
|
|
|
413
413
|
},
|
|
414
414
|
};
|
|
415
415
|
|
|
416
|
+
/**
|
|
417
|
+
* `paperlint lint` NEVER READS THE CLOCK. Its findings are a function of the files and the installed
|
|
418
|
+
* package: the same checkout lints the same tomorrow, and a deadline's distance belongs to a status
|
|
419
|
+
* view or a calendar, not to a gate (a lint that turns red on an untouched tree gets muted). The
|
|
420
|
+
* clock is an effect like the disk, so it is read where effects are — an adapter's `*.io.ts` and the
|
|
421
|
+
* root, `src/cli.ts` — and passed in. Matched on the AST: `Date.now` (called or passed along),
|
|
422
|
+
* `new Date()` and `Date()` without an argument. `new Date(ms)` converts a given time and passes.
|
|
423
|
+
* `toolchain.ts` and `build-engine.ts` keep `Date.now` as a default for their own timing (build and
|
|
424
|
+
* install only, neither runs under `lint`); that legacy I/O moves behind a port in #76.
|
|
425
|
+
*/
|
|
426
|
+
const READS_THE_CLOCK = [
|
|
427
|
+
"MemberExpression[object.name='Date'][property.name='now']",
|
|
428
|
+
"NewExpression[callee.name='Date'][arguments.length=0]",
|
|
429
|
+
"CallExpression[callee.name='Date']",
|
|
430
|
+
].map((selector) => ({
|
|
431
|
+
selector,
|
|
432
|
+
message:
|
|
433
|
+
"Lint never reads the clock: its findings are a function of the files. Take the time as an argument; read it in an adapter's *.io.ts or src/cli.ts.",
|
|
434
|
+
}));
|
|
435
|
+
|
|
436
|
+
/** Every source file the checks are made of, but the places effects are allowed. Same scope as axis B. */
|
|
437
|
+
export const NO_CLOCK = {
|
|
438
|
+
files: ["src/**/*.ts"],
|
|
439
|
+
ignores: [
|
|
440
|
+
...IO_GLOBALS.ignores,
|
|
441
|
+
// Legacy I/O, moves behind a port in #76 (see READS_THE_CLOCK).
|
|
442
|
+
"src/toolchain.ts",
|
|
443
|
+
"src/build-engine.ts",
|
|
444
|
+
],
|
|
445
|
+
rules: {
|
|
446
|
+
"no-restricted-syntax": ["error", AS_UNKNOWN_AS, ...READS_THE_CLOCK],
|
|
447
|
+
},
|
|
448
|
+
};
|
|
449
|
+
|
|
416
450
|
/**
|
|
417
451
|
* EVERY PRESET HAS A CARD, AND EVERY CARD'S RULES TABLE IS THE PRESET'S. Two checks, one block each:
|
|
418
452
|
*
|
|
@@ -486,7 +520,6 @@ const TYPESCRIPT_ONLY = [
|
|
|
486
520
|
"test/e2e/**/*.{js,mjs,cjs}",
|
|
487
521
|
"test/fixtures/layers/src/**/*.{js,mjs,cjs}",
|
|
488
522
|
"fixtures/accepted-papers/**/*.{js,mjs,cjs}",
|
|
489
|
-
"fixtures/real-markdown-paper/**/*.{js,mjs,cjs}",
|
|
490
523
|
"skills/paper-pipeline/scripts/fixtures/**/*.{js,mjs,cjs}",
|
|
491
524
|
"skills/plan-paper-timeline/fixtures/**/*.{js,mjs,cjs}",
|
|
492
525
|
];
|
|
@@ -608,6 +641,7 @@ export default [
|
|
|
608
641
|
},
|
|
609
642
|
// Hexagonal layers, both axes (src/CLAUDE.md): knowledge by element, purity by file category.
|
|
610
643
|
IO_GLOBALS,
|
|
644
|
+
NO_CLOCK,
|
|
611
645
|
layerBoundaries(dirname(fileURLToPath(import.meta.url))),
|
|
612
646
|
// The ratchet: per-file ceilings for the files written before the limits existed. See RATCHET.
|
|
613
647
|
...Object.entries(RATCHET).map(([file, max]) => ({
|
|
@@ -805,7 +839,12 @@ export default [
|
|
|
805
839
|
files: ["eslint-rules/*.ts", "eslint-rules/*.mjs"],
|
|
806
840
|
ignores: ["eslint-rules/*.test.*", "eslint-rules/*.harness.*"],
|
|
807
841
|
rules: {
|
|
808
|
-
"no-restricted-syntax": [
|
|
842
|
+
"no-restricted-syntax": [
|
|
843
|
+
"error",
|
|
844
|
+
AS_UNKNOWN_AS,
|
|
845
|
+
...GIT_IN_A_RULE,
|
|
846
|
+
...READS_THE_CLOCK,
|
|
847
|
+
],
|
|
809
848
|
},
|
|
810
849
|
},
|
|
811
850
|
// LAST, so no block above can replace its `no-restricted-syntax` options for these files (none
|