Commit eda7731
feat: give issues and PRs an explicit state machine, so the backlog loop can progress work (#658)
* docs: design the issue/PR state machine, whose collapsed In Review column stalled the fleet
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uvZsYtTvvyhGFxjGc29kM
* docs: plan the issue/PR state machine in 13 tasks
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uvZsYtTvvyhGFxjGc29kM
* docs: correct the plan against the real test harness, found in the pre-flight scan
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uvZsYtTvvyhGFxjGc29kM
* fix: report every PR on an issue, which one-to-many made invisible
pr_for returned matches[0], silently discarding every PR past the first --
guaranteed to happen since the no-auto-close rule means an issue routinely
carries a beta PR and a separate graduation PR. Replaced with prs_for, which
returns the full sorted set as `prs: [{number, mergeable, isDraft}]`, and
taught both the open-PR matcher and the merged-PR refs scan the `Refs #N`
verb an intermediate PR is required to use. backlog-rhythm.sh and its tests
follow the same shape change.
* fix: request isDraft in gh pr list so prs[].isDraft stops resolving to null
Review round 1 on Task 2: prs_for emitted isDraft on every PR object, but
the open-PR gh pr list --json list never asked gh for that field, so it was
permanently null in production and only ever populated in tests because the
fixture handed the field over regardless of what --json actually requested.
Added isDraft to the --json selection, and added a test that inspects the
digest's real gh pr list invocation (failing loudly if isDraft is absent from
--json) rather than trusting a fixture that would mask the same regression
again.
* feat: name the merged-but-unreleased phase, and stop a wait rewriting it
column() gains In Verification for an issue whose fix merged to main but
has not yet reached a stable release, and stops letting Awaiting rewrite
the phase -- Status is the phase, Awaiting is the wait, and they are now
orthogonal. Ready for Dev still requires no blocker and no wait, so an
unsettled item still cannot read as dispatchable.
* feat: count resume handoffs, so a twice-dead session is a fact not a feeling
* feat: compute what open PRs already touch, which the collision gate needs exactly
Adds a top-level in_flight_files map (path -> [PR numbers]) to
backlog-digest.sh, built from one `gh pr diff --name-only` per open PR.
No error suppression on that call: a PR whose diff cannot be read must
fail the digest, not silently read as touching nothing.
* feat: make an escalation loud, which suppressing Awaiting made quiet
Awaiting is now signed: reporter/upstream/discussion mean someone else
owes us and stay quiet, while maintainer means the loop cannot advance
without a human decision and must rank first in the pass. Two more
triggers derive an escalation the same way: two handed-back
implementation sessions (resume_count >= 2), and three
CHANGES_REQUESTED review rounds without an intervening approval. Both
are guarded so the same item never also reports resume_implementation.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uvZsYtTvvyhGFxjGc29kM
* test: strengthen the resume-guard escalation test to discriminate
test_two_resume_handoffs_escalate asserted only that escalated fired,
never that resume_implementation was absent, so the and .resume_count
< 2 guard on resume_implementation had no test that would catch its
removal. Add the missing negative assertion; verified it fails with
the guard removed and passes with it restored.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uvZsYtTvvyhGFxjGc29kM
* fix: rank actions right-to-left, which an alphabetical sort had inverted
sort_by(.action, ...) put dispatchable before resume_implementation for no
reason beyond d < m < r, so every backlog-rhythm pass led with start new
work and buried finish started work -- the inverse of the empty-the-board-
from-the-right flow policy. Each action now gets a numeric rank field
(0 = escalations, then rightmost column first, unmatched actions rank last
at 6) and actions sort on rank before action name.
* fix: name every grooming action explicitly, narrow the rank catch-all
set_awaiting, add_card, set_priority, move_card and triage_labels were
landing last only because they fell through an unlabeled else branch --
correct by position, but any future action would silently inherit last
place the same way, with nothing to flag it. Name all five at rank 6
explicitly (a no-op for current ordering), and move the catch-all to a
distinct rank 9 so an action with no rank branch sorts conspicuously after
grooming instead of blending into it.
* feat: cap work in progress at 3, which unbounded WIP had jammed
8 open PRs, all drafts, 6 conflicting, none ever approved -- the
observable shape of unbounded WIP. In Progress and In Review count as
one piece of work (a branch and its PR at two stages); In Verification
does not count, since it has already merged and is only waiting on a
release. dispatchable is suppressed while over the limit, and the WIP
line is now reported on every human-readable pass, including the
quiet path, so a suppressed queue is never silent.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uvZsYtTvvyhGFxjGc29kM
* test: pin that In Verification never counts toward WIP
Review finding on the WIP-limit commit: the exclusion was correct but
unpinned, so a mutation widening the column filter to include
In Verification would pass the existing suite silently. This is a
deliberate semantic (already merged to main, waiting only on a
release, occupying no implementation slot) rather than an accident of
implementation, and exactly the kind of thing a future maintainer
could plausibly "fix" the wrong way.
Verified the new test discriminates: temporarily widened the jq
select to include In Verification, confirmed the test fails
(count 4/over True instead of 0/False), reverted, confirmed it passes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uvZsYtTvvyhGFxjGc29kM
* feat: gate dispatch on file collisions, which advisory prose never prevented
Task 9 of the issue/PR state machine plan. backlog-rhythm.sh now checks a
Ready for Dev candidate's predicted_files against in_flight_files (Task 5)
before reporting dispatchable: a clash queues the candidate behind the
in-flight PR, an unpredicted touch-set blocks dispatch outright, and two
Ready items that would collide with each other are folded into one cluster.
Also closes the gap the removed 2>/dev/null || true left in backlog-digest.sh:
both scripts run under set -euo pipefail, so one PR whose diff gh cannot read
used to abort the whole rhythm pass and block all triage/dispatch. A failed
gh pr diff is now recorded in undiffable_prs instead, and backlog-rhythm.sh
suppresses dispatchable fleet-wide (like the WIP limit) while emitting one
undiffable_pr action per affected PR, reported in both JSON and human output.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uvZsYtTvvyhGFxjGc29kM
* feat: extract the review loop into advance-pr, so advancing a PR costs a step not a session
Every open PR resolves to one action today -- run /implement-issue at $1-4
a session -- which is why 8 PRs sit open, 6 conflicting, none approved.
advance-pr moves Step 11's review loop out into its own skill: one
transition-table row per PR fact, exactly one state advanced per invocation,
then exit. Three callers reach it: implement-issue, backlog-rhythm.sh, and
the maintainer directly -- one copy of the loop instead of a second one
going stale, per backlog/SKILL.md's own argument.
* fix: resolve project-id for the board write, add --limit, handle orphan PRs
Fix round 1 review findings on advance-pr:
- gh project item-edit's ID-based form requires --project-id alongside
--id/--field-id/--single-select-option-id or it fails outright
("project-id must be provided"); add the gh project view resolution step.
- gh project item-list defaults to --limit 30 and truncates silently past
it (57 items on the live board today); pass --limit 200 like
backlog-digest.sh does.
- a PR with no Refs #N / Closes #N has no issue to write Awaiting onto;
stop and report it rather than let the lookup silently return nothing.
* refactor: delegate the review loop to advance-pr, keeping one copy
Step 11 now invokes /advance-pr repeatedly instead of restating the
verdict-handling loop, per the plan in
.superpowers/sdd/2026-08-18-issue-pr-state-machine/. Also requires
Refs #<issue> (not Closes) on every non-graduation PR, teaches Step 0
to open an issue for bare refactors/TODO items, and posts the
resume-handoff marker on resume so double-handoffs are detectable.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uvZsYtTvvyhGFxjGc29kM
* fix: point remaining Step 11 gh-pr-ready references at advance-pr
Fix round 1 review found three surviving lines that told a reader to
run `gh pr ready` directly, or credited Step 11 itself with the flip,
bypassing advance-pr for exactly the case Task 11 moved out of this
skill. Worst was the Step 0 resume table, which told a resumed session
to run `gh pr ready` on an APPROVED PR with no mergeability re-check or
push-after-approval rule -- both now live only in advance-pr -- which
is the #609 failure reintroduced. Reworded all four to route through
advance-pr while keeping each line's original reasoning.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uvZsYtTvvyhGFxjGc29kM
* fix: gate the CHANGES_REQUESTED rework row on whether the caller holds Step 2/3 context
A bare /advance-pr <n> or a backlog-rhythm dispatch has no Step 2 diagnosis
or Step 3 scope assessment to distinguish a real review finding from a
decision already made and rejected deliberately -- exactly the failure
superpowers:receiving-code-review exists to prevent. Split the transition
table's CHANGES_REQUESTED row in two: rework in place only when the caller
is implement-issue Step 11 (holds that context); otherwise collect the
findings and hand back to /implement-issue <n> rather than guess.
* docs: document the state machine the scripts now implement
Rewrites backlog/SKILL.md's states, rhythm action table, flow policy and
issue-card deferral to match backlog-rhythm.sh/backlog-digest.sh as they
actually run today, including two gaps found along the way: resume_implementation
never calls /advance-pr directly (Step 0 does, via implement-issue Step 11),
and predicted_files has no production data source yet, so the collision gate
can only ever report needs_touch_set.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uvZsYtTvvyhGFxjGc29kM
* fix: route a mechanical draft PR through advance-pr, not implement-issue
The rhythm pass handed every unfinished draft PR to a whole implement-issue
session, which is the gap between this design and its own headline promise:
advancing a PR costs a step, not a session. Splits the resume_implementation
PR branch by verdict, using the same reviewDecision/last-non-COMMENTED-review
seam already used elsewhere: a draft needing only a mechanical next step
(no review yet, review in flight, approved but not green) now routes to
/advance-pr <n>; a draft carrying CHANGES_REQUESTED still routes to
/implement-issue <n>, because only that session holds the Step 2/3 context
needed to tell a real review finding apart from a decision already made and
rejected on purpose.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uvZsYtTvvyhGFxjGc29kM
* fix: close the gaps a whole-branch review found in the PR state machine
Seven fixes from a review of the advance-pr/backlog-rhythm/implement-issue
branch:
- CONFLICTING draft PRs now route to /advance-pr instead of a sweep-prs
handoff that writes nothing and re-emits forever (non-draft still goes
to sweep-prs).
- request-pr-review.sh gates its trigger comment on the existing
review_run_state() detector so a review already in flight is never
re-triggered by a second caller.
- advance-pr never edits code: CHANGES_REQUESTED always hands back to
implement-issue, which is the session holding the Step 2/3 context;
this supersedes the earlier "rework in place" carve-out.
- Restored the gh api pulls/<n>/comments command in implement-issue Step
11 so inline review findings are actually fetched (they never show up
in --json reviews or --json comments).
- implement-issue's hard-cap text now says the 3-round escalation is
derived by backlog-rhythm.sh, not written by advance-pr, matching
advance-pr's own "two escalations this skill owns" section.
- mark_ready's detail now names /advance-pr instead of a bare gh pr
ready, so the mergeability re-check and push-after-approval rule
can't be skipped at the one call site an unattended pass reads.
- Deferral (Awaiting/P4) recorded on an issue card now suppresses its
PR's actions the same way a pr_board card does, matching backlog
SKILL.md's documented "one card per unit of work, on the issue" model.
Every guard added or changed here was verified by mutation: inverted or
removed, confirmed the covering test reddens, restored, confirmed green.
See .superpowers/sdd/2026-08-18-issue-pr-state-machine/final-fix-report.md
(gitignored, not part of this commit) for the per-finding evidence.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uvZsYtTvvyhGFxjGc29kM
* docs: fix stale CONFLICTING routing and rework-scope wording
backlog/SKILL.md still routed every CONFLICTING PR to sweep-prs; the
rhythm script has branched on isDraft since the advance-pr split, so a
conflicted draft now goes to /advance-pr and only a non-draft still goes
to sweep-prs. Narrow advance-pr's "never edits code" absolute (and its
implement-issue echo) to what is actually true: it never reworks a
review verdict, not that it never touches the diff at all -- merging
main to resolve a conflict and fixing a red check stay mechanical rows.
Also corrects a stale "blind rework" justification and an operator-facing
rhythm detail string that still said advance-pr does the rework.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uvZsYtTvvyhGFxjGc29kM
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>1 parent f70f387 commit eda7731
11 files changed
Lines changed: 3345 additions & 301 deletions
File tree
- .claude/skills
- advance-pr
- backlog
- implement-issue
- backend/tests
- docs/superpowers
- plans
- specs
- scripts
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
116 | 116 | | |
117 | 117 | | |
118 | 118 | | |
| 119 | + | |
| 120 | + | |
119 | 121 | | |
120 | | - | |
| 122 | + | |
121 | 123 | | |
122 | 124 | | |
123 | 125 | | |
124 | 126 | | |
125 | 127 | | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
126 | 139 | | |
| 140 | + | |
| 141 | + | |
127 | 142 | | |
128 | 143 | | |
129 | 144 | | |
130 | 145 | | |
131 | 146 | | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
132 | 152 | | |
133 | 153 | | |
134 | 154 | | |
135 | 155 | | |
136 | 156 | | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
137 | 165 | | |
138 | 166 | | |
139 | 167 | | |
| |||
194 | 222 | | |
195 | 223 | | |
196 | 224 | | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
197 | 310 | | |
198 | 311 | | |
199 | 312 | | |
| |||
391 | 504 | | |
392 | 505 | | |
393 | 506 | | |
394 | | - | |
| 507 | + | |
395 | 508 | | |
396 | 509 | | |
397 | | - | |
398 | | - | |
399 | | - | |
400 | | - | |
401 | | - | |
402 | | - | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
403 | 514 | | |
404 | 515 | | |
405 | 516 | | |
| |||
409 | 520 | | |
410 | 521 | | |
411 | 522 | | |
412 | | - | |
| 523 | + | |
413 | 524 | | |
414 | | - | |
| 525 | + | |
415 | 526 | | |
416 | 527 | | |
417 | 528 | | |
| |||
453 | 564 | | |
454 | 565 | | |
455 | 566 | | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
456 | 600 | | |
457 | 601 | | |
458 | 602 | | |
| |||
529 | 673 | | |
530 | 674 | | |
531 | 675 | | |
532 | | - | |
533 | | - | |
| 676 | + | |
| 677 | + | |
534 | 678 | | |
535 | 679 | | |
536 | 680 | | |
537 | | - | |
| 681 | + | |
538 | 682 | | |
539 | 683 | | |
540 | 684 | | |
| |||
544 | 688 | | |
545 | 689 | | |
546 | 690 | | |
547 | | - | |
548 | | - | |
| 691 | + | |
| 692 | + | |
549 | 693 | | |
550 | | - | |
| 694 | + | |
551 | 695 | | |
552 | 696 | | |
553 | 697 | | |
| |||
582 | 726 | | |
583 | 727 | | |
584 | 728 | | |
585 | | - | |
586 | | - | |
587 | | - | |
588 | | - | |
| 729 | + | |
589 | 730 | | |
590 | 731 | | |
591 | 732 | | |
| |||
714 | 855 | | |
715 | 856 | | |
716 | 857 | | |
717 | | - | |
| 858 | + | |
| 859 | + | |
| 860 | + | |
718 | 861 | | |
719 | 862 | | |
720 | 863 | | |
| |||
1006 | 1149 | | |
1007 | 1150 | | |
1008 | 1151 | | |
| 1152 | + | |
| 1153 | + | |
| 1154 | + | |
| 1155 | + | |
| 1156 | + | |
| 1157 | + | |
| 1158 | + | |
| 1159 | + | |
| 1160 | + | |
| 1161 | + | |
| 1162 | + | |
| 1163 | + | |
| 1164 | + | |
| 1165 | + | |
| 1166 | + | |
| 1167 | + | |
| 1168 | + | |
| 1169 | + | |
| 1170 | + | |
| 1171 | + | |
| 1172 | + | |
| 1173 | + | |
| 1174 | + | |
| 1175 | + | |
| 1176 | + | |
| 1177 | + | |
| 1178 | + | |
| 1179 | + | |
| 1180 | + | |
| 1181 | + | |
| 1182 | + | |
| 1183 | + | |
| 1184 | + | |
| 1185 | + | |
| 1186 | + | |
| 1187 | + | |
| 1188 | + | |
| 1189 | + | |
| 1190 | + | |
| 1191 | + | |
| 1192 | + | |
| 1193 | + | |
| 1194 | + | |
| 1195 | + | |
| 1196 | + | |
| 1197 | + | |
| 1198 | + | |
| 1199 | + | |
| 1200 | + | |
| 1201 | + | |
| 1202 | + | |
| 1203 | + | |
| 1204 | + | |
| 1205 | + | |
| 1206 | + | |
| 1207 | + | |
| 1208 | + | |
| 1209 | + | |
| 1210 | + | |
| 1211 | + | |
| 1212 | + | |
| 1213 | + | |
| 1214 | + | |
| 1215 | + | |
| 1216 | + | |
| 1217 | + | |
| 1218 | + | |
| 1219 | + | |
| 1220 | + | |
| 1221 | + | |
| 1222 | + | |
| 1223 | + | |
| 1224 | + | |
| 1225 | + | |
| 1226 | + | |
| 1227 | + | |
| 1228 | + | |
| 1229 | + | |
| 1230 | + | |
| 1231 | + | |
| 1232 | + | |
| 1233 | + | |
| 1234 | + | |
| 1235 | + | |
| 1236 | + | |
| 1237 | + | |
| 1238 | + | |
| 1239 | + | |
| 1240 | + | |
| 1241 | + | |
| 1242 | + | |
| 1243 | + | |
| 1244 | + | |
| 1245 | + | |
| 1246 | + | |
| 1247 | + | |
| 1248 | + | |
| 1249 | + | |
| 1250 | + | |
| 1251 | + | |
| 1252 | + | |
| 1253 | + | |
| 1254 | + | |
| 1255 | + | |
| 1256 | + | |
0 commit comments