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.
Files changed (172) hide show
  1. package/.github/workflows/ci.yml +16 -23
  2. package/CLAUDE.md +31 -11
  3. package/CONTRIBUTING.md +23 -7
  4. package/README.md +11 -8
  5. package/dist/adapters/banal/invocation.d.ts +1 -1
  6. package/dist/adapters/latex/document-class.d.ts +22 -0
  7. package/dist/adapters/latex/document-class.d.ts.map +1 -0
  8. package/dist/adapters/latex/document-class.js +102 -0
  9. package/dist/adapters/latex/document-class.js.map +1 -0
  10. package/dist/adapters/latex/hidden.d.ts +16 -0
  11. package/dist/adapters/latex/hidden.d.ts.map +1 -0
  12. package/dist/adapters/latex/hidden.js +49 -0
  13. package/dist/adapters/latex/hidden.js.map +1 -0
  14. package/dist/adapters/latex/index.d.ts +29 -0
  15. package/dist/adapters/latex/index.d.ts.map +1 -0
  16. package/dist/adapters/latex/index.js +18 -0
  17. package/dist/adapters/latex/index.js.map +1 -0
  18. package/dist/adapters/latex/nodes.d.ts +54 -0
  19. package/dist/adapters/latex/nodes.d.ts.map +1 -0
  20. package/dist/adapters/latex/nodes.js +93 -0
  21. package/dist/adapters/latex/nodes.js.map +1 -0
  22. package/dist/adapters/latex/outline.d.ts +10 -0
  23. package/dist/adapters/latex/outline.d.ts.map +1 -0
  24. package/dist/adapters/latex/outline.js +39 -0
  25. package/dist/adapters/latex/outline.js.map +1 -0
  26. package/dist/adapters/latex/parse.d.ts +42 -0
  27. package/dist/adapters/latex/parse.d.ts.map +1 -0
  28. package/dist/adapters/latex/parse.js +94 -0
  29. package/dist/adapters/latex/parse.js.map +1 -0
  30. package/dist/adapters/latex/rendered.d.ts +10 -0
  31. package/dist/adapters/latex/rendered.d.ts.map +1 -0
  32. package/dist/adapters/latex/rendered.js +39 -0
  33. package/dist/adapters/latex/rendered.js.map +1 -0
  34. package/dist/adapters/pdfjs/fill.d.ts.map +1 -1
  35. package/dist/adapters/pdfjs/fill.js +12 -12
  36. package/dist/adapters/pdfjs/fill.js.map +1 -1
  37. package/dist/build.js +4 -4
  38. package/dist/build.js.map +1 -1
  39. package/dist/cli.d.ts.map +1 -1
  40. package/dist/cli.js +29 -5
  41. package/dist/cli.js.map +1 -1
  42. package/dist/domain/tex-document.d.ts +84 -0
  43. package/dist/domain/tex-document.d.ts.map +1 -0
  44. package/dist/domain/tex-document.js +43 -0
  45. package/dist/domain/tex-document.js.map +1 -0
  46. package/dist/facts-file.d.ts.map +1 -1
  47. package/dist/facts-file.js +2 -2
  48. package/dist/facts-file.js.map +1 -1
  49. package/dist/new-paper.d.ts +5 -0
  50. package/dist/new-paper.d.ts.map +1 -1
  51. package/dist/new-paper.js +3 -0
  52. package/dist/new-paper.js.map +1 -1
  53. package/dist/package-dirs.d.ts +5 -0
  54. package/dist/package-dirs.d.ts.map +1 -0
  55. package/dist/package-dirs.js +14 -0
  56. package/dist/package-dirs.js.map +1 -0
  57. package/dist/ports/latex.d.ts +24 -0
  58. package/dist/ports/latex.d.ts.map +1 -0
  59. package/dist/ports/latex.js +2 -0
  60. package/dist/ports/latex.js.map +1 -0
  61. package/dist/presets.d.ts +15 -2
  62. package/dist/presets.d.ts.map +1 -1
  63. package/dist/presets.js +19 -6
  64. package/dist/presets.js.map +1 -1
  65. package/dist/tex-requirements.d.ts +11 -0
  66. package/dist/tex-requirements.d.ts.map +1 -1
  67. package/dist/tex-requirements.js +14 -8
  68. package/dist/tex-requirements.js.map +1 -1
  69. package/dist/tex-venue-rules.d.ts +83 -0
  70. package/dist/tex-venue-rules.d.ts.map +1 -0
  71. package/dist/tex-venue-rules.js +254 -0
  72. package/dist/tex-venue-rules.js.map +1 -0
  73. package/dist/venue-rules.d.ts +1 -1
  74. package/dist/venue-rules.d.ts.map +1 -1
  75. package/docs/configuration.md +3 -2
  76. package/docs/e2e.md +69 -40
  77. package/docs/install.md +1 -1
  78. package/docs/prior-art/README.md +1 -1
  79. package/docs/prior-art/package-location.md +5 -5
  80. package/docs/prior-art/repro/README.md +1 -1
  81. package/docs/prior-art/test-tooling.md +21 -10
  82. package/docs/rules/tex/required-section.md +73 -0
  83. package/docs/rules/tex/template.md +62 -0
  84. package/docs/rules/tex/venue-leftover.md +71 -0
  85. package/docs/rules.md +70 -48
  86. package/docs/testing.md +17 -4
  87. package/docs/texlive-install-decision.md +1 -1
  88. package/docs/toolchain.md +2 -2
  89. package/eslint.config.mjs +155 -15
  90. package/fixtures/build-e2e/acmart/expect.json +26 -0
  91. package/fixtures/build-e2e/aidc/PIPELINE-STATUS.md +3 -0
  92. package/fixtures/build-e2e/aidc/expect.json +14 -0
  93. package/fixtures/build-e2e/aidc/paper.tex +46 -0
  94. package/fixtures/build-e2e/aidc/paperlint.json +1 -0
  95. package/fixtures/build-e2e/broken/expect.json +11 -0
  96. package/fixtures/build-e2e/cite/expect.json +27 -0
  97. package/fixtures/build-e2e/empty/expect.json +12 -0
  98. package/fixtures/build-e2e/fallback/expect.json +53 -0
  99. package/fixtures/build-e2e/guards/expect.json +6 -0
  100. package/fixtures/build-e2e/no-source/expect.json +10 -0
  101. package/fixtures/build-e2e/unbalanced/expect.json +60 -0
  102. package/fixtures/pdf-facts/README.md +1 -1
  103. package/fixtures/real-markdown-paper/baseline.ts +1 -1
  104. package/fixtures/render-paper/build-clean.sh +3 -5
  105. package/package.json +13 -2
  106. package/{skills/submit-paper/references/venues → presets}/acm-sigconf.jsonc +4 -1
  107. package/{skills/submit-paper/references/venues → presets}/agenticdev.jsonc +4 -0
  108. package/presets/aidc.jsonc +73 -0
  109. package/{skills/submit-paper/references/venues → presets}/aisec.jsonc +4 -0
  110. package/presets/ieee-conference.jsonc +66 -0
  111. package/{skills/submit-paper/references/venues → presets}/realm.jsonc +8 -1
  112. package/{skills/submit-paper/references/venues → presets/tex}/paper-guards.tex +1 -1
  113. package/{skills/submit-paper/references/venues → presets}/venue-profile.schema.json +30 -2
  114. package/scripts/check.harness.ts +64 -10
  115. package/scripts/check.test.ts +60 -13
  116. package/scripts/check.ts +83 -30
  117. package/scripts/test.test.ts +1 -1
  118. package/scripts/test.ts +1 -1
  119. package/skills/paper-pipeline/PIPELINE-MAP.md +2 -2
  120. package/skills/paper-pipeline/SKILL.md +2 -2
  121. package/skills/paper-pipeline/SKILL.md.spec.ts +1 -1
  122. package/skills/paper-pipeline/scripts/consumer.d.mts +0 -2
  123. package/skills/paper-pipeline/scripts/consumer.mjs +1 -15
  124. package/skills/render-paper/SKILL.md +2 -2
  125. package/skills/render-paper/SKILL.md.spec.ts +1 -1
  126. package/skills/render-paper/check-render.sh +16 -63
  127. package/skills/render-paper/render-paper.harness.mjs +17 -59
  128. package/src/adapters/banal/index.test.ts +1 -1
  129. package/src/adapters/banal/invocation.ts +1 -1
  130. package/src/adapters/banal/output.test.ts +1 -1
  131. package/src/adapters/banal/run.test.ts +1 -1
  132. package/src/adapters/banal/xml.test.ts +1 -1
  133. package/src/adapters/latex/document-class.ts +147 -0
  134. package/src/adapters/latex/hidden.ts +70 -0
  135. package/src/adapters/latex/index.test.ts +361 -0
  136. package/src/adapters/latex/index.ts +55 -0
  137. package/src/adapters/latex/nodes.ts +141 -0
  138. package/src/adapters/latex/outline.ts +64 -0
  139. package/src/adapters/latex/parse.ts +136 -0
  140. package/src/adapters/latex/rendered.ts +59 -0
  141. package/src/adapters/pdfjs/fill.test.ts +1 -1
  142. package/src/adapters/pdfjs/fill.ts +18 -10
  143. package/src/build.harness.mjs +5 -7
  144. package/src/build.ts +4 -4
  145. package/src/cli.harness.mjs +1 -1
  146. package/src/cli.test.ts +2 -8
  147. package/src/cli.ts +33 -8
  148. package/src/domain/tex-document.test.ts +80 -0
  149. package/src/domain/tex-document.ts +110 -0
  150. package/src/engine.harness.mjs +1 -1
  151. package/src/facts-file.test.ts +4 -7
  152. package/src/facts-file.ts +2 -2
  153. package/src/latex-loop.harness.ts +1 -1
  154. package/src/new-paper.test.ts +28 -3
  155. package/src/new-paper.ts +8 -0
  156. package/src/package-dirs.ts +17 -0
  157. package/src/paper-settings-commands.test.ts +6 -1
  158. package/src/ports/latex.ts +29 -0
  159. package/src/presets.test.ts +65 -4
  160. package/src/presets.ts +38 -15
  161. package/src/rule-cases.test.ts +474 -0
  162. package/src/rule-docs.test.ts +159 -0
  163. package/src/tex-requirements.harness.ts +13 -23
  164. package/src/tex-requirements.ts +35 -8
  165. package/src/tex-venue-rules.test.ts +659 -0
  166. package/src/tex-venue-rules.ts +429 -0
  167. package/src/toolchain.harness.mjs +1 -1
  168. package/src/venue-rules.test.ts +96 -4
  169. package/src/venue-rules.ts +1 -1
  170. package/templates/paper/paper.tex +2 -1
  171. /package/{skills/submit-paper/references/venues → presets/tex}/agenticdev.tex +0 -0
  172. /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 | 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
- | `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 |
22
- | `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`) |
23
- | `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) |
24
- | `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 |
25
- | `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`) |
26
- | `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 |
27
- | `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` |
28
- | `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) |
29
- | `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 |
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 | extends | template | kinds | page limit checked | rules it turns on |
83
- | ----------------------- | ----------------------- | -------------------- | -------------------------------------------------------- | ------------------ | ----------------------- |
84
- | `paperlint:acm-sigconf` | — | ACM `acmart` sigconf | none | no (no kinds) | — |
85
- | `paperlint:agenticdev` | `paperlint:acm-sigconf` | ACM `acmart` sigconf | `short` (5 + 2 refs), `full` (10 + 2), `demo` (5 + 2) | yes | `pdf/last-page-balance` |
86
- | `paperlint:aisec` | `paperlint:acm-sigconf` | ACM `acmart` sigconf | `research`, `benchmark`, `position`, `sok` (10 + 2 each) | yes | — |
87
- | `paperlint:realm` | — | ACL | `long`, `short` | no — see below | — |
88
-
89
- That is all that ships today. There is no IEEE, NeurIPS, USENIX or Springer preset. A paper for an
90
- ACM venue nobody has profiled can extend `paperlint:acm-sigconf` directly: page size, columns and
91
- fonts are checked, and `pdf/profile` says the page limit is not. For anything else, write your own
92
- preset ([below](#writing-your-own-venue-preset)). REALM's preset sets no page limit on purpose:
93
- banal counts the Limitations and Ethics sections as body, ACL does not, and a limit on banal's
94
- number would fail a correct paper. AgenticDev's turns on `pdf/last-page-balance` because its
95
- proceedings are produced by Conference Publishing Consulting, which sends back an unbalanced last
96
- page; AISec's does not, because nothing in hand says who produces the AISec proceedings.
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`](../skills/submit-paper/references/venues/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 | what it is |
174
- | ---------- | --------------------------------------------------------------------------------------------------- |
175
- | `extends` | the preset this one builds on: `paperlint:<name>` or `./path` / `../path`, relative to this file |
176
- | `name` | how messages name the venue; the file name otherwise |
177
- | `template` | the `\documentclass` the venue's template uses |
178
- | `format` | page size, columns, fonts, font sizes, and `kinds` (page limits per kind of paper) |
179
- | `tex` | TeX Live packages, each with the files that prove it is installed. Required unless `extends` is set |
180
- | `rules` | rules the venue implies, rule id → severity or `[severity, options]` |
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/*.ts`
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 # everything: vitest, then every harness
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 # the end-to-end runs
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 `skills/submit-paper/references/venues/*.jsonc`
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
- `skills/submit-paper/references/venues/<venue>.jsonc` carries a `tex` block — CTAN package → the
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