Tutorial
A PI publication report you can defend
This tutorial walks the PI acceptance path from PLAN.md §8. You'll build a researcher profile, confirm which author entries are really that person, link publications to a grant with evidence, list roles and coauthors, record dated citation observations, and export a reproducible report. Commands used: /ref:person, /ref:discover, /ref:verify, /ref:grant, /ref:weave, /ref:publications, /ref:audit --citations, and /ref:report.
The journey
(pmid, author_index) pair that you accepted, and each acceptance is logged in that paper's corrections.json.The researcher is lindqvist-maja, one coauthor is rivera-ana, and the grant is nimh-wm-r01. The PMIDs 38000101–38000103 and all titles are placeholders, already added with /ref:add. The ORCID is ORCID's public documentation example. Outputs labelled scratch library are real script output. In those runs, hand-written JSON stood in for the PubMed and PMC lookups.
Steps
-
Create the researcher profile
Person IDs are slugs you choose, and they're library-wide. The name you give becomes the first name variant.
output from a scratch library
› /ref:person create lindqvist-maja --name "Lindqvist, Maja" --orcid 0000-0002-1825-0097 { "slug": "lindqvist-maja", "name_variants": ["Lindqvist, Maja"], "orcid": "0000-0002-1825-0097", "affiliations": [], "confirmed_publications": [], "candidate_publications": [], "rejected_publications": [] } › /ref:person create lindqvist-maja --name "Lindqvist, M" error: person 'lindqvist-maja' already exists at people/lindqvist-maja.json
Name variants: current limit./ref:personimplements onlycreate,showandlist. There's no subcommand for adding variants or affiliations after creation, soname_variantsholds only the name you gave at create time. Duplicate slugs are refused rather than suffixed. If a researcher publishes under several forms, tell Claude which ones to use when it builds the discovery query in step 2. That query is saved exactly as run. -
Discover candidate publications
Claude builds a PubMed author query from the profile, runs it, and saves the exact query, the retrieval time and the candidates onto the person record. Candidates don't count as publications yet.
output from a scratch library (trimmed)
› /ref:discover --person lindqvist-maja { "candidate_publications": [ { "pmid": "38000101", "title": "Transcranial stimulation and working memory in adults with ADHD: a randomized trial" }, { "pmid": "38000102", "title": "Working memory training outcomes: a cohort study" }, { "pmid": "38000103", "title": "Stimulation effects on attention in children" }, { "pmid": "37999001", "title": "Lindqvist M. Soil microbiome diversity in boreal forests" } ], "confirmed_publications": [], … "discovery_runs": [ { "query": "\"Lindqvist M\"[Author] OR 0000-0002-1825-0097[auid]", "retrieved_at": "2026-09-15T10:16:49.875997+00:00", "candidate_count": 4 } ] }
--grantdiscovery isn't implemented. Grant records have no candidate fields yet, so the script refuses--grantand asks for--person. -
Confirm, or reject, each match
A match links the person to one position in the author list.
author_indexis zero-based and refers to the order preserved at ingest. Accepting a match moves the candidate toconfirmed_publications. Rejecting it keeps the PMID inrejected_publications, so later discovery runs don't suggest it again.output from a scratch library (trimmed)
› /ref:verify person-identity 38000101 lindqvist-maja 2 accept --rationale "affiliation and ORCID match" --reviewer maja { "correction_id": "cor-0ccfa93fd6c0", "target_type": "person_identity", "target_id": "lindqvist-maja:38000101:2", "decision": "accept", … "evidence_locator": "authorship.json#author[2]", "status": "active" } › /ref:verify person-identity 38000102 lindqvist-maja 0 accept --reviewer maja › /ref:verify person-identity 38000103 lindqvist-maja 1 accept --reviewer maja › /ref:verify person-identity 37999001 lindqvist-maja 0 reject --rationale "different researcher (soil science)" --reviewer maja # the coauthor, so coauthor rows can use a confirmed identity › /ref:verify person-identity 38000101 rivera-ana 0 accept --reviewer maja
Contribution flags are separate, and position alone never sets them:
output from a scratch library
› /ref:verify author-contribution 38000101 2 corresponding error: author_contribution flag 'corresponding' refused: an explicit evidence statement is required, never inferred from author position (§3c) › /ref:verify author-contribution 38000101 2 corresponding --evidence "Correspondence: Maja Lindqvist" --reviewer maja { "author_index": 2, "flag": "corresponding", "evidence_statement": "Correspondence: Maja Lindqvist", "reviewer": "maja", "timestamp": "2026-09-15T10:16:51.083767+00:00" }
-
Record the grant, its aliases, and evidenced links
PubMed often reports one award in several spellings. Keep each spelling as an approved alias. Linking a publication is a separate, reviewed decision that records where the evidence came from.
output from a scratch library (trimmed)
› /ref:grant create nimh-wm-r01 --funder "NIMH" --award "R01 MH123456" --title "Neuromodulation of working memory" --pi lindqvist-maja { "slug": "nimh-wm-r01", "funder": "NIMH", "award_number": "R01 MH123456", "approved_aliases": ["R01 MH123456"], "title": "Neuromodulation of working memory", "pi": "lindqvist-maja", "aims": null, "publication_links": [] } › /ref:grant add-alias nimh-wm-r01 --alias "R01MH123456" { … "approved_aliases": ["R01 MH123456", "R01MH123456"], … } › /ref:verify grant-link 38000101 nimh-wm-r01 accept --evidence-locator "PubMed GrantList: R01 MH123456/MH/NIMH NIH HHS" --award-number "R01 MH123456" --reviewer maja { "pmid": "38000101", "award_number": "R01 MH123456", "match_method": "manual_review", "evidence_locator": "PubMed GrantList: R01 MH123456/MH/NIMH NIH HHS", "decision": "accept", "reviewer": "maja", "rationale": "", "timestamp": "2026-09-15T10:16:51.730855+00:00" } › /ref:verify grant-link 38000102 nimh-wm-r01 accept --evidence-locator "PubMed GrantList: R01MH123456/MH/NIMH NIH HHS" --award-number "R01MH123456" --reviewer maja
Why links never add aliases. Folding a new award string into a grant automatically could merge two genuinely different awards from the same funder.grant-linkrecords the link and nothing else. Adding an alias is always a separate, deliberateadd-alias.To see these decisions as graph edges, regenerate the derived views:
output from a scratch library (people graph part only)
› /ref:weave --regenerate-only # … okf_emit output omitted … { "researcher_authored_publication": 4, "publication_acknowledges_grant": 2, "publication_supports_aim": 0, "researcher_lab_membership": 0, "publication_supports_aim_note": "unpopulated -- no mechanism in any phase links a publication to a specific grant aim (§5a)", "researcher_lab_membership_note": "unpopulated -- no /ref:lab command exists in PLAN.md; labs/<slug>.json has no writer yet" }
publication_acknowledges_grantedges come from exact matches between a paper'sfunding.jsonaward strings and the grant's award number or approved aliases. Here, the second paper matches only because of the alias. -
List authorship roles
Roles are computed from the confirmed index and the preserved author list. Filter with
--role sole|first|last|middle|unresolved.output from a scratch library
› /ref:publications --person lindqvist-maja [ { "pmid": "38000101", "author_index": 2, "role": "last", "author_count": 3 }, { "pmid": "38000102", "author_index": 0, "role": "sole", "author_count": 1 }, { "pmid": "38000103", "author_index": 1, "role": "middle", "author_count": 4 } ] › /ref:publications --person lindqvist-maja --role last [ { "pmid": "38000101", "author_index": 2, "role": "last", "author_count": 3 } ]
mutually exclusive reporting role no confident role evidence-only flagThe four roles partition a verified, complete author list, and unresolvedcatches everything else. Flags answer a different question and can coexist with any role. -
Derive a coauthor declaration
Use this for NSF Collaborators and Other Affiliations or NIH conflict forms. You get one row per distinct coauthor across the window, each with its identity method and the PMIDs that establish it.
output from a scratch library
› /ref:publications --person lindqvist-maja --coauthors --since 2023 --to 2024 { "person": "lindqvist-maja", "since": "2023", "until": "2024", "coauthors": [ { "name": "ADHD Imaging Consortium", "identity_method": "exact_name", "affiliation": null, "most_recent_pub": "2024", "pmids": ["38000103"] }, { "name": "Chen W", "identity_method": "exact_name", "affiliation": null, "most_recent_pub": "2024", "pmids": ["38000103"] }, { "name": "Okafor C", "identity_method": "exact_name", "affiliation": null, "most_recent_pub": "2023", "pmids": ["38000101"] }, { "name": "Rivera A", "identity_method": "confirmed_identity", "affiliation": null, "most_recent_pub": "2023", "pmids": ["38000101"] }, { "name": "Rivera A", "identity_method": "exact_name", "affiliation": null, "most_recent_pub": "2024", "pmids": ["38000103"] } ], "unconfirmed_candidates": [], "gaps": [], "scope_note": "derived from library holdings, not a complete publication record (§3c)" }
Why "Rivera A" appears twice. On38000101that author position is a confirmed identity,rivera-ana. On38000103it only matches by published name. The two cases are kept apart rather than merged. Confirm the second position, and the rows collapse into one confirmed identity.Gaps and group authors depend on flags inauthorship.json. A paper lands ingapswhen itsauthorship.jsonhas"complete": false. A group is reported as a group when its author entry has"is_group": true./ref:addwrites neither flag, and no current command sets them. In this scratch library the consortium is therefore listed by exact name, andgapsis empty. Check truncated or consortium author lists yourself before you submit a declaration. -
Record dated citation observations
/ref:audit --citationsappends one dated observation per paper tocitations.json. It never overwrites an earlier one. A paper without a PMCID gets ano_pmcidmarker. A failed lookup getscheck_failed, never a zero.No cited-by source is wired in yet. None of the PubMed MCP tools in this environment return a PMC cited-by count.find_related_articlesmeasures similarity, not citations. The persistence machinery is complete and tested. Until a real PMC ELink source is available, Claude should tell you there's nothing to record rather than invent counts. The output below comes from observations we supplied by hand.output from a scratch library (hand-supplied observations)
› /ref:audit 38000101 38000102 38000103 --citations [ { "pmid": "38000101", "entry": { "source": "pmc_elink", "query": "elink.fcgi?dbfrom=pmc&linkname=pmc_pmc_citedby&id=PMC0000101", "count": 14, "coverage": "citing articles indexed in PMC; not a total citation count", "retrieved_at": "2026-09-15T10:16:52.063180+00:00" }, "total_observations": 1 }, { "pmid": "38000102", "entry": { "retrieved_at": "2026-09-15T10:16:52.065402+00:00", "status": "no_pmcid", "reason": "paper has no PMCID" }, "total_observations": 1 }, { "pmid": "38000103", "entry": { "retrieved_at": "2026-09-15T10:16:52.066812+00:00", "status": "check_failed", "reason": "ELink request timed out" }, "total_observations": 1 } ] › /ref:audit show-citations 38000101 38000102 38000103 [ { "pmid": "38000101", "latest_observation": { "count": 14, … "source": "pmc_elink" }, "stale": false }, { "pmid": "38000102", "latest_observation": null, "stale": null }, { "pmid": "38000103", "latest_observation": null, "stale": null } ]
Each result record must carry exactly one of
observation,no_pmcidorerror. Anything else is rejected as malformed:output from a scratch library
error: citations result for pmid '38000101' has none of 'observation'/'no_pmcid'/'error' -- malformed input, not a real failed lookup -
Generate the report
The report covers confirmed publications only, within a year window. It writes CSV and Markdown plus a manifest of the policies used. Rerun it with unchanged data, and the CSV and Markdown come out identical. Only
generated_atin the manifest changes.output from a scratch library
› /ref:report --person lindqvist-maja --from 2023 --to 2024 --citations { "person": "lindqvist-maja", "from": "2023", "to": "2024", "date_basis": "publication_year", "missing_date_handling": "excluded", "identity_review_policy": "confirmed_publications_only", "dedup_policy": "unique_by_pmid", "publication_count": 3, "generated_at": "2026-09-15T10:16:52.380729+00:00", "includes_citations": true, "citation_stale_days": 180, "citation_coverage_note": "citing articles indexed in PMC; not a total citation count (D25)" }
reports/lindqvist-maja-2023-2024/report.md:# Publication report — lindqvist-maja Scope: confirmed publications in this library, 2023–2024 (`publication_year`). PubMed-only coverage; portfolio completeness not reviewed (§3c). Citation counts below are **PMC-indexed citing-article counts (D25)** — not total citations, and not comparable to Scopus/Web of Science. Each count names its source and retrieval date; `unknown` means no observation has been recorded, never zero. | PMID | Year | Role | Title | Citing articles (PMC) | Source | Retrieved | Stale | |---|---|---|---|---|---|---|---| | 38000101 | 2023 | last | Transcranial stimulation and working memory in adults with ADHD: a randomized trial | 14 | pmc_elink | 2026-09-15 | no | | 38000102 | 2024 | sole | Working memory training outcomes: a cohort study | unknown | — | — | — | | 38000103 | 2024 | middle | Stimulation effects on attention in children | unknown | — | — | — | Total unique publications: 3
The report can only state what reviewed records establish. Its scope line says "confirmed publications in this library", and every citation figure carries its source and retrieval date. Not yet the full acceptance test.PLAN.md§8 asks for an annual report grouped by grant and authorship role. The implemented/ref:reportis the basic slice: one person and one year window, with the role as a column. It has no--rolefilter and no grant or lab grouping. For per-grant views, use thepublication_linksongrants/nimh-wm-r01.jsonand thepublication_acknowledges_grantedges from step 4.
What you now have
<LIBRARY>/
people/
lindqvist-maja.json # variants, ORCID, confirmed / rejected, discovery_runs
rivera-ana.json
grants/
nimh-wm-r01.json # award, approved_aliases, publication_links with evidence locators
papers/38000101/ … 38000103/
authorship.json # preserved order + contribution_flags
corrections.json # person_identity, grant_link, author_contribution decisions
citations.json # append-only dated observations and markers
papers/37999001/corrections.json # the rejected match, kept for audit
graph/
people_relations.jsonl # researcher_authored_publication, publication_acknowledges_grant
reports/lindqvist-maja-2023-2024/
publications.csv · report.md · manifest.json
Command reference: /ref:person, /ref:publications, /ref:report, /ref:verify, /ref:audit.