ref-manager

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.

How they fit

INSIDE THE LIBRARY /ref:search --scope evidence|notes|all substring scan title · journal · abstract · notes.md · relevance hits tagged by kind evidence · personal_note · project_relevance /ref:ask question + your 2–4 expansion terms 1 RETRIEVE rebuild index/catalog.sqlite FTS5 over claims + passages diversify across PMIDs 2 SYNTHESIZE one ref-synthesizer subagent over the whole candidate set cites as [^pmid] 3 VALIDATE every [^pmid] must be in the candidates it was given nothing is written to disk optional §5c selector narrows the set first OUTWARD TO PUBMED (MCP) /ref:search-pubmed first run, --create /ref:update-queries same stored expression queries/<slug>.yaml runs append, never mutate → --query <slug> [--run <id>] /ref:related backward · forward papers/<pmid>/related.json /ref:add your decision
slash command subagent file written your decision
/ref:search is a plain substring scan that needs no index. /ref:ask rebuilds the SQLite FTS5 catalog before every retrieval, so it never answers from a stale index. The PubMed-facing scripts never call the network. Claude makes the MCP call and passes the resulting PMIDs to the script as a JSON file.

Selector grammar, as implemented

Commands that work on a set of papers share one selector grammar, from PLAN.md §5c and lib_selector.py. On this page, only /ref:ask uses it. A selector first resolves to an explicit PMID list, with counts by extraction tier and retraction status. An empty result is an error that names the selector, never a silently empty answer.

SelectorResolves toNotes
<pmid…>exactly those PMIDs (de-duplicated)base case
--from-file <path>one PMID per line from a filefor long lists
--project <slug>members in projects/<slug>/papers.yamlunknown slug is an error
--query <slug> [--run <id>]PMIDs of one saved run in queries/<slug>.yamlmost recent run if --run is omitted
--study <id>PMIDs grouped in studies/studies.jsonl
--search "<expr>"PMIDs of /ref:search --scope evidence hitssubstring, resolved once
--screened included|excluded|pendingnarrows to the latest screening decisionrequires --project
--read · --queue <state>narrows by membership reading_statusrequires --project
--question <qid>accepted and recorded in the expressiondoes not narrow membership in the current code
--tier abstract|full|anyfilters on meta.json extraction_tierdefault any
--exclude <pmid…>removes PMIDs from the result
--concept <id>raises “not available until phase 8”not wired to the graph yet

Only one base source is used, in this order: PMIDs, --from-file, --project, --query, --study, --search. The state filters then narrow it (AND). Human-verification counts are still reported as not_yet_tracked. In ask_retrieve.py, --concept on its own does not count as a selector, so retrieval runs across the whole library.

Commands

/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

  1. 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.
  2. 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.
  3. 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.
  4. One ref-synthesizer subagent receives the question and all candidates. It returns answer, coverage_note, and unresolved_questions.
  5. validate_citations.py checks every [^pmid] in the answer against the candidate set. Unresolved citations are reported, never silently dropped.
  6. 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-synthesizervalidate_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

  1. Claude identifies population, intervention/exposure, comparator, outcome, design, date, age, species, language, and author constraints.
  2. 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.
  3. 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

  1. Claude builds the query. The script neither parses PICO nor calls PubMed.
  2. Claude runs the query through the PubMed MCP search_articles tool and writes the PMIDs to a temp JSON array.
  3. pubmed_query.py new-run appends a run containing the run_id, exact query, source, retrieved_at, and pmids.
  4. 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_articlespubmed_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

  1. Builds or accepts the exact PubMed query, then runs PubMed MCP search_articles.
  2. Saves the complete search run through pubmed_query.py new-run before limiting.
  3. Takes the first N PMIDs, adds metadata through add.py, then fetches full text through fetch.py.
  4. 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_articlespubmed_query.py new-run → PubMed MCP get_article_metadataadd.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

  1. pubmed_query.py show reads the saved query.
  2. Claude calls the PubMed MCP search with the exact query from the most recent run, unaltered.
  3. 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