paperlint 2.0.0 → 2.1.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 +4 -4
- package/CLAUDE.md +3 -3
- package/CONTRIBUTING.md +21 -0
- package/LICENSE +1 -1
- package/README.md +81 -24
- package/action.harness.mjs +10 -10
- package/action.mutations.mjs +3 -3
- package/action.yml +11 -11
- package/bin/{rpp.mjs → paperlint.mjs} +2 -2
- package/dist/adapters/banal/invocation.js +3 -3
- package/dist/adapters/banal/invocation.js.map +1 -1
- package/dist/adapters/banal/run.d.ts +1 -1
- package/dist/adapters/banal/run.js +1 -1
- package/dist/adapters/banal/run.js.map +1 -1
- package/dist/adapters/banal/settings.d.ts +4 -4
- package/dist/adapters/banal/settings.d.ts.map +1 -1
- package/dist/adapters/banal/settings.js +4 -4
- package/dist/adapters/banal/settings.js.map +1 -1
- package/dist/adapters/banal/xml.js +1 -1
- package/dist/adapters/banal/xml.js.map +1 -1
- package/dist/adapters/curl/download.io.d.ts.map +1 -1
- package/dist/adapters/curl/download.io.js +2 -2
- package/dist/adapters/curl/download.io.js.map +1 -1
- package/dist/build-engine.d.ts +1 -1
- package/dist/build-engine.d.ts.map +1 -1
- package/dist/build-engine.js +6 -3
- package/dist/build-engine.js.map +1 -1
- package/dist/build.d.ts +1 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +5 -4
- package/dist/build.js.map +1 -1
- package/dist/cli.d.ts +29 -30
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +174 -98
- package/dist/cli.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +21 -6
- package/dist/doctor.js.map +1 -1
- package/dist/engine.d.ts +2 -2
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +1 -1
- package/dist/facts-file.d.ts +33 -9
- package/dist/facts-file.d.ts.map +1 -1
- package/dist/facts-file.js +117 -17
- package/dist/facts-file.js.map +1 -1
- package/dist/hooks-settings.d.ts +0 -18
- package/dist/hooks-settings.d.ts.map +1 -1
- package/dist/hooks-settings.js +70 -42
- package/dist/hooks-settings.js.map +1 -1
- package/dist/init.d.ts +13 -10
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +74 -42
- package/dist/init.js.map +1 -1
- package/dist/link-skills.js +2 -2
- package/dist/link-skills.js.map +1 -1
- package/dist/new-paper.d.ts +2 -2
- package/dist/new-paper.d.ts.map +1 -1
- package/dist/new-paper.js +17 -2
- package/dist/new-paper.js.map +1 -1
- package/dist/paper-settings.d.ts +68 -0
- package/dist/paper-settings.d.ts.map +1 -0
- package/dist/paper-settings.js +144 -0
- package/dist/paper-settings.js.map +1 -0
- package/dist/presets.d.ts +84 -0
- package/dist/presets.d.ts.map +1 -0
- package/dist/presets.js +186 -0
- package/dist/presets.js.map +1 -0
- package/dist/rules-config.d.ts +8 -1
- package/dist/rules-config.d.ts.map +1 -1
- package/dist/rules-config.js +13 -8
- package/dist/rules-config.js.map +1 -1
- package/dist/structure.d.ts.map +1 -1
- package/dist/structure.js +2 -1
- package/dist/structure.js.map +1 -1
- package/dist/tex-requirements.d.ts +63 -7
- package/dist/tex-requirements.d.ts.map +1 -1
- package/dist/tex-requirements.js +91 -24
- package/dist/tex-requirements.js.map +1 -1
- package/dist/toolchain.d.ts +2 -2
- package/dist/toolchain.d.ts.map +1 -1
- package/dist/toolchain.js +8 -8
- package/dist/toolchain.js.map +1 -1
- package/dist/types.d.ts +4 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/venue-rules.d.ts +109 -0
- package/dist/venue-rules.d.ts.map +1 -0
- package/dist/venue-rules.js +387 -0
- package/dist/venue-rules.js.map +1 -0
- package/docs/configuration.md +73 -17
- package/docs/e2e.md +3 -4
- package/docs/install.md +11 -3
- package/docs/optional-rules.md +26 -25
- package/docs/rules.md +132 -3
- package/docs/toolchain.md +5 -5
- package/eslint-rules/paper-typography.mjs +1 -1
- package/eslint-rules/paper-typography.mutations.mjs +1 -1
- package/eslint-rules/papers.mjs +6 -2
- package/eslint-rules/pdf-last-page-balance.harness.mjs +5 -3
- package/eslint-rules/pdf-last-page-balance.mjs +23 -2
- package/fixtures/build-e2e/acmart/paperlint.json +1 -0
- package/fixtures/build-e2e/guards/paper.tex +1 -1
- package/fixtures/pdf-facts/README.md +1 -1
- package/fixtures/real-markdown-paper/baseline.json +1 -1
- package/fixtures/real-markdown-paper/baseline.mjs +2 -2
- package/fixtures/toolchain-mirror/install-tl +2 -2
- package/fixtures/toolchain-mirror/stub-pdflatex +1 -1
- package/fixtures/toolchain-mirror/stub-tlmgr +1 -1
- package/hooks/hooks.harness.mjs +1 -1
- package/lib/paper-config.d.mts +7 -0
- package/lib/paper-config.harness.mjs +3 -3
- package/lib/paper-config.mjs +34 -3
- package/lib/skill-trigger-cases.harness.mjs +1 -1
- package/package.json +4 -4
- package/plugin/hooks/hooks.json +3 -3
- package/scripts/check.mjs +1 -1
- package/scripts/harness-api.frozen.json +1 -1
- package/scripts/harness-api.test.ts +2 -2
- package/scripts/layer-legacy.frozen.json +1 -1
- package/scripts/mutation-batteries-frozen.harness.mjs +1 -1
- package/scripts/mutation-batteries.frozen.json +2 -2
- package/scripts/release-config.test.ts +1 -1
- package/skills/find-venue/SKILL.md +5 -1
- package/skills/find-venue/SKILL.md.spec.ts +4 -0
- package/skills/paper-pipeline/PIPELINE-MAP.md +14 -4
- package/skills/paper-pipeline/SKILL.md +4 -3
- package/skills/paper-pipeline/SKILL.md.spec.ts +3 -2
- package/skills/paper-pipeline/description-language.eval.mjs +1 -1
- package/skills/paper-pipeline/framing-vs-vocabulary.eval.mjs +1 -1
- package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +2 -2
- package/skills/paper-pipeline/pipeline-firing.eval.mjs +1 -1
- package/skills/paper-pipeline/pipeline-language.eval.mjs +1 -1
- package/skills/paper-pipeline/scripts/consumer.d.mts +1 -0
- package/skills/paper-pipeline/scripts/consumer.harness.mjs +10 -4
- package/skills/paper-pipeline/scripts/consumer.mjs +8 -1
- package/skills/paper-pipeline/scripts/consumer.mutations.mjs +1 -1
- package/skills/render-paper/SKILL.md +3 -3
- package/skills/render-paper/SKILL.md.spec.ts +2 -2
- package/skills/render-paper/extract-pdf-facts.harness.mjs +6 -6
- package/skills/render-paper/extract-pdf-facts.mjs +4 -4
- package/skills/study-accepted-papers/SKILL.md +2 -1
- package/skills/study-accepted-papers/SKILL.md.spec.ts +1 -0
- package/skills/submit-paper/SKILL.md +9 -1
- package/skills/submit-paper/SKILL.md.spec.ts +8 -0
- package/skills/submit-paper/references/venues/acm-sigconf.jsonc +86 -0
- package/skills/submit-paper/references/venues/agenticdev.jsonc +34 -102
- package/skills/submit-paper/references/venues/aisec.jsonc +21 -82
- package/skills/submit-paper/references/venues/realm.jsonc +45 -43
- package/skills/submit-paper/references/venues/tex-base.jsonc +1 -1
- package/skills/submit-paper/references/venues/venue-profile.schema.json +120 -30
- package/src/adapters/banal/index.test.ts +1 -1
- package/src/adapters/banal/invocation.test.ts +3 -3
- package/src/adapters/banal/invocation.ts +3 -3
- package/src/adapters/banal/locate.test.ts +4 -4
- package/src/adapters/banal/run.ts +2 -2
- package/src/adapters/banal/settings.test.ts +12 -6
- package/src/adapters/banal/settings.ts +9 -5
- package/src/adapters/banal/xml.test.ts +1 -1
- package/src/adapters/banal/xml.ts +1 -1
- package/src/adapters/curl/download.io.ts +4 -2
- package/src/adapters/curl/download.test.ts +3 -1
- package/src/adapters/node/files.test.ts +1 -1
- package/src/adapters/node/process.test.ts +2 -2
- package/src/adapters/node/workspace.test.ts +4 -4
- package/src/build-engine.harness.mjs +2 -2
- package/src/build-engine.ts +11 -3
- package/src/build.harness.mjs +27 -22
- package/src/build.mutations.mjs +3 -2
- package/src/build.ts +6 -5
- package/src/cli.harness.mjs +72 -131
- package/src/cli.mutations.mjs +7 -32
- package/src/cli.ts +204 -121
- package/src/doctor.harness.mjs +9 -25
- package/src/doctor.ts +28 -5
- package/src/engine.harness.mjs +4 -2
- package/src/engine.ts +2 -2
- package/src/facts-file.test.ts +54 -7
- package/src/facts-file.ts +145 -24
- package/src/hooks-settings.harness.mjs +24 -15
- package/src/hooks-settings.mutations.mjs +2 -4
- package/src/hooks-settings.ts +92 -49
- package/src/init.ts +88 -52
- package/src/latex-log.harness.mjs +1 -1
- package/src/link-skills.harness.mjs +3 -1
- package/src/link-skills.mutations.mjs +1 -1
- package/src/link-skills.ts +2 -2
- package/src/new-paper.harness.mjs +10 -7
- package/src/new-paper.test.ts +145 -0
- package/src/new-paper.ts +22 -2
- package/src/paper-settings-commands.test.ts +311 -0
- package/src/paper-settings.test.ts +220 -0
- package/src/paper-settings.ts +206 -0
- package/src/pdf-facts.harness.mjs +1 -1
- package/src/presets.test.ts +236 -0
- package/src/presets.ts +307 -0
- package/src/rules-config.ts +14 -9
- package/src/structure.harness.mjs +4 -4
- package/src/structure.ts +2 -1
- package/src/tex-requirements.harness.mjs +12 -18
- package/src/tex-requirements.ts +179 -29
- package/src/toolchain.harness.mjs +34 -25
- package/src/toolchain.ts +14 -8
- package/src/types.ts +4 -4
- package/src/venue-rules.test.ts +545 -0
- package/src/venue-rules.ts +573 -0
- package/templates/paper/paperlint.json +4 -0
- package/fixtures/build-e2e/acmart/venue.json +0 -1
- package/plugin/.claude-plugin/plugin.json +0 -8
package/docs/configuration.md
CHANGED
|
@@ -18,8 +18,8 @@ The README carries the minimal version of this. Everything below is the full sur
|
|
|
18
18
|
"causeMarker": "Cause:",
|
|
19
19
|
"rules": [
|
|
20
20
|
{
|
|
21
|
-
"files": ["papers/
|
|
22
|
-
"rules": { "
|
|
21
|
+
"files": ["papers/old-draft/**"],
|
|
22
|
+
"rules": { "paper/typography": "off" }
|
|
23
23
|
}
|
|
24
24
|
]
|
|
25
25
|
}
|
|
@@ -57,6 +57,61 @@ Until 2026-09-24 this field was called `papers`. The old name is not read as a f
|
|
|
57
57
|
hooks stay silent instead. The name is defined once, as `PAPERS_DIR_FIELD` in
|
|
58
58
|
`lib/paper-config.mjs`.
|
|
59
59
|
|
|
60
|
+
## Three levels of settings
|
|
61
|
+
|
|
62
|
+
Each level is named after the tool, and each says something the others cannot:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
package.json "paperlint": { … } the PROJECT: where the papers are, what every paper gets
|
|
66
|
+
papers/
|
|
67
|
+
my-paper/
|
|
68
|
+
paper.tex
|
|
69
|
+
PIPELINE-STATUS.md
|
|
70
|
+
paperlint.json { "extends": … } THIS PAPER: its venue preset, its kind, its own rules — `paperlint new` writes it
|
|
71
|
+
venues/usenix-sec.jsonc (optional, your own) a VENUE PRESET: format, page limits, TeX packages, rules
|
|
72
|
+
node_modules/paperlint/skills/submit-paper/references/venues/
|
|
73
|
+
acm-sigconf.jsonc agenticdev.jsonc aisec.jsonc realm.jsonc the shipped presets (paperlint:<name>)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`paperlint new` writes `<paper>/paperlint.json` from the template (`templates/paper/paperlint.json`,
|
|
77
|
+
or your `<papers>/.template/paperlint.json` if you keep one), with `"extends": null` — no venue chosen
|
|
78
|
+
yet — and a `$comment` saying what goes there. Until `extends` names a preset, `paperlint lint` gives
|
|
79
|
+
that paper one warning, `pdf/measured`: "this paper names no venue preset yet … set "extends" in
|
|
80
|
+
papers/my-paper/paperlint.json". A paper folder with no `paperlint.json` at all — one created before
|
|
81
|
+
2.1.0 — gets no venue checks and no warning; `npx paperlint new <its name>` adds the file and
|
|
82
|
+
changes nothing else.
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"extends": "paperlint:aisec",
|
|
87
|
+
"kind": "research",
|
|
88
|
+
"rules": { "pdf/body-size": "off" }
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
| key | what it is |
|
|
93
|
+
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
94
|
+
| `extends` | the venue preset the built PDF is judged against: `paperlint:<name>` (shipped) or `./path` / `../path` (your own, relative to this file) — [`rules.md`](rules.md#checks-against-the-venue) |
|
|
95
|
+
| `kind` | the kind of paper (`short`, `research`, …) whose page limit applies |
|
|
96
|
+
| `pdf` | where the built PDF is, relative to the paper, when it is not `paper.pdf` |
|
|
97
|
+
| `rules` | rule id → severity, for this paper alone — the same entries as a `rules` block below |
|
|
98
|
+
| `$comment` | a note for humans (JSON Schema's comment keyword); ignored |
|
|
99
|
+
|
|
100
|
+
Any other key is an error naming the file and the key, as in `package.json`.
|
|
101
|
+
|
|
102
|
+
**Where a paper's rules come from, in order — a later one wins, rule by rule:** paperlint's own
|
|
103
|
+
configuration → the preset chain's `rules`, from the root preset to the one the paper extends →
|
|
104
|
+
the paper's own `rules` → the project's `rules` blocks in `package.json`. So a venue can turn a
|
|
105
|
+
rule on for its papers, a paper can turn it off for itself, and the project can still override
|
|
106
|
+
both. Only rules paperlint ships may be named, at every level.
|
|
107
|
+
|
|
108
|
+
**`paperlint.json` replaces `venue.json` (2.1.0); `npx paperlint init` moves it.** The old name is not
|
|
109
|
+
read: a paper with only a `venue.json` gets a `pdf/profile` error and `paperlint doctor` names the
|
|
110
|
+
file, both pointing at `init`. `init` writes the same settings as `paperlint.json` —
|
|
111
|
+
`"venue": "aisec"` becomes `"extends": "paperlint:aisec"`, and the `"_"` some files used as a
|
|
112
|
+
comment becomes `"$comment"` — then deletes `venue.json`. It removes a `venue.json` whose
|
|
113
|
+
`paperlint.json` already says the same, and refuses, changing nothing, when both exist and differ.
|
|
114
|
+
|
|
60
115
|
## Why the key lives in `package.json`
|
|
61
116
|
|
|
62
117
|
Because of a count: the `package.json` key has **five** readers — the three editor hooks, the
|
|
@@ -65,12 +120,8 @@ import code and cannot walk up a tree looking for a config; it can read a path i
|
|
|
65
120
|
and the one path it can always name is the project's `package.json`.
|
|
66
121
|
|
|
67
122
|
`paperlint lint` looks for it in the current directory and then upwards, the way eslint and tsc find
|
|
68
|
-
theirs, and prints which file it found. `--config <file>` overrides the search
|
|
69
|
-
|
|
70
|
-
⚠️ **`rpp.json` is deprecated and still read.** Earlier versions of `init` created it; `init` no
|
|
71
|
-
longer does, and a run that reads one says so on its first line. The hooks never read it, so
|
|
72
|
-
leaving settings there is how the linter and the guard end up watching different directories —
|
|
73
|
-
`paperlint init` copies the value across for you.
|
|
123
|
+
theirs, and prints which file it found. `--config <file>` overrides the search; the file has the
|
|
124
|
+
same shape, with the settings under the `paperlint` key.
|
|
74
125
|
|
|
75
126
|
## The `rules` key: turning rules on and off
|
|
76
127
|
|
|
@@ -80,11 +131,15 @@ the three keys that make sense in JSON — `files`, `ignores` and `rules`. paper
|
|
|
80
131
|
**after** its own configuration, so, as in ESLint, a later block wins: a block can turn on a rule
|
|
81
132
|
that is off by default, or change the severity of one that is on.
|
|
82
133
|
|
|
134
|
+
For ONE paper, the paper's own `paperlint.json` is simpler — no glob to get wrong
|
|
135
|
+
([above](#three-levels-of-settings)). Use a block here for a rule across several papers, or to
|
|
136
|
+
override what a paper says:
|
|
137
|
+
|
|
83
138
|
```json
|
|
84
139
|
"rules": [
|
|
85
140
|
{
|
|
86
|
-
"files": ["papers
|
|
87
|
-
"rules": { "pdf/
|
|
141
|
+
"files": ["papers/**"],
|
|
142
|
+
"rules": { "pdf/body-size": "off" }
|
|
88
143
|
},
|
|
89
144
|
{
|
|
90
145
|
"files": ["papers/old-draft/**"],
|
|
@@ -120,7 +175,7 @@ and the real findings leave with it.
|
|
|
120
175
|
|
|
121
176
|
```json
|
|
122
177
|
"structure": {
|
|
123
|
-
"markers": ["PIPELINE-STATUS.md", "paper.tex", "paper.md", "
|
|
178
|
+
"markers": ["PIPELINE-STATUS.md", "paper.tex", "paper.md", "paperlint.json"],
|
|
124
179
|
"require": ["PIPELINE-STATUS.md"],
|
|
125
180
|
"requireOneOf": [["paper.tex", "paper.md"]],
|
|
126
181
|
"ignore": []
|
|
@@ -129,7 +184,7 @@ and the real findings leave with it.
|
|
|
129
184
|
|
|
130
185
|
Those are the defaults; you only write the block to change them. `paper.md` is still in them
|
|
131
186
|
because Markdown papers are deprecated but not yet removed
|
|
132
|
-
([#57](https://github.com/zernie/
|
|
187
|
+
([#57](https://github.com/zernie/paperlint/issues/57)). They were measured against a
|
|
133
188
|
real five-paper corpus rather than chosen — it passes with zero findings, while adding
|
|
134
189
|
`paper.pdf` to `require` produces two findings on papers that are perfectly fine, which is why it
|
|
135
190
|
is not there.
|
|
@@ -177,11 +232,12 @@ papers/my-paper
|
|
|
177
232
|
|
|
178
233
|
The build does **not** judge the layout. A balanced last page, a page limit, the fonts a venue
|
|
179
234
|
wants — those are verdicts about the finished PDF, and they belong to lint rules that can be
|
|
180
|
-
turned on per venue, given a severity and suppressed with a reason
|
|
235
|
+
turned on per venue, given a severity and suppressed with a reason: the `pdf/` venue rules
|
|
236
|
+
([`rules.md`](rules.md#checks-against-the-venue)) and the optional `pdf/last-page-balance`. paperlint once searched for a
|
|
181
237
|
`\balance` position itself and failed the build when none worked; that was removed on
|
|
182
238
|
2026-09-24.
|
|
183
239
|
|
|
184
|
-
The class and its options and the venue in `
|
|
240
|
+
The class and its options and the venue in `paperlint.json` are read from the paper and shown in the
|
|
185
241
|
plan; later steps decide from them whether they apply.
|
|
186
242
|
|
|
187
243
|
**`paper.pdf` is deleted before anything runs**, for every targeted paper — before the TeX Live is
|
|
@@ -216,18 +272,18 @@ having run on it, because a missing build read as nothing to do.
|
|
|
216
272
|
⚠️ **A `build.sh` or `repro/build-submission.sh` in the paper directory is IGNORED.** Earlier
|
|
217
273
|
versions ran it; `paperlint build` now says one line — `build.sh is ignored — paperlint builds the paper
|
|
218
274
|
itself` — and builds the paper itself. The `buildScripts` key is ignored the same way.
|
|
219
|
-
Why: [#59](https://github.com/zernie/
|
|
275
|
+
Why: [#59](https://github.com/zernie/paperlint/issues/59).
|
|
220
276
|
|
|
221
277
|
## Using the rules from an existing ESLint config
|
|
222
278
|
|
|
223
279
|
Under the hood `paperlint lint` builds an ESLint flat config and runs it. If your repository already
|
|
224
280
|
lints with ESLint, you can import the rule modules from `paperlint/eslint-rules/`
|
|
225
|
-
and wire them yourself; `bin/
|
|
281
|
+
and wire them yourself; `bin/paperlint.mjs` exports `buildConfig(options, texLanguage)` that returns
|
|
226
282
|
the exact config the CLI uses, so the shortest path is:
|
|
227
283
|
|
|
228
284
|
```js
|
|
229
285
|
// eslint.config.mjs
|
|
230
|
-
import { buildConfig } from "paperlint/bin/
|
|
286
|
+
import { buildConfig } from "paperlint/bin/paperlint.mjs";
|
|
231
287
|
import { texLanguage } from "paperlint/eslint-rules/latex-language.mjs";
|
|
232
288
|
export default buildConfig({ minFindings: 3 }, texLanguage);
|
|
233
289
|
```
|
package/docs/e2e.md
CHANGED
|
@@ -35,8 +35,7 @@ temporary tree, **under npm and under pnpm separately**, and drives the installe
|
|
|
35
35
|
|
|
36
36
|
- the install itself finishes
|
|
37
37
|
- `paperlint --help` answers with zero
|
|
38
|
-
- `paperlint init` declares the papers directory in `package.json
|
|
39
|
-
carrier `rpp.json` behind
|
|
38
|
+
- `paperlint init` declares the papers directory in `package.json`
|
|
40
39
|
- `paperlint init` finishes with zero — its doctor found no discrepancy
|
|
41
40
|
- `paperlint init` wires the hooks into `.claude/settings.json` — the same commands `hooks.json`
|
|
42
41
|
publishes, once each — and says they need `npm install` in a fresh clone; a second `init`
|
|
@@ -88,10 +87,10 @@ paperlint's own cache, because the runner has no other.
|
|
|
88
87
|
|
|
89
88
|
## `test/e2e/toolchain.mjs` — real TeX Live, and only it
|
|
90
89
|
|
|
91
|
-
`paperlint toolchain` into `$
|
|
90
|
+
`paperlint toolchain` into `$PAPERLINT_TEXLIVE_DIR` against real CTAN; a second run must say "nothing to do"
|
|
92
91
|
within seconds; `--check` must exit 0; then the `acmart` fixture is built with PATH holding `node`
|
|
93
92
|
only, so no other TeX Live and no PDF tool can stand in, and the PDF must carry Libertine and Biolinum
|
|
94
|
-
and no Computer Modern face. Without `
|
|
93
|
+
and no Computer Modern face. Without `PAPERLINT_TEXLIVE_DIR` it is a declared skip: installing ~270 MB
|
|
95
94
|
into a home directory as a side effect of `npm run check` is the unasked install rule 11 forbids.
|
|
96
95
|
`src/toolchain.harness.mjs` covers the installer's logic (mirror fallback, archive check, time
|
|
97
96
|
limit, verification, idempotence) against a fake mirror on disk, without the network.
|
package/docs/install.md
CHANGED
|
@@ -20,6 +20,15 @@ your project's `node_modules`, so it needs the install first.
|
|
|
20
20
|
and skill links that pointed into the old package. Until then the old key is still read, with a
|
|
21
21
|
warning.
|
|
22
22
|
|
|
23
|
+
**Upgrading from 2.0.0**: 2.0.0 wrote hook commands that run `node_modules/paperlint/bin/rpp.mjs`,
|
|
24
|
+
a file later versions do not ship, so those hooks stop running. `npx paperlint doctor` names them;
|
|
25
|
+
`npx paperlint init` replaces them and keeps your own commands in the same matcher. The TeX Live
|
|
26
|
+
and banal caches moved from `~/.cache/rpp/` to `~/.cache/paperlint/`, so `npx paperlint toolchain`
|
|
27
|
+
downloads TeX Live once more (delete the old directory afterwards), and the environment variables
|
|
28
|
+
are now `PAPERLINT_TEXLIVE_DIR`, `PAPERLINT_BANAL_DIR` and `PAPERLINT_CTAN_MIRROR` — the `RPP_*`
|
|
29
|
+
names are no longer read. A leftover `rpp.json` is ignored; its settings belong under the
|
|
30
|
+
`"paperlint"` key of `package.json`.
|
|
31
|
+
|
|
23
32
|
The npm package carries everything: the `paperlint` command, the ESLint rules, the Claude Code skills
|
|
24
33
|
and hooks, and the scripts the skills run. External programs are separate:
|
|
25
34
|
|
|
@@ -68,8 +77,7 @@ gets the hooks; the hook commands run files inside `node_modules`, so a fresh cl
|
|
|
68
77
|
config; it can only read a path it can spell, and the one it can always spell is
|
|
69
78
|
`$CLAUDE_PROJECT_DIR/package.json`. That key is read by the three hooks, `eslint-rules/papers.mjs`,
|
|
70
79
|
`lib/skill-trigger-cases.mjs` and `skills/paper-pipeline/scripts/consumer.mjs`; a separate
|
|
71
|
-
|
|
72
|
-
`paperlint lint` says so.
|
|
80
|
+
config file would be read only by the CLI, so there is none.
|
|
73
81
|
|
|
74
82
|
**Nothing runs at install time.** No postinstall script and no automatic TeX download. npm's rule
|
|
75
83
|
is that _"the only valid use of install or preinstall scripts is for compilation"_; husky removed
|
|
@@ -135,7 +143,7 @@ on the tree the manager laid out. It also checks that every shipped skill is rea
|
|
|
135
143
|
directory under a skill's name survives.
|
|
136
144
|
|
|
137
145
|
Yarn Plug'n'Play has no `node_modules`, and the hook commands in `plugin/hooks/hooks.json` name
|
|
138
|
-
`${CLAUDE_PROJECT_DIR}/node_modules/paperlint/bin/
|
|
146
|
+
`${CLAUDE_PROJECT_DIR}/node_modules/paperlint/bin/paperlint.mjs`. Supporting it would need
|
|
139
147
|
a different answer to "where is the runtime", not a flag.
|
|
140
148
|
|
|
141
149
|
## Install size
|
package/docs/optional-rules.md
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# Optional rules
|
|
2
2
|
|
|
3
|
-
Some checks matter only for some venues. paperlint ships them **off
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Some checks matter only for some venues. paperlint ships them **off**. A venue preset that needs one
|
|
4
|
+
turns it on for its papers (AgenticDev's turns on `pdf/last-page-balance`), and a paper can turn one
|
|
5
|
+
on or off for itself in its own `paperlint.json`
|
|
6
|
+
([`configuration.md`](configuration.md#three-levels-of-settings)). No glob is involved: a paper's
|
|
7
|
+
rules apply to that paper. The `rules` setting of your `package.json` still overrides both, for one
|
|
8
|
+
paper or many ([`configuration.md`](configuration.md#the-rules-key-turning-rules-on-and-off)).
|
|
6
9
|
|
|
7
10
|
| rule | what it checks | who needs it |
|
|
8
11
|
| ----------------------- | ------------------------------------------------------------- | --------------------------------------------------------- |
|
|
@@ -12,21 +15,18 @@ papers that need them, in the `rules` setting of your `package.json`
|
|
|
12
15
|
|
|
13
16
|
### Turning it on
|
|
14
17
|
|
|
18
|
+
A paper that extends `paperlint:agenticdev` has it already, at 120 pt. For any other paper, in its
|
|
19
|
+
`papers/my-paper/paperlint.json`:
|
|
20
|
+
|
|
15
21
|
```json
|
|
16
22
|
{
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
{
|
|
21
|
-
"files": ["papers/agenticdev-2026/**"],
|
|
22
|
-
"rules": { "pdf/last-page-balance": ["error", { "tolerancePt": 120 }] }
|
|
23
|
-
}
|
|
24
|
-
]
|
|
25
|
-
}
|
|
23
|
+
"extends": "paperlint:aisec",
|
|
24
|
+
"kind": "research",
|
|
25
|
+
"rules": { "pdf/last-page-balance": ["error", { "tolerancePt": 120 }] }
|
|
26
26
|
}
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
`
|
|
29
|
+
It applies to that paper alone; `"off"` there turns it off for a paper whose preset turns it on. `tolerancePt` is how far apart, in points, the two
|
|
30
30
|
columns may end; it defaults to 120. On a real accepted paper the balanced build ended 2.7 pt apart
|
|
31
31
|
and the one the publisher sent back 321.4 pt apart — nothing in between — so the default leaves a
|
|
32
32
|
wide margin on both sides.
|
|
@@ -34,21 +34,22 @@ wide margin on both sides.
|
|
|
34
34
|
### What it reads
|
|
35
35
|
|
|
36
36
|
The rule does not open the PDF to measure it. `paperlint build` measures every PDF it builds and writes
|
|
37
|
-
the result to `<paper>/_build/paper.facts.json` ([`configuration.md`](configuration.md#how-
|
|
37
|
+
the result to `<paper>/_build/paper.facts.json` ([`configuration.md`](configuration.md#how-paperlint-build-compiles-a-paper)),
|
|
38
38
|
and the rule judges that file, reporting on the paper's `paper.tex` at the `\documentclass` line.
|
|
39
39
|
So: **build, then lint.**
|
|
40
40
|
|
|
41
|
-
| the rule finds | it says
|
|
42
|
-
| --------------------------------------------------- |
|
|
43
|
-
| columns further apart than `tolerancePt` | **the finding**, with both heights and how to fix it (below)
|
|
44
|
-
| no `_build/paper.facts.json` | build the paper first
|
|
45
|
-
| facts about a different PDF than the one on disk | the facts are stale (their SHA-256 differs) — rebuild
|
|
46
|
-
| facts whose PDF is gone (a failed build removes it) | rebuild
|
|
47
|
-
| a last page of a few lines | nothing — there is no layout to balance
|
|
48
|
-
| a review build with numbered lines | nothing — the numbers run down the whole page, so both columns measure full height, and balance is a camera-ready requirement
|
|
49
|
-
|
|
50
|
-
If you turn the rule on
|
|
51
|
-
so, rather than reporting a clean
|
|
41
|
+
| the rule finds | it says |
|
|
42
|
+
| --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
|
43
|
+
| columns further apart than `tolerancePt` | **the finding**, with both heights and how to fix it (below) |
|
|
44
|
+
| no `_build/paper.facts.json` | build the paper first — unless the paper extends a venue preset: then `pdf/measured` already warns, once, and this rule is silent |
|
|
45
|
+
| facts about a different PDF than the one on disk | the facts are stale (their SHA-256 differs) — rebuild |
|
|
46
|
+
| facts whose PDF is gone (a failed build removes it) | rebuild |
|
|
47
|
+
| a last page of a few lines | nothing — there is no layout to balance |
|
|
48
|
+
| a review build with numbered lines | nothing — the numbers run down the whole page, so both columns measure full height, and balance is a camera-ready requirement |
|
|
49
|
+
|
|
50
|
+
If you turn the rule on — in a `paperlint.json` beside no `paper.tex`, or with a `files` glob in
|
|
51
|
+
`package.json` that reaches none — `paperlint lint` fails and says so, rather than reporting a clean
|
|
52
|
+
run for a rule that never ran.
|
|
52
53
|
|
|
53
54
|
### Which venues need it
|
|
54
55
|
|
package/docs/rules.md
CHANGED
|
@@ -14,6 +14,13 @@ reads and when it fails. Errors fail `paperlint lint`; warnings print and do not
|
|
|
14
14
|
| `tex/acm-frontmatter-override` | error | `paper.tex` | an `acmart` build overrides ACM's front-matter commands and drops template elements from page 1 |
|
|
15
15
|
| `review/findings-cause` | error | `reviews/*.md` | a review lists at least `minFindings` (default 3) findings and no cell introduces a cause with the marker (default `Cause:`) |
|
|
16
16
|
| `doc/fields` | warn | `reviews/*.md` | a front-matter field is missing or holds a value outside the list you configured. Off entirely unless you configure `docFields` |
|
|
17
|
+
| `pdf/fresh` | error | `paper.tex` → `_build/paper.facts.json` | the paper names a venue and the facts cannot be judged: not JSON, a schema other than 2, or they describe a PDF that is gone or differs from the one on disk (its SHA-256) |
|
|
18
|
+
| `pdf/profile` | error | `paper.tex` → `paperlint.json` | `paperlint.json` is not JSON or has an unknown key, only a pre-2.1.0 `venue.json` is there (`npx paperlint init` moves it), its `extends` does not resolve (not found, a cycle, a chain longer than four, a preset that fails the schema, an npm name), it names no `kind` while the preset has kinds, or names a kind the preset does not have |
|
|
19
|
+
| `pdf/fonts` | error | `paper.tex` → `_build/paper.facts.json` | a font the pages draw is Type 3 or not embedded, or no font starts with the family the venue preset names for body text (`fonts_text`) or headings (`fonts_title`) |
|
|
20
|
+
| `pdf/geometry` | error | `paper.tex` → `_build/paper.facts.json` | the page width or height is more than `dimTol` (default 0.05 in) off the preset's, or the column count differs |
|
|
21
|
+
| `pdf/limits` | error | `paper.tex` → `_build/paper.facts.json` | body or reference pages exceed the limit of the paper's kind, or the reference font size is outside the preset's range widened by `body_pt_tol` |
|
|
22
|
+
| `pdf/body-size` | warn | `paper.tex` → `_build/paper.facts.json` | the body font size is more than `body_pt_tol` off the preset's. A warning: banal measures the mode of the rendered text, not the declared size (9.30 pt measured at a declared 9) |
|
|
23
|
+
| `pdf/measured` | warn | `paper.tex` → `_build/paper.facts.json` | the paper's `paperlint.json` names no venue preset yet; or it names one and there are no facts (it was not built), or the facts carry no page geometry (banal was not found) — so the checks above did not run |
|
|
17
24
|
|
|
18
25
|
Optional rules — off unless you turn them on in the `rules` setting, because only some venues need
|
|
19
26
|
them — are on their own page: [`optional-rules.md`](optional-rules.md). Today there is one,
|
|
@@ -23,15 +30,137 @@ Besides these rules, `paperlint lint` reports a paper directory that is missing
|
|
|
23
30
|
default `PIPELINE-STATUS.md`) as an error. Which files are required is configurable — see
|
|
24
31
|
[`configuration.md`](configuration.md#required-files).
|
|
25
32
|
|
|
33
|
+
## Checks against the venue
|
|
34
|
+
|
|
35
|
+
A paper says where it is submitted in a `paperlint.json` beside `paper.tex`
|
|
36
|
+
([`configuration.md`](configuration.md#three-levels-of-settings)):
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{ "extends": "paperlint:aisec", "kind": "research" }
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`extends` names a **venue preset** — the way an ESLint config extends a shareable config. `kind`
|
|
43
|
+
names the kind of paper, whose page limit applies. A preset holds the numbers from the venue's call
|
|
44
|
+
for papers, each with the quote it came from, the TeX packages its template needs, and the rules
|
|
45
|
+
the venue implies. A preset may itself extend another: the ACM venues extend the `acm-sigconf`
|
|
46
|
+
family, which holds everything the ACM template decides.
|
|
47
|
+
|
|
48
|
+
| preset | extends | template | kinds | page limit checked | rules it turns on |
|
|
49
|
+
| ----------------------- | ----------------------- | -------------------- | -------------------------------------------------------- | ------------------ | ----------------------- |
|
|
50
|
+
| `paperlint:acm-sigconf` | — | ACM `acmart` sigconf | none | no (no kinds) | — |
|
|
51
|
+
| `paperlint:agenticdev` | `paperlint:acm-sigconf` | ACM `acmart` sigconf | `short` (5 + 2 refs), `full` (10 + 2), `demo` (5 + 2) | yes | `pdf/last-page-balance` |
|
|
52
|
+
| `paperlint:aisec` | `paperlint:acm-sigconf` | ACM `acmart` sigconf | `research`, `benchmark`, `position`, `sok` (10 + 2 each) | yes | — |
|
|
53
|
+
| `paperlint:realm` | — | ACL | `long`, `short` | no — see below | — |
|
|
54
|
+
|
|
55
|
+
That is all that ships today. There is no IEEE, NeurIPS, USENIX or Springer preset. A paper for an
|
|
56
|
+
ACM venue nobody has profiled can extend `paperlint:acm-sigconf` directly: page size, columns and
|
|
57
|
+
fonts are checked, and `pdf/profile` says the page limit is not. For anything else, write your own
|
|
58
|
+
preset ([below](#writing-your-own-venue-preset)). REALM's preset sets no page limit on purpose:
|
|
59
|
+
banal counts the Limitations and Ethics sections as body, ACL does not, and a limit on banal's
|
|
60
|
+
number would fail a correct paper. AgenticDev's turns on `pdf/last-page-balance` because its
|
|
61
|
+
proceedings are produced by Conference Publishing Consulting, which sends back an unbalanced last
|
|
62
|
+
page; AISec's does not, because nothing in hand says who produces the AISec proceedings.
|
|
63
|
+
|
|
64
|
+
**Build, then lint.** The rules judge what `paperlint build` measured and wrote to
|
|
65
|
+
`_build/paper.facts.json` — page count, fonts, and, through banal, page size, columns, font sizes
|
|
66
|
+
and the split into body and reference pages. They report on the paper's `paper.tex`, at the
|
|
67
|
+
`\documentclass` line. What they say when there is nothing to judge, one rule per reason:
|
|
68
|
+
|
|
69
|
+
| the paper | what you get |
|
|
70
|
+
| ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
71
|
+
| has a `paperlint.json` that extends no preset (`"extends": null` is what `paperlint new` writes) | `pdf/measured` warning naming the file to set — no venue chosen yet |
|
|
72
|
+
| has no `paperlint.json` at all (a paper made before 2.1.0) | nothing |
|
|
73
|
+
| extends a preset that does not resolve (a typo, a missing file) | `pdf/profile` error, listing the shipped presets |
|
|
74
|
+
| has not been built (no facts file) | `pdf/measured` warning — it does not fail the run, because lint often runs where nothing is built (the CI action only lints) |
|
|
75
|
+
| was built without banal | `pdf/measured` warning; fonts are still checked, the rest is not |
|
|
76
|
+
| has facts about another PDF than the one on disk | `pdf/fresh` error, and nothing else is judged |
|
|
77
|
+
| names no `kind`, or a kind the preset lacks | `pdf/profile` error; everything but the page limit is still checked |
|
|
78
|
+
|
|
79
|
+
The venue comes from `paperlint.json`, not from the facts, so changing it needs no rebuild: the
|
|
80
|
+
measurements do not depend on it. Messages name the venue by the preset's `name`, else by the file
|
|
81
|
+
name of what the paper extends (`paperlint:agenticdev` → `agenticdev`).
|
|
82
|
+
|
|
83
|
+
To skip a check for one paper — a finding you accept — set it to `"off"` in the paper's own
|
|
84
|
+
`paperlint.json`:
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"extends": "paperlint:aisec",
|
|
89
|
+
"kind": "research",
|
|
90
|
+
"rules": { "pdf/body-size": "off" }
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Writing your own venue preset
|
|
95
|
+
|
|
96
|
+
A preset is a JSONC file (JSON with comments — keep the call-for-papers quote beside each number)
|
|
97
|
+
of the same shape as the shipped ones, validated by
|
|
98
|
+
[`venue-profile.schema.json`](../skills/submit-paper/references/venues/venue-profile.schema.json).
|
|
99
|
+
Put it in your repository and extend it by a path relative to the file that names it:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
package.json
|
|
103
|
+
venues/
|
|
104
|
+
usenix-sec.jsonc
|
|
105
|
+
papers/
|
|
106
|
+
usenix-2027/
|
|
107
|
+
paper.tex
|
|
108
|
+
paperlint.json { "extends": "../../venues/usenix-sec.jsonc", "kind": "full" }
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
```jsonc
|
|
112
|
+
// venues/usenix-sec.jsonc
|
|
113
|
+
{
|
|
114
|
+
"name": "USENIX Security",
|
|
115
|
+
// a standalone preset needs its template's TeX packages; one that extends a family inherits them
|
|
116
|
+
"template": "article",
|
|
117
|
+
// TeX Live packages only, each with a file that proves it is installed. The USENIX style file
|
|
118
|
+
// itself is not in TeX Live: keep it beside paper.tex.
|
|
119
|
+
"tex": { "packages": { "psnfss": ["times.sty"] } },
|
|
120
|
+
"format": {
|
|
121
|
+
"page_size": "letter",
|
|
122
|
+
"page_w_in": 8.5,
|
|
123
|
+
"page_h_in": 11,
|
|
124
|
+
"columns": 2,
|
|
125
|
+
"body_pt": 10,
|
|
126
|
+
"body_pt_tol": 0.5,
|
|
127
|
+
// the page limit of each kind of paper, as the call for papers states it
|
|
128
|
+
"kinds": { "full": { "body_pages_max": 13 } },
|
|
129
|
+
},
|
|
130
|
+
// only if the proceedings' producer asks for a balanced last page
|
|
131
|
+
"rules": { "pdf/last-page-balance": "error" },
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The numbers and packages above illustrate the shape; take yours from the venue's own call for papers and template.
|
|
136
|
+
|
|
137
|
+
| key | what it is |
|
|
138
|
+
| ---------- | --------------------------------------------------------------------------------------------------- |
|
|
139
|
+
| `extends` | the preset this one builds on: `paperlint:<name>` or `./path` / `../path`, relative to this file |
|
|
140
|
+
| `name` | how messages name the venue; the file name otherwise |
|
|
141
|
+
| `template` | the `\documentclass` the venue's template uses |
|
|
142
|
+
| `format` | page size, columns, fonts, font sizes, and `kinds` (page limits per kind of paper) |
|
|
143
|
+
| `tex` | TeX Live packages, each with the files that prove it is installed. Required unless `extends` is set |
|
|
144
|
+
| `rules` | rules the venue implies, rule id → severity or `[severity, options]` |
|
|
145
|
+
|
|
146
|
+
How a chain merges, from the root preset to the paper: `tex` is the union — a child never removes a
|
|
147
|
+
package; `format` keys are replaced one by one, and a child's kind replaces that kind whole;
|
|
148
|
+
`rules` are replaced rule by rule, and the paper's own `rules` come last. A chain is at most four
|
|
149
|
+
presets long, and a cycle is refused by name. `paperlint toolchain` installs the packages of every
|
|
150
|
+
shipped preset and of every preset your papers extend.
|
|
151
|
+
|
|
152
|
+
Presets from npm packages are not supported yet.
|
|
153
|
+
|
|
26
154
|
## The paper is LaTeX
|
|
27
155
|
|
|
28
|
-
The paper body is `paper.tex`, and it gets four rules: `paper/research-question`,
|
|
29
|
-
`paper/typography`, `tex/future-promise` and `tex/acm-frontmatter-override
|
|
156
|
+
The paper body is `paper.tex`, and it gets four rules of its own: `paper/research-question`,
|
|
157
|
+
`paper/typography`, `tex/future-promise` and `tex/acm-frontmatter-override` — plus the `pdf/`
|
|
158
|
+
venue rules above, which run on it but judge the files beside it. The scorecard and
|
|
30
159
|
the review notes are Markdown files, and the other five rules read those.
|
|
31
160
|
|
|
32
161
|
A Markdown body (`paper.md`, or `draft.md`) is still read today and gets only the two `paper/`
|
|
33
162
|
rules, which is why they list it above. Markdown papers are deprecated and being removed
|
|
34
|
-
([#57](https://github.com/zernie/
|
|
163
|
+
([#57](https://github.com/zernie/paperlint/issues/57)); do not start a new one.
|
|
35
164
|
|
|
36
165
|
## The scorecard's `bytes:` and `sourceBytes:`
|
|
37
166
|
|
package/docs/toolchain.md
CHANGED
|
@@ -36,12 +36,12 @@ The command has two halves, TeX Live and banal (below). Both always run, so one
|
|
|
36
36
|
hide the other, and the exit code is 0 only when both are ready.
|
|
37
37
|
|
|
38
38
|
It downloads `install-tl` from a CTAN mirror (four in turn; each download has its own time limit,
|
|
39
|
-
TLS is always verified), installs `scheme-basic` into `~/.cache/
|
|
40
|
-
(`$XDG_CACHE_HOME/
|
|
39
|
+
TLS is always verified), installs `scheme-basic` into `~/.cache/paperlint/texlive/<TeX Live year>`
|
|
40
|
+
(`$XDG_CACHE_HOME/paperlint/texlive` when that is set, `$PAPERLINT_TEXLIVE_DIR` over both), and `tlmgr install`s
|
|
41
41
|
every package the venue profiles declare. Then it checks the RESULT: `kpsewhich` must find every
|
|
42
42
|
file each profile names, and each declared tool must be an executable in the bin directory. A gap
|
|
43
43
|
fails the command and names the package and its file. Linux and macOS; on Windows it refuses, and
|
|
44
|
-
`paperlint build` uses a TeX Live on PATH that has the packages. `
|
|
44
|
+
`paperlint build` uses a TeX Live on PATH that has the packages. `PAPERLINT_CTAN_MIRROR` names one mirror
|
|
45
45
|
(a `…/systems/texlive/tlnet` URL) to use instead of the list.
|
|
46
46
|
|
|
47
47
|
Measured 2026-09-24 from an empty directory: **3 min 04 s, 269 MB** (du: 298 MB), TeX Live 2026, 47
|
|
@@ -109,8 +109,8 @@ therefore does not contain banal. `paperlint toolchain` downloads it from HotCRP
|
|
|
109
109
|
| version | banal 1.2 |
|
|
110
110
|
| sha256 | `fd8cc4ae189b9da02460ae442a34f14434e5784210489fb668313ac671006911` |
|
|
111
111
|
|
|
112
|
-
It refuses a file with any other sha256, stores it in `~/.cache/
|
|
113
|
-
(`$XDG_CACHE_HOME/
|
|
112
|
+
It refuses a file with any other sha256, stores it in `~/.cache/paperlint/banal/<commit>/banal`
|
|
113
|
+
(`$XDG_CACHE_HOME/paperlint/banal` when set, `$PAPERLINT_BANAL_DIR` over both), and accepts it only after banal
|
|
114
114
|
has run on a one-page probe and measured it. paperlint then runs it as a separate program — `perl banal
|
|
115
115
|
-no-time -json <file>.xml` — and reads its JSON output. Nothing of banal is copied, linked or
|
|
116
116
|
translated into this package.
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* two (blanking preserves length), so scanning `raw` and reporting the offset found there
|
|
28
28
|
* still points at the right byte.
|
|
29
29
|
*
|
|
30
|
-
* ── WHY `bareDecimal` READS NEITHER, BUT THE PARSED TREE (
|
|
30
|
+
* ── WHY `bareDecimal` READS NEITHER, BUT THE PARSED TREE (paperlint#44) ─────────────────────────
|
|
31
31
|
* Origin. On a workshop paper (HotCRP #20) Reviewer B wrote: `Numbers should be completed:
|
|
32
32
|
* e.g. ".05" -> 0.05`. The submitted PDF really carried `p < .05`, `p=.002`, `p=.006` and
|
|
33
33
|
* `p=.037` in its text (measured with pdftotext on the submitted version); the camera-ready
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* fixture exists, not that the count is correct.
|
|
15
15
|
*
|
|
16
16
|
* The bare-decimal count is the exception, because what it gets wrong is not the lexeme but the
|
|
17
|
-
* INPUT:
|
|
17
|
+
* INPUT: paperlint#44 was a correct regex run over the wrong text. So two cases below change what it
|
|
18
18
|
* reads — back to the raw source (the SILENT half must die), and with math dropped from the walk
|
|
19
19
|
* (the CAUGHT half must die, since math is where p-values live).
|
|
20
20
|
*/
|
package/eslint-rules/papers.mjs
CHANGED
|
@@ -85,6 +85,7 @@ import {
|
|
|
85
85
|
CONFIG_KEY,
|
|
86
86
|
DEFAULT_PAPERS_ROOT,
|
|
87
87
|
PAPERS_DIR_FIELD,
|
|
88
|
+
PAPER_SETTINGS_FILE,
|
|
88
89
|
declaredSettings,
|
|
89
90
|
renamedFieldMessage,
|
|
90
91
|
settingsOf,
|
|
@@ -154,8 +155,11 @@ export function paperFiles(root) {
|
|
|
154
155
|
tex: [`${root}/*/paper.tex`],
|
|
155
156
|
/** The per-paper stage ledger. */
|
|
156
157
|
status: [`${root}/*/PIPELINE-STATUS.md`],
|
|
157
|
-
/**
|
|
158
|
-
|
|
158
|
+
/**
|
|
159
|
+
* The per-paper settings file, `paperlint.json` (`venue.json` before 2.1.0). The key keeps its
|
|
160
|
+
* old name so a consumer's config that lints these files keeps matching them.
|
|
161
|
+
*/
|
|
162
|
+
venue: [`${root}/*/${PAPER_SETTINGS_FILE}`],
|
|
159
163
|
/** Facts extracted from the built PDF. */
|
|
160
164
|
pdfFacts: [`${root}/*/_build/paper.facts.json`],
|
|
161
165
|
/** Facts extracted from the bibliography. */
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Colocated test for `eslint-rules/pdf-last-page-balance.mjs` (`pdf/last-page-balance`).
|
|
3
3
|
* Run: `npx vigiles test eslint-rules/pdf-last-page-balance.harness.mjs`
|
|
4
4
|
*
|
|
5
|
-
* The rule is driven the way a consumer drives it: through
|
|
5
|
+
* The rule is driven the way a consumer drives it: through paperlint's own `buildConfig`, with the
|
|
6
6
|
* rule turned on by a `rules` block in the settings — not through a config invented here. Each
|
|
7
7
|
* paper is a temporary directory holding `paper.tex`, `paper.pdf` and the facts `paperlint build`
|
|
8
8
|
* would write beside them.
|
|
@@ -51,7 +51,9 @@ const facts = (lastPage, over = {}) => ({
|
|
|
51
51
|
});
|
|
52
52
|
const MEASURED = (l, r) => ({ kind: "measured", columns_pt: [l, r] });
|
|
53
53
|
|
|
54
|
-
const root = realpathSync(
|
|
54
|
+
const root = realpathSync(
|
|
55
|
+
mkdtempSync(join(tmpdir(), "paperlint-balance-rule-")),
|
|
56
|
+
);
|
|
55
57
|
/** A paper directory with paper.tex, paper.pdf and, unless `null`, `_build/paper.facts.json`. */
|
|
56
58
|
const paper = (name, f, { pdf = PDF } = {}) => {
|
|
57
59
|
const dir = join(root, "papers", name);
|
|
@@ -67,7 +69,7 @@ const paper = (name, f, { pdf = PDF } = {}) => {
|
|
|
67
69
|
return dir;
|
|
68
70
|
};
|
|
69
71
|
|
|
70
|
-
/** Lint files with
|
|
72
|
+
/** Lint files with paperlint's config, the rule turned on for `papers/**` (unless `rules` says otherwise). */
|
|
71
73
|
async function lint(
|
|
72
74
|
files,
|
|
73
75
|
rules = [
|
|
@@ -29,12 +29,14 @@
|
|
|
29
29
|
*
|
|
30
30
|
* Silent, by design, where there is nothing to judge: a stub last page (a few lines) and a review
|
|
31
31
|
* build (numbered lines in the margins make both columns measure full height). Loud where the input
|
|
32
|
-
* is missing: no facts file
|
|
32
|
+
* is missing: no facts file (unless the paper extends a venue preset — then `pdf/measured` says it),
|
|
33
|
+
* a foreign schema, facts about a PDF that is not on disk or changed
|
|
33
34
|
* since — a rule that is on and reads nothing must not look like a rule that passed.
|
|
34
35
|
*/
|
|
35
36
|
import { createHash } from "node:crypto";
|
|
36
37
|
import { existsSync, readFileSync } from "node:fs";
|
|
37
38
|
import { basename, dirname, isAbsolute, join } from "node:path";
|
|
39
|
+
import { PAPER_SETTINGS_FILE } from "../lib/paper-config.mjs";
|
|
38
40
|
|
|
39
41
|
/** The difference, in points, that two columns may end apart. See the harness for why 120. */
|
|
40
42
|
export const DEFAULT_TOLERANCE_PT = 120;
|
|
@@ -133,11 +135,30 @@ function reportLine(file) {
|
|
|
133
135
|
}
|
|
134
136
|
}
|
|
135
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Whether the paper extends a venue preset. Then the venue rules run on it too, and `pdf/measured`
|
|
140
|
+
* already says — once, as a warning — that a paper with no facts was not checked; this rule
|
|
141
|
+
* repeating it as an error would fail a lint-only CI for every paper of a venue whose preset turns
|
|
142
|
+
* this rule on.
|
|
143
|
+
*/
|
|
144
|
+
function extendsPreset(paperDir) {
|
|
145
|
+
try {
|
|
146
|
+
const s = JSON.parse(
|
|
147
|
+
readFileSync(join(paperDir, PAPER_SETTINGS_FILE), "utf8"),
|
|
148
|
+
);
|
|
149
|
+
return typeof s?.extends === "string" && s.extends !== "";
|
|
150
|
+
} catch {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
136
155
|
/** Everything the rule decides for one paper, as a finding or null. */
|
|
137
156
|
function verdict(paperDir, tolerancePt) {
|
|
138
157
|
const factsFile = join(paperDir, FACTS_REL);
|
|
139
158
|
if (!existsSync(factsFile))
|
|
140
|
-
return
|
|
159
|
+
return extendsPreset(paperDir)
|
|
160
|
+
? null
|
|
161
|
+
: { messageId: "noFacts", data: { file: FACTS_REL } };
|
|
141
162
|
const facts = parseFacts(readFileSync(factsFile, "utf8"));
|
|
142
163
|
if (!facts.ok) return { messageId: facts.messageId, data: facts.data };
|
|
143
164
|
return staleness(paperDir, facts) ?? judgeColumns(facts.last, tolerancePt);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "extends": "paperlint:agenticdev", "kind": "short" }
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
% `\input{paper-guards}` resolves with NO configuration:
|
|
1
|
+
% `\input{paper-guards}` resolves with NO configuration: paperlint puts its own venues directory on
|
|
2
2
|
% TEXINPUTS. The guards fire only on the final pass (\finalpass), so a clean paper with a
|
|
3
3
|
% forward \ref must still build.
|
|
4
4
|
\documentclass{article}
|