@henryqw/pi-pr 6.0.8 → 6.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  # `@henryqw/pi-pr`
2
2
 
3
- See the current-branch pull request in the Pi footer, then run `/pr` for its next safe step. It
4
- shows CI, review, merge, and lifecycle status without repeated `gh` commands.
3
+ See the current branch pull request in the Pi footer. Use `/pr` to run its next safe step. It shows CI, review, merge, and lifecycle status without repeated `gh` commands.
5
4
 
6
5
  ## Install
7
6
 
@@ -9,60 +8,9 @@ shows CI, review, merge, and lifecycle status without repeated `gh` commands.
9
8
  pi install npm:@henryqw/pi-pr
10
9
  ```
11
10
 
12
- Requires an authenticated GitHub CLI session (`gh auth login`) and a checkout on GitHub.com or
13
- GitHub Enterprise. Run `gh auth status` to verify authentication.
11
+ Requires an authenticated GitHub CLI session (`gh auth login`) and a checkout on GitHub.com or GitHub Enterprise. Run `gh auth status` to verify authentication.
14
12
 
15
- ## Works with
16
-
17
- | Package | Relationship | Purpose |
18
- | --- | --- | --- |
19
- | [`@henryqw/pi-footer`](https://pi.henry.wang/extensions/pi-footer) | Improves | Shows current-branch pull-request status in the footer. |
20
- | [`@henryqw/pi-herdr`](https://pi.henry.wang/extensions/pi-herdr) | Improves | Adds the pull-request number to a Herdr workspace label after creation. |
21
- | [`@henryqw/pi-process`](https://pi.henry.wang/packages/pi-process) | Required | Runs bounded child processes used by Git and GitHub workflows. |
22
-
23
- ## Use
24
-
25
- In a GitHub checkout, run `/pr`. It reads fresh pull-request and local state, then runs one route or
26
- explains why action is blocked. The pull-request hostname selects its GitHub API host, and the
27
- extension works outside Herdr.
28
-
29
- Commands are for people; tools and skills are for the agent. The extension exposes these interfaces:
30
-
31
- | Surface | Type | Purpose |
32
- | --- | --- | --- |
33
- | `/pr [--base BRANCH] [creation instructions]` | command | Discover the current pull request and run its next safe route; base and instructions apply only to creation. |
34
- | `/pr --feedback` | command | Explicitly start or resume the guarded feedback sweep. |
35
- | `pi_pr_create` | tool | Agent-only guarded actions for pull-request creation. |
36
- | `pi_pr_fix_ci` | tool | Agent-only guarded actions for repairing failed GitHub Actions. |
37
- | `pi_pr_sweep` | tool | Agent-only guarded actions for reviewing and resolving feedback. |
38
- | `pi_pr_update_branch` | tool | Agent-only guarded actions for updating a pull-request branch. |
39
- | `pi-pr-comment-sweep` | skill | Agent workflow for triaging feedback and resolving addressed review threads. |
40
- | `pi-pr-create` | skill | Agent workflow for safely creating and publishing a pull request. |
41
- | `pi-pr-fix-ci` | skill | Agent workflow for diagnosing and publishing a scoped CI fix. |
42
- | `pi-pr-update-branch` | skill | Agent workflow for merging the exact base revision into the pull-request branch. |
43
- | `Footer` | ui | Show a linked `PR #number` and one plain-language status. |
44
- | `Widget` | ui | Show one action hint or transient routing status. |
45
- | Confirmation | ui | Ask before linking an inferred branch or squash-merging a pull request. |
46
- | Notification | ui | Report blocked discovery, no-action status, refresh errors, and Herdr rename warnings. |
47
- | `node skills/pi-pr-comment-sweep/scripts/pr-feedback.mjs` | command | Read-only feedback diagnostic CLI; supported commands and arguments are below. |
48
-
49
- For creation, put an optional base branch first. For example, run `/pr --base release/2026 Keep the
50
- title concise.` The base is a branch name, not a host or repository. Only creation accepts the base
51
- and remaining guidance; other routes reject them instead of ignoring them. Run `/pr --feedback` to
52
- explicitly start or resume a feedback sweep for actionable conversation comments that do not select
53
- the sweep automatically. It cannot be combined with a base, other options, or instructions.
54
-
55
- The footer shows the pull request and status. Actionable widgets omit duplicate identity and status.
56
- Each uses one semantic status icon, a space, and a plain `Run /pr to …` route. `✗` marks errors, `!`
57
- warnings, `✓` success, and `●` accent or neutral routes. In TUI, only the icon uses a theme color.
58
- RPC and non-TUI output use the same plain text without ANSI.
59
-
60
- The widget switches to `⠋ Checking pull request…` as soon as `/pr` starts discovery. The braille
61
- spinner animates in TUI mode; RPC receives one plain static line. The footer stays unchanged. The
62
- routing widget clears after route selection and before any prompt, notification, mutation, or
63
- workflow dispatch.
64
-
65
- ### Feedback snapshots
13
+ ## Feedback snapshots
66
14
 
67
15
  Run the bundled read-only diagnostic CLI from the installed package directory:
68
16
 
@@ -73,229 +21,185 @@ node skills/pi-pr-comment-sweep/scripts/pr-feedback.mjs checks [--pr PR] --expec
73
21
  node skills/pi-pr-comment-sweep/scripts/pr-feedback.mjs self-test
74
22
  ```
75
23
 
76
- `fetch` prints a compact feedback index with IDs, kinds, states, authors, locations, and parent IDs
77
- as needed; it does not print comment or review bodies. With `--out`, it atomically replaces `FILE`
78
- as a mode-0600 file but does not change the parent directory's permissions. The saved snapshot still
79
- contains the complete feedback, as does `fetch --json` output. Read one item with `show`.
24
+ `checks` verifies the current pull request and its checks against the full head SHA; `self-test`
25
+ checks local CLI behavior. `pr-feedback.mjs fetch --out FILE` prints a compact feedback index. The index
26
+ includes IDs, kinds, states, authors, locations, and parent IDs as needed. It
27
+ does not print comment or review bodies.
28
+
29
+ With `--out`, it atomically replaces `FILE` as a mode-0600 file. It does not
30
+ change the parent directory's permissions.
31
+
32
+ The saved snapshot still contains the complete feedback. `fetch --json` also
33
+ keeps the complete JSON output. Read one item with
34
+ `pr-feedback.mjs show --snapshot FILE --id ID`.
35
+
36
+ `show` prints one JSON record with its exact stored body and fields. A thread
37
+ record includes child IDs without child bodies. Treat that record as a container.
38
+ Inspect the `thread_comment` IDs directly. Do not call `show` on the parent only
39
+ to find children. Show the parent only when it has no child, or when you need
40
+ parent-level metadata. Issue independent `show` lookups in one tool-call round.
41
+ A nested comment includes only its parent thread's ID, state, and location.
42
+ Missing, unknown, duplicate, or ambiguous IDs fail. `show` does not run Git, call
43
+ GitHub, use the network, or write files.
44
+
45
+ ## Works with
46
+
47
+ **Requires.** [`@henryqw/pi-herdr`](https://pi.henry.wang/extensions/pi-herdr) is the shared Herdr CLI client. It installs with this package.
48
+
49
+ **Uses.** [`@henryqw/pi-process`](https://pi.henry.wang/packages/pi-process) runs bounded child processes. It installs with this package.
50
+
51
+ **Improves.** [`@henryqw/pi-footer`](https://pi.henry.wang/extensions/pi-footer) shows current-branch pull-request status in the footer.
52
+
53
+ ## Use
54
+
55
+ Run `/pr` in a GitHub checkout. It reads fresh pull request and local state, then continues through safe routes in one invocation until it needs external input, meets an ambiguous blocker, waits for CI or review, or merges. The PR hostname selects its GitHub API host, and the extension works outside Herdr.
80
56
 
81
- `show` prints one JSON record with its exact stored body and fields. A thread record includes child
82
- IDs without child bodies; treat it as a container and inspect each `thread_comment` ID directly. Do
83
- not call `show` on the parent only to find children. Show the parent only when it has no child or
84
- when parent-level metadata is needed. Issue independent `show` lookups in one tool-call round. A
85
- nested comment includes only its parent thread's ID, state, and location. Missing, unknown,
86
- duplicate, or ambiguous IDs fail. `show` does not run Git, call GitHub, use the network, or write
87
- files.
57
+ `/pr` takes no flags, prose, or base argument. The package selects the branch base from one `branch.<branch>.gh-merge-base` setting or the validated `origin` default branch. After each published change, the extension rediscovers fresh GitHub mergeability, feedback, and CI before another action; no remembered flags or second `/pr` are needed. A stopped workflow may be resumed with a new `/pr` after the blocker is addressed.
88
58
 
89
- `checks` verifies the current pull request and its checks against the supplied full head SHA.
90
- `self-test` checks the bundled CLI's local behavior.
59
+ | Surface | Type | Purpose |
60
+ | --- | --- | --- |
61
+ | `/pr` | command | Inspect and run the current pull request's next safe route. |
62
+ | Footer | ui | Show a linked `PR #number` and one plain-language status. |
63
+ | Widget | ui | Show one action hint or transient routing status. |
64
+
65
+ The footer already shows the pull request and status. Actionable widgets omit duplicate identity and status. Each uses one semantic status icon, a space, and a plain `Run /pr to …` route. `✗` marks errors, `!` warnings, `✓` success, and `●` accent or neutral routes. In TUI, only the icon uses a theme color. RPC and non-TUI output use the same plain text without ANSI.
66
+
67
+ The widget switches to `⠋ Checking pull request…` as soon as `/pr` starts discovery. The braille spinner animates in TUI mode. RPC receives one plain static line. The footer stays unchanged. The routing widget clears after route selection and before any prompt, notification, mutation, or workflow dispatch.
91
68
 
92
69
  ## Flow
93
70
 
94
- Each footer entry is one linked `PR #number` plus one plain-language status: `N unresolved`,
95
- `draft`, `open`, `approved`, `CI running`, `CI failed`, `changes requested`, `base update required`,
96
- `merge conflict`, `merge-ready`, `merged`, or `closed`. Colors support the text; they do not carry
97
- meaning alone.
71
+ Each footer entry is one linked `PR #number` plus one plain-language status: `N unresolved`, `draft`, `open`, `approved`, `CI running`, `CI failed`, `changes requested`, `base update required`, `merge conflict`, `merge-ready`, `merged`, or `closed`. Colors support the text; they do not carry meaning alone.
98
72
 
99
- ![Flowchart showing /pr reading fresh GitHub and local state, choosing the first matching condition, and stopping after one route](./docs/pr-routing.svg)
73
+ ![Flowchart showing /pr reading fresh GitHub and local state and choosing the next safe route](./docs/pr-routing.svg)
100
74
 
101
75
  ### Routes
102
76
 
103
77
  | Current condition | `/pr` route |
104
78
  | --- | --- |
105
79
  | No current-branch pull request, no published matching ref, safe Git push configuration, and a commit or ordinary pending work | Start pull-request creation. |
106
- | One open pull request inferred from a published matching ref | Confirm the exact `remote/ref`, then link the local branch. |
80
+ | One open pull request inferred from a published matching ref | Revalidate the exact `remote/ref`, link the local branch without another prompt, then rediscover and continue. |
107
81
  | Ambiguous or unsafe discovery | Show the blocked reason and do not mutate Git or GitHub. |
108
- | Base update required or merge conflict | Update from the base branch's current target when the tree is clean and local HEAD equals the PR head. |
82
+ | Open PR with intended uncommitted or ahead local work | Only when local HEAD descends from the published PR head: inspect, scope, commit if needed, validate and push the exact OID with the saved lease. A behind or diverged HEAD blocks commits. Stop and report when ownership is ambiguous or unrelated work cannot be separated. |
83
+ | Confirmed merge conflict | Rebase onto the pinned base commit when the tree is clean and local HEAD equals the PR head. Resolve conflicts only with clear intent; otherwise stop and report. A previously verified rebase resumes guarded publication instead of rewriting HEAD again. |
109
84
  | GitHub Actions job failed | Run the CI fix workflow when the same local prerequisite holds. |
110
85
  | External check or commit status failed | Show `CI failed` as a no-action blocker. |
111
86
  | Changes requested or unresolved review threads | Start or resume the package comment sweep when the same local prerequisite holds. |
112
- | Explicit `/pr --feedback` on an open, configured pull request | Start or resume the sweep when the tree is clean and local HEAD equals the PR head. |
87
+ | New standalone feedback (including conversation comments) | Start or resume a guarded sweep when the tree is clean and local HEAD equals the PR head. |
113
88
  | No-action state | Report the state without taking action. |
114
- | Merge-ready pull request | Ask for final confirmation, recheck fresh state, and squash-merge if confirmed. |
115
-
116
- `pi-pr-create` selects its base in this order: the leading `/pr --base BRANCH`, one
117
- `branch.<branch>.gh-merge-base` value, then the default branch of validated `origin`. It captures
118
- the selected base OID and merge-base. Creation requires a commit ahead or ordinary pending work,
119
- including untracked files. A Git operation in progress does not count as pending work. If the
120
- current branch is the selected base, pi-pr stays silent because GitHub cannot create a pull request
121
- from a ref to itself.
122
-
123
- The base always comes from validated `origin`. The head may use that repository or a fork with the
124
- same GitHub source. Base and head must use the same GitHub host. Other fork relationships stop
125
- before mutation.
126
-
127
- Creation merges the captured base commit before validation and push. It resolves clear conflicts and
128
- stops when the base or conflict intent is ambiguous.
129
-
130
- A configured target never changes branch upstream settings. Without a target, the helper pushes the
131
- captured OID to the local branch ref on validated `origin` and fetches its tracking ref. It leaves
132
- upstream unset. It creates or updates and validates the exact PR before setting and verifying
133
- upstream. A failed setup rolls back only unchanged helper-owned settings. If configuration changed
134
- concurrently, it stops without overwriting it. Retrying `publish` resumes setup without another push
135
- or PR mutation.
136
-
137
- Without a configured push target, discovery checks validated remotes for the same branch ref. One
138
- exact open PR becomes an inferred target. `/pr` names the exact `remote/ref` and asks before linking
139
- it. The extension revalidates the branch, PR, remote OID, and Git configuration before mutation. It
140
- rolls back its upstream and remote-tracking changes if final verification fails.
141
-
142
- Multiple candidate remotes, multiple matching PRs, OID mismatches, and unsafe Git push configuration
143
- block routing. A published ref with no PR also blocks creation. If no candidate ref exists, creation
144
- uses only a validated `origin` destination.
145
-
146
- The creation workflow repeats destination, remote OID, PR, and configuration checks immediately
147
- before pushing. It pushes to the saved validated URL, not a mutable remote name. Every push uses the
148
- saved remote OID as an exact lease. Existing refs must also be ancestors of the captured local OID.
149
- A missing ref uses an empty lease as a create-only compare-and-swap.
150
-
151
- Each helper workflow receives a random run ID and its first action. The run stays bound to one
152
- session, canonical worktree, route, and fresh authority. Helper calls from another run, session,
153
- worktree, or route fail.
154
-
155
- For comment sweeps, `/pr` checks the package recovery file without changing it. It selects `start`
156
- when recovery is absent, and `resume` only when valid recovery matches the fresh route authority.
157
- Invalid recovery stays unchanged and blocks dispatch with its path and reason.
158
-
159
- `/pr --feedback` uses the same discovery, reservation, recovery, and guard checks. It can select the
160
- sweep even when CI failure or merge readiness would otherwise select another route. Without the
161
- flag, route priority stays unchanged. Direct skill or `pi_pr_*` tool calls cannot create route
162
- authority; run `/pr` to reserve a fresh route.
163
-
164
- Only one helper run can exist at a time. Most runs expire when the agent settles. A create or
165
- branch-update conflict stays available for one user-guided continuation, then expires after that
166
- continuation settles. Session replacement and shutdown forget the run without aborting or cleaning a
167
- pending merge.
168
-
169
- After a `/pr` create workflow settles, the extension waits for a refresh that finds a configured
170
- current PR. Failed or empty discovery leaves one rename pending for a later refresh. A restored
171
- configured PR completes the rename even when GitHub reports it as merged or closed. It then prefixes
172
- the Herdr workspace label with `#<number> • `. It removes repeated leading `#<number> • ` prefixes
173
- and legacy trailing ` · PR #<number>` suffixes before adding one current prefix. The remaining
174
- workspace name must be non-empty. It renames only the workspace, requires `HERDR_ENV=1` and a
175
- non-empty, trimmed `HERDR_WORKSPACE_ID`, and does nothing outside Herdr. Each Herdr command has a
176
- 10-second timeout. If Herdr lookup, JSON validation, or rename fails, the PR and normal UI refresh
177
- remain available; the extension warns with `Herdr workspace rename failed: <error>`.
178
-
179
- Current-branch discovery reads pull requests associated with the exact push repository ref. It does
180
- not run a global branch search. It finds a fork-head PR whose base is an upstream repository. A
181
- unique historical match uses the exact remote push-ref OID, not local HEAD.
182
-
183
- A no-action state includes drafts, merged or closed pull requests, unsupported failed CI, pending
184
- review, and blocked merge policy. Running CI blocks merge but not other mutating workflows. A dirty
185
- tree or mismatched local HEAD also blocks a mutating workflow.
89
+ | Merge-ready pull request | Recheck fresh state and squash-merge without another prompt. |
90
+
91
+ `pi-pr-create` selects its base in this order: one `branch.<branch>.gh-merge-base` value, then the default branch of validated `origin`. It captures the selected base OID and merge-base. Creation requires a commit ahead or ordinary pending work, including untracked files. A Git operation in progress does not count as pending work. If the current branch is the selected base, pi-pr stays silent because GitHub cannot create a pull request from a ref to itself.
92
+
93
+ The base always comes from validated `origin`. The head may use that repository or a fork with the same GitHub source. Base and head must use the same GitHub host. Other fork relationships stop before mutation.
94
+
95
+ It does not merge or rebase the base during creation. The package helper inspects and commits selected pending paths, including both sides of a staged rename. After a clean verification and relevant validation, it pushes the captured OID.
96
+
97
+ A configured target never changes branch upstream settings. Without a target, the helper pushes the captured OID to the local branch ref on validated `origin` and fetches its tracking ref. It leaves upstream unset. It creates or updates and validates the exact PR before it sets and verifies upstream. A failed setup rolls back only unchanged helper-owned settings. If configuration changed concurrently, it stops without overwriting it. Retrying `publish` resumes setup without another push or PR mutation.
98
+
99
+ Without a configured push target, discovery checks validated remotes for the same branch ref. One exact open PR becomes an inferred target. `/pr` links the single exact `remote/ref` without a separate confirmation, then rediscovers the same configured PR before continuing. The extension revalidates the branch, PR, remote OID, and Git configuration before mutation. It rolls back its upstream and remote-tracking changes if final verification fails.
100
+
101
+ Multiple candidate remotes, multiple matching PRs, OID mismatches, and unsafe Git push configuration block routing. A published ref with no PR also blocks creation. If no candidate ref exists, creation uses only a validated `origin` destination.
102
+
103
+ The creation workflow repeats destination, remote OID, PR, and configuration checks immediately before pushing. It pushes to the saved validated URL, not a mutable remote name. Every push uses the saved remote OID as an exact lease. Existing refs must also be ancestors of the captured local OID. A missing ref uses an empty lease as a create-only compare-and-swap.
104
+
105
+ Each helper workflow receives a random run ID and its first action. The run stays bound to one session, canonical worktree, route, and fresh authority. Helper calls from another run, session, worktree, or route fail.
106
+
107
+ For comment sweeps, `/pr` checks the package recovery file without changing it. It selects `start` when recovery is absent. It selects `resume` only when valid recovery matches the fresh route authority. Invalid recovery stays unchanged and blocks dispatch with its path and reason. One `/pr` inspects all feedback, records a complete disposition ledger and exact owned paths, then makes scoped fixes, validates, and publishes without a second approval. New sweeps require a clean worktree at the original head before recording the plan; publication validates the clean scoped commit. Recovery preserves the saved plan and checks owned paths and remote authority before continuing. After publication, the helper carries unchanged decisions through a fresh feedback snapshot; new or edited feedback remains blocked for a later fix cycle. It does not expand path ownership or push again in the same sweep.
108
+
109
+ A flagless `/pr` reads complete standalone and inline feedback before merge or waiting when the tree is clean and HEAD equals the configured PR head. It compares feedback against the last finalized sweep. New or edited feedback selects the guarded sweep; comments already assessed in that sweep do not. Immediately before merging, it checks again and cancels if feedback arrived meanwhile. A malformed attention marker is preserved and blocks routing rather than silently losing triage history.
110
+
111
+ Direct skill or `pi_pr_*` tool calls cannot create route authority. Run `/pr` to reserve a fresh route.
112
+
113
+ Only one helper run can exist at a time. Most runs expire when the agent settles. A branch-update conflict stays available for one user-guided continuation, then expires after that continuation settles. Session replacement and shutdown forget the run without aborting or cleaning a pending rebase. A verified rebase can be resumed through a fresh `/pr` run; an unverified rebase intent requires manual recovery, never an automatic retry.
114
+
115
+ After a `/pr` create workflow settles, the extension waits for a refresh that finds a configured current PR. It then prefixes the Herdr workspace label with `#<number> • `.
116
+
117
+ Failed or empty discovery leaves one rename pending for a later refresh. A restored configured PR completes the rename even when GitHub reports it as merged or closed.
118
+
119
+ It removes repeated leading `#<number> • ` prefixes and legacy trailing ` · PR #<number>` suffixes before adding one current prefix. The remaining workspace name must be non-empty. This requires `HERDR_ENV=1` and a non-empty, trimmed `HERDR_WORKSPACE_ID`.
120
+
121
+ It renames only the workspace. Outside Herdr, it does nothing.
122
+
123
+ If Herdr lookup, JSON validation, or rename fails, the PR and normal UI refresh remain available. Each Herdr command has a 10-second timeout. The extension warns with `Herdr workspace rename failed: <error>`.
124
+
125
+ Current-branch discovery reads pull requests associated with the exact push repository ref. It does not run a global branch search. It finds a fork-head PR whose base is an upstream repository. A unique historical match uses the exact remote push-ref OID, not local HEAD.
126
+
127
+ A no-action state includes drafts, merged or closed pull requests, unsupported failed CI, pending review, and blocked merge policy. Running CI blocks merge but not other mutating workflows. A matching verified branch-update recovery takes priority, then matching comment-sweep recovery. Otherwise a dirty tree or ahead local HEAD selects the scoped local publication helper first. A behind or diverged head remains a blocker unless matching recovery can resume.
186
128
 
187
129
  ### Route priority
188
130
 
189
- A missing pull request uses creation. For an existing pull request, the first matching condition
190
- wins:
131
+ A missing pull request uses creation. For an existing configured open, non-draft pull request, verified branch-update recovery is checked first, then matching sweep recovery. They can resume guarded publication or owned edits before the local clean/equal gate. Otherwise, the first matching condition wins:
191
132
 
192
133
  1. Merged, closed, or draft: no action.
193
- 2. Base update required or merge conflict. Run only with a clean tree and equal local and PR heads.
194
- 3. A failed GitHub Actions job. Apply the same local prerequisite. Other failed checks remain blockers.
195
- 4. Changes requested or unresolved review threads. Apply the same local prerequisite.
196
- 5. Waiting or local safety block: no action.
197
- 6. Merge-ready: allow clean local HEAD equal to or behind the PR head. Confirm, then merge directly.
198
-
199
- Ordinary conversation comments do not trigger a route or block a merge. Changes requested and
200
- unresolved review threads can select the package comment sweep. Use `/pr --feedback` when a
201
- conversation comment needs action.
202
-
203
- The comment sweep resolves its bundled helper and references from the installed package skill path.
204
- It does not require an external `jq` executable. After publishing, `refresh` freezes the complete
205
- latest feedback and returns only IDs and kinds. Use `show` to inspect every fresh item. A second
206
- guarded `record` must cover that exact snapshot before resolution or finalization and keeps the
207
- paths from the initial record. The sweep runs existing non-destructive checks on the clean committed
208
- `HEAD` before publishing. Finalization reruns them as a later state guard.
134
+ 2. Dirty worktree or ahead local HEAD: scope and publish intended local work. In-progress Git operations block its helper; unrelated pending paths require an ownership decision.
135
+ 3. Confirmed merge conflict: rebase onto the pinned base OID only with a clean, equal local HEAD. A behind base alone never triggers a rebase.
136
+ 4. Diagnosable GitHub Actions failure: run CI fix with the same local prerequisite.
137
+ 5. Changes requested or unresolved review threads: run the comment sweep.
138
+ 6. New or edited standalone feedback, including conversation comments and review bodies: triage and fix scoped issues without another approval.
139
+ 7. Running CI, pending review, blocked policy, or unsafe local merge state: wait or report the blocker.
140
+ 8. Merge-ready: allow a clean local HEAD equal to or behind the PR head. Revalidate, then squash-merge directly.
141
+
142
+ The sweep finalizes its feedback marker only after checking the complete refreshed generation. GitHub offers no resolution control for standalone comments or review bodies; they are triaged and reported, not marked resolved.
143
+
144
+ The comment sweep resolves its bundled helper and references from the installed package skill path. It does not require an external `jq` executable.
145
+
146
+ After publishing, `refresh` freezes the complete latest feedback, retains unchanged decisions, and blocks new or edited actionable items for the next `/pr` cycle. It needs no second record or approval. `resolve` selects eligible unresolved review threads with no blocked children, posts a commit URL for addressed threads or a one-sentence ledger reason for non-actionable threads, and verifies the returned reply ID before resolving. The helper checks feedback capacity and leaves blocked threads open. If a reply response is lost without a saved ID, recovery stops without replaying or guessing from a matching comment body. `finalize` uses its saved projection and rechecks complete live feedback. Standalone comments and review bodies have no GitHub resolution state; report them without claiming they were resolved.
147
+
148
+ The sweep runs existing non-destructive checks on the clean committed `HEAD` before publishing. Finalization reruns them as a later state guard.
209
149
 
210
150
  ### Refresh
211
151
 
212
- PR discovery starts in the background at session start, so the Pi footer appears before PR status
213
- is ready. On a session switch, the previous status and action hint clear immediately; new ones
214
- appear after discovery. A directory outside a Git worktree stays silent. Other discovery failures
215
- show `PR · status unavailable` and only a generic error.
216
-
217
- The footer and widget refresh after local commits, PR creation, pushes, and each dispatched
218
- workflow settles. During creation, intermediate refreshes wait until the workflow settles. They
219
- also refresh after successful delegated tasks. There is no periodic presentation refresh, so
220
- external changes may leave them stale indefinitely. `/pr` cancels any pending presentation lookup
221
- and reads fresh state before routing or acting; it remains authoritative.
222
-
223
- The create widget stays hidden on a clean branch with no commit ahead. It appears for a commit ahead
224
- or ordinary pending work. It stays hidden during a Git operation and when the current branch is the
225
- selected base. `/pr` replaces any hint with routing feedback while it selects a route. The feedback
226
- clears before route interaction. A dispatched workflow keeps the widget hidden until the agent
227
- settles. Direct and no-action routes refresh it after completion. A failed command restores the
228
- prior hint and schedules a refresh, except when fresh lookup hits the GitHub API quota: it shows the
229
- sanitized message `GitHub API rate limit exhausted; retry after GitHub resets it` and does not
230
- immediately retry.
231
-
232
- Presentation uses route priority, so draft appears before running CI. `/pr` reads fresh state before
233
- routing or merging. The command is authoritative for actions.
234
-
235
- ## State and storage
236
-
237
- The extension records one configured PR identity in the Pi session. It stores only the PR URL,
238
- number, host, head identity, and configured target identity. It does not store lifecycle, CI,
239
- review, merge readiness, or base state. Event-driven refreshes do not add duplicate entries, and no
240
- repository cache file is created.
241
-
242
- Normal discovery always runs first. If the configured remote ref was deleted, the footer and `/pr`
243
- may reload the exact observed PR URL. The current host, repository, branch, remote, ref, and local
244
- HEAD must still match the observation. Repository names use case-insensitive GitHub matching. The
245
- GitHub response must match the observed URL, host, repository, head ref, head OID, and PR number.
246
- GitHub supplies fresh mutable state. Invalid session data is ignored. A failed GitHub lookup stops
247
- routing and cannot start PR creation.
248
-
249
- Comment sweeps own one versioned recovery file per canonical worktree at
250
- `<agent-dir>/config/pi-pr/sweep/<worktree-id>/state.json`. The file is private, bounded to 1 MiB,
251
- and replaced atomically. It contains the frozen PR authority, original head and lease, complete
252
- feedback, exact ledger, owned paths, and mutation attempts. A missing file allows a fresh `start`; a
253
- valid file matching the fresh route authority allows `resume`. Resume rechecks the canonical
254
- worktree, local changes, PR linkage, and remote head under its lock, and reconciles an attempted
255
- push or thread resolution before issuing a new epoch and run ID. Calls from the old run then fail.
256
-
257
- A completed post-publish `refresh` stores the new complete snapshot before any replacement ledger.
258
- Recovery keeps that snapshot in `refresh-pending`, with its new generation and fingerprint; status
259
- exposes only item IDs and kinds. Use `show` with the resumed guard to inspect every frozen item,
260
- then `record` without `ownedPaths` to supply exact complete coverage for that snapshot. Resolution
261
- and finalization stay blocked until this record succeeds. Malformed, oversized, obsolete,
262
- wrong-worktree, or route-mismatched recovery is preserved and blocks dispatch. Never repair, move,
263
- replace, or delete it automatically; report the state path and exact blocker. An unknown mutation is
264
- never replayed. If reconciliation cannot prove its exact result, stop and report the state path and
265
- blocker.
152
+ PR discovery starts in the background at session start, so the Pi footer appears before the PR status is ready. When switching sessions, the previous PR status and action hint clear immediately; the new ones appear when discovery finishes. A directory outside a Git worktree stays silent. The UI shows `PR · status unavailable` for other discovery failures and reports only a generic error.
153
+
154
+ They refresh after local commits, PR creation, pushes, and each dispatched workflow settles. A successful terminal helper action redispatches through fresh discovery before settlement; an incomplete or failed helper does not chain. During creation, intermediate refreshes wait until the workflow settles. They also refresh after any successful delegated task settles. There is no periodic presentation refresh, so external changes may leave the footer and widget stale indefinitely. `/pr` cancels any pending presentation lookup and reads fresh state before routing or acting; it remains authoritative.
155
+
156
+ The create widget stays hidden on a clean branch with no commit ahead. It appears for a commit ahead or ordinary pending work. It stays hidden during a Git operation and when the current branch is the selected base. `/pr` replaces any hint with routing feedback while it selects a route. The feedback clears before route interaction. A dispatched workflow keeps the widget hidden until the agent settles. Direct and no-action routes refresh it after completion. A failed command restores the prior hint and schedules a refresh, except when fresh lookup hits the GitHub API quota: it shows the sanitized message `GitHub API rate limit exhausted; retry after GitHub resets it` and does not immediately retry.
157
+
158
+ Presentation uses route priority, so draft appears before running CI. `/pr` reads fresh state before routing or merging. The command is authoritative for actions.
159
+
160
+ ### Session identity
161
+
162
+ The extension records one configured PR identity in the Pi session. It stores only the PR URL, number, host, head identity, and configured target identity. It does not store lifecycle, CI, review, readiness, or base state. Event-driven refreshes do not add duplicate entries, and no repository cache file is created.
163
+
164
+ Normal discovery always runs first. If the configured remote ref was deleted, the footer and `/pr` may reload the exact observed PR URL. The current host, repository, branch, remote, ref, and local HEAD must still match the observation. Repository names use case-insensitive GitHub matching.
165
+
166
+ The GitHub response must match the observed URL, host, repository, head ref, head OID, and PR number. GitHub supplies fresh mutable state. Invalid session data is ignored. A failed GitHub lookup stops routing and cannot start PR creation.
266
167
 
267
168
  ## Limits and recovery
268
169
 
269
- - `/pr` accepts either standalone `--feedback` or creation syntax with leading `--base BRANCH` and
270
- optional guidance. It rejects unknown or conflicting options. It does not open a browser.
170
+ A comment sweep keeps a private, atomically replaced recovery file under
171
+ `<agent-dir>/config/pi-pr/sweep/<worktree-id>/state.json`. It contains the frozen PR identity,
172
+ original head and lease, full feedback, ledger, owned paths, and mutation attempts. After a push,
173
+ `refresh` stores the new complete snapshot before a replacement ledger; `show` and `record` must
174
+ cover it before thread mutations or finalization. Resume rechecks local state and the remote head
175
+ and reconciles attempted mutations before issuing a new run. Malformed or mismatched recovery is
176
+ preserved and blocks dispatch; never remove it or replay an uncertain mutation to continue.
177
+
178
+ A branch update keeps a private recovery file under `<agent-dir>/config/pi-pr/update-branch/`.
179
+ It records the original lease before rewriting HEAD, then the verified HEAD after checking the clean
180
+ branch against the pinned base. A fresh `/pr` run checks that record and the branch before returning
181
+ the verified result for validation and exact-lease publication; it never repeats Git rebase.
182
+ If a rebase ended without verification, the record is preserved and routing stops for manual
183
+ recovery. After an uncertain push, publication checks the exact remote postcondition without
184
+ replaying the push. Malformed or mismatched records stay unchanged and block routing.
185
+
186
+ - `/pr` accepts no arguments and does not open a browser.
271
187
  - It does not run `/done` or `/sweep`.
272
- - Presentation refreshes do not auto-triage comments or start a workflow. The package comment sweep
273
- starts or resumes only when an explicit `/pr` selects it.
188
+ - Presentation refreshes do not auto-triage comments or start a workflow. The package comment sweep starts or resumes only when an explicit `/pr` selects it. New or blocked standalone comments can select the sweep again after fresh inspection.
274
189
  - It does not enable auto-merge or add a merge queue.
275
- - It does not rebase the local branch, overwrite concurrent remote updates, delete branches, or
276
- clean up worktrees. Creation uses exact leases plus ancestry checks; an empty lease is only an
277
- atomic absence check.
278
- - Creation, discovery, and comment-sweep pushes require one unambiguous push URL for the configured
279
- destination.
280
- - Presentation fetches use that exact push URL and exact advertised OID. They do not use shared
281
- fetch state.
282
- - A pull request that GitHub reports as behind requires a base update.
283
- - Direct merges always use squash. GitHub rejects the mutation if repository policy does not allow
284
- it.
285
- - Before merge, `/pr` fetches the exact head OID from the validated push URL without shared fetch
286
- state.
287
- - A merge, rebase, cherry-pick, revert, or sequencer state blocks direct merge, even when `git
288
- status` is empty.
289
- - A branch update resolves the base repository ref directly. It stops if that ref moves before merge
290
- or push.
291
- - Before a comment-sweep push, it revalidates the configured destination, full PR identity, and
292
- local HEAD. It pushes the captured OID.
293
- - CI repair resolves workflow runs from check-suite IDs. It does not treat HTML details links as
294
- identity.
190
+ - It rebases only after a confirmed conflict. It never rebases merely because the base is behind, overwrites concurrent remote updates, deletes branches, or cleans up worktrees. Creation uses exact leases and an empty lease is only an atomic absence check.
191
+ - Creation, discovery, and comment-sweep pushes require one unambiguous push URL for the configured destination.
192
+ - Presentation fetches use that exact push URL and exact advertised OID. They do not use shared fetch state.
193
+ - GitHub may block merging while the base is behind. That alone does not authorize a rebase.
194
+ - Direct merges always use squash. GitHub rejects the mutation if repository policy does not allow it.
195
+ - Before merge, `/pr` fetches the exact head OID from the validated push URL without shared fetch state.
196
+ - A merge, rebase, cherry-pick, revert, or sequencer state blocks direct merge, even when `git status` is empty.
197
+ - A conflict rebase resolves the base repository ref directly. It stops if that ref moves before rebase or force-with-lease push. Branches with merge commits since the fork point cannot be rebased automatically; preserve their merge resolutions manually.
198
+ - Before a comment-sweep push, it revalidates the configured destination, full PR identity, and local HEAD. It pushes the captured OID.
199
+ - CI repair resolves workflow runs from check-suite IDs. It does not treat HTML details links as identity.
295
200
  - It streams a bounded failed-step log tail and runs one narrow local reproducer before editing.
296
- - Before push, CI repair revalidates the saved destination, open PR, failure evidence, and repair
297
- HEAD.
201
+ - Before push, CI repair revalidates the saved destination, open PR, failure evidence, and repair HEAD.
298
202
  - An already-published local HEAD needs no second push.
299
- - Direct merge requires final confirmation and a fresh readiness check.
203
+ - Direct merge requires a fresh readiness check and an exact head OID; `/pr` is the authorization, not a separate confirmation dialog.
300
204
  - After a successful merge, the create widget stays hidden until a new local commit.
301
205
  - Only authenticated GitHub.com and GitHub Enterprise repositories are supported.
@@ -1,7 +1,7 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
2
  <svg xmlns="http://www.w3.org/2000/svg" width="1280" height="720" viewBox="0 0 1280 720" role="img" aria-labelledby="pr-routing-title pr-routing-desc">
3
- <title id="pr-routing-title">How /pr picks one route</title>
4
- <desc id="pr-routing-desc">The /pr command creates a pull request when needed, gates branch update, feedback sweep, and CI repair on a clean worktree with local HEAD equal to the PR head, and permits direct merge when clean local HEAD is equal to or behind the PR head.</desc>
3
+ <title id="pr-routing-title">How /pr picks each next route</title>
4
+ <desc id="pr-routing-desc">The /pr command creates a pull request when needed, scopes local work before publication, rebases only confirmed conflicts, and checks new feedback before merge, and permits direct merge when clean local HEAD is equal to or behind the PR head.</desc>
5
5
  <defs>
6
6
  <style>@import url("https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&amp;family=Geist:wght@400;500;600&amp;family=Geist+Mono:wght@400;500;600&amp;display=swap");</style>
7
7
  <marker id="pr-routing-arrow" markerWidth="8" markerHeight="8" refX="8" refY="4" orient="auto">
@@ -18,10 +18,10 @@
18
18
  <rect width="1280" height="720" fill="#f0eee9"/>
19
19
 
20
20
  <!-- Header -->
21
- <text x="80" y="68" fill="#101828" font-size="28" font-weight="400" font-family="'Instrument Serif', serif">How /pr picks one route</text>
22
- <text x="80" y="88" fill="#4c5665" font-size="8" font-family="'Geist', sans-serif">Fresh state enters a strict, top-down priority ladder.</text>
21
+ <text x="80" y="68" fill="#101828" font-size="28" font-weight="400" font-family="'Instrument Serif', serif">How /pr picks each next route</text>
22
+ <text x="80" y="88" fill="#4c5665" font-size="8" font-family="'Geist', sans-serif">Fresh state enters a strict, top-down priority ladder after each completed route.</text>
23
23
  <text x="1200" y="48" fill="#4c5665" font-size="8" font-weight="500" font-family="'Geist Mono', monospace" text-anchor="end" letter-spacing="0.12em">FIRST MATCHING CONDITION WINS</text>
24
- <text x="1200" y="68" fill="#4c5665" font-size="8" font-weight="500" font-family="'Geist Mono', monospace" text-anchor="end" letter-spacing="0.12em">AT MOST ONE ROUTE</text>
24
+ <text x="1200" y="68" fill="#4c5665" font-size="8" font-weight="500" font-family="'Geist Mono', monospace" text-anchor="end" letter-spacing="0.12em">FRESH AFTER EACH ROUTE</text>
25
25
 
26
26
  <!-- Continuation arrows, drawn before nodes -->
27
27
  <g fill="none" stroke="#4c5665" stroke-width="1.2" marker-end="url(#pr-routing-arrow)">
@@ -67,10 +67,10 @@
67
67
  <line x1="704" y1="296" x2="704" y2="328" stroke="rgba(16,24,40,0.141)" stroke-width="1"/>
68
68
  <text x="148" y="320" fill="#4c5665" font-size="12" font-family="'Geist Mono', monospace" text-anchor="middle">3</text>
69
69
  <text x="200" y="304" fill="#4c5665" font-size="8" font-weight="500" font-family="'Geist Mono', monospace" letter-spacing="0.12em">CONDITION</text>
70
- <text x="200" y="324" fill="#101828" font-size="12" font-weight="600" font-family="'Geist', sans-serif">Base behind or conflicted</text>
70
+ <text x="200" y="324" fill="#101828" font-size="12" font-weight="600" font-family="'Geist', sans-serif">Dirty/ahead work or confirmed conflict</text>
71
71
  <text x="728" y="304" fill="#4c5665" font-size="8" font-weight="500" font-family="'Geist Mono', monospace" letter-spacing="0.12em">ROUTE</text>
72
- <text x="728" y="324" fill="#101828" font-size="12" font-weight="600" font-family="'Geist', sans-serif">Update if clean + equal</text>
73
- <text x="1136" y="324" fill="#4c5665" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">otherwise no action</text>
72
+ <text x="728" y="324" fill="#101828" font-size="12" font-weight="600" font-family="'Geist', sans-serif">Publish work or rebase conflict</text>
73
+ <text x="1136" y="324" fill="#4c5665" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">safe authority required</text>
74
74
 
75
75
  <!-- 4 -->
76
76
  <rect x="120" y="352" width="1040" height="48" rx="6" fill="#ffffff" stroke="rgba(16,24,40,0.141)" stroke-width="1"/>
@@ -89,7 +89,7 @@
89
89
  <line x1="704" y1="424" x2="704" y2="456" stroke="rgba(16,24,40,0.141)" stroke-width="1"/>
90
90
  <text x="148" y="448" fill="#4c5665" font-size="12" font-family="'Geist Mono', monospace" text-anchor="middle">5</text>
91
91
  <text x="200" y="432" fill="#4c5665" font-size="8" font-weight="500" font-family="'Geist Mono', monospace" letter-spacing="0.12em">CONDITION</text>
92
- <text x="200" y="452" fill="#101828" font-size="12" font-weight="600" font-family="'Geist', sans-serif">Changes requested or unresolved threads</text>
92
+ <text x="200" y="452" fill="#101828" font-size="12" font-weight="600" font-family="'Geist', sans-serif">Changes requested or new feedback</text>
93
93
  <text x="728" y="432" fill="#4c5665" font-size="8" font-weight="500" font-family="'Geist Mono', monospace" letter-spacing="0.12em">ROUTE</text>
94
94
  <text x="728" y="452" fill="#101828" font-size="12" font-weight="600" font-family="'Geist', sans-serif">Sweep if clean + equal</text>
95
95
  <text x="1136" y="452" fill="#4c5665" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">otherwise no action</text>
@@ -103,7 +103,7 @@
103
103
  <text x="200" y="516" fill="#101828" font-size="12" font-weight="600" font-family="'Geist', sans-serif">Other waiting or merge-local block</text>
104
104
  <text x="728" y="496" fill="#4c5665" font-size="8" font-weight="500" font-family="'Geist Mono', monospace" letter-spacing="0.12em">ROUTE</text>
105
105
  <text x="728" y="516" fill="#101828" font-size="12" font-weight="600" font-family="'Geist', sans-serif">No action</text>
106
- <text x="1136" y="516" fill="#4c5665" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">CI running, review or policy pending, dirty, ahead, or diverged</text>
106
+ <text x="1136" y="516" fill="#4c5665" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">CI running, review or policy pending, behind, or diverged</text>
107
107
 
108
108
  <!-- 7 -->
109
109
  <rect x="120" y="544" width="1040" height="48" rx="6" fill="rgba(29,78,216,0.08)" stroke="#1d4ed8" stroke-width="1.2"/>
@@ -121,7 +121,7 @@
121
121
  <line x1="80" y1="636" x2="80" y2="664" stroke="#4c5665" stroke-width="1.2" marker-end="url(#pr-routing-arrow)"/>
122
122
  <text x="104" y="640" fill="#4c5665" font-size="8" font-weight="500" font-family="'Geist Mono', monospace" letter-spacing="0.12em">DOWNWARD CONNECTOR</text>
123
123
  <text x="104" y="664" fill="#101828" font-size="8" font-family="'Geist', sans-serif">Otherwise, continue to the next condition.</text>
124
- <text x="668" y="660" fill="#101828" font-size="28" font-weight="400" font-family="'Instrument Serif', serif">1×</text>
125
- <text x="724" y="640" fill="#4c5665" font-size="8" font-weight="500" font-family="'Geist Mono', monospace" letter-spacing="0.12em">ONE-ROUTE RULE</text>
126
- <text x="724" y="664" fill="#101828" font-size="8" font-family="'Geist', sans-serif">At most one route: first matching condition wins, then /pr stops.</text>
124
+ <text x="668" y="660" fill="#101828" font-size="28" font-weight="400" font-family="'Instrument Serif', serif">↻</text>
125
+ <text x="724" y="640" fill="#4c5665" font-size="8" font-weight="500" font-family="'Geist Mono', monospace" letter-spacing="0.12em">CONTINUATION RULE</text>
126
+ <text x="724" y="664" fill="#101828" font-size="8" font-family="'Geist', sans-serif">The first match runs; completed routes return to fresh discovery.</text>
127
127
  </svg>