Imported from mslshao/claude-harness (
dotclaude/skills/pr-intel/SKILL.md). Install upstream withnpx skills add mslshao/claude-harness --skill pr-intel. Copyright stays with the author.
PR Intel
Produce an actionable PR intelligence briefing for a human reviewer. Gather context, dispatch specialist agents for deep analysis, synthesize findings, and present draft review comments the reviewer can directly use on GitHub.
Reviewer Context
Raw invocation: /pr-intel $ARGUMENTS
Parse the raw invocation above to extract:
- PR number: first numeric token (e.g.,
7640), or a GitHub PR URL - Mode flags:
- neither (default): full analysis, one-shot briefing. The dead multi-phase
@claudeverify loop is retired (PR #9888 removed the self-hosted bot). An unverified falsifiable or cross-system assertion is contained as an UNVERIFIED-ASSERTION finding (synthesis.md "Unverified-Assertion Containment + Cross-System Investigation"), never a posted bot question. --once: identical to default (both are one-shot now). Retained as an explicit alias for callers that previously used it to opt out of the post-and-wait loop.--mine(self-review): one-shot. An unverified falsifiable or cross-system claim surfaces as a pre-submission item to check (Verdict "Needs work first"), not a bot question.--quick(triage only): one-shot, no specialist dispatch, no loop.
- neither (default): full analysis, one-shot briefing. The dead multi-phase
- Reviewer context: everything else is free-text instructions from the reviewer. Use this to steer your analysis focus, specialist prompts, and output framing. It takes priority over default analysis behavior.
If no PR number is found, auto-detect from the current branch:
gh pr view --json number --jq '.number'
If that also fails, stop and ask the user for a PR number.
Reading Paths (conditional, by mode and title-prefix size)
The PR title's automated size prefix (XS/S/M/L/XL/2XL/3XL, stamped by Graphite/GitHub tooling) is the CANONICAL size for routing below; it is the same complexity signal a human reviewer sees. Infer from additions+deletions ONLY when no prefix exists (same fallback as Size Classification). Do not substitute independent complexity judgment for the prefix (2026-06-09 directive, bd docr-pnx9).
This SKILL.md is always read in full. Sub-files load per the table; skipping a "Skip (sanctioned)" file on a matching run is correct behavior, not a shortcut.
| Mode / size | Read | Skip (sanctioned) |
|---|---|---|
| every run | output-formats.md (the output contract; never skippable, any mode, any size) | |
--quick |
nothing further | all other sub-files |
default / --once / --mine, XS-S |
prior-reviews.md (if prior rounds exist), compliance-checks.md (if Jira ticket or CI failures), provenance-classification.md, bot-reactions.md (default mode only) | dispatch.md (no specialist dispatch at XS/S, EXCEPT has_terraform with a net-new .tf OR has_new_tf_resource (a modified .tf that adds a resource/module/data block) OR has_ci_workflow_change (a changed .github/workflows/*.ya?ml): dispatch mx2-devops-build-deploy even at XS/S, since IaC/CI-workflow blast radius is decoupled from line count AND from whether the file is net-new; mirrors the Checkov net-new-tf-or-new-resource trigger and the size-independent changes_public_surface rule; and EXCEPT adds_capability: load dispatch.md for its orchestrator-owned Active Reuse-Search step, which runs even at XS/S with no specialist dispatch implied, since capability duplication is decoupled from line count and one motivating miss, PR #10944, was 21 lines), synthesis.md, verification.md, diagrams.md |
default / --once / --mine, M+ |
all of the above plus dispatch.md, dispatch-mechanics.md, synthesis.md, static-analyzers.md | verification.md at M when no BLOCKING-class findings |
| L+ | plus verification.md | |
| trigger-conditional, any size | checkov.md (has_terraform + net-new tf), diagrams.md (M+ AND multi_service), design-doc.md (Confluence link in body), context.md (migration/series triggers), freshness.md / grounding.md when those checks need their exact commands |
The Stop-hook backstop (stop-validate-pr-intel.sh) still validates every render, so a misjudged reading path degrades to a caught retry, not a silent miss. Mandatory PHASES (provenance classification, bot reactions) are unaffected by reading paths; only reference depth varies.
Already-reviewed comment-only follow-up: verify the claims, skip the machinery. A narrow carve-out, and every condition is required. When (a) the diff is comment-only or otherwise has provably zero plan/runtime impact, (b) THIS session already reviewed the parent change and so already holds the files, the ticket and the sibling-PR context, and (c) the follow-up exists to address a finding this session itself raised, then the substantive review is confirming the author's factual claims, not re-running the fan-out. Do that directly: verify each falsifiable claim in the PR description, confirm the no-impact property from the diff rather than asserting it (every changed line is a comment, and the semantically load-bearing values appear only as unchanged context), then render and post. State in the output that the full pass was not run AND why, so the choice is visible rather than silently skipped.
Guard rails, because this carve-out is easy to over-apply. It does NOT fire on a comment-only diff whose parent this session did not review, on a first-round review by any author, or on anything with a runtime-reachable change however small: a config VALUE change is not comment-only even when the diff is two lines, since the value is the payload. Condition (b) is about context you actually hold, not familiarity with the area. When any condition is uncertain, run the normal path; the cost of an unnecessary XS render is a few tool calls, and the cost of a missed finding on a change nobody else reviewed is a defect. The per-PR self-registration in the inject log is still required (see "This applies per PR NUMBER, not per Skill invocation" below), because the log is keyed by PR and a skipped registration produces a false nudge at post time.
(2026-08-19, #11837: a comment-only follow-up to #11832 fixing the exact wording nit that session's own review had raised, two files, +7/-4, every changed line a comment with FOLIO_API_DNS_NAME unchanged in both. The review that mattered was verifying three factual claims in the description, each of which held: infra/folio/ has exactly beta/continuousdelivery/prod with no dev, has only the dev and prod trees, and infra/aws-us_east_1-eng carries no stack. A full fan-out would have added nothing the parent review had not already established.)
Flagging an unverifiable cross-system or repo-HEAD claim is a first-class outcome of
the initial pass, not a fallback. Neither PR size nor "I found no specific unverified
claim" gates it. When a finding's verdict would rest on a falsifiable cross-system or
repo-wide-state assertion you have not actually traced (a mirror-table column existing,
"all callers migrated", a stated end-to-end effect such as "returns 404", a frozen or
high-blast-radius module touch per architecture.md), do NOT state it as a confident
verdict: run the local investigation (synthesis.md Cross-System Investigation for the
mirror-column class) and surface an UNVERIFIED-ASSERTION finding if it does not resolve.
Surface it during the original synthesis, including at XS-S where dispatch and
synthesis.md are otherwise skipped, so the verdict reflects the honest result. Default
toward investigating when in doubt; an XS prefix lowers neither the investigation bar nor
the verification bar. The dead @claude-bot question is retired; @claude review once
(managed Code Review) is the manual out-of-band escalation for inconclusive cases.
Empirical execution beats precedent-citation on disputed test behavior. When the
author and the reviewer disagree about what a test DOES (an assertion "doesn't work",
a mock "doesn't intercept", a fixture "breaks under xdist"), do not re-cite precedent
a second time: dispatch a worktree agent to check out the PR tip, restore or write the
disputed test, and RUN it (pants test <target>), iterating on the failure output up
to 3 attempts. The author's observation is usually real even when their diagnosis is
wrong, and only execution distinguishes the two; the deliverable is a verified patch
plus the actual root cause, which ends the loop in one round (PR #9725, 2026-07-16:
three assertion-deletion rounds ended by one empirical run that found a repo-global
moto fixture shadowing responses.calls). The same move applies when the author says a
test fix is DONE: the red-green-revert cycle in .claude/rules/verification.md is what
proves a matcher actually bites, and only execution supplies it. For a TypeScript PR the
pants test path does not apply and a fresh worktree has no node_modules; the
symlink-plus-direct-binary recipe is bd recall gotcha:worktree-typescript-vitest-run
(PR #11442, 2026-08-10: proved an expanded Word.Alignment stub made a previously-vacuous
alignment matcher load-bearing, three runs under 4s total).
A dispatch authorized to mutate never shares a worktree with a reader. This rule and
the next one hand an agent permission to edit, run, and revert, and the reviewer roster's
Bash grant makes that possible even though none of them hold Edit/Write, so the tool
list cannot tell you which specialists mutate. Give the mutating agent its own worktree, or
dispatch it in its own sequential batch after the readers return. Otherwise a sibling reader
reads half-reverted code, and the harness's ordinary file-changed notice reaches it mid-run
looking exactly like a prompt-injection attempt (PR #11386, 2026-08-17: a legitimate
red-green-revert on run_local.py:91 drove a reviewer to report a fabricated system-reminder,
which the orchestrator relayed to the user as a security event and then had to retract).
Full rule and teardown: dispatch-mechanics.md.
When the diff's SUBJECT is tool semantics, execute every falsifiable claim in the
changed prose. The rule above fires on a disputed test; this one fires with no dispute
at all, because a spec/docs diff about how a tool behaves carries assertions that read as
self-evidently true and are checkable in seconds. Verifying one form and generalizing to
an adjacent form is the specific failure: on PR #11539 (2026-08-12) round 1 confirmed
three dots was right for git diff origin/main...HEAD (merge-base diff, correct for a
changed-file list) and carried that to the neighbouring git log origin/main...HEAD
without running it. Three-dot git log is the SYMMETRIC DIFFERENCE, so the fix still
leaked other people's ticket IDs into the step that greps the log; measured later on that
branch, three dots gave 32 commits against 10 and dragged in 8 foreign IDs. Two rounds
passed it and the author found it. Same-flag-different-verb is the highest-risk case, so
run each form in a throwaway worktree at the PR tip rather than reasoning about it, and
pair every "returned nothing" result with a positive control proving the toolchain could
have returned something (on that PR every changed file was .md, which yields zero pants
targets either way, so the first zero was meaningless until a control base spanning 15
.py files returned 16 targets). Full lesson: memory/reviewer-discipline.md lesson 18.
Data Gathering
Pre-flight checklist. Before Phase 0, confirm each of these was executed or explicitly deemed not applicable. Skipping a conditional step (e.g., Jira ticket hydration when no ticket is referenced) is fine; skipping a conditional step when the trigger IS present is the failure mode this checklist exists to prevent.
- PR metadata fetched
- Run self-registered in the inject log (Skill-tool invocations; see Self-register the run)
- Diff fetched (skip for
--quick) - PR head ref fetched locally (for worktree)
- Merge Base Freshness checked
- Prior review memories loaded via
bd memories pr-<number>(see prior-reviews.md) - DynamoDB prior reviews loaded (skipped if SSO not active) via
pr_review_state.list_reviews_for_pr(see prior-reviews.md) - Jira ticket hydrated if
MX2-NNNNNor similar appears in PR body - Design doc hydrated if any
<company>.atlassian.net/wiki/URL appears in PR body (see lesson #11 inreviewer-discipline.md) - Inline review comments fetched if prior reviews exist (required for dedup)
- PR Series context checked if Jira ticket was found
- Service Context extracted from CLAUDE.md or README
Narrative details for each step follow below.
Fetch all PR data using these commands. Run them in parallel for speed:
# Metadata (always needed)
gh pr view <number> --json title,author,baseRefName,headRefName,headRefOid,state,isDraft,labels,body,commits,reviews,comments,files,statusCheckRollup,url,additions,deletions,changedFiles,reviewDecision
# Diff (skip for --quick mode)
gh pr diff <number>
# Repo name (needed for inline comments)
gh repo view --json nameWithOwner --jq '.nameWithOwner'
# Pre-fetch PR head ref (ensures commit is locally reachable for worktree creation)
# headRefName comes from the PR metadata above
git fetch origin <headRefName>
Sanitize the fetched title before any render. Graphite/GitHub PR titles routinely
carry U+2014 (PR #10818: ... rewrite [U+2014] summary/derived-artifact ...), and the
title is echoed verbatim into the ## PR #<N>: <title> header, so the em-dash gate in
output-formats.md cannot catch it by re-reading authored prose. Replace it at the source
once, and every downstream render inherits the clean string:
gh pr view <number> --json title --jq '.title' | sed 's/\xe2\x80\x94/-/g'
The same applies to any upstream string quoted into the briefing (bot comment text, author reply text, Jira summary, commit messageHeadline): sanitize on echo, never paste through.
Self-register the run (required, once per invocation, alongside the metadata fetch):
append this run to the inject log so nudge-handrolled-review.sh knows /pr-intel ran for
this PR in this session. The UserPromptSubmit hook only logs runs the user typed; a
Skill-tool invocation must log itself or later posts to this PR get a false nudge
(three occurrences, 2026-07-15):
echo "{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"session_id\":\"${CLAUDE_CODE_SESSION_ID}\",\"pr\":\"<PR_NUMBER>\",\"prompt_preview\":\"skill-invoked /pr-intel <PR_NUMBER>\"}" >> ~/.claude/logs/pr-intel-contract-inject.jsonl
This applies per PR NUMBER, not per Skill invocation. Extending pr-intel-grade
analysis to an additional PR within the same conversation (e.g. the user says "also
review #N") without a fresh Skill("pr-intel", ...) call still requires its own
self-register line for N; the log is keyed by PR, and skipping it here is the same
false-nudge failure mode, just one call removed from the one the log was built to catch
(observed 2026-08-17: two PRs reviewed in one continued pass, both missed registration,
both later blocked at /post-review time).
Prior Reviews
Before any other analysis, check if this PR has been reviewed in a prior session across two channels in parallel:
bd memories pr-<number>for terminal-side review memories that/post-reviewwrites on every successful post.- The DynamoDB
pr-reviewtable (cross-modality state shared with the Slack bot).
Without this step, dedup against prior rounds runs only against in-conversation
comments; multi-day re-reviews silently re-raise points already posted. The
delta-focused diff (git diff <prior_head_sha> <headRefOid>) sent to specialists
is also the single highest-value signal on re-reviews; it cannot be computed
without the prior head_sha.
For the bd memories key format, DynamoDB SSO fallback (the table is in the dev
account, the heredoc needs AWS_PROFILE=dev AWS_DEFAULT_REGION=us-east-1 and
uv run --with boto3 --with 'pydantic>=2'), revision-delta computation, prior-
review dedup, briefing header format, and default-recommendation-shift rules,
see prior-reviews.md.
If both channels fail, proceed as first-round (see correction:skill:pr-intel-first-round
in beads memory).
Merge Base Freshness & Ghost Diffs
After fetching the PR head ref, run three local git checks (always run):
Resolve baseRefName FIRST; it is not always main. All three checks below diff
against the PR's BASE, and on a Graphite stack that base is a downstack branch. Diffing
against main silently attributes the downstack PR's content to this one and produces
numbers that look plausible and announce nothing: 2026-08-12 on #11386 (base
cfried/ruleseng-search) the freshness check read 54 files / +3790 against the real 15
files / +824, and the ghost-diff comparison returned .claude/** paths that only meant
"the branch is 814 commits behind main." freshness.md writes origin/main throughout
as shorthand for the resolved base.
- Merge Base Freshness: files in the PR's changeset whose content is
identical to current main (already shipped via a sibling PR). Store
merge_base_freshness(stale_files,net_new_files,is_stale); Size Classification, Dispatch Signals, and inline comments all use net-new only. If ALL files are already on main, short-circuit: the branch needs a rebase. - Ghost Diffs (reverse freshness): files in the 2-dot
git diffbut ABSENT from the PR's file list. GitHub's three-dot merge base hides these; they are usually a rebase conflict silently reverting a recently-merged change. High-consequence: surface as BLOCKING with the recently-merged PR reference. Cannot receive inline comments; report in the review body. - Concurrent Main Edits: commits on main that touched the PR's OWN files after
its merge base (
git log <merge-base>..origin/main -- <PR's files>). The diff you are reviewing was written against an older version of those files. On a hit, test-merge onto main and assert on merged CONTENT before calling it a concern: additive edits to a shared structured container auto-merge clean while dropping one side's entry. Report the verification even when it clears.
If git fetch failed earlier, skip all three and treat all files as net-new. For the
exact git diff/git log commands, the merge_base_freshness field shapes, and
the downstream-effects list, see freshness.md.
After metadata is loaded, fetch inline review comments (in addition to the issue-level
comments already pulled via gh pr view --json comments). Bot commenters split across
both endpoints: Copilot and Sentry leave inline review comments; SonarQube, PR Metrics,
Vercel, Mergify, Datadog leave issue-level comments. Always fetch both - reporting
only one is incomplete. This fetch is also required for dedup during synthesis; if
skipped, the dedup step in synthesis.md cannot run and bot comment pile-on will occur.
Use gh api (the GitHub MCP was rejected 2026-04-14 due to auth brittleness):
gh api /repos/<owner>/<repo>/pulls/<number>/comments \
--jq '[.[] | {id: .id, user: .user.login, path: .path, line: .line, body: .body, in_reply_to_id: .in_reply_to_id}]'
This returns a flat list. Treat each entry as an independent comment for dedup purposes.
The is_outdated flag is unavailable from this endpoint; treat all comments as current.
Group by in_reply_to_id if thread structure is needed (root = null, replies = parent id).
If gh api fails, do NOT proceed straight to "dedup could not run": that conclusion is
about the READ, not the PR. Try the REVIEW-SCOPED endpoint first, which stayed up through
a multi-hour window where the list endpoint returned 404 (2026-08-17, #11732, cost a
posted pile-on that had to be deleted):
gh api /repos/<owner>/<repo>/pulls/<N>/reviews/<numeric_review_id>/comments.
gh pr view --json reviews exposes only the GraphQL node id (PRR_...), so derive the
numeric id by base64url-decoding the part after PRR_ and reading the third value of the
msgpack 3-array (0x93, 0xce u32 repo, 0xcf u64 review). Full recipe:
bd recall github-inline-comments. Only after that fallback also fails, note it explicitly
and flag in the output that bot dedup could not run.
Jira Ticket Hydration
Scan the PR body for MX2-\d+ references (or other known project prefixes). If
found, hydrate the ticket using the procedure in
../enrich/sources.md Section 1 (Jira Tickets), in parallel
with other post-metadata fetches.
Without Jira hydration, the AC Compliance Check below cannot run, and the empty-ticket-blocking rule (template-boilerplate detection) silently passes.
Store the same fields named in enrich/sources.md: summary, status, assignee,
priority, description AND customfield_11220 (both render in the Jira UI as
of ~2026-04-30; per project convention non-SF tickets put content in
description while SF-specific tickets mirror to customfield_11220 - check
whichever has content), comments, and issuelinks. Plus, for review use:
- Acceptance criteria (may live in the description body, or in a dedicated AC field)
- Issue type (Story, Bug, Task; informs what "correct" looks like)
If no ticket reference is found in the PR body, note this in Phase 0 as a description quality gap (same as today) but skip hydration. If the MCP call fails, note the failure and continue without ticket context. Do not block on Jira availability.
Design Doc Hydration & Spec Compliance Check
After metadata is loaded, scan the PR body for Confluence links matching
<company>.atlassian.net/wiki/. If found, hydrate the page body and comments (via
mcp__atlassian__getConfluencePage + inline/footer comment calls) in parallel
with Jira hydration, then compare the implementation against the spec: trace each
behavioral specification in the diff, flag deviations (response shapes, parameters,
routing, unhandled edge cases), and surface unresolved design-doc comments as open
threads. Deviations are not automatically bugs; the goal is to let the reviewer ask
"was this intentional?" This is the class of issue code-only review (all specialist
agents) cannot detect. Produces a Design Doc Compliance output section; runs in
default and --mine, skipped for --quick. If no Confluence link is found, skip
silently. For the exact MCP calls (cloudId, page-ID extraction), the stored fields,
and the deviation taxonomy, see design-doc.md.
PR Context (series, service, migration)
After Jira hydration, run three context-gathering checks before specialist dispatch:
- PR Series Context: when a Jira ticket was hydrated and sibling PRs exist, classify new exports as referenced or unreferenced across the series.
- Service Context: scan for service-level CLAUDE.md / README in the changed paths and surface a 3-5 line orientation block.
- Migration State (permanent as of 2026-07-02; 2026-07-01 audit: revisit
never ran, check in active use): when the PR touches an in-flight migration,
load operational state from
bd memories <migration-name>before forming review concerns.
Without these, sibling-PR awareness is missing (orphan-export false positives when a peer PR consumes the export), service-level orientation is missing (reviewers waste effort relearning the codebase), and migration state is missing (review concerns about "sequencing risk" or "missing fallback" fire on already-resolved cutovers).
For trigger detection, tiered fetch logic, the bead-memories migration query patterns, downstream effects on specialist preamble / synthesis / Draft Review Summary, and the rationale for the authoritative-state principle, see context.md.
If no Jira ticket was hydrated, no migration signal is detected, and no service-level docs are found, all three checks degrade silently (note absence, do not block).
Size Classification
Extract size from the PR title prefix (XS, S, M, L, XL, 2XL, 3XL). If no prefix, infer from additions + deletions: <=500 = S, <=1500 = M, else XL.
Size drives two behaviors:
- Diff strategy: XL+ PRs skip the full diff; fetch individual files as needed
with
gh pr diff <number> -- <path>. - Verification depth: L+ PRs run a challenge/consult loop on findings before output. See the Verification section below.
Dispatch Signals
Compute these booleans from the diff for specialist dispatch:
-
has_try_except_raise: added lines contain
try:,except, orraise -
has_security_patterns: added lines contain
SecretStr,logger.,.info(,.error(,.exception( -
security_files: changed file paths matching
auth|security|token|jwt|permission|rbac|document|upload|download|access|audit|secret|credential|patient -
renders_model_output: the diff renders model-produced content into a UI, or feeds it back into a subsequent prompt. True when ANY of:
- a changed path or filename matches
llm|prompt|chat|generative|completion|agent|copilot|rag|answer - added lines pass an API-response-derived value into markup: a
MarkdownRenderer/ReactMarkdownprop,dangerouslySetInnerHTML, or JSX children, where the value came from afetch/authFetch/response.json()in the same diff - added lines interpolate an API-response field into a string handed to a submit, send, ask, or prompt function (model output becoming the next turn's input)
Drives
mx2-security-auditorINDEPENDENT ofsecurity_files, because the path regex is a poor proxy for this class. PR #11355 (2026-08-17) rendered LLM output and round-tripped it into the next prompt across 32 files underai-doc-chat/, matched none ofauth|document|access|patient, and the security lens was skipped in the fan-out. Run later as a separate round it produced the sharpest finding of that review: LLM-authored text can reach a fixed client-side routing prefix, and the{{...}}citation discipline covered only one of the fields that feed prompt construction. On a legal platform, model output is attacker-influenced input whenever the source documents are adversarial, which for discovery material and opposing-expert reports is the normal case, not the edge case.Calibration: expect this to fire on most
ai-doc-chat,bulk-chat,intake_agent, andllmdiffs. That is intended, and the auditor's explicit clean-result confirmation is cheap. If it proves noisy after ~10 firings, narrow the name half rather than deleting the two content halves, which are the part the path regex cannot reach. - a changed path or filename matches
-
has_test_files: changed files matching
*_test.py|test_*|conftest.py -
has_terraform: changed files matching
*.tfor*.hcl -
has_new_tf_resource:
has_terraformis true AND an added line declares a new top-level Terraform block:^\+(resource|module|data)\s+". Set by a MODIFIED.tfthat appends a block, not only a net-new file. IaC blast radius (a new secret, a new plan-timedataread against a shared multi-env stack) is decoupled from line count AND from whether the file is new, so this drivesmx2-devops-build-deploydispatch at any size, exactly like a net-new.tf. (PR #10554: a +18 modifiedsalesforce.tfadding onemoduleblock gated a shared dev+prod secrets stack; the net-new-file trigger alone would have skipped the dispatch.) -
has_ci_workflow_change: changed files matching
.github/workflows/*.ya?ml, added OR modified. CI/CD workflow blast radius (secret exposure via${{ secrets.* }}, untrusted-actor trigger gating,permissions:/id-tokenscope, supply-chain via unpinneduses:) is decoupled from line count, exactly likehas_terraform, so this drivesmx2-devops-build-deploydispatch at any size. (PR #10836: an XS +58 net-newclaude.ymlgatedANTHROPIC_API_KEYbehind an author-association trigger guard and a write-capable OIDC app token; the XS-no-dispatch default would have skipped the devops lens on a security-sensitive file.) -
has_typescript_files: changed files matching
*.ts|*.tsx|*.mts|*.ctsAND outsidesrc/gen-typescript/(generated TS is excluded; review the generator instead) -
structural_risk_size: diff > 200 lines OR > 5 files changed
-
has_python_module_change: changed files include a
src/python/**/*.pythat is added, deleted, or renamed, OR whose added lines declare a net-new top-leveldef/class(column-0 on a+line). Drivesmodule-cohesion-reviewerdispatch (cross-file cohesion lens). A changeset whose only Python changes are test files (*_test.py|test_*|conftest.py) does not set it unless production/test-only mixing is the concern. -
has_file_history: count of merged PRs in last 180 days touching ANY file in the changeset (computed via
gh pr list --state merged --search "<file>" --limit 5per top-3 changed files, cap at 3 per file). Boolean = aggregate count >= 3. -
has_pattern_precedent: at least one file in changeset has >= 2 prior merged PRs in last 180 days AND the diff adds new public symbols. Symbol detection: added lines matching
^\+\s*(export |def |class |interface |type ). Reuses the samegh pr listcalls ashas_file_history. -
changes_public_surface: added/removed/modified lines declare a public symbol. Detection: lines matching
^[+-]\s*(def |async def |class |interface |type |export ), OR^[+-]\s*[A-Z_]+\s*[:=](constants, enum values), OR^[+-]\s*\w+:\s*\w+inside files matching*Settings*or Pydantic model classes (schema/Settings field changes). Excludes private symbols (leading underscore in Python, non-exported in TS). Drivesbot-reviewdispatch (cross-file blast-radius lens). Size is a poor proxy for blast radius; an XS PR that changes a public type signature has higher downstream impact than an M PR refactoring internals. -
adds_capability: added lines introduce a NEW outbound capability rather than extending an existing one. Any of: an HTTP call (
requests.,httpx.,urllib.request,aiohttp,fetch(); a subprocess invocation (subprocess.,os.system,shell=True); a queue or topic publish (publish(,send_message(,put_events(); a new route declaration (@app.,@router.,APIRouter(); a new CLI entry point (argparse,click., a new__main__block); a system-package or image install (apt-get install, a DockerfileRUNadding a binary); or a new raw SQL or warehouse query string. Test-only files alone do not set it. Compute at ALL sizes, XS included: it drives the orchestrator-owned Active Reuse-Search in dispatch.md, not a specialist dispatch, so the XS/S no-dispatch default does not apply (PR #10944, one of the motivating misses, was 21 lines). Pattern set is duplicated from theadds_capabilityrow in~/.claude/skills/review/SKILL.mdstep 2, the canonical copy per docr-z3qw4; if the two drift, reconcile toward the /review copy. -
multi_service: changed files (net-new only) span 2+ distinct top-level service directories. Service directory = first path segment after
src/python/mx2/,src/typescript/mx2/, orinfra/. Files outside these prefixes (root scripts, generated code) do not contribute. Drives the optional Sequence Diagram briefing section for M+ PRs. See diagrams.md. -
spot_check_eligible: ALL of (a) size in {L, XL, 2XL, 3XL}, (b) net-new file count >= 10 AND median per-file diff lines <= 25 (mechanical-pattern proxy: many small uniform edits), (c) PR description contains a methodology statement detectable by regex (
script:|ran (the )?(command|script|tool)|applied (rule|codemod|transform)|using (yapf|ruff|isort|black|sed|jscodeshift|comby|grit)|migration script|codemod). Drives the spot-check mode under Specialist Dispatch (the engineering lead's Code Review Guide #11: "focus your review on the methodology... spot-check a few instances"). Conservative-by-default: when any of (a)/(b)/(c) is uncertain, set to false (full-diff dispatch is the safe default; spot-check trades coverage for speed and that trade only makes sense when the mechanical pattern is unambiguous). -
has_observability_signal, has_pydantic_settings_signal: defined in dispatch.md; compute them when dispatch.md loads (M+ PRs).
Phase 0: Description Quality Check
Before dispatching specialists, evaluate the PR description:
- Is it present and non-boilerplate?
- Does it explain intent (why), not just content (what)?
- Is there a linked Jira ticket or meaningful context?
Boilerplate detection patterns (any of these alone is sufficient to fail Phase 0):
- Description IS the ticket title (no additional context)
- Only template skeleton remains ("As a [type of user]", "Given that...", unfilled checklist headers)
- Lists WHAT changed without WHY: leads with verbs like "Adds X / Implements Y / Refactors Z" without any "because", "to fix", "needed for", or other rationale connective
- Single sentence < 30 chars
- Empty body with only
Jira issue link: MX2-XXXX
If the description is absent or inadequate, short-circuit: produce a brief "send it back" briefing instead of full specialist dispatch. The framing is the engineering lead's #1 explicitly: "don't waste your time reviewing without context." Skip CI status check, AC compliance, SonarCloud pre-check, and specialist dispatch entirely. The output should:
- Set Action to Comment (NOT Approve with Comments; the PR is not ready for that signal)
- Open the Draft Review Summary with one sentence naming what's missing (intent, ticket link, rationale) and asking the author to fill it in before the next review pass
- List what a good description should contain for this PR based on the diff scope (3-5 bullets: the components touched, the user-facing effect, the rationale, any operational risk)
- Set Front Door count to 1 (description) in the Review Recommendation header, even though no other front-door findings were detected
- Skip the Front Door section's findings table; the briefing IS the short-circuit, so the Draft Review Summary carries the action
The short-circuit avoids the cost of specialist dispatch on a PR that the
author should fix before further review. Re-running /pr-intel after the
author updates the description is the right next step.
CI Status + AC Compliance Checks
After Phase 0 description quality, run three pre-dispatch checks:
- CI Status Check: parse
statusCheckRollupfrom PR metadata; if failures exist, fetch the last 3 merged PRs to classify each as PR-specific regression vs global flake. - AC Compliance Check (when Jira ticket is available): trace each acceptance criterion against the diff, flag deviations, and run the empty-ticket-blocking detector.
- Static Analyzer Pre-Check (always; specific sub-tools depend on what
posted on the PR): query each available static analyzer for findings
scoped to the PR. Three sub-tools today: SonarCloud (MCP available),
Datadog code analysis (MCP via
search_pr_insights), Sentry (no live bot on MX2 PRs; static patterns ride inmx2-code-reviewerinstead). the engineering lead's Code Review Guide #7 explicitly says review these findings and call out anything that should be blocking.
Without compliance-checks.md, CI failures are reported without distinguishing PR-specific regressions from global flakes (over-escalation of approvals over unrelated breakage), and AC compliance silently passes on tickets with only template boilerplate (the empty-ticket-blocking rule never fires). Both failure modes have shipped before; the rules are load-bearing.
For the failing-check classification logic (test/build vs code-quality gate), the boilerplate-detection patterns ("As a [type of user]", "Given that..."), the deviation taxonomy, and the mode-irrespective always-runs invariant, see compliance-checks.md.
Static Analyzer Pre-Check
Three static analyzers post on MX2 PRs (or could): SonarCloud
(mcp__sonarqube__*), Datadog code analysis (mcp__datadog__search_pr_insights),
and Sentry (no live bot on MX2 PRs as of 2026-05-28; static patterns ride in
mx2-code-reviewer). Surface their findings alongside specialist results so the
reviewer sees the engineering lead's #7 in one place rather than scattered across bot comments.
Always runs (mode-irrespective, like AC Compliance); --quick skips only the
SonarCloud leak-period diff filter. Static-analyzer findings are inline-iterate,
not Front Door class: a finding can still be BLOCKING in its own severity bucket,
which routes through the regular Recommendation Table (BLOCKING -> Request Changes),
not the Front Door track. The shared dedup rule (an analyzer finding overlapping
a specialist finding on the same file+line keeps the specialist finding and appends
the analyzer rule code as an attribution line) runs in synthesis Step 2.
For the per-tool fetch paths and process, the SonarCloud leak-period scope filter
and severity-by-metric mapping (rule violations vs coverage-flag-only vs gate
conditions), the catalog-walk secondary, the Datadog search_pr_insights process,
the Sentry no-bot rationale and future trigger conditions, and the dated calibration
(feedback:pr-review:sonarqube-leak-period-scope 2026-05-18, PR 9274 coverage-gate
phrasing 2026-05-21, config:sonarcloud-mcp 2026-05-21), see
static-analyzers.md.
Specialist Dispatch
For worktree isolation (the one shared worktree per invocation, the own-worktree
rule for a specialist whose prompt authorizes mutation, the --mine
skip, setup/teardown bash, and the git-fetch-failed BRANCH WARNING fallback),
Spot-Check Mode for large mechanical refactors (deterministic 3-file sample,
the mandatory briefing-addition template, what stays full-diff, the
one-step-more-conservative recommendation), and --mine Review-Cache Reuse
(diff-identity HIT/MISS keying, the reuse roster, the always-re-dispatch
mx2-pr-precedent rule, bead docr-xvnr), see
dispatch-mechanics.md.
Dispatch
Evaluate dispatch triggers using the computed signals. Launch all triggered specialists in parallel via the Agent tool. This is NOT optional for default mode.
Active reuse-search precedes the fan-out. When adds_capability fired, run the orchestrator-owned Active Reuse-Search in dispatch.md BEFORE building specialist prompts, so its candidate-owner results ride into the mx2-code-reviewer and module-cohesion-reviewer prompts as grounding evidence. It runs at every size (including XS/S renders that dispatch no specialists), and its outcome always renders on the header reuse-search: line per output-formats.md.
CRITICAL: All specialist agents MUST be foreground calls. Do NOT use
run_in_background: true. Send all Agent calls in a single message (parallel
foreground), wait for ALL results, THEN begin synthesis. If you start writing
output while agents are still running, the results arrive as async notifications
and get appended as awkward "noted, agent returned" postscripts instead of being
integrated into the briefing. The user sees a finished report followed by
trailing acknowledgments. This is broken output.
For dispatch triggers and specialist prompt templates, see dispatch.md.
Inline IaC Analysis (Checkov)
When has_terraform: true AND mode is default or --mine, run a Checkov pass on
net-new *.tf files in parallel with specialist dispatch. This is an inline tool call
(not a subagent), bounded to 5-10 seconds per file. Findings flow through synthesis
alongside specialist results. See checkov.md for invocation, suppression
list, and severity mapping.
For --quick mode: skip specialist dispatch entirely.
Synthesis
After specialist results return, synthesize into the final briefing. For the full synthesis process, consequence assessment, design review surfaces, review recommendation logic, and verifiability map, see synthesis.md. Before writing ANY output, read output-formats.md for the active mode's template. The template is the structural contract: populate each section in order, do not free-form narrate findings.
Optional: Sequence Diagram
When size is M or larger AND multi_service: true AND mode is default, generate a
Mermaid sequence diagram for the briefing per diagrams.md. The section
is emitted only when the diff produces an unambiguous call-path summary; the generator
emits a sentinel (SEQUENCE_UNCLEAR / SEQUENCE_TOO_LARGE) when grounding fails, and
the section is omitted in that case rather than fabricating relationships.
Provenance Classification
After Synthesis produces the finalized Draft Inline Comments list (and any
substantive Draft Review Summary bullets that warrant classification), batch-dispatch
the provenance-classifier agent to classify each finding as speed-amplified
(reviewer would have caught from careful single-file diff reading; the bot got
there faster) or bot-surfaced (verification path required live-state checks,
multi-page document synthesis, or cross-file blast-radius analysis the reviewer
could not have sustained at speed).
This is a mandatory top-level phase, not an optional step. The dispatch
happens REGARDLESS of size (XS/S/M/L/XL), regardless of specialist dispatch
outcome, and regardless of finding count. Zero findings is still a valid input;
the agent returns an empty classification array. The classifications drive the
Provenance: and Decision count: lines in the Review Recommendation header
(enforced by stop-validate-pr-intel.sh) and the per-finding Classification:
line in each Draft Inline Comment briefing context.
For the full dispatch contract (input schema, agent prompt construction, classification application, low-confidence handling, source-tagging discipline upstream), see provenance-classification.md.
Skip rule: --quick mode skips this phase entirely (no inline comments to
classify; the quick template has no Provenance line). --mine mode runs the
phase normally; the classification still informs the briefing-context audit
even when no comments will be posted.
Bot Reactions
After Provenance Classification, build the bot reactions list from the dedup
decisions made during Synthesis Step 2. Bot comments that overlapped with
synthesizer findings get classified as either +1 (bot finding is correct)
or -1 (bot finding is a false positive), independent of whether the
reviewer also keeps an inline comment for additional context or rebuttal.
This is a mandatory top-level phase, not an optional step. Reactions are
how the reviewer signals bot accuracy WITHOUT duplicating bot prose in their
own comments; thumbs-down on a false positive is how the reviewer discourages
that bot's noise patterns over time. The reactions list is consumed by
/post-review Step 3.5, which posts the reactions via gh api .../reactions -X POST -f content=<+1|-1>.
For the full 5-category decision tree, reaction-vs-comment orthogonality, endpoint distinction (inline review comments vs issue-level conversation comments), and the handoff schema for /post-review, see bot-reactions.md.
Skip rule: --quick and --mine modes skip this phase (no review is
being posted; no reactions to apply). Default mode always runs it; empty
reactions list is valid output when no bot comments overlapped.
Verification
After synthesis, run a verification pass scaled to PR size to catch false positives. For the verification process, see verification.md. Skip for XS/S PRs.
Output
The output MUST follow the structural template in output-formats.md for the active mode (default, --once, --mine, --quick). This is not optional and does not depend on PR size, specialist dispatch, or number of findings.
A default-mode render must contain, in order: the ## PR #<N>: <title> header block, Scope, Review Recommendation (metadata lines only), a fenced Draft Review Summary, Draft Inline Comments (or an explicit "None"), and a Verdict. This holds for every size and even for an Approve with zero findings.
The template is the output contract. Do not narrate findings in free-form prose.
Anchor check before emitting (mandatory, every mode with inline comments). Every
drafted inline anchor must sit inside the PR's net three-dot diff hunks or GitHub 422s
it, and /post-review catches it only after the briefing is rendered and previewed:
python3 ~/.claude/skills/pr-intel/verify-anchors.py --base <baseRefName> --head <headRefOid> <path:line> ...
On NOT_IN_HUNK, apply the grounding.md remedy in PREFERENCE ORDER: re-anchor to the
nearest net-new line that names the behavior (the script prints it as nearest:) and
make the comment text self-locating ("on line 222 just below this hunk"); only when no
net-new line triggers the finding at all, move it to the Draft Review Summary. A
body-folded finding has no resolvable thread, so it is materially likelier to be skipped:
on #10818 the author actioned every inline in the same review and silently skipped the
one body-folded item, costing an extra round. Do not emit an
anchor the script rejected. This is the mechanical enforcement of grounding.md's
anchor-grounding rule, which lives in a trigger-conditional sub-file that XS-S runs skip.
Attribution check before emitting (mandatory, every mode with inline comments). Every
drafted inline comment must open with a tool-attribution lede per output-formats.md's
Tool-source attribution rule, and block-unattributed-review-comment*.sh blocks the post
if one does not. Like the anchor check above, /post-review catches this only at Step 2.5,
AFTER the briefing was rendered and the user read it, so a non-compliant draft means the
comment text the user approved is not the text that gets posted (acute on the verb-named
/post-review <verb> path, which posts without a second confirm). Validate the drafted
bodies against the same evaluate() the hooks call, before emitting:
python3 ~/.claude/scratch/scripts/check-review-attribution.py <payload.json>
A bare {"comments":[{"path","line","body"}]} payload is enough (body/event are not
required; verified 2026-08-07). Expect the block to arrive at WRITE time rather than from
the script: block-unattributed-review-comment-file.sh is a PostToolUse(Write) hook that
scans review-shaped JSON, so a non-compliant throwaway payload trips it before you get to
run the checker. Either way the remedy is the same: rewrite the lede, do not defer to the
post-time gate. output-formats.md:190 carries the first-token rule the checker enforces.
Observed 2026-08-07 on #11307: an XS single-finding render drafted an unattributed lede
that Step 2.5 caught, costing a rewrite after the user had already approved the briefing.
Unverified-Assertion Containment (default mode)
Default mode is a one-shot briefing; the multi-phase @claude verify loop is retired
(PR #9888 removed the self-hosted bot, which answered targeted questions; the managed
Claude Code Review replacement is a full-PR reviewer, not a Q&A bot). A finding whose
verdict would rest on an unverified falsifiable or cross-system assertion is contained as
an UNVERIFIED-ASSERTION finding rather than a posted bot question: run the local
investigation (synthesis.md "Unverified-Assertion Containment + Cross-System
Investigation"; for the mirror-column class the named Cross-System Investigation recipe),
and let the result drive the verdict (default/--once Comment, --mine "Needs work first",
--quick "Warrants careful review" when it does not resolve). @claude review once
(managed Code Review) is the manual out-of-band escalation when local investigation is
inconclusive; it is operator discretion and never gates the first-round verdict.
Section Conditionality
Omit ANY section that would say "nothing to note." Always-present sections: Scope, Review Recommendation, Verdict, Draft Review Summary.
Exception: when mx2-security-auditor was dispatched, include positive confirmation
even if clean: "Reviewed for: PII exposure, auth/authz, audit trails, encryption. No concerns identified."
Principles
- Actionable output. Every finding should help the reviewer take a specific action.
- High signal, no noise. Only flag things with evidence. False positives erode trust.
- Two audiences per finding. Briefing text (reviewer) and draft comment (PR author).
- Review the why, not just the what. A PR is a claim that a change achieves a goal; the review's first job is to test that claim, not only to check that the surface code is well-built. Anchor on the goal (the description's why, the ticket intent), then ask whether the implementation achieves it AND whether it is the simplest path given what the deployment platform, runtime, framework, and existing services already provide. A clean specialist fan-out that never asks "does this mechanism need to exist at all?" will approve code that should not exist. Operationalized by the Goal-Fit & Mechanism Necessity design-review surface (synthesis.md) and the
mx2-skepticnecessity question (dispatch.md); do not let surface-level polish substitute for the goal-fit question. - Depth by default, speed on request. Specialist dispatch is default.
--quickfor triage. - Don't duplicate existing tools. No
pantsruns. Posting is delegated to/post-review, never reimplemented. The briefing itself is never auto-posted, and/pr-intelperforms no posting of its own; all posting is/post-reviewafter your explicit OK. - The engineering lead's priority order is the human-reviewer standard. The engineering lead's
Code Review Guide for Humans (an internal Confluence page)
(Mar 2026) defines the priority order: description, then types, then
complexity / naming, then boolean / behavior-switching params, then tests,
then correctness-via-tests (NOT in-head execution), then static analyzers,
then pragma review, then exception design, then large-refactor methodology.
Phase 0 (description quality) implements item 1; the specialist dispatch
route (
mx2-code-reviewerDesign Judgment Checks) implements items 2-10. When a Phase 0 short-circuit fires, the briefing stops at "send it back" without further specialist dispatch, matching the engineering lead's "don't waste your time reviewing without context."
Additional Resources
- For specialist dispatch triggers and prompt templates, see dispatch.md
- For inline Checkov IaC analysis (Terraform PRs), see checkov.md
- For Mermaid sequence diagram generation (M+ cross-service PRs), see diagrams.md
- For synthesis process, consequence assessment, design surfaces, and verifiability map, see synthesis.md
- For size-gated verification process, see verification.md
- For output format templates for each mode, see output-formats.md
- For grounding rules and evidence categories, see grounding.md