paperlint
v9.0.0
Published
A linter for scientific papers written in LaTeX: catches mistakes before you submit to a conference or journal.
Maintainers
Readme
paperlint
Write a research paper in LaTeX from idea to camera-ready, and catch what gets it sent back before you submit. It comes in two parts:
✅ A linter — a command-line tool, no AI needed. Its headline catches:
- 📏 a paper over the page limit for its kind at your venue;
- 🔤 the wrong font — LaTeX silently fell back to Computer Modern because a package was missing;
- 🔗 a bad reference — a cited work that does not exist, or lists the preprint's authors.
Smaller slips too:
§for "Section",.05for0.05— fixed for you.🧠 Skills for Claude Code — optional, covering the whole pipeline: the idea, the venue, the study, the draft, the reviews, submission.
⚠️ An aid, not a guarantee. A clean run does not mean your paper meets the venue's rules: presets are transcribed from calls for papers that change, and any check can miss something. Check against the venue's own call and template. Provided "as is", without warranty — see LICENSE.
Contents
- The pipeline
- Supported venues
- Getting started
- Commands
- Skills
- Lint and build
- Configuration
- Run it in CI
- FAQ
- Docs
🧭 The pipeline
stage what you get skills linter
1 idea a go / no-go, with the reason yes
2 venue venues ranked, deadlines planned yes
3 study a study design, honest statistics yes
4 draft a first draft, then a tighter one yes lint
5 review the objections, before the reviewers yes lint
6 submit a PDF within the limit, recorded yes build, lint
7 camera-ready the final, de-anonymised PDF yes build, lint
8 extend a plan for the next paper yes
skills: optional, with Claude Code
lint: every edit and every CI run, once the paper exists (paperlint new)
build: compiles and measures the PDF; lint then checks pages, fonts, references🎯 Supported venues
A venue preset holds a venue's format and page limits. A paper's kind picks which limit
applies: at AgenticDev a short paper gets 5 pages and a full one 10. You choose it once per paper.
| preset | venue | format | kinds and page limits |
| --------------------------- | ------------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| paperlint:acm-sigconf | any ACM conference | ACM two-column | no kinds: the format only, no page limit |
| paperlint:agenticdev | AgenticDev @ ASE | ACM two-column | short 5, full 10, demo 5 pages, + 2 pages of references |
| paperlint:aisec | AISec @ ACM CCS | ACM two-column | research, benchmark, position, sok: 10 pages + 2 pages of references |
| paperlint:realm | REALM @ EMNLP | ACL two-column, A4 | long 8, short 4 — recorded, not checked (why) |
| paperlint:ieee-conference | any IEEE conference | IEEE two-column | no kinds: the format only, no page limit |
| paperlint:aidc | AIDC @ IEEE ACSAC | IEEE two-column, compsoc | regular 12, short 6, counted before the references (why); requires the «LLM Usage Statement»; double-blind |
| paperlint:msr | MSR (Technical) | IEEE two-column, no compsoc | technical 10 pages, appendices included, + 2 pages of only references (why); double-blind |
Not shipped yet: USENIX, NeurIPS, Springer, IEEE venues other than AIDC and MSR, and ACL venues other than REALM. Add yours in one small file.
🚀 Getting started
A new paper
Install (Node 22.13 or newer):
npm i -D paperlintSet up.
initasks three questions: where your papers live (defaultpapers/), whether to install TeX Live now, and whether to add a CI workflow. With Claude Code it also installs the skills and three hooks (what they do).npx paperlint initCreate a paper.
--venuenames its venue preset from the table above,--kindwhich of its page limits applies. Name the folder after the work, not the venue: a rejected paper moves to another venue and keeps its folder, sonewrefuses a name likeaisec-2026(--allow-venue-namewhen the word really is the work's):npx paperlint new my-paper --venue agenticdev --kind shortIt writes
papers/my-paper/(in the folder you gaveinit) with three files:paper.tex— your paper;paperlint.json— its venue preset and kind;PIPELINE-STATUS.md— which stage the paper is at; the skills read and update it.
Build the PDF. This needs TeX Live, a ~270 MB download (~3 min, once). If you said no in
init, runnpx paperlint toolchainfirst.npx paperlint build papers/my-paperCheck it:
npx paperlint lintOn the stub from step 3 the first run already catches something (output shortened):
papers/my-paper/paper.tex 1:1 error 1 column(s), AgenticDev requires 2 — the wrong document class or class option pdf/geometry 1:1 error no font starts with `LinLibertine` (body text of AgenticDev); the PDF has: CMR10, … pdf/fonts 8:4 warning `§` instead of the word «Section» — `paperlint lint --fix` writes it paper/section-wordnewwrites a format-neutral stub in plainarticle, and AgenticDev wants ACM's class. Make the first line\documentclass[sigconf]{acmart}, build again, and the errors are gone.
A paper you already have
paperlint finds a paper by its folder: <papersDir>/<name>/, with the main file named paper.tex.
- Put the paper there — for example
papers/my-paper/paper.tex, the rest of its files beside it. If your paper folders already live elsewhere, saythesis/, setpapersDirinstead. - Run
npx paperlint new my-paper --venue <preset> --kind <kind>. On an existing folder it only adds the missingpaperlint.jsonandPIPELINE-STATUS.md; it never touchespaper.tex. - Build and lint, as above.
🧰 Commands
| command | what it does |
| ----------------------------------------------------------- | ------------------------------------------------------------------------------ |
| npx paperlint init | sets the project up |
| npx paperlint new my-paper --venue <preset> --kind <kind> | creates a paper folder for that venue, or completes an existing one |
| npx paperlint build <paper> | compiles paper.tex to paper.pdf, measures it, checks the references online |
| npx paperlint lint | runs every check over your papers; --fix fixes what can be fixed |
| npx paperlint toolchain | installs TeX Live with the packages your venues need (~270 MB, ~3 min, once) |
| npx paperlint doctor | checks the setup and exits non-zero if something is miswired |
| npx paperlint submission show <paper> | the paper's submission on the venue's portal, and whether it holds your build |
| npx paperlint submission update <paper> | checks a new PDF/abstract against the portal; --save sends it for real |
| npx paperlint --help | every command and flag |
🧠 Skills
You do not need to learn the skills' names: you ask Claude Code, and the matching skill starts.
paper-pipeline walks you through the stages; any skill also starts on its own when you ask for
what it does — "is this idea worth a paper?", "find me a venue for this". init installs them.
- A go / no-go on your idea, with the reason.
research-ideate - A ranked list of venues that fit — deadline, page limit, indexing.
find-venue - A first draft from your results.
draft-paper - A hostile review before the real one.
paper-adversarial-review - A ready / not ready verdict before you submit, worst problem first.
harden-paper - Where your paper stands, measured from the real build.
paper-status - The PDF and abstract replaced on HotCRP, dry run first, and proof the portal holds your build.
submission-portal
Every skill, by stage: docs/skills.md.
🔍 Lint and build
| | paperlint build | paperlint lint |
| ------ | ------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| needs | Node, TeX Live, the network | Node |
| does | compiles paper.tex, measures the PDF, checks each reference online (exists, right authors) | checks the source, and judges what build recorded: page limit, fonts, the reference results — offline |
| writes | paper.pdf, in _build/ the measurements and the reference results, and repro/references-cache.json | nothing (--fix: the three fixable slips) |
- If
paper.pdfor the bibliography changed since the last build, lint fails and tells you to rebuild. - Commit
repro/references-cache.json: it keeps what the citation services answered, so a build asks only about new or edited entries (and answers older than 30 days), so an unchanged bibliography usually builds without the network (docs/references.md). - A paper whose body is in other files (
\input{sections/intro}) is linted file by file: a finding lands at the file and line it is in, and--fixedits that file. The files are the ones TeX read after\begin{document}in the last build (_build/sources.json) — not every.texbeside it, and not an included.bbl(a tool's output, read only as part of the whole paper). A paper that was not built, or changed since, is linted aspaper.texalone, andpaper/sources-freshsays so. - Lint also checks the pipeline's own records:
PIPELINE-STATUS.md, reviews, notes on related papers (docs/rules.md). - The rules run on ESLint, so a deliberate exception is a comment on the line above:
% eslint-disable-next-line paper/leading-zero -- quoted from the reviewer. - Errors fail the run; warnings only print, unless you pass
--max-warnings <n>.
Every check: docs/rules.md. How build compiles:
docs/configuration.md.
🧩 Configuration
Two levels, one file name, both optional:
paperlint.json the project: papersDir, rules, defaults for every paper
papers/my-paper/paperlint.json one paper: its venue preset ("extends"), its kind, its own rulespapersDiris the folder that holds your paper folders; it defaults topapers.- The paper's venue preset is its
"extends"key — the onenew --venuewrites. - A paper's
paperlint.jsonoverrides the project's, key by key. - An unknown key is an error, so a typo cannot silently turn a setting off.
- A paper that moves between venues keeps a list of attempts,
cycles, instead of"extends"; each records its deadlines as read from the portal and the call, plus any override the chairs granted, and the earlier reading binds (docs/configuration.md). - Deadlines of a shipped venue preset stay current on their own: paperlint's repository re-reads
each preset's portal daily and releases a patch when a date moves, so your normal dependency update
(Dependabot, Renovate) brings it.
paperlint lintitself never reads the clock or the network.
Every key: docs/configuration.md. Already use ESLint for other files? See
docs/configuration.md.
➕ Add a venue that isn't listed
A venue preset is a small JSONC file: the preset it builds on, and the page limit of each kind of
paper. For an ACM workshop with a 4-page limit for short papers, venues/my-workshop.jsonc:
{
// page size, columns and fonts of the ACM two-column format
"extends": "paperlint:acm-sigconf",
"format": {
// one entry per kind of paper the call for papers names
"kinds": {
// the limit from the call for papers, in pages, references not counted
"short": { "body_pages_max": 4 },
},
},
}npx paperlint new my-paper --venue ./venues/my-workshop.jsonc --kind shortGive the path from where you run the command; new rewrites it relative to the paper's own
paperlint.json. A venue in another format: docs/rules.md.
🤖 Run it in CI
Two options:
| job | time | checks |
| -------------------------- | ----------------------------- | ------------------------------------------ |
| build, then lint | ~3 min first run, then cached | everything |
| lint only (init adds it) | seconds | the source; not pages, fonts or references |
Lint judges pages, fonts and references from what build measured, so the full job builds first:
name: papers
on: [push, pull_request]
jobs:
papers:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- uses: actions/cache@v4
with:
path: ~/.cache/paperlint/texlive
key: texlive-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
- run: npx paperlint toolchain
- run: npx paperlint build --all
- run: npx paperlint lintLint only — each paper then gets one warning that it was not built. Call the action from the
installed package, after npm ci: the action and the CLI it runs are then one revision, pinned by
your lockfile.
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- uses: ./node_modules/paperlintWith no paths it lints the papersDir of your paperlint.json; pass paths: only to lint less.
❓ FAQ
Does it install anything without asking?
No. TeX Live comes only from paperlint toolchain, or when you say yes in init or build. On
Windows, install TeX Live yourself.
Several papers for different venues in one repo?
Yes. Each paper names its own venue preset in its own paperlint.json.
Does it change my paper?
Only paperlint lint --fix, and only three rules: paper/section-word (§ → Section),
paper/leading-zero (.05 → 0.05) and paper/figure-ref-style (one figure-reference style).
📚 Docs
docs/skills.md— every skill, by stagedocs/install.md— whatinitdoes, the hooks, package managers, troubleshootingdocs/configuration.md— every setting, howbuildcompiles, using your own ESLintdocs/rules.md— every check, venue presets, recording a submitted PDFdocs/references.md— the reference check, and the cache file to commitdocs/submission.md— reading and updating a submission on the venue's portal (HotCRP)docs/optional-rules.md— checks only some venues needdocs/toolchain.md— TeX Live, and Banal (HotCRP's page-geometry checker, GPL)CONTRIBUTING.md— how the package is tested and released, and adding a venue to it
License
MIT. Banal, used for page geometry, is GPL and not part of this package:
docs/toolchain.md.
