paperlint 4.0.0 → 4.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 +16 -23
- package/CLAUDE.md +31 -11
- package/CONTRIBUTING.md +23 -7
- package/README.md +11 -8
- package/dist/adapters/banal/invocation.d.ts +1 -1
- package/dist/adapters/latex/document-class.d.ts +22 -0
- package/dist/adapters/latex/document-class.d.ts.map +1 -0
- package/dist/adapters/latex/document-class.js +102 -0
- package/dist/adapters/latex/document-class.js.map +1 -0
- package/dist/adapters/latex/hidden.d.ts +16 -0
- package/dist/adapters/latex/hidden.d.ts.map +1 -0
- package/dist/adapters/latex/hidden.js +49 -0
- package/dist/adapters/latex/hidden.js.map +1 -0
- package/dist/adapters/latex/index.d.ts +29 -0
- package/dist/adapters/latex/index.d.ts.map +1 -0
- package/dist/adapters/latex/index.js +18 -0
- package/dist/adapters/latex/index.js.map +1 -0
- package/dist/adapters/latex/nodes.d.ts +54 -0
- package/dist/adapters/latex/nodes.d.ts.map +1 -0
- package/dist/adapters/latex/nodes.js +93 -0
- package/dist/adapters/latex/nodes.js.map +1 -0
- package/dist/adapters/latex/outline.d.ts +10 -0
- package/dist/adapters/latex/outline.d.ts.map +1 -0
- package/dist/adapters/latex/outline.js +39 -0
- package/dist/adapters/latex/outline.js.map +1 -0
- package/dist/adapters/latex/parse.d.ts +42 -0
- package/dist/adapters/latex/parse.d.ts.map +1 -0
- package/dist/adapters/latex/parse.js +94 -0
- package/dist/adapters/latex/parse.js.map +1 -0
- package/dist/adapters/latex/rendered.d.ts +10 -0
- package/dist/adapters/latex/rendered.d.ts.map +1 -0
- package/dist/adapters/latex/rendered.js +39 -0
- package/dist/adapters/latex/rendered.js.map +1 -0
- package/dist/adapters/pdfjs/fill.d.ts.map +1 -1
- package/dist/adapters/pdfjs/fill.js +12 -12
- package/dist/adapters/pdfjs/fill.js.map +1 -1
- package/dist/build.js +4 -4
- package/dist/build.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +29 -5
- package/dist/cli.js.map +1 -1
- package/dist/domain/tex-document.d.ts +84 -0
- package/dist/domain/tex-document.d.ts.map +1 -0
- package/dist/domain/tex-document.js +43 -0
- package/dist/domain/tex-document.js.map +1 -0
- package/dist/facts-file.d.ts.map +1 -1
- package/dist/facts-file.js +2 -2
- package/dist/facts-file.js.map +1 -1
- package/dist/new-paper.d.ts +5 -0
- package/dist/new-paper.d.ts.map +1 -1
- package/dist/new-paper.js +3 -0
- package/dist/new-paper.js.map +1 -1
- package/dist/package-dirs.d.ts +5 -0
- package/dist/package-dirs.d.ts.map +1 -0
- package/dist/package-dirs.js +14 -0
- package/dist/package-dirs.js.map +1 -0
- package/dist/ports/latex.d.ts +24 -0
- package/dist/ports/latex.d.ts.map +1 -0
- package/dist/ports/latex.js +2 -0
- package/dist/ports/latex.js.map +1 -0
- package/dist/presets.d.ts +15 -2
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +19 -6
- package/dist/presets.js.map +1 -1
- package/dist/tex-requirements.d.ts +11 -0
- package/dist/tex-requirements.d.ts.map +1 -1
- package/dist/tex-requirements.js +14 -8
- package/dist/tex-requirements.js.map +1 -1
- package/dist/tex-venue-rules.d.ts +83 -0
- package/dist/tex-venue-rules.d.ts.map +1 -0
- package/dist/tex-venue-rules.js +254 -0
- package/dist/tex-venue-rules.js.map +1 -0
- package/dist/venue-rules.d.ts +1 -1
- package/dist/venue-rules.d.ts.map +1 -1
- package/docs/configuration.md +3 -2
- package/docs/e2e.md +69 -40
- package/docs/install.md +1 -1
- package/docs/prior-art/README.md +1 -1
- package/docs/prior-art/package-location.md +5 -5
- package/docs/prior-art/repro/README.md +1 -1
- package/docs/prior-art/test-tooling.md +21 -10
- package/docs/rules/tex/required-section.md +73 -0
- package/docs/rules/tex/template.md +62 -0
- package/docs/rules/tex/venue-leftover.md +71 -0
- package/docs/rules.md +70 -48
- package/docs/testing.md +17 -4
- package/docs/texlive-install-decision.md +1 -1
- package/docs/toolchain.md +2 -2
- package/eslint.config.mjs +155 -15
- package/fixtures/build-e2e/acmart/expect.json +26 -0
- package/fixtures/build-e2e/aidc/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/aidc/expect.json +14 -0
- package/fixtures/build-e2e/aidc/paper.tex +46 -0
- package/fixtures/build-e2e/aidc/paperlint.json +1 -0
- package/fixtures/build-e2e/broken/expect.json +11 -0
- package/fixtures/build-e2e/cite/expect.json +27 -0
- package/fixtures/build-e2e/empty/expect.json +12 -0
- package/fixtures/build-e2e/fallback/expect.json +53 -0
- package/fixtures/build-e2e/guards/expect.json +6 -0
- package/fixtures/build-e2e/no-source/expect.json +10 -0
- package/fixtures/build-e2e/unbalanced/expect.json +60 -0
- package/fixtures/pdf-facts/README.md +1 -1
- package/fixtures/real-markdown-paper/baseline.ts +1 -1
- package/fixtures/render-paper/build-clean.sh +3 -5
- package/package.json +13 -2
- package/{skills/submit-paper/references/venues → presets}/acm-sigconf.jsonc +4 -1
- package/{skills/submit-paper/references/venues → presets}/agenticdev.jsonc +4 -0
- package/presets/aidc.jsonc +73 -0
- package/{skills/submit-paper/references/venues → presets}/aisec.jsonc +4 -0
- package/presets/ieee-conference.jsonc +66 -0
- package/{skills/submit-paper/references/venues → presets}/realm.jsonc +8 -1
- package/{skills/submit-paper/references/venues → presets/tex}/paper-guards.tex +1 -1
- package/{skills/submit-paper/references/venues → presets}/venue-profile.schema.json +30 -2
- package/scripts/check.harness.ts +64 -10
- package/scripts/check.test.ts +60 -13
- package/scripts/check.ts +83 -30
- package/scripts/test.test.ts +1 -1
- package/scripts/test.ts +1 -1
- package/skills/paper-pipeline/PIPELINE-MAP.md +2 -2
- package/skills/paper-pipeline/SKILL.md +2 -2
- package/skills/paper-pipeline/SKILL.md.spec.ts +1 -1
- package/skills/paper-pipeline/scripts/consumer.d.mts +0 -2
- package/skills/paper-pipeline/scripts/consumer.mjs +1 -15
- package/skills/render-paper/SKILL.md +2 -2
- package/skills/render-paper/SKILL.md.spec.ts +1 -1
- package/skills/render-paper/check-render.sh +16 -63
- package/skills/render-paper/render-paper.harness.mjs +17 -59
- package/src/adapters/banal/index.test.ts +1 -1
- package/src/adapters/banal/invocation.ts +1 -1
- package/src/adapters/banal/output.test.ts +1 -1
- package/src/adapters/banal/run.test.ts +1 -1
- package/src/adapters/banal/xml.test.ts +1 -1
- package/src/adapters/latex/document-class.ts +147 -0
- package/src/adapters/latex/hidden.ts +70 -0
- package/src/adapters/latex/index.test.ts +361 -0
- package/src/adapters/latex/index.ts +55 -0
- package/src/adapters/latex/nodes.ts +141 -0
- package/src/adapters/latex/outline.ts +64 -0
- package/src/adapters/latex/parse.ts +136 -0
- package/src/adapters/latex/rendered.ts +59 -0
- package/src/adapters/pdfjs/fill.test.ts +1 -1
- package/src/adapters/pdfjs/fill.ts +18 -10
- package/src/build.harness.mjs +5 -7
- package/src/build.ts +4 -4
- package/src/cli.harness.mjs +1 -1
- package/src/cli.test.ts +2 -8
- package/src/cli.ts +33 -8
- package/src/domain/tex-document.test.ts +80 -0
- package/src/domain/tex-document.ts +110 -0
- package/src/engine.harness.mjs +1 -1
- package/src/facts-file.test.ts +4 -7
- package/src/facts-file.ts +2 -2
- package/src/latex-loop.harness.ts +1 -1
- package/src/new-paper.test.ts +28 -3
- package/src/new-paper.ts +8 -0
- package/src/package-dirs.ts +17 -0
- package/src/paper-settings-commands.test.ts +6 -1
- package/src/ports/latex.ts +29 -0
- package/src/presets.test.ts +65 -4
- package/src/presets.ts +38 -15
- package/src/rule-cases.test.ts +474 -0
- package/src/rule-docs.test.ts +159 -0
- package/src/tex-requirements.harness.ts +13 -23
- package/src/tex-requirements.ts +35 -8
- package/src/tex-venue-rules.test.ts +659 -0
- package/src/tex-venue-rules.ts +429 -0
- package/src/toolchain.harness.mjs +1 -1
- package/src/venue-rules.test.ts +96 -4
- package/src/venue-rules.ts +1 -1
- package/templates/paper/paper.tex +2 -1
- /package/{skills/submit-paper/references/venues → presets/tex}/agenticdev.tex +0 -0
- /package/{skills/submit-paper/references/venues → presets}/tex-base.jsonc +0 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# tex/required-section
|
|
2
|
+
|
|
3
|
+
**Level:** error · **Reads:** `paper.tex`, and the preset its `paperlint.json` extends
|
|
4
|
+
|
|
5
|
+
## What it catches
|
|
6
|
+
|
|
7
|
+
A section the venue requires is missing, or is not where the venue requires it. The preset lists
|
|
8
|
+
the required sections by title in `required_sections`; the paper must have a `\section` or
|
|
9
|
+
`\section*` with exactly that title (whitespace collapsed), and, when the preset says
|
|
10
|
+
`position: "last"`, no section of the main text may come after it.
|
|
11
|
+
|
|
12
|
+
It only knows the sections a preset lists. A paper whose `paperlint.json` names no preset, or a
|
|
13
|
+
preset with no `required_sections`, gets no finding.
|
|
14
|
+
|
|
15
|
+
## Why
|
|
16
|
+
|
|
17
|
+
Some venues make a section mandatory and desk-reject without it. ACSAC's AI Usage Policy, which
|
|
18
|
+
its AIDC workshop applies (https://www.acsac.org/2026/submissions/ai/):
|
|
19
|
+
|
|
20
|
+
> If LLMs are used, authors must include a separate, clearly marked section titled "LLM Usage
|
|
21
|
+
> Statement" at the end of the paper. This section does not count towards the page limit.
|
|
22
|
+
|
|
23
|
+
> Failure to comply with these requirements is grounds for desk rejection without further review.
|
|
24
|
+
|
|
25
|
+
A common near miss is a bold paragraph in another venue's wording («Use of Generative AI.»): not a
|
|
26
|
+
section, and not that title.
|
|
27
|
+
|
|
28
|
+
**"If LLMs are used" is not something a rule can know, so the rule always requires the section for
|
|
29
|
+
a preset that lists it.** An author who used none says so in it («No LLMs were used in this
|
|
30
|
+
work.»).
|
|
31
|
+
|
|
32
|
+
## Examples
|
|
33
|
+
|
|
34
|
+
Under a preset with
|
|
35
|
+
`"required_sections": [{ "title": "LLM Usage Statement", "position": "last" }]`:
|
|
36
|
+
|
|
37
|
+
```latex
|
|
38
|
+
\paragraph{Use of Generative AI.} We used an LLM. % fails: a paragraph, not a section
|
|
39
|
+
\section*{LLM usage statement} % fails: not the exact title
|
|
40
|
+
\section*{LLM Usage Statement} % passes, after the last section of the text
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
A missing section reports:
|
|
44
|
+
|
|
45
|
+
> aidc requires a section titled «LLM Usage Statement» and there is none — add
|
|
46
|
+
> `\section*{LLM Usage Statement}` (the title exactly; a bold paragraph does not count)
|
|
47
|
+
|
|
48
|
+
The right title in the wrong place — before the introduction — reports:
|
|
49
|
+
|
|
50
|
+
> «LLM Usage Statement» must close the paper at aidc, and the section «Introduction» comes after it
|
|
51
|
+
> — move it after the last section of the body (after the bibliography is fine)
|
|
52
|
+
|
|
53
|
+
## Preset field
|
|
54
|
+
|
|
55
|
+
No rule options. It reads `required_sections`; a child preset's list replaces its parent's:
|
|
56
|
+
|
|
57
|
+
```jsonc
|
|
58
|
+
"required_sections": [{ "title": "LLM Usage Statement", "position": "last" }]
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`title` is compared exactly after collapsing whitespace — the venue's words are the contract.
|
|
62
|
+
`position: "last"` requires it after every section of the main text, which is every section before
|
|
63
|
+
`\appendix` and the bibliography (`\bibliography`, `\printbibliography`, `thebibliography`); after
|
|
64
|
+
the bibliography or an appendix is fine. Without `position`, anywhere will do.
|
|
65
|
+
|
|
66
|
+
## What it does not check
|
|
67
|
+
|
|
68
|
+
- Whether LLMs were used, or what the section says.
|
|
69
|
+
- Sections pulled in with `\input` or `\include`: the rule reads `paper.tex` alone.
|
|
70
|
+
|
|
71
|
+
## How to fix
|
|
72
|
+
|
|
73
|
+
Add `\section*{<the title>}` where the message says, with the content the venue asks for.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# tex/template
|
|
2
|
+
|
|
3
|
+
**Level:** error · **Reads:** `paper.tex`, and the preset its `paperlint.json` extends
|
|
4
|
+
|
|
5
|
+
## What it catches
|
|
6
|
+
|
|
7
|
+
The paper is not set in the class its venue requires. The rule compares the `\documentclass` line
|
|
8
|
+
of `paper.tex` with the preset's `template`: the class must be the same, and every option the
|
|
9
|
+
template names must be there. The paper may add options of its own.
|
|
10
|
+
|
|
11
|
+
It only knows the class a preset names. A paper whose `paperlint.json` names no preset, or a
|
|
12
|
+
preset with no `template`, gets no finding.
|
|
13
|
+
|
|
14
|
+
## Why
|
|
15
|
+
|
|
16
|
+
A venue checks the template before anyone reads the paper, and a paper in the wrong template can
|
|
17
|
+
be rejected with no review. Venues that name a class say so plainly — AIDC, for one: «LaTeX
|
|
18
|
+
submissions must use the IEEEtran.cls version 1.8b template with the
|
|
19
|
+
\documentclass[conference,compsoc]{IEEEtran} document class option», and «This requirement will be
|
|
20
|
+
strictly enforced» (https://aidcworkshop.github.io/).
|
|
21
|
+
|
|
22
|
+
The `pdf/*` rules see the consequences of a wrong class only after a build — fonts, page size,
|
|
23
|
+
columns — and some not at all: `compsoc` changes the text block and the headings, not the fonts.
|
|
24
|
+
This rule reads the cause, in the source, before any build.
|
|
25
|
+
|
|
26
|
+
## Examples
|
|
27
|
+
|
|
28
|
+
Under a preset with `"template": "\\documentclass[conference,compsoc]{IEEEtran}"`:
|
|
29
|
+
|
|
30
|
+
```latex
|
|
31
|
+
\documentclass[conference]{IEEEtran} % fails: compsoc is missing
|
|
32
|
+
\documentclass[sigconf]{acmart} % fails: another class
|
|
33
|
+
\documentclass[compsoc, conference]{IEEEtran} % passes: any order, spaces allowed
|
|
34
|
+
\documentclass[conference,compsoc,final]{IEEEtran} % passes: an extra option of the paper's own
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The first line reports:
|
|
38
|
+
|
|
39
|
+
> the class option `compsoc` is missing: aidc requires `\documentclass[conference,compsoc]{IEEEtran}`
|
|
40
|
+
|
|
41
|
+
## Preset field
|
|
42
|
+
|
|
43
|
+
No rule options. It reads `template`, merged along `extends` (a child's replaces its parent's):
|
|
44
|
+
|
|
45
|
+
```jsonc
|
|
46
|
+
"template": "\\documentclass[conference,compsoc]{IEEEtran}"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
A whole `\documentclass[…]{…}` line, or a bare class name (`"article"`), which requires the class
|
|
50
|
+
and no option. A value that is neither is reported as a finding that names the preset file.
|
|
51
|
+
|
|
52
|
+
## What it does not check
|
|
53
|
+
|
|
54
|
+
- Options a venue forbids (`review`, `anonymous` in a camera-ready): only that what the venue
|
|
55
|
+
requires is there.
|
|
56
|
+
- The version of the class file (#94).
|
|
57
|
+
- A `\documentclass` inside a comment: the rule reads the parse tree, so it does not count.
|
|
58
|
+
|
|
59
|
+
## How to fix
|
|
60
|
+
|
|
61
|
+
Set the `\documentclass` line to the one the message quotes, keeping your own extra options. A new
|
|
62
|
+
paper made with `paperlint new <name> --venue <preset>` already has it.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# tex/venue-leftover
|
|
2
|
+
|
|
3
|
+
**Level:** warn · **Reads:** `paper.tex`, and the shipped venue presets
|
|
4
|
+
|
|
5
|
+
## What it catches
|
|
6
|
+
|
|
7
|
+
The paper mentions a venue other than the one set in its `paperlint.json`. Usually that is text
|
|
8
|
+
left over from an earlier submission: "Submission to VenueA 2026" in the author block of a paper
|
|
9
|
+
now going to VenueB.
|
|
10
|
+
|
|
11
|
+
It only knows the venues paperlint ships a preset for, by each preset's `name` and `aliases`. A
|
|
12
|
+
venue with no preset is not recognized.
|
|
13
|
+
|
|
14
|
+
It looks at what a reader sees — prose, the author block, captions, footnotes, a citation's note in
|
|
15
|
+
square brackets, and front-matter macros such as `\acmConference` — and ignores comments, citation
|
|
16
|
+
keys, labels, URLs (a `\url`, and the address of an `\href` — its link text is read), math, code and
|
|
17
|
+
the bibliography. Names are matched as whole words, case-sensitive.
|
|
18
|
+
|
|
19
|
+
A macro definition that names another venue — `\newcommand{\oldvenue}{VenueA}` — is reported at the
|
|
20
|
+
definition, whether or not the macro is used: the rule does not expand macros, so a use such as
|
|
21
|
+
`Submitted to \oldvenue` has no text of its own, and the definition is where the old name lives.
|
|
22
|
+
|
|
23
|
+
## Why
|
|
24
|
+
|
|
25
|
+
A reviewer reads the author block before the abstract. A line naming the previous venue tells them
|
|
26
|
+
the paper was written for somewhere else, and at a double-blind venue it can say more than that.
|
|
27
|
+
Nothing else compares what the source says about the venue with what the settings say.
|
|
28
|
+
|
|
29
|
+
## Examples
|
|
30
|
+
|
|
31
|
+
Under `"extends": "paperlint:realm"`, where the `agenticdev` preset's alias is `AgenticDev`:
|
|
32
|
+
|
|
33
|
+
```latex
|
|
34
|
+
\acmConference[AgenticDev '26]{Workshop on Agentic Development} % warns: «AgenticDev»
|
|
35
|
+
As shown~\cite{agenticdev2025}. % silent: a citation key
|
|
36
|
+
% AgenticDev draft % silent: a comment
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The first line reports:
|
|
40
|
+
|
|
41
|
+
> «AgenticDev» names agenticdev, and this paper extends realm — a leftover from an earlier
|
|
42
|
+
> submission? A reviewer reads it before the abstract. Comments and citation keys are not
|
|
43
|
+
> reported; a sentence that names the other venue on purpose can keep it with a disable directive
|
|
44
|
+
|
|
45
|
+
The same line passes under `"extends": "paperlint:agenticdev"`: it names the paper's own venue.
|
|
46
|
+
|
|
47
|
+
## Preset fields
|
|
48
|
+
|
|
49
|
+
No rule options. It reads every shipped preset's `name` and `aliases`, collected along `extends`:
|
|
50
|
+
|
|
51
|
+
```jsonc
|
|
52
|
+
"aliases": ["AgenticDev"]
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
A name the paper's own chain also declares (two workshops of one parent conference) is never
|
|
56
|
+
reported.
|
|
57
|
+
|
|
58
|
+
## What it does not check
|
|
59
|
+
|
|
60
|
+
- A venue no shipped preset describes.
|
|
61
|
+
- A name split by markup (`\textbf{AI}Sec`), and text pulled in with `\input`.
|
|
62
|
+
|
|
63
|
+
## How to fix
|
|
64
|
+
|
|
65
|
+
Remove or replace the old venue's name. A sentence that names another venue on purpose keeps it
|
|
66
|
+
with ESLint's directive and the reason:
|
|
67
|
+
|
|
68
|
+
```latex
|
|
69
|
+
% eslint-disable-next-line tex/venue-leftover -- comparing with the venue that published [3]
|
|
70
|
+
Unlike AgenticDev, this workshop …
|
|
71
|
+
```
|
package/docs/rules.md
CHANGED
|
@@ -3,30 +3,37 @@
|
|
|
3
3
|
The README lists what each check catches in one line. This page says exactly which file each one
|
|
4
4
|
reads and when it fails. Errors fail `paperlint lint`; warnings print and do not.
|
|
5
5
|
|
|
6
|
-
| Rule
|
|
7
|
-
|
|
|
8
|
-
| `paper/stages`
|
|
9
|
-
| `paper/source`
|
|
10
|
-
| `paper/author-list`
|
|
11
|
-
| `paper/research-question`
|
|
12
|
-
| `paper/section-word`
|
|
13
|
-
| `paper/leading-zero`
|
|
14
|
-
| `paper/figure-ref-style`
|
|
15
|
-
| `bib/reachable-entry`
|
|
16
|
-
| `paper/cite-exists`
|
|
17
|
-
| `paper/refs-fresh`
|
|
18
|
-
| `paper/refs-checked`
|
|
19
|
-
| `tex/future-promise`
|
|
20
|
-
| `tex/acm-frontmatter-override`
|
|
21
|
-
| `
|
|
22
|
-
| `
|
|
23
|
-
| `
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
26
|
-
| `pdf/
|
|
27
|
-
| `pdf/
|
|
28
|
-
| `pdf/
|
|
29
|
-
| `pdf/
|
|
6
|
+
| Rule | Level | Reads | Fails when |
|
|
7
|
+
| ------------------------------------------------------- | ----- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
8
|
+
| `paper/stages` | error | `PIPELINE-STATUS.md` | a declared stage's PDF is missing, or the `bytes:` it names does not match the file's real size, or a frozen PDF exists that no stage declares |
|
|
9
|
+
| `paper/source` | error | `PIPELINE-STATUS.md` | a declared stage has no frozen `.tex` beside its PDF (a commit hash does not count — squash and gc destroy it) |
|
|
10
|
+
| `paper/author-list` | error | `paper.tex` → `_build/references.json` | an entry's authors are not those of the version it cites — typically the arXiv preprint's list under a published venue. Checked online by `paperlint build` (DBLP), reported on the entry's own line |
|
|
11
|
+
| `paper/research-question` | warn | `paper.tex`, `paper.md`, `PIPELINE-STATUS.md` | a stage is declared and either the scorecard has no `researchQuestion` field, or it has one the paper does not contain. The declared sentence is compared against the source with whitespace collapsed — your words, not a pattern. Advisory, because nothing here can judge whether what you declared _is_ a research question |
|
|
12
|
+
| `paper/section-word` | warn | `paper.tex`, `paper.md` | `§` or `\S\ref` instead of the word Section. **Fixable:** `--fix` writes `Section~\ref{…}` / `Section 5` |
|
|
13
|
+
| `paper/leading-zero` | warn | `paper.tex`, `paper.md` | a decimal below 1 without its leading zero (`.05` instead of `0.05`, IEEE / ISO 80000-1 style), counted only where the reader sees it — prose, math and table cells, not macro options, column specs, comments, code or tikz. A number right after `¶` or `§` (`¶¶.42`) is a paragraph designator and is not reported; in a paragraph that cites numbers that way, another bare decimal is reported without a fix, with the zero as a suggestion. **Fixable** |
|
|
14
|
+
| `paper/figure-ref-style` | warn | `paper.tex` | `Fig.~\ref` and `Figure~\ref` mixed in one document; each minority occurrence is reported. **Fixable:** `--fix` writes the majority form |
|
|
15
|
+
| `bib/reachable-entry` | warn | `paper.tex` (its `filecontents` bibliography) | an entry has no doi, url or arXiv id — a reader has nothing to follow. Reported on the entry; no fix, the link has to be looked up |
|
|
16
|
+
| `paper/cite-exists` | error | `paper.tex` → `_build/references.json` | an entry's doi or arXiv id resolves to nothing, or to a different work. Checked online by `paperlint build`; a work that was merely not found is not a finding |
|
|
17
|
+
| `paper/refs-fresh` | error | `paper.tex` → `_build/references.json` | the bibliography changed since the build checked it (its SHA-256 differs) — run `npx paperlint build` |
|
|
18
|
+
| `paper/refs-checked` | warn | `paper.tex` → `_build/references.json` | the paper has a bibliography and no build has checked it, or the last build could not (no network) — so the two rules above did not run |
|
|
19
|
+
| `tex/future-promise` | warn | `paper.tex` | a camera-ready build still says "will be released" about something already handed over |
|
|
20
|
+
| `tex/acm-frontmatter-override` | error | `paper.tex` | an `acmart` build overrides ACM's front-matter commands and drops template elements from page 1 |
|
|
21
|
+
| [`tex/template`](rules/tex/template.md) | error | `paper.tex` → `paperlint.json` | the `\documentclass` is not the preset's `template` class, or lacks an option it names — [page](rules/tex/template.md) |
|
|
22
|
+
| [`tex/required-section`](rules/tex/required-section.md) | error | `paper.tex` → `paperlint.json` | a section the preset requires (AIDC: «LLM Usage Statement», last) is missing or not where it must be — [page](rules/tex/required-section.md) |
|
|
23
|
+
| [`tex/venue-leftover`](rules/tex/venue-leftover.md) | warn | `paper.tex` → `paperlint.json` | the text names another shipped venue than the one the paper extends («Submission to VenueA 2026» in a paper now set for VenueB) — [page](rules/tex/venue-leftover.md) |
|
|
24
|
+
| `review/frontmatter` | error | `reviews/*.md` | the review's frontmatter fails paperlint's JSON Schema — a `findings` record without `id`/`status`, an unknown field, or an **open** finding with no `cause` (`skill-defect` · `missing-skill` · `hook` · `rule`). A review with no `findings` key is not checked for findings |
|
|
25
|
+
| `sibling/frontmatter` | warn | `siblings/*.md` (not `README.md`) | a sibling card's frontmatter has no `read:` saying how much of the competing paper was read (`full` · `abstract` · `none`) |
|
|
26
|
+
| `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) |
|
|
27
|
+
| `pdf/profile` | error | `paper.tex` → `paperlint.json` | `paperlint.json` is not JSON or has an unknown key, 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 |
|
|
28
|
+
| `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`) |
|
|
29
|
+
| `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 |
|
|
30
|
+
| `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` |
|
|
31
|
+
| `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) |
|
|
32
|
+
| `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 |
|
|
33
|
+
|
|
34
|
+
`tex/required-section` requires AIDC's «LLM Usage Statement» **always**. ACSAC asks for it «if
|
|
35
|
+
LLMs are used», which no rule can know; an author who used none says so in the section
|
|
36
|
+
([page](rules/tex/required-section.md)).
|
|
30
37
|
|
|
31
38
|
Optional rules — off unless you turn them on in the `rules` setting, because only some venues need
|
|
32
39
|
them — are on their own page: [`optional-rules.md`](optional-rules.md). Today there is one,
|
|
@@ -79,21 +86,34 @@ for papers, each with the quote it came from, the TeX packages its template need
|
|
|
79
86
|
the venue implies. A preset may itself extend another: the ACM venues extend the `acm-sigconf`
|
|
80
87
|
family, which holds everything the ACM template decides.
|
|
81
88
|
|
|
82
|
-
| preset
|
|
83
|
-
|
|
|
84
|
-
| `paperlint:acm-sigconf`
|
|
85
|
-
| `paperlint:agenticdev`
|
|
86
|
-
| `paperlint:aisec`
|
|
87
|
-
| `paperlint:realm`
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
89
|
+
| preset | extends | template | kinds | page limit checked | rules it turns on |
|
|
90
|
+
| --------------------------- | --------------------------- | ------------------------------------- | -------------------------------------------------------- | ------------------ | ----------------------- |
|
|
91
|
+
| `paperlint:acm-sigconf` | — | ACM `acmart` sigconf | none | no (no kinds) | — |
|
|
92
|
+
| `paperlint:agenticdev` | `paperlint:acm-sigconf` | ACM `acmart` sigconf | `short` (5 + 2 refs), `full` (10 + 2), `demo` (5 + 2) | yes | `pdf/last-page-balance` |
|
|
93
|
+
| `paperlint:aisec` | `paperlint:acm-sigconf` | ACM `acmart` sigconf | `research`, `benchmark`, `position`, `sok` (10 + 2 each) | yes | — |
|
|
94
|
+
| `paperlint:realm` | — | ACL | `long`, `short` | no — see below | — |
|
|
95
|
+
| `paperlint:ieee-conference` | — | IEEE `IEEEtran` conference | none | no (no kinds) | — |
|
|
96
|
+
| `paperlint:aidc` | `paperlint:ieee-conference` | IEEE `IEEEtran` conference, `compsoc` | `regular` (12), `short` (6) | no — see below | — |
|
|
97
|
+
|
|
98
|
+
That is all that ships today. There is no NeurIPS, USENIX or Springer preset. A paper for an ACM or
|
|
99
|
+
IEEE conference venue nobody has profiled can extend `paperlint:acm-sigconf` or
|
|
100
|
+
`paperlint:ieee-conference` directly: page size, columns and fonts are checked, and `pdf/profile`
|
|
101
|
+
says the page limit is not. For anything else, write your own preset
|
|
102
|
+
([below](#writing-your-own-venue-preset)). REALM's preset sets no page limit on purpose: banal
|
|
103
|
+
counts the Limitations and Ethics sections as body, ACL does not, and a limit on banal's number
|
|
104
|
+
would fail a correct paper. AIDC's sets none for the same reason, measured: banal counts a page as
|
|
105
|
+
body when an appendix or the LLM Usage Statement sits on it before the bibliography, and AIDC counts
|
|
106
|
+
neither, so a correct 12-page paper measured 13. AgenticDev's turns on `pdf/last-page-balance`
|
|
107
|
+
because its proceedings are produced by Conference Publishing Consulting, which sends back an
|
|
108
|
+
unbalanced last page; AISec's does not, because nothing in hand says who produces the AISec
|
|
109
|
+
proceedings.
|
|
110
|
+
|
|
111
|
+
**How presets are named.** A template family — the preset venues extend, owning the template,
|
|
112
|
+
the format and the TeX packages — is `<publisher>-<template variant>`: `acm-sigconf`, `ieee-conference`. A venue
|
|
113
|
+
is its own short name, lowercase, with no publisher and no parent-conference prefix: `aisec`,
|
|
114
|
+
`agenticdev`, `realm`, `aidc`. Which template a venue uses is said by its `extends`, not by its
|
|
115
|
+
name — a name repeating it would be wrong the day the venue changes template — and the parent
|
|
116
|
+
conference (CCS, ASE, ACSAC) goes into the preset's comment and its `aliases`.
|
|
97
117
|
|
|
98
118
|
**Build, then lint.** The rules judge what `paperlint build` measured and wrote to
|
|
99
119
|
`_build/paper.facts.json` — page count, fonts, and, through banal, page size, columns, font sizes
|
|
@@ -130,7 +150,7 @@ To skip a check for one paper — a finding you accept — set it to `"off"` in
|
|
|
130
150
|
|
|
131
151
|
A preset is a JSONC file (JSON with comments — keep the call-for-papers quote beside each number)
|
|
132
152
|
of the same shape as the shipped ones, validated by
|
|
133
|
-
[`venue-profile.schema.json`](../
|
|
153
|
+
[`venue-profile.schema.json`](../presets/venue-profile.schema.json).
|
|
134
154
|
Put it in your repository and extend it by a path relative to the file that names it:
|
|
135
155
|
|
|
136
156
|
```
|
|
@@ -170,14 +190,16 @@ papers/
|
|
|
170
190
|
|
|
171
191
|
The numbers and packages above illustrate the shape; take yours from the venue's own call for papers and template.
|
|
172
192
|
|
|
173
|
-
| key
|
|
174
|
-
|
|
|
175
|
-
| `extends`
|
|
176
|
-
| `name`
|
|
177
|
-
| `template`
|
|
178
|
-
| `
|
|
179
|
-
| `
|
|
180
|
-
| `
|
|
193
|
+
| key | what it is |
|
|
194
|
+
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
195
|
+
| `extends` | the preset this one builds on: `paperlint:<name>` or `./path` / `../path`, relative to this file |
|
|
196
|
+
| `name` | how messages name the venue; the file name otherwise |
|
|
197
|
+
| `template` | the `\documentclass` the venue's template uses — a whole line (`\documentclass[conference,compsoc]{IEEEtran}`) or a bare class name; `tex/template` requires the paper's class and every option named here |
|
|
198
|
+
| `aliases` | other names the venue goes by in a paper's text — its acronym, its parent conference: `["AISec", "ACM CCS"]`. Collected along `extends`, with `name`. Read by `tex/venue-leftover` |
|
|
199
|
+
| `required_sections` | sections the venue requires, `[{ "title": "LLM Usage Statement", "position": "last" }]`: the exact title, and `last` when it must close the paper. Read by `tex/required-section`; a child's list replaces its parent's |
|
|
200
|
+
| `format` | page size, columns, fonts, font sizes, and `kinds` (page limits per kind of paper) |
|
|
201
|
+
| `tex` | TeX Live packages, each with the files that prove it is installed. Required unless `extends` is set |
|
|
202
|
+
| `rules` | rules the venue implies, rule id → severity or `[severity, options]` |
|
|
181
203
|
|
|
182
204
|
How a chain merges, from the root preset to the paper: `tex` is the union — a child never removes a
|
|
183
205
|
package; `format` keys are replaced one by one, and a child's kind replaces that kind whole;
|
package/docs/testing.md
CHANGED
|
@@ -40,6 +40,11 @@ When in doubt between unit and integration: if the function takes a port (`Files
|
|
|
40
40
|
- **Example:** `src/adapters/references/index.test.ts` — the reference checker over a fake
|
|
41
41
|
`fetch`: a cold run stores every answer, a warm run makes no request at all (the recorded list of
|
|
42
42
|
requests is empty), an edited entry asks only for itself.
|
|
43
|
+
- **A rule through real ESLint is integration, not e2e:** `src/rule-cases.test.ts` reads every rule
|
|
44
|
+
the registry holds (`rulePlugins`) and runs each through `paperlint lint --json` in-process on a
|
|
45
|
+
small paper tree — the CLI's own config, the `tex/latex` language, `loc`, message templating. Each
|
|
46
|
+
rule needs a case that reports (id, severity, line) and one that stays silent; a registered rule
|
|
47
|
+
without cases fails the test by name. A `_build/` file a rule reads is planted, not built.
|
|
43
48
|
|
|
44
49
|
## Harness — vigiles, the agent-facing surface only
|
|
45
50
|
|
|
@@ -56,14 +61,20 @@ When in doubt between unit and integration: if the function takes a port (`Files
|
|
|
56
61
|
- **Skills are tested through vigiles**, not a bespoke runner: a home-grown runner here once printed
|
|
57
62
|
byte-identical "clean" verdicts for three skills that had never loaded.
|
|
58
63
|
|
|
59
|
-
## End-to-end — `test/e2e
|
|
64
|
+
## End-to-end — `test/e2e/<area>/*.e2e.ts`
|
|
60
65
|
|
|
61
66
|
- **Answers:** does the package work once it is somewhere else? The tarball installed into another
|
|
62
67
|
tree, a real `pdflatex` build, the PDF's fonts measured.
|
|
63
68
|
- **May touch:** everything a user's machine has, except the network: a citation service is a
|
|
64
|
-
counting fake `fetch` even here (`test/e2e/build.ts`).
|
|
69
|
+
counting fake `fetch` even here (`test/e2e/tex/build.e2e.ts`).
|
|
65
70
|
- **Types:** the runs import the built modules from `dist/` (the shipped path is the point), so
|
|
66
71
|
their types are the declarations `tsc` emits — `npm run build` before type-checking them.
|
|
72
|
+
- **Where:** a directory per environment the tests need — `test/e2e/install/` (npm and pnpm),
|
|
73
|
+
`test/e2e/tex/` (TeX Live) — each a vitest project that finds `*.e2e.ts` by its glob. A new e2e
|
|
74
|
+
file needs no list anywhere. Helpers beside them are named without `.e2e.ts`.
|
|
75
|
+
- **A missing tool:** `missing(what, present)` from `test/e2e/need.ts` → `describe.skipIf`. Locally
|
|
76
|
+
the tests are reported skipped; with `CI` set they fail.
|
|
77
|
+
- **A build fixture:** a folder in `fixtures/build-e2e/` with its own `expect.json`; no test code.
|
|
67
78
|
- **What each run proves, and when a change owes one:** [`e2e.md`](e2e.md).
|
|
68
79
|
|
|
69
80
|
## Running
|
|
@@ -71,9 +82,11 @@ When in doubt between unit and integration: if the function takes a port (`Files
|
|
|
71
82
|
```bash
|
|
72
83
|
npx vitest run <file or folder> # unit and integration tests
|
|
73
84
|
npx vigiles test <file> # one harness
|
|
74
|
-
npm test #
|
|
85
|
+
npm test # unit and integration (vitest project `unit`), then every harness
|
|
75
86
|
npm run coverage # the same under c8, failing below the thresholds in .c8rc.json
|
|
76
|
-
npm run test:e2e #
|
|
87
|
+
npm run test:e2e # every end-to-end run
|
|
88
|
+
npm run test:e2e:install # the package packed and installed under npm and pnpm
|
|
89
|
+
npm run test:e2e:tex # real TeX Live: toolchain, builds of every fixture, banal
|
|
77
90
|
npm run check # every gate CI runs, and the list of what it does not reproduce
|
|
78
91
|
```
|
|
79
92
|
|
|
@@ -18,7 +18,7 @@ another"_.
|
|
|
18
18
|
> **Where this ended up (2026-09-24).** Method 5 below won, and it is no longer a CI script: it is
|
|
19
19
|
> `paperlint toolchain` (`src/toolchain.ts`), the command users run and the `build-e2e` job runs on Linux
|
|
20
20
|
> and macOS. The package list and the file contract (`REQUIRED_FILES` in the deleted
|
|
21
|
-
> `ensure-toolchain.sh`) moved into the venue profiles — each `
|
|
21
|
+
> `ensure-toolchain.sh`) moved into the venue profiles — each `presets/*.jsonc`
|
|
22
22
|
> declares a `tex` block of CTAN package → proof files, and `tex-base.jsonc` the set every paper
|
|
23
23
|
> gets. This file stays as the record of why tlmgr and not apt or a container; the scripts it names
|
|
24
24
|
> (`ensure-toolchain.sh`, `ci-install-texlive.sh`) were deleted in the same change.
|
package/docs/toolchain.md
CHANGED
|
@@ -58,7 +58,7 @@ that has every declared package, so a new-year install that was interrupted neve
|
|
|
58
58
|
complete older one.
|
|
59
59
|
|
|
60
60
|
**Where the package list lives:** in the venue profiles, not here and not in a script.
|
|
61
|
-
`
|
|
61
|
+
`presets/<venue>.jsonc` carries a `tex` block — CTAN package → the
|
|
62
62
|
files that prove it is installed — and `tex-base.jsonc` carries what every paper gets, and all a
|
|
63
63
|
paper with an unknown venue gets. The shape is `venue-profile.schema.json` in the same directory.
|
|
64
64
|
A venue that needs a new package gets one line there.
|
|
@@ -97,7 +97,7 @@ accepts as input. Measured on 50 PDFs / 598 pages (2026-09-25): banal on paperli
|
|
|
97
97
|
real `pdftohtml` agree on every field the facts file keeps, with no venue verdict changed. They
|
|
98
98
|
agree only because paperlint leaves out rotated and invisible text (as `pdftohtml` does), writes each
|
|
99
99
|
text's colour so banal drops light text by its own rule, and writes sizes and coordinates at the
|
|
100
|
-
zoom and precision banal expects. `test/e2e/banal.ts` checks each of those against the real banal
|
|
100
|
+
zoom and precision banal expects. `test/e2e/tex/banal.e2e.ts` checks each of those against the real banal
|
|
101
101
|
on the committed fixtures.
|
|
102
102
|
|
|
103
103
|
**How it is installed — and the licence boundary.** banal is **GPL-2.0-or-later**; paperlint is MIT. paperlint
|