touchneedle

Verifies that the citations in a document are real, accurately described and consistently used — including the signature a fabricated bibliography leaves behind.

  • Python 3.11+
  • CLI
  • Claude Code plugin
  • MIT

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