epic-story-pr
Move one story from local review or local DONE into a GitHub PR, recording PR metadata on the story file. Optional stage between IN REVIEW and DONE.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Move one story from local review or local DONE into a GitHub PR, recording PR metadata on the story file. Optional stage between IN REVIEW and DONE.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Open, attach, or refresh optional GitHub PR delivery metadata/evidence for one OpenSpec story. Does not change story Status.
Archive a locally completed OpenSpec change workspace after pre-flight checks (review approved, tasks done, PR merged or explicitly waived). Thin wrapper over /opsx:archive.
Absorb structured review/tool, PR, or reviewer feedback into an OpenSpec initiative by routing it to story edits, review rework, story candidates, or initiative-level decisions. Use when feedback needs to be incorporated without bloating or drifting stories.
Interview-driven OpenSpec initiative planning — creates openspec/initiatives/<slug>/initiative.md with goal, context, story candidates, decisions, constraints, and external resources. Use when starting a new body of work that needs an OpenSpec-backed initiative before any change workspaces can be drafted.
Inspect the current or selected OpenSpec initiative, change, or spec state and recommend the single next workflow action with concise reasoning. Use when you need lightweight lifecycle routing before choosing a planning, implementation, PR, feedback, or archive command.
Claim one ready, unclaimed story from an OpenSpec initiative and execute it end-to-end, leaving a clean handoff. Use when starting a fresh session on a new story in an OpenSpec change workspace.
| name | epic-story-pr |
| description | Move one story from local review or local DONE into a GitHub PR, recording PR metadata on the story file. Optional stage between IN REVIEW and DONE. |
| disable-model-invocation | true |
| argument-hint | <epic-name> <story-number-or-spec-file> [pr-url|OPEN=true] |
| allowed-tools | Read Edit Write Grep Glob Bash(git status:*) Bash(git log:*) Bash(git branch:*) Bash(gh pr list:*) Bash(gh pr view:*) Bash(gh pr edit:*) Bash(gh pr create:*) Bash(curl:*) |
Transition a story from 🟣 IN REVIEW to 🔵 IN PR, recording GitHub PR metadata on the step file. This is the optional stage between local review acceptance and merged-to-main (✅ DONE). It can also inject a PR into an explicitly selected, non-archived ✅ DONE story when that DONE meant local completion and the remote PR stage is being added late.
Argument: $ARGUMENTS — <epic_name> <story_number_or_spec_file> [<pr_url_or_OPEN=true>]. Epic and story may be inferred for active 🟣 IN REVIEW / 🔵 IN PR work. ✅ DONE PR injection requires the story to be explicit. The third arg is either a full GitHub PR URL (attach mode) or the literal OPEN=true to have this flow open the PR via gh (open mode). For an explicitly selected ✅ DONE story, omitting the third arg implies open mode after existing PR detection fails.
This flow applies when a story has passed local review and the changes need to go through the normal GitHub PR review and merge process before the story can be marked ✅ DONE. It is not required. Stories that do not need a separate GitHub PR stage skip straight from 🟣 IN REVIEW to ✅ DONE.
Sometimes a story was already marked ✅ DONE because the team treated it as locally complete, then later decides to open a GitHub PR for the same implementation. In that case, run /epic-story-pr <epic> <story>. If the PR is unmerged, this flow records PR metadata and moves the story back to 🔵 IN PR until remote review finishes. If the PR is already merged, it records PR metadata and keeps the story ✅ DONE.
⬜ TODO — not started🔄 IN PROGRESS — actively being worked🟣 IN REVIEW — local review pass ready🔵 IN PR — this flow — local review passed, PR opened, awaiting GitHub review + merge✅ DONE — PR merged, or local-only review accepted when no PR stage was known⛔ BLOCKED — external blockerThis flow accepts three positional inputs in $ARGUMENTS — <epic>, <story>, and <pr_url_or_OPEN=true> — and runs three independent inference passes for any of them that is missing. Explicit values always win and skip their corresponding inference pass. The goal is that an operator who has just claimed or resumed exactly one story can run /epic-story-pr with no arguments at all.
Parse $ARGUMENTS first. Treat any of the three slots that is empty as a request to infer.
<epic> is empty)<cwd>/agent_coordination/epics/*/ directories that contain a MASTER.md.MASTER.md story tracker has at least one row whose status is not ✅ DONE and whose Spec link does not point into archive/.inferred epic: <name> (single active epic).no active epic found under agent_coordination/epics/. Pass <epic> explicitly, or create one first.multiple active epics; pass <epic> explicitly to disambiguate.<story> is empty)After the epic is known, read <epic>/MASTER.md and collect every story row whose status is one of:
🟣 IN REVIEW — the canonical entry condition for this flow🔵 IN PR — included so re-running this flow for refresh works without argsDo not auto-infer ✅ DONE stories. DONE PR injection requires the story argument because DONE rows are completed history, not active work. If <story> was passed explicitly and the row is non-archived ✅ DONE, accept it for the late PR injection path.
inferred story: <NN> — <title> (status: <emoji>).🔄 IN PROGRESS, say: no story is in review yet. Story <NN> — <title> is still in progress; finish implementation and run /epic-story-review <epic> <NN> first.🔄 IN PROGRESS, list them and recommend /epic-story-review for the one the operator means.no story is in review or in progress. Run /epic-story-claim <epic> to start one.<step> | <status> | <title> and abort with: multiple stories are eligible; pass <story> explicitly to disambiguate.<pr_url_or_OPEN=true> is empty)After the story is resolved, decide whether this is an attach (existing PR) or open (new PR) operation. Walk the inference chain in order:
PR Tracking section. Read the resolved step file and look for a ## PR Tracking section. If it exists and has a PR URL: <url> line, that is the existing PR. Use attach mode in refresh form. Print: inferred PR (from PR Tracking): <url>. Skip the rest of the chain.
✅ DONE and PR Tracking points to an unmerged PR, report the status drift and ask what to do before changing files. Recommend moving the story back to 🔵 IN PR and refreshing PR metadata. If the user declines, abort without changing MASTER.md, the story file, or the PR.Project repo detection. Parse the story's ## Active Claim section for the Primary write surfaces: field. Take the first path. Walk up the directory tree until you find a .git/ directory; that is the project repo. If no .git/ is found, fall back to the workspace .git/ if one exists. If still none, skip directly to step 4.
Branch-based PR lookup.
git rev-parse --abbrev-ref HEAD to get the current branch.main/master is not how PRs are opened).gh pr list --head <branch> --state open --json url,number,headRefName,title from inside the project repo.inferred PR (from current branch <branch>): <url> and ask the user to confirm before attaching. The branch may legitimately host work unrelated to this story.<number> | <title> | <url> and ask which to attach.Fall through to OPEN mode. If no existing PR was found by any previous step:
✅ DONE story, treat OPEN=true as implicit and proceed to open mode without an extra confirmation.no existing PR found for branch <branch>. Open a new one via gh? [Y/n]OPEN=true path in "PR creation mode".pass <pr_url> explicitly when one exists, or rerun with OPEN=true to open a fresh PR.Before doing any work that affects MASTER.md, the PR, or the step file, print a single resolved-context block so the user can verify what was inferred:
Resolved context:
- epic: <name> (explicit | inferred from single active epic)
- story: <NN> — <title> (explicit | inferred from single eligible row)
- status: <emoji>
- PR: <url> (explicit | from PR Tracking | from current branch | new via OPEN)
Print this even when everything was passed explicitly — the printout is the contract the user approves before any destructive step runs.
Once the story is resolved, abort fast unless its current status is one of 🟣 IN REVIEW, 🔵 IN PR, or ✅ DONE.
🟣 IN REVIEW is the canonical entry condition for opening or attaching a PR.🔵 IN PR is refresh/resync mode per "Refresh existing PR metadata" below.✅ DONE is allowed only when the story was explicitly selected and the Spec link is not in archive/. Treat it as late PR injection from local DONE. Do not require a second confirmation after explicit epic + story selection, but still ask before attaching a branch-inferred PR and before overwriting a substantial PR body.✅ DONE stories are ineligible. Abort with: Story <NN> is archived. PR injection only works for non-archived DONE stories.Also abort when the matched row has a Plan column and Plan is not 🟢 PLAN APPROVED. Recovery hint: run /epic-story-plan-converge <epic> <story> before opening, refreshing, or merging PR state.
Abort for IN PROGRESS, BLOCKED, TODO, or any unknown status with a recovery hint naming the correct preceding command.
Primary write surfaces. If a story's surfaces span multiple repos, pass the PR URL explicitly.Active Claim section cannot have their project repo inferred. This usually means the story has never been claimed via /epic-story-claim / /epic-story-resume. Pass the PR URL explicitly in that case.This is the most important rule of this flow. The PR body is a spec-vs-code verification contract for the GitHub reviewer, not a developer diary. The reviewer must be able to answer one question from the PR body alone: does this code deliver what the story promised?
Extract only product-facing content from the resolved step file:
Covers: A<n> and help the reviewer understand expected behaviorTriggering Need, Purpose,
Scope, or other product-facing prose, when presentSuppress anything that describes how the code was implemented rather than what it delivers:
## Active Claim (session-local metadata)## Progress Log (implementation diary with timestamps)## Session Handoff (inter-session handoff notes)## Review Log (prior review round notes)Test the inclusion boundary: if a reviewer could hypothetically accept a completely different implementation that still passes the Acceptance criteria and preserves the Contract changes, then the piece you're considering does not belong in the PR body.
Generate the body using this structure. Omit any section that has no content rather than writing "N/A".
## Summary
<one short paragraph in product language — the user-visible outcome this PR delivers>
## Original tickets
- <optional label>: <url>
## Requirements
<bulleted list extracted from the step file's Purpose / Goal>
## Acceptance criteria
<bulleted list extracted from the step file's Acceptance section, rephrased in reviewer-verifiable terms>
## Contract changes
<only if the story changes external contracts: config keys added/removed/renamed, CLI flag changes, API/file-format changes, migration requirements, user-visible defaults>
## Out of scope
<bulleted list from the step file's Out of Scope section>
## How to verify
<user-facing verification steps a reviewer can run or inspect without reading the code>
## Epic reference
- Epic: <epic name>
- Story: <story number> — <story title>
- Step file: <relative path to step file>
Read these sections of the step file in order and map them to the body:
## Purpose / ## Goal → Summary + Requirements## Triggering Need, ## Purpose, ## Scope, and any visible
product-facing prose → explicit original ticket/card links only. Include
links near the top when found; omit ## Original tickets silently when no
link is found.## Actors → Requirements only when role context is product-facing## Scenarios / Behavior Examples → Acceptance criteria only for normative scenarios linked with exactly one Covers: A<n>; omit orientation-only examples## Acceptance / ## Acceptance criteria → Acceptance criteria## Scope → filter for contract-affecting parts only → Contract changes## Out of Scope → Out of scope## Verification → filter for user-facing checks only → How to verifyFor original ticket/card links:
Do not paste sections verbatim if they contain internal terminology. Rephrase into reviewer-facing language. A reviewer who has never seen the step file should understand the PR body.
Attach mode (default) — a PR URL is provided:
https://github.com/<org>/<repo>/pull/<n>)gh pr view <PR_URL> --json number,title,headRefName,state,url,body to enrich metadata and read the current bodygh pr edit <PR_URL> --body-file <tmpfile>
gh is unavailable, skip enrichment and the body edit, record only what the user provided, and tell the user the PR body was not updatedOpen mode — user passes OPEN=true with no URL, or explicitly selects a ✅ DONE story with no URL and no existing PR was found:
git status is clean or only contains intended changesgh pr create --title "<story title>" --body-file <tmpfile>✅ DONE story, leave MASTER.md, the story header, and ## PR Tracking untouched.gh fails or is unavailable, abort fast and ask the user to open the PR manually and rerun in attach mode. For a ✅ DONE story, leave MASTER.md, the story header, and ## PR Tracking untouched.In both modes, never force-push, never bypass hooks, never rewrite history without explicit user confirmation.
Add or refresh a ## PR Tracking section in the resolved step file:
## PR Tracking
- PR URL: <url>
- Number: <n>
- Title: <pr title>
- Branch: <head ref>
- Opened at: <UTC ISO timestamp>
- PR status: open | changes_requested | approved | merged | closed
- Merge commit: <sha or "—">
- Last synced: <UTC ISO timestamp>
Append a timestamped bullet under ## Progress Log. Use the entry that matches the transition:
- <UTC ISO timestamp> Moved step to `🔵 IN PR` — <PR URL>
- <UTC ISO timestamp> Reopened remote review from local `✅ DONE`; moved step to `🔵 IN PR` — <PR URL>
- <UTC ISO timestamp> Attached merged PR to local `✅ DONE` story — <PR URL>
Do not create a duplicate PR Tracking section. If one already exists, update its fields in place.
When the story file has a recognizable header status, update it with the same status written to MASTER.md. If the header is missing or ambiguous, leave it unchanged and report that MASTER.md remains authoritative.
If the step is already 🔵 IN PR and the user reinvokes this flow, treat it as a refresh:
gh pr view if available and update PR status, Merge commit, and Last syncedPR status is now merged, transition the step to ✅ DONEPR status is changes_requested or the reviewer requested code changes, transition the step back to 🔄 IN PROGRESS and record the reason under ## Progress Log. Tell the user to rerun /epic-story-resume to address the feedback.🔵 IN PR and update Last syncedIf the step is ✅ DONE and already has ## PR Tracking for an unmerged PR, report the DONE/PR drift and ask what to do. Recommend moving the story back to 🔵 IN PR and refreshing metadata. If the user declines, abort without write-back.
Before transitioning to ✅ DONE, check ## Active Claim -> - Worktrees: for uncommitted changes. If any worktree is dirty (e.g., local metadata or post-PR adjustments), offer to commit before marking done. If the step has no ## Active Claim section, skip.
Update the selected row in <epic>/MASTER.md:
| From | Action |
|---|---|
🟣 IN REVIEW | set to 🔵 IN PR |
🔵 IN PR (refresh, PR still open) | leave at 🔵 IN PR |
🔵 IN PR (PR merged) | set to ✅ DONE |
🔵 IN PR (PR requests code changes) | set to 🔄 IN PROGRESS |
✅ DONE (explicit, non-archived, PR still open) | set to 🔵 IN PR |
✅ DONE (explicit, non-archived, PR merged) | leave at ✅ DONE |
If the epic's MASTER.md Legend section does not list 🔵 IN PR, add it immediately after the 🟣 IN REVIEW line:
- `🔵 IN PR` — local review passed, PR opened, awaiting GitHub review + merge
✅ DONE from this flow unless the PR is actually merged. Merged means gh pr view --json state returns MERGED, or the user explicitly states so with a merge commit.gh pr create call in open mode).🔵 IN PR story. /epic-squash skips them by design.Last synced stale across transitions.Progress Log, Active Claim, Session Handoff, or Review Log content into the PR body. Those sections are implementation diary, not product contract.✅ DONE requires explicit story selection.✅ DONE after the operator chooses to proceed. Move both MASTER.md and a parseable story header to 🔵 IN PR.State:
MASTER.mdgh enrichment was used/epic-story-resume to address PR feedback/epic-story-pr with the same PR URL to resync PR state/epic-squash once the story is ✅ DONE and stable