---
name: review-ticket
description: Write the developer review for a Jira story, the implementation layer of the Dev Review SOP. Use when Gib invokes /review-ticket KACP-XXXXX on a story that needs its Developer Review Instructions, estimate, risk level, dependencies, and testing tables filled in. Produces a code-verified draft for review first, applies to Jira only after approval.
---

# Review Ticket Workflow

Performs the per story portion of the team's Dev Review SOP (see `references/sop.md`): fills the Developer Review Instructions field with implementation guidance, sets the Original Estimate and Story Risk Level, links dependencies, fills the Risk Mitigation and Test Cases tables, and proposes automated test subtasks where a mitigation needs one. It is the third sibling of `ticket` (work a ticket) and `create-ticket` (write the description): this one writes the developer review.

Invoked as `/review-ticket KACP-XXXXX`. Two phases with a hard stop between them: draft, then apply. Nothing is written to Jira in the draft phase.

This is a *dev review*, not a code review. It is part of writing a ticket, before any
code exists: it fills in implementation guidance, an estimate, a risk level, and the
testing tables so a developer can pick the story up. Reviewing code that has already
been written is a separate thing entirely, and lives in the `ticket` skill at Phase 2
step 5.

## Files this skill uses

- `templates/dev-review-template.md`, the team's Developer Instructions Template. The section names come from here.
- `references/example.md`, a real reviewed story rendered to markdown. This is the calibration target for structure, depth, and voice. Its file paths belong to a different repo, only its anatomy is the standard.
- `references/estimation.md`, the estimation method and the anchor table calibrated from real Command Center estimates. Follow it exactly.
- `references/sop.md`, the SOP duties this skill automates.
- `scripts/review2adf.py`, markdown to ADF: `render <review.md>` produces the field document, `tables <current-field.json> <rows.md>` appends rows to an existing testing table while preserving its instruction panel and headers.
- Jira fetch reuses `~/.agents/skills/ticket/scripts/jira-fetch-issue.sh`. Same credential rule as the ticket skill: `source ~/.bashrc 2>/dev/null` in the same Bash call, then check `JIRA_CREDENTIALS` is set, and stop to ask rather than guessing if it is not.

## House rules

- No em dashes, en dashes, semicolons, or arrow glyphs anywhere in `review.md`. Plain punctuation only. Sweep before finishing.
- The voice is the Lead writing to the implementing developer. Imperative, concrete, calm. Guidance names the seams that already exist and says do not where a tempting wrong turn exists.
- The redundancy rule from the SOP: the review adds context and information not already in the story. Reference acceptance criteria by their IDs (AC-3, AC-7) when pointing at them, never restate their content. The review sits one layer below the story: the story says what and why, the review says where and how.
- Every claim about current code must be verified in the actual repo the story targets. Every file path in the review must either exist (verify with a mechanical check, not memory) or be explicitly marked as new. A review that names a file that does not exist burns the Lead's trust and is worse than no review.
- Do not reuse another story's Current State bullets without re verifying them. Twenty similar stories reviewed by one skill invites copy paste sameness, and each story deserves fresh eyes on the code.
- This skill never closes the epic's Dev Review ticket (a human does that when the whole epic is reviewed), never creates LaunchDarkly flags (no dashboard access, assess and report instead), and never sets an Original Estimate above 10 hours under any circumstances.

## Phase 0, where does this story stand

1. Validate the key looks like `[A-Z]+-[0-9]+`.
2. Locate the story's docs directory: `find .claude/docs/epics -mindepth 2 -maxdepth 2 -type d -name "<KEY>"`. If the story has no directory yet, create `.claude/docs/epics/<EPIC>/<KEY>/` after fetching (the epic comes from `fields.parent.key`).
3. If `<dir>/review.md` already exists, ask the user: apply it to Jira as approved, redraft from scratch, or revise specific sections. Otherwise enter the draft phase.

## Phase 1, draft

1. Fetch the story fresh. If a local `story.md` exists, render it with the epic's md2adf tooling if available and diff against the fetched description. The PM edits stories directly in Jira, so treat the fetched version as truth and flag any drift to the user before proceeding. Diff on extracted text and on normalized ADF (drop localId, colwidth, width, and empty attrs objects), a structure only difference with identical text is Jira editor normalization, not drift.
2. Load context, in this order: the epic's `summary.md` if one exists (implementation context maintained for agents), `epic.md`, the spike deliverables the story references, and the mock if the story has one. The mock is the visual spec, the review should point the developer at the exact mock screens.
3. Research the code. This is the load bearing step and is never optional:
   - Current State claims come from reading the real models, routers, components, hooks, and permission constants. Note what exists, where, and what does not exist yet.
   - Identify the established patterns the story should reuse: the closest existing grid, form, modal, router procedure, notification path, seed, migration, or flag wiring. Name them by path.
   - For datamodel stories, read the actual schema and write the intended model changes as a real Prisma block in the repo's conventions.
   - Identify cross story seams: what this story owns, what its neighbors own, where the boundary is. The epic's dependency spine matters here.
   - Where the story's shape is itself in question, how deep a module should be, where a seam belongs, or what an interface should expose, call the Skill tool with "codebase-design" and use its vocabulary. Say seam rather than boundary in the review: boundary is overloaded with bounded context and reads as a domain claim the story is not making.
4. Write `<dir>/review.md` following the anatomy below.
5. Write the Relationships section from the story's Requires notes, the epic's story map, and the code seams found in research, including the Not blocked by denials for dependencies a reader might wrongly assume.
6. Estimate per `references/estimation.md`: derive from the code paths table, show the breakdown, round up to the half hour, sanity check against the anchor table. Bugs and small tasks default to 2 hours. Stories normally land in 2 to 8. Above 8, the review must argue the split and sketch it. Above 10, no estimate is proposed at all until the split is resolved.
   When the story is oversized because it is a wide refactor, one mechanical change whose blast radius fans across the codebase (renaming a column, retyping a shared symbol), the split is not a vertical slice and should not be sketched as one. Sequence it as expand, then migrate, then contract: add the new form beside the old, migrate the call sites in batches sized by blast radius with each batch its own story, then delete the old form once no caller remains. Every batch stays green because the old form still exists until the last one, which is what keeps the epic deployable throughout. Sketch the split that way and estimate the batches, not the whole.
7. Choose the Story Risk Level by uncertainty, not size: LOW is pattern following work any dev can do, MEDIUM has some novel modeling or ambiguity, HIGH is a complex subsystem for a lead, CRITICAL is rare and means top devs collaborating. State the level and one sentence of why in the draft.
8. List dependencies for the apply phase, derived from the Relationships section: the story's own Requires notes cross checked against the epic's story map, expressed as intended Blocks links (blocker first). Note which links already exist in Jira.
9. Draft the testing rows: Risk Mitigation rows (Risk Summary, Risk Description, Priority, Likelihood, Mitigation Strategy, empty Mitigation Proof) and Test Cases rows (Summary, Steps, Expected Results, empty Working Feature Proof, Notes). Where a mitigation strategy is an automated test, mark it as a proposed subtask with its type, coverage, and estimate. Keep rows to the ones that matter, three to six of each, not padding.
   A proposed subtask is a promise about what gets tested, so name the behavior the test must demonstrate rather than the file it will live in. The `ticket` skill treats these subtasks as its test first cases in Phase 2, so a vague subtask becomes a vague test.
10. Assess the LaunchDarkly flag situation for the epic and note it in the draft's handoff section: which flag the epic needs or has, and that creation and the Releases field connection are manual steps.
11. Run the checks:
    - Path check: extract every repo path mentioned in `review.md` and verify each exists on disk, or is marked (new). Fix or mark every miss.
    - Prose sweep: call the Skill tool with "unslop" and apply its pattern detection to `review.md`, meaning puffery, superficial -ing phrases, "not just X but Y", vague attributions, and rule of three padding, alongside the punctuation rules above. Skip its "Adding soul" section, the voice here is the Lead writing to the implementing developer, not a blog. Henry reads every one of these, and a review that reads as machine generated loses its credibility before its content is weighed.
    - Anatomy check against the section list below.
    - Confirm no Jira write has happened.
12. Stop. Hand the user the draft with the estimate, risk level, dependency list, and any split recommendation surfaced in the summary, and wait for their review. Do not apply in the same run unless the user has already told you to.

## Phase 2, apply

Entered only after the user has seen the draft and said go.

1. Re fetch the story and re check drift. If the description changed since drafting, stop and show the diff.
2. Render and write the field: `python3 scripts/review2adf.py render <dir>/review.md` and PUT it to `customfield_10122` via `{"fields": {"customfield_10122": <adf>}}`.
3. Set the estimate and risk level from the approved draft: PUT `{"fields": {"timetracking": {"originalEstimate": "<Nh>"}, "customfield_10146": {"value": "<exact option string>"}}}`. The risk options are LOW, MEDIUM, HIGH, CRITICAL with their full descriptive strings, and their trailing whitespace is inconsistent (LOW and HIGH end with a space, MEDIUM does not), so always fetch editmeta and copy the exact string rather than typing it. Never set above 10h. If the draft recommended a split, do not set an estimate, tell the user the split question blocks it.
4. Create the missing dependency links: for each intended link not already present, POST `/rest/api/3/issueLink` with type Blocks, `inwardIssue` the blocker and `outwardIssue` the blocked. This direction is empirically verified: it renders as the blocked story is blocked by the blocker. Always re fetch the links after creating and read the rendered direction, an inverted link silently poisons planning. Fetch existing links first and skip duplicates.
5. Fill the testing tables: fetch the current `customfield_10129` (risk) and `customfield_10253` (test cases) field JSON, write the draft's rows as a markdown table matching each table's column count, merge with `scripts/review2adf.py tables <field.json> <rows.md>`, and PUT the result. The merge preserves the instruction panels and header rows, never replace those fields with a from scratch document.
6. Create proposed test subtasks the draft called for: POST issue with the Subtask issue type, parent set to the story, a one line description of the test type and coverage, and its estimate.
7. Verify: re fetch the story, re render `review.md`, and compare the stored field against it normalizing `localId`, `colwidth`, and `width` attrs, and treating empty `attrs` objects as absent (Jira's editor adds `attrs: {}` to paragraphs on any resave, it is not drift). Confirm the estimate, risk level, links, and tables landed. Report exactly what was set and what remains manual (LaunchDarkly, closing the epic's Dev Review ticket).

## The review anatomy

`review.md` uses the template's sections shaped the way the team's real reviews shape them (see `references/example.md`):

- `## 1. Objective`, one or two sentences, no technical detail.
- `## 2. Current State`, bullets, factual, each grounded in a verified path: what exists, what pattern it demonstrates, what does not exist, what neighboring stories own. N/A only for genuinely net new surfaces.
- `## 3. Desired State`, observable behavior after the story, not implementation.
- `## 4. Scope`, four subsections:
  - `### In scope`, concrete bullets of what this story includes.
  - `### Developer acceptance criteria`, opening with the house convention line, `The story's AC-1 through AC-N remain authoritative and unchanged.`, optionally followed by one sentence naming any delta the review introduces. Then the implementation grade conditions the reviewer holds the work to: which seams are used, what is not imported or duplicated, what the tests must demonstrate. These complement the story's ACs, referenced by ID, never restated.
  - `### Explicitly out of scope`, the guardrails, including what neighboring stories own.
  - `### Estimate`, the work and hours breakdown table, then `Recommended Jira original estimate: N engineering hours.` Then the risk level line with its one sentence reason. If over 8 hours, the split argument lives here.
- `## Relationships`, after Scope. The dependency picture in prose the developer can act on, four labels with a one line reason each: `Blocked by:` (what interface or schema this needs and from whom), `Blocks:` (what downstream work waits on this), `Not blocked by:` (dependencies a reader might wrongly assume, explicitly denied with the reason), and `Coordinates with:` (stories touching the same surfaces where behavior must line up without a blocking edge). The reasons matter more than the list, and the Not blocked by line is often the most valuable, it kills false serialization. The Handoff data links are derived from this section.
- `## 5. Suggested Implementation`, opening with `### Code paths and intended updates`, a two column table, one row per file including test files, each intent one or two sentences. After the table, intended code shape snippets as fenced code blocks where shape matters (the authorization check, the Prisma block, the derivation function), and do not guidance naming the existing seams that make workarounds unnecessary.
- `## 6. Happy Path`, a numbered straight through flow of the feature working.
- `## 7. Edge Cases to Consider`, two subsections: `### Provided` (from the story and its notes) and `### Added during dev review` (the reviewer's own, this is where the review earns its keep).
- `## Unresolved product inputs before final approval`, only when genuine open inputs exist, never manufactured. Each item is a decision phrased for its owner (PM decision, PM/UX decision, or a named coordination with another story's review), with the options stated and, where the choice changes the estimate, the cost of each option. A dev implementation choice the reviewer can make is not a product input, decide it in the review instead. When this section exists, surface its items in the draft summary to the user, and keep the list to the smallest real set. Where the section holds several items for one owner and they need answering async rather than in a conversation, offer to turn them into a questionnaire with `/to-questionnaire`, which puts them in one document that owner can fill in in a single pass.

The template's Acceptance Checklist and Out of Scope sections fold into Scope as shown above, matching house practice. The template's Developer Responsibilities block is omitted, it belongs to the developer at MR time and the ticket skill's mr.md already answers it.

Below the anatomy, `review.md` ends with a `## Handoff data` section that is not rendered into the Jira field (strip it before rendering): the chosen risk level and reason, the dependency links to create, the testing table rows, proposed subtasks, and the LaunchDarkly note. Keep the cut point clean: `scripts/review2adf.py render` gets a copy of the file truncated at the Handoff data heading.
