paperlint 7.0.0 → 7.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 (75) hide show
  1. package/README.md +18 -11
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +2 -2
  4. package/dist/cli.js.map +1 -1
  5. package/dist/domain/body-pages.d.ts +29 -0
  6. package/dist/domain/body-pages.d.ts.map +1 -1
  7. package/dist/domain/body-pages.js +25 -0
  8. package/dist/domain/body-pages.js.map +1 -1
  9. package/dist/paper-includes.d.ts +3 -0
  10. package/dist/paper-includes.d.ts.map +1 -1
  11. package/dist/paper-includes.js +17 -2
  12. package/dist/paper-includes.js.map +1 -1
  13. package/dist/presets.d.ts +10 -3
  14. package/dist/presets.d.ts.map +1 -1
  15. package/dist/presets.js +8 -7
  16. package/dist/presets.js.map +1 -1
  17. package/dist/submission.d.ts +8 -0
  18. package/dist/submission.d.ts.map +1 -1
  19. package/dist/submission.js +19 -7
  20. package/dist/submission.js.map +1 -1
  21. package/dist/tex-requirements.d.ts +9 -0
  22. package/dist/tex-requirements.d.ts.map +1 -1
  23. package/dist/tex-requirements.js +7 -3
  24. package/dist/tex-requirements.js.map +1 -1
  25. package/dist/tex-venue-rules.d.ts +5 -2
  26. package/dist/tex-venue-rules.d.ts.map +1 -1
  27. package/dist/tex-venue-rules.js +29 -10
  28. package/dist/tex-venue-rules.js.map +1 -1
  29. package/dist/venue-rules.d.ts +2 -1
  30. package/dist/venue-rules.d.ts.map +1 -1
  31. package/dist/venue-rules.js +55 -7
  32. package/dist/venue-rules.js.map +1 -1
  33. package/docs/configuration.md +1 -1
  34. package/docs/e2e.md +14 -12
  35. package/docs/rules/format/page-limit.md +18 -1
  36. package/docs/rules/tex/template.md +25 -5
  37. package/docs/rules/tex/venue-leftover.md +13 -0
  38. package/docs/rules.md +63 -55
  39. package/docs/submission.md +5 -1
  40. package/fixtures/build-e2e/msr/PIPELINE-STATUS.md +3 -0
  41. package/fixtures/build-e2e/msr/expect.json +18 -0
  42. package/fixtures/build-e2e/msr/paper.tex +49 -0
  43. package/fixtures/build-e2e/msr/paperlint.json +1 -0
  44. package/fixtures/build-e2e/msr-appendix-after/PIPELINE-STATUS.md +3 -0
  45. package/fixtures/build-e2e/msr-appendix-after/expect.json +20 -0
  46. package/fixtures/build-e2e/msr-appendix-after/paper.tex +48 -0
  47. package/fixtures/build-e2e/msr-appendix-after/paperlint.json +1 -0
  48. package/package.json +1 -1
  49. package/presets/ieee-conference.md +1 -1
  50. package/presets/msr.jsonc +71 -0
  51. package/presets/msr.md +94 -0
  52. package/presets/venue-profile.schema.json +25 -0
  53. package/skills/camera-ready/SKILL.md +8 -1
  54. package/skills/camera-ready/SKILL.md.spec.ts +7 -0
  55. package/skills/submission-portal/SKILL.md +2 -2
  56. package/skills/submission-portal/SKILL.md.spec.ts +1 -1
  57. package/skills/submit-paper/SKILL.md +9 -1
  58. package/skills/submit-paper/SKILL.md.spec.ts +8 -0
  59. package/src/cli.ts +7 -2
  60. package/src/domain/body-pages.test.ts +144 -0
  61. package/src/domain/body-pages.ts +61 -1
  62. package/src/new-paper.test.ts +27 -0
  63. package/src/paper-includes.test.ts +26 -0
  64. package/src/paper-includes.ts +20 -2
  65. package/src/paper-settings-commands.test.ts +1 -1
  66. package/src/preset-card-rule.test.ts +1 -0
  67. package/src/presets.test.ts +66 -1
  68. package/src/presets.ts +23 -6
  69. package/src/submission.test.ts +42 -3
  70. package/src/submission.ts +28 -7
  71. package/src/tex-requirements.ts +21 -4
  72. package/src/tex-venue-rules.test.ts +161 -0
  73. package/src/tex-venue-rules.ts +45 -15
  74. package/src/venue-rules.test.ts +173 -2
  75. package/src/venue-rules.ts +71 -10
package/docs/rules.md CHANGED
@@ -3,54 +3,56 @@
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/folder-venue-leftover`](rules/paper/folder-venue-leftover.md) | warn | the folder name → `paperlint.json` | the paper's folder is named after another shipped venue than the one it extends (`papers/aisec-2026/` for a paper now set for AIDC); reported on `PIPELINE-STATUS.md` — [page](rules/paper/folder-venue-leftover.md) |
11
- | `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 |
12
- | `paper/section-word` | warn | `paper.tex` and its body files, `paper.md` | `§` or `\S\ref` instead of the word Section. **Fixable:** `--fix` writes `Section~\ref{…}` / `Section 5` |
13
- | `paper/leading-zero` | warn | `paper.tex` and its body files, `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` and its body files | `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` and its body files | 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/heading-case`](rules/tex/heading-case.md) | optional; the AgenticDev preset sets `error` | `paper.tex` and the files it includes | a word in the paper's `\title` or in a `\section`, `\subsection`, `\subsubsection`, `\paragraph` or `\subparagraph` title is not in the style its scope asks: `{ "title", "headings", "levels"? }`, each `chicago-headline` (Conference Publishing's quote of the Chicago Manual of Style), `sentence` (first word only) or `any`. Words that are a preposition in one title and a particle in another (`up`, `on`, `in`) are not read mid-title. **Fixable:** `--fix` — [page](rules/tex/heading-case.md) |
24
- | [`format/layout-override`](rules/format/layout-override.md) | error | `paper.tex` → `paperlint.json` | the source changes the page layout the venue's template sets for the whole document — `geometry`, a layout `\setlength`, `\linespread`, `\baselinestretch` — [page](rules/format/layout-override.md) |
25
- | [`anonymity/identity`](rules/anonymity/identity.md) | error | `paper.tex` → `_build/paper.facts.json` | a double-blind venue (`"blind": true`) and no `identity` declared, or a declared name, handle, email or project in the PDF's text, metadata or link targets — [page](rules/anonymity/identity.md) |
26
- | [`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) |
27
- | [`tex/claim-provenance`](rules/tex/claim-provenance.md) | warn | `paper.tex` | a sentence of the body states a number and does not say whose it is — no citation or link, no _we_/_our_, no `\ref` or "Table 2", no sample ("n = 134", "48 runs") — [page](rules/tex/claim-provenance.md) |
28
- | [`tex/register`](rules/tex/register.md) | warn | `paper.tex` and the files it includes | the body runs to a post's register: more than 0.8 sentences per 1000 words open with _And_, _So_, _But_, _Nor_, _Or_ or _Yet_ — the limit is an option — [page](rules/tex/register.md) |
29
- | [`tex/contrast-frames`](rules/tex/contrast-frames.md) | warn | `paper.tex` and the files it includes → `paperlint.json` | contrast frames (_X, not Y_, _not X but Y_, _rather than_, _instead of_, _as opposed to_) per 10,000 words fall outside the band the venue preset's anchors set (AIDC: 0–10.4, from five accepted ACSAC papers); above it, each frame is reported too — [page](rules/tex/contrast-frames.md) |
30
- | [`tex/claim-emphasis`](rules/tex/claim-emphasis.md) | warn | `paper.tex` and the files it includes → `paperlint.json` | claims set in bold inside a sentence (six words or more, or holding a number; not a term, not a label opening a paragraph) per 10,000 words fall outside the preset's band (AIDC: 0–17.8); above it, each is reported too — [page](rules/tex/claim-emphasis.md) |
31
- | [`tex/relation-markers`](rules/tex/relation-markers.md) | warn | `paper.tex` and the files it includes → `paperlint.json` | words that name how a sentence relates to the one before (_because_, _therefore_, _however_, _for example_ …) per 10,000 words fall outside the preset's band (AIDC: 25.4–135.0) — the floor that catches relations left to infer — [page](rules/tex/relation-markers.md) |
32
- | `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 |
33
- | `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`) |
34
- | `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 3, or they describe a PDF that is gone or differs from the one on disk (its SHA-256) |
35
- | `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 |
36
- | `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`) |
37
- | `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 |
38
- | [`format/page-limit`](rules/format/page-limit.md) | error | `paper.tex` → `_build/paper.facts.json` | body or reference pages exceed the limits of the paper's kind. With `body_ends_at: "references"` (AIDC) the body ends at the references or the appendix, whichever comes first — found by hyperref's anchors, then the heading line — and the page it ends on counts when body text stands above the heading; an end it cannot find, or signals that disagree, are reported — [page](rules/format/page-limit.md) |
39
- | [`pdf/body-size`](rules/pdf/body-size.md) | error | `paper.tex` → `_build/paper.facts.json` | the body font size is more than `body_pt_tol` off the preset's, or the reference font size is outside the preset's range widened by `body_pt_tol`. banal reads 0.3 pt above the size the glyphs are set in; checked on five built accepted papers (none reported) and three variants one size off (each reported) — [page](rules/pdf/body-size.md) |
40
- | `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 |
41
- | [`talk/profile`](rules/talk/profile.md) | error | `paper.tex` → `paperlint.json` | the paper declares `talk` and names no preset, the preset has no `talk` block, the mode is not one the venue allows, or the venue sets no talk slot for the paper's kind — [page](rules/talk/profile.md) |
42
- | [`talk/required-files`](rules/talk/required-files.md) | error | `paper.tex` → `paperlint.json`, the talk folder | a file the venue requires for the paper's talk mode (video, one-slide, captions…) is not in the talk folder — [page](rules/talk/required-files.md) |
43
- | [`talk/undeclared`](rules/talk/undeclared.md) | warn | `paper.tex` → the paper's `talk/` folder | an `.mp4` sits in `talk/` and the paper declares no `talk`, so nothing checks it — [page](rules/talk/undeclared.md) |
44
- | [`talk/duration`](rules/talk/duration.md) | error | `paper.tex` → the talk video | the video runs longer than the slot's `talk_s_max`, or its audio track is more than 1 s off the video's length — [page](rules/talk/duration.md) |
45
- | [`talk/duration-floor`](rules/talk/duration-floor.md) | warn | `paper.tex` → the talk video | the video runs shorter than the slot's `talk_s_min` — [page](rules/talk/duration-floor.md) |
46
- | [`talk/video-format`](rules/talk/video-format.md) | error | `paper.tex` → the talk video | the video is not ISO media, is QuickTime where the venue asks for MP4, is under `min_height_px`, or over `max_bytes` — [page](rules/talk/video-format.md) |
47
- | [`talk/one-slide-size`](rules/talk/one-slide-size.md) | error | `paper.tex` → the one-slide PNG | the one-slide image is not the size the paper or the preset states, or not a PNG — [page](rules/talk/one-slide-size.md) |
48
- | [`talk/captions-cover`](rules/talk/captions-cover.md) | warn | `paper.tex` → the captions and the video | the captions start late, end early or leave a long gap (uncalibrated thresholds) — [page](rules/talk/captions-cover.md) |
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/folder-venue-leftover`](rules/paper/folder-venue-leftover.md) | warn | the folder name → `paperlint.json` | the paper's folder is named after another shipped venue than the one it extends (`papers/aisec-2026/` for a paper now set for AIDC); reported on `PIPELINE-STATUS.md` — [page](rules/paper/folder-venue-leftover.md) |
11
+ | `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 |
12
+ | `paper/section-word` | warn | `paper.tex` and its body files, `paper.md` | `§` or `\S\ref` instead of the word Section. **Fixable:** `--fix` writes `Section~\ref{…}` / `Section 5` |
13
+ | `paper/leading-zero` | warn | `paper.tex` and its body files, `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` and its body files | `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` and its body files | 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/heading-case`](rules/tex/heading-case.md) | optional; the AgenticDev preset sets `error` | `paper.tex` and the files it includes | a word in the paper's `\title` or in a `\section`, `\subsection`, `\subsubsection`, `\paragraph` or `\subparagraph` title is not in the style its scope asks: `{ "title", "headings", "levels"? }`, each `chicago-headline` (Conference Publishing's quote of the Chicago Manual of Style), `sentence` (first word only) or `any`. Words that are a preposition in one title and a particle in another (`up`, `on`, `in`) are not read mid-title. **Fixable:** `--fix` — [page](rules/tex/heading-case.md) |
24
+ | [`format/layout-override`](rules/format/layout-override.md) | error | `paper.tex` → `paperlint.json` | the source changes the page layout the venue's template sets for the whole document — `geometry`, a layout `\setlength`, `\linespread`, `\baselinestretch` — [page](rules/format/layout-override.md) |
25
+ | [`anonymity/identity`](rules/anonymity/identity.md) | error | `paper.tex` → `_build/paper.facts.json` | a double-blind venue (`"blind": true`) and no `identity` declared, or a declared name, handle, email or project in the PDF's text, metadata or link targets — [page](rules/anonymity/identity.md) |
26
+ | [`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) |
27
+ | [`tex/claim-provenance`](rules/tex/claim-provenance.md) | warn | `paper.tex` | a sentence of the body states a number and does not say whose it is — no citation or link, no _we_/_our_, no `\ref` or "Table 2", no sample ("n = 134", "48 runs") — [page](rules/tex/claim-provenance.md) |
28
+ | [`tex/register`](rules/tex/register.md) | warn | `paper.tex` and the files it includes | the body runs to a post's register: more than 0.8 sentences per 1000 words open with _And_, _So_, _But_, _Nor_, _Or_ or _Yet_ — the limit is an option — [page](rules/tex/register.md) |
29
+ | [`tex/contrast-frames`](rules/tex/contrast-frames.md) | warn | `paper.tex` and the files it includes → `paperlint.json` | contrast frames (_X, not Y_, _not X but Y_, _rather than_, _instead of_, _as opposed to_) per 10,000 words fall outside the band the venue preset's anchors set (AIDC: 0–10.4, from five accepted ACSAC papers); above it, each frame is reported too — [page](rules/tex/contrast-frames.md) |
30
+ | [`tex/claim-emphasis`](rules/tex/claim-emphasis.md) | warn | `paper.tex` and the files it includes → `paperlint.json` | claims set in bold inside a sentence (six words or more, or holding a number; not a term, not a label opening a paragraph) per 10,000 words fall outside the preset's band (AIDC: 0–17.8); above it, each is reported too — [page](rules/tex/claim-emphasis.md) |
31
+ | [`tex/relation-markers`](rules/tex/relation-markers.md) | warn | `paper.tex` and the files it includes → `paperlint.json` | words that name how a sentence relates to the one before (_because_, _therefore_, _however_, _for example_ …) per 10,000 words fall outside the preset's band (AIDC: 25.4–135.0) — the floor that catches relations left to infer — [page](rules/tex/relation-markers.md) |
32
+ | `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 |
33
+ | `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`) |
34
+ | `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 3, or they describe a PDF that is gone or differs from the one on disk (its SHA-256) |
35
+ | `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 |
36
+ | `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`) |
37
+ | `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 |
38
+ | [`format/page-limit`](rules/format/page-limit.md) | error | `paper.tex` → `_build/paper.facts.json` | body or reference pages exceed the limits of the paper's kind. With `body_ends_at: "references"` (AIDC) the body ends at the references or the appendix, whichever comes first — found by hyperref's anchors, then the heading line — and the page it ends on counts when body text stands above the heading; with `appendix_in_body` too (MSR) the appendix is body wherever it sits and the reference pages are the ones holding only references; an end it cannot find, or signals that disagree, are reported — [page](rules/format/page-limit.md) |
39
+ | [`pdf/body-size`](rules/pdf/body-size.md) | error | `paper.tex` → `_build/paper.facts.json` | the body font size is more than `body_pt_tol` off the preset's, or the reference font size is outside the preset's range widened by `body_pt_tol`. banal reads 0.3 pt above the size the glyphs are set in; checked on five built accepted papers (none reported) and three variants one size off (each reported) — [page](rules/pdf/body-size.md) |
40
+ | `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 |
41
+ | [`talk/profile`](rules/talk/profile.md) | error | `paper.tex` → `paperlint.json` | the paper declares `talk` and names no preset, the preset has no `talk` block, the mode is not one the venue allows, or the venue sets no talk slot for the paper's kind — [page](rules/talk/profile.md) |
42
+ | [`talk/required-files`](rules/talk/required-files.md) | error | `paper.tex` → `paperlint.json`, the talk folder | a file the venue requires for the paper's talk mode (video, one-slide, captions…) is not in the talk folder — [page](rules/talk/required-files.md) |
43
+ | [`talk/undeclared`](rules/talk/undeclared.md) | warn | `paper.tex` → the paper's `talk/` folder | an `.mp4` sits in `talk/` and the paper declares no `talk`, so nothing checks it — [page](rules/talk/undeclared.md) |
44
+ | [`talk/duration`](rules/talk/duration.md) | error | `paper.tex` → the talk video | the video runs longer than the slot's `talk_s_max`, or its audio track is more than 1 s off the video's length — [page](rules/talk/duration.md) |
45
+ | [`talk/duration-floor`](rules/talk/duration-floor.md) | warn | `paper.tex` → the talk video | the video runs shorter than the slot's `talk_s_min` — [page](rules/talk/duration-floor.md) |
46
+ | [`talk/video-format`](rules/talk/video-format.md) | error | `paper.tex` → the talk video | the video is not ISO media, is QuickTime where the venue asks for MP4, is under `min_height_px`, or over `max_bytes` — [page](rules/talk/video-format.md) |
47
+ | [`talk/one-slide-size`](rules/talk/one-slide-size.md) | error | `paper.tex` → the one-slide PNG | the one-slide image is not the size the paper or the preset states, or not a PNG — [page](rules/talk/one-slide-size.md) |
48
+ | [`talk/captions-cover`](rules/talk/captions-cover.md) | warn | `paper.tex` → the captions and the video | the captions start late, end early or leave a long gap (uncalibrated thresholds) — [page](rules/talk/captions-cover.md) |
49
49
 
50
50
  **Body files** are the files `paper.tex` includes from its document body (`\input`, `\include`,
51
51
  `\subfile`, nested ones too), found in the paper's directory — not a preamble include, not a file
52
- found only in paperlint's own inputs, and not any other `.tex` beside the paper (`versions/`). A
53
- finding in one is reported at that file and line, and `--fix` edits that file. Which reference form
52
+ found only in paperlint's own inputs, and not any other `.tex` beside the paper (`versions/`). Only
53
+ the `.tex` ones are linted on their own: an included `.bbl` or `.pgf` is a tool's output, read into
54
+ the whole paper where TeX typesets it, but not judged or fixed as prose of its own. A
55
+ finding in a body file is reported at that file and line, and `--fix` edits that file. Which reference form
54
56
  is the majority and whether the build is a review build are decided over the whole paper. An include
55
57
  that names no file is printed as a note: lint did not read it, and the build will stop on it.
56
58
 
@@ -112,14 +114,15 @@ for papers, each with the quote it came from, the TeX packages its template need
112
114
  the venue implies. A preset may itself extend another: the ACM venues extend the `acm-sigconf`
113
115
  family, which holds everything the ACM template decides.
114
116
 
115
- | preset | extends | template | kinds | page limit checked | rules it turns on |
116
- | --------------------------- | --------------------------- | ------------------------------------- | -------------------------------------------------------- | -------------------------------------- | ------------------------------------------- |
117
- | `paperlint:acm-sigconf` | — | ACM `acmart` sigconf | none | no (no kinds) | — |
118
- | `paperlint:agenticdev` | `paperlint:acm-sigconf` | ACM `acmart` sigconf | `short` (5 + 2 refs), `full` (10 + 2), `demo` (5 + 2) | yes | `pdf/last-page-balance`, `tex/heading-case` |
119
- | `paperlint:aisec` | `paperlint:acm-sigconf` | ACM `acmart` sigconf | `research`, `benchmark`, `position`, `sok` (10 + 2 each) | yes | — |
120
- | `paperlint:realm` | — | ACL | `long`, `short` | no — see below | — |
121
- | `paperlint:ieee-conference` | — | IEEE `IEEEtran` conference | none | no (no kinds) | — |
122
- | `paperlint:aidc` | `paperlint:ieee-conference` | IEEE `IEEEtran` conference, `compsoc` | `regular` (12), `short` (6) | yes, before the references — see below | — (`blind`) |
117
+ | preset | extends | template | kinds | page limit checked | rules it turns on |
118
+ | --------------------------- | --------------------------- | ------------------------------------------------- | -------------------------------------------------------- | -------------------------------------- | ------------------------------------------- |
119
+ | `paperlint:acm-sigconf` | — | ACM `acmart` sigconf | none | no (no kinds) | — |
120
+ | `paperlint:agenticdev` | `paperlint:acm-sigconf` | ACM `acmart` sigconf | `short` (5 + 2 refs), `full` (10 + 2), `demo` (5 + 2) | yes | `pdf/last-page-balance`, `tex/heading-case` |
121
+ | `paperlint:aisec` | `paperlint:acm-sigconf` | ACM `acmart` sigconf | `research`, `benchmark`, `position`, `sok` (10 + 2 each) | yes | — |
122
+ | `paperlint:realm` | — | ACL | `long`, `short` | no — see below | — |
123
+ | `paperlint:ieee-conference` | — | IEEE `IEEEtran` conference | none | no (no kinds) | — |
124
+ | `paperlint:aidc` | `paperlint:ieee-conference` | IEEE `IEEEtran` conference, `compsoc` | `regular` (12), `short` (6) | yes, before the references — see below | — (`blind`) |
125
+ | `paperlint:msr` | `paperlint:ieee-conference` | IEEE `IEEEtran` `[10pt,conference]`, no `compsoc` | `technical` (10 + 2 of only references) | yes, appendices included — see below | — (`blind`) |
123
126
 
124
127
  That is all that ships today. There is no NeurIPS, USENIX or Springer preset. A paper for an ACM or
125
128
  IEEE conference venue nobody has profiled can extend `paperlint:acm-sigconf` or
@@ -131,7 +134,12 @@ would fail a correct paper. AIDC's limit is checked by another count, for the sa
131
134
  a page as body when an appendix or the LLM Usage Statement sits on it, and AIDC counts neither, so a
132
135
  correct 12-page paper measured 13. Its preset sets `"body_ends_at": "references"`: the body is the
133
136
  pages before the one where the references heading first appears. It is also `"blind": true`, which
134
- turns on [`anonymity/identity`](rules/anonymity/identity.md). AgenticDev's turns on `pdf/last-page-balance`
137
+ turns on [`anonymity/identity`](rules/anonymity/identity.md). MSR's limit is checked by a third
138
+ count: its ten pages of main text include the appendices, and only pages holding nothing but
139
+ references are the two extra ones. banal types a page whose first heading reads «Appendix» as
140
+ bibliography and an appendix after the references as references, so its preset adds
141
+ `"appendix_in_body": true` ([page](rules/format/page-limit.md)); it also forbids the `compsoc` and
142
+ `compsocconf` class options its call rules out (`template_forbids`). AgenticDev's turns on `pdf/last-page-balance`
135
143
  because its proceedings are produced by Conference Publishing Consulting, which sends back an
136
144
  unbalanced last page; AISec's does not, because nothing in hand says who produces the AISec
137
145
  proceedings.
@@ -139,7 +147,7 @@ proceedings.
139
147
  **How presets are named.** A template family — the preset venues extend, owning the template,
140
148
  the format and the TeX packages — is `<publisher>-<template variant>`: `acm-sigconf`, `ieee-conference`. A venue
141
149
  is its own short name, lowercase, with no publisher and no parent-conference prefix: `aisec`,
142
- `agenticdev`, `realm`, `aidc`. Which template a venue uses is said by its `extends`, not by its
150
+ `agenticdev`, `realm`, `aidc`, `msr`. Which template a venue uses is said by its `extends`, not by its
143
151
  name — a name repeating it would be wrong the day the venue changes template — and the parent
144
152
  conference (CCS, ASE, ACSAC) goes into the preset's comment and its `aliases`.
145
153
 
@@ -19,7 +19,7 @@ Two keys, each in the file that owns the fact, and a token:
19
19
  | the paper's `paperlint.json` | `"submission": { "id": 7 }` | which submission on that portal is this paper's |
20
20
  | the environment | `HOTCRP_TOKEN` | an API token for your HotCRP account |
21
21
 
22
- `paperlint:aidc` declares its portal. For another venue, add `portal` to your own preset (see
22
+ `paperlint:aidc` and `paperlint:msr` declare their portals. For another venue, add `portal` to your own preset (see
23
23
  [`rules.md`](rules.md)) — or upload by hand: a kind paperlint has no adapter for is refused with
24
24
  `portal kind "<kind>" is not supported; upload by hand`.
25
25
 
@@ -54,6 +54,10 @@ abstract (`--abstract <file>`, plain text) and the status "submitted" (`--submit
54
54
  change touched (`change_list`), HotCRP's messages — and the local file's sha256. Exit 1 when the
55
55
  change is not valid or the HTTP status is not 2xx.
56
56
 
57
+ Before it sends anything, dry run or not, it prints one line asking you to open that exact PDF and
58
+ check it against the venue's call for papers — named by the preset's `url` when the preset is a
59
+ venue: what paperlint checks is an aid, and the venue's reading of the PDF is what counts.
60
+
57
61
  The safe order: `show`, `update` (dry run), read what it says, `update --save`, then `show` again and
58
62
  check `match yes`.
59
63
 
@@ -0,0 +1,3 @@
1
+ # Fixture
2
+
3
+ Stage: draft
@@ -0,0 +1,18 @@
1
+ {
2
+ "$comment": "A real IEEEtran [10pt,conference] paper for MSR whose ten pages of main text include an appendix before the references, with two pages of only references: format/page-limit, tex/template and anonymity/identity have nothing to say. banal's own count (body 9, references 3) would fail it.",
3
+ "build": {
4
+ "outcome": "pdf",
5
+ "pages": 12
6
+ },
7
+ "fonts": {
8
+ "some": "NimbusRomNo9L",
9
+ "none": "^(CMR|CMBX|CMTI|CMTT|CMSS|LMRoman)"
10
+ },
11
+ "lint": {
12
+ "rules": {
13
+ "format/page-limit": [],
14
+ "tex/template": [],
15
+ "anonymity/identity": []
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,49 @@
1
+ % A paper in the class MSR's call names, built by the e2e on paperlint's own TeX Live, that MEETS
2
+ % MSR's limit: ten pages of main text, its appendix included, and two pages holding only references
3
+ % (rendered and looked at, 2026-10-05: the appendix heading stands mid-column on page 9, appendix
4
+ % text then «REFERENCES» on page 10, nothing but entries on pages 11 and 12). banal's count says body
5
+ % 9, references 3 — a page whose first heading reads «Appendix» is typed as bibliography — and would
6
+ % fail it; format/page-limit counts from the text and must not.
7
+ %
8
+ % The prose is one paragraph set many times; each copy carries its own number in words on every
9
+ % line, so no line repeats from page to page as a running header would.
10
+ \documentclass[10pt,conference]{IEEEtran}
11
+ \usepackage{hyperref}
12
+ \newcount\paracount
13
+ \newcommand{\n}{\romannumeral\paracount}
14
+ \newcommand{\para}{\global\advance\paracount by 1
15
+ Paragraph \n{} studies the history of a repository, and \n{} counts its commits by author \n{}
16
+ and by month \n{}, so that the measurement \n{} has a body of prose \n{} set in the two columns
17
+ \n{} of the conference format. Paragraph \n{} says nothing new, and \n{} does not need to: the
18
+ fixture \n{} is about the class, \n{} the fonts and \n{} the page count, and \n{} not about its
19
+ content. A reviewer of \n{} would find it dull, \n{} which is what a measurement \n{} should be.\par}
20
+ \newcount\loopcount
21
+ \newcommand{\paras}[1]{\loopcount=0 \loop\para\advance\loopcount by 1 \ifnum\loopcount<#1\repeat}
22
+ \newcount\bibcount
23
+ \newcommand{\entries}[1]{\bibcount=0 \loop\advance\bibcount by 1
24
+ \bibitem{r\the\bibcount} A. Author, ``Mining study \romannumeral\bibcount,'' in \emph{Proc.
25
+ Workshop}, 2024, pp. 1--10.\ifnum\bibcount<#1\repeat}
26
+ \begin{document}
27
+ \title{A Fixture Typeset in the Class the Venue Requires}
28
+ \author{\IEEEauthorblockN{Anonymous Author}\IEEEauthorblockA{Anonymous Institution}}
29
+ \maketitle
30
+ \begin{abstract}
31
+ The abstract exists so the build has the front matter an IEEE paper carries.
32
+ \end{abstract}
33
+ \section{Introduction}
34
+ \paras{24}
35
+ \section{Part Two}
36
+ \paras{24}
37
+ \section{Part Three}
38
+ \paras{24}
39
+ \section{Part Four}
40
+ \paras{24}
41
+ \section{Part Five}
42
+ \paras{12}
43
+ \appendix
44
+ \section{Detailed Results}
45
+ \paras{12}
46
+ \begin{thebibliography}{999}
47
+ \entries{290}
48
+ \end{thebibliography}
49
+ \end{document}
@@ -0,0 +1 @@
1
+ {"extends": "paperlint:msr", "kind": "technical", "identity": ["Ada Example", "adaexample", "ada@example.org", "Example University", "exampletool"]}
@@ -0,0 +1,3 @@
1
+ # Fixture
2
+
3
+ Stage: draft
@@ -0,0 +1,20 @@
1
+ {
2
+ "$comment": "MSR: an appendix after the references brings the main text to eleven pages. Exactly one format/page-limit finding, naming where the appendix starts. banal's own count (body 9, references 2) would pass it.",
3
+ "build": {
4
+ "outcome": "pdf",
5
+ "pages": 11
6
+ },
7
+ "lint": {
8
+ "rules": {
9
+ "format/page-limit": [
10
+ {
11
+ "severity": 2,
12
+ "says": [
13
+ "body pages (up to the references on page 10, and the appendix from page 10): 11, over the limit 10 for MSR/technical"
14
+ ]
15
+ }
16
+ ],
17
+ "tex/template": []
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,48 @@
1
+ % A paper in the class MSR's call names, built by the e2e on paperlint's own TeX Live, that is ONE
2
+ % PAGE OVER MSR's limit through its appendix: the body ends on page 10, the references follow it
3
+ % there, and the appendix after them runs from the foot of page 10 through page 11 (rendered and
4
+ % looked at, 2026-10-05) — eleven pages holding main text. banal's count says body 9, references 2,
5
+ % and would pass it; format/page-limit counts the appendix as body and must not.
6
+ %
7
+ % The prose is one paragraph set many times; each copy carries its own number in words on every
8
+ % line, so no line repeats from page to page as a running header would.
9
+ \documentclass[10pt,conference]{IEEEtran}
10
+ \usepackage{hyperref}
11
+ \newcount\paracount
12
+ \newcommand{\n}{\romannumeral\paracount}
13
+ \newcommand{\para}{\global\advance\paracount by 1
14
+ Paragraph \n{} studies the history of a repository, and \n{} counts its commits by author \n{}
15
+ and by month \n{}, so that the measurement \n{} has a body of prose \n{} set in the two columns
16
+ \n{} of the conference format. Paragraph \n{} says nothing new, and \n{} does not need to: the
17
+ fixture \n{} is about the class, \n{} the fonts and \n{} the page count, and \n{} not about its
18
+ content. A reviewer of \n{} would find it dull, \n{} which is what a measurement \n{} should be.\par}
19
+ \newcount\loopcount
20
+ \newcommand{\paras}[1]{\loopcount=0 \loop\para\advance\loopcount by 1 \ifnum\loopcount<#1\repeat}
21
+ \newcount\bibcount
22
+ \newcommand{\entries}[1]{\bibcount=0 \loop\advance\bibcount by 1
23
+ \bibitem{r\the\bibcount} A. Author, ``Mining study \romannumeral\bibcount,'' in \emph{Proc.
24
+ Workshop}, 2024, pp. 1--10.\ifnum\bibcount<#1\repeat}
25
+ \begin{document}
26
+ \title{A Fixture Typeset in the Class the Venue Requires}
27
+ \author{\IEEEauthorblockN{Anonymous Author}\IEEEauthorblockA{Anonymous Institution}}
28
+ \maketitle
29
+ \begin{abstract}
30
+ The abstract exists so the build has the front matter an IEEE paper carries.
31
+ \end{abstract}
32
+ \section{Introduction}
33
+ \paras{24}
34
+ \section{Part Two}
35
+ \paras{24}
36
+ \section{Part Three}
37
+ \paras{24}
38
+ \section{Part Four}
39
+ \paras{24}
40
+ \section{Part Five}
41
+ \paras{24}
42
+ \begin{thebibliography}{999}
43
+ \entries{60}
44
+ \end{thebibliography}
45
+ \appendix
46
+ \section{Detailed Results}
47
+ \paras{12}
48
+ \end{document}
@@ -0,0 +1 @@
1
+ {"extends": "paperlint:msr", "kind": "technical", "identity": ["Ada Example", "adaexample", "ada@example.org", "Example University", "exampletool"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paperlint",
3
- "version": "7.0.0",
3
+ "version": "7.1.0",
4
4
  "description": "A linter for scientific papers written in LaTeX: catches mistakes before you submit to a conference or journal.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -30,7 +30,7 @@ versions. `compsoc` changes the text block and the headings, and nothing the `pd
30
30
  <!-- prettier-ignore-start -->
31
31
  <!-- Generated from ieee-conference.jsonc by `npx eslint --fix` (rule preset/card-rules). Edit the preset, not this section. -->
32
32
 
33
- **`paperlint:ieee-conference`** — template family, no venue of its own · extended by `paperlint:aidc`
33
+ **`paperlint:ieee-conference`** — template family, no venue of its own · extended by `paperlint:aidc`, `paperlint:msr`
34
34
 
35
35
  No preset of this chain sets a rule: under it, every rule keeps paperlint's default.
36
36
 
@@ -0,0 +1,71 @@
1
+ // MSR 2027 Technical Papers (the 24th International Conference on Mining Software Repositories) — a
2
+ // VENUE preset over the IEEE conference family. Everything the IEEEtran template decides (page
3
+ // size, columns, fonts, body size, TeX packages) comes from ieee-conference.jsonc; this file adds
4
+ // what MSR's call for papers sets.
5
+ //
6
+ // Verbatim from https://2027.msrconf.org/track/msr-2027-technical-papers (fetched 2026-10-05):
7
+ // «All submissions must conform to the IEEE conference proceedings template, specified in the
8
+ // IEEE Conference Proceedings Formatting Guidelines (title in 24pt font and full text in 10pt
9
+ // type, LaTeX users must use \documentclass[10pt,conference]{IEEEtran} without including the
10
+ // compsoc or compsocconf options).»
11
+ // «Note that IEEE format is being used this year, whereas last year it was ACM format.»
12
+ // «All submissions must not exceed 10 pages for the main text, inclusive of all figures, tables,
13
+ // appendices, etc. Two more pages containing only references are permitted. All submissions
14
+ // must be in PDF. Accepted papers will be allowed one extra page for the main text of the
15
+ // camera-ready version.»
16
+ // «The MSR 2027 Technical Track will employ a double-anonymous review process.»
17
+ //
18
+ // The dates are not lint facts; they are in the card, msr.md beside this file.
19
+ {
20
+ "type": "venue",
21
+ "name": "MSR",
22
+ // The call quoted above.
23
+ "url": "https://2027.msrconf.org/track/msr-2027-technical-papers",
24
+ "extends": "paperlint:ieee-conference",
25
+
26
+ // «The MSR 2027 Technical Track will employ a double-anonymous review process. Thus, no
27
+ // submission may reveal its authors' identities.» (the call above), and `"blind":true` in the
28
+ // portal's data (https://msr2027.hotcrp.com/deadlines, 2026-10-05). anonymity/identity then
29
+ // requires the paper's `identity`.
30
+ "blind": true,
31
+
32
+ // «Papers must be submitted through HotCRP: https://msr2027.hotcrp.com/» (the call above).
33
+ // Recorded 2026-10-05: `/api/whoami` on this site answers 401 "Missing credentials" without a
34
+ // token — the HotCRP API, there.
35
+ "portal": { "kind": "hotcrp", "url": "https://msr2027.hotcrp.com" },
36
+
37
+ // What the venue is called in a paper's text (tex/venue-leftover) — and only beside a year
38
+ // («MSR 2027», «MSR'27»): «MSR» is also Microsoft Research, and an acronym papers define for
39
+ // themselves («Manual Speech Recognition (MSR)», in an accepted ACSAC paper of
40
+ // fixtures/accepted-papers/, warned six times when any whole word counted).
41
+ "aliases": ["MSR"],
42
+ "mentions": "with-year",
43
+
44
+ // The class line the call names, with the two options it forbids (tex/template). The family's
45
+ // `[conference]` text block (7.14 x 9.30 in) is the one this line builds, so no `format` override.
46
+ "template": "\\documentclass[10pt,conference]{IEEEtran}",
47
+ "template_forbids": ["compsoc", "compsocconf"],
48
+
49
+ "format": {
50
+ // 🔴 THE MAIN TEXT INCLUDES THE APPENDICES, AND THE REFERENCE PAGES ARE THE ONES WITH ONLY
51
+ // REFERENCES — not banal's count. Measured 2026-10-05 on IEEEtran [10pt,conference] builds:
52
+ // 10 pages of main text, its appendix before the references, 2 reference-only pages
53
+ // → banal: body 9, references 3 (a page whose first heading reads «Appendix» is typed as
54
+ // bibliography) — a correct paper fails
55
+ // 11 pages of main text, the appendix after the references
56
+ // → banal: body 10, references 2 (the appendix taken for more references) — an over-long
57
+ // paper passes
58
+ // So format/page-limit counts from the text (`appendix_in_body`): the body is every page up to
59
+ // the references, that page too when body text stands above the heading, plus every page from
60
+ // an appendix that follows the references; the pages between are the reference pages
61
+ // (docs/rules/format/page-limit.md). An appendix after the references is found by hyperref's
62
+ // anchor: load hyperref.
63
+ "body_ends_at": "references",
64
+ "appendix_in_body": true,
65
+
66
+ // One kind: the Technical Track's papers. The camera-ready's extra page is not this limit.
67
+ "kinds": {
68
+ "technical": { "body_pages_max": 10, "ref_pages_max": 2 },
69
+ },
70
+ },
71
+ }