Mechanical checks for LaTeX theses and papers: the boring layer of review, automated, so supervision time goes to the argument instead of the formatting.
144 rules across figures, cross-references, abbreviations, prose mechanics, bibliography hygiene, bibliography verification against Crossref/OpenAlex/DBLP, compile-log analysis, accessibility, anonymity, reporting conventions, the things that compile on your laptop but not on Overleaf, and per-venue submission requirements for CHI, ASSETS, AutomotiveUI, IMWUT and Transportation Research Part F.
Nothing to install. Save
browser/mechcheck.html, double-click it, and drop
your Overleaf .zip on the page. Or load extension/ in Chrome
and get a button inside Overleaf itself.
If you would rather have a command line:
pip install "git+https://github.com/M-Colley/mechcheck"
mechcheck check . # a thesis
mechcheck check . --venue chi # + CHI's submission requirements
mechcheck check . --venue autoui --profile paper-anonymous --stage final
mechcheck fix . # apply the unambiguous correctionsPick the row that matches how you work. Each is spelt out below the table.
| You want | You need | Do this |
|---|---|---|
| a page to drop a zip on | any browser | save browser/mechcheck.html, double-click it |
| a button inside Overleaf | Chrome, Edge or Brave | load extension/ unpacked — two minutes, no store account |
| checks on every Overleaf compile | nothing; works on the free plan | upload latex/mechcheck.sty, add \usepackage{mechcheck} |
| a command line, and CI | Python 3.10 or newer | pip install "git+https://github.com/M-Colley/mechcheck" |
Neither needs Python, and nothing leaves your machine except the reference lookups you switch on. Setup: docs/browser.md and docs/chrome-extension.md.
mechcheck is not on PyPI yet, so pip installs it from this repository. You need Python 3.10 or newer and git. There are no other dependencies: the standard library does the parsing, the configuration and the network.
pip install "git+https://github.com/M-Colley/mechcheck"To work on it instead, install a clone in editable mode:
git clone https://github.com/M-Colley/mechcheck
cd mechcheck
pip install -e ".[dev]" # adds pytest and PyYAMLConfirm it landed:
mechcheck rules | tail -1prints 144 rules. Then run it on the self-test document, whose answer is
known in advance:
mechcheck check tests/fixtures/selftest --profile paper-anonymous --venue autoui --offlineExpect 12 error(s), 15 warning(s), 10 note(s). If you see that line, the
whole chain works.
If mechcheck is not recognised — the normal state of a fresh Python on
Windows, where pip's Scripts directory is not on PATH — every command also
works as
python -m mechcheck check .Optional: PyYAML. mechcheck.yaml is read by a built-in parser that
understands the small YAML subset the documentation uses. With PyYAML
installed (pip install PyYAML) the full language is accepted. Nothing that
ships here needs it.
Network. The seven BIO* rules ask Crossref, OpenAlex and DBLP whether
the works you cite exist. They need internet access, cache every answer for
30 days under ~/.cache/mechcheck, and are skipped with --offline. Put your
e-mail address under mailto: in mechcheck.yaml (or in the MECHCHECK_MAILTO
environment variable) to join Crossref's polite pool: it is faster, and it is
the courteous thing to do.
Upgrading and removing. Re-run the pip install line with --upgrade;
an editable install follows git pull. pip uninstall mechcheck removes it.
latex/mechcheck.sty runs the checks LaTeX itself can make — floats,
captions, alt text, and with [crossref] labels and references — inside every
compile, on any Overleaf plan. Upload it to the project and add
\usepackage{mechcheck} to the preamble. Findings appear as package warnings
in the log panel and in mechcheck-report.txt under Other logs & files.
docs/overleaf-setup.md has the details, and the
GitHub mirror that brings the full rule set to an Overleaf project.
mechcheck init . --with-ci --with-stywrites a mechcheck.yaml, a GitHub Actions workflow that installs mechcheck
from this repository and posts findings as annotations and a job summary, and
a copy of the .sty.
| To run | You need | Command |
|---|---|---|
| the Python suite | pip install -e ".[dev]" |
python -m pytest -q |
| the browser engine | Node 18 or newer | node browser/test-engine.mjs |
| the extension harness | any browser | node browser/build-extension.mjs, then open extension/test-harness.built.html |
| the LaTeX package | TeX Live, or any TeX with latexmk |
bash latex/verify-sty.sh |
The LaTeX script looks for TeX Live in its usual install locations when
latexmk is not on PATH, which on Windows it usually is not. After editing
browser/mechcheck.html, run the build script: extension/engine.js is
generated from the page, and CI fails if the two drift apart. After adding or
changing a rule, regenerate the reference: mechcheck rules --markdown > docs/rules.md.
| Symptom | Cause, and what to do |
|---|---|
mechcheck is not recognised or not found |
pip's script directory is not on PATH. Use python -m mechcheck ..., or add the directory pip named during installation. |
No module named mechcheck |
A different Python than the one you installed into. Run python -m pip install ... with the same python you use to run it. |
N rule(s) did not run at the end of a report |
Each skipped rule has a reason: --offline, no compiled .log/.pdf to read, or disabled by the profile. mechcheck check . --show-skipped lists them. |
latexmk not found from verify-sty.sh |
Install TeX Live, or add its bin directory to PATH. The script tries the standard locations first. |
docs/rules.md is stale in CI |
A rule changed. Run mechcheck rules --markdown > docs/rules.md and commit the result. |
The .sty reports every figure as missing its caption |
It could not attach to your document class; the report file says so. Use the command line for those checks. |
Overleaf's Git integration is premium and its GitHub sync is manual — there is no webhook, so nothing can fire on a student's edit. That constraint produced a layered design; use whichever layers suit you.
| What | Runs | Needs | |
|---|---|---|---|
| A | Chrome extension | a button inside Overleaf | Chrome, 2 minutes |
| B | mechcheck.sty |
every Overleaf compile | nothing — works on the free plan |
| C | browser/mechcheck.html |
when you drop a project on it | a browser, nothing else |
| D | mirror + CI | every 30 min, automatically | Overleaf premium (git bridge) |
| E | mechcheck CLI |
locally and in CI | Python 3.10+ |
If you want no Python and no GitHub: use A and B. The extension puts the checks in the Overleaf window itself — it reads the project straight from Overleaf, and reference verification works there because a Manifest V3 service worker is allowed to make cross-origin requests. Layer B then covers every compile automatically, on any plan.
Setup: docs/chrome-extension.md (extension) · docs/browser.md (standalone page) · docs/overleaf-setup.md (Overleaf and CI).
A real example, run against a deliberately flawed paper:
main.tex
x error VEN002:1 missing \documentclass option `manuscript` (required for submission)
x error VEN002:1 \documentclass option `sigconf` must not be used for submission
x error ANON001:4 \author is present but the document is not compiled with `anonymous`
x error ANON003:12 identifying link: https://github.com/mcolley
! warn ANON004:13 funding mentioned: 'funded by'
x error ACC001:14 figure has no \Description (alt text)
! warn ACC004:19 'The red line' identifies data by colour alone
x error VEN003:20 \bibliographystyle{plain} but ACM AutomotiveUI requires ACM-Reference-Format
i info POL006:11 'F = 4.7' has no degrees of freedom
i info POL007:11 'p < .05' is reported with no effect size nearby
Against a bibliography drafted with LLM help:
refs.bib
x error BIO001:13 `baddoi`: DOI 10.1145/9999999.9999999 does not resolve
x error BIO002:17 `mismatch`: the DOI resolves to "A Design Space for External
Communication of Autonomous Vehicles" (similarity 0.11)
! warn BIO005:23 `hallucinated` could not be found in Crossref, DBLP or OpenAlex.
Closest match: "The calibration of trust in an automated system" (0.43)
x error BIB006:30 `etal` has 'et al.' in the author field
And against a project that compiles on the author's laptop and nowhere else:
main.tex
! warn STR010:3 package `subfigure` is obsolete
x error STR013:6 `Chapters/Intro` is stored as `chapters/intro.tex`; Overleaf is case-sensitive and will not find it
! warn STY020:7 'et. al.' should be 'et al.'
! warn REF010:8 consecutive \cite commands print as separate brackets
! warn STY016:8 bare URL: https://osf.io/abcde
x error FIG012:10 `Figures/Plot.PNG` is stored as `figures/plot.png`; Overleaf is case-sensitive and will not find it
Windows and macOS open Figures/Plot.PNG when the file is figures/plot.png;
Linux, and therefore Overleaf and every CI runner, do not. mechcheck fix
rewrites the path, merges the citations, wraps the URL and corrects the
"et al." — the corrections with exactly one right answer.
The full list is in docs/rules.md — generated from the code, so it cannot drift.
These are the properties that decide whether a mandatory checker is a help or a tax, so they are worth stating explicitly.
Only mechanical things. Every rule is decidable from the characters on the page. Nothing here has an opinion about whether the contribution is interesting, whether the related work is adequate, or whether the writing is good. That boundary is what makes it safe to require: passing means "nothing embarrassing is left", not "this is good work".
A false positive is worse than a miss. A checker that cries wolf gets
ignored, and then the real findings go with it. Where a rule cannot be sure, it
reports INFO, or nothing. BIO005 (reference not found anywhere) is a warning,
never an error, because German-language theses, standards and older workshop
papers are genuinely missing from the indexes. Every rule is tested in both
directions, and every false positive reported from real use becomes a
regression test.
Never accuse. The reference checks state facts — "this DOI does not resolve", "the DOI resolves to a different title" — and leave the conclusion to a person. There is a real difference between a mistyped DOI and a fabricated citation, and a tool cannot tell them apart.
Nothing blocks a draft. --stage draft reports everything and fails
nothing. Strictness arrives at submission, and at final every warning
becomes an error. Students meet the checker as a helper long before it becomes a
gate.
Always an escape hatch, always visible. Any rule can be silenced on one line, with a reason that stays in the diff:
\includegraphics{divider} % mechcheck: off ACC001 -- decorative rule, no contentAdoptable mid-thesis. mechcheck baseline . freezes today's findings so only
new problems fail. Nobody has to fix 300 warnings before they can benefit.
python scripts/digest.py --config students.yaml --out digest.md --offlineOne table per week: who is compiling, who is stuck, word count, error count,
days since the last commit, days to the deadline, and what each thesis is
failing on most. .github/workflows/supervisor-digest.yml posts it as an issue
every Monday.
The digest deliberately reports mechanical counts only. It is a triage list for deciding who needs a message this week — not an assessment.
Every check here is remedial: it catches a mistake after it is made. The preventive half is a document where the mistake is harder to make.
M-Colley/thesis-template is a
LaTeX thesis wired up for all of this — mechcheck.sty already loaded, the
house style already applied, each section a prompt that turns into a
compilation error if it survives to the final version. The template is kept
at zero findings from this checker, online checks included, so the first
finding a student sees is genuinely theirs.
Submission requirements are data, not code
(mechcheck/venues/*.yaml), because they change every
cycle. Adding a venue means adding a file.
document_class: acmart
bibliography_style: ACM-Reference-Format
class_options:
submission:
required: [manuscript]
forbidden: [sigconf]
length:
unit: pages
min_pages: 6
max_pages: 13
excludes: 'references do not count towards the submission page limit'Every pack carries a verified date and a source_url, and VEN008 reminds you
when a pack is more than nine months old. The packs are a convenience, not an
authority: the call for papers is the authority. Each pack also lists what
could not be verified — see the uncertain: block at the bottom of each file.
Shipped: chi, assets, autoui, imwut, trf.
mechcheck check . # the default: thesis, submission stage
mechcheck check . --stage draft # report everything, fail nothing
mechcheck check . --venue assets # + ASSETS accessibility requirements
mechcheck check . --offline # skip the network lookups
mechcheck check . --build-dir build # also read the compiled PDF and log
mechcheck check . --show-skipped # which rules did not run, and why
mechcheck explain FIG003 # what one rule means, and why
mechcheck rules --category accessibility # what exists
mechcheck baseline . # adopt mid-project
mechcheck init . --with-ci --with-sty # set up a projectOutput formats: text, markdown (job summaries and PR comments), github
(inline annotations), sarif (GitHub code scanning), json (the digest).
mechcheck/ the checker
rules/ one module per rule family, prefix per module
venues/ venue packs (data)
texsource.py the LaTeX parser everything else reads through
bibtex.py a tolerant .bib reader
net.py Crossref / OpenAlex / DBLP, cached and polite
extension/ the Chrome extension (engine.js is generated)
browser/
mechcheck.html the entire checker in one file, no install
test-engine.mjs runs that engine in Node against the Python fixtures
build-extension.mjs regenerates the extension's copy of the engine
latex/
mechcheck.sty the in-Overleaf layer
verify-sty.sh compiles the demo against a real TeX and checks the report
demo/ a deliberately flawed document CI compiles to prove it works
.github/workflows/
mechcheck.yml tests + compiles the .sty against a real LaTeX install
overleaf-mirror.yml pulls from Overleaf on a schedule
supervisor-digest.yml the Monday table
scripts/digest.py the multi-repository digest
docs/ setup, rule reference, workflow design, the self-test procedure
tests/ the Python suite, and the fixtures both engines are checked against
Python side: 453 tests passing, and the bibliography verification has been run against the live Crossref, OpenAlex and DBLP APIs.
Browser side: 177 checks passing (node browser/test-engine.mjs) against the
same fixtures as the Python suite — including the assertion that both engines
produce exactly the same findings on the self-test document.
Extension: 22 checks passing in a real browser via extension/test-harness.html
— panel rendering, project-zip reading, filtering, export, error path — and
loaded against a live Overleaf project once, on 2026-08-28. Re-run
docs/testing.md test 1 after updating.
latex/mechcheck.sty: 14 checks passing against TeX Live 2026
(bash latex/verify-sty.sh), the same script CI runs: it compiles the flawed
demo and asserts both that the planted faults are reported and that the
well-formed figure is not. It has not yet been verified inside Overleaf
itself; that is test 3 in docs/testing.md.
MIT — see LICENSE. Use it, change it, hand it to your students.
If it saves you an evening, or if a rule fires wrongly on your paper, an issue is welcome. False positives are the most useful thing you can report: this checker earns its authority by not crying wolf, and every wrong finding is a bug worth fixing.