touchneedle
Verifies that the citations in a document are real, accurately described and consistently used — including the signature a fabricated bibliography leaves behind.
The pitch
A model-drafted reference list fails in a specific way: a real title
carrying the wrong authors, or a perfectly plausible paper that does not
exist. touchneedle parses the reference list straight out of Markdown or
.docx and checks each entry against whichever authority can
actually confirm it.
Quick start
$ pip install touchneedle $ touchneedle check thesis.docx --out report.md
Exit status 2 when something needs attention, 0 when the list is clean —
so the same command drops into CI unchanged. Nothing needed beyond
Python; pandoc on PATH for .docx input, and for
nothing else.
What it checks
- Existence and metadata arXiv ids against arXiv, DOIs against Crossref, RFCs and Internet-Drafts against the IETF datatracker (including whether the cited revision is still current), quoted titles against Crossref then OpenAlex, and a bare URL against the page it actually returns.
- Internal consistency Every in-text citation resolves to a list entry, every entry is cited somewhere, and 2025a / 2025b suffixes are unambiguous. Shortened notes and Ibid. link back to the citation they repeat.
- Claim support The pass that needs reading rather than fetching: each citation paired with the sentence making the claim, to be ruled SUPPORTED / PARTIAL / UNSUPPORTED one row at a time. This catches a real source attached to a claim it does not make.
- Four style families Author-date, numeric, MLA and footnote styles, auto-detected or
forced with
--style.
Why not a .bib checker
Commercial citation checkers want a .bib file and check it against
academic databases. That covers journal articles and misses standards, specifications,
vendor documentation and blog posts — in a lot of real bibliographies, half
the list. touchneedle reads the prose reference list instead.
Two ways to run it
check is entirely scripted. It fetches records and compares
fields, so it returns the same answer every run with no model involved
anywhere in it. claims is the half a script cannot finish: it
emits a worklist, and ruling on those rows means reading the sources.
Work that list from a terminal, or run the tool inside a coding agent that calls the same script for the scripted pass and then reads each source to fill in the second. The agent path is not a wrapper — it is the same file either way.
# as a Claude Code plugin > /plugin marketplace add nicoleman0/touchneedle > /plugin install touchneedle