API · phases 0–11
The programmatic surface
Slash commands are markdown prompts. The deterministic work happens in stdlib-only Python scripts, which the prompts call with explicit flags. This page covers those scripts, the modules they share, the record validators, and the JSON contracts for the two subagents.
Architecture
Each commands/ref-<name>.md file is a prompt that Claude Code runs as /ref:<name>. The prompt parses $ARGUMENTS, reads library_root from ~/.config/ref-manager/config.json (and stops, pointing at /ref:init, if the file is missing), then does some mix of three things:
YOU TYPE
/ref:add 31452104
commands/ref-add.md
markdown prompt · parses $ARGUMENTS
prints each script call before running it
~/.config/ref-manager/config.json
library_root → passed as --repo <library_root>
missing → fail loudly, point at /ref:init
PUBMED MCP TOOLS
…PubMed__get_article_metadata
…PubMed__get_full_text_article
…PubMed__search_articles
…PubMed__find_related_articles
prefix mcp__claude_ai_ · network lives here
PYTHON SCRIPTS
python3 "${CLAUDE_PLUGIN_ROOT}/
skills/ref-manager/scripts/<s>.py"
--repo · --input-file / --results-file
validate → lock → atomic commit
stdout JSON or lines · exit 1 on error
SUBAGENTS (AGENT TOOL)
ref-extractor ×N
one per PMID, in parallel
ref-synthesizer ×1
one per question · return JSON only
MCP results and agent JSON reach scripts as temp files
<LIBRARY>/ committed files
only scripts write here
papers/ projects/ graph/ studies/ …
catalog.sqlite · okf/
rebuildable projections
catalog.py rebuild · okf_emit.py
NOT MCP
WebFetch / firecrawl skill: publisher
HTML and OA PDFs for /ref:fetch
Scripts never call PubMed or run a subagent. The only HTTP request a script makes itself is fetch.py's Unpaywall lookup; the converters shell out to local tools (pandoc, trafilatura, anydoc). Commands write MCP and agent output to temp files and pass the paths in with flags such as --metadata-file, --input-file, --results-file, --answer-file or --checks-file.
MCP tools the command files reference
Also named in command files: convert_article_ids (PMCID lookup in /ref:audit --citations); the okf plugin's search_concepts, read_concept and get_neighbors as readers of the generated okf/ (/ref:weave); and the non-MCP WebFetch tool and firecrawl skill in /ref:fetch.
Subagent fan-out
ref-extractor : /ref:extract launches one Agent-tool call per PMID, all in one message so they run concurrently, then passes the collected JSON to extract.py --input-file.
ref-synthesizer : one invocation per question in /ref:ask and /ref:brief, and over the candidate list in /ref:summarize. The answer is checked with validate_citations.py (ask, brief).
Config file
Location: ~/.config/ref-manager/config.json (init_repo.CONFIG_PATH). init_repo.py writes it atomically and validates it with validate_config. On a fresh init it contains exactly:
{
"library_root": "/private/tmp/…/scratchpad/concepts-api/lib"
}
Key Required Written by Read by
library_root yes init_repo.pystatus.py; every command prompt resolves --repo from it
unpaywall_email no nothing; add it by hand fetch.py: enables the Unpaywall lookup when a DOI is known
papers_export_dir no nothing; add it by hand export_papers.py: default destination when --to is absent
Heads-up. commands/ref-fetch.md describes unpaywall_email as recorded at /ref:init, but init_repo.py accepts only path and --force and writes only library_root. Re-running init --force rewrites the file with library_root alone, so any keys you added by hand are lost.
Script CLI reference
Every script lives in skills/ref-manager/scripts/, is a PEP 723 single file (requires-python >= 3.11, no dependencies), and is run as python3 "${CLAUDE_PLUGIN_ROOT}/skills/ref-manager/scripts/<script>.py". The usage text below is the real --help output. Subcommands are argparse positional choices, not subparsers, so each script has one help screen.
Library. All scripts except init_repo.py, status.py and validate_citations.py take --repo <library_root>. A path that is not a directory gives error: no library at … on stderr and exit 1.
Errors. Validation failures (SchemaError, SlugError, SelectorError) print error: … to stderr and exit 1.
Batches. add, fetch, attach and extract process each PMID under its own lock and report one line per PMID, so a single failure never blocks the rest of the batch.
Selector scripts share the flags [pmids ...] --project --question --screened --read --queue --query --run --search --from-file --study --concept --tier --exclude (semantics ).
Library & index
init_repo.py
Create the library directories and log.md, and write config.json. Refuses when a library is already configured unless --force is given.
Called by /ref:init · stdout: plain text
init_repo.py --helpusage: init_repo.py [-h] [--force] path
positional arguments:
path
options:
-h, --help show this help message and exit
--force
status.py
Report the configured library, the indexed paper count and the project count. It has no argparse CLI and takes no arguments; it reads config.json itself and exits 1 when no library is configured.
Called by /ref:status · stdout: plain text
python3 skills/ref-manager/scripts/status.py
catalog.py
init or rebuild index/catalog.sqlite (tables papers, passages_fts, claims, claims_fts, concepts, relations, catalog_meta) from committed records.
Called by /ref:index · stdout: plain text
catalog.py --helpusage: catalog.py [-h] --repo REPO {rebuild,init}
positional arguments:
{rebuild,init}
options:
-h, --help show this help message and exit
--repo REPO
Acquire
add.py
RESOLVE and DEDUP: read a metadata envelope (JSON array) and write meta.json, authorship.json, funding.json and raw/<sha256>/response.json. Never calls the network.
Called by /ref:add · stdout: one result line per PMID (plus indented diagnostic: lines)
add.py --helpusage: add.py [-h] --repo REPO --metadata-file METADATA_FILE {add}
positional arguments:
{add}
options:
-h, --help show this help message and exit
--repo REPO
--metadata-file METADATA_FILE
fetch.py
Full-text acquisition from a per-PMID envelope (jats_xml, publisher_html, plain_text); makes one direct Unpaywall REST call when unpaywall_email is configured.
Called by /ref:fetch · stdout: one result line per PMID (plus indented diagnostic: lines)
fetch.py --helpusage: fetch.py [-h] --repo REPO --input-file INPUT_FILE
options:
-h, --help show this help message and exit
--repo REPO
--input-file INPUT_FILE
attach.py
Attach local PDFs as <pmid> <path> pairs after a pdftotext identity check (DOI or title); same hash again is a no-op; conversion goes through convert_pdf.
Called by /ref:attach · stdout: one result line per PMID (plus indented diagnostic: lines)
attach.py --helpusage: attach.py [-h] --repo REPO [--force] pairs [pairs ...]
positional arguments:
pairs <pmid> <path> [<pmid> <path> ...]
options:
-h, --help show this help message and exit
--repo REPO
--force attach despite a failed identity check
vision.py
request checks the per-figure cache (figure hash + model + prompt); store saves the description the calling agent wrote.
Called by /ref:describe-figure · stdout: JSON document
vision.py --helpusage: vision.py [-h] --repo REPO --pmid PMID [--version VERSION]
--figure-id FIGURE_ID --model MODEL --prompt PROMPT
[--description DESCRIPTION]
{request,store}
positional arguments:
{request,store}
options:
-h, --help show this help message and exit
--repo REPO
--pmid PMID
--version VERSION
--figure-id FIGURE_ID
--model MODEL
--prompt PROMPT
--description DESCRIPTION
Find
search.py
FTS search over evidence, personal notes, or all, labelled separately.
Called by /ref:search · stdout: JSON document
search.py --helpusage: search.py [-h] --repo REPO [--scope {evidence,notes,all}] --q Q
options:
-h, --help show this help message and exit
--repo REPO
--scope {evidence,notes,all}
--q Q
ask_retrieve.py
Retrieval half of /ref:ask: resolve the selector, then collect claim and passage candidates within the candidate and token budgets.
Called by /ref:ask · /ref:brief · /ref:check-citations · stdout: JSON document
ask_retrieve.py --helpusage: ask_retrieve.py [-h] --repo REPO --q Q [--expand [EXPAND ...]]
[--candidate-budget CANDIDATE_BUDGET]
[--token-budget TOKEN_BUDGET] [--project PROJECT]
[--question QUESTION]
[--screened {included,excluded,pending}] [--read]
[--queue QUEUE] [--query QUERY] [--run RUN]
[--search SEARCH] [--from-file FROM_FILE]
[--study STUDY] [--concept CONCEPT]
[--tier {abstract,full,any}] [--exclude [EXCLUDE ...]]
[pmids ...]
positional arguments:
pmids explicit PMIDs (base case)
options:
-h, --help show this help message and exit
--repo REPO
--q Q the research question
--expand [EXPAND ...]
query-expansion terms the calling agent supplied (§5)
--candidate-budget CANDIDATE_BUDGET
--token-budget TOKEN_BUDGET
--project PROJECT
--question QUESTION
--screened {included,excluded,pending}
--read
--queue QUEUE
--query QUERY
--run RUN
--search SEARCH
--from-file FROM_FILE
--study STUDY
--concept CONCEPT
--tier {abstract,full,any}
--exclude [EXCLUDE ...]
validate_citations.py
Check that every [^pmid] in an answer resolves to a supplied candidate.
Called by /ref:ask · /ref:brief · stdout: JSON document
validate_citations.py --helpusage: validate_citations.py [-h] --answer-file ANSWER_FILE
--candidates-file CANDIDATES_FILE
options:
-h, --help show this help message and exit
--answer-file ANSWER_FILE
--candidates-file CANDIDATES_FILE
ask_retrieve.py's candidates JSON
pubmed_query.py
Saved PubMed queries in queries/<slug>.yaml with immutable run histories.
Called by /ref:search-pubmed · /ref:update-queries · stdout: JSON document
pubmed_query.py --helpusage: pubmed_query.py [-h] --repo REPO [--slug SLUG]
[--query-text QUERY_TEXT] [--source SOURCE]
[--pmids-file PMIDS_FILE] [--create]
{new-run,rerun,show,list}
positional arguments:
{new-run,rerun,show,list}
options:
-h, --help show this help message and exit
--repo REPO
--slug SLUG
--query-text QUERY_TEXT
--source SOURCE
--pmids-file PMIDS_FILE
--create
related.py
Backward (reference list of committed full text) and forward (related PMIDs supplied by the caller) snowballing, persisted to related.json.
Called by /ref:related · stdout: JSON document
related.py --helpusage: related.py [-h] --repo REPO --pmid PMID [--link-type LINK_TYPE]
[--results-file RESULTS_FILE] [--max-results MAX_RESULTS]
{backward,forward,show}
positional arguments:
{backward,forward,show}
options:
-h, --help show this help message and exit
--repo REPO
--pmid PMID
--link-type LINK_TYPE
--results-file RESULTS_FILE
JSON array of forward-related PMIDs
--max-results MAX_RESULTS
Organize
project.py
Projects, per-project question IDs, and membership (relevance, priority, reading status).
Called by /ref:project · stdout: JSON document
project.py --helpusage: project.py [-h] --repo REPO [--slug SLUG] [--scope SCOPE]
[--question-id QUESTION_ID] [--text TEXT] [--pmid PMID]
[--relevance RELEVANCE] [--priority PRIORITY]
[--reading-status READING_STATUS]
{create,add-question,add-paper,show,list}
positional arguments:
{create,add-question,add-paper,show,list}
options:
-h, --help show this help message and exit
--repo REPO
--slug SLUG
--scope SCOPE
--question-id QUESTION_ID
--text TEXT
--pmid PMID
--relevance RELEVANCE
--priority PRIORITY
--reading-status READING_STATUS
queue.py
Set or show reading state, priority and why-saved on a project membership.
Called by /ref:queue · stdout: JSON document
queue.py --helpusage: queue.py [-h] --repo REPO --project PROJECT [--pmid PMID]
[--status STATUS] [--priority PRIORITY] [--why WHY]
{set,show}
positional arguments:
{set,show}
options:
-h, --help show this help message and exit
--repo REPO
--project PROJECT
--pmid PMID
--status STATUS
--priority PRIORITY
--why WHY
screen.py
Append a screening decision to screening.jsonl and mirror it onto the membership.
Called by /ref:screen · stdout: JSON document
screen.py --helpusage: screen.py [-h] --repo REPO --project PROJECT [--pmid PMID]
[--decision DECISION] [--reason REASON] [--run RUN]
{decide,history}
positional arguments:
{decide,history}
options:
-h, --help show this help message and exit
--repo REPO
--project PROJECT
--pmid PMID
--decision DECISION
--reason REASON
--run RUN
note.py
Append to or show papers/<pmid>/notes.md.
Called by /ref:note · stdout: plain text
note.py --helpusage: note.py [-h] --repo REPO --pmid PMID [--text TEXT] {append,show}
positional arguments:
{append,show}
options:
-h, --help show this help message and exit
--repo REPO
--pmid PMID
--text TEXT
verify.py
Correction overlays for claims, grant links, author contributions, person identity and appraisal domains.
Called by /ref:verify · /ref:review · stdout: JSON document
verify.py --helpusage: verify.py [-h] --repo REPO --pmid PMID [--claim-id CLAIM_ID]
[--decision DECISION] [--reviewer REVIEWER]
[--rationale RATIONALE] [--replacement-file REPLACEMENT_FILE]
[--grant GRANT] [--evidence-locator EVIDENCE_LOCATOR]
[--award-number AWARD_NUMBER] [--author-index AUTHOR_INDEX]
[--flag FLAG] [--evidence-statement EVIDENCE_STATEMENT]
[--person PERSON] [--checklist CHECKLIST]
[--domain-key DOMAIN_KEY]
{review-claim,review-grant-link,review-author-contribution,review-person-identity,review-appraisal,show}
positional arguments:
{review-claim,review-grant-link,review-author-contribution,review-person-identity,review-appraisal,show}
options:
-h, --help show this help message and exit
--repo REPO
--pmid PMID
--claim-id CLAIM_ID
--decision DECISION
--reviewer REVIEWER
--rationale RATIONALE
--replacement-file REPLACEMENT_FILE
--grant GRANT
--evidence-locator EVIDENCE_LOCATOR
--award-number AWARD_NUMBER
--author-index AUTHOR_INDEX
--flag FLAG
--evidence-statement EVIDENCE_STATEMENT
--person PERSON
--checklist CHECKLIST
--domain-key DOMAIN_KEY
study.py
Create and list study, dataset and method records.
Called by /ref:study · stdout: JSON document
study.py --helpusage: study.py [-h] --repo REPO [--id ID] [--pmid [PMID ...]]
[--confidence CONFIDENCE] [--evidence EVIDENCE] [--name NAME]
[--notes NOTES] [--context CONTEXT] [--locator LOCATOR]
{create-study,create-dataset,create-method,list-studies,list-datasets,list-methods}
positional arguments:
{create-study,create-dataset,create-method,list-studies,list-datasets,list-methods}
options:
-h, --help show this help message and exit
--repo REPO
--id ID
--pmid [PMID ...]
--confidence CONFIDENCE
--evidence EVIDENCE
--name NAME
--notes NOTES
--context CONTEXT
--locator LOCATOR
Compare & write
compare.py
Evidence matrix over a selector, grouped by recorded study. --edit-* writes to the separate user-edit layer.
Called by /ref:compare · stdout: JSON document
compare.py --helpusage: compare.py [-h] [--project PROJECT] [--question QUESTION]
[--screened {included,excluded,pending}] [--read]
[--queue QUEUE] [--query QUERY] [--run RUN]
[--search SEARCH] [--from-file FROM_FILE] [--study STUDY]
[--concept CONCEPT] [--tier {abstract,full,any}]
[--exclude [EXCLUDE ...]] --repo REPO --batch BATCH
[--refresh] [--edit-pmid EDIT_PMID]
[--edit-column EDIT_COLUMN] [--edit-value EDIT_VALUE]
[pmids ...]
positional arguments:
pmids explicit PMIDs (base case)
options:
-h, --help show this help message and exit
--project PROJECT
--question QUESTION
--screened {included,excluded,pending}
--read
--queue QUEUE
--query QUERY
--run RUN
--search SEARCH
--from-file FROM_FILE
--study STUDY
--concept CONCEPT
--tier {abstract,full,any}
--exclude [EXCLUDE ...]
--repo REPO
--batch BATCH
--refresh
--edit-pmid EDIT_PMID
--edit-column EDIT_COLUMN
--edit-value EDIT_VALUE
methods.py
Methods, protocols and datasets for the selected papers, with locators.
Called by /ref:methods · stdout: JSON document
methods.py --helpusage: methods.py [-h] [--project PROJECT] [--question QUESTION]
[--screened {included,excluded,pending}] [--read]
[--queue QUEUE] [--query QUERY] [--run RUN]
[--search SEARCH] [--from-file FROM_FILE] [--study STUDY]
[--concept CONCEPT] [--tier {abstract,full,any}]
[--exclude [EXCLUDE ...]] --repo REPO
[pmids ...]
positional arguments:
pmids explicit PMIDs (base case)
options:
-h, --help show this help message and exit
--project PROJECT
--question QUESTION
--screened {included,excluded,pending}
--read
--queue QUEUE
--query QUERY
--run RUN
--search SEARCH
--from-file FROM_FILE
--study STUDY
--concept CONCEPT
--tier {abstract,full,any}
--exclude [EXCLUDE ...]
--repo REPO
summarize.py
--dump-candidates prints the synthesizer input; --answer-file persists a summary batch; --show reads one back.
Called by /ref:summarize · stdout: JSON document
summarize.py --helpusage: summarize.py [-h] [--project PROJECT] [--question QUESTION]
[--screened {included,excluded,pending}] [--read]
[--queue QUEUE] [--query QUERY] [--run RUN]
[--search SEARCH] [--from-file FROM_FILE] [--study STUDY]
[--concept CONCEPT] [--tier {abstract,full,any}]
[--exclude [EXCLUDE ...]] --repo REPO [--batch BATCH]
[--refresh] [--answer-file ANSWER_FILE]
[--coverage-note COVERAGE_NOTE]
[--unresolved [UNRESOLVED ...]] [--show]
[--dump-candidates]
[pmids ...]
positional arguments:
pmids explicit PMIDs (base case)
options:
-h, --help show this help message and exit
--project PROJECT
--question QUESTION
--screened {included,excluded,pending}
--read
--queue QUEUE
--query QUERY
--run RUN
--search SEARCH
--from-file FROM_FILE
--study STUDY
--concept CONCEPT
--tier {abstract,full,any}
--exclude [EXCLUDE ...]
--repo REPO
--batch BATCH
--refresh
--answer-file ANSWER_FILE
--coverage-note COVERAGE_NOTE
--unresolved [UNRESOLVED ...]
--show
--dump-candidates resolve the selector and print the candidate evidence
list for the ref-synthesizer subagent, without
requiring an --answer-file (there's no answer yet at
this point -- the calling agent needs candidates
BEFORE it can produce one)
appraise.py
Advanced /ref:review: RoB 2 / Newcastle-Ottawa / AMSTAR-2 drafts per PMID plus GRADE certainty.
Called by /ref:review · stdout: JSON document
appraise.py --helpusage: appraise.py [-h] [--project PROJECT] [--question QUESTION]
[--screened {included,excluded,pending}] [--read]
[--queue QUEUE] [--query QUERY] [--run RUN]
[--search SEARCH] [--from-file FROM_FILE] [--study STUDY]
[--concept CONCEPT] [--tier {abstract,full,any}]
[--exclude [EXCLUDE ...]] --repo REPO --batch BATCH
[--refresh]
[pmids ...]
positional arguments:
pmids explicit PMIDs (base case)
options:
-h, --help show this help message and exit
--project PROJECT
--question QUESTION
--screened {included,excluded,pending}
--read
--queue QUEUE
--query QUERY
--run RUN
--search SEARCH
--from-file FROM_FILE
--study STUDY
--concept CONCEPT
--tier {abstract,full,any}
--exclude [EXCLUDE ...]
--repo REPO
--batch BATCH
--refresh
prisma.py
PRISMA 2020 flow snapshot for a project from saved runs, screening, acquisition and studies.
Called by /ref:review · stdout: JSON document
prisma.py --helpusage: prisma.py [-h] --repo REPO --project PROJECT [--query QUERY]
[--refresh]
options:
-h, --help show this help message and exit
--repo REPO
--project PROJECT
--query QUERY query slug, optionally slug:run_id; repeatable
--refresh
check_citations.py
Persist (check) or read back (show) a paragraph citation check; findings are validated per verdict.
Called by /ref:check-citations · stdout: JSON document
check_citations.py --helpusage: check_citations.py [-h] --repo REPO [--project PROJECT]
[--paragraph-file PARAGRAPH_FILE]
[--findings-file FINDINGS_FILE]
[--candidates-file CANDIDATES_FILE]
[--resolution-file RESOLUTION_FILE] [--export-bib]
[--id ID]
{check,show}
positional arguments:
{check,show}
options:
-h, --help show this help message and exit
--repo REPO
--project PROJECT
--paragraph-file PARAGRAPH_FILE
--findings-file FINDINGS_FILE
--candidates-file CANDIDATES_FILE
ask_retrieve.py's candidates JSON, after
merge_cited_pmid_candidates if the calling command did
that
--resolution-file RESOLUTION_FILE
--export-bib
--id ID check_id, for 'show'
brief.py
Save, edit (a separate user revision layer) or show versioned research briefs.
Called by /ref:brief · stdout: JSON document
brief.py --helpusage: brief.py [-h] --repo REPO --key KEY [--project PROJECT]
[--question QUESTION] [--answer-file ANSWER_FILE]
[--evidence-file EVIDENCE_FILE]
[--resolution-file RESOLUTION_FILE]
[--unresolved [UNRESOLVED ...]] [--refresh]
[--revision-file REVISION_FILE]
{save,edit,show}
positional arguments:
{save,edit,show}
options:
-h, --help show this help message and exit
--repo REPO
--key KEY
--project PROJECT
--question QUESTION
--answer-file ANSWER_FILE
--evidence-file EVIDENCE_FILE
ask_retrieve.py's candidates JSON
--resolution-file RESOLUTION_FILE
--unresolved [UNRESOLVED ...]
--refresh
--revision-file REVISION_FILE
cite.py
Print the stable @citekey for a PMID.
Called by /ref:cite · stdout: plain text
cite.py --helpusage: cite.py [-h] --repo REPO --pmid PMID
options:
-h, --help show this help message and exit
--repo REPO
--pmid PMID
export.py
BibTeX and CSL-JSON export batch over a selector, into exports/<batch>/.
Called by /ref:export · stdout: JSON document
export.py --helpusage: export.py [-h] [--project PROJECT] [--question QUESTION]
[--screened {included,excluded,pending}] [--read]
[--queue QUEUE] [--query QUERY] [--run RUN] [--search SEARCH]
[--from-file FROM_FILE] [--study STUDY] [--concept CONCEPT]
[--tier {abstract,full,any}] [--exclude [EXCLUDE ...]]
--repo REPO --batch BATCH [--refresh]
[pmids ...]
positional arguments:
pmids explicit PMIDs (base case)
options:
-h, --help show this help message and exit
--project PROJECT
--question QUESTION
--screened {included,excluded,pending}
--read
--queue QUEUE
--query QUERY
--run RUN
--search SEARCH
--from-file FROM_FILE
--study STUDY
--concept CONCEPT
--tier {abstract,full,any}
--exclude [EXCLUDE ...]
--repo REPO
--batch BATCH
--refresh
export_papers.py
Papers handoff: references.bib in Papers' dialect, PDF copies, and a manifest. --to falls back to papers_export_dir, then to exports/papers/<batch>/.
Called by /ref:export-papers · stdout: JSON document
export_papers.py --helpusage: export_papers.py [-h] [--project PROJECT] [--question QUESTION]
[--screened {included,excluded,pending}] [--read]
[--queue QUEUE] [--query QUERY] [--run RUN]
[--search SEARCH] [--from-file FROM_FILE]
[--study STUDY] [--concept CONCEPT]
[--tier {abstract,full,any}] [--exclude [EXCLUDE ...]]
--repo REPO [--batch BATCH] [--to TO]
[--layout {papers,flat}] [--pdfs {copy,link,none}]
[--notes] [--notes-force] [--tags TAGS]
[--tags-from TAGS_FROM] [--skip-known] [--force]
[--collection COLLECTION] [--dry-run] [--refresh]
[--papers-db PAPERS_DB]
[pmids ...]
positional arguments:
pmids explicit PMIDs (base case)
options:
-h, --help show this help message and exit
--project PROJECT
--question QUESTION
--screened {included,excluded,pending}
--read
--queue QUEUE
--query QUERY
--run RUN
--search SEARCH
--from-file FROM_FILE
--study STUDY
--concept CONCEPT
--tier {abstract,full,any}
--exclude [EXCLUDE ...]
--repo REPO
--batch BATCH
--to TO
--layout {papers,flat}
--pdfs {copy,link,none}
--notes
--notes-force
--tags TAGS
--tags-from TAGS_FROM
--skip-known
--force
--collection COLLECTION
--dry-run
--refresh
--papers-db PAPERS_DB
path to a Papers.app sqlite db (tests must point this
at a synthetic scratch db, never the real library)
Graph
concept.py
Concept nodes in graph/concepts.jsonl with accumulating aliases and alias provenance.
Called by /ref:concept · /ref:weave · stdout: JSON document
concept.py --helpusage: concept.py [-h] --repo REPO [--id ID] [--name NAME] [--alias ALIAS]
[--source SOURCE]
{create,find,add-alias,show,list}
positional arguments:
{create,find,add-alias,show,list}
options:
-h, --help show this help message and exit
--repo REPO
--id ID
--name NAME
--alias ALIAS
--source SOURCE
relation.py
Typed concept edges: propose/create potential_conflict, create-manual, review, refresh staleness, neighbors.
Called by /ref:weave · /ref:index · stdout: JSON document
relation.py --helpusage: relation.py [-h] --repo REPO [--id ID] [--type TYPE]
[--reviewer REVIEWER] [--rationale RATIONALE]
[--concept CONCEPT] [--claim-a-file CLAIM_A_FILE]
[--claim-b-file CLAIM_B_FILE]
[--subject-concept SUBJECT_CONCEPT]
[--object-concept OBJECT_CONCEPT]
{propose,create,create-manual,review,refresh,show,list,neighbors}
positional arguments:
{propose,create,create-manual,review,refresh,show,list,neighbors}
options:
-h, --help show this help message and exit
--repo REPO
--id ID
--type TYPE
--reviewer REVIEWER
--rationale RATIONALE
--concept CONCEPT
--claim-a-file CLAIM_A_FILE
--claim-b-file CLAIM_B_FILE
--subject-concept SUBJECT_CONCEPT
--object-concept OBJECT_CONCEPT
okf_emit.py
Regenerate okf/ (concepts, papers, people, grants, index.md, log.md) from the structured sources.
Called by /ref:weave · stdout: JSON document
okf_emit.py --helpusage: okf_emit.py [-h] --repo REPO [--now NOW]
options:
-h, --help show this help message and exit
--repo REPO
--now NOW
graph_people.py
Regenerate graph/people_relations.jsonl.
Called by /ref:weave · stdout: JSON document
graph_people.py --helpusage: graph_people.py [-h] --repo REPO
options:
-h, --help show this help message and exit
--repo REPO
gaps.py
Structural gap queries: single-study claims, unresolved conflicts, co-mentioned but ungrouped concepts, population/outcome gaps.
Called by /ref:gaps · stdout: JSON document
gaps.py --helpusage: gaps.py [-h] [--project PROJECT] [--question QUESTION]
[--screened {included,excluded,pending}] [--read]
[--queue QUEUE] [--query QUERY] [--run RUN] [--search SEARCH]
[--from-file FROM_FILE] [--study STUDY] [--concept CONCEPT]
[--tier {abstract,full,any}] [--exclude [EXCLUDE ...]]
--repo REPO [--intervention-concept INTERVENTION_CONCEPT]
[--types [{single_study_fragile,unresolved_conflicts,co_mentioned_ungrouped,population_outcome_gap} ...]]
[pmids ...]
positional arguments:
pmids explicit PMIDs (base case)
options:
-h, --help show this help message and exit
--project PROJECT
--question QUESTION
--screened {included,excluded,pending}
--read
--queue QUEUE
--query QUERY
--run RUN
--search SEARCH
--from-file FROM_FILE
--study STUDY
--concept CONCEPT
--tier {abstract,full,any}
--exclude [EXCLUDE ...]
--repo REPO
--intervention-concept INTERVENTION_CONCEPT
required for the population_outcome_gap query
--types [{single_study_fragile,unresolved_conflicts,co_mentioned_ungrouped,population_outcome_gap} ...]
restrict to specific gap types; default runs all
applicable
hypothesize.py
Swanson ABC: candidates runs a two-hop traversal; finalize attaches the caller's PubMed check results.
Called by /ref:hypothesize · stdout: JSON document
hypothesize.py --helpusage: hypothesize.py [-h] --repo REPO [--concept CONCEPT]
[--checks-file CHECKS_FILE]
{candidates,finalize}
positional arguments:
{candidates,finalize}
options:
-h, --help show this help message and exit
--repo REPO
--concept CONCEPT
--checks-file CHECKS_FILE
finalize: JSON {concept_c_id: {query, retrieved_at,
result_pmids}}
People & reporting
person.py
Researcher profiles plus confirm/reject of an author position.
Called by /ref:person · /ref:discover · stdout: JSON document
person.py --helpusage: person.py [-h] --repo REPO [--slug SLUG] [--name NAME] [--orcid ORCID]
[--pmid PMID] [--author-index AUTHOR_INDEX]
{create,show,list,confirm-publication,reject-publication}
positional arguments:
{create,show,list,confirm-publication,reject-publication}
options:
-h, --help show this help message and exit
--repo REPO
--slug SLUG
--name NAME
--orcid ORCID
--pmid PMID
--author-index AUTHOR_INDEX
publications.py
Role-filtered publication list for a person, or --coauthors collaborator export for a window.
Called by /ref:publications · stdout: JSON document
publications.py --helpusage: publications.py [-h] --repo REPO --person PERSON [--role ROLE]
[--coauthors] [--since SINCE] [--to TO]
options:
-h, --help show this help message and exit
--repo REPO
--person PERSON
--role ROLE
--coauthors
--since SINCE
--to TO
grant.py
Grant records and approved award aliases.
Called by /ref:grant · stdout: JSON document
grant.py --helpusage: grant.py [-h] --repo REPO [--slug SLUG] [--funder FUNDER]
[--award AWARD] [--alias ALIAS] [--title TITLE] [--pi PI]
[--aims AIMS]
{create,add-alias,show,list}
positional arguments:
{create,add-alias,show,list}
options:
-h, --help show this help message and exit
--repo REPO
--slug SLUG
--funder FUNDER
--award AWARD
--alias ALIAS
--title TITLE
--pi PI
--aims AIMS
discover.py
Persist a manual person/grant PubMed discovery run and its candidates.
Called by /ref:discover · stdout: JSON document
discover.py --helpusage: discover.py [-h] --repo REPO [--person PERSON] [--grant GRANT]
--query-text QUERY_TEXT --candidates-file CANDIDATES_FILE
options:
-h, --help show this help message and exit
--repo REPO
--person PERSON
--grant GRANT
--query-text QUERY_TEXT
--candidates-file CANDIDATES_FILE
report.py
Reproducible person report for a date window; --citations adds PMC-indexed counts.
Called by /ref:report · stdout: JSON document
report.py --helpusage: report.py [-h] --repo REPO --person PERSON --from DATE_FROM
--to DATE_TO [--label LABEL] [--citations]
[--stale-days STALE_DAYS]
options:
-h, --help show this help message and exit
--repo REPO
--person PERSON
--from DATE_FROM
--to DATE_TO
--label LABEL
--citations include PMC-indexed citing-article counts (D25), never
total citations
--stale-days STALE_DAYS
Maintain
audit.py
retraction and citations apply caller-supplied check results; propagate lists saved artifacts made stale; show-citations reads observations. No selector means the whole library.
Called by /ref:audit · stdout: JSON document
audit.py --helpusage: audit.py [-h] [--project PROJECT] [--question QUESTION]
[--screened {included,excluded,pending}] [--read]
[--queue QUEUE] [--query QUERY] [--run RUN] [--search SEARCH]
[--from-file FROM_FILE] [--study STUDY] [--concept CONCEPT]
[--tier {abstract,full,any}] [--exclude [EXCLUDE ...]]
--repo REPO [--results-file RESULTS_FILE]
[--stale-days STALE_DAYS]
{retraction,citations,propagate,show-citations} [pmids ...]
positional arguments:
{retraction,citations,propagate,show-citations}
pmids explicit PMIDs (base case)
options:
-h, --help show this help message and exit
--project PROJECT
--question QUESTION
--screened {included,excluded,pending}
--read
--queue QUEUE
--query QUERY
--run RUN
--search SEARCH
--from-file FROM_FILE
--study STUDY
--concept CONCEPT
--tier {abstract,full,any}
--exclude [EXCLUDE ...]
--repo REPO
--results-file RESULTS_FILE
JSON array of per-PMID check results from the calling
agent
--stale-days STALE_DAYS
open_in_papers.py
Open a paper's acquired PDF in Papers.app.
Called by /ref:open · stdout: JSON document
open_in_papers.py --helpusage: open_in_papers.py [-h] --repo REPO pmid
positional arguments:
pmid
options:
-h, --help show this help message and exit
--repo REPO
pull_annotations.py
Upsert one paper's Papers annotations from a read-only snapshot into annotations.json.
Called by /ref:pull-annotations · stdout: JSON document
pull_annotations.py --helpusage: pull_annotations.py [-h] --repo REPO --pmid PMID
[--papers-db PAPERS_DB]
options:
-h, --help show this help message and exit
--repo REPO
--pmid PMID
--papers-db PAPERS_DB
Library modules
These modules are imported by scripts and have no CLI of their own. Only public names are listed.
lib_atomic.py: atomic writes and locking
atomic_write_bytes(path, data) Temp file in the same directory, fsync, os.replace.
atomic_write_text(path, text) UTF-8 wrapper over atomic_write_bytes.
atomic_write_json(path, obj) Two-space indent, sorted keys, trailing newline.
library_lock(library_root) Exclusive flock on index/.library.lock: citekeys, slug registries, graph commits.
pmid_lock(library_root, pmid) Exclusive flock on index/.locks/<pmid>.lock.
commit_version(paper_dir, version_id, write_fn) Fill versions/.staging-<id>, rename it into place, then point current.json at it. Returns the final path.
lib_ids.py: identifier contract
SlugError ValueError subclass.
SLUG_RE ^[a-z0-9]+(-[a-z0-9]+)*$; length 1–64 is checked separately.
validate_slug(slug) Raises SlugError on an illegal slug.
slug_exists(library_root, kind, slug) Path check for project/query/person/lab/grant; alias-registry check for study/dataset/method/concept.
allocate_slug(library_root, kind, slug) Refuses a collision, naming the conflicting record; registers registry kinds.
rename_slug(library_root, kind, old, new) Renames under the library lock and records old → new as an alias.
resolve_slug(library_root, kind, slug) Follows alias chains to the live slug.
check_question_id(project_yaml, question_id) Question slugs must be unique within their project.
make_citekey_base(author_lastname, year, first_title_word) Returns authorYearFirstword, lowercased and alphanumeric only.
allocate_citekey(library_root, author_lastname, year, first_title_word) Adds a -2, -3 … suffix under the lock and records it in index/citekeys.json.
gen_opaque_id(prefix="") Prefix + secrets.token_hex(6).
lib_schema.py: record validators
SchemaError(ValueError); validate_config, validate_meta, validate_project, validate_screening_record, validate_claim, validate_correction, validate_study, validate_person, validate_grant, validate_citation_check_finding, validate_concept, validate_relation, validate_retraction_status, validate_citation_observation. Constants: CLAIM_NORMALIZED_FIELDS, STUDY_TYPES, CITATION_CHECK_VERDICTS, RELATION_TYPES. Field-level detail is in Record schemas .
lib_selector.py: selector grammar
SelectorError · NotAvailableError An empty or invalid selection; --concept raises the latter.
resolve(library_root, *, pmids, project, question, screened, read, queue_state, query, run, search, from_file, study, concept, tier="any", exclude) Returns {pmids, selector_expression, report}.
selector_expression(**kwargs) Canonical --flag value string, or <all>.
add_selector_args(ap) Adds the shared selector flags to an ArgumentParser.
resolve_from_args(library_root, args) resolve() driven by parsed args.
lib_cite.py: CSL-JSON and BibTeX
to_csl CSL-JSON generation from library metadata.
csl_to_bibtex_entry BibTeX rendering from CSL-JSON.
build_exports Returns CSL entries and BibTeX text for a PMID list; used by export.py and check_citations.py.
lib_verify_link.py: shared by extract.py and verify.py
corrections_path · registry_path Paths to corrections.json and claim_registry.json.
load_corrections · load_registry Read the files, with empty defaults.
revalidate_corrections(library_root, pmid) Flips claim corrections whose target is superseded to pending_review.
apply_reject_to_registry(library_root, pmid, claim_id) Sets excluded_from_synthesis; the claim is kept.
lib_status_check.py: retraction change detection for saved artifacts
current_status(library_root, pmid) A PMID's current retraction status string.
diff_status(library_root, prior_status_by_pmid) Returns a change record for each PMID whose live status differs from the one recorded when the artifact was generated.
Helper modules without a CLI
convert.py convert_jats, convert_html, convert_plain_text, convert_pdf: one output contract (source.md, figures/, figures.json, status, diagnostics). Used by fetch.py and attach.py.
funding_extract.py extract_funding_observations(xml_text) returns (observations, state) from JATS funding markup.
papers_snapshot.py SnapshotUnavailable, take_snapshot (SQLite backup of the live DB), read_items, try_read_items (returns (items, None) or (None, reason)), find_duplicate.
Record schemas
These are the validators in lib_schema.py. They check only the fields listed; writers add more fields than the validators require.
config: validate_config
Field Type Rule
library_root str required
papers_export_dir · unpaywall_email — optional (comment only)
papers/<pmid>/meta.json: validate_meta
Field Type Rule
pmid · citekey · title · status · checked_at str required (add.py writes status: "active")
extraction_tier str abstract | full | unavailable
doi · pmcid · journal · year · abstract_available — not validated; written by add.py
retraction_status object see validate_retraction_status; written by extract.py and audit.py
full_text · oa_location — not validated; written by fetch.py and attach.py
meta.retraction_status: validate_retraction_status
Field Type Rule
status str retracted | erratum | none | unknown
source · checked_at str | null required keys
projects/<slug>/project.yaml: validate_project
Field Type Rule
slug str required
questions list each item needs id (str); IDs unique within the project
screening.jsonl line: validate_screening_record
Field Type Rule
pmid · reason · timestamp str required
decision str included | excluded | pending
search_run str optional
versions/<id>/claims.json entry: validate_claim
Field Type Rule
claim_id · pmid · version_id · evidence_tier · source_hash · locator · evidence_span str required
study_type str rct | cohort | case_control | meta_analysis | molecular | imaging | review | mixed | unknown
CLAIM_NORMALIZED_FIELDS any every key present ("unknown" allowed): population, intervention, comparator, outcome, timepoint, direction, effect_value, effect_measure, uncertainty_interval, study_design, cohort_identity, adjustment_context
corrections.json entry: validate_correction
Field Type Rule
correction_id · target_id · reviewer · timestamp · evidence_locator str required
target_type str claim | concept_mapping | person_identity | grant_link | author_contribution | appraisal
decision str accept | edit | reject
status str active | pending_review
studies/studies.jsonl row: validate_study
Field Type Rule
study_id · confidence · evidence · review_state str required (study.py uses confidence confirmed|likely|uncertain)
pmids list required
people/<slug>.json: validate_person
Field Type Rule
slug str required
name_variants list required
orcid — optional
grants/<slug>.json: validate_grant
Field Type Rule
slug · funder · award_number str required
approved_aliases list required (grant.py seeds it with the award number)
check-citations findings.json entry: validate_citation_check_finding
Field Type Rule
assertion_text str required
verdict str supported | overstated | conflicting | insufficient | unavailable
evidence list each item needs pmid; must be empty for unavailable and non-empty otherwise
existing_citation_pmid · citation_mismatch · note — optional
graph/concepts.jsonl row: validate_concept
Field Type Rule
concept_id · name · created_at · updated_at str required
aliases list required
alias_provenance dict required
graph/relations.jsonl row: validate_relation
Field Type Rule
relation_id · subject_concept_id · object_concept_id · created_at · updated_at str required
type str supports | potential_conflict | contradicts | extends | replicates
supporting_claims list at least one; each needs pmid and claim_id
source_version_ids list required
review_state str unreviewed | reviewed
rationale str | null contradicts requires reviewed and a non-empty rationale
stale bool required
papers/<pmid>/citations.json entry: validate_citation_observation
Field Type Rule
retrieved_at str always required
status str check_failed | no_pmcid marks a non-observation: needs reason (str) and must not have count
source · query · coverage str required for a real observation
count int required for a real observation, ≥ 0
Subagent contracts
Both agents return a single JSON object with no prose and no fences. Neither can write to the library: the calling command validates their output and hands it to a script.
Input: pmid, title, abstract, and full_text (markdown from versions/<id>/source.md) or null for an abstract-tier paper. Unknown fields are the literal string "unknown"; "claims": [] is a valid answer.
Output (verbatim)
{
"pmid": "<string>",
"study_type": "rct|cohort|case_control|meta_analysis|molecular|imaging|review|mixed|unknown",
"study_type_confidence": "confident|uncertain",
"claims": [
{
"locator": "<string>",
"evidence_span": "<direct quote>",
"evidence_tier": "abstract|full",
"population": "...", "intervention": "...", "comparator": "...",
"outcome": "...", "timepoint": "...", "direction": "...",
"effect_value": "...", "effect_measure": "...",
"uncertainty_interval": "...", "study_design": "...",
"cohort_identity": "...", "adjustment_context": "..."
}
]
}
agents/ref-synthesizer.md
Input: question, plus candidates, each with at least pmid, citekey, kind (claim|passage), text, locator, evidence_tier (abstract|full|null) and retraction_status (unknown|none|retracted|erratum). Every [^pmid] must refer to a supplied candidate.
Output (verbatim)
{
"answer": "<the grounded answer text, in markdown, with [^pmid] citations inline>",
"coverage_note": "<one or two sentences on evidence tier/status/sufficiency, or null if nothing notable>",
"unresolved_questions": ["<follow-up the evidence raises but doesn't answer>", "..."]
}
Testing
The phase suites are plain unittest files that build temporary libraries. Run one:
$ python3 skills/ref-manager/scripts/tests/test_phase0.py
Or run all of them, with HOME pointed at a scratch directory so your real ~/.config is never touched:
$ for t in skills/ref-manager/scripts/tests/test_phase*.py; do HOME=$(mktemp -d) python3 "$t" || echo "FAILED: $t"; done
Results at time of writing (2026-09-15, Python 3.14): all 17 suites pass, 220 tests .
Suite Tests Result
test_phase0.py 13 OK
test_phase1.py 12 OK
test_phase2.py 10 OK
test_phase3_acquire.py 20 OK
test_phase3_papers.py 9 OK
test_phase4_annotate.py 13 OK
test_phase4_extract.py 18 OK
test_phase5_compare.py 18 OK
test_phase5_prisma.py 7 OK
test_phase6_ask.py 13 OK
test_phase7_checkcitations.py 12 OK
test_phase8_graph.py 17 OK
test_phase8_okf.py 7 OK
test_phase9_gaps.py 14 OK
test_phase9_related.py 7 OK
test_phase10_summarize_review.py 15 OK
test_phase11_audit.py 15 OK
Fixtures live in skills/ref-manager/scripts/tests/fixtures/ (sample.html, sample.jats.xml). Tests that touch Papers.app use --papers-db with a synthetic scratch database, never the real library.