ref-manager

Commands · Organize

Projects, reading state, screening, notes, and review decisions

These commands record your judgement: which projects a paper belongs to, how far you have read it, whether it passed screening, what you think of it, which extracted claims you accept, and which papers report the same study. Acquisition and extraction never write any of this, and regenerating derived files never overwrites it.

How they fit

PER PROJECT · projects/<slug>/ PER PAPER · papers/<pmid>/ LIBRARY-WIDE · studies/ /ref:project /ref:queue /ref:screen project.yaml scope · questions[] papers.yaml one membership per PMID: relevance · priority reading_status · why_saved screening (latest) screening.jsonl append-only history /ref:note notes.md dated entries, yours /ref:verify corrections.json overlay on claims.json active ↔ pending_review when evidence changes also updates: grants/<slug>.json · authorship.json · person records (via person.py) /ref:study studies.jsonl grouping + confidence + evidence datasets.jsonl reuse ≠ same study methods.jsonl context · source locator §5c selectors read these files --project · --screened · --read · --queue ← papers.yaml --study ← studies.jsonl rejected claims drop out of /ref:ask, /ref:compare, …
slash command file it owns correction overlay side effect
State lives where it belongs. Reading status and screening are per project, so one paper can be read and included in one project while still to_screen in another. Notes and corrections are per paper, and study groupings are library-wide. Commands that work on a set of papers read this state through the selector grammar.

Commands

/ref:project

phase 1

Create research projects, give them questions, and add papers with project-specific relevance, priority, and reading state.

/ref:project create <slug> [--scope "<text>"] /ref:project add-question <slug> --id <qid> --text "<text>" /ref:project add-paper <slug> <pmid> [--relevance "<text>"] [--priority N] [--reading-status <state>] /ref:project show <slug> /ref:project list

Arguments

create <slug>
Creates projects/<slug>/project.yaml and an empty papers.yaml. The slug is validated, and a duplicate is refused with the existing project named.
add-question <slug>
--id <qid> (passed to the script as --question-id) and --text. Question IDs only need to be unique within their project.
add-paper <slug> <pmid>
Adds a membership record. --reading-status is one of to_screen, to_read, reading, read. If the paper is already a member, the existing record is returned unchanged.
show <slug>
Prints the project and its membership list, plus a summary with paper count, question count, reading-state breakdown, and source/completeness counts.
list
Prints each project's slug, scope, question count, reading-state breakdown, and source/completeness counts, so it can double as a quick project dashboard.

What happens

  1. Resolves the library root, then runs the matching project.py action.
  2. Prints the JSON result verbatim. On a slug collision, Claude asks you for another name instead of adding a suffix, so references never point at a surprise project.

Reads & writes

Writes projects/<slug>/project.yaml and papers.yaml. Both are JSON despite the extension, and both are written atomically. Membership is one record per PMID per project, so each project's relevance, priority, and reading state are independent.

Examples

output from a scratch library

 /ref:project create thesis-ch3 --scope "Cortical thickness in autism"
{
  "slug": "thesis-ch3",
  "scope": "Cortical thickness in autism",
  "questions": []
}
 /ref:project add-paper thesis-ch3 31000001 --relevance "Core adult evidence" --priority 1 --reading-status to_read
{
  "pmid": "31000001",
  "relevance": "Core adult evidence",
  "priority": 1,
  "reading_status": "to_read",
  "why_saved": null,
  "screening": null,
  "added_at": "2026-09-15T10:14:33.760136+00:00"
}
 /ref:project create thesis-ch3
error: project 'thesis-ch3' already exists at projects/thesis-ch3

Under the hood

project.py create|add-question|add-paper|show|list --repo … [--slug …] [--question-id …] [--text …] [--pmid …] [--relevance …] [--priority N] [--reading-status …]

See also

/ref:queue · /ref:screen · selector grammar · Thesis chapter tutorial

/ref:queue

phase 1

Set or show a paper's reading state, priority, and why-saved note within one project.

/ref:queue set --project <slug> --pmid <pmid> [--status <state>] [--priority N] [--why "<text>"] /ref:queue show --project <slug> [--pmid <pmid>]

Arguments

set
Updates only the fields you pass. --status is one of to_screen, to_read, reading, read. --why sets why_saved.
show
With --pmid, prints that one membership. Without it, prints the whole project queue, including reading-state and source/completeness summaries, so the queue doubles as a quick dashboard.
--project <slug>
Required for both.

What happens

  1. Finds the paper's membership record in papers.yaml and updates it in place.
  2. If the PMID is not a member yet, the command errors and points you to /ref:project add-paper.

Why only here: reading state records what a human has actually read. Fetching full text or promoting claims never sets it, so --read selects papers you have read, not papers a model processed.

Reads & writes

Reads and writes projects/<slug>/papers.yaml.

Examples

output from a scratch library

 /ref:queue set --project thesis-ch3 --pmid 31000001 --status reading --why "Largest adult sample"
{
  "added_at": "2026-09-15T10:14:33.760136+00:00",
  "pmid": "31000001",
  "priority": 1,
  "reading_status": "reading",
  "relevance": "Core adult evidence",
  "screening": null,
  "why_saved": "Largest adult sample"
}
 /ref:queue set --project thesis-ch3 --pmid 31000003 --status read
error: pmid '31000003' is not a member of project 'thesis-ch3' (add it first via /ref:project)

Under the hood

queue.py set|show --repo … --project … [--pmid …] [--status …] [--priority N] [--why …]

See also

/ref:project · /ref:status

/ref:screen

phase 2

Record a project-specific include, exclude, or pending decision, with a reason, for one paper.

/ref:screen --project <slug> --pmid <pmid> --decision included|excluded|pending --reason "<text>" [--run <query-run-id>]

Arguments

--project <slug>
Required.
--pmid <pmid>
Required. The paper does not have to be in the library or the project yet.
--decision
Required: included, excluded, or pending.
--reason "<text>"
Required, so every exclusion can be explained later, for example in a PRISMA flow.
--run <id>
Optional. Links the decision to the saved search run it came from, stored as search_run.

What happens

  1. Appends a {pmid, decision, reason, timestamp[, search_run]} record to screening.jsonl. Earlier decisions stay in the log.
  2. Mirrors the latest decision onto the membership's screening field, so --screened can filter without replaying the log.
  3. If the PMID is not a member yet, it is added with no relevance note. You can screen first and annotate later.

Reads & writes

Appends to projects/<slug>/screening.jsonl and updates papers.yaml. The script also has a history action that the command file does not expose.

Example

output from a scratch library

 /ref:screen --project thesis-ch3 --pmid 31000002 --decision included --reason "Reports adult cortical thickness"
{
  "pmid": "31000002",
  "decision": "included",
  "reason": "Reports adult cortical thickness",
  "timestamp": "2026-09-15T10:14:34.166756+00:00"
}

Under the hood

screen.py decide --repo … --project … --pmid … --decision … --reason "…" [--run …]

See also

/ref:search-pubmed · /ref:review --prisma · Systematic review tutorial

/ref:note

phase 1

Append your own dated thoughts to a paper's notes.md, or print what is already there.

/ref:note <pmid> <text> /ref:note <pmid> --show

Arguments

<pmid> <text>
Appends <text> as a new entry, separated by --- and a UTC timestamp.
<pmid> --show
Prints the whole notes file, or nothing if it doesn't exist yet.

What happens

  1. Checks that papers/<pmid>/ exists. If not, it errors and suggests /ref:add.
  2. Appends the entry or prints the file.

notes.md is authoritative user content. This is the only command that writes to it, and no generated view ever overwrites it. /ref:search --scope notes labels its matches personal_note, never evidence.

Reads & writes

Appends to papers/<pmid>/notes.md.

Example

output from a scratch library

 /ref:note 31000001 Check whether site effects were modelled.
appended to <library>/papers/31000001/notes.md
 /ref:note 31000001 --show

---
2026-09-15T10:14:34.335329+00:00

Check whether site effects were modelled.

Under the hood

note.py append|show --repo … --pmid … [--text "…"]

See also

/ref:search --scope notes · /ref:pull-annotations

/ref:verify

phase 4

Record a human review decision as a correction overlay: accept, edit, or reject a claim, and review grant links, author contributions, and identity matches.

/ref:verify claim <pmid> <claim_id> accept|edit|reject [--replacement <json-file>] [--rationale "…"] [--reviewer "…"] /ref:verify grant-link <pmid> <grant-slug> accept|reject --evidence-locator "…" [--award-number "…"] [--rationale "…"] [--reviewer "…"] /ref:verify author-contribution <pmid> <author_index> shared_first|shared_senior|corresponding --evidence "<statement>" [--reviewer "…"] /ref:verify person-identity <pmid> <person-slug> <author_index> accept|reject [--rationale "…"] [--reviewer "…"] /ref:verify show <pmid>

Arguments

claim
Reviews one claim from the paper's registry. An unknown claim_id is refused. Reviewing a superseded claim is allowed. reject also marks the claim in the registry, so it drops out of default synthesis. edit takes a replacement JSON file.
grant-link
Records a reviewed link on grants/<slug>.json, replacing any earlier link for that PMID, with match_method: "manual_review". It never adds the award number to the grant's aliases. That takes a deliberate /ref:grant add-alias.
author-contribution
Sets an independent shared_first, shared_senior, or corresponding flag in authorship.json. It refuses without an explicit evidence statement, because these roles are never inferred from author position. author_index is 0-based.
person-identity
Accepts or rejects that author N is a given person. It delegates to /ref:person's confirm/reject machinery, then logs a correction record so the audit trail matches the other review kinds.
show <pmid>
Revalidates all corrections against the current claim registry, then prints them. Corrections whose evidence changed show as pending_review.
--reviewer
Defaults to user.

What happens

  1. Validates the target, then appends a record to corrections.json with correction_id, target_type, target_id, decision, original_value, replacement_value, rationale, reviewer, timestamp, evidence_locator, and status.
  2. Applies any side effects for that kind: registry reject, grant link, authorship flag, or person link.
  3. Corrections are an overlay, not an edit to extracted data. If re-extraction leaves the evidence unchanged, they carry over. If it materially changes, they flip to pending_review, never silently reapplied or dropped.

Reads & writes

Appends to papers/<pmid>/corrections.json. Depending on the kind, also writes the claim registry, grants/<slug>.json, papers/<pmid>/authorship.json, or person records. verify.py also has a review-appraisal action for RoB2, NOS, and AMSTAR-2 domains, which /ref:review calls.

Examples

output from a scratch library

 /ref:verify author-contribution 31000001 0 shared_first --evidence ""
error: author_contribution flag 'shared_first' refused: an explicit evidence statement is required, never inferred from author position (§3c)
 /ref:verify author-contribution 31000001 0 shared_first --evidence "These authors contributed equally to this work."
{
  "author_index": 0,
  "flag": "shared_first",
  "evidence_statement": "These authors contributed equally to this work.",
  "reviewer": "user",
  "timestamp": "2026-09-15T10:14:35.548943+00:00"
}

illustrative output

 /ref:verify claim 31000001 <claim_id> reject --rationale "Effect reported for adolescents, not adults"
{
  "correction_id": "cor-…",
  "target_type": "claim",
  "target_id": "<claim_id>",
  "decision": "reject",
  "original_value": { "population": "…", "intervention": "…", "comparator": "…", "outcome": "…" },
  "replacement_value": null,
  "rationale": "Effect reported for adolescents, not adults",
  "reviewer": "user",
  "timestamp": "…",
  "evidence_locator": "…",
  "status": "active"
}

Under the hood

verify.py review-claim | review-grant-link | review-author-contribution | review-person-identity | show --repo … --pmid …. Command words map to script flags: --replacement--replacement-file, --evidence--evidence-statement, the flag word → --flag, the grant slug → --grant, the person slug → --person.

See also

/ref:extract · /ref:grant · /ref:person · /ref:report · Concepts

/ref:study

phase 5

Group PMIDs that report the same underlying investigation, and record the datasets and methods papers share.

/ref:study create-study <study-id> --pmid <pmid…> --confidence confirmed|likely|uncertain --evidence "<why>" /ref:study create-dataset <dataset-id> --name "<display name>" [--pmid <pmid…>] [--notes "…"] /ref:study create-method <method-id> --name "<display name>" [--pmid <pmid…>] [--context "…"] [--locator "…"] /ref:study list-studies | list-datasets | list-methods

Arguments

create-study
Needs at least one PMID, a confidence, and a non-empty --evidence such as a shared trial registration or an explicit "we previously reported…". It is refused without evidence. New studies start with review_state: "unreviewed".
create-dataset
Records a cohort or dataset that papers reuse. Linking two papers to the same dataset does not make them one study.
create-method
Records a protocol, tool, or control, with optional context and a source locator.
list-studies · list-datasets · list-methods
Print the corresponding JSONL file as a JSON array.

What happens

  1. Allocates the ID as a library-global slug. A collision is refused with the conflicting record named, never silently suffixed.
  2. Appends the row under the library lock.

Why grouping is explicit: several papers from one trial shouldn't count as independent evidence, but shared authors or a shared dataset don't prove two papers report the same trial. /ref:compare and --study <id> therefore only trust groupings you have stated.

Reads & writes

Appends to studies/studies.jsonl, studies/datasets.jsonl, or studies/methods.jsonl.

Examples

output from a scratch library

 /ref:study create-study asd-multisite --pmid 31000001 31000002 --confidence likely --evidence "Same cohort registration cited in both methods sections"
{
  "study_id": "asd-multisite",
  "pmids": [
    "31000001",
    "31000002"
  ],
  "confidence": "likely",
  "evidence": "Same cohort registration cited in both methods sections",
  "review_state": "unreviewed",
  "created_at": "2026-09-15T10:14:35.054841+00:00",
  "updated_at": "2026-09-15T10:14:35.054841+00:00"
}
 /ref:study create-study asd-x --pmid 31000001 --confidence likely --evidence ""
error: evidence is required: why these PMIDs are the same investigation (§3b)
 /ref:study create-dataset abide-ii --name "ABIDE II" --pmid 31000001
{
  "dataset_id": "abide-ii",
  "name": "ABIDE II",
  "pmids": [
    "31000001"
  ],
  "notes": null,
  "created_at": "2026-09-15T10:14:35.247166+00:00"
}

Under the hood

study.py create-study|create-dataset|create-method|list-studies|list-datasets|list-methods --repo … [--id …] [--pmid …] [--confidence …] [--evidence …] [--name …] [--notes …] [--context …] [--locator …]

See also

/ref:compare · /ref:methods · /ref:review --prisma · --study selector