Systematic Reviews skill for Claude Code

Protocol first, PRISMA counts that actually add up. Claude writes a registrable protocol before any screening, builds PRESS-checked search strategies, runs dual independent screening with kappa checks, and extracts data with page-level locators. A bundled script computes PRISMA 2020 flow counts from your screening log and flags missing reasons or impossible decisions, so the diagram, bias tables and GRADE ratings stay consistent.

  • namesystematic-reviews
  • size178 lines · ≈2,200 tokens
  • files3
  • licenseMIT
  • updated

What Claude sees before loading it

This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.

description: Plans and runs PRISMA 2020 systematic reviews and scoping reviews — registered protocol, eligibility criteria, reproducible multi-database searches, dual independent screening, piloted data extraction forms, risk-of-bias assessment with RoB 2, ROBINS-I, or QUADAS-2, GRADE certainty, and a computed PRISMA flow diagram. Use when the user mentions a systematic review, meta-analysis, scoping review, PRISMA, PROSPERO, Cochrane-style evidence synthesis, risk of bias, or needs an exhaustive and auditable answer to a focused research question.

Try asking

  • Draft a PROSPERO-ready protocol for a systematic review of exercise therapy for chronic low back pain
  • Compute the PRISMA flow diagram numbers from my Rayyan screening export
  • Which risk-of-bias tool should I use for these cohort studies and how do I record it?

Use it when

  • You are planning or running a systematic or scoping review
  • You need PRISMA 2020 flow numbers and checklist items
  • You must assess risk of bias or GRADE certainty
  • A reviewer asked for an auditable search and screening record

Skip it when

  • A narrative related-work section is enough — use literature-review
  • You are appraising a single paper rather than a body of evidence — use paper-analysis

How to check the result

prisma_flow.py exits 0 on the screening log, every pooled number traces to an extraction cell with a page locator, and each included study ID resolves.

The common mistake it prevents

Counting multiple reports of one trial as separate studies, which double-counts participants and inflates certainty.

~/.claude/skills/systematic-reviews/SKILL.md

--- name: systematic-reviews description: Plans and runs PRISMA 2020 systematic reviews and scoping reviews — registered protocol, eligibility criteria, reproducible multi-database… license: MIT ---

Done well means: the protocol was fixed before screening, any deviation is reported, every record can be traced from search hit to final decision with a reason, two people (or one person plus a documented check) made each eligibility and bias judgment, the PRISMA counts add up, and conclusions match the GRADE certainty.

Hard rules

  • Never report a study you have not retrieved and read. Every included study has a verified DOI/PMID/registry ID. Never fabricate counts, effect sizes, or bias judgments; leave cells empty and flag them.
  • If the full search cannot actually be executed (no database access), produce the protocol, search strategy, and forms, and say clearly that searching is pending.
  • Write the protocol before screening; changes after that are “deviations” and are reported with reasons.
  • An AI-assisted screen is a single screener at most. State its role in the methods, and have a human verify all exclusions or a validated random sample.

Review type

QuestionTypeReporting guideline
Does X improve Y? Effect size wantedSystematic review +/- meta-analysisPRISMA 2020
What evidence exists on a broad topic?Scoping reviewPRISMA-ScR, JBI guidance
Synthesis of existing reviewsUmbrella reviewPRIOR
Diagnostic accuracyDTA reviewPRISMA-DTA
Time-limited decision supportRapid reviewCochrane rapid review guidance; report shortcuts

Workflow

- [ ] 1. Protocol: question, criteria, outcomes, analysis plan; register
- [ ] 2. Search strategy: peer-review it (PRESS), run, export, log (PRISMA-S)
- [ ] 3. Deduplicate; keep counts
- [ ] 4. Title/abstract screening, dual and independent; pilot first
- [ ] 5. Full-text screening with recorded exclusion reasons
- [ ] 6. Data extraction with a piloted form
- [ ] 7. Risk of bias per study per outcome
- [ ] 8. Synthesis: meta-analysis or structured narrative (SWiM)
- [ ] 9. Certainty of evidence (GRADE)
- [ ] 10. PRISMA flow + checklist; write up

1. Protocol

Fill this template and register it (PROSPERO for health outcomes; OSF Registries for anything else; scoping reviews usually on OSF):

Title / review type
Question (PICO): Population | Intervention/Exposure | Comparator | Outcomes (primary, secondary) | Study designs | Setting
Eligibility: include ... / exclude ... (designs, languages, dates, publication status)
Information sources: databases with platforms, registers, grey literature, citation chasing, expert contact
Search strategy: full string for one database (others in appendix)
Selection process: two independent reviewers, pilot on 50-100 records, conflict resolution
Data items: list of variables extracted, outcome definitions and time points
Risk of bias tool(s): per design
Effect measures: RR/OR/MD/SMD; how converted
Synthesis: criteria for pooling, model (random effects), heterogeneity, subgroup and sensitivity analyses
Reporting bias assessment; certainty assessment (GRADE)
Timeline, funding, conflicts
  • Minimum for health: MEDLINE, Embase, CENTRAL, plus a trials register (ClinicalTrials.gov, WHO ICTRP). Other fields: the 2-4 core discipline databases plus Scopus or Web of Science.
  • Combine controlled vocabulary and free text per concept; validate against known included studies; have a second person check it with the PRESS checklist.
  • Log database, platform, date, exact string, limits, and hits for each run. Export full records (RIS) with abstracts.
  • Grey literature: theses, preprints, conference abstracts, regulatory documents. Decide in the protocol, not after seeing results.
  • For database syntax, see the literature-review skill’s search-syntax reference.

3-5. Screening

  • Tools: Rayyan, Covidence, ASReview (active learning), or a spreadsheet with locked columns. Blind reviewers to each other’s decisions.
  • Pilot on 50-100 records; compute agreement; refine criteria wording, not the criteria themselves (that would be a deviation).
  • Report inter-rater agreement (Cohen’s kappa) for title/abstract screening. Kappa below 0.6 after the pilot means the criteria are ambiguous: clarify and re-pilot.
  • Full-text exclusions need one reason each, from a fixed ordered list (first failing criterion): wrong population > wrong intervention > wrong comparator > wrong outcome > wrong design > duplicate report > not retrievable.
  • Track multiple reports of the same study (linked by registry ID or author/sample); PRISMA counts studies and reports separately.

Keep a screening log CSV and compute the flow counts from it, never by hand:

python3 scripts/prisma_flow.py screening.csv          # counts + consistency errors, exit 1 if any
python3 scripts/prisma_flow.py screening.csv --json   # for plotting tools

Columns: record_id, source, duplicate, auto_excluded, title_abstract, retrieved, full_text, exclusion_reason, study_id. Sources prefixed other: (citation searching, websites) follow the “other methods” column of the PRISMA 2020 diagram. Paste the counts into the PRISMA 2020 flow diagram template or the PRISMA2020 R package / Shiny app.

6. Data extraction

Build the form from the protocol’s data items, pilot it on 3-5 studies with two extractors, then freeze it.

Study ID | Report(s) | Registry ID | Country/setting | Design | Funding/COI
Population: N randomized/analysed per arm, inclusion criteria, baseline characteristics
Intervention / comparator: dose, duration, delivery, co-interventions
Outcomes: definition, measure, time point, effect estimate, 95% CI or SD, N per arm
Analysis: ITT/per-protocol, adjustment variables, missing-data handling
Notes: discrepancies between registry and publication; author contact log
Source locator for every number: page / table / figure
  • Extract the outcome result closest to the protocol’s time point and definition; record the rule used when several are available.
  • Prefer the registry or clinical study report when it conflicts with the article; record the discrepancy.
  • Double extraction for outcome data; single extraction with verification for descriptive items is acceptable if stated.

7. Risk of bias

Pick the tool by design (RoB 2 for randomized trials, ROBINS-I for non-randomized interventions, QUADAS-2 for diagnostic accuracy). Assess per outcome, two reviewers, with a supporting quote for each domain judgment. For the full tool table, domains, the recording format, and GRADE rating rules, read references/risk-of-bias.md.

8. Synthesis

ConditionApproach
>= 2 studies, same outcome construct, compatible measuresMeta-analysis, random effects; report pooled estimate, 95% CI, tau^2, I^2, prediction interval
Same construct, different scalesStandardized mean difference (Hedges’ g)
Clinically or methodologically incompatibleStructured narrative synthesis following SWiM; vote counting by direction of effect only, not by significance
Scoping reviewCharting: frequencies and evidence map; no pooled effects, no bias assessment by default

Pre-specified sensitivity analyses: exclude high risk-of-bias studies; fixed vs. random effects; imputed vs. observed SDs. Assess small-study effects with funnel plots only when there are 10 or more studies.

9-10. Certainty and reporting

  • GRADE each critical outcome; present a Summary of Findings table.
  • Complete the 27-item PRISMA 2020 checklist with page numbers; include the flow diagram, full search strategies for every database (PRISMA-S), the list of excluded full texts with reasons, and the protocol registration number.
  • Use certainty-matched language: “probably reduces” (moderate), “may reduce” (low).

Verification

  • prisma_flow.py exits 0; identification minus removals equals screened; assessed minus excluded equals included reports.
  • A random 10% of exclusions re-screened by a second reviewer agree, or disagreements are resolved and the criteria clarified.
  • Every number in the results traces to an extraction cell with a page/table locator.
  • Every included study’s identifier resolves; check retraction status before submission.

Gotchas

  • Changing eligibility criteria after seeing results without reporting it is the most common integrity problem in published reviews.
  • Counting reports as studies inflates the evidence base and double-counts participants.
  • Searching only PubMed or only Google Scholar is not systematic.
  • Language and date limits must be justified in the protocol; “English only” is a known source of bias.
  • Searches older than 12 months at submission usually need an update run.

Bundled files

Loaded only when a step in the skill needs them. Review them here before installing.

references/risk-of-bias.md 97 lines · 4.8 KB
# Risk of bias and certainty: tool selection and domains

Read this at the bias-assessment and certainty steps. Use the official tool guidance
documents for signalling questions; this file summarizes structure so you pick the right
tool and record judgments consistently. Do not reproduce tool forms verbatim in outputs;
cite the tool.

## Tool selection

| Included design | Tool | Judgment levels |
|---|---|---|
| Randomized trials (parallel, cluster, crossover) | Cochrane RoB 2 (variants for cluster and crossover) | Low / Some concerns / High |
| Non-randomized studies of interventions | ROBINS-I (version 2 released 2025; check which version the protocol names) | Low / Moderate / Serious / Critical (V2 adds algorithm-suggested judgments) |
| Non-randomized studies of exposures | ROBINS-E | Low / Some concerns / High / Very high |
| Diagnostic accuracy | QUADAS-2 (QUADAS-C for comparative) | Low / High / Unclear, plus applicability |
| Prognostic factor studies | QUIPS | Low / Moderate / High |
| Prediction model studies | PROBAST | Low / High / Unclear |
| Qualitative studies | CASP qualitative checklist or JBI checklist | Methodological limitations (feeds GRADE-CERQual) |
| Systematic reviews (umbrella review) | AMSTAR 2 or ROBIS | Confidence: High / Moderate / Low / Critically low |
| Animal studies | SYRCLE RoB | Low / High / Unclear |
| Cross-sectional / prevalence | JBI prevalence checklist | Checklist items, summarized |

## RoB 2 domains (randomized trials)

1. Bias arising from the randomization process
2. Bias due to deviations from intended interventions (specify: effect of assignment
   [ITT] or effect of adhering)
3. Bias due to missing outcome data
4. Bias in measurement of the outcome
5. Bias in selection of the reported result

Overall: Low only if all domains are low; High if any domain is high or several have
some concerns that together substantially lower confidence. Assess per outcome, not per
study.

## ROBINS-I domains (non-randomized interventions)

Pre-intervention: confounding; selection of participants. At intervention:
classification of interventions. Post-intervention: deviations from intended
interventions; missing data; measurement of outcomes; selection of the reported result.
Requires a pre-specified list of important confounders and a "target trial" description
in the protocol. Version 2 also addresses immortal time bias explicitly.

## QUADAS-2 domains (diagnostic accuracy)

Patient selection; index test; reference standard; flow and timing. Each has risk of
bias; the first three also have applicability concerns.

## Recording format

One row per study per outcome. Every judgment needs a supporting quote or location.

```markdown
| Study | Outcome | D1 | D2 | D3 | D4 | D5 | Overall | Support (quotes / page refs) |
|-------|---------|----|----|----|----|----|---------|------------------------------|
| Lee 2021 | Pain at 12 wk | Low | Some | Low | High | Low | High | D4: unblinded self-report (p.5); D2: 18% crossover (Table 2) |
```

Two reviewers assess independently; record disagreements and how they were resolved.
Visualize with robvis (R package or web app) as traffic-light and summary plots.

## GRADE certainty of evidence

Start: randomized trials at High; observational studies at Low (with ROBINS-I, start at
High and let the tool's judgments rate down).

Rate down (one or two levels each):
- Risk of bias
- Inconsistency (unexplained heterogeneity; I^2 high, non-overlapping CIs)
- Indirectness (population, intervention, comparator or outcome differ from the question)
- Imprecision (CI crosses decision thresholds; optimal information size not met)
- Publication bias (small-study effects, funnel asymmetry with >= 10 studies, industry-only data)

Rate up (observational evidence without serious limitations):
- Large magnitude of effect
- Dose-response gradient
- All plausible residual confounding would reduce the observed effect

Final levels: High, Moderate, Low, Very low. Present in a Summary of Findings table:

```markdown
| Outcome | No. studies (participants) | Relative effect (95% CI) | Absolute effect | Certainty | Reasons |
|---------|----------------------------|---------------------------|-----------------|-----------|---------|
| Mortality 30 d | 6 RCTs (2,140) | RR 0.82 (0.69-0.97) | 21 fewer per 1,000 | Moderate | Imprecision (-1) |
```

Plain-language statements by certainty: High "reduces", Moderate "probably reduces",
Low "may reduce", Very low "the evidence is very uncertain about the effect".

## Meta-analysis checks

- Random-effects by default when studies differ clinically; report tau^2 and a
  prediction interval, not just I^2.
- Do not pool when designs, outcomes, or measures are incompatible; use structured
  narrative synthesis (SWiM reporting guideline) instead.
- Funnel plots and Egger's test only with 10 or more studies.
- Pre-specify subgroup and sensitivity analyses (e.g., excluding high risk-of-bias studies).
scripts/prisma_flow.py 184 lines · 7.6 KB
#!/usr/bin/env python3
"""Compute PRISMA 2020 flow-diagram counts from a screening log CSV and check it
for inconsistencies (missing decisions, decisions after exclusion, full-text
exclusions without a reason).

Usage: python3 prisma_flow.py screening.csv [--json]

Expected columns (header row required, case-insensitive):
  record_id         unique id per record
  source            database or register name (e.g. "PubMed", "register:CT.gov"),
                    or "other:<method>" (citation searching, websites, experts)
  duplicate         yes/no  - removed as duplicate before screening
  auto_excluded     yes/no  - optional; marked ineligible by automation tools
  title_abstract    include/exclude (blank for other-method records)
  retrieved         yes/no  - full text obtained (for records passing screening)
  full_text         include/exclude
  exclusion_reason  required when full_text = exclude
  study_id          optional; groups multiple reports of one study

Exit status: 0 consistent, 1 inconsistencies found, 2 usage error.
"""
from __future__ import annotations

import csv
import json
import sys
from collections import Counter

YES = {"yes", "y", "true", "1"}
NO = {"no", "n", "false", "0", ""}


def norm(v: str | None) -> str:
    return (v or "").strip().lower()


def flow(rows: list[dict]) -> tuple[dict, list[str]]:
    errs: list[str] = []
    ids = Counter(norm(r.get("record_id")) for r in rows)
    for rid, c in ids.items():
        if c > 1:
            errs.append(f"record_id '{rid}' appears {c} times")

    def pathway(r: dict) -> str:
        return "other" if norm(r.get("source")).startswith("other") else "db"

    out = {p: Counter() for p in ("db", "other")}
    by_source: Counter = Counter()
    reasons = {"db": Counter(), "other": Counter()}
    included_studies: dict[str, set] = {"db": set(), "other": set()}

    for r in rows:
        rid = r.get("record_id", "?")
        p = pathway(r)
        c = out[p]
        c["identified"] += 1
        by_source[r.get("source", "").strip() or "(blank)"] += 1
        dup, auto = norm(r.get("duplicate")), norm(r.get("auto_excluded"))
        ta, ret, ft = norm(r.get("title_abstract")), norm(r.get("retrieved")), norm(r.get("full_text"))
        for col, val, allowed in (("duplicate", dup, YES | NO), ("auto_excluded", auto, YES | NO),
                                  ("retrieved", ret, YES | NO),
                                  ("title_abstract", ta, {"include", "exclude", ""}),
                                  ("full_text", ft, {"include", "exclude", ""})):
            if val not in allowed:
                errs.append(f"{rid}: invalid {col} value '{val}'")
        if dup in YES:
            c["removed_duplicates"] += 1
            if ta or ft:
                errs.append(f"{rid}: duplicate but has screening decisions")
            continue
        if auto in YES:
            c["removed_automation"] += 1
            if ta or ft:
                errs.append(f"{rid}: automation-excluded but has screening decisions")
            continue
        if p == "db":
            c["screened"] += 1
            if not ta:
                errs.append(f"{rid}: missing title_abstract decision")
                continue
            if ta == "exclude":
                c["excluded_screening"] += 1
                if ret in YES or ft:
                    errs.append(f"{rid}: excluded at screening but has full-text data")
                continue
        c["sought"] += 1
        if ret not in YES:
            c["not_retrieved"] += 1
            if ft:
                errs.append(f"{rid}: full_text decision without retrieved=yes")
            continue
        c["assessed"] += 1
        if not ft:
            errs.append(f"{rid}: retrieved but missing full_text decision")
            continue
        if ft == "exclude":
            reason = (r.get("exclusion_reason") or "").strip()
            if not reason:
                errs.append(f"{rid}: full-text exclusion without exclusion_reason")
                reason = "(no reason recorded)"
            reasons[p][reason] += 1
            continue
        c["included_reports"] += 1
        included_studies[p].add((r.get("study_id") or "").strip() or f"rec:{rid}")

    for p in out:
        out[p]["included_studies_pathway"] = len(included_studies[p])
    total_studies = len(included_studies["db"] | included_studies["other"])
    total_reports = out["db"]["included_reports"] + out["other"]["included_reports"]
    result = {
        "identified_by_source": dict(by_source),
        "databases_registers": dict(out["db"]),
        "other_methods": dict(out["other"]),
        "full_text_exclusion_reasons": {k: dict(v) for k, v in reasons.items()},
        "total_included_studies": total_studies,
        "total_included_reports": total_reports,
    }
    return result, errs


def render(res: dict) -> str:
    d, o = res["databases_registers"], res["other_methods"]
    g = lambda c, k: c.get(k, 0)
    lines = ["PRISMA 2020 flow counts", "", "IDENTIFICATION"]
    for s, n in sorted(res["identified_by_source"].items()):
        lines.append(f"  {s}: n = {n}")
    lines += [
        f"  Records identified (databases/registers): n = {g(d, 'identified')}",
        f"  Records removed before screening: duplicates n = {g(d, 'removed_duplicates')}, "
        f"automation n = {g(d, 'removed_automation')}",
        f"  Records identified (other methods): n = {g(o, 'identified')}"
        f" (duplicates removed n = {g(o, 'removed_duplicates')})",
        "", "SCREENING (databases/registers | other methods)",
        f"  Records screened: n = {g(d, 'screened')}",
        f"  Records excluded: n = {g(d, 'excluded_screening')}",
        f"  Reports sought for retrieval: n = {g(d, 'sought')} | n = {g(o, 'sought')}",
        f"  Reports not retrieved: n = {g(d, 'not_retrieved')} | n = {g(o, 'not_retrieved')}",
        f"  Reports assessed for eligibility: n = {g(d, 'assessed')} | n = {g(o, 'assessed')}",
    ]
    for p, label in (("db", "databases/registers"), ("other", "other methods")):
        rs = res["full_text_exclusion_reasons"][p]
        if rs:
            lines.append(f"  Reports excluded ({label}): n = {sum(rs.values())}")
            for reason, n in sorted(rs.items(), key=lambda x: -x[1]):
                lines.append(f"    {reason}: n = {n}")
    lines += ["", "INCLUDED",
              f"  Studies included in review: n = {res['total_included_studies']}",
              f"  Reports of included studies: n = {res['total_included_reports']}"]
    return "\n".join(lines)


def main(argv: list[str]) -> int:
    args = [a for a in argv if not a.startswith("--")]
    if len(args) != 1:
        print(__doc__.strip(), file=sys.stderr)
        return 2
    try:
        with open(args[0], newline="", encoding="utf-8-sig") as fh:
            reader = csv.DictReader(fh)
            reader.fieldnames = [f.strip().lower() for f in (reader.fieldnames or [])]
            rows = list(reader)
    except OSError as exc:
        print(f"error: {exc}", file=sys.stderr)
        return 2
    missing = {"record_id", "source", "duplicate", "title_abstract", "retrieved", "full_text"} - set(reader.fieldnames)
    if missing:
        print(f"error: missing columns: {', '.join(sorted(missing))}", file=sys.stderr)
        return 2
    res, errs = flow(rows)
    if "--json" in argv:
        print(json.dumps({**res, "errors": errs}, indent=2))
    else:
        print(render(res))
        if errs:
            print(f"\n{len(errs)} inconsistency(ies):")
            for e in errs:
                print(f"  - {e}")
        else:
            print("\nLog is internally consistent.")
    return 1 if errs else 0


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))

About this skill

Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.

esc