From Doer Work Kit
Review an external pull request (GitHub or GitLab) with configurable advisor personas (security, performance, mobile, a11y, api). Optionally posts the findings as a PR review comment. Personas are shared with /wk:advise.
How this skill is triggered — by the user, by Claude, or both
Slash command
/wk:reviewThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
User-facing skill for reviewing a pull request or merge request that lives outside the local repository (i.e., a PR the dev did NOT generate via the `/wk:doer` pipeline). Fetches PR metadata, diff, and comments from the platform CLI, then dispatches one or more advisor personas from `lib/advisor-personas/` in parallel. Produces a structured report, and optionally posts it back to the platform a...
User-facing skill for reviewing a pull request or merge request that lives outside the local repository (i.e., a PR the dev did NOT generate via the /wk:doer pipeline). Fetches PR metadata, diff, and comments from the platform CLI, then dispatches one or more advisor personas from lib/advisor-personas/ in parallel. Produces a structured report, and optionally posts it back to the platform as a review comment.
How it differs from /wk:advise: wk:advise is spec-first and local (it reviews specs, ACs, or files from the current working tree). wk:review is FETCH-FIRST: it gathers PR context from a remote platform before dispatching personas. It targets a coworker's PR, not a ticket in flight.
| Command | Description |
|---|---|
/wk:review <pr-ref> | Review using the default persona set resolved via preferences.sh get-flag review_default_personas (defaults to ["security"] if unset). |
/wk:review <pr-ref> --personas <id1>,<id2>,... | Override the persona set for this invocation. |
/wk:review <pr-ref> --persona <id> | Single-persona shortcut (equivalent to --personas <id>). |
/wk:review <pr-ref> --post | After generating the report, post it as a review comment via the platform CLI. |
/wk:review --list-personas | List all available personas from ${CLAUDE_PLUGIN_ROOT}/lib/advisor-personas/. |
The <pr-ref> argument accepts the following forms.
Full URL:
https://github.com/<owner>/<repo>/pull/<N>https://gitlab.com/<group>/<repo>/-/merge_requests/<N>Short form:
<owner>/<repo>#<N> (e.g. acme/backend#42)<group>/<repo>!<N> (e.g. acme/backend!42)Bare number inside a clone with a single remote:
#<N> (e.g. #42). The skill resolves the remote from git remote get-url origin.!<N> (e.g. !42). Same resolution.Auto-detection logic:
github.com or the short form uses #, use gh.gitlab.com or the short form uses !, use glab.AskUserQuestion (two options: GitHub PR / GitLab MR): "Is this a GitHub PR or a GitLab MR?"| Platform | CLI | Install | Auth |
|---|---|---|---|
| GitHub | gh | brew install gh / cli.github.com | gh auth login |
| GitLab | glab | brew install glab / gitlab.com/gitlab-org/cli | glab auth login |
Before fetching anything, the skill checks that the required CLI is available:
command -v gh # for GitHub refs
command -v glab # for GitLab refs
If the CLI is missing, the skill exits with a clear message:
ERROR: 'gh' is not installed or not on PATH. Install it with 'brew install gh' and run 'gh auth login' before retrying.
Do NOT attempt to fall back to raw curl/git operations. The platform CLIs handle auth, pagination, and field normalization.
MANDATORY before any fetch or persona dispatch. Run the canonical guard from ${CLAUDE_PLUGIN_ROOT}/lib/workspace-guard.md. The minimum invariant /wk:review enforces is that cwd is NOT $HOME and NOT /. On failure:
ERROR: /wk:review must be run from a project directory, not from ~ or /.
Personas are defined in ${CLAUDE_PLUGIN_ROOT}/lib/advisor-personas/. Each persona is a JSON file named <id>.json. The skill reads persona definitions at runtime; it does NOT redefine the schema here.
To list available personas:
ls "${CLAUDE_PLUGIN_ROOT}/lib/advisor-personas/"
Each persona JSON is expected to provide (at minimum) a system_prompt, a focus_checklist, out_of_scope, and an output_schema. See ${CLAUDE_PLUGIN_ROOT}/lib/advisor-personas/ for the authoritative schema and available persona IDs.
--list-personas behavior:
*.json files in ${CLAUDE_PLUGIN_ROOT}/lib/advisor-personas/.<id> and the first line of its system_prompt (or a description field if present).Follow these numbered steps in order on every invocation (except --list-personas, which exits after step 1).
Run the workspace-guard check described above. Abort on failure.
Parse <pr-ref> to extract:
platform: github or gitlabowner: org or grouprepo: repository namenumber: PR/MR number (integer)For bare #<N> / !<N> refs, run:
git remote get-url origin
Parse owner and repo from the remote URL (handles both HTTPS and SSH forms).
Verify the required CLI is available (see "Required CLIs" above). Abort with the error message if missing.
Run ${CLAUDE_PLUGIN_ROOT}/lib/helpers/preferences.sh get-flag review_default_personas. The helper reads ${CLAUDE_CONFIG_DIR:-$HOME/.claude}/wk/preferences.json and emits a comma-separated list of persona IDs. If the output is empty, default to security.
If --personas or --persona was given on the command line, use that list instead (override).
For each resolved persona ID, verify that ${CLAUDE_PLUGIN_ROOT}/lib/advisor-personas/<id>.json exists. For any missing ID, narrate a warning and remove it from the list. If the list becomes empty, exit with:
ERROR: No valid personas remain after resolution. Run '/wk:review --list-personas' to see available IDs.
GitHub:
# Metadata
gh pr view "<owner>/<repo>#<N>" \
--json title,body,author,headRefName,baseRefName,additions,deletions,labels,state,reviewDecision
# Diff
gh pr diff "<owner>/<repo>#<N>"
# Top-level review comments
gh pr view "<owner>/<repo>#<N>" --comments
GitLab:
# Metadata
glab mr view <N> --repo "<owner>/<repo>" -F json
# Diff
glab mr diff <N> --repo "<owner>/<repo>"
# Notes (comments)
glab mr note list <N> --repo "<owner>/<repo>"
Capture all three outputs (metadata JSON, diff text, comments text) in memory. Do NOT write them to disk. Do NOT write them to any file in cwd or in .doer/. The fetched content is ephemeral context for this invocation only.
Privacy and safety: do NOT inline any local .env file, any shell environment variable containing secrets, or any credential found in cwd into the Agent prompts below. The PR diff is the only external content that goes to the Agents.
Issue one Agent call per persona in a SINGLE tool block (parallel dispatch). Each Agent receives its own persona context and the full PR content.
Agent prompt template for each persona (fill in the placeholders):
You are an advisor performing a code review of an external pull request.
== Persona ==
<contents of ${CLAUDE_PLUGIN_ROOT}/lib/advisor-personas/<id>.json, full JSON>
Use the persona's `system_prompt` as your framing.
Stay within the persona's `focus_checklist` and do not address items in `out_of_scope`.
== PR Metadata ==
<metadata JSON from Step 3>
== PR Diff ==
<diff text from Step 3>
== PR Comments ==
<comments text from Step 3>
== Task ==
Review the pull request above through the lens of your persona.
Output your findings as a JSON array matching the persona's `output_schema`.
Each finding MUST be an object with these fields:
{
"severity": "blocker | high | medium | low | info",
"title": "<short title, one line>",
"where": "<file path and line or range, e.g. src/auth.ts:42 or N/A>",
"explain": "<one to three sentences explaining the finding>",
"fix": "<concrete suggested fix or 'N/A' if no actionable fix>"
}
Output ONLY a valid JSON array. No prose before or after the array.
Em-dashes are forbidden. Use commas, periods, or parentheses instead.
All output MUST be in English.
Wait for all Agents to return before proceeding.
Collect the JSON arrays returned by each Agent. Parse each array. Attribute each finding to its source persona.
Aggregate findings by severity within each persona section:
Priority order (highest first): blocker, high, medium, low, info.
Report template:
## PR Review: <owner>/<repo>#<N> "<title>"
Personas: <comma-separated persona IDs>
### <PersonaDisplayName> findings (<count>)
- [BLOCKER] <title> at <where>
<explain>
Suggested fix: <fix>
- [HIGH] ...
- [MEDIUM] ...
...
(Repeat the section above for each persona.)
### Summary
<One line per persona: "<PersonaID>: <N> blocker(s), <N> high, <N> medium, <N> low, <N> info">
Overall vote: <approve | comment | request_changes>
Vote rule (deterministic, apply in order):
severity == "blocker", vote is request_changes.severity == "high", vote is comment.approve.The vote reflects what this review would recommend as a formal GitHub/GitLab review action.
Without --post:
Print the report to stdout. Exit.
With --post on GitHub:
Determine the gh pr review flag from the vote:
request_changes vote: use --request-changescomment or approve vote: use --comment(Note: never use --approve automatically. Approval requires human intent.)
gh pr review "<owner>/<repo>#<N>" \
--request-changes \ # or --comment
--body "<report text>"
With --post on GitLab:
GitLab does not support automated approve/reject via glab in this skill. Post a comment only:
glab mr note create <N> \
--repo "<owner>/<repo>" \
--message "<report text>"
The GitLab vote line in the report still reflects the recommended action (so the dev knows what to do manually), but the skill does not call glab mr approve or glab mr merge.
After posting, narrate: "Report posted to /#."
The full report structure:
## PR Review: acme/backend#42 "Add OAuth2 refresh token rotation"
Personas: security, performance
### Security findings (3)
- [BLOCKER] Refresh token not invalidated on reuse at src/auth/token.ts:88
The old refresh token remains valid after rotation. An attacker who
intercepts a token can keep using it indefinitely.
Suggested fix: Invalidate the previous token in the same transaction
that issues the new one.
- [HIGH] Token stored in localStorage at src/auth/storage.ts:14
localStorage is accessible to any JS on the page, making it vulnerable
to XSS exfiltration.
Suggested fix: Store tokens in httpOnly cookies or in-memory only.
- [LOW] Missing rate limit on /auth/refresh at src/routes/auth.ts:201
No rate limiting means brute-force is possible on the refresh endpoint.
Suggested fix: Add a sliding-window rate limiter (e.g. 10 req/min/IP).
### Performance findings (1)
- [MEDIUM] N+1 query on user roles at src/auth/rbac.ts:55
For each request, roles are fetched in a loop without batching.
Suggested fix: Load all relevant roles in a single query before the loop.
### Summary
security: 1 blocker, 1 high, 0 medium, 1 low, 0 info
performance: 0 blockers, 0 high, 1 medium, 0 low, 0 info
Overall vote: request_changes
Three end-to-end runs (GitHub PR with blockers, GitHub PR clean approval, GitLab MR with --post) live in ${CLAUDE_PLUGIN_ROOT}/skills/review/examples.md. Read on demand only when verifying expected behavior.
| Condition | Message |
|---|---|
cwd is ~ or / | ERROR: /wk:review must be run from a project directory, not from ~ or /. |
| Required CLI not found | ERROR: 'gh' is not installed or not on PATH. Install it with 'brew install gh' and run 'gh auth login' before retrying. |
| Persona ID not found | Warning: Persona '<id>' not found in lib/advisor-personas/. Skipping. |
| All personas invalid | ERROR: No valid personas remain after resolution. Run '/wk:review --list-personas' to see available IDs. |
| Ambiguous bare number | Ask once: "Is this a GitHub PR (#) or a GitLab MR (!)?" |
| Agent returns non-JSON | Log the raw output, skip the persona's findings with a warning, continue with remaining personas. |
${CLAUDE_PLUGIN_ROOT}/lib/advisor-personas/. This skill is a consumer; it does NOT define or modify persona schemas.skills/doer/SKILL.md under "Parallel subagents (opt-in)" (WK-6). Each Agent is independent; the orchestrator waits for all before aggregating.npx claudepluginhub icarloscornejo/doer --plugin wkCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.