manage-refs — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited manage-refs (Agent Skill) and scored it 91/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 1 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
The text {match} is the classic direct prompt-injection phrasing. Placed in a skill body that the agent reads as trusted instructions, it tries to make the agent abandon its prior rules and follow whatever comes next — a full system-prompt override.
ignore/disregard/forget … previous instructions sentence.Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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.
| 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 |
User provides manuscript.md with [@bibkey] citations + refs.bib.
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-paper is reported as UNDEFINED too: resolve each by adding the citation to Zotero (/lit-sync then refreshes refs.bib) and replacing the placeholder with the real [@bibkey]. Never let a [@NEW:...] reach a rendered DOCX.
"${CLAUDE_SKILL_DIR}/scripts/render_pandoc.sh" \
-j european-radiology \
-i manuscript.md \
-b refs.bib \
-o manuscript_final.docxFor the current inventory and what each style renders, read citation_styles/README.md — that table is the registry. render_pandoc.sh also lists what is on disk when -j names a style it cannot find, so ask the script rather than trusting a list written here. Two standing fallbacks: use radiology for RYAI and vancouver for JVIR (neither has a dedicated CSL). A cited key that is not in the .bib makes the render exit 5 and name the key: pandoc prints it as (key?) in the output and does not fail on its own.
python3 "${CLAUDE_SKILL_DIR}/scripts/check_xref.py" \
--md manuscript.md --docx manuscript_final.docx \
--out qc/xref_audit.json --strictTreat submission_safe: false as a halt. Route fixes by symptom — see the table in references/check_xref_symptoms.md.
/verify-refs for the PubMed/CrossRef auditbefore sign-off.
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.
python3 "${CLAUDE_SKILL_DIR}/scripts/md_marker_convert.py" \
--input manuscript.md --output manuscript_keys.md \
--map ref_map.json --to-keysThe conversion never guesses a Zotero key for a number: unmapped markers stay as [N], are reported on stderr, and make the script exit 1 (the partial output is still written; pass --allow-partial to 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,19 for a sample build first (a 5-reference sample limits the Word Refresh blast radius while debugging).
[@key] markers, OR pre-build a .docx some other way that still contains plain [@key] text.
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.txtThe 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.
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.
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.
User got rejected from journal A and /find-journal recommended journal B.
citation_styles/ (or fetch fromhttps://citationstyles.org/styles and drop in).
render_pandoc.sh -j <new-csl> against the same manuscript.md +refs.bib.
check_xref.py --strict./verify-refs if any new references were added during theinter-journal revision.
User shipped a manuscript and a reviewer flagged a Table/Figure mismatch.
check_xref.py --strict on the current manuscript.md + .docx.qc/xref_audit.json. Body caption is the SSOT — fix manuscript.mdand rebuild, never patch the .docx by hand.
references/check_xref_symptoms.md for theMISSING_BODY / MISSING_DOCX / MISMATCH triage table.
(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 --docx was supplied and proved the float is not inthe rendered main document. That is what a separate attachment looks like.
MISSING_BODY with no `--docx` supplied — nothing was checked. Excused on your word,printed as EXCUSED WITHOUT EVIDENCE, and counted in summary.downgraded_unchecked.
MISMATCH stays P0. So does MISSING_BODY when the float is in the rendered DOCX — that is SSOT drift, which no attachment policy excuses.
Run once with `--docx` before submitting. The flag is a declaration, not a verification; supplying the DOCX is what converts an excuse into evidence.
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-md5 alone: MD5 identity check. Identical bytes = FAIL.--vN-docx-md5 + --vN-md: additionally extracts the markdown-only diffbetween 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.
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/tablesStage 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 .bib titlesverify_refs.py refs.bib --strict — PubMed / CrossRef per-entry verificationrender_pandoc.sh -S -j <csl> -i ... -b ... -o ... — invoked only when --docx is omitted(--journal <csl>, default vancouver)
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.
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 entriesThis skill defines three submission gates and one user approval gate:
check_citation_keys.py exits non-zero onUNDEFINED keys. The pipeline halts; the user reviews and fixes.
check_xref.py --strict exits 1 onany MISSING_DOCX / MISSING_BODY / MISMATCH row (a float the markdown defines but the DOCX lacks is MISSING_DOCX even when no in-text citation of it was recognised; a legend inside an HTML comment or fenced code is not rendered, so it stays UNCITED). With --docx and no python-docx it exits 2: the DOCX audit did not run. The user reviews qc/xref_audit.json and resolves before proceeding. Under --allow-separate-attachments, check summary.downgraded_unchecked as well as submission_safe: a non-zero count means rows passed without being checked.
/verify-refs and confirm submission_safe: true in qc/reference_audit.json. This skill never marks the bibliography audited on its own.
Zotero → Add/Edit Bibliography manually after the first inject_zotero_cwyw.py build. The skill cannot automate this and warns on stderr that it is required.
scripts/_vendor_citation_writer.py is vendored from alisoroushmd/zotero-mcp @ ed5dfb71, MIT licensed. See NOTICE.md and LICENSE.zotero-mcp.
zotero_to_csl_json that fetches Zotero's native CSL-JSON; do not bypass this patch.
check_xref.py reads onlyTable 1 from 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 as MISSING_DOCX; one that is neither defined nor rendered is not seen. Write "Tables 1 and 2" or "Table 1 and Table 2".
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.