- name
- review-pr
- description
- Review a PR for correctness, pattern compliance, testing, accessibility, and safety. Produces a confidence score for merge readiness.
- disable-model-invocation
- true
- argument-hint
- <PR-number-or-branch>
- allowed-tools
- Bash Read Grep Glob
# Review a Pull Request
Review PR **$ARGUMENTS** and produce a confidence score for merge readiness.
## Phase 1: Gather PR Context
```bash
# PR metadata
gh pr view $ARGUMENTS --json title,body,author,labels,files,additions,deletions,baseRefName,headRefName,state,isDraft,number
# Changed files list
gh pr diff $ARGUMENTS --name-only
# Full diff
gh pr diff $ARGUMENTS
# CI status
gh pr checks $ARGUMENTS
```
## Phase 2: Classify PR Type
Determine the PR type from changed files and metadata:
| Type | Detection | Check scope |
| ---------------- | -------------------------------------------------------------------- | ---------------------------------------------- |
| **docs-only** | All files are `*.md`, `docs/**`, `**/stories/**`, `**/.storybook/**` | Change file only |
| **test-only** | All files are `*.test.*`, `*.spec.*`, `**/testing/**` | Change file + test quality |
| **bug-fix** | Branch starts with `fix/` or title contains "fix" | All checks, extra weight on tests |
| **feature** | Branch starts with `feat/` or adds new exports | All checks, extra weight on API + patterns |
| **refactor** | No new exports, restructures existing code | All checks, extra weight on no behavior change |
| **config/infra** | Changes to CI, configs, scripts only | Change file + no regressions |
For **v8 packages** (`packages/react/`): skip V9 pattern checks — those are maintenance-only with different patterns.
For **web-components** (`packages/web-components/`): skip React-specific checks.
## Phase 3: Run Checks
Run each check category. For each finding, assign a severity:
- **BLOCKER** — must fix before merge
- **WARNING** — should address
- **INFO** — consider
### A. Beachball Change File
Required if any published package source code changed (not just tests/stories/docs).
- Check `change/` directory in the diff for new `.json` files
- Verify change type: `patch` for fixes, `minor` for features, never `major` without explicit approval
- Not required for changes that only affect tests, stories, docs, or snapshots
**BLOCKER** if missing for published source changes.
### B. V9 Component Pattern Compliance
Only for files in `packages/react-components/react-*/library/src/`:
| Check | Look for | Severity |
| ------------------- | --------------------------------------------------------------------------------------------------- | -------- |
| No `React.FC` | `React.FC`, `: FC<`, `React.FunctionComponent` in added lines | BLOCKER |
| No hardcoded styles | Hex colors `#[0-9a-fA-F]{3,8}`, hardcoded `px` values for spacing/radius/font in `.styles.ts` files | WARNING |
| Griffel usage | Style files must use `makeStyles` from `@griffel/react`, not inline styles | WARNING |
| mergeClasses order | User `className` must be the LAST argument in `mergeClasses()` | WARNING |
| Slot system | New components must use `slot.always`/`slot.optional` and `assertSlots` | WARNING |
Reference: [docs/architecture/component-patterns.md](../../../docs/architecture/component-patterns.md)
### C. Dependency Layer Violations
For changes to `package.json` files or new imports in Tier 3 component packages:
- **BLOCKER** if a Tier 3 package (`react-button`, `react-menu`, etc.) adds a dependency on another Tier 3 package
- Allowed Tier 2 deps: `react-utilities`, `react-theme`, `react-shared-contexts`, `react-tabster`, `react-positioning`, `react-portal`
- Allowed Tier 1 deps: `@griffel/react`, `@fluentui/tokens`, `@fluentui/react-jsx-runtime`
Reference: [docs/architecture/layers.md](../../../docs/architecture/layers.md)
### D. SSR Safety
Grep added lines for unguarded browser API access:
| Pattern | Severity |
| ------------------------------------------------------------- | -------- |
| `window.` without `canUseDOM` or `typeof window` guard nearby | BLOCKER |
| `document.` without guard | BLOCKER |
| `navigator.` without guard | BLOCKER |
| `localStorage` / `sessionStorage` without guard | BLOCKER |
| `instanceof HTMLElement` | WARNING |
Check 3 lines above each match for a guard (`canUseDOM`, `typeof window !== 'undefined'`).
### E. Testing
| Check | Severity |
| --------------------------------------------------------------------------- | -------- |
| Source files changed but no corresponding `.test.tsx` changes | WARNING |
| New component missing `testing/isConformant.ts` | WARNING |
| Snapshot files need updating (render/style changes without `.snap` updates) | INFO |
### F. API Surface
| Check | Severity |
| ------------------------------------------------- | -------- |
| Public API changed but `etc/*.api.md` not updated | WARNING |
| Existing exports removed (breaking change) | BLOCKER |
| New exports added (flag for human review) | INFO |
### G. Accessibility
| Check | Severity |
| -------------------------------------------------- | -------- |
| Existing `aria-*` attributes removed | BLOCKER |
| `onClick` without `onKeyDown`/`onKeyUp` handler | WARNING |
| Interactive elements missing `role` or `aria-*` | WARNING |
| Images/icons without `aria-label` or `aria-hidden` | WARNING |
### H. Security and Quality
| Check | Severity |
| --------------------------------------------- | -------- |
| `eval()` or `new Function()` | BLOCKER |
| `dangerouslySetInnerHTML` | WARNING |
| `console.log` / `debugger` in production code | WARNING |
| `// @ts-ignore` without explanation | WARNING |
| `any` type in new code | INFO |
### I. Documentation coverage
A code change frequently lands something that **someone downstream** needs to learn about. The "someone" splits into two audiences with very different reading habits, so this check actually walks two passes:
- **Pass 1 — user-facing docs.** Component consumers (storybook stories, migration guides, docsite, MDX, change-file comments).
- **Pass 2 — harness / agent-facing docs.** Future agent sessions reading the skills, `AGENTS.md`, `docs/workflows/*`, and `docs/architecture/*`. When a PR changes a build target, a script, a CI step, a label taxonomy, a project-board field, or an assumption that a skill makes, the skill or harness doc has to be updated **in the same PR** — otherwise every fresh `/triage-issues`, `/visual-test`, `/review-pr`, etc. invocation pays the discovery cost over again.
Both passes ask the same two questions: should this PR have updated docs, and if so, did it? Bug fixes that restore documented behavior, internal refactors, and test-only PRs default to PASS in both passes — the interesting cases are below.
#### Pass 1 — user-facing docs
| Source change | Expected doc surface |
| --------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| New prop on a public component (added in `*.types.ts`) | A Storybook story or MDX entry under `packages/react-components/react-<name>/stories/` that exercises the prop |
| New public export added to a `library/src/index.ts` barrel | A story or MDX entry covering it (or a justified note in the PR body) |
| New component package (new directory under `packages/react-components/react-*`) | Stories package, `library/docs/Spec.md`, and an entry on the docsite (`apps/public-docsite-v9`) |
| Behavior change to a public API's defaults (e.g. a new conditional in `use*_unstable` that changes observable output) | Mention in `library/docs/MIGRATION.md` or a "BREAKING CHANGE / behavior change" section in the PR body |
| Removal or deprecation of a public export | `library/docs/MIGRATION.md` entry and/or a `@deprecated` JSDoc on the symbol |
| New design token, classname constant, or CSS custom property | A line in the relevant docs (often the component's stories MDX) explaining how to use/override it |
#### Pass 2 — harness engineering / agent-facing docs
This pass exists because skills are part of the contract, not just convenience. When the skills are stale, the agents are wrong — quietly. Symptoms: a `/visual-test` invocation that points at a nonexistent project name, a `/triage-issues` flow that recommends a label the repo no longer has, a `/triage-board` skill that misses a new view filter the maintainers added.
Walk the diff for any of these and check whether the matching agent-facing doc was updated:
| Source change | Expected harness update |
View on GitHub