Commands · Find
Search the library, ask it questions, discover new papers
Five commands for finding things. Two look inside your library: /ref:search for quick matches, /ref:ask for grounded answers. Three look outward to PubMed: saved searches and snowballing. None of them adds a paper for you. Candidates only enter the library when you run /ref:add.
Commands
/ref:search
phase 2read-only
Find papers, personal notes, or project relevance comments that contain a piece of text.
/ref:search --q "<text>" [--scope evidence|notes|all]
Arguments
- --q "<text>"
- Required. A case-insensitive substring, not a boolean or FTS expression.
- --scope evidence
- Default. Matches title, abstract, journal, DOI, PMCID, citekey, author names, and grant metadata in
meta.json/authorship.json/funding.json/raw/<hash>/response.json, plus saved-query context where present.
- --scope notes
- Searches
papers/<pmid>/notes.md, plus the relevance and why_saved text on every project membership.
- --scope all
- Searches both, with every hit tagged by
kind.
What happens
- Resolves the library root. If none is configured, it fails and points you to
/ref:init.
- Runs
search.py and prints its JSON verbatim.
- Each hit's
kind is evidence, personal_note, project_relevance, or saved_query. Claude never presents a note or relevance comment as a published finding.
- Evidence hits also report which structured field matched (e.g.
title, doi, author, grant) and, when available, a lifecycle state: metadata-only, abstract-only, full-text, pdf-backed, or oa-pending.
Title and abstract only. This command does not search converted full-text passages. For passage-level retrieval, use
/ref:ask, which queries the FTS5 index.
Reads & writes
Reads papers/*/meta.json, papers/*/raw/*/response.json, papers/*/notes.md and projects/*/papers.yaml. Writes nothing.
Example
output from a scratch library
› /ref:search --scope all --q "site"
{
"query": "site",
"scope": "all",
"results": [
{
"kind": "evidence",
"pmid": "31000001",
"citekey": "rivera2019cortical",
"field": "title",
"snippet": "Cortical thickness in autistic adults: a multisite MRI study"
},
{
"kind": "personal_note",
"pmid": "31000001",
"snippet": "---\n2026-09-15T10:14:34.335329+00:00\n\nCheck whether site effects were modelled."
}
]
}
Under the hood
search.py --repo <library> --scope <scope> --q "<text>". The --search selector reuses its run() function with scope="evidence".
See also
/ref:ask · /ref:note · /ref:index
/ref:ask
phase 6ref-synthesizerread-only
Answer a research question using only evidence in your library, with every citation checked against what was retrieved.
/ref:ask "<question>" [<pmid…> | --project <slug> | --query <slug> | --study <id> | --search "<expr>" | --from-file <path>] [refiners]
Arguments
- <question>
- Required free text. It is passed to the script as
--q, because --question already means a project question ID in the selector grammar.
- selector
- Optional. Any §5c selector constrains retrieval to that set. Without one, the whole library is searched.
- refiners
--screened, --read, --queue (each with --project), --tier, --exclude.
- --candidate-budget N
- Script option, default 30.
- --token-budget N
- Script option, default 6000. Tokens are estimated at 4 characters each.
What happens
- Claude picks 2–4 MeSH terms, synonyms, or aliases and passes them as
--expand. The library has no embeddings, so this expansion is how related wording gets matched.
ask_retrieve.py rebuilds index/catalog.sqlite, then ORs the question words and expansion terms into an FTS5 match over active claims and passages. Rejected or excluded claims are left out.
- Candidates are de-duplicated, with a claim preferred over an overlapping passage. They are then spread round-robin across PMIDs and trimmed to the candidate and token budgets. The
report states any truncation. If insufficient_coverage is true, the command says so and stops.
- One ref-synthesizer subagent receives the question and all candidates. It returns
answer, coverage_note, and unresolved_questions.
validate_citations.py checks every [^pmid] in the answer against the candidate set. Unresolved citations are reported, never silently dropped.
- You see the answer, the coverage note, open questions, and the evidence tier and retraction status behind it.
Reads & writes
Reads committed claims and passages through the rebuilt index/catalog.sqlite, the only file it regenerates. Persists no answer. For a saved, refreshable answer, use /ref:brief, which wraps this same flow.
Example
illustrative output
› /ref:ask "Is cortical thickness altered in autistic adults?" --project thesis-ch3
# retrieval report (from ask_retrieve.py)
"report": { "candidate_budget": 30, "token_budget": 6000,
"total_matches_before_limits": 14, "returned": 14,
"truncated_by_candidate_budget": false, "dropped_for_token_budget": 0,
"insufficient_coverage": false, "selector_constrained": true }
# ref-synthesizer answer, then validate_citations.py
{ "cited_pmids": ["31000001", "31000002"], "resolved": ["31000001", "31000002"],
"unresolved": [], "all_resolved": true }
output from a scratch library (no extracted claims yet)
# the three scratch papers were only added, not extracted, so the index has nothing to match
{
"candidates": [],
"report": {
"candidate_budget": 30,
"token_budget": 6000,
"match_terms": ["cortical", "thickness", "altered", "autistic", "adults",
"autism spectrum disorder", "cortical morphology"],
"total_matches_before_limits": 0,
"returned": 0,
"truncated_by_candidate_budget": false,
"dropped_for_token_budget": 0,
"insufficient_coverage": true,
"selector_constrained": true
}
}
# an empty selector is an error, not an empty answer
error: selector matched no papers: --project thesis-ch3 --screened excluded
Under the hood
ask_retrieve.py --repo … --q … --expand … [selector] → ref-synthesizer → validate_citations.py --answer-file … --candidates-file …
See also
/ref:brief · /ref:check-citations · /ref:extract · /ref:search
/ref:pubmed-query
phase 2read-only
Translate a natural-language research prompt or PICO into a PubMed Boolean query without running the search.
/ref:pubmed-query <question or PICO> [--broad|--specific]
Arguments
- <question or PICO>
- Prose or explicit P/I/C/O lines.
- --broad
- Favor sensitivity: more synonyms, fewer restrictive filters.
- --specific
- Favor precision: tighter terms and study-design filters when appropriate.
What happens
- Claude identifies population, intervention/exposure, comparator, outcome, design, date, age, species, language, and author constraints.
- It emits a PubMed query using stable MeSH terms plus title/abstract synonyms, date filters with
[dp], and author or publication-type tags when requested.
- It prints the query, a short strategy note, and any limitations. It does not call PubMed or write files.
Example
› /ref:pubmed-query "P: autistic adolescents; I: tDCS; C: sham; O: social cognition" --specific
query:
(("Autism Spectrum Disorder"[Mesh] OR autism[tiab] OR autistic[tiab])
AND ("transcranial direct current stimulation"[tiab] OR tDCS[tiab])
AND (sham[tiab] OR placebo[tiab])
AND ("social cognition"[tiab] OR "theory of mind"[tiab]))
Under the hood
No script and no network call. The query is produced by the command layer so the user can inspect or edit it before running /ref:search-pubmed.
See also
/ref:search-pubmed · /ref:search-add-fetch
/ref:search-pubmed
phase 2PubMed MCP
Turn a question into a PubMed query, run it, and save the exact expression and its results as an immutable run.
/ref:search-pubmed <question or query> --slug <slug> [--create]
Arguments
- <question or query>
- Natural language, which Claude turns into a PICO-framed boolean query with MeSH and free-text terms. An explicit PubMed expression is used verbatim.
- --slug <slug>
- Required. Names
queries/<slug>.yaml.
- --create
- Required for a slug's first run. Without it, a new slug is refused.
What happens
- Claude builds the query. The script neither parses PICO nor calls PubMed.
- Claude runs the query through the PubMed MCP
search_articles tool and writes the PMIDs to a temp JSON array.
pubmed_query.py new-run appends a run containing the run_id, exact query, source, retrieved_at, and pmids.
- No paper is added. Screen the results or
/ref:add the ones you want.
Reads & writes
Writes queries/<slug>.yaml, which is JSON despite the extension. Runs only ever append, so any artifact frozen on --query <slug> --run <id> still resolves to the same set later.
Example
output from a scratch library (PMIDs supplied by hand in place of the MCP call)
› /ref:search-pubmed '("Autism Spectrum Disorder"[MeSH]) AND cortical thickness[tiab]' --slug asd-cortex --create
{
"run_id": "run-24eadfb8f77b",
"query": "(\"Autism Spectrum Disorder\"[MeSH]) AND cortical thickness[tiab]",
"source": "pubmed",
"retrieved_at": "2026-09-15T10:14:34.607296+00:00",
"pmids": [
"31000001",
"31000002",
"39999991"
]
}
Under the hood
MCP search_articles → pubmed_query.py new-run --repo … --slug … --query-text "…" --source pubmed --pmids-file … [--create]. The script also has show and list actions.
See also
/ref:update-queries · /ref:screen · /ref:review --prisma · Systematic review tutorial
/ref:search-add-fetch
phase 3PubMed MCP
Run a prompt/PICO PubMed search, save the full run, then add and fetch a bounded set of PMIDs.
/ref:search-add-fetch <question or query> --slug <slug> [--create] [--limit <N>] [--broad|--specific]
Arguments
- <question or query>
- Natural language/PICO or an explicit PubMed expression.
- --slug <slug>
- Required. Names the immutable saved search run.
- --create
- Required for a new slug.
- --limit <N>
- Number of returned PMIDs to ingest. Default
20; values above 100 require confirmation.
What happens
- Builds or accepts the exact PubMed query, then runs PubMed MCP
search_articles.
- Saves the complete search run through
pubmed_query.py new-run before limiting.
- Takes the first
N PMIDs, adds metadata through add.py, then fetches full text through fetch.py.
- Prints separate
search, save-run, add, and fetch stage outputs.
Reads & writes
Writes queries/<slug>.yaml, then the same paper files as /ref:add and /ref:fetch for selected PMIDs.
Example
› /ref:search-add-fetch "autism tDCS sham social cognition" --slug autism-tdcs --create --limit 20
search:
found: 42
selected: 20
add:
40665956: added (citekey=luckhardt2025transcranial)
fetch:
40665956: acquired (source=pmc_jats, figures=3, images=3/3)
Under the hood
PubMed MCP search_articles → pubmed_query.py new-run → PubMed MCP get_article_metadata → add.py → full-text acquisition ladder → fetch.py.
See also
/ref:pubmed-query · /ref:search-pubmed · /ref:add-fetch
/ref:update-queries
phase 2PubMed MCP
Re-run a saved query's stored expression, unchanged, and record what was added or removed.
/ref:update-queries <slug>
Arguments
- <slug>
- Required. A saved query with at least one prior run.
What happens
pubmed_query.py show reads the saved query.
- Claude calls the PubMed MCP search with the exact
query from the most recent run, unaltered.
pubmed_query.py rerun appends a new run that reuses the stored query and source, then reports added and removed PMIDs against the previous run.
Re-runs only happen when you ask. Nothing is scheduled, and earlier runs are never edited.
Reads & writes
Reads and appends to queries/<slug>.yaml.
Example
output from a scratch library
› /ref:update-queries asd-cortex
{
"run": {
"run_id": "run-47456189b1d6",
"query": "(\"Autism Spectrum Disorder\"[MeSH]) AND cortical thickness[tiab]",
"source": "pubmed",
"retrieved_at": "2026-09-15T10:14:34.789920+00:00",
"pmids": ["31000001", "31000002", "39999992"]
},
"added": ["39999992"],
"removed": ["39999991"]
}
Under the hood
pubmed_query.py show → MCP search → pubmed_query.py rerun --repo … --slug … --pmids-file …
See also
/ref:search-pubmed · /ref:screen
/ref:related
phase 9PubMed MCP
Snowball from a paper you already have: backward through its reference list, forward through PubMed's related-articles links.
/ref:related <pmid> [--direction backward|forward|both] [--link-type <type>]
Arguments
- <pmid>
- Required. Must already have
papers/<pmid>/meta.json.
- --direction
backward, forward, or both (default).
- --link-type
pubmed_pubmed (default), pubmed_pmc, pubmed_gene, pubmed_protein, or pubmed_nucleotide. Passed through to find_related_articles.
- show
- Script action that prints the stored
related.json without querying anything.
What happens
- Backward needs no network. It finds a
References heading in the current version's source.md and keeps each citation line as raw text with resolved: false. Without full text, it reports available: false with a reason.
- Forward: Claude calls MCP
find_related_articles and takes the PMIDs from linksets[0].linksetdbs[0].links. The script records each one with method: "find_related_articles:<link_type>".
- Rows are upserted by (direction, method, candidate), so a re-run refreshes
retrieved_at instead of duplicating rows. The summary counts already_in_library and not_yet_added.
Similarity, not citations. The pubmed_pubmed link type ranks by similar words in titles, abstracts, and MeSH terms. It is not a cited-by graph, and every forward row says which link type produced it. Zero results are reported as zero_results: true, never as evidence that no related work exists. Candidates are never added automatically.
Reads & writes
Reads papers/<pmid>/current.json and versions/<id>/source.md. Writes papers/<pmid>/related.json under the per-PMID lock.
Example
output from a scratch library (forward PMIDs supplied by hand in place of the MCP call)
› /ref:related 31000001
# backward: the paper has no full text yet
{
"available": false,
"reason": "no committed full-text version for this PMID (run /ref:fetch or /ref:attach first)"
}
# forward (rows abbreviated to one)
{
"count": 2,
"rows": [
{
"candidate": "38888881",
"resolved": true,
"direction": "forward",
"method": "find_related_articles:pubmed_pubmed",
"query": { "link_type": "pubmed_pubmed", "source_pmid": "31000001" },
"retrieved_at": "2026-09-15T10:14:34.958685+00:00",
"already_in_library": false
}
],
"zero_results": false,
"note": null,
"persisted": { "total_candidates": 2, "already_in_library": 1, "not_yet_added": 1 }
}
Under the hood
related.py backward --repo … --pmid … · MCP find_related_articles → related.py forward --repo … --pmid … --link-type … --results-file … · related.py show
See also
/ref:fetch · /ref:add · /ref:audit --citations · Graph discovery tutorial