skills/bluebook-audit/skills/audit-crossrefs/SKILL.md
Phase 7: Convert hardcoded cross-references to auto-updating NOTEREF fields
npx skillsauth add edwinhu/workflows audit-crossrefsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Convert hardcoded supra/infra note numbers to NOTEREF field codes that auto-update when footnotes are renumbered, then tie each cross-reference to a bibkey from references/sources.bib so the bibliography becomes the semantic identity layer.
sources.bib ← make_bib_from_docx.py # BOOTSTRAP (once per paper, Gemini)
↓
docx ← create_crossrefs.py # ONE-TIME conversion of hand-typed text
↓
docx ← audit_crossref_targets.py --grep --apply # DRIFT CORRECTION (deterministic)
↓
docx ← bib_integrate.py --bib references/sources.bib # BIBKEY-TAG SYNC (deterministic)
(also pre-computes cached display values)
All four live in ${CLAUDE_SKILL_DIR}/../../../../skills/bluebook-audit/scripts/.
After a Word session that added/edited footnotes (including new hand-typed
<X>, supra note N text), run:
"${CLAUDE_SKILL_DIR}/../../../../skills/bluebook-audit/scripts/sync_crossrefs.sh" \
draft.docx references/sources.bib
The shell wrapper runs create_crossrefs → audit_crossref_targets → bib_integrate in sequence. All three are idempotent and fully deterministic — re-running on a clean doc does nothing; ~5 seconds total runtime.
| Stage | Script | Gemini? | When |
|-------|--------|---------|------|
| Bootstrap | make_bib_from_docx.py | Yes (one-off batch) | Paper has hand-typed footnotes and no sources.bib yet |
| Initial conversion | create_crossrefs.py | No | Convert hardcoded supra note N text → NOTEREF fields |
| Drift correction | audit_crossref_targets.py --grep | No | After footnote edits — fix any supras whose target drifted |
| Drift correction (ambiguous) | audit_crossref_targets.py --batch | Yes | Only for refs grep can't resolve uniquely |
| Bibkey-tag sync | bib_integrate.py | No | After cross-refs settle — rename bookmarks to _RefBib_<bibkey> and pre-compute cached display |
Convert hardcoded <X>, supra note N text to NOTEREF cross-reference fields.
# Preview
uv run python3 "${CLAUDE_SKILL_DIR}/../../../../skills/bluebook-audit/scripts/create_crossrefs.py" --docx <path> --dry-run
# Apply
uv run python3 "${CLAUDE_SKILL_DIR}/../../../../skills/bluebook-audit/scripts/create_crossrefs.py" --docx <path>
references/sources.bib yet):
uv run --with lxml --with google-genai --with google-cloud-storage python3 \
"${CLAUDE_SKILL_DIR}/../../../../skills/bluebook-audit/scripts/make_bib_from_docx.py" \
--docx <path> --out references/sources.bib
Walks the docx footnotes, skips supra-only / bio fns, sends each first-cite candidate to Gemini Vertex Batch, emits BibTeX with note = {fnN} linking each entry to its source footnote.audit_crossref_targets.py --grep --apply (see below). Hand-typed supra note N numbers go stale as footnotes are added or reordered; the create_crossrefs script faithfully bookmarks the literal N, which can be silently wrong for every reference. Grep resolves the easy cases deterministically; only ambiguous refs need --batch.bib_integrate.py --bib references/sources.bib --apply. Renames bookmarks to _RefBib_<bibkey>, retargets supras by bibkey, and pre-computes cached display values so the doc renders correctly on first read (no F9 needed).fn+F9 (Mac) or Ctrl+A → F9 (Windows) to force-refresh fields if needed.audit_crossref_targets.py (drift correction, also runnable standalone)# Mechanical audit only — flags refs whose surnames don't appear in the target footnote
uv run --with lxml python3 \
"${CLAUDE_SKILL_DIR}/../../../../skills/bluebook-audit/scripts/audit_crossref_targets.py" \
--docx <path>
# Deterministic first pass — grep each reference for its first cite. No LLM cost.
# On a 138-ref test set: resolved 62 refs unique with 98% accuracy, deferred
# 76 truly ambiguous cases. Always run this BEFORE invoking Gemini/Batch — it
# strips the easy cases and saves LLM calls.
uv run --with lxml python3 \
"${CLAUDE_SKILL_DIR}/../../../../skills/bluebook-audit/scripts/audit_crossref_targets.py" \
--docx <path> --grep --apply
# Grep + production LLM batch for the residue (recommended for full pipeline)
uv run --with lxml --with google-genai --with google-cloud-storage python3 \
"${CLAUDE_SKILL_DIR}/../../../../skills/bluebook-audit/scripts/audit_crossref_targets.py" \
--docx <path> --grep --batch --apply --location global
# LLM-only paths (skip grep — coverage-risky for --gemini):
uv run … --docx <path> --batch --model gemini-3.1-flash-lite-preview --apply
uv run … --docx <path> --gemini --apply
bib_integrate.py (bibkey-tag sync, fully deterministic)uv run --with lxml python3 \
"${CLAUDE_SKILL_DIR}/../../../../skills/bluebook-audit/scripts/bib_integrate.py" \
--docx <path> --bib references/sources.bib --apply
What it does:
sources.bib.fn_id → bibkey map from current docx content — sidesteps any drift between bib's note={fnN} tag and current numbering._RefBib_<bibkey> bookmarks wrapping body footnoteReferences. Multi-cite footnotes get multiple bookmarks (one per source) — each pointing at the same body footnoteReference, so the displayed number stays the same; only the bookmark name differs by source.--bio-count, default 3 for *, †, ‡ author bios that don't consume display numbers when numRestart=eachSect is set).SUPRA_BIB_AUDIT.md listing any supras whose surnames don't match the picked bibkey's haystack (author + title + howpublished + bibkey).Fully deterministic — no LLM calls. Idempotent on re-runs.
make_bib_from_docx.py (bootstrap, Gemini)uv run --with lxml --with google-genai --with google-cloud-storage python3 \
"${CLAUDE_SKILL_DIR}/../../../../skills/bluebook-audit/scripts/make_bib_from_docx.py" \
--docx <path> --out references/sources.bib
Walks the docx footnotes, splits multi-cite footnotes on ;, sends each first-cite candidate to a Vertex AI Batch job (one independent request per citation; gemini-3.1-flash-lite-preview default). Emits BibTeX entries with bibkey conventions firstauthorlastYEAR for academic works and short slugs (gao2017, crs2024, secReg2020) for institutional sources.
Each entry includes note = {fnN} linking back to the source footnote.
Once sources.bib exists, maintain it directly — do not regenerate. Edits to the bib (typos, missing fields, new sources) should be in-place.
Vertex AI batch requires:
gcloud auth application-default login for ADC--project (default $GOOGLE_CLOUD_PROJECT or activist-defense-nal)--gcs-bucket (default $GEMINI_BATCH_BUCKET or nal-batch-extraction)us-central1 (default)For gemini-3.x models, thinkingLevel: MINIMAL is set automatically — without it, batch responses silently return empty content (see /gemini-batch SKILL gotcha 12).
Output written to <docx-dir>/scratch/:
crossref_audit.json — every cross-reference with current target + match statuscrossref_remap.json — Gemini's proposed corrections (with confidence)CROSSREF_AUDIT.md — human-readable diff reportThe audit is safe to run standalone without re-running the rest of the bluebook-audit workflow. Use it any time create_crossrefs has been run and you want to validate that hand-typed supra/infra numbers were correct.
--grep first. Cheapest, deterministic, ~98% precision on unique
matches. Resolves ~half the refs at $0 cost.--batch (Vertex AI, per-ref independence) on the deferred set.--grep worksA cross-reference reads <short-form identifier>, supra/infra note N. The
identifier can be any Bluebook short form, not just author surnames:
Hu, Malenko & ZytnickGAO Report, CRS Report, Rosenberg, 2020 SEC RegulationBest Practice Principles, Policies and Procedures, Senate Banking Committee LetterISS v. SECChoi, Fisch & Kahan, Power of Proxy AdvisorsFor each reference the script:
See, But see, E.g.).<X>, supra/infra note <N> phrase from each candidate footnote, so a footnote that merely cites the same identifier via supra doesn't count as the first cite.unique, apply deterministically.ambiguous, defer to LLM (picking the earliest is wrong ~17% of the time — typically when the identifier appears in passing in one footnote and as a first cite in another).no_match, defer to LLM (which has the catalog and can guess, or correctly say "not in document").Iron Law: Neither Gemini pass is fully trustworthy on its own. A two-batch consensus is high-confidence, but the residual disagreements ALWAYS require human first-cite verification. Do not stop after --apply.
The failure modes we observed on a real 248-footnote draft:
See also or multi-cite list (model misses it and picks a different paper by the same author)See/hereinafter/inline-discussion lead-ins are flagged as mismatches even when correct.for fn in fns.findall('.//w:footnote'):
txt = ''.join(t.text or '' for t in fn.iter('w:t'))
if re.search(r'<surname-pattern>', txt) and 'supra' not in txt[:300].lower():
print(fn.get('w:id'), txt[:200])
(source_fn, surnames, current_bookmark) → correct_fn_id, save as crossref_remap_manual.json, and apply with --remap PATH --apply.See also/multi-cite list.SUPRA_UNRESOLVED.md listing the source FN, surnames, and the search patterns tried.--gemini and --batch)SUPRA_UNRESOLVED.md written (or empty if all resolved)| Pattern | Example | Result |
|---------|---------|--------|
| Single supra | supra note 42 | NOTEREF to FN42 bookmark |
| Single infra | infra note 188 | NOTEREF to FN188 bookmark |
| Range | infra notes 209-210 | Two NOTEREFs with separator |
| With pincite | supra note 42, at 15 | NOTEREF + roman , at 15 |
| Existing NOTEREF | (already converted) | Skipped |
supra Section I.A., infra Part III)audit_crossref_targets.py run + Step 4 manual verification complete; no NOTEREF points at an unrelated footnotePresent final summary to user:
tools
Use when "query Dewey Data", "deweydata.io", "SafeGraph places/patterns/spend", "Advan foot traffic", "POI / points of interest", "mobility data", "dataplor", "Veraset", "PassBy", "crypto/Bitcoin ATM locations", or any pull from the Dewey Data academic marketplace (UVA/NYU Platform Subscription) via the deweypy/deweydatapy client, DuckDB, or the Dewey MCP server.
development
Use when submitting jobs to UVA HPC (Rivanna/Afton), writing Slurm scripts (sbatch/srun/squeue), converting SGE to Slurm, running compute on any Slurm-managed cluster, or building WRDS data pipelines with polars on HPC. Triggers: 'submit to HPC', 'sbatch', 'squeue', 'slurm job', 'run on Rivanna', 'run on Afton', 'HPC array job', 'convert SGE to Slurm', 'polars on HPC', 'WRDS from HPC'.
testing
Internal skill for literature review and source materialization. Called after brainstorm, before setup. NOT user-facing.
development
This skill should be used when the user asks to "add paper", "paperpile add", "fetch PDF for", "find and add", "search paperpile", "find in paperpile", "paperpile search", "label paper", "trash paper", "download paper", "paperpile index", "edit paper metadata", "update paper title", "fix paper author", "paperpile edit", "find PDF online", "search google for PDF", "resolve PDF", "fetch PDF for citation", "get full-text for DOI", "resolve cite to PDF", or any request to manage their Paperpile library or resolve a citation to a local PDF.