SkillAgentSearch skills...

citation-crosscheck

Double-blind bibliographic cross-check: one agent extracts each cited entry verbatim from the local .bib, a second independently re-fetches the same paper's record from the web while blind to the .bib, and a third extractor emits a mechanical field-by-field delta to catch dropped/re-ordered authors,…

Install / Use

npx skills add wanshuiyin/Auto-claude-code-research-in-sleep --skill citation-crosscheck

Installs into whichever agent you are using.

About this skill
📄

SKILL.md

Installable skill definition

Quality Score

96/100

Supported Platforms

Universal

Tags

Our assessment of citation-crosscheck

citation-crosscheck scores 96/100 on our quality scale, 37th of 601 Data & Analytics skills we index (top 7%).

Its SKILL.md is 36 KB long, well organised into 53 sections with 3 code examples: a thorough specification that gives an agent plenty to work with.

With 17,747 GitHub stars, it is one of the more widely adopted skills in the catalogue.

Substance
30/30
Structure
18/20
Description
15/15
Adoption
18/20
Freshness
15/15

Maintenance, license and trust

  • The repository was last updated yesterday, so citation-crosscheck is actively maintained.
  • It is released under the MIT license, a permissive license that allows use, modification and commercial use with attribution.
  • Its trust signals score 100/100, with no cautions. These come from repository metadata, not a code audit — read the skill file before letting an agent act on it.

citation-crosscheck compared with similar skills

All 4 of these similar skills score higher than citation-crosscheck; compare them before choosing.

SkillScoreStarsUpdatedFormat
citation-crosscheck (this skill)by wanshuiyin9617.7k1d agoSKILL.md
algorithmic-artby anthropics100177.9k27d agoSKILL.md
pptxby anthropics100177.9k27d agoSKILL.md
designby nextlevelbuilder100137.2k5d agoSKILL.md
ui-ux-pro-maxby nextlevelbuilder100137.2k5d agoSKILL.md

Frequently asked questions

How do I install citation-crosscheck?
Run npx skills add wanshuiyin/Auto-claude-code-research-in-sleep --skill citation-crosscheck. The install tabs above show the steps for each supported agent.
Which AI agents does citation-crosscheck work with?
It is written for Universal, as a SKILL.md file. Other agents that read the same format can often use it too.
Is citation-crosscheck safe to use?
It is MIT-licensed and scores 100/100 on trust signals. Skills are instructions an agent will follow, so read the file before installing it and do not approve commands you do not understand.
Is citation-crosscheck still maintained?
The repository was last updated yesterday, so citation-crosscheck is actively maintained.

name: citation-crosscheck description: "Double-blind bibliographic cross-check: one agent extracts each cited entry verbatim from the local .bib, a second independently re-fetches the same paper's record from the web while blind to the .bib, and a third extractor emits a mechanical field-by-field delta to catch dropped/re-ordered authors, wrong years, wrong venues, and bad DOIs/arXiv ids. The main agent (never a shard) assigns the MATCH/MINOR/MISMATCH verdict per reference, and exports a side-by-side spreadsheet. Use when user says "citation crosscheck", "double-blind citation check", "verify bib against scholar", "bib field audit", "引用交叉核对", or before submission when bibliography FIELD accuracy (not just existence) must be certified." argument-hint: "[paper-directory-or-bib-file] [--xlsx <path>] [--cited-only|--all]" allowed-tools: Bash(*), Read, Grep, Glob, Write, Agent, WebFetch, WebSearch

Citation Cross-Check (double-blind bib-vs-web field audit)

🔒 Do not wrap this skill in /loop, /schedule, or CronCreate. It is verdict-bearing — it emits a per-reference verdict that gates .bib edits (an evidence instrument, not a cross-model acquittal; see the acceptance-gate note in Step 2). Re-running it on a timer adds no signal (the verdict changes only when the bibliography changes). Schedule the external wait that precedes it: bibliography finalized → run once. See shared-references/external-cadence.md.

Why this exists (and how it differs from citation-audit)

citation-audit is a single cross-model reviewer that judges three things holistically: existence, metadata, and context-fit (is the cited paper used for a claim it actually supports?). This skill does not do context-fit. It does one thing that a single reviewer cannot do reliably: an adversarial, double-blind, field-level diff of the local .bib entry against an independently reconstructed record.

The failure mode it targets is the hand-entry error that survives a plausibility read: a dropped or re-ordered author, a wrong year, a swapped venue, a DOI that 404s. A single reviewer that sees the .bib entry tends to anchor on it and rate it "looks correct." Two agents that never see each other's output cannot anchor — a discrepancy only survives if both independent records agree, and the verifier reports exactly where they diverge.

In practice it catches dropped/re-ordered authors, wrong years, and bad DOIs that a single-pass audit rated "looks clean" — while NEEDS_WEBCHECK stops it from false-flagging a legitimate non-arXiv (blog / tech-report) citation as fabricated. Use it in addition to citation-audit, not instead: citation-audit protects your review score (context-fit); this protects against the integrity red flag of a wrong or fabricated reference (existence + fields).

| | citation-audit | citation-crosscheck (this) | |---|---|---| | Owns | existence + metadata + context-fit | existence + field accuracy, adversarially | | Method | one cross-model reviewer | two blind extractors + a mechanical field-diff | | Reads the .tex claims? | yes | no (bibliographic only) | | Deliverable | KEEP/FIX/REPLACE/REMOVE per entry | side-by-side .xlsx + MATCH/MINOR/MISMATCH | | Best at catching | wrong-context citation | dropped/re-ordered author, wrong year/venue/DOI |

Constants

  • BLIND = true — the web agent (B) MUST be prompted with only each paper's title + first author, plus its opaque dedup_key (a run-local ordinal that carries no bibliographic content), and MUST NOT read the .bib or any repo file. This is the whole point; do not relax it.
  • SOURCES = arXiv (/abs/<id>, /bibtex/<id>), DBLP, CrossRef, ACL Anthology, NeurIPS/PMLR proceedings, OpenReview. Report what the fetched page shows; never fabricate a field.
  • VERDICTS (assigned by the main agent only, in Step 4 — never by a shard) = MATCH (bibliographically equivalent, cosmetic-only) / MINOR (formatting or a preprint-vs-published venue, no correction required) / MISMATCH (real discrepancy: wrong/missing author, wrong year, wrong venue, wrong id) / NEEDS_WEBCHECK (B could not confirm existence, but the entry is a @misc/blog/tech-report whose url B's index-based search structurally cannot see — the main agent must fetch that url in Step 4 before any verdict; never downgrade such an entry straight to MISMATCH).
  • SCOPE = cited-only by default (entries that render in the .bbl); --all also covers uncited .bib entries.
  • OUTPUT = <paper-dir>/citation_crosscheck_<date>.xlsx (+ the four intermediate JSONs — cx_A_ourbib, cx_B_web, cx_C_fielddiff, and the gate's cx_gate.json — kept for audit).

Workflow

Step 1 — Resolve the citation set (deterministic, main agent)

# Search the paper's own source tree, not the whole repo: bundled author-kit /
# template .tex carry demo \cite{...} that inflate the set. Set PAPER_DIR to the dir of
# the main .tex (\documentclass + \bibliography); recurse (cites often live in
# sections/*.tex); exclude vendored/example dirs.
PAPER_DIR="<dir of the paper's main .tex>"
cd "$PAPER_DIR"
python3 - <<'PY'
import re, pathlib
# Skip vendored/example/template trees: bundled author kits carry demo \cite{...}.
# NOTE: this is a NAME match — a dir you *want* audited must not match (rename it or
# narrow SKIP), e.g. `examples_of_results/` would be skipped.
SKIP = re.compile(r'(?i)(^|/)(.*template.*|.*upstream.*|.*example.*|.*vendor.*)(/|$)')
# Verbatim-ish blocks AND inline \verb|...| / \lstinline are code samples, not citations.
VERB = re.compile(r'\\begin\{(verbatim|lstlisting|minted|Verbatim)\}.*?\\end\{\1\}', re.S)
# The optional arg may itself contain brackets: \lstinline[language={[LaTeX]TeX}]|...|
VERBINLINE = re.compile(r'\\(?:verb|lstinline)\*?'
                        r'(?:\[(?:[^\[\]]|\[[^\]]*\])*\])?\s*'
                        r'([^\sA-Za-z])(?:(?!\1).)*\1', re.S)
# url/href/path args may contain a literal % that is NOT a comment. Neutralize them
# BEFORE comment-stripping, or a real \cite later on that line is silently lost.
URLCMD = re.compile(r'\\(?:url|href|path|nolinkurl)\s*\{[^{}]*\}')
COMMENT = re.compile(r'(?m)(?<!\\)%.*$')
# EXPLICIT ALLOWLIST of cite commands. Do NOT build this from a prefix/suffix pattern:
# a Cartesian `(no|paren|auto|...)?cite(s|p|t|...)?` both invents commands (\citecite,
# \parenciteyear -> fake keys from any brace arg) and misses real ones (\footfullcite,
# \volcite, \notecite). Longest-first so \footfullcite wins over \footcite.
# volcite-family: the FIRST brace arg is a volume/page, NOT a cite key.
VOLCITE = {'volcite', 'Volcite', 'pvolcite', 'Pvolcite', 'fvolcite', 'ftvolcite',
           'svolcite', 'tvolcite', 'avolcite',
           'volcites', 'Volcites', 'pvolcites', 'Pvolcites', 'fvolcites', 'svolcites',
           'tvolcites', 'avolcites', 'ftvolcite', 'ftvolcites'}
NAMES = sorted({
    # natbib / LaTeX core (capitalized variants are real natbib commands)
    'cite', 'Cite', 'citep', 'citet', 'citealp', 'citealt', 'citenum', 'citeyear', 'citeyearpar',
    'citeauthor', 'citefullauthor', 'Citep', 'Citet', 'Citealp', 'Citealt', 'Citeauthor',
    # biblatex singular + plural
    'parencite', 'Parencite', 'footcite', 'footcitetext', 'footcitetexts', 'textcite',
    'Textcite', 'smartcite', 'Smartcite', 'supercite', 'autocite', 'Autocite', 'fullcite',
    'footfullcite', 'notecite', 'Notecite', 'pnotecite', 'Pnotecite', 'fnotecite',
    'citetitle', 'citeurl', 'citedate', 'autocites', 'parencites', 'textcites',
    'smartcites', 'supercites', 'footcites', 'cites', 'Cites', 'nocite', 'nocites',
    # aliases + field/list forms + bibentry — real biblatex/natbib families
    'citetalias', 'citepalias', 'Citetalias', 'Citepalias', 'citefield', 'citelist',
    'bibentry', 'fullciter',
    *VOLCITE,
}, key=len, reverse=True)
# (?![A-Za-z]) stops \cite matching the \citeXYZ of an unknown command; the trailing run
# accepts any mix of [opt] and {grp} so \cites{k1}[note]{k2} yields BOTH keys.
CITE = re.compile(r'\\(?:' + '|'.join(map(re.escape, NAMES)) + r')\*?(?![A-Za-z])'
                  r'((?:\s*(?:\[[^\]]*\]|\{(?:[^{}]|\{[^{}]*\})*\}))+)', re.S)
# Strip [optional] args BEFORE reading brace groups, or a braced note like
# \cite[see {Smith} p.3]{k} contributes 'Smith' as a phantom key.
OPTARG = re.compile(r'\[[^\]]*\]', re.S)
GROUP = re.compile(r'\{((?:[^{}]|\{[^{}]*\})*)\}', re.S)
CMDNAME = re.compile(r'\\([A-Za-z]+)')
# \addbibresource[opts]{x.bib} takes an optional arg; \bibliography{a,b} may list several.
BIBRES = re.compile(r'\\(?:bibliography|addbibresource)\s*(?:\[[^\]]*\])?\{([^}]+)\}')
keys, bibs, nocite_all = set(), [], False
for p in sorted(pathlib.Path('.').rglob('*.tex')):
    if SKIP.search(str(p.parent)):
        continue
    src = p.read_text(errors='replace')
    src = VERB.sub(' ', src)
    src = VERBINLINE.sub(' ', src)
    src = URLCMD.sub(' ', src)
    src = COMMENT.sub('', src)
    for res in BIBRES.findall(src):
        bibs += [r.strip() for r in res.split(',') if r.strip()]
    for m in CITE.finditer(src):
        raw_cmd = CMDNAME.match(m.group(0)).group(1)
        cmd = raw_cmd.lower()
        grps = GROUP.findall(OPTARG.sub(' ', m.group(1)))
        if raw_cmd in VOLCITE and len(grps) > 1:
            # volcite takes {volume}{key}; the PLURAL volcites alternates
            # {vol}{key}{vol}{key}... — so keep the odd-indexed args, not just grps[1:],
            # or the second volume number is harvested as a phantom key.
            grps = grps[1::2]
        elif raw_cmd in ('citefield', 'Citefield', 'citelist', 'Citelist',
                          'bibentry') and len(grps) > 1:
            # These commands take a single key(s) arg. If a stray second brace group
            # follows (a defensive read of an ambiguous source), do not harvest it as a
            # phantom key ('\citelist{k}{publisher}' -> just 'k', not '{k, publisher}').
            grps = grps[:1]
        for grp in grps:
            toks = [''.join(g.split()) for g in grp.split(',')]
            # \nocite{*} AND \nocite{*,named}: the wildcard renders EVERY bib entry.
            if cmd.startswith('nocite') and '*' in toks:
                nocite_all = True      # see the NOCITE_ALL note below
            for k in toks:
                if k and k != '*':
                    keys.add(k)
print(f"BIB_RESOURCE={bibs[0] if bibs else '(none found - use the fallback below)'}")
print(f"BIB_RESOURCES_ALL={bibs}")
print(f"NOCITE_ALL={nocite_all}")
print(f"CITED_KEY_COUNT={len(keys)}")
import pathlib
# Persist the harvest so the shell can read it back deterministically (the heredoc only
# PRINTS to stdout; it cannot set a parent-shell variable).
pathlib.Path('/tmp/cx_cited_keys.txt').write_text('\n'.join(sorted(keys)))
pathlib.Path('/tmp/cx_cited_count.txt').write_text(str(len(keys)))
print('\n'.join(sorted(keys)))
PY
# Set $CITED_KEY_COUNT as a REAL shell variable (Step 3.5 passes it as --expect): the
# heredoc above only prints it, so read it back from the file the harvest just wrote.
CITED_KEY_COUNT=$(cat /tmp/cx_cited_count.txt)

Cross-check against a compiled main.bbl by KEY SET, not count: a count match with a substituted key still hides a harvest miss (drop one real cite, gain one stray → same count). Extract the rendered keys and diff the sets:

# \bibitem[label]{key} (natbib/plain) OR \entry{key}{type}{} (biblatex) — key is the first
# brace group either way. Empty diff = harvest and .bbl agree.
grep -oE '\\(bibitem|entry)\*?(\[[^]]*\])?\{[^}]+\}' main.bbl 2>/dev/null \
  | sed -E 's/.*\{([^}]+)\}$/\1/' | sort -u > /tmp/cx_bbl_keys.txt
comm -3 <(sort -u /tmp/cx_cited_keys.txt) /tmp/cx_bbl_keys.txt   # any output ⇒ investigate

**The .bbl cross-check is conditional — an uncompiled submission has none. When absent, confirm `C

Truncated for display — read the full file on GitHub.

Related Skills

View on GitHub
GitHub Stars17.7k
CategoryData
Updated1d ago
Forks1.7k

Languages

Python

Trust signals

100/100

From repository metadata: license, adoption, age and documentation. Not a code audit — see the Safety scan above for what the skill file itself contains.

No cautions