Build a frozen evidence matrix over a selected set of papers, where every cell points to the claims behind it or says why it is empty.
/ref:compare <selector> --batch <label> [--refresh]
/ref:compare --batch <label> [--project <slug>] --edit <pmid> <column> <value>
Arguments
- <selector>
- Any §5c selector:
<pmid…>, --project [--question], --screened, --read/--queue, --query [--run], --study, --search, --from-file, refined by --tier/--exclude. --concept is rejected with "not available until phase 8".
- --batch <label>
- Required. Names the table. Re-running without
--refresh returns the frozen table unchanged.
- --refresh
- Re-resolve the selector, rebuild cells, and report
added, removed, changed_evidence_cells, and retraction_status_changes.
- --edit <pmid> <column> <value>
- Record your own override for one cell (the script takes it as
--edit-pmid/--edit-column/--edit-value). Pass --project if the table was built with one.
What happens
- Resolves the library root; fails pointing at
/ref:init if unconfigured.
- If the batch exists and
--refresh is absent, returns reused_frozen_table without touching the selector.
- Otherwise resolves the selector and builds rows. Papers sharing a recorded study (
/ref:study) share one row, with the study's confidence shown; shared datasets alone never group papers.
- Fills nine columns per paper: population, design, methods, sample_size, comparator, results, uncertainty, limitations, relevance.
not_extracted means no claim covers that dimension (sample_size and limitations always, since extraction doesn't capture them); not_reported means claims exist but the paper didn't state it. The two are kept distinct on purpose.
- Each row also carries a
provenance block keyed by PMID with extraction tier, last checked timestamp, and whether the record currently has an abstract or full text available.
- On refresh, re-applies your edits: an edit whose underlying cell is unchanged survives; one whose evidence changed is flagged
stale, never silently dropped or silently re-applied.
- Writes the table, then the manifest (including a per-PMID retraction-status snapshot for the next refresh).
Reads & writes
Reads papers/<pmid>/meta.json, claim registries, studies/ records, projects/<slug>/papers.yaml (relevance). Writes projects/<slug>/tables/<batch>/{manifest.json, table.json, edits.json}, or tables/<batch>/ at the library root without --project. A bare PMID list and an equivalent --project produce identical rows; only selector_expression differs.
Example
output from a scratch library
› /ref:compare --project thesis-ch3 --batch ch3-matrix
{
"status": "created",
"batch": "ch3-matrix",
"manifest": {
"batch": "ch3-matrix",
"selector_expression": "--project thesis-ch3",
"pmids": ["10000001", "10000002"],
"project": "thesis-ch3",
"report": {
"count": 2,
"by_extraction_tier": {"abstract": 2, "full": 0, "unavailable": 0, "missing_record": 0},
"by_human_verification_state": {"not_yet_tracked": 2},
"by_retraction_errata_status": {"unknown": 2}
},
"retraction_status_snapshot": {"10000001": "unknown", "10000002": "unknown"}
},
"rows": [
{
"pmids": ["10000001"],
"study": null,
"cells": {
"10000001": {
"population": {"value": "not_extracted"},
"design": {"value": "not_extracted"},
# … remaining columns and the second row trimmed; these papers have no claims yet
}
}
}
]
}
Under the hood
compare.py --repo <root> [selector] --batch <label> [--refresh], or with --edit-pmid/--edit-column/--edit-value. No subagent.
See also
/ref:methods · /ref:review · /ref:study · /ref:extract
/ref:methods
phase 5read-only
List what is known about each selected paper's design, adjustment, cohort, and linked methods and datasets, without guessing the rest.
/ref:methods <selector>
Arguments
- <selector>
- Same grammar as /ref:compare. No
--batch: nothing is saved.
What happens
- Resolves the selector and prints its report.
- Groups papers by recorded study, as
/ref:compare does.
- Per paper, collects distinct
study_design, adjustment_context, cohort_identity from active claims, plus methods and datasets explicitly linked with /ref:study create-method/create-dataset.
- Each per-paper row also includes provenance metadata (
extraction_tier and checked_at) so you can see freshness and source state without opening raw files.
instruments, software, and controls always read not_reported: the claim schema doesn't capture them, and the command won't reconstruct protocol details from what similar papers usually do.
Reads & writes
Reads claim registries and studies/ method, dataset, and study records. Writes nothing.
Example
output from a scratch library
› /ref:methods 10000001
{
"report": {
"count": 1,
"by_extraction_tier": {"abstract": 1, "full": 0, "unavailable": 0, "missing_record": 0},
"by_human_verification_state": {"not_yet_tracked": 1},
"by_retraction_errata_status": {"unknown": 1}
},
"rows": [
{
"pmids": ["10000001"],
"study": null,
"per_paper": [
{
"pmid": "10000001",
"study_design": "not_reported",
"adjustment_context": "not_reported",
"cohort_identity": "not_reported",
"linked_methods": "not_reported",
"linked_datasets": "not_reported",
"instruments": "not_reported",
"software": "not_reported",
"controls": "not_reported"
}
]
}
]
}
Under the hood
methods.py --repo <root> [selector]. No subagent.
See also
/ref:compare · /ref:study
/ref:summarize
phase 10agent
Write a cited prose narrative over a selected set of papers (or one paper), saved as a frozen batch.
/ref:summarize <selector> --batch <label> [--refresh]
Arguments
- <selector>
- Required. Any §5c selector; it fixes the paper set.
- --batch <label>
- Required. Names the saved summary; reused unless
--refresh.
- --refresh
- Re-resolve and regenerate, reporting
added_pmids, removed_pmids, withdrawn_evidence, and retraction_status_changes.
What happens
- Runs the script with
--dump-candidates to get the resolution report and the evidence before anything is written.
- The candidates are every active claim of the resolved papers, not ranked search hits: the selector already chose the set.
- An abstract-only set may still be summarized, but the summary must say so. (Contrast /ref:review, which refuses.)
- Spawns one ref-synthesizer for the whole set, never one per paper, with a fixed instruction to cover populations, interventions, outcomes, direction of effect, disagreement, tier, and retraction status.
- The saved manifest includes a provenance block with the resolved PMIDs and the project/question context so a brief can be traced to the exact library slice that generated it.
- Saves the returned answer with its coverage note and unresolved questions. One paper and many papers go through the same code path.
Reads & writes
Reads claim registries and meta.json. Writes projects/<slug>/summaries/<batch>/{manifest.json, summary.md, evidence.json}, or summaries/<batch>/ without --project.
Example
output from a scratch library (step 1 only)
› /ref:summarize 10000001 --batch rivera-summary
# step 1: resolve and collect candidates, before the subagent runs
{
"resolution": {
"pmids": ["10000001"],
"selector_expression": "--pmid 10000001",
"report": {
"count": 1,
"by_extraction_tier": {"abstract": 1, "full": 0, "unavailable": 0, "missing_record": 0},
"by_human_verification_state": {"not_yet_tracked": 1},
"by_retraction_errata_status": {"unknown": 1}
}
},
"candidates": []
}
# empty: this synthetic paper has no extracted claims, so there is nothing to summarize
illustrative output
# after ref-synthesizer returns, the saved batch is reported as
{
"status": "created",
"batch": "ch3-summary",
"manifest": {
"batch": "ch3-summary",
"project": "thesis-ch3",
"selector_expression": "--project thesis-ch3",
"pmids": ["…", "…"],
"report": { … },
"coverage_note": "…",
"unresolved_questions": ["…"]
},
"summary": "… prose with [^pmid] citations …"
}
Under the hood
summarize.py --repo <root> [selector] --dump-candidates, then summarize.py … --batch <label> --answer-file <tmp> [--coverage-note …] [--unresolved …] [--refresh]. The script also accepts --show with --batch to print a saved summary. Subagent: ref-synthesizer.
See also
/ref:ask · /ref:brief · /ref:compare
/ref:review
phase 10--prisma: phase 5
Either appraise a selected set (risk of bias per paper, GRADE certainty for the set) or, with --prisma, count a project's search flow from committed records.
/ref:review <selector> --batch <label> [--refresh]
/ref:review --prisma --project <slug> [--query <slug>[:<run_id>] …] [--refresh]
The two modes share a name but nothing else. If the arguments are neither --prisma nor a selector, the command stops rather than guessing.
Arguments · appraised mode
- <selector>
- Required. Any §5c selector.
- --batch <label>
- Required. Names the saved review; reused unless
--refresh.
- --refresh
- Re-resolve and regenerate; picks up review decisions recorded since, and reports
added_pmids/removed_pmids.
What happens · appraised mode
- Checks tiers first. If the resolved set has no full-tier paper, returns
"status": "refused" and generates nothing: RoB 2, Newcastle-Ottawa, AMSTAR-2 and GRADE need full-text detail.
- Picks a checklist per paper from the claims' study type: RCT → RoB 2, cohort/case-control → Newcastle-Ottawa, meta-analysis → AMSTAR-2 (a representative subset of items). Other study types get a note, not a forced rating. Abstract-tier papers in a mixed set are marked
insufficient_information, not dropped.
- Rates each domain deterministically from claim fields, with
claim_ids and a note. No LLM is involved. Most domains land on insufficient_information because the claim schema doesn't record things like blinding or allocation concealment; that is the honest answer, not a bug.
- Computes set-level GRADE: a baseline from study types, then each downgrade factor listed with
downgrade, not_assessed, and reason. Nothing is silently omitted.
- Every rating starts as
model_draft. Record a human decision per domain with verify.py review-appraisal (/ref:verify); a refreshed review then shows human_confirmed, human_edited or human_rejected. GRADE itself has no per-paper home and lives in grade.json.
Reads & writes · appraised mode
Reads claim registries, meta.json, corrections.json review decisions, relation records. Writes projects/<slug>/reviews/<batch>/{manifest.json, grade.json, appraisals/<pmid>.json}, or reviews/<batch>/ without --project. A refusal writes nothing.
Arguments · --prisma
- --project <slug>
- Required.
- --query <slug>[:<run_id>]
- Repeatable. A saved query whose run history feeds "identified"; without
:run_id its latest run is used. Projects don't record their queries, so you name them each time. Omit it and identified/duplicates read "unknown", not zero.
- --refresh
- Re-derive from current state and report
included_added/included_removed; otherwise the last snapshot is returned verbatim.
What happens · --prisma
- Identified and duplicates come from
queries/<slug>.yaml run histories; screened and excluded (by reason) from projects/<slug>/screening.jsonl; sought and not-retrieved from each included paper's full_text flag; included studies from studies/studies.jsonl.
- Nothing is re-run or estimated. Counts with no committed evidence read
"unknown" and are named in unresolved_query_specs or unevidenced_* rather than fudged to make the arithmetic balance.
- Included publications and included studies are reported separately, since several papers can report one study.
Reads & writes · --prisma
Writes projects/<slug>/prisma/<snapshot_id>/{manifest.json, flow.md, flow.csv} and projects/<slug>/prisma/latest.json. Snapshot IDs are opaque (prisma-…).
Example
output from a scratch library
› /ref:review --project thesis-ch3 --batch ch3-review
{
"status": "refused",
"batch": "ch3-review",
"reason": "selected set is wholly abstract-tier -- appraisal needs full-text detail; not proceeding (§8 phase-10 gate)",
"by_extraction_tier": {"abstract": 2, "full": 0, "unavailable": 0, "missing_record": 0}
}
# fetch or attach full text and run /ref:extract first
› /ref:review --prisma --project thesis-ch3
{
"status": "created",
"snapshot_id": "prisma-f64fae44bffb",
"manifest": {
"snapshot_id": "prisma-f64fae44bffb",
"project": "thesis-ch3",
"query_specs": [],
"flow": {
"identified": {
"per_source": {},
"total_raw": "unknown",
"note": "no --query given: identified counts are unknown, not zero"
},
"duplicates_removed": "unknown",
"unresolved_query_specs": [],
"screened": 0,
"excluded": {"count": 0, "by_reason": {}},
"unevidenced_screened_pmids": [],
"reports": {"sought": 0, "not_retrieved": 0, "not_retrieved_pmids": [], "unevidenced_sought_pmids": []},
"included": {
"publications_count": 0,
"publications": [],
"studies_count": "unknown",
"studies_note": "studies/studies.jsonl not available -- included-studies count is unknown, not assumed equal to included-publications count",
"grouped_studies": [],
"ungrouped_publications": []
}
# … project and data_cutoff fields trimmed
}
}
}
Under the hood
Appraised: appraise.py --repo <root> [selector] --batch <label> [--refresh]; reviews recorded with verify.py review-appraisal --pmid … --checklist RoB2|Newcastle-Ottawa|AMSTAR-2 --domain-key … --decision accept|edit|reject --reviewer … --rationale …. PRISMA: prisma.py --repo <root> --project <slug> [--query …] [--refresh]. No subagent in either mode.
See also
/ref:screen · /ref:search-pubmed · /ref:verify · /ref:study · Systematic review tutorial
/ref:check-citations
phase 7
Split a paragraph into assertions and record, for each, whether library evidence supports it, without ever rewriting your text.
/ref:check-citations "<paragraph>" [<selector>] [--export-bib]
/ref:check-citations --file <path> [<selector>] [--export-bib]
Arguments
- <paragraph> | --file <path>
- Required. The text to check.
- <selector>
- Optional:
--project, --study, --search, --from-file, or a bare PMID list, to scope retrieval.
- --export-bib
- Also write BibTeX and CSL-JSON for every PMID the report cites as evidence.
What happens
- Notes any
[^pmid] markers already in the paragraph: those citations must be verified, not just searched around.
- Retrieves candidates like /ref:ask (query expansion, then
ask_retrieve.py), and adds the active claims of any cited PMID that retrieval missed.
- The Claude session itself splits the paragraph and gives each assertion one verdict:
supported, overstated, conflicting, insufficient, or unavailable. An existing citation that doesn't back its sentence gets citation_mismatch: true. No subagent is used, since this verifies text rather than writing it.
- The script validates the findings: each
assertion_text must be an exact substring of your paragraph, every evidence PMID and claim ID must be in the candidate set, and unavailable must carry no evidence.
- Persists the paragraph verbatim, the findings, and verdict counts, and always prints the coverage caveat.
Reads & writes
Writes checks/<check_id>/{input.md, findings.json, manifest.json} (or under projects/<slug>/checks/), plus references.bib and references.csl.json with --export-bib. Check IDs are opaque (check-…); each check is a one-off snapshot.
Example
illustrative output
› /ref:check-citations "Cortical thickness is reduced in autistic adults [^10000001]. This causes social difficulties." --project thesis-ch3
{
"check_id": "check-…",
"manifest": {
"check_id": "check-…",
"project": "thesis-ch3",
"selector_expression": "--project thesis-ch3",
"referenced_pmids": ["10000001"],
"verdict_counts": {"supported": 1, "overstated": 0, "conflicting": 0, "insufficient": 0, "unavailable": 1},
"caveat": "Available library coverage does not establish a comprehensive literature check (§5a) -- an 'unavailable' or 'insufficient' verdict means this library doesn't have evidence either way, not that none exists.",
"bibliography_exported": false
},
"findings": [
{"assertion_text": "Cortical thickness is reduced in autistic adults", "verdict": "supported",
"evidence": [{"pmid": "10000001", "claim_id": "…"}]},
{"assertion_text": "This causes social difficulties.", "verdict": "unavailable", "evidence": []}
],
"caveat": "…"
}
Under the hood
ask_retrieve.py for candidates, then check_citations.py check --repo <root> [--project <slug>] --paragraph-file … --findings-file … --candidates-file … [--resolution-file …] [--export-bib]. check_citations.py show --id <check_id> reprints a saved check. Judgment is inline in the session; no subagent.
See also
/ref:ask · /ref:export · /ref:audit · Thesis chapter tutorial
Save a grounded answer to a recurring question with its evidence, and change it only when you ask for a refresh.
/ref:brief "<question>" --key <label> [--project <slug>] [<selector>] [--refresh]
/ref:brief --key <label> [--project <slug>] --show
/ref:brief --key <label> [--project <slug>] --edit "<text>"
Arguments
- <question>
- Required on first save.
- --key <label>
- Required. Your name for this standing question. Distinct from the opaque
snapshot_id minted on every save.
- --project <slug>
- Optional; omit for a library-wide brief.
- <selector>
- Optional, constrains retrieval as in
/ref:ask.
- --refresh
- Re-run retrieval and synthesis, save a new snapshot, report the diff.
- --show
- Print the current snapshot without running anything.
- --edit <text>
- Attach your revision to the current snapshot.
What happens
--show and --edit call the script directly and stop.
- Otherwise runs the same sequence as /ref:ask: query expansion,
ask_retrieve.py, one ref-synthesizer, then validate_citations.py.
- If a snapshot exists and
--refresh is absent, returns reused_frozen_brief. There is no scheduled monitoring: a brief never updates on its own.
- On refresh, reports
added_support_claim_ids, new_pmids, withdrawn_evidence (claims no longer active or rejected via /ref:verify), and retraction_status_changes. Deciding whether new evidence conflicts is left to the synthesis step; the script only reports what it can check deterministically.
- Your
--edit survives a refresh whose evidence is unchanged, and is marked "stale": true (kept, not discarded) when it changed.
Reads & writes
Writes projects/<slug>/briefs/<key>/{latest.json, edits.json, <snapshot_id>/{manifest.json, answer.md, evidence.json}}, or briefs/<key>/ at the library root. Old snapshots stay on disk.
Example
illustrative output
› /ref:brief --key thickness --project thesis-ch3 --refresh
{
"status": "refreshed",
"snapshot_id": "brief-…",
"manifest": {
"snapshot_id": "brief-…",
"key": "thickness",
"project": "thesis-ch3",
"question": "Is cortical thickness altered in autistic adults?",
"selector_expression": "<all>",
"pmids_at_resolution": null,
"unresolved_questions": ["…"],
"evidence_hash": "…"
},
"answer": "… [^pmid] …",
"added_support_claim_ids": ["…"],
"new_pmids": ["…"],
"withdrawn_evidence": [
{"claim_id": "…", "pmid": "…", "reason": "excluded_from_synthesis (rejected via /ref:verify)"}
],
"retraction_status_changes": [],
"user_edit": {"revision": "…", "based_on_evidence_hash": "…", "edited_at": "…", "stale": true}
}
Under the hood
brief.py save --repo <root> --key … [--project …] --question … --answer-file … --evidence-file … [--resolution-file …] [--unresolved …] [--refresh]; brief.py show; brief.py edit … --revision-file …. Subagent: ref-synthesizer.
See also
/ref:ask · /ref:summarize · /ref:verify · /ref:audit
/ref:cite
phase 2read-only
Print the stable @citekey for one paper while you write.
/ref:cite <pmid>
Arguments
- <pmid>
- Required. Must already be in the library.
What happens
- Reads
papers/<pmid>/meta.json and prints @ plus its citekey. The citekey was allocated at /ref:add and does not change, so it is safe to type into a manuscript.
Reads & writes
Reads meta.json. Writes nothing.
Example
output from a scratch library
› /ref:cite 10000001
@rivera2021cortical
› /ref:cite 99999999
error: pmid '99999999' not in library — add it first via /ref:add
Under the hood
cite.py --repo <root> --pmid <pmid>. No subagent.
See also
/ref:export · /ref:add
Export a selected set as BibTeX and CSL-JSON into a frozen, named batch.
/ref:export <selector> --batch <label> [--refresh]
Arguments
- <selector>
<pmid…>, --project [--question], --screened, --read/--queue, --query [--run], --search, --from-file, refined by --tier/--exclude.
- --batch <label>
- Required. Directory name under
exports/. Reused unless --refresh.
- --refresh
- Re-resolve, rewrite both files, and report
added/removed against the prior freeze.
What happens
- An existing batch without
--refresh returns reused_frozen_batch and doesn't re-resolve.
- Otherwise resolves the selector, builds CSL-JSON from
meta.json + authorship.json, and renders BibTeX from that CSL-JSON, so the two formats can't disagree.
- Writes both files and a manifest with the selector, PMIDs, citekeys, and resolution report.
Reads & writes
Writes exports/<batch>/{references.bib, references.csl.json, manifest.json} at the library root, even when the selector is --project.
Example
output from a scratch library
› /ref:export --project thesis-ch3 --batch ch3
{
"status": "created",
"batch": "ch3",
"manifest": {
"batch": "ch3",
"selector_expression": "--project thesis-ch3",
"pmids": ["10000001", "10000002"],
"citekeys": ["rivera2021cortical", "lindqvist2019surface"],
"resolved_at": "2026-09-15T10:14:30.552480+00:00",
"report": {
"count": 2,
"by_extraction_tier": {"abstract": 2, "full": 0, "unavailable": 0, "missing_record": 0},
"by_human_verification_state": {"not_yet_tracked": 2},
"by_retraction_errata_status": {"unknown": 2}
}
}
}
# exports/ch3/references.bib
@article{rivera2021cortical,
title = {Cortical thickness in autistic adults: a cohort study},
author = {Rivera, Ana and Okafor, Chidi},
journal = {Example Journal of Neuroimaging},
year = 2021,
doi = {10.0000/example.1},
pmid = {10000001}
}
# second entry trimmed. Running the same command again:
{
"status": "reused_frozen_batch",
"batch": "ch3",
# … same manifest
Under the hood
export.py --repo <root> --batch <label> [selector] [--refresh], using lib_cite.build_exports. No subagent.
See also
/ref:cite · /ref:export-papers · /ref:check-citations --export-bib
/ref:export-papers
phase 3Papers.app
Write selected papers, their PDFs, and optionally notes and tags into a folder ReadCube Papers imports from, without touching its database.
/ref:export-papers <selector> [--to <dir>] [--layout papers|flat] [--pdfs copy|link|none]
[--notes[=force]] [--tags <a,b>] [--tags-from <slug>]
[--skip-known] [--force] [--collection <name>]
[--dry-run] [--batch <label>]
Arguments
- <selector>
- Any §5c selector.
- --to <dir>
- Destination. Default:
papers_export_dir from ~/.config/ref-manager/config.json, else exports/papers/<batch>/ in the library (and the output says which applied).
- --layout papers|flat
papers writes <LastAuthor>/<Journal>-<Year>.pdf (default for the configured folder); flat writes <citekey>.pdf (default otherwise).
- --pdfs copy|link|none
- Default
copy. link symlinks; none omits PDFs and local-url.
- --notes[=force]
- Push
notes.md into the BibTeX note field. Refuses a paper whose note an earlier export already pushed; =force (script: --notes-force) overwrites.
- --tags <a,b> · --tags-from <slug>
- Fill
keywords with explicit tags, or with the project slug for papers in that project.
- --skip-known · --force
- With a readable Papers database, skip papers it already holds (matched by PMID or DOI);
--force exports them anyway.
- --collection <name>
- Recorded in the manifest only. No Papers collection is created.
- --dry-run
- Compute and print the plan; write nothing.
- --batch <label>
- Names the fallback
exports/papers/<batch>/ folder.
- --refresh
- Listed in the command file as "re-resolve even if a manifest exists"; the current script accepts it but always resolves live, so it has no effect.
What happens
- Looks for
~/Library/Application Support/Papers/*.db and passes the first match as --papers-db, read-only. If none is found or it can't be read, the export proceeds without duplicate detection; that is expected, not an error.
- Resolves the selector, then per paper builds CSL-JSON and adds abstract, volume, issue, pages, and ISSN from the stored raw record when present. Missing ones are listed under
field_omissions.
- Finds an acquired PDF (via
acquisitions.json, falling back to raw/*/source.pdf), allocates its destination path, and copies or links it. Papers without one appear in missing_pdfs.
- Never overwrites a file at the destination that an earlier ref-manager export didn't write. Such paths go into
foreign_conflicts; under the papers layout, name clashes get -2, -3 suffixes.
- Writes a Papers-dialect
references.bib (double-braced title and abstract, literal UTF-8, local-url), the manifest, and the written-path index. You then import references.bib through Papers' own UI; this command doesn't drive the app.
Reads & writes
Writes <dest>/{references.bib, manifest.json, .ref-manager-written.json} and the PDFs; records note pushes in exports/papers/note_pushes.json in the library. Reads the Papers database only for duplicates; never writes it, never opens the app.
Example
output from a scratch library
› /ref:export-papers --project thesis-ch3 --to ~/scratch/papers-out --dry-run
{
"dry_run": true,
"destination": "…/papers-out",
"manifest": {
"selector_expression": "--project thesis-ch3",
"pmids": ["10000001", "10000002"],
"citekeys": ["rivera2021cortical", "lindqvist2019surface"],
"destination": "…/papers-out",
"destination_reason": "--to",
"layout": "flat",
"pdfs_mode": "copy",
"per_paper": {
"10000001": {"citekey": "rivera2021cortical", "pdf_path": null, "already_known": false, "pushed_note_hash": null},
"10000002": {"citekey": "lindqvist2019surface", "pdf_path": null, "already_known": false, "pushed_note_hash": null}
},
"missing_pdfs": ["10000001", "10000002"],
"already_known": [],
"skipped_known": [],
"field_omissions": {
"10000001": ["volume", "issue", "pages", "issn"],
"10000002": ["volume", "issue", "pages", "issn"]
},
"foreign_conflicts": [],
"duplicate_detection_available": false,
"duplicate_detection_note": "no --papers-db given",
"collection": null
},
"note_statuses": {}
}
# timestamp trimmed; no PDFs because these papers were never fetched or attached
Under the hood
export_papers.py [selector] --repo <root> [--to …] [--layout …] [--pdfs …] [--notes] [--notes-force] [--tags …] [--tags-from …] [--skip-known] [--force] [--collection …] [--dry-run] [--refresh] [--batch …] [--papers-db …], with papers_snapshot.py for the read-only duplicate check. No subagent.
See also
/ref:export · /ref:attach · /ref:pull-annotations · /ref:open · Papers handoff tutorial