| name | pm-review |
| description | Use this skill when reviewing the quality and completeness of an existing Linear or GitHub issue — does it have a type tag, does it match the template for that type, are the required sections filled, is the acceptance criteria testable. Trigger phrases "review this issue", "review issue <ID>", "is this issue ready", "check the issue", "audit this issue", "/pm-review", "review the bug I just filed", "lint the issue". Also use proactively when an issue is about to be picked up for work (handoff into "In Progress") and the operator wants a sanity pass first. Produces a structured review report; with `--apply`, posts the findings as a comment on the issue. |
| version | 0.44.4 |
PM Review
Audit an existing issue against the template for its type. The review is structural — it checks whether the issue has a type, whether the body matches the registered template, and whether each required section has substantive content. It does not enrich (that's pm-improve) and it does not rewrite the issue (only the operator does that, after deciding which findings to accept).
The reviewer's job is to be honest, specific, and useful — flag what's actually missing, not generic "could be more detailed." When everything is fine, say so. Reviewer fatigue ("everything always has 17 nits") destroys the signal.
What the reviewer checks
For an issue ID, in order:
- Has a type tag. The issue body has a
[bug] / [feature] / [task] / [spike] prefix in the title, or the issue carries a tracker label that maps to a known tag in the pm-templates manifest (labels.linear[] / labels.github[]).
- Matches a known tag. The classified tag exists in
_manifest.json. Unknown → flag and stop; suggest registering the tag via pm-templates or correcting the label.
- Has an execution-mode label (
hitl or afk). Every issue must carry one or the other. Missing → flag with "Issue has no execution-mode label — file under hitl (human-in-loop) or afk (agent-runnable)." Don't infer; ask the operator to apply the label explicitly. The hitl_default_hint from the manifest is for creation, not for retroactive classification.
- Has every required section. For each entry in the tag's
required_sections, the body contains a markdown ## <Section> heading and non-placeholder content underneath it. Placeholder content ([One sentence: ...], empty bullets, TBD, TODO, just the heading) counts as missing.
- Acceptance criteria are testable. For tags whose required_sections include "Acceptance Criteria", each criterion must be a verifiable statement. Reject vague verbs without targets: "improve performance", "enhance UX", "handle edge cases" with no specifics. A criterion is OK when a reviewer could write a test or click-through to confirm pass/fail.
- Conventions check (if configured). If
<repo>/.claude/pm/conventions.md exists, the reviewer consults pm-conventions for project-specific rules (issue title format, required labels, naming patterns) and flags conventions violations as a separate report section so the user can tell structural issues from house-style issues.
The reviewer does not check whether the work is a good idea, scope it, or estimate it. Those are upstream decisions; the reviewer only validates that what's been written is reviewable.
Inputs
- An issue ID (Linear
ENG-42) or GitHub issue number (#123).
- Optionally, a tag override (
--tag feature) if the operator wants to review against a specific template instead of classifying.
- Optionally,
--apply to post the review as a comment on the issue. Without it, the review prints to the terminal and goes nowhere.
Workflow
Step 1: Fetch the issue
linear issue view <ID> --json
gh issue view <NUMBER> --repo <org/repo> --json number,title,body,labels,assignees,url
If both fail, stop with a transport error — do not review from cached state (cache lacks the up-to-date labels the classifier needs).
Step 2: Classify the tag
Apply pm-templates' classifier (the resolution logic lives there; do not duplicate it here):
- If
--tag <X> was passed, use it directly.
- Else inspect the title for a
[<tag>] prefix.
- Else read the issue's labels and look up the canonical tag via the manifest's
labels.<tracker> array.
- Else infer from the body content + the manifest's
description field. Mark this as a soft classification in the report so the user knows the tag was inferred.
If nothing matches, the review's first finding is "No type tag — issue cannot be reviewed until a tag is assigned." Stop checks 3-4.
Step 3: Load the template
Resolve the template body for the classified tag via pm-templates (repo override → user overlay → plugin default). Read the _manifest.json entry to get required_sections and optional_sections.
Step 4: Section-by-section check
For each required_sections heading:
- Present — the body contains
## <Section> exactly. Headings with extra suffixes (## Goal (revised)) count as present.
- Filled — at least one non-placeholder, non-empty line under the heading. Placeholder detection: lines that match the template's example text verbatim, lines that are just
TBD / TODO / ?, or empty bullet lists (- [ ] with no text after).
- Testable (acceptance criteria only) — each
- [ ] item names a behavior or outcome that a reviewer could verify. Apply the heuristic: would a tester know whether this passed?
For each optional_sections heading: if present, just note it. If absent, do not flag.
Step 5: Conventions check (if a conventions file exists)
If <repo>/.claude/pm/conventions.md exists, pass it through pm-conventions to extract rules that apply to issues (title format, required labels, prefix patterns, banned phrases). Run them against the issue and emit a separate "Conventions" block in the report.
If no conventions file exists, skip — do not invent house style.
Step 6: Write the report
Use this exact structure:
# Review: <ID> · <Title>
**Tag:** <tag> (<classification source: explicit-prefix | label | inferred>)
**Mode:** <hitl | afk | MISSING>
**Template:** <path used>
## Structural findings
- [PASS] Execution mode — `hitl` label present
- [PASS] What's Broken — present, filled
- [PASS] Steps to Reproduce — present, 2 numbered steps
- [FAIL] Acceptance Criteria — heading present, but all criteria are placeholders
## Conventions findings *(only if conventions_path is set)*
- [PASS] Title starts with `[bug]`
- [FAIL] Missing required label `area:*` (see conventions §labels)
## Verdict
- [ ] Ready to start work
- [x] Needs more information before starting
**Suggested next step:** Open the issue and fill the empty acceptance criteria, then re-run `/pm-review <ID>`.
Keep findings to one line each. The verdict is binary — ready or not. If everything passes, the report is short and that's the right shape:
# Review: ENG-42 · Add JWT refresh
**Tag:** feature (label)
All required sections present and filled. No conventions violations.
**Verdict:** Ready to start work.
When the tag had to be inferred (no explicit [bug] prefix, no matching tracker label — Step 2 fell through to description-matching), call that out in the same Tag: line so the operator sees what the classifier guessed and can correct it before the rest of the review is trusted:
# Review: ENG-58 · Refactor auth middleware
**Tag:** task (inferred — soft)
**Template:** ${CLAUDE_PLUGIN_ROOT}/skills/pm-templates/templates/task.md
> ⚠ Tag was inferred from issue content (no `[tag]` prefix, no mapped tracker label).
> If this is actually a `feature` or `bug`, the structural checks below may be against the wrong template.
> Add the right label in the tracker or re-run with `--tag <X>`.
## Structural findings
- [PASS] Goal — present, filled
- [PASS] Acceptance Criteria — present, 3 testable items
The (inferred — soft) marker in the Tag: line is the canonical format. The warning callout is what the operator actually reads — most people won't notice the parenthetical alone.
Step 7: Apply (optional)
If the operator passed --apply, post the report as a comment on the issue. The comment is signed so future reviewers know it was machine-generated:
linear issue comment create <ID> --body-file "$REVIEW_FILE"
gh issue comment <NUMBER> --repo <org/repo> --body-file "$REVIEW_FILE"
The comment header — visible provenance + machine marker
Every posted comment carries two layers at the top: a human-visible blockquote and a machine-readable HTML marker. They serve different audiences.
Posted comment format (literal — paste this exact shape):
> *Generated by `pm-review` during structural audit. Edits welcome — this is a starting point, not the final word.*
<!-- pm-review: v<plugin-version> -->
# Review: <ID> · <title>
... (the rest of the review report) ...
The blockquote is the human-facing provenance. When someone reads the issue comments months later — possibly someone who's never heard of this plugin — they need to know at a glance that the comment came from an automated audit, not a teammate. Hiding that signal in an HTML comment ranks poorly on the "no surprises for the reader" axis.
The HTML marker is the machine-readable handle for re-review idempotency. It does two jobs at once:
- Idempotency — when re-reviewing, detect any prior pm-review comment so we can offer to replace it rather than stack. Match by prefix (
<!-- pm-review: … up to -->), ignoring the version. A v0.20.3 review run should still recognize and replace a v0.20.0 marker. The whole point is that any prior machine review counts as "we've been here before."
- Provenance — when a human reads the comment, the version tells them which release of the skill produced it. That's a documentation concern, not a match key.
When re-reviewing an issue, grep the existing comments for <!-- pm-review: (no version). If found, ask whether to replace the existing review comment or add a new one — never stack silently. The blockquote stays on the replacement comment unchanged.
Without --apply, the report stays on the terminal. That's the default — most reviews are draft passes for the operator, not public comments.
What this skill does NOT do
- It does not rewrite the issue. Even with
--apply, only a comment is posted. The body stays the operator's to edit.
- It does not enrich the issue with research, repo information, or related-work links. That's
pm-improve. Run that next if the operator wants enrichment.
- It does not estimate, prioritize, or scope. The reviewer is structural —
priority_hint in the manifest is a default for issue creation, not a scoring criterion for the reviewer.
- It does not block work. A failing verdict is advisory.
Pipelining with pm-improve
A common flow is review → improve → re-review:
/pm-review ENG-42 # find what's missing
/pm-improve ENG-42 # enrich with repo context, research, related issues
/pm-review ENG-42 # confirm the improvements addressed the gaps
pm-review is intentionally idempotent — re-running it produces the same report (modulo prior-review-comment detection). That makes the pipeline safe to iterate.