Manage refs
Cross-cutting reference manager for medical manuscripts. Single entry point for citation-key validation, journal-CSL pandoc rendering, manuscript ↔ DOCX cross-reference QC, marker conversion (``[N]`` ↔ ``[@key]``), and native Zotero CWYW field-code injection. Replaces the inline reference-handling that previously lived in ``/write-paper`` Phase 7.6 and is reused by ``/revise``, ``/peer-review``, ``/sync-submission``, and any skill that produces a journal submission. Audit-only verification stays in ``/verify-refs`` — this skill writes (renders, injects, converts); that skill only reads.From its SKILL.md
npx -y skills add Aperivue/medsci-skills --skill manage-refsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
SKILL.md
18.7 KB, ~4.8k tokens by cl100k_base, as published. Nobody here has run it
Manage-Refs Skill
Canonical source (issue #16). This SKILL.md is the single canonical reference for the reference-workflow (validate keys → render CSL → convert markers → QC cross-references → inject Zotero CWYW). Audit-only bib verification is owned by
skills/verify-refs/SKILL.md. Any user-scope rule or external note about reference handling should point here (workflow) or to verify-refs (audit) rather than restating the "how", to prevent drift.
You are routing reference-handling work for a medical manuscript. The user is somewhere in the lifecycle — drafting, building a circulation DOCX, swapping CSL after a journal rejection, fixing a cross-reference defect surfaced by QC, or wiring up live Zotero field codes for a co-author Word workflow. Pick the right tool from the decision table; do not invent a parallel pipeline.
Why This Skill Exists
Reference handling spans every late-stage skill: /write-paper builds the
first DOCX, /revise rebuilds it after each reviewer round, /peer-review
emits a critique that quotes references back, /sync-submission packages the
final tarball, /find-journal informs CSL swaps on rejection cascade, and
/verify-refs audits the bibliography. Until 2026-05-01 these scripts lived
under skills/write-paper/scripts/, which made /revise and /sync-submission
silently depend on a sibling skill — a layering inversion that broke when
/write-paper was loaded into a non-research project. Moving the
lifecycle tools here turns reference handling into a first-class concern
with one decision tree, one set of CSL files, and one provenance file
(NOTICE.md) for the vendored Zotero CWYW writer.
Validated 2026-05-01 against a 21-reference meta-analysis manuscript (a meta-analysis project's submission) for both pandoc-citeproc and Zotero-CWYW paths.
Anti-Hallucination Guarantees
-
Citekey discipline (Phase 0): every in-text citation must be
[@bibkey]resolvable inrefs.bib.scripts/check_citation_keys.pyis a hard gate — UNDEFINED keys exit non-zero and block the build.[@NEW:topic]placeholder convention: while drafting,/write-papermay emit[@NEW:topic_slug]markers for citations the author still needs to source.check_citation_keys.pyclassifies these asNEW_PLACEHOLDER(not UNDEFINED) and exits 0 — the build is allowed to proceed during drafting. Phase 7.6 (DOCX render) is a hard gate: zero NEW_PLACEHOLDER entries must remain. Resolve each by adding the citation to Zotero (then/lit-syncrefreshes refs.bib) and replacing the placeholder with the real[@bibkey]. Never let a[@NEW:...]reach a rendered DOCX. -
No hand-typed References list — references are always rendered by pandoc citeproc + journal CSL or by the Zotero Word plugin (CWYW). See
~/.claude/rules/manuscript-references.md. -
Zotero metadata is never invented —
inject_zotero_cwyw.pyfetches item data live fromhttp://localhost:23119. Any HTTP failure aborts with a non-zero exit so partial bibliographies never reach the user. -
Marker conversion is mapping-driven —
md_marker_convert.pywill never guess a Zotero key for a number; unmapped markers stay as[N]and are reported on stderr. -
Cross-reference QC is a submission gate —
scripts/check_xref.py--strictexits 1 on anyMISSING_DOCX/MISSING_BODY/MISMATCH, blocking pipelines that try to ship a DOCX whose Table/Figure citations don't match captions. -
Audit boundary: this skill writes; bibliographic correctness against PubMed/CrossRef stays in
/verify-refs. Always invoke/verify-refsafter a render before signing off — one read-only audit, one writer.
Decision Tree
| Situation | Tool | Why |
|---|---|---|
Validate [@bibkey] ↔ refs.bib (UNDEFINED / UNUSED keys) | scripts/check_citation_keys.py | Hard build gate, runs in seconds |
| Single-author submission lockdown, frozen output | scripts/render_pandoc.sh -j <journal> | Reproducible, CI-friendly |
| Cascade rejection (e.g., ER → JVIR → CVIR) | render_pandoc.sh with new -j | CSL swap reformats references in seconds |
| Verify a journal CSL renders the in-text format / DOI / journal-name style the author guide actually requires | scripts/check_csl_render.py --csl <x>.csl --bib refs.bib --journal <key> | A stub/"dependent" CSL inherits its parent's format, which may differ from the guide (parenthetical vs superscript, DOI kept, full journal names). Run BEFORE submission, not after the proof PDF |
| Reference list prints FULL journal names but the journal wants NLM abbreviations | scripts/fill_journal_abbrev.py | Resolves each entry DOI → PMID → PubMed NLM shortjournal into the .bib so CSL form="short" renders abbreviations; authoritative source, never invents abbreviations |
| Reviewer revision: add 1–2 refs to a Word doc with co-authors live | Zotero Word plugin (user GUI) | Minimal disruption to track-changes flow |
| Reviewer revision: bulk reference change | Edit markdown SSOT, re-run render_pandoc.sh | Consistency, no cherry-pick risk |
Migrate [N] numeric markers → [@key] for pandoc | scripts/md_marker_convert.py --to-keys | Mapping-driven, partial conversion safe |
Convert [@key] → [N] for round-trip / debug | scripts/md_marker_convert.py --to-numbers | Same map, opposite direction |
| Wire native Zotero CWYW field codes into a .docx (live Refresh in Word) | scripts/inject_zotero_cwyw.py | Co-author Word workflow, post-circulation editability |
| Manuscript ↔ rendered DOCX cross-reference QC | scripts/check_xref.py --strict | Submission gate (P0 blocker on mismatch) |
| Figures/tables submitted as separate attachments (radiology, most medical journals) | check_xref.py --strict --allow-separate-attachments | Downgrades MISSING_DOCX to WARN; MISSING_BODY/MISMATCH remain P0 |
| v_(N+1) docx build-time regeneration check | check_xref.py --vN-docx-md5 <prev>.docx [--vN-md <prev>.md] | Defense-in-depth: identity = unmodified seed copy; missing diff lines = body not regenerated |
| Duplicate bibliography in the built artifact | scripts/check_reference_duplication.py --docx <built>.docx (or --text <rendered>.md) | Fires when the reference list is duplicated — DUP_REF_HEADING / REF_NUMBER_RESTART / REF_SIGNATURE_DUP (Major). Catches the hybrid hand-typed ## References list + pandoc --citeproc auto-bibliography, which renders two lists (the second often after the legends). Run after any citeproc build |
Publisher markup in a .bib title (renders as garbage) | scripts/check_bib_title_markup.py --bib refs.bib --strict | CrossRef ships <scp>WHO</scp> / <i>IDH</i> in titles and a DOI-add stores them verbatim; BBT then escapes them ({$<$}scp{$>$}) or strips them without restoring the space (andTERTPromoter, 1p/19q,IDH). verify_refs proves the reference is true; this proves it will print. TITLE_MARKUP / TITLE_FUSION (Major) |
| Master pre-submission gate (recommended before any submission) | scripts/pre_submission_gate.sh | Chains check_citation_keys → check_bib_title_markup → verify_refs --strict → render_pandoc (optional) → check_xref --strict; single artifact qc/pre_submission_gate.json |
| Direct render with a built-in reference audit | scripts/render_pandoc.sh (audits the .bib via /verify-refs first; blocks on FABRICATED/MISMATCH/duplicates) | Defense-in-depth so even a direct render call cannot ship hallucinated citations; best-effort (skips with a warning if /verify-refs is not alongside), opt out with -S. The master gate passes -S since it audits in stage 2 |
| Bibliographic audit against PubMed / CrossRef | delegate to /verify-refs | Audit-only — keep writer/auditor separation |
Workflows
A. Pandoc citeproc (default for solo authors and final submissions)
User provides manuscript.md with [@bibkey] citations + refs.bib.
- Gate:
python "${CLAUDE_SKILL_DIR}/scripts/check_citation_keys.py" manuscript.md refs.bib— exits non-zero on UNDEFINED keys. Fix and re-run. - Render:
Bundled CSLs (in"${CLAUDE_SKILL_DIR}/scripts/render_pandoc.sh" \ -j european-radiology \ -i manuscript.md \ -b refs.bib \ -o manuscript_final.docxcitation_styles/):european-radiology,radiology,american-journal-of-roentgenology,cardiovascular-and-interventional-radiology,korean-journal-of-radiology,vancouver,vancouver-superscript,springer-basic-brackets,springer-vancouver-brackets. Useradiologyfor RYAI; usevancouverfor JVIR (no dedicated CSL). - QC:
Treatpython3 "${CLAUDE_SKILL_DIR}/scripts/check_xref.py" \ --md manuscript.md --docx manuscript_final.docx \ --out qc/xref_audit.json --strictsubmission_safe: falseas a halt. Route fixes by symptom — see the table inreferences/check_xref_symptoms.md. - Audit hand-off: invoke
/verify-refsfor the PubMed/CrossRef audit before sign-off.
B. Zotero CWYW (co-author Word workflow)
User has a markdown SSOT and wants reviewers to edit citations directly in
Word. Each reference must already exist as a Zotero item; the user supplies
a [N] → ZoteroKey mapping.
- Convert markers:
Optionally stage withpython3 "${CLAUDE_SKILL_DIR}/scripts/md_marker_convert.py" \ --input manuscript.md --output manuscript_keys.md \ --map ref_map.json --to-keys--active-ns 1,2,3,4,19for a sample build first (validated on an active meta-analysis project: 5-ref sample reduces Word Refresh blast radius when debugging). - Render to .docx with pandoc (workflow A) so the body has plain text
[@key]markers, OR pre-build a .docx some other way that still contains plain[@key]text. - Inject CWYW:
The script fetches Zotero metadata via the local connector (port 23119); any HTTP failure aborts with non-zero exit.python3 "${CLAUDE_SKILL_DIR}/scripts/inject_zotero_cwyw.py" \ --input manuscript_keys.docx --output manuscript_cwyw.docx \ --user-id 16613550 --keys-from keys.txt - First-build instruction (REQUIRED — see Known Limitation #1): open the output in Word → Zotero tab → Add/Edit Bibliography once. After that, Refresh keeps citations and bibliography in sync as authors edit.
- Surgical patches are unsafe: for ref additions in later rounds, edit
the markdown SSOT and rebuild the whole .docx instead of regex-patching
the post-CWYW file. Zotero's rendered
[N]superscripts can collide with plain[N]markers and corrupt the field codes.
C. Cascade rejection re-render (find-journal hand-off)
User got rejected from journal A and /find-journal recommended journal B.
- Confirm the new CSL exists in
citation_styles/(or fetch from https://citationstyles.org/styles and drop in). - Re-run
render_pandoc.sh -j <new-csl>against the samemanuscript.md+refs.bib. - Re-run
check_xref.py --strict. - Re-run
/verify-refsif any new references were added during the inter-journal revision.
D. Cross-reference QC only
User shipped a manuscript and a reviewer flagged a Table/Figure mismatch.
- Run
check_xref.py --stricton the currentmanuscript.md+.docx. - Inspect
qc/xref_audit.json. Body caption is the SSOT — fixmanuscript.mdand rebuild, never patch the .docx by hand. - See
references/check_xref_symptoms.mdfor theMISSING_BODY/MISSING_DOCX/MISMATCHtriage table. - For journals that accept figures and tables as separate attachment files
(the default in European Radiology, Radiology, AJR, JVIR, KJR, and most
medical journals), pass
--allow-separate-attachments.MISSING_DOCXrows are then recorded as WARN rather than FAIL;MISSING_BODYandMISMATCHremain P0 because they indicate SSOT drift, not attachment policy.
D'. v_(N+1) docx regeneration check (build-time companion)
When building v_(N+1) from a frozen v_N, the v_(N+1) docx MUST differ
from v_N docx by content — a byte-identical copy is a silent seed-copy
that will revert markdown edits at peer review. check_xref.py carries
two flags for the build-time companion to the submission-time gate
in scripts/verify_package_integrity.py --assert-vN-docx-changed:
python3 "${CLAUDE_SKILL_DIR}/scripts/check_xref.py" \
--md manuscript_v2.md \
--docx manuscript_v2.docx \
--vN-docx-md5 manuscript_v1.docx \
--vN-md manuscript_v1.md \
--strict
--vN-docx-md5alone: MD5 identity check. Identical bytes = FAIL.--vN-docx-md5 + --vN-md: additionally extracts the markdown-only diff between v_N and v_(N+1) and verifies each ≥40-char diff line appears verbatim (whitespace-normalized, case-insensitive) in the new docx body XML. Missing diff lines = body did not pick up the markdown edits.
Output records the result under vN_docx_check in qc/xref_audit.json.
Either failure mode causes a non-zero exit even without --strict.
E. Master pre-submission gate (recommended end-to-end chain)
The single entry point that combines workflows A and D plus /verify-refs
into one aborting chain. Use this immediately before submission or before
circulating a v_N package to senior co-authors.
bash "${CLAUDE_SKILL_DIR}/scripts/pre_submission_gate.sh" \
--md manuscript/manuscript.md \
--bib manuscript/_src/refs.bib \
--docx submission/<journal>/manuscript.docx \
--allow-separate-attachments # omit if the journal accepts inline figures/tables
Stage order (first failure aborts):
check_citation_keys.py manuscript.md refs.bib— UNDEFINED / UNUSED keysverify_refs.py refs.bib --strict— PubMed / CrossRef per-entry verificationrender_pandoc.sh -j <csl> -i ... -b ... -o ...— invoked only when--docxis omittedcheck_xref.py --md ... --docx ... --strict [--allow-separate-attachments]
On success the chain writes qc/pre_submission_gate.json (plus the
per-stage artifacts qc/reference_audit.json and qc/xref_audit.json)
with submission_safe: true. On any failure the JSON records the failing
stage and exit code, and the script exits non-zero — do not submit until
the failing stage passes.
Critical: the gate does not reimplement any check. It calls the existing scripts as subprocesses. If you find yourself wanting to add a check, add it to the underlying script (the gate then picks it up automatically).
F. BibTeX author-format corruption (rendered-name check)
Entries written as author = {Surname AB and Surname2 CD} (family + initials, no comma) make BibTeX treat the last token as the family name, rendering "AB S, CD S2". Always store author = {Family, Full Given}. Concatenated initials even with a comma (Family, AB) still collapse to a single initial under CSL initialize-with, so use the full forename from PubMed efetch.
/verify-refs compares bib content against PubMed but does not see the rendered output; grep the rendered docx and the bib separately:
unzip -p out.docx word/document.xml | sed 's/<[^>]*>//g' | grep -oE "[A-Z]{2} [A-Z], [A-Z]{2} [A-Z]" # corruption signature in output
grep -nE 'author\s*=\s*\{[A-Z][a-z]+ [A-Z]{1,3}( |\})' refs.bib # no-comma source entries
Quality Gates
This skill defines three submission gates and one user approval gate:
- Gate 1 (citekey integrity):
check_citation_keys.pyexits non-zero on UNDEFINED keys. The pipeline halts; the user reviews and fixes. - Gate 2 (cross-reference integrity):
check_xref.py --strictexits 1 on anyMISSING_DOCX/MISSING_BODY/MISMATCHrow. The user reviewsqc/xref_audit.jsonand resolves before proceeding. - Gate 3 (audit hand-off): before sign-off, the user must run
/verify-refsand confirmsubmission_safe: trueinqc/reference_audit.json. This skill never marks the bibliography audited on its own. - User approval gate (CWYW first build): the user must perform Word →
Zotero → Add/Edit Bibliography manually after the first
inject_zotero_cwyw.pybuild. The skill cannot automate this and warns on stderr that it is required.
Provenance
scripts/_vendor_citation_writer.py is vendored from
alisoroushmd/zotero-mcp @ ed5dfb71, MIT licensed. See
NOTICE.md and LICENSE.zotero-mcp.
Related
~/.claude/rules/manuscript-references.md— global rule (decision tree this skill implements)~/.claude/rules/agent-skill-routing.md— skill router (this skill is the reference-handling row)~/.claude/rules/zotero-workflow.md— BBT auto-export, MCP setup/verify-refs— read-only audit (PubMed / CrossRef + first-author cross-check)/lit-sync— Zotero ↔ Obsidian sync,refs.bibprovider/write-paperPhase 7.6 — calls this skill (one-line delegation)/revise,/sync-submission,/find-journal— call this skill on rebuild / re-render / cascade
Known Limitations
- First-build empty BIBL field (CWYW):
inject_zotero_cwyw.pywrites a stubADDIN ZOTERO_BIBLfield; Word's Zotero Refresh treats an empty stub as user-customized and refuses to populate it. User must run Add/Edit Bibliography once. Subsequent Refresh works as expected. Validated on Word for Mac, an active meta-analysis project. - Webpage / non-journal item types: handled by the patched
zotero_to_csl_jsonthat fetches Zotero's native CSL-JSON; do not bypass this patch. - Surgical post-build regex patches are unsafe — see Workflow B step 5.
- Local Zotero required for CWYW — port 23119 must be reachable; no
web-API fallback yet (would need
ZOTERO_API_KEY). On failure the script aborts with non-zero exit so partial builds never ship.
What ships with it: 52 files
327.2 KB alongside SKILL.md, 22 of them executable
citation_styles/
- american-journal-of-roentgenology.csl6.8 KB
- american-medical-association.csl11.3 KB
- cardiovascular-and-interventional-radiology.csl1.3 KB
- european-radiology.csl1.2 KB
- journal-of-cachexia-sarcopenia-and-muscle.csl5.3 KB
- journal-of-korean-medical-science.csl891 B
- journal-of-korean-medical-science-strict.csl17.7 KB
- korean-journal-of-radiology.csl5.7 KB
- liver-international.csl17.8 KB
- nature.cslruns6.3 KB
- nlm-citation-sequence.csl17.7 KB
- radiology.csl7.1 KB
- README.md4.3 KB
- springer-basic-brackets.csl8.1 KB
- springer-vancouver-brackets.csl9.0 KB
- vancouver.csl17.7 KB
- vancouver-superscript.csl17.9 KB
references/
- check_xref_symptoms.md4.4 KB
- REFERENCE_STYLE_SPECS.md3.6 KB
scripts/
- check_bib_title_markup.pyruns6.6 KB
- check_citation_keys_challenge/fixture/manuscript.md127 B
- check_citation_keys_challenge/fixture/refs_clean.bib210 B
- check_citation_keys_challenge/fixture/refs_undefined.bib103 B
- check_citation_keys_challenge/verify.shruns1.8 KB
- check_citation_keys.pyruns6.4 KB
- check_csl_render.pyruns7.6 KB
- check_reference_duplication.pyruns10.0 KB
- check_xref.pyruns30.4 KB
- fill_journal_abbrev.pyruns5.2 KB
- inject_zotero_cwyw.pyruns5.2 KB
- md_marker_convert.pyruns6.6 KB
- pre_submission_gate.shruns9.1 KB
- render_pandoc.shruns4.4 KB
- _vendor_citation_writer.pyruns18.9 KB
tests/
- LICENSE.zotero-mcp1.0 KB
- NOTICE.md1.6 KB
- skill.yml3.0 KB
12 more files not listed here. See all 52 in the repository.