Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
bump: patch
---

### Fixed

- `/source-control:pull-request` `create` checks each issue the body would close against the diff before writing it: an issue with a done-when item not delivered moves to `Refs: #N` with a `## Remaining` section, and closing keywords appear only on the linkage line. `ready` runs the same check on GitHub's closing references before the flip and leaves the PR in draft on an over-claim.
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,23 @@ gh pr view "$PR_NUMBER" --json headRefOid -q '.headRefOid'
gh pr diff "$PR_NUMBER"
```

## Read the closing claims

The issues GitHub would close on merge, each with its repository, then each issue's body and open
sub-issues read in that repository, and the body's `Siblings:` line. Unlike the Refs issue read,
no read here swallows a failure: a non-zero exit or an empty body is a failed read, which the
phase counts as not delivered.

```bash
CLAIMS=$(gh pr view "$PR_NUMBER" --json closingIssuesReferences \
-q '.closingIssuesReferences[] | "\(.repository.owner.login)/\(.repository.name) \(.number)"') || exit 1
while read -r ISSUE_REPO ISSUE_NUM; do
[[ -n "$ISSUE_NUM" ]] || continue
gh issue view "$ISSUE_NUM" --repo "$ISSUE_REPO" --json state,body,subIssues # a failure here is a failed read
done <<< "$CLAIMS"
gh pr view "$PR_NUMBER" --json body -q '.body' | grep -E '^Siblings: '
```

## Record the skipped steps

One conversation comment per run, after the hold re-check and right before the flip. One
Expand Down
4 changes: 2 additions & 2 deletions plugins/source-control/skills/pull-request/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ For runs spanning 3+ phases, copy `${CLAUDE_PLUGIN_ROOT}/skills/pull-request/tem
| `prep quick` | Phase 1 (fast) | Code errors only, skip simplify and the comment pass |
| `prep review-only` | Phase 1 (partial) | Just review + verify findings |
| `prep simplify-only` | Phase 1 (partial) | Just simplify + comment pass + re-verify |
| `create [--draft '<reason>']` | Phase 2 | Branch-name check + commit + changelog fragments where the repo releases from them ([reference/create.md](reference/create.md) §2.3.3) + push + open the PR as a draft, its body carrying the plan Brief's acceptance criteria when the branch implements a plan and a `## Remaining` section when it links an issue with `Refs:` ([reference/create.md](reference/create.md) §2.4.1), then run `ready` on it when the resolved PR open state is `ready`; `--draft` requests a draft whose `Draft:` line names the reason. Reports the PR URL, its open state and the level that set it, then arms event delivery ([reference/create.md](reference/create.md) §2.6) when the session keeps acting on the PR, else stops |
| `create [--draft '<reason>']` | Phase 2 | Branch-name check + commit + changelog fragments where the repo releases from them ([reference/create.md](reference/create.md) §2.3.3) + push + open the PR as a draft, its body carrying the plan Brief's acceptance criteria when the branch implements a plan and `Closes` only for an issue whose done-when items the diff delivers, else `Refs:` with a `## Remaining` section ([reference/create.md](reference/create.md) §2.4.0, §2.4.1), then run `ready` on it when the resolved PR open state is `ready`; `--draft` requests a draft whose `Draft:` line names the reason. Reports the PR URL, its open state and the level that set it, then arms event delivery ([reference/create.md](reference/create.md) §2.6) when the session keeps acting on the PR, else stops |
| `create --pushed --worktree <path> [--draft '<reason>'] [--hold '<line>']` | Phase 2 (PR-only) | **PR-only entry for an orchestrated flow**, the branch is already committed and pushed (by a dispatched worker), so this skips commit / push / rebase, re-resolves branch and diff from the given target worktree (not the session cwd), and runs body assembly + gates + opens the PR for `<branch>`, then follows the resolved open state like an attended run (the repository level is read at the base branch, never from the worktree). `--draft` requests a draft whose `Draft:` line names the reason; `--hold` puts a `Hold:` line in the body the PR opens with. Used by `/work-items:work`'s orchestrator after its pre-PR gate. See [reference/create.md](reference/create.md) §2.7 |
| `ready` | Phase 2.5 | Merge the base into the branch (never a rewrite of its history), run the code and security reviews over the PR diff in fresh-context subagents (each skipped when its CI review lane is present and healthy, [reference/review-venue.md](reference/review-venue.md)) and the verify gate on the merged head (its test suites skipped when CI's test lanes already passed on that head, reported as `skipped (CI covers it)`; `ready --verify-local` runs them anyway), flip the draft to ready (the base branch's `pre_ready` list can turn off any of the five pre-flip steps; each step's outcome is reported), then hand the PR to `/review:explain-change`, whose `digest_policy` decides whether a digest is offered or built (it gates nothing). Never arms auto-merge: ready starts the AI reviews. Refuses on a branch with no PR, and leaves a held or superseded draft in draft. `ready --unattended` (every lane, loop or dispatched caller) also leaves a draft a person asked for (its `Draft:` body line) in draft. `ready [<pr>] --skip <step>[,<step>...] --reason "<text>"` lets a person present skip pre-flip steps for this run only, reported and recorded in one PR comment, never in the body; refused unattended. See [reference/ready-for-review.md](reference/ready-for-review.md) |
| `ready` | Phase 2.5 | Merge the base into the branch (never a rewrite of its history), run the code and security reviews over the PR diff in fresh-context subagents (each skipped when its CI review lane is present and healthy, [reference/review-venue.md](reference/review-venue.md)) and the verify gate on the merged head (its test suites skipped when CI's test lanes already passed on that head, reported as `skipped (CI covers it)`; `ready --verify-local` runs them anyway), flip the draft to ready (the base branch's `pre_ready` list can turn off any of the five pre-flip steps; each step's outcome is reported), then hand the PR to `/review:explain-change`, whose `digest_policy` decides whether a digest is offered or built (it gates nothing). Never arms auto-merge: ready starts the AI reviews. Refuses on a branch with no PR, and leaves in draft a held or superseded draft, or one whose body closes an issue the diff does not finish. `ready --unattended` (every lane, loop or dispatched caller) also leaves a draft a person asked for (its `Draft:` body line) in draft. `ready [<pr>] --skip <step>[,<step>...] --reason "<text>"` lets a person present skip pre-flip steps for this run only, reported and recorded in one PR comment, never in the body; refused unattended. See [reference/ready-for-review.md](reference/ready-for-review.md) |
| `monitor` | Phase 3 | Watch CI, fix failures, evaluate comments. **Three-tier event delivery: (1) push channel** when your environment ships a forge-events channel (an MCP server delivering webhook events into the session), ~0 idle requests; **(2) Monitor tool** fallback (60s poll); **(3) polling** in cloud/headless sessions, plus the PR-activity subscription where the session offers it. Check the push channel FIRST per [monitor.md](reference/monitor.md) §3.0.05 before falling back |
| `comments` | Phase 3.5 | Evaluate/respond to PR comments only |
| `comments --ci` | Phase 3.5 (CI) | The pr-unblock lane's unattended mode: the runner's kept reviewer-bot threads, or an earlier pr-verify FAIL's notes, only, through its review-thread tool, running no head code. See [reference/comments-ci.md](reference/comments-ci.md) |
Expand Down
29 changes: 26 additions & 3 deletions plugins/source-control/skills/pull-request/reference/create.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,21 @@ on the tracker-bound path, the `refs` item's `ISSUE_BODY` serves.

Persist chosen line(s) into `${CLOSES_LINE}`. NEVER wrap a closing keyword in an HTML comment: `<!-- Closes #N -->` still parses and auto-closes the issue on merge. Fenced code blocks ARE inert, so example snippets are safe.

**Close only what the diff finishes.** Run the siblings step below once on every issue
`${CLOSES_LINE}` would close, then read each such issue (the
[Refs issue read](../../../reference/providers/github/pull-request/create.md#refs-issue-read), or
`ISSUE_BODY` on the tracker-bound path) and mark each of its done-when or acceptance items, and each
open sub-issue, delivered or not. An item counts as delivered when it is already checked, this diff
delivers it, or a pull request on `${SIBLINGS_LINE}` says in its body that it delivered that item,
the credit pr-verify gives (pr-pipeline convention, section "Verifier verdict"); anything else,
and every item of an issue whose read failed or came back empty, counts as not delivered. Any issue
with an item not delivered moves from `${CLOSES_LINE}` to its own `Refs: #N` line (on the
tracker-bound path, the item's `refs` text from `/work-items:track link`), and §2.4.1 lists the
undelivered items under `## Remaining`. When an issue moved, run the siblings step again with the
issues still closed. Never write `does not close #N` (see "Linkage scaffolds" below). pr-verify
still judges the claim after the PR opens; this check keeps the body from claiming more than the
diff delivers.

**Siblings of a closing pull request.** Once `${CLOSES_LINE}` is final, when it closes one or more
GitHub issues, earlier pull requests may have delivered part of one with `Refs: #N`. Name them so
pr-verify judges this diff only for what they left (the pr-verify section of the marketplace's
Expand Down Expand Up @@ -381,10 +396,10 @@ writes into each item. The shape, the scrub, the missing-half rule and the `Summ
condition are the plan line's. A found plan wins outright: a half it lacks is not filled from the
item. With no plan and no item body, add no line.

**Remaining work on a `Refs:` issue.** When the linkage line is `Refs: #N` (or the tracker's
`refs` text) and the body closes nothing, append a `## Remaining` section after the scaffold
**Remaining work on a `Refs:` issue.** For each `Refs: #N` linkage line (or the tracker's
`refs` text), append a `## Remaining` section after the scaffold
blocks, never added to `${REQUIRED_SECTIONS[@]}`, listing as plain bullets what stays open on that
issue: its unchecked `- [ ]` acceptance items, its open sub-issues, or the slices its body lists,
issue (one section, a sub-list per issue when there are several): its unchecked `- [ ]` acceptance items, its open sub-issues, or the slices its body lists,
leaving out any this PR delivers. When the issue has none, write one line naming what this PR
delivers and the condition that closes the issue, with no closing keyword before an issue
number (`#N closes when the export ships`, never `Closes #N when`). The `Summary` (or the first scaffold section)
Expand All @@ -408,6 +423,14 @@ Section content is prose a reviewer reads: plain language, bottom line first, no

**Left out.** The section that records verification (`Verification`, or `Test plan` under the portable default) ends with one `Left out:` line naming what was not run or checked, each with its reason (for example, `Left out: end-to-end suite, needs a staging environment`), so a reviewer can tell a gap from an omission; when everything relevant was run, it says `Left out: nothing`. When that section closes the body, the §2.4.3 attribution line still follows it as the body's footer.

**Closing keywords outside the linkage line.** The only closing keywords in the body are the ones
on `${CLOSES_LINE}`, wherever else a line sits (`## Related` and `## Summary` included); mention any
other issue or pull request without one, for example `delivered by #M` or `follows #N`.

- **Pointer**: GitHub's [Linking a pull request to an issue using a keyword](https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword), for the keywords and where in a body they apply.
- **As of**: 2026-11-04
- **Recheck trigger**: that section changes the keyword list or the parts of a pull request it reads.

**Write the body to a file with the Write tool.** Compose the body as literal text: `${CLOSES_LINE}`; `${SIBLINGS_LINE}` (§2.4.0) when non-empty, at the first column directly below it; when §2.7's `--hold` gave a line, that line on its own line below those, before the first `##` block, so the §2.4.2 gates check the body the PR opens with; a blank line when any is non-empty; then one `## <heading>` block per entry in `${SECTION_ORDER[@]}` with real content, leaving out an optional heading that has none (`Related` carries `${REFS_LINES}` when non-empty, else `N/A`), then the `## Acceptance criteria` block when a plan supplied one and no scaffold heading holds it, then the `## Remaining` block for a `Refs:` link, then the ad hoc `## Related` block when the rule above calls for one. Leave the attribution line out; §2.4.3 appends it after the gates. Write it to an absolute path outside the working tree (the session's scratchpad directory when one is listed, else the system temp directory), for example `<tmp>/pr-body-<branch-slug>.md`, and pass that path as `BODY_FILE` to every call below.

**Why a file, not a shell variable:** the Write tool's content is a JSON string no shell parses, so a `$(...)`, `${...}` or backtick typed into the orphan-PR or multi-issue prompt stays literal, and a long body never pushes a Bash command past the tool's length cap (inline `--body` calls were rejected for length on an unattended lane). The §2.4.2 gates read the same file `--body-file` hands to `gh`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,21 @@ When nothing was pushed, skip the wait and flip as before.
Then run the hold check of 2.5.1 again
([GitHub](../../../reference/providers/github/pull-request/ready-for-review.md#check-for-a-hold)),
since a hold can land during the reviews; on any exit but 0, leave the PR in draft, report the
check's output, and stop. The flip's own shell command also checks that the forge's head for the
check's output, and stop.

**Closing claims.** Then, in every run and on the head that will flip, check each issue the PR
would close against that head's diff the way [create.md](create.md) §2.4.0 does ("Close only what
the diff finishes"). Take the issues from the forge's own list of the PR's closing references, not
from the linkage line alone, and read each in its own repository
([GitHub](../../../reference/providers/github/pull-request/ready-for-review.md#read-the-closing-claims)).
An earlier pull request's credit counts only when that pull request is on the body's own
`Siblings:` line and its body says it delivered the item, as pr-verify credits it (pr-pipeline
convention, section "Verifier verdict"). A closing reference whose read fails or comes back empty
counts as not delivered, never as a pass. This is not a `pre_ready` step: neither the list nor
`--skip` turns it off. When an issue has an item not delivered, leave the PR in draft and report,
per issue, the `Refs: #N` line and the `## Remaining` items the body needs (or the failed read),
the way create.md §2.4.1 writes them; the body is the author's to change, and `ready` runs again
after it changes. The flip's own shell command also checks that the forge's head for the
PR is that local head, then runs `verify-gate.sh check` for it, and goes on only on exit 0: a head
the forge never matched or that moved since, a `fail`, or no result recorded for that head (a
commit after the record voids it) refuses the flip and the PR stays a draft. Otherwise, when
Expand Down Expand Up @@ -439,8 +453,9 @@ Report one line per step, in the order they ran, each with exactly one outcome:
is never reported as passing.

The lines are the hold check, `base-merge`, `plan-evidence`, `code-review`, `security-review`,
`verify`, the push, the CI-run wait (found, or timed out), the hold re-check, the flip, the ready-run
confirmation (found, or toggled draft and ready once with its outcome), the digest (skipped, offered, built, or not
`verify`, the push, the CI-run wait (found, or timed out), the hold re-check, the closing-claims
check (each closed issue delivered, or the issues held back with their undelivered items), the
flip, the ready-run confirmation (found, or toggled draft and ready once with its outcome), the digest (skipped, offered, built, or not
available), the confirm verdict, and the measure verdict when one is recorded. Above them, one line for where the step list came from: the
reader's `source`, and for `fail-closed` its reason. Then choose who watches the PR from here, per
[watch-handoff.md](watch-handoff.md).
Expand Down
Loading