manage-refs
Use when references must be written, rendered or converted. Checks [@key] citation keys, renders the reference list with a journal CSL via pandoc, converts [N] markers, injects Zotero Word field codes and runs manuscript-DOCX cross-reference QC. Read-only auditing is /verify-refs.
How do I install this agent skill?
npx skills add https://github.com/aperivue/medsci-skills --skill manage-refsIs this agent skill safe to install?
- Gen Agent Trust Hubpass
The manage-refs skill is a comprehensive toolkit for automating scientific manuscript reference workflows, including citekey validation, formatting via pandoc, and Zotero integration. It performs external data fetching from the PubMed API and interacts with a local Zotero instance to retrieve bibliographic metadata. Analysis identified standard data-ingestion surfaces and tool-chaining operations (pandoc execution), all of which are consistent with the skill's primary purpose. No malicious patterns such as credential exfiltration, prompt injection, or obfuscation were detected.
- Socketpass
No alerts
- Snykpass
Risk: LOW · No issues
What does this agent skill do?
Manage-Refs Skill
Pick the tool from the Decision Tree; do not invent a parallel pipeline. References are never
hand-typed: the list is always rendered by pandoc citeproc + journal CSL or by the Zotero Word
plugin (CWYW). This skill writes (renders, injects, converts); bibliographic correctness against
PubMed/CrossRef stays in /verify-refs — 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; 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] | 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) | DUP_REF_HEADING / REF_NUMBER_RESTART / REF_SIGNATURE_DUP (Major). Catches a hand-typed ## References list plus the 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 titles carry <scp>WHO</scp> / <i>IDH</i>; BBT escapes them ({$<$}scp{$>$}) or strips them without restoring the space (andTERTPromoter). 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; reports UNVERIFIED rows as not clean, without blocking) | Best-effort (skips with a warning if /verify-refs is not alongside); opt out with -S. The master gate passes -S because it runs verify_refs in its stage 3 |
| 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:
python3 "${CLAUDE_SKILL_DIR}/scripts/check_citation_keys.py" manuscript.md refs.bib— exits non-zero on UNDEFINED keys. Fix and re-run. A[@NEW:topic_slug]drafting placeholder from/write-paperis reported as UNDEFINED too: resolve each by adding the citation to Zotero (/lit-syncthen refreshesrefs.bib) and replacing the placeholder with the real[@bibkey]. Never let a[@NEW:...]reach a rendered DOCX. - Render:
For the current inventory and what each style renders, read"${CLAUDE_SKILL_DIR}/scripts/render_pandoc.sh" \ -j european-radiology \ -i manuscript.md \ -b refs.bib \ -o manuscript_final.docxcitation_styles/README.md— that table is the registry.render_pandoc.shalso lists what is on disk when-jnames a style it cannot find, so ask the script rather than trusting a list written here. Two standing fallbacks: useradiologyfor RYAI andvancouverfor JVIR (neither has a dedicated CSL). A cited key that is not in the.bibmakes the render exit 5 and name the key: pandoc prints it as(key?)in the output and does not fail on its own. - 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:
The conversion never guesses a Zotero key for a number: unmapped markers stay aspython3 "${CLAUDE_SKILL_DIR}/scripts/md_marker_convert.py" \ --input manuscript.md --output manuscript_keys.md \ --map ref_map.json --to-keys[N], are reported on stderr, and make the script exit 1 (the partial output is still written; pass--allow-partialto accept it). Ranges such as[1-3]are expanded. Any bracketed number counts as a marker, so a year in brackets ([2019-2021]) is reported UNMAPPED too. Optionally stage with--active-ns 1,2,3,4,19for a sample build first (a 5-reference sample limits the Word Refresh blast radius while 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 item metadata live from the local Zotero connector (port 23119), so Zotero must be running locally; there is no web-API fallback. Any HTTP failure aborts with a non-zero exit, so a partial bibliography never reaches the user. Never invent Zotero metadata.python3 "${CLAUDE_SKILL_DIR}/scripts/inject_zotero_cwyw.py" \ --input manuscript_keys.docx --output manuscript_cwyw.docx \ --user-id <zotero-user-id> --keys-from keys.txt - First-build instruction (REQUIRED): the script writes an empty
ADDIN ZOTERO_BIBLstub, which Word's Zotero Refresh treats as user-customized and refuses to populate. 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 (European Radiology, Radiology, AJR, JVIR, KJR, and most medical journals), pass
--allow-separate-attachments. It downgrades two rows, reported apart because their evidence differs:MISSING_DOCX— a--docxwas supplied and proved the float is not in the rendered main document. That is what a separate attachment looks like.MISSING_BODYwith no--docxsupplied — nothing was checked. Excused on your word, printed asEXCUSED WITHOUT EVIDENCE, and counted insummary.downgraded_unchecked.
MISMATCHstays P0. So doesMISSING_BODYwhen the float is in the rendered DOCX — that is SSOT drift, which no attachment policy excuses.Run once with
--docxbefore submitting. The flag is a declaration, not a verification; supplying the DOCX is what converts an excuse into evidence.
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 /sync-submission's 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 keyscheck_bib_title_markup.py --strict— publisher markup / tag-strip fusion in.bibtitlesverify_refs.py refs.bib --strict— PubMed / CrossRef per-entry verificationrender_pandoc.sh -S -j <csl> -i ... -b ... -o ...— invoked only when--docxis omitted (--journal <csl>, defaultvancouver)check_xref.py --md ... --docx ... --strict [--allow-separate-attachments]
On success the chain writes qc/pre_submission_gate.json (plus the
per-stage artifacts qc/bib_title_markup.json, 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.
The gate does not reimplement any check; it calls the existing scripts as subprocesses. A new check belongs in the underlying script, which the gate then picks up.
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 (a float the markdown defines but the DOCX lacks isMISSING_DOCXeven when no in-text citation of it was recognised; a legend inside an HTML comment or fenced code is not rendered, so it staysUNCITED). With--docxand nopython-docxit exits 2: the DOCX audit did not run. The user reviewsqc/xref_audit.jsonand resolves before proceeding. Under--allow-separate-attachments, checksummary.downgraded_uncheckedas well assubmission_safe: a non-zero count means rows passed without being checked. - 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.
Known Limitations
- Webpage / non-journal item types: handled by the patched
zotero_to_csl_jsonthat fetches Zotero's native CSL-JSON; do not bypass this patch. - "Table 1 and 2" (singular kind word, number list):
check_xref.pyreads onlyTable 1from it; parsing a bare number after "and" in prose would read "Table 1 and 2 patients" as a citation. A float defined in the markdown but absent from the DOCX is still blocked asMISSING_DOCX; one that is neither defined nor rendered is not seen. Write "Tables 1 and 2" or "Table 1 and Table 2".
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/aperivue/medsci-skills/manage-refs">View manage-refs on skillZs</a>