From c5a62be147d40ea1972f422f41005b2d9a6653d7 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Thu, 3 Sep 2026 20:06:17 -0700 Subject: [PATCH] Add the backlog-burndown Skill and Seat Its Orchestration in the Skills It Drives (#1291) Working one issue is `drive-pr`'s job. Working a whole backlog is a different problem, and nothing packaged it: parallelism is bounded by file overlap rather than by agent count, the backlog changes every round as reviews file deferral issues, and a prose-heavy repository conflicts on content rather than on syntax, so two agents rewording one section produce a conflict no tool resolves. ## What this adds `backlog-burndown` is the orchestrator's procedure. It ranks the open issues, groups them so no file is claimed twice, verifies each claim against everything in flight before dispatching, runs at most four workers that each drive their own feature -> develop pull request, opens at most one develop -> main promotion pull request per round for the maintainer to merge, and re-ranks from scratch. It performs no edit of its own and never merges `main`. ## Seating it in the skills it drives Three sibling skills needed seating for a dispatched seat to follow them: - `drive-pr`: a dispatched drive takes its target from the brief rather than asking, and a brief is never itself an authorization, which binds the dispatching seat rather than asking a worker to verify a grant it cannot verify. - `pr-review-conduct`: an agent that cannot reach the maintainer escalates to whoever dispatched it, and the dispatcher asks at the point that work stopped rather than holding the question. - `local-strict-review`: the reviewer's tier is the one the dispatch can name rather than the one a down-tiered worker runs on, with the headless route named for a seat that cannot dispatch at all. `AGENTS.md` carries the map entry and one Session Scope exception, since a run outlives the single deliverable a session normally covers. ## Two destructive defects the review rounds caught The closing-issue derivation would have deleted the backlog it exists to work. It read the issues each feature pull request's body referenced, and a body routinely references the deferral issues its own review round filed, which have to stay open as the next round's ranking input. Feature pull requests now carry an explicit `Closes on promotion:` line naming only what they fix, the worker brief requires it, and the promotion body reads that line and never the mentions. The dead-worker worktree takeover was self-refuting. It said no read distinguishes a dead worker from a slow one, then handed that worktree to a replacement told to commit in it, which is two live tasks in one tree whenever the first was merely slow. Liveness now comes from the dispatch mechanism, a clean tree is retired and its issue re-ranked, and a dirty one is left untouched for the maintainer. ## What this lands with, and what it does not Sixteen review rounds ran over this branch: diff passes over the whole change, and whole-unit carried-content passes over each of the 25 canonical units it moves. The early rounds settled every finding they raised. The later ones hit the churn `backlog-burndown`'s own "Bounding a Prose Group" describes, where "a whole-unit prose review can run many rounds where a finding was introduced by the previous round's fix", and the rule it states for that case is to "land what is correct and file the remainder rather than churning". Two rounds' fixes were themselves worse than what they replaced and are reverted here, with the evidence that disproved them filed. So this lands with a known defect list rather than clean. What it fixes is every finding this branch introduced or made worse, and the root causes running through them: retiring a worktree and deleting its branch were treated as one disposition with one test, and an escalation route added to `pr-review-conduct` was never swept onto the surfaces a dispatched worker reads. What it defers is nineteen filed issues, #1272 through #1290. Two of those are worth reading before the promotion, since they are design calls rather than wording: - **#1290**, the sharpest. This skill's central rule is that no file is claimed by two live groups at once, and in this repository that rule is unsatisfiable, because every canonical-content worker writes `reports/canonical-review.md` and its global counts merge cleanly to a wrong value, failing `report --check` on a later unrelated pull request. - **#1285**, that every worker holds a judgment `AGENTS.md` says never to tier down, since a worker declines review findings by construction. Nothing carries this skill yet, and the remaining defects are contradictions in a procedure document rather than anything executable, which is why filing them beat holding the change. ## Verification Gates green: `build_dist.py --check`, `prose_lint.py`, `spec/validate.py`, `repo_gate` 3/3, `test_build_dist` 28, `test_host_gate` 83, `canonical_review.py check` 21/21 covered. Local strict review recorded against the branch's full diff, and all 22 changed carried canonical units covered by a recorded pass. Co-Authored-By: Claude Opus 5 (1M context) ## Summary by CodeRabbit * **New Features** * Added a backlog-burndown workflow for ranking issues, coordinating parallel work, and managing pull request promotions. * Registered the workflow across supported skill integrations. * **Documentation** * Clarified authorization, reviewer selection, escalation paths, maintainer availability, and promotion procedures. * Updated contributor guidance and canonical review coverage reporting. * Documented handling for blocked work and paused reviews while allowing unrelated work to continue. * **Chores** * Refreshed skill metadata and source tracking information. --------- Co-authored-by: Claude Opus 5 (1M context) --- .agents/skills/backlog-burndown/SKILL.md | 509 ++++++++++++++++++ .agents/skills/drive-pr/SKILL.md | 31 +- .agents/skills/local-strict-review/SKILL.md | 4 +- .agents/skills/pr-review-conduct/SKILL.md | 10 + .../fleet-skills/.claude-plugin/plugin.json | 1 + .claude-plugin/fleet-skills/.source-digest | 2 +- .../skills/backlog-burndown/SKILL.md | 509 ++++++++++++++++++ .../fleet-skills/skills/drive-pr/SKILL.md | 31 +- .../skills/local-strict-review/SKILL.md | 4 +- .../skills/pr-review-conduct/SKILL.md | 10 + .github/skills/backlog-burndown/SKILL.md | 509 ++++++++++++++++++ .github/skills/drive-pr/SKILL.md | 31 +- .github/skills/local-strict-review/SKILL.md | 4 +- .github/skills/pr-review-conduct/SKILL.md | 10 + AGENTS.md | 3 +- reports/canonical-review.json | 200 ++++++- reports/canonical-review.md | 12 +- 17 files changed, 1845 insertions(+), 35 deletions(-) create mode 100644 .agents/skills/backlog-burndown/SKILL.md create mode 100644 .claude-plugin/fleet-skills/skills/backlog-burndown/SKILL.md create mode 100644 .github/skills/backlog-burndown/SKILL.md diff --git a/.agents/skills/backlog-burndown/SKILL.md b/.agents/skills/backlog-burndown/SKILL.md new file mode 100644 index 00000000..47c14be1 --- /dev/null +++ b/.agents/skills/backlog-burndown/SKILL.md @@ -0,0 +1,509 @@ +--- +name: backlog-burndown +description: >- + Burns a ptr727/ProjectTemplate fleet repository's open-issue backlog down by rounds: rank the + open issues, group them so no two groups touch the same file, dispatch one subagent per group to + drive its own feature -> develop pull request to merge, open at most one develop -> main + promotion pull request per round for the maintainer to merge, then re-rank and go again, + because every review round files new issues that change what the next round should pick. Use + this whenever asked to work the backlog, burn the backlog down, clear the open issues, resolve + or cull the backlog, or run issues in parallel until they are gone, and whenever the ask is a + standing one rather than a single named issue. Triggers even when the backlog looks small enough + to work by hand, because the failure it exists to prevent is two agents editing the same + prose-heavy Markdown file in the same round, which surfaces as a merge conflict long after both + branches are already deep in review. Drives one repository, the one the session is in, never a + fleet-wide sweep. Ends when a re-rank finds nothing left it can act on, and never merges main, + which stays the maintainer's own step through merge-and-release. +--- + +# Backlog Burndown + +## Why This Exists + +Asking for one issue to be fixed is `drive-pr`'s job and needs no skill above it. Asking for a +whole backlog to be worked down is a different problem, and three things about it are not obvious. +Parallelism is bounded by file overlap rather than by agent count, so the grouping decides the +throughput. The backlog is not a fixed list, since every review round files deferral issues that +belong in the next round's ranking, so a plan made once is stale by its second round. And a +prose-heavy repository conflicts on content rather than on syntax, so two agents rewording the +same section produce a conflict no tool resolves and no reviewer catches early. + +## The Two Seats + +Everything below turns on which seat is acting, so both are named once here. + +- **The orchestrator** is the session this skill runs in. It ranks, groups, dispatches, and drives + the promotion pull request. It opens no feature branch and fixes no issue itself, which is what + keeps it out of every worker's files. It does write: it comments on issues, it drives and + amends the promotion pull request, and it owns worktree and branch cleanup, which "Dispatching a + Worker" states in full. +- **A worker** is one dispatched subagent holding one group, one worktree, and one feature branch, + which is `AGENTS.md` "Session Scope"'s one-branch-one-deliverable rule applied as written. It + drives its own pull request into develop and ends there. + +## Scope + +One repository, the one the session is in, resolved from its own `origin`. Reads are unrestricted +per `GOVERNANCE.md` "Repository Boundaries and Write Safety", so reading another repository's +issues breaks no rule. Working them is out of this skill's scope, and a fleet-wide backlog +sweep is a different request. That section bounds writes to the owner of +this repository rather than to this repository alone, and a run staying inside the one repository +it was invoked for is narrower than the rule requires, deliberately. + +## What Invoking This Skill Authorizes + +- Naming this skill is the maintainer's explicit go-ahead for the feature -> develop squash merges + this run performs, in every round of it. A per-round merge question would idle every agent at + every boundary, which is the thing this skill exists to avoid. +- **The grant is bounded by the session it was named in.** A run interrupted and resumed in a new + session needs the skill named again, which costs one sentence and is the difference between a + grant and a mode. A grant read back from a note is one nobody gave. +- The grant does not weaken the `pr-review-conduct` Merge Gate. It answers that gate's item 5 for + this run's feature -> develop merges and nothing else, so a pull request with one open finding + still does not merge. +- It is never authorization to merge a develop -> main promotion pull request, to dispatch a + release, to close an issue on judgment, or to touch another repository. Each stays the + maintainer's, and merging a promotion pull request is `merge-and-release`, invoked on its own. + +## The Round + +A round is the unit. Each one runs these steps in order. + +1. **Rank** every open issue, per "Ranking". +2. **Group** the top of that ranking, per "Grouping and File Claims". +3. **Verify** each group's predicted file set against everything in flight before dispatching + anything. A group whose files are already claimed waits for the next round. +4. **Dispatch** at most four workers, one per group, per "Dispatching a Worker". +5. **Collect** each worker's outcome: merged to develop, stopped on a question only the + maintainer can answer, parked behind another group's file claim, or abandoned, which is what + the adjudication in "Grouping and File Claims" and a confirmed-gone worker both produce. Bound + this wait per "Bounding the Wait on a Worker". +6. **Clean up** the worktrees, local branches, and merged remote branches of every group that has + finished or been abandoned, per "Dispatching a Worker". +7. **Promote**, per "The Promotion Boundary". +8. **Re-rank from scratch**, and note that the next round prepares under the freeze "The Promotion + Boundary" describes whenever a promotion pull request is still waiting on the maintainer, so it + ranks, groups, and verifies claims, and dispatches nothing until that merge lands. Do not carry + the previous round's ranking forward. The deferral issues this round's reviews filed are now + open issues with a claim on the next round's attention, and an issue that ranked low last round + can rank high once a sibling fix lands. + +## Ranking + +Where the repository carries no priority label, and the hub does not, the ranking is the +orchestrator's judgment against stated criteria rather than a field read off the issue. Where a +repository does carry one, that label is the first input and these criteria order what it leaves +tied. Write the ranking, and the reason for the top of it, into the report this skill makes at +each round boundary, per "Ending the Run". + +Rank on these, highest first where they conflict: + +- **It blocks other work.** An issue whose fix changes a rule, a gate, or a shared contract that + other issues' fixes must then obey is worth doing before them, not after. +- **It is a correctness or safety defect** in something that runs, over an improvement to + something that reads. +- **It is a root cause rather than a leaf.** A parent issue grouping several filed symptoms is + worth more than any one of its children, and fixing it may close them. +- **It is new.** An issue filed by a recent review round is evidence of something the current + content actually got wrong, and it is the freshest context anyone has on it. +- **It is small and self-contained**, as a tie-break only. Size breaks a tie between two issues of + equal value, and it never promotes a trivial issue over a real defect. + +An issue that asks a question rather than states a defect is not ranked and is never guessed at. +It has no group, no worker, and no claim, so nothing in "Raising a Blocked Question" applies to it +except how the question travels. It goes to the maintainer at the end of +ranking, in the same prompt as any other question the run is sending at that moment and in one of +its own otherwise, rather than waiting for a stop that may not come. It stays unranked until +answered. + +## Grouping and File Claims + +Group so that **no file is claimed by two live groups at once.** This is the rule the skill exists +for, and it binds harder than any throughput target. + +- **Group by the files a fix will touch**, not by the issues' subject matter. Two issues that read + as unrelated but both edit a shared governance file are one group. Two issues that read as near + duplicates but touch different files are two groups. +- **Genuine duplicates are one group.** Never close an issue during triage on the orchestrator's + own judgment. Comment to cross-link the pair, and let the fix close both. +- **Closing keywords go on the promotion pull request**, not the feature pull request, per + `operational-vs-release-workflow`. A feature pull request merging into develop fires no + auto-close, so a `Fixes #N` line there closes nothing. **The feature pull request body instead + carries a line reading `Closes on promotion: #N`**, listing every issue that pull request + actually fixes and nothing it merely mentions, which is the line the promotion body is assembled + from, per "The Promotion Boundary". Without that line nothing records which issues a merged pull + request closes, since the closing keyword is deliberately absent and a body's other issue + references are not the same set. +- **Predict each group's file set** by reading the issues, not by guessing from their titles. +- **Record every claim on the issue itself**, as a comment naming the predicted file set **and the + branch that holds it**, before dispatching. The branch name is what lets a later session walk + from a worktree it found back to the claim explaining it, which is the direction the next bullet + actually travels. Working notes do not survive the session, so a claim living only in them is + invisible to the round that has to respect it, and recording it on the issue is what makes the + next bullet a read of durable state rather than of the orchestrator's memory. +- **Verify the prediction before dispatching**, against everything in flight, which is wider than + this round: the files changed by every open **feature** pull request on this repository (`gh pr + diff --name-only` per open pull request), and the claim comments of every group still + holding a branch, parked groups from earlier rounds included. `git worktree list` reports the + registered worktrees and the branch checked out in each, which is not the same as every branch + that exists, so pair it with `git branch -r`, after `git fetch --prune origin`, for one that was + pushed and whose worktree is already gone, and with `git branch` for one that was never pushed + and whose worktree is already gone. That third read is not optional here: the worktree-only + disposition retires a tree and leaves its branch standing, so this skill produces exactly that + state, and a local branch holding commits no remote has is invisible to both other reads. Prune + rather than plain fetch because `--prune` is what drops a remote-tracking ref whose branch is + gone from the remote, deleted there by another session or through the web interface, and a + plain fetch leaves that ref in `git branch -r` to defer valid groups forever. Stop and report a + failed fetch rather than reading `git branch -r` anyway: the remote-tracking refs still resolve + from what the last successful fetch left, so the scan returns a confident answer about a remote + it did not reach, missing a branch pushed since and keeping one deleted since. The round + stops there and reports, rather than dispatching against a stale answer, and stopping rather + than deferring is what the cleanup and promotion steps need too, since both read the same + remote. + Those three enumerate the branches, with one gap: a branch in the standalone clone `repo-worktree` allows as a fallback is + reached only once it is pushed, since `git branch -r` inventories the remote rather than this + repository's checkouts. The claim comments are what say which files each branch holds, since a branch name says nothing about a file set, an unpushed branch has no + pull request diff to read, and no diff of any branch reports the predicted set a claim records + before the work is committed. **Exclude the open promotion pull request from that enumeration.** + Its diff is all of `origin/main..origin/develop`, so counting it claims nearly every file any + earlier round touched, and a round run during the freeze would defer every group it formed. A + predicted claim that collides with a real one is a group deferred to the next round, never one + dispatched hoping the overlap stays small. +- **A branch no claim comment covers still has to yield a file set.** The maintainer's own + worktree and a hand-driven task's branch are both enumerated above and neither carries a claim + comment, so reading only the comments records them as holding nothing, which is the collision + this section exists to prevent rather than the absence of one. Read the branch itself instead: + `git diff --name-only origin/develop...` for what it has committed, and, for a + registered worktree, `git -C status --porcelain` for what it holds uncommitted. Where + neither read is available the set is unknown rather than empty, and an unknown set collides with + every group, so ask the maintainer what that branch holds per "Raising a Blocked Question". +- **Re-verify when a worker reports that its real file set grew** beyond its claim. A worker + needing a file another group holds stops and reports rather than editing it, and the + orchestrator decides which group keeps the file, then tells the loser which of three things to + do rather than leaving it to choose: narrow its change to drop that file, park until the holder + merges, or abandon its branch and return the issue to the next round's ranking. Where the loser + has already opened a pull request, say whether it closes or waits, since one left open on an + abandoned branch reads to every later round as a live claim. **Update the claim comment whenever + the adjudication changes what a group holds**, in either direction, or the durable record drifts + from the real claim it exists to report. +- **Cap the round at four workers**, whatever the grouping allows. + +## Bounding a Prose Group + +A group whose files are prose-heavy Markdown bloats in a way a code group does not, and it needs +its own bound stated in the worker's brief. + +- **The change stays inside the units the issue names.** Rewording an adjacent section because it + now reads inconsistently is the next issue, filed, not this one's diff. +- **Deleting a claim beats qualifying it.** Where a review disproves something a rule leaned on, + remove the rule that leaned on it. A narrowed qualifier is where a new false claim gets + introduced, and it is the most common way a prose round produces the finding the following round + then fixes. +- **Set a review-round budget before the first push.** A whole-unit prose review can run many + rounds where a finding was introduced by the previous round's fix, so state a number in the + brief, and when it is reached, land what is correct and file the remainder rather than churning. + +## Dispatching a Worker + +Brief on `AGENTS.md` "Context and Delegation Discipline"'s subagent shape. + +- **The worker drives its group to a develop merge**, by invoking `drive-pr` with the target + stated as develop only. That skill owns the review loop, the finding disposition, and the merge, + so brief the group and the bounds rather than restating the loop. +- **The worker creates its own worktree**, always, as `drive-pr` step 1 and `repo-worktree`'s + task-start mandate already require of the task itself. No worker inherits another's worktree, + which is why "Bounding the Wait on a Worker" either removes a dead worker's tree and its + branch or leaves that tree untouched for the maintainer, and never passes it on. +- **The worker does no cleanup**, which is this skill's one stated override of `drive-pr` step 4 + and of `repo-worktree`'s post-merge procedure. Say so in the brief, because a worker following + either alone will clean up. The worker still performs step 4's merge itself, and what the override + moves is that step's two cleanup halves, the worktree procedure and the verify-then-delete of the + merged remote branch, **both** rather than only the first. "Cleanup Is the Orchestrator's" below, in this + same section, says why and what it covers. +- **The worker runs `local-strict-review` before every push**, including one that only fixes a + review finding. That pass dispatches a reviewer of its own, so a harness where a subagent cannot + dispatch one leaves the worker unable to run it and unable to push. It reports that rather than + pushing, and its worktree is then retired, since git refuses to attach that branch anywhere else + while the reporting tree holds it. The branch is left standing for its own reason, that the + commits it already carries are what the re-dispatched worker continues from. This is the + worktree-only disposition "Cleanup Is the Orchestrator's" separates out, so a clean tree is the + whole test. A clean + tree is retired and the group re-dispatched to a seat that can dispatch. A dirty one is left + exactly as it stands and the group stopped for the maintainer, as is a group for which no seat + that can dispatch exists. That retire-and-re-dispatch case presumes the branch is reachable from + this repository, which the standalone clone `repo-worktree` allows as a fallback breaks: a worker that never + pushed holds its commits only in that clone, where this repository has no ref to hand a + replacement and nothing to retire, so re-dispatching loses the work rather than continuing it. + That group stops for the maintainer with the clone named, and no seat this skill defines resumes + it, since a worker never inherits another's checkout and the orchestrator opens no branch and + edits nothing. Neither the worker nor the orchestrator pushes around the missing pass. +- **The brief names the branch the worker will use**, which is what lets the claim comment record + it before dispatch. The worker still creates its own worktree, on that named branch rather than + one of its choosing, since a claim naming a branch nobody used points at nothing. +- **The brief requires the `Closes on promotion:` line** in the pull request body, listing exactly + the issues this group fixes. A worker never reads this skill, and `drive-pr` does not ask for the + line, so a brief that omits it produces a pull request nothing can derive a closing set from. +- **The brief names the files this group owns and the files it must not touch.** A subagent never + reads this skill, so a claim it was never given is a claim it cannot respect. State this group's + claimed set, state that any other group's file is out of bounds, and state the duty that makes + the re-verification path work: a worker needing a file outside its claim stops and reports rather + than editing it, and waits for the orchestrator to adjudicate. +- **The worker never merges to main** and never resolves a thread it did not actually dispose of. + +### Cleanup Is the Orchestrator's + +`repo-worktree`'s post-merge procedure returns the base clone to current develop before proving +the cleanup, and `operational-vs-release-workflow` states that requirement independently. Four workers doing +that concurrently mutate one shared checkout, which `GOVERNANCE.md` "Repository Boundaries and +Write Safety" forbids outright by giving each task its own checkout. A worker also cannot +finish the procedure from inside its own worktree, since removing that worktree leaves it with no +working directory in which to delete its branch. + +So the whole procedure moves to the orchestrator, which runs it from the base clone at the round's +cleanup step, while no worker is live in a tree it touches. It carries `drive-pr` step 4's remote +half too, verifying the merged branch's tip against the pull request's `headRefOid` before +`git push origin --delete`, since taking that step from the worker without naming a new owner +would leave a live remote branch behind every group. It covers every group that is done with its tree, +which is the finished ones **and the abandoned ones**: a group told to abandon its branch keeps a +registered worktree until something removes it, and that worktree holds a live claim that would +collide with the very group the next round re-forms for the same issue. For a merged group the +procedure is deferred rather than changed: `repo-worktree`'s verify-before-removing and +prove-the-cleanup steps run unchanged, just later and in one seat. + +**Retiring a worktree and deleting its branch are two dispositions with two tests**, and citing +one for the other is how a removal that discards nothing gets routed to the maintainer, or a +removal that discards commits gets waved through. Retiring a worktree alone, the branch left +standing, risks only what is uncommitted in it: a clean tree is the whole test, the branch's own +contents do not enter it, and a tree that is not clean is left exactly as it stands while the +group goes to the maintainer per "Raising a Blocked Question". Deleting the branch as well risks what is committed, so it carries +whichever branch check the group's state calls for, `repo-worktree`'s verify-before-removing for a +group whose pull request merged and the no-merge substitute below for one whose has not. Which +check that is matters: a squash merge never makes the feature tip an ancestor of develop, so the +substitute would fail on every merged group if it were read as covering them. Every disposition in +this skill names which of the two it is. + +An abandoned group, and a dead worker's clean tree, have no merged pull request for +`repo-worktree`'s verify-before-removing step to read, so the check that step gives way to here is +what it exists to establish, that nothing unmerged is +being thrown away: confirm the branch carries no commit that is not already on develop, and that +its worktree is clean. Both hold, and the worktree and branch go the same way a merged group's do, +with no remote branch to delete where none was pushed. Either fails, and cleanup stops there and +the group goes to the maintainer per "Raising a Blocked Question", since past that point removal +discards work. A worker still live, a promotion fix included, keeps its worktree until the next +round's cleanup step. + +### Choosing the Worker's Model Tier + +`AGENTS.md` "Delegation" owns the model-tier rule. Read it there. This is only what it leaves to +judgment here: the tier is chosen per group rather than defaulted, because a stronger tier +produces better work up front and takes fewer review rounds to land it, which often costs less +than a cheaper worker looping. Three kinds of group are never tiered down: + +- One touching **carried canonical content**: rule text, a Skill, or anything else this repository + authors and other repositories carry, since a wrong rule propagates to every carrier. +- One touching **a gate, a ruleset, a release condition, or a carried governance section**, which + is `AGENTS.md`'s own list of what counts as a design change however small the diff looks. +- One whose issues are **complex or entangled**, where the fix depends on reasoning across several + files or on a contract not stated in the file being edited. + +State the chosen tier and its reason in the round's report. + +## Bounding the Wait on a Worker + +`AGENTS.md` requires a wait to separate its outcomes and to be bounded, so this one is. A worker +reports merged, parked, or stopped. A worker that reports nothing at all is the case needing a +bound, since it is indistinguishable from a slow one and dying mid-drive is ordinary here. + +The bound is a state read rather than a clock: when the other workers in the round have reported, +read the silent worker's branch and pull request directly, `git log` on that branch and +`gh pr view `, passing the branch as the positional argument that command takes, since a +bare `gh pr view` resolves the pull request of whatever branch the caller is standing on and never +the worker's. Let what they show decide. A pull request that is merged, or a branch whose work is +complete, means the worker died after doing the work and the group is finished. + +Anything else needs one thing established before anything is touched: whether that worker is gone +or merely slow. No git read answers that, and the two call for opposite actions, so the answer +comes from the dispatch mechanism itself, which knows whether the subagent it started is still +running. Nothing about the worktree is acted on while the answer is "still running", however long +that is. Waiting costs a round's latency and guessing costs another task's uncommitted work. + +Once the worker is confirmed gone, its worktree decides what follows. **A clean one** is cleaned up +as an abandoned group's is, per "Cleanup Is the Orchestrator's", which confirms the branch carries +no commit that is not already on develop before anything is removed. A worker that committed its +fix and then died leaves a clean tree standing over commits develop has never seen, so that check +is what separates the two. It holds, and the issue returns to the next round's ranking to be +dispatched fresh, its claim comment released with the worktree. It fails, and cleanup stops there +and the group goes to the maintainer, since past that point removal discards work. **A dirty one is left exactly as it stands** +and the group is stopped for the maintainer per "Raising a Blocked Question", naming the worktree +and what is uncommitted in it. The orchestrator does not commit that work, hand the tree to a +replacement to commit, or remove it: reaching into a tree a task was live in is what +`GOVERNANCE.md` "Repository Boundaries and Write Safety" forbids, and doing it by proxy is still +doing it. Where no other worker remains to bound the wait, the same liveness answer bounds it +alone. + +## Raising a Blocked Question + +A group reaching a question only the maintainer can answer stops that group and nothing else. +`pr-review-conduct`'s "Escalate to the maintainer when" list is what makes a finding a question +rather than a decision. + +- **The group stops, and nothing about it is disposed of.** No thread is resolved, no finding is + answered on the orchestrator's own judgment, and no pull request merges. +- **The other groups keep driving.** One stopped group never idles the round. +- **The question travels worker to orchestrator to maintainer, and reaches the maintainer at the + point the work stops.** A worker escalates to whoever dispatched it, per `pr-review-conduct`, + since a dispatched subagent is not the seat that can prompt anyone. The orchestrator is that + seat, and it asks then and there through the interface's own prompt mechanism, per + `GOVERNANCE.md` "Communicating with the User". Holding the question for a round boundary is the + handoff-buried-in-a-paragraph that section forbids, and a boundary can be a long way off or, + for a group blocking the promotion pull request, never arrive at all. Where several groups stop + close together, their questions go in one prompt, which is batching without deferral. +- **The question is also written on its issue**, so it survives the session that asked it. +- **A stopped group keeps its branch and its claim**, and its worktree is left exactly as it + stands while the question is open, since the answer may be that the work in it continues. Say in + the prompt whether that worktree holds uncommitted work, because what becomes of it is part of + what is being asked rather than something to settle while waiting. +- **Resuming retires that worktree first, then dispatches a fresh worker.** Git refuses to attach a + branch already checked out somewhere, per `repo-worktree`, so a fresh worker cannot take the + branch while the stopped tree holds it. Once the answer is in, remove that worktree if it is + clean, or apply what the answer said about its uncommitted work and then remove it, and only then + dispatch. This is the same retire-then-dispatch shape "Bounding the Wait on a Worker" uses, and + no worker ever inherits another's tree. + +## The Promotion Boundary + +Each round ends with at most one develop -> main promotion pull request, driven to green and left +for the maintainer, so that one carries a single round rather than accumulating several. + +**This section assumes the release workflow model**, where feature work reaches develop through +squash-merged pull requests and a promotion pull request carries develop to main. A repository +whose registry `workflowModel` reads `operational` differs on both counts, per +`operational-vs-release-workflow`: it commits to develop directly, and it opens a promotion pull +request only occasionally rather than per round, so confirm with the maintainer whether one is +wanted at all there. + +Neither difference changes how this run's own work is read. Every worker invokes `drive-pr` +whatever the model, so this run's fixes still arrive as squash-merged feature pull requests +carrying the `Closes on promotion:` line, and the two hops still read them. What the model adds is +a second kind of commit in the same range, a direct push that never had a pull request, whose +issues are recoverable only from the commit message itself. Read both, the pull requests for this +run's work and the commit messages for the direct pushes, since reading either alone returns a +partial set, and the range rather than this round is still what covers earlier work no promotion +has carried. + +1. **Open it whenever develop is ahead of main**, which `git fetch origin` and then + `git rev-list --count origin/main..origin/develop` answers, and this round's own outcome does + not. Fetch first every time: a stale remote-tracking ref reports zero and the round would report + nothing to promote while develop carries work. A round in which every group deferred or parked + can still owe a promotion pull request, for work an earlier round landed and no promotion has + yet carried. A count of zero is the only case with nothing to promote, and the round reports + that instead of attempting one. +2. Drive its review loop per `drive-pr` steps 5 through 8, **with a review-round budget set before + the first one**, the same discipline "Bounding a Prose Group" applies to a feature branch. That + loop repeats until the promotion pull request carries no open finding, and nothing in it + terminates on its own, so when the budget is reached, stop and put the state to the maintainer + rather than continuing to spend the run's only forward gear on one pull request. +3. Put the ready pull request to the maintainer through the interface's own prompt mechanism, + naming the merge as the action that unblocks the run. The maintainer's merge is the run's clock, so one + reported in a closing paragraph and never actually asked about stalls every round behind it. + Do not merge it. +4. **While it waits, develop takes only what that pull request itself needs.** A finding against + it lands as its own feature -> develop pass, and that landing moving its head is expected, since + its head **is** develop. **That pass is dispatched as a worker like any other**, which is the + one push the freeze permits and the reason the orchestrator still opens no branch of its own. + `drive-pr` step 6 sends the seat driving a promotion pull request back through its own steps 1 + to 4 for such a fix, and here that seat dispatches rather than drives it. +5. **A promotion fix outranks any file claim.** A group holding a file it needs yields, because the + promotion pull request is what the whole run is queued behind. A holder that is merely parked + yields by handing the file over. A holder that already pushed and has an open pull request + yields by having that pull request wait, its branch untouched, and by the promotion fix taking + the file, since the two must not be in flight on one file at once. Once the fix lands, that + pull request waits untouched until the freeze lifts. Reconciling its content is the next round's + worker's job rather than the orchestrator's, which opens no branch and edits nothing. The + orchestrator retires that group's worktree, the branch and its pull request left standing, + which is the worktree-only disposition "Cleanup Is the Orchestrator's" separates out and the + retire-then-dispatch shape "Raising a Blocked Question" uses, and then dispatches a fresh + worker on that same branch, briefed either to merge develop in to pick the fix up or to narrow + the change to drop the file. Never rebase it: + its branch is already pushed, so a rebase needs the force-push `git-commit-conventions` forbids + outright. +6. **Nothing else pushes, and nothing else is dispatched.** The promotion fix of step 4 is the one + exception to both, and everything in this step is said of the next round's work rather than of + it. That round's preparation is orchestrator work and continues: rank, group, and verify claims. + Its dispatch waits, because a worker has exactly one procedure, `drive-pr`, whose second step + pushes and opens a pull request, so a next-round worker dispatched under the freeze would either + break it or sit in a state that procedure does not describe. None is left running across the + wait either, since a worker held idle for an unbounded maintainer wait is one doing nothing at a + cost, and dispatching it after the merge starts it against the state that merge produced rather + than the state it was briefed on. +7. The merge unfreezes the run, and the prepared round dispatches then. + +The run advances no faster than the maintainer merges promotion pull requests. That is the human +gate, stated plainly rather than left for a stalled round to reveal. + +### Assembling the Promotion Body + +The body carries one `Fixes #N` per issue whose fix is on develop and not yet on main. Two hops +are needed rather than one, because the commits in `origin/main..origin/develop` are squash merges +whose subjects carry the **pull request** number and not the issue number, and this skill +deliberately keeps the closing keyword off the feature pull request, so nothing in the range names +an issue directly. Read the pull request numbers out of that range, freshly fetched, then read each +of those pull requests for its `Closes on promotion:` line, the one "Grouping and File Claims" +requires every feature pull request to carry and every worker brief to ask for. + +**That line exists because the set has to be stated rather than inferred**, distinct from any +issue a body merely mentions. A body routinely references an issue it +does not fix, the deferral issues its own review round filed most of all, and those have to stay +open as the next round's ranking input. Sweeping in everything a body mentions would close them at +the promotion merge and delete the next round's backlog, so the promotion body reads the explicit +line and never the mentions. An issue named nowhere is one nothing closes, which is a missed +closure a later round notices, where the opposite error destroys work. + +Deriving the set from the range rather than from what this round dispatched is what covers a group +that deferred or parked, contributing none, and an earlier round's work that no promotion has yet +carried. A fix landing during the freeze adds its issue to a body already written, so amend the +body when it lands rather than leaving the issue to be closed by hand. + +## Run State + +- **Working notes outside the repository hold the round**: the ranking, the working groups, the + tier choices, and the worker assignments. A scratch file the harness gives a session serves + where there is one, and any note kept out of the tree serves where there is not. It is working + state, and nothing about it is committed. +- **GitHub holds what outlives the session.** A claim comment records a group's file set, a pull + request body records what a round carried, a `Fixes #N` line records what the promotion closes, a + deferral issue records what was put off and why, a thread reply records how a finding was + disposed of, and a stopped group's question is a comment on its issue. +- **No tracker file is committed for the run.** A committed tracker is a file every round rewrites, + which is the contention the file-claim rule exists to prevent. + +## Ending the Run + +The run ends at either of two points, and they are different endings. + +- **The backlog is worked out**, meaning a full re-rank finds no open issue this skill can act on. + That is not the same as zero open issues, since a backlog of nothing but maintainer questions is + a finished run. Report it as finished, with the questions put to the maintainer. +- **The session ends**, for a context limit or because the maintainer stops it. The run ends with + it, since the merge authorization was bounded to that session. What the rounds already landed + stands on its own in GitHub, and the branches, claim comments, and questions left behind are + what a later run reads to pick the work up. That later run is a new run, named again, not this + one continuing. + +Report at every round boundary and at either ending: what merged to develop, what the promotion +pull request carries, what was newly filed, what is stopped and on which question, and what the +next round would pick. + +## Mechanics Live Elsewhere + +- The review loop, the Merge Gate, the five finding outcomes, and when a finding is a question: + `pr-review-conduct`. +- Driving one pull request, and the promotion-pull-request wrinkle: `drive-pr`. +- Worktree isolation, the base branch, and the cleanup procedure this skill re-seats: + `repo-worktree`. +- Closing keywords, branch protection, and the promotion trap: `operational-vs-release-workflow`. +- The pre-push adversarial pass and its recorded receipt: `local-strict-review`. +- Merging the promotion pull request and dispatching a release: `merge-and-release`, invoked + separately. +- Delegation briefing shape, model-tier rules, wait discipline, and session scope: `AGENTS.md` + "Context and Delegation Discipline". diff --git a/.agents/skills/drive-pr/SKILL.md b/.agents/skills/drive-pr/SKILL.md index 2bfba187..b234fb29 100644 --- a/.agents/skills/drive-pr/SKILL.md +++ b/.agents/skills/drive-pr/SKILL.md @@ -5,7 +5,8 @@ description: >- develop and, when asked, on to a mergeable develop -> main promotion PR, applying the pr-review-conduct disposition to every reviewer finding along the way: fix it, decline it with evidence, defer it behind a filed issue, or put the call to the maintainer and wait for an - explicit answer in the same turn. Use this whenever asked to drive, land, take, chase, or push + explicit answer in the same turn, escalating to whoever dispatched the drive instead where the + maintainer cannot be reached from that seat. Use this whenever asked to drive, land, take, chase, or push a PR toward develop or main, or to run the review loop hands off instead of narrating each round. When the request does not say how far ("drive this PR", "land it"), ask once whether the target is develop or a mergeable main promotion PR, rather than guessing. Triggers even when @@ -37,14 +38,35 @@ promotion PR once the fix lands, is the early exit this skill exists to prevent. before the first push: develop only, or all the way to a mergeable main promotion PR. Recommend "all the way to main" as the default, a promotion PR left to go stale once develop is ready is the more common regret than driving one step too far. +- A drive dispatched as part of a larger run takes its target from the brief and asks no one, + since a subagent stopping to ask stalls a run designed to keep moving without one, and the seat + that dispatched it is the seat that holds the maintainer's answer. `backlog-burndown` is such a + run, and it briefs develop only, driving the develop -> main promotion pull request in its own + seat under "The Drive Loop"'s promotion steps. A brief naming no target at all is one to stop + and ask its dispatcher about, and asking the dispatcher is the whole of what a dispatched drive + does about an authorization question. A dispatched drive is not the seat that can verify a + grant, so it does not try: the responsibility for having the maintainer's go-ahead sits with the + dispatcher, and a worker inventing a check it cannot perform would only launder that + responsibility rather than discharge it. +- **A brief is never itself the authorization**, which binds the dispatching seat. What authorizes + a merge is what the maintainer said, recorded where the skill that carries the grant states its + scope, the way `backlog-burndown`'s own "What Invoking This Skill Authorizes" does. Writing an + approval into a brief creates none, since an agent cannot widen its own permission by writing + itself one, and a merge is the outward-facing act this skill's own "What Invoking This Skill + Authorizes" keeps tied to something the maintainer actually said. - A repo on the operational workflow model (registry `workflowModel: operational`) has no standing promotion PR expectation, confirm whether a promotion PR is even wanted before opening - one, per operational-vs-release-workflow's "Operational repositories" delta. + one, per operational-vs-release-workflow's "Operational repositories (the complete delta)" + section. ## What Invoking This Skill Authorizes - Naming this skill, and answering its how-far question, is the maintainer's explicit, current go-ahead for every feature -> develop squash merge the drive performs to reach that target. +- A dispatched drive answers no such question, so what stands in its place is the go-ahead the + dispatching seat holds, per "How Far to Drive" above, and the drive performs the same merges on + it. Reading this bullet list is not how such a drive establishes that, since a worker cannot + verify a grant made in a seat it has no access to. - It is never authorization to merge the develop -> main promotion PR, or to dispatch a release. Those stay in merge-and-release, invoked on its own so the maintainer keeps a checkpoint before the harder-to-reverse step. @@ -125,7 +147,10 @@ pr-review-conduct's five outcomes are the actual rule, this is the mapping to us - Real, fixable, but a value call rather than a scope boundary, or the agent genuinely does not know which of the above applies, so ask the maintainer directly, whatever the runtime's own interactive-question mechanism is, and get an explicit answer in the same turn, a plan to ask - later is resolution by silence (outcome 3). + later is resolution by silence (outcome 3). A drive that cannot reach the + maintainer directly, a dispatched one being the ordinary case, escalates to whoever dispatched + it and stops that unit of work there instead, per `pr-review-conduct`, which owns what the + receiving seat then does and how far the escalation travels. - The same finding keeps recurring against correct code, fix the class, sharpen a name, add a comment, or take the rule itself to the maintainer, rather than re-arguing the instance every round (outcome 5). diff --git a/.agents/skills/local-strict-review/SKILL.md b/.agents/skills/local-strict-review/SKILL.md index cecb485e..336effcc 100644 --- a/.agents/skills/local-strict-review/SKILL.md +++ b/.agents/skills/local-strict-review/SKILL.md @@ -57,6 +57,8 @@ Bounds: read-only. No edit, no stage, no commit, no push, no PR-hosted write of **Model tier:** the strongest tier this session can reach, per `AGENTS.md` "Match the model tier to the judgment" and "Never tier down the seat holding the judgment", applied here to the reviewer rather than the author. Run the pass on the same tier that authored the change when only one tier is reachable, a second, adversarially-prompted look still catches what the authoring pass's own "looks ready" judgment did not. +"This session can reach" means the tier this session can name when it dispatches the reviewer, rather than the tier this session is itself running on. A session deliberately tiered down for execution work, a worker dispatched by an orchestrator being the ordinary case, names a stronger tier for the reviewer where its harness lets it, since tiering down the author is the reason the reviewer must not follow it down. What a given harness and account actually permit varies, so treat this as the tier to ask for rather than one to assume. Where a dispatch reaches several tiers but exposes no way to name one, take what it gives and run the pass, on the same reasoning as the single-reachable-tier sentence above. A seat that cannot dispatch a subagent at all cannot perform this pass. Instead of pushing, it reports that it could not run the pass, to whoever dispatched it, or to the maintainer where nobody did. Either way it is a push that does not happen rather than a pass quietly skipped. The headless `run --backend` route under "Recording the Pass" is not the substitute: it runs a vendor CLI against its own review, which never carries the brief above, so it satisfies the rule this section states only where that separate route is what a capture point asked for. + ## Recording the Pass `scripts/local_review.py` is what makes this rule checkable rather than something each session has to remember. For the pass above, the engine only records that it happened, keyed on the content the reviewer actually saw, and its `run --backend ` subcommand is the separate case where a headless backend performs the review and records its own count. That receipt is what a capture point reads, the hub's own `.husky/pre-push` hook being the only one today, and a repository having none unless it adds one, since no manifest entry carries it. @@ -123,7 +125,7 @@ The digest is bound to the read for the same reason `--expect-digest` is above: ## Disposing of Findings -Every finding maps to one of `pr-review-conduct`'s five outcomes, at whichever moment this pass ran: fixed (1), evidence-disproven (2), escalated to the maintainer for an explicit call (3), filed as a deferred issue (4), or, if it keeps recurring, taken as a signal to fix the class (5). Outcome 2 is the agent's own on its own evidence, covering a finding that is not real and one that is structurally out of scope. A finding judged real and left unfixed is never the agent's alone, so outcome 3 needs the maintainer's explicit answer in the same turn, reached only once outcome 2 is ruled out, and outcomes 4 and 5 reach the maintainer too, for the deferral and for the rule itself. Running this pass is required before every push toward a pull request, per `agent-conduct`. Two claims sit next to each other here and they point opposite ways, so they are stated apart rather than in one sentence. **The pass is mandatory**, and where a capture point enforces it, a push carrying content no recorded pass covers is refused. That refusal is the gate working rather than a fault to route around. **The findings stay advisory**, and the count a pass raises gates nothing at all, since a pass records that a review ran and never that the content is clean. The disposition above is what closes each finding, the same posture local lint holds today. It posts nothing to GitHub, it only reports to the session driving the work. A finding raised here and not fixed is not thereby resolved: the same finding shape reaching a PR-hosted reviewer later still gets its own fresh disposition, per `pr-review-conduct`'s "a disposition decided on one PR does not carry to the next." +Every finding maps to one of `pr-review-conduct`'s five outcomes, at whichever moment this pass ran: fixed (1), evidence-disproven (2), escalated to the maintainer for an explicit call (3), filed as a deferred issue (4), or, if it keeps recurring, taken as a signal to fix the class (5). Outcome 2 is the agent's own on its own evidence, covering a finding that is not real and one that is structurally out of scope. A finding judged real and left unfixed is never the agent's alone, so outcome 3 needs the maintainer's explicit answer in the same turn, reached only once outcome 2 is ruled out, or, where this pass ran in a seat that cannot reach the maintainer, an escalation to whoever dispatched it that stops the work there, which stops the push this pass runs before, and outcomes 4 and 5 reach the maintainer too, for the deferral and for the rule itself. Running this pass is required before every push toward a pull request, per `agent-conduct`. Two claims sit next to each other here and they point opposite ways, so they are stated apart rather than in one sentence. **The pass is mandatory**, and where a capture point enforces it, a push carrying content no recorded pass covers is refused. That refusal is the gate working rather than a fault to route around. **The findings stay advisory**, and the count a pass raises gates nothing at all, since a pass records that a review ran and never that the content is clean. The disposition above is what closes each finding, the same posture local lint holds today. It posts nothing to GitHub, it only reports to the session driving the work. A finding raised here and not fixed is not thereby resolved: the same finding shape reaching a PR-hosted reviewer later still gets its own fresh disposition, per `pr-review-conduct`'s "a disposition decided on one PR does not carry to the next." ## When to Run It diff --git a/.agents/skills/pr-review-conduct/SKILL.md b/.agents/skills/pr-review-conduct/SKILL.md index 836fdb32..1073b824 100644 --- a/.agents/skills/pr-review-conduct/SKILL.md +++ b/.agents/skills/pr-review-conduct/SKILL.md @@ -172,6 +172,16 @@ from a hub checkout. Do not use a provider connector or reconstruct the GitHub m - A finding is judged real but should not be fixed. That decision is never the agent's alone. - An architectural redesign is proposed rather than a bug fix. +An agent that cannot reach the maintainer directly, a dispatched subagent being the ordinary case, +escalates to whoever dispatched it and stops that unit of work there. It never substitutes its own +judgment for the escalation because asking is inconvenient from where it sits, and it never resolves +the thread to keep moving. A dispatcher receiving one puts it to the maintainer at the point that +work stopped, per `GOVERNANCE.md` "Communicating with the User", and deciding it instead so the +dispatcher's own work keeps moving is the same resolution by silence this skill's own +ask-the-maintainer outcome forbids, one seat further from the maintainer. The escalation may travel through several seats, and what stays stopped is the +escalated unit of work, in whichever seat holds it, until the answer arrives. A dispatcher's other +work is not stopped by it. + ## Mechanics Live Elsewhere This skill is the provider-agnostic contract. Use `scripts/pr_review.py` from a hub checkout for diff --git a/.claude-plugin/fleet-skills/.claude-plugin/plugin.json b/.claude-plugin/fleet-skills/.claude-plugin/plugin.json index e7913205..e7eadd00 100644 --- a/.claude-plugin/fleet-skills/.claude-plugin/plugin.json +++ b/.claude-plugin/fleet-skills/.claude-plugin/plugin.json @@ -9,6 +9,7 @@ "./skills/add-host-tool", "./skills/agent-conduct", "./skills/audit-a-repo", + "./skills/backlog-burndown", "./skills/carried-instruction-file-guard", "./skills/code-review", "./skills/comment-and-doc-style", diff --git a/.claude-plugin/fleet-skills/.source-digest b/.claude-plugin/fleet-skills/.source-digest index 54877f1c..f451cc2f 100644 --- a/.claude-plugin/fleet-skills/.source-digest +++ b/.claude-plugin/fleet-skills/.source-digest @@ -1 +1 @@ -9e9025addf504948 +ecfc716438f27a07 diff --git a/.claude-plugin/fleet-skills/skills/backlog-burndown/SKILL.md b/.claude-plugin/fleet-skills/skills/backlog-burndown/SKILL.md new file mode 100644 index 00000000..47c14be1 --- /dev/null +++ b/.claude-plugin/fleet-skills/skills/backlog-burndown/SKILL.md @@ -0,0 +1,509 @@ +--- +name: backlog-burndown +description: >- + Burns a ptr727/ProjectTemplate fleet repository's open-issue backlog down by rounds: rank the + open issues, group them so no two groups touch the same file, dispatch one subagent per group to + drive its own feature -> develop pull request to merge, open at most one develop -> main + promotion pull request per round for the maintainer to merge, then re-rank and go again, + because every review round files new issues that change what the next round should pick. Use + this whenever asked to work the backlog, burn the backlog down, clear the open issues, resolve + or cull the backlog, or run issues in parallel until they are gone, and whenever the ask is a + standing one rather than a single named issue. Triggers even when the backlog looks small enough + to work by hand, because the failure it exists to prevent is two agents editing the same + prose-heavy Markdown file in the same round, which surfaces as a merge conflict long after both + branches are already deep in review. Drives one repository, the one the session is in, never a + fleet-wide sweep. Ends when a re-rank finds nothing left it can act on, and never merges main, + which stays the maintainer's own step through merge-and-release. +--- + +# Backlog Burndown + +## Why This Exists + +Asking for one issue to be fixed is `drive-pr`'s job and needs no skill above it. Asking for a +whole backlog to be worked down is a different problem, and three things about it are not obvious. +Parallelism is bounded by file overlap rather than by agent count, so the grouping decides the +throughput. The backlog is not a fixed list, since every review round files deferral issues that +belong in the next round's ranking, so a plan made once is stale by its second round. And a +prose-heavy repository conflicts on content rather than on syntax, so two agents rewording the +same section produce a conflict no tool resolves and no reviewer catches early. + +## The Two Seats + +Everything below turns on which seat is acting, so both are named once here. + +- **The orchestrator** is the session this skill runs in. It ranks, groups, dispatches, and drives + the promotion pull request. It opens no feature branch and fixes no issue itself, which is what + keeps it out of every worker's files. It does write: it comments on issues, it drives and + amends the promotion pull request, and it owns worktree and branch cleanup, which "Dispatching a + Worker" states in full. +- **A worker** is one dispatched subagent holding one group, one worktree, and one feature branch, + which is `AGENTS.md` "Session Scope"'s one-branch-one-deliverable rule applied as written. It + drives its own pull request into develop and ends there. + +## Scope + +One repository, the one the session is in, resolved from its own `origin`. Reads are unrestricted +per `GOVERNANCE.md` "Repository Boundaries and Write Safety", so reading another repository's +issues breaks no rule. Working them is out of this skill's scope, and a fleet-wide backlog +sweep is a different request. That section bounds writes to the owner of +this repository rather than to this repository alone, and a run staying inside the one repository +it was invoked for is narrower than the rule requires, deliberately. + +## What Invoking This Skill Authorizes + +- Naming this skill is the maintainer's explicit go-ahead for the feature -> develop squash merges + this run performs, in every round of it. A per-round merge question would idle every agent at + every boundary, which is the thing this skill exists to avoid. +- **The grant is bounded by the session it was named in.** A run interrupted and resumed in a new + session needs the skill named again, which costs one sentence and is the difference between a + grant and a mode. A grant read back from a note is one nobody gave. +- The grant does not weaken the `pr-review-conduct` Merge Gate. It answers that gate's item 5 for + this run's feature -> develop merges and nothing else, so a pull request with one open finding + still does not merge. +- It is never authorization to merge a develop -> main promotion pull request, to dispatch a + release, to close an issue on judgment, or to touch another repository. Each stays the + maintainer's, and merging a promotion pull request is `merge-and-release`, invoked on its own. + +## The Round + +A round is the unit. Each one runs these steps in order. + +1. **Rank** every open issue, per "Ranking". +2. **Group** the top of that ranking, per "Grouping and File Claims". +3. **Verify** each group's predicted file set against everything in flight before dispatching + anything. A group whose files are already claimed waits for the next round. +4. **Dispatch** at most four workers, one per group, per "Dispatching a Worker". +5. **Collect** each worker's outcome: merged to develop, stopped on a question only the + maintainer can answer, parked behind another group's file claim, or abandoned, which is what + the adjudication in "Grouping and File Claims" and a confirmed-gone worker both produce. Bound + this wait per "Bounding the Wait on a Worker". +6. **Clean up** the worktrees, local branches, and merged remote branches of every group that has + finished or been abandoned, per "Dispatching a Worker". +7. **Promote**, per "The Promotion Boundary". +8. **Re-rank from scratch**, and note that the next round prepares under the freeze "The Promotion + Boundary" describes whenever a promotion pull request is still waiting on the maintainer, so it + ranks, groups, and verifies claims, and dispatches nothing until that merge lands. Do not carry + the previous round's ranking forward. The deferral issues this round's reviews filed are now + open issues with a claim on the next round's attention, and an issue that ranked low last round + can rank high once a sibling fix lands. + +## Ranking + +Where the repository carries no priority label, and the hub does not, the ranking is the +orchestrator's judgment against stated criteria rather than a field read off the issue. Where a +repository does carry one, that label is the first input and these criteria order what it leaves +tied. Write the ranking, and the reason for the top of it, into the report this skill makes at +each round boundary, per "Ending the Run". + +Rank on these, highest first where they conflict: + +- **It blocks other work.** An issue whose fix changes a rule, a gate, or a shared contract that + other issues' fixes must then obey is worth doing before them, not after. +- **It is a correctness or safety defect** in something that runs, over an improvement to + something that reads. +- **It is a root cause rather than a leaf.** A parent issue grouping several filed symptoms is + worth more than any one of its children, and fixing it may close them. +- **It is new.** An issue filed by a recent review round is evidence of something the current + content actually got wrong, and it is the freshest context anyone has on it. +- **It is small and self-contained**, as a tie-break only. Size breaks a tie between two issues of + equal value, and it never promotes a trivial issue over a real defect. + +An issue that asks a question rather than states a defect is not ranked and is never guessed at. +It has no group, no worker, and no claim, so nothing in "Raising a Blocked Question" applies to it +except how the question travels. It goes to the maintainer at the end of +ranking, in the same prompt as any other question the run is sending at that moment and in one of +its own otherwise, rather than waiting for a stop that may not come. It stays unranked until +answered. + +## Grouping and File Claims + +Group so that **no file is claimed by two live groups at once.** This is the rule the skill exists +for, and it binds harder than any throughput target. + +- **Group by the files a fix will touch**, not by the issues' subject matter. Two issues that read + as unrelated but both edit a shared governance file are one group. Two issues that read as near + duplicates but touch different files are two groups. +- **Genuine duplicates are one group.** Never close an issue during triage on the orchestrator's + own judgment. Comment to cross-link the pair, and let the fix close both. +- **Closing keywords go on the promotion pull request**, not the feature pull request, per + `operational-vs-release-workflow`. A feature pull request merging into develop fires no + auto-close, so a `Fixes #N` line there closes nothing. **The feature pull request body instead + carries a line reading `Closes on promotion: #N`**, listing every issue that pull request + actually fixes and nothing it merely mentions, which is the line the promotion body is assembled + from, per "The Promotion Boundary". Without that line nothing records which issues a merged pull + request closes, since the closing keyword is deliberately absent and a body's other issue + references are not the same set. +- **Predict each group's file set** by reading the issues, not by guessing from their titles. +- **Record every claim on the issue itself**, as a comment naming the predicted file set **and the + branch that holds it**, before dispatching. The branch name is what lets a later session walk + from a worktree it found back to the claim explaining it, which is the direction the next bullet + actually travels. Working notes do not survive the session, so a claim living only in them is + invisible to the round that has to respect it, and recording it on the issue is what makes the + next bullet a read of durable state rather than of the orchestrator's memory. +- **Verify the prediction before dispatching**, against everything in flight, which is wider than + this round: the files changed by every open **feature** pull request on this repository (`gh pr + diff --name-only` per open pull request), and the claim comments of every group still + holding a branch, parked groups from earlier rounds included. `git worktree list` reports the + registered worktrees and the branch checked out in each, which is not the same as every branch + that exists, so pair it with `git branch -r`, after `git fetch --prune origin`, for one that was + pushed and whose worktree is already gone, and with `git branch` for one that was never pushed + and whose worktree is already gone. That third read is not optional here: the worktree-only + disposition retires a tree and leaves its branch standing, so this skill produces exactly that + state, and a local branch holding commits no remote has is invisible to both other reads. Prune + rather than plain fetch because `--prune` is what drops a remote-tracking ref whose branch is + gone from the remote, deleted there by another session or through the web interface, and a + plain fetch leaves that ref in `git branch -r` to defer valid groups forever. Stop and report a + failed fetch rather than reading `git branch -r` anyway: the remote-tracking refs still resolve + from what the last successful fetch left, so the scan returns a confident answer about a remote + it did not reach, missing a branch pushed since and keeping one deleted since. The round + stops there and reports, rather than dispatching against a stale answer, and stopping rather + than deferring is what the cleanup and promotion steps need too, since both read the same + remote. + Those three enumerate the branches, with one gap: a branch in the standalone clone `repo-worktree` allows as a fallback is + reached only once it is pushed, since `git branch -r` inventories the remote rather than this + repository's checkouts. The claim comments are what say which files each branch holds, since a branch name says nothing about a file set, an unpushed branch has no + pull request diff to read, and no diff of any branch reports the predicted set a claim records + before the work is committed. **Exclude the open promotion pull request from that enumeration.** + Its diff is all of `origin/main..origin/develop`, so counting it claims nearly every file any + earlier round touched, and a round run during the freeze would defer every group it formed. A + predicted claim that collides with a real one is a group deferred to the next round, never one + dispatched hoping the overlap stays small. +- **A branch no claim comment covers still has to yield a file set.** The maintainer's own + worktree and a hand-driven task's branch are both enumerated above and neither carries a claim + comment, so reading only the comments records them as holding nothing, which is the collision + this section exists to prevent rather than the absence of one. Read the branch itself instead: + `git diff --name-only origin/develop...` for what it has committed, and, for a + registered worktree, `git -C status --porcelain` for what it holds uncommitted. Where + neither read is available the set is unknown rather than empty, and an unknown set collides with + every group, so ask the maintainer what that branch holds per "Raising a Blocked Question". +- **Re-verify when a worker reports that its real file set grew** beyond its claim. A worker + needing a file another group holds stops and reports rather than editing it, and the + orchestrator decides which group keeps the file, then tells the loser which of three things to + do rather than leaving it to choose: narrow its change to drop that file, park until the holder + merges, or abandon its branch and return the issue to the next round's ranking. Where the loser + has already opened a pull request, say whether it closes or waits, since one left open on an + abandoned branch reads to every later round as a live claim. **Update the claim comment whenever + the adjudication changes what a group holds**, in either direction, or the durable record drifts + from the real claim it exists to report. +- **Cap the round at four workers**, whatever the grouping allows. + +## Bounding a Prose Group + +A group whose files are prose-heavy Markdown bloats in a way a code group does not, and it needs +its own bound stated in the worker's brief. + +- **The change stays inside the units the issue names.** Rewording an adjacent section because it + now reads inconsistently is the next issue, filed, not this one's diff. +- **Deleting a claim beats qualifying it.** Where a review disproves something a rule leaned on, + remove the rule that leaned on it. A narrowed qualifier is where a new false claim gets + introduced, and it is the most common way a prose round produces the finding the following round + then fixes. +- **Set a review-round budget before the first push.** A whole-unit prose review can run many + rounds where a finding was introduced by the previous round's fix, so state a number in the + brief, and when it is reached, land what is correct and file the remainder rather than churning. + +## Dispatching a Worker + +Brief on `AGENTS.md` "Context and Delegation Discipline"'s subagent shape. + +- **The worker drives its group to a develop merge**, by invoking `drive-pr` with the target + stated as develop only. That skill owns the review loop, the finding disposition, and the merge, + so brief the group and the bounds rather than restating the loop. +- **The worker creates its own worktree**, always, as `drive-pr` step 1 and `repo-worktree`'s + task-start mandate already require of the task itself. No worker inherits another's worktree, + which is why "Bounding the Wait on a Worker" either removes a dead worker's tree and its + branch or leaves that tree untouched for the maintainer, and never passes it on. +- **The worker does no cleanup**, which is this skill's one stated override of `drive-pr` step 4 + and of `repo-worktree`'s post-merge procedure. Say so in the brief, because a worker following + either alone will clean up. The worker still performs step 4's merge itself, and what the override + moves is that step's two cleanup halves, the worktree procedure and the verify-then-delete of the + merged remote branch, **both** rather than only the first. "Cleanup Is the Orchestrator's" below, in this + same section, says why and what it covers. +- **The worker runs `local-strict-review` before every push**, including one that only fixes a + review finding. That pass dispatches a reviewer of its own, so a harness where a subagent cannot + dispatch one leaves the worker unable to run it and unable to push. It reports that rather than + pushing, and its worktree is then retired, since git refuses to attach that branch anywhere else + while the reporting tree holds it. The branch is left standing for its own reason, that the + commits it already carries are what the re-dispatched worker continues from. This is the + worktree-only disposition "Cleanup Is the Orchestrator's" separates out, so a clean tree is the + whole test. A clean + tree is retired and the group re-dispatched to a seat that can dispatch. A dirty one is left + exactly as it stands and the group stopped for the maintainer, as is a group for which no seat + that can dispatch exists. That retire-and-re-dispatch case presumes the branch is reachable from + this repository, which the standalone clone `repo-worktree` allows as a fallback breaks: a worker that never + pushed holds its commits only in that clone, where this repository has no ref to hand a + replacement and nothing to retire, so re-dispatching loses the work rather than continuing it. + That group stops for the maintainer with the clone named, and no seat this skill defines resumes + it, since a worker never inherits another's checkout and the orchestrator opens no branch and + edits nothing. Neither the worker nor the orchestrator pushes around the missing pass. +- **The brief names the branch the worker will use**, which is what lets the claim comment record + it before dispatch. The worker still creates its own worktree, on that named branch rather than + one of its choosing, since a claim naming a branch nobody used points at nothing. +- **The brief requires the `Closes on promotion:` line** in the pull request body, listing exactly + the issues this group fixes. A worker never reads this skill, and `drive-pr` does not ask for the + line, so a brief that omits it produces a pull request nothing can derive a closing set from. +- **The brief names the files this group owns and the files it must not touch.** A subagent never + reads this skill, so a claim it was never given is a claim it cannot respect. State this group's + claimed set, state that any other group's file is out of bounds, and state the duty that makes + the re-verification path work: a worker needing a file outside its claim stops and reports rather + than editing it, and waits for the orchestrator to adjudicate. +- **The worker never merges to main** and never resolves a thread it did not actually dispose of. + +### Cleanup Is the Orchestrator's + +`repo-worktree`'s post-merge procedure returns the base clone to current develop before proving +the cleanup, and `operational-vs-release-workflow` states that requirement independently. Four workers doing +that concurrently mutate one shared checkout, which `GOVERNANCE.md` "Repository Boundaries and +Write Safety" forbids outright by giving each task its own checkout. A worker also cannot +finish the procedure from inside its own worktree, since removing that worktree leaves it with no +working directory in which to delete its branch. + +So the whole procedure moves to the orchestrator, which runs it from the base clone at the round's +cleanup step, while no worker is live in a tree it touches. It carries `drive-pr` step 4's remote +half too, verifying the merged branch's tip against the pull request's `headRefOid` before +`git push origin --delete`, since taking that step from the worker without naming a new owner +would leave a live remote branch behind every group. It covers every group that is done with its tree, +which is the finished ones **and the abandoned ones**: a group told to abandon its branch keeps a +registered worktree until something removes it, and that worktree holds a live claim that would +collide with the very group the next round re-forms for the same issue. For a merged group the +procedure is deferred rather than changed: `repo-worktree`'s verify-before-removing and +prove-the-cleanup steps run unchanged, just later and in one seat. + +**Retiring a worktree and deleting its branch are two dispositions with two tests**, and citing +one for the other is how a removal that discards nothing gets routed to the maintainer, or a +removal that discards commits gets waved through. Retiring a worktree alone, the branch left +standing, risks only what is uncommitted in it: a clean tree is the whole test, the branch's own +contents do not enter it, and a tree that is not clean is left exactly as it stands while the +group goes to the maintainer per "Raising a Blocked Question". Deleting the branch as well risks what is committed, so it carries +whichever branch check the group's state calls for, `repo-worktree`'s verify-before-removing for a +group whose pull request merged and the no-merge substitute below for one whose has not. Which +check that is matters: a squash merge never makes the feature tip an ancestor of develop, so the +substitute would fail on every merged group if it were read as covering them. Every disposition in +this skill names which of the two it is. + +An abandoned group, and a dead worker's clean tree, have no merged pull request for +`repo-worktree`'s verify-before-removing step to read, so the check that step gives way to here is +what it exists to establish, that nothing unmerged is +being thrown away: confirm the branch carries no commit that is not already on develop, and that +its worktree is clean. Both hold, and the worktree and branch go the same way a merged group's do, +with no remote branch to delete where none was pushed. Either fails, and cleanup stops there and +the group goes to the maintainer per "Raising a Blocked Question", since past that point removal +discards work. A worker still live, a promotion fix included, keeps its worktree until the next +round's cleanup step. + +### Choosing the Worker's Model Tier + +`AGENTS.md` "Delegation" owns the model-tier rule. Read it there. This is only what it leaves to +judgment here: the tier is chosen per group rather than defaulted, because a stronger tier +produces better work up front and takes fewer review rounds to land it, which often costs less +than a cheaper worker looping. Three kinds of group are never tiered down: + +- One touching **carried canonical content**: rule text, a Skill, or anything else this repository + authors and other repositories carry, since a wrong rule propagates to every carrier. +- One touching **a gate, a ruleset, a release condition, or a carried governance section**, which + is `AGENTS.md`'s own list of what counts as a design change however small the diff looks. +- One whose issues are **complex or entangled**, where the fix depends on reasoning across several + files or on a contract not stated in the file being edited. + +State the chosen tier and its reason in the round's report. + +## Bounding the Wait on a Worker + +`AGENTS.md` requires a wait to separate its outcomes and to be bounded, so this one is. A worker +reports merged, parked, or stopped. A worker that reports nothing at all is the case needing a +bound, since it is indistinguishable from a slow one and dying mid-drive is ordinary here. + +The bound is a state read rather than a clock: when the other workers in the round have reported, +read the silent worker's branch and pull request directly, `git log` on that branch and +`gh pr view `, passing the branch as the positional argument that command takes, since a +bare `gh pr view` resolves the pull request of whatever branch the caller is standing on and never +the worker's. Let what they show decide. A pull request that is merged, or a branch whose work is +complete, means the worker died after doing the work and the group is finished. + +Anything else needs one thing established before anything is touched: whether that worker is gone +or merely slow. No git read answers that, and the two call for opposite actions, so the answer +comes from the dispatch mechanism itself, which knows whether the subagent it started is still +running. Nothing about the worktree is acted on while the answer is "still running", however long +that is. Waiting costs a round's latency and guessing costs another task's uncommitted work. + +Once the worker is confirmed gone, its worktree decides what follows. **A clean one** is cleaned up +as an abandoned group's is, per "Cleanup Is the Orchestrator's", which confirms the branch carries +no commit that is not already on develop before anything is removed. A worker that committed its +fix and then died leaves a clean tree standing over commits develop has never seen, so that check +is what separates the two. It holds, and the issue returns to the next round's ranking to be +dispatched fresh, its claim comment released with the worktree. It fails, and cleanup stops there +and the group goes to the maintainer, since past that point removal discards work. **A dirty one is left exactly as it stands** +and the group is stopped for the maintainer per "Raising a Blocked Question", naming the worktree +and what is uncommitted in it. The orchestrator does not commit that work, hand the tree to a +replacement to commit, or remove it: reaching into a tree a task was live in is what +`GOVERNANCE.md` "Repository Boundaries and Write Safety" forbids, and doing it by proxy is still +doing it. Where no other worker remains to bound the wait, the same liveness answer bounds it +alone. + +## Raising a Blocked Question + +A group reaching a question only the maintainer can answer stops that group and nothing else. +`pr-review-conduct`'s "Escalate to the maintainer when" list is what makes a finding a question +rather than a decision. + +- **The group stops, and nothing about it is disposed of.** No thread is resolved, no finding is + answered on the orchestrator's own judgment, and no pull request merges. +- **The other groups keep driving.** One stopped group never idles the round. +- **The question travels worker to orchestrator to maintainer, and reaches the maintainer at the + point the work stops.** A worker escalates to whoever dispatched it, per `pr-review-conduct`, + since a dispatched subagent is not the seat that can prompt anyone. The orchestrator is that + seat, and it asks then and there through the interface's own prompt mechanism, per + `GOVERNANCE.md` "Communicating with the User". Holding the question for a round boundary is the + handoff-buried-in-a-paragraph that section forbids, and a boundary can be a long way off or, + for a group blocking the promotion pull request, never arrive at all. Where several groups stop + close together, their questions go in one prompt, which is batching without deferral. +- **The question is also written on its issue**, so it survives the session that asked it. +- **A stopped group keeps its branch and its claim**, and its worktree is left exactly as it + stands while the question is open, since the answer may be that the work in it continues. Say in + the prompt whether that worktree holds uncommitted work, because what becomes of it is part of + what is being asked rather than something to settle while waiting. +- **Resuming retires that worktree first, then dispatches a fresh worker.** Git refuses to attach a + branch already checked out somewhere, per `repo-worktree`, so a fresh worker cannot take the + branch while the stopped tree holds it. Once the answer is in, remove that worktree if it is + clean, or apply what the answer said about its uncommitted work and then remove it, and only then + dispatch. This is the same retire-then-dispatch shape "Bounding the Wait on a Worker" uses, and + no worker ever inherits another's tree. + +## The Promotion Boundary + +Each round ends with at most one develop -> main promotion pull request, driven to green and left +for the maintainer, so that one carries a single round rather than accumulating several. + +**This section assumes the release workflow model**, where feature work reaches develop through +squash-merged pull requests and a promotion pull request carries develop to main. A repository +whose registry `workflowModel` reads `operational` differs on both counts, per +`operational-vs-release-workflow`: it commits to develop directly, and it opens a promotion pull +request only occasionally rather than per round, so confirm with the maintainer whether one is +wanted at all there. + +Neither difference changes how this run's own work is read. Every worker invokes `drive-pr` +whatever the model, so this run's fixes still arrive as squash-merged feature pull requests +carrying the `Closes on promotion:` line, and the two hops still read them. What the model adds is +a second kind of commit in the same range, a direct push that never had a pull request, whose +issues are recoverable only from the commit message itself. Read both, the pull requests for this +run's work and the commit messages for the direct pushes, since reading either alone returns a +partial set, and the range rather than this round is still what covers earlier work no promotion +has carried. + +1. **Open it whenever develop is ahead of main**, which `git fetch origin` and then + `git rev-list --count origin/main..origin/develop` answers, and this round's own outcome does + not. Fetch first every time: a stale remote-tracking ref reports zero and the round would report + nothing to promote while develop carries work. A round in which every group deferred or parked + can still owe a promotion pull request, for work an earlier round landed and no promotion has + yet carried. A count of zero is the only case with nothing to promote, and the round reports + that instead of attempting one. +2. Drive its review loop per `drive-pr` steps 5 through 8, **with a review-round budget set before + the first one**, the same discipline "Bounding a Prose Group" applies to a feature branch. That + loop repeats until the promotion pull request carries no open finding, and nothing in it + terminates on its own, so when the budget is reached, stop and put the state to the maintainer + rather than continuing to spend the run's only forward gear on one pull request. +3. Put the ready pull request to the maintainer through the interface's own prompt mechanism, + naming the merge as the action that unblocks the run. The maintainer's merge is the run's clock, so one + reported in a closing paragraph and never actually asked about stalls every round behind it. + Do not merge it. +4. **While it waits, develop takes only what that pull request itself needs.** A finding against + it lands as its own feature -> develop pass, and that landing moving its head is expected, since + its head **is** develop. **That pass is dispatched as a worker like any other**, which is the + one push the freeze permits and the reason the orchestrator still opens no branch of its own. + `drive-pr` step 6 sends the seat driving a promotion pull request back through its own steps 1 + to 4 for such a fix, and here that seat dispatches rather than drives it. +5. **A promotion fix outranks any file claim.** A group holding a file it needs yields, because the + promotion pull request is what the whole run is queued behind. A holder that is merely parked + yields by handing the file over. A holder that already pushed and has an open pull request + yields by having that pull request wait, its branch untouched, and by the promotion fix taking + the file, since the two must not be in flight on one file at once. Once the fix lands, that + pull request waits untouched until the freeze lifts. Reconciling its content is the next round's + worker's job rather than the orchestrator's, which opens no branch and edits nothing. The + orchestrator retires that group's worktree, the branch and its pull request left standing, + which is the worktree-only disposition "Cleanup Is the Orchestrator's" separates out and the + retire-then-dispatch shape "Raising a Blocked Question" uses, and then dispatches a fresh + worker on that same branch, briefed either to merge develop in to pick the fix up or to narrow + the change to drop the file. Never rebase it: + its branch is already pushed, so a rebase needs the force-push `git-commit-conventions` forbids + outright. +6. **Nothing else pushes, and nothing else is dispatched.** The promotion fix of step 4 is the one + exception to both, and everything in this step is said of the next round's work rather than of + it. That round's preparation is orchestrator work and continues: rank, group, and verify claims. + Its dispatch waits, because a worker has exactly one procedure, `drive-pr`, whose second step + pushes and opens a pull request, so a next-round worker dispatched under the freeze would either + break it or sit in a state that procedure does not describe. None is left running across the + wait either, since a worker held idle for an unbounded maintainer wait is one doing nothing at a + cost, and dispatching it after the merge starts it against the state that merge produced rather + than the state it was briefed on. +7. The merge unfreezes the run, and the prepared round dispatches then. + +The run advances no faster than the maintainer merges promotion pull requests. That is the human +gate, stated plainly rather than left for a stalled round to reveal. + +### Assembling the Promotion Body + +The body carries one `Fixes #N` per issue whose fix is on develop and not yet on main. Two hops +are needed rather than one, because the commits in `origin/main..origin/develop` are squash merges +whose subjects carry the **pull request** number and not the issue number, and this skill +deliberately keeps the closing keyword off the feature pull request, so nothing in the range names +an issue directly. Read the pull request numbers out of that range, freshly fetched, then read each +of those pull requests for its `Closes on promotion:` line, the one "Grouping and File Claims" +requires every feature pull request to carry and every worker brief to ask for. + +**That line exists because the set has to be stated rather than inferred**, distinct from any +issue a body merely mentions. A body routinely references an issue it +does not fix, the deferral issues its own review round filed most of all, and those have to stay +open as the next round's ranking input. Sweeping in everything a body mentions would close them at +the promotion merge and delete the next round's backlog, so the promotion body reads the explicit +line and never the mentions. An issue named nowhere is one nothing closes, which is a missed +closure a later round notices, where the opposite error destroys work. + +Deriving the set from the range rather than from what this round dispatched is what covers a group +that deferred or parked, contributing none, and an earlier round's work that no promotion has yet +carried. A fix landing during the freeze adds its issue to a body already written, so amend the +body when it lands rather than leaving the issue to be closed by hand. + +## Run State + +- **Working notes outside the repository hold the round**: the ranking, the working groups, the + tier choices, and the worker assignments. A scratch file the harness gives a session serves + where there is one, and any note kept out of the tree serves where there is not. It is working + state, and nothing about it is committed. +- **GitHub holds what outlives the session.** A claim comment records a group's file set, a pull + request body records what a round carried, a `Fixes #N` line records what the promotion closes, a + deferral issue records what was put off and why, a thread reply records how a finding was + disposed of, and a stopped group's question is a comment on its issue. +- **No tracker file is committed for the run.** A committed tracker is a file every round rewrites, + which is the contention the file-claim rule exists to prevent. + +## Ending the Run + +The run ends at either of two points, and they are different endings. + +- **The backlog is worked out**, meaning a full re-rank finds no open issue this skill can act on. + That is not the same as zero open issues, since a backlog of nothing but maintainer questions is + a finished run. Report it as finished, with the questions put to the maintainer. +- **The session ends**, for a context limit or because the maintainer stops it. The run ends with + it, since the merge authorization was bounded to that session. What the rounds already landed + stands on its own in GitHub, and the branches, claim comments, and questions left behind are + what a later run reads to pick the work up. That later run is a new run, named again, not this + one continuing. + +Report at every round boundary and at either ending: what merged to develop, what the promotion +pull request carries, what was newly filed, what is stopped and on which question, and what the +next round would pick. + +## Mechanics Live Elsewhere + +- The review loop, the Merge Gate, the five finding outcomes, and when a finding is a question: + `pr-review-conduct`. +- Driving one pull request, and the promotion-pull-request wrinkle: `drive-pr`. +- Worktree isolation, the base branch, and the cleanup procedure this skill re-seats: + `repo-worktree`. +- Closing keywords, branch protection, and the promotion trap: `operational-vs-release-workflow`. +- The pre-push adversarial pass and its recorded receipt: `local-strict-review`. +- Merging the promotion pull request and dispatching a release: `merge-and-release`, invoked + separately. +- Delegation briefing shape, model-tier rules, wait discipline, and session scope: `AGENTS.md` + "Context and Delegation Discipline". diff --git a/.claude-plugin/fleet-skills/skills/drive-pr/SKILL.md b/.claude-plugin/fleet-skills/skills/drive-pr/SKILL.md index 2bfba187..b234fb29 100644 --- a/.claude-plugin/fleet-skills/skills/drive-pr/SKILL.md +++ b/.claude-plugin/fleet-skills/skills/drive-pr/SKILL.md @@ -5,7 +5,8 @@ description: >- develop and, when asked, on to a mergeable develop -> main promotion PR, applying the pr-review-conduct disposition to every reviewer finding along the way: fix it, decline it with evidence, defer it behind a filed issue, or put the call to the maintainer and wait for an - explicit answer in the same turn. Use this whenever asked to drive, land, take, chase, or push + explicit answer in the same turn, escalating to whoever dispatched the drive instead where the + maintainer cannot be reached from that seat. Use this whenever asked to drive, land, take, chase, or push a PR toward develop or main, or to run the review loop hands off instead of narrating each round. When the request does not say how far ("drive this PR", "land it"), ask once whether the target is develop or a mergeable main promotion PR, rather than guessing. Triggers even when @@ -37,14 +38,35 @@ promotion PR once the fix lands, is the early exit this skill exists to prevent. before the first push: develop only, or all the way to a mergeable main promotion PR. Recommend "all the way to main" as the default, a promotion PR left to go stale once develop is ready is the more common regret than driving one step too far. +- A drive dispatched as part of a larger run takes its target from the brief and asks no one, + since a subagent stopping to ask stalls a run designed to keep moving without one, and the seat + that dispatched it is the seat that holds the maintainer's answer. `backlog-burndown` is such a + run, and it briefs develop only, driving the develop -> main promotion pull request in its own + seat under "The Drive Loop"'s promotion steps. A brief naming no target at all is one to stop + and ask its dispatcher about, and asking the dispatcher is the whole of what a dispatched drive + does about an authorization question. A dispatched drive is not the seat that can verify a + grant, so it does not try: the responsibility for having the maintainer's go-ahead sits with the + dispatcher, and a worker inventing a check it cannot perform would only launder that + responsibility rather than discharge it. +- **A brief is never itself the authorization**, which binds the dispatching seat. What authorizes + a merge is what the maintainer said, recorded where the skill that carries the grant states its + scope, the way `backlog-burndown`'s own "What Invoking This Skill Authorizes" does. Writing an + approval into a brief creates none, since an agent cannot widen its own permission by writing + itself one, and a merge is the outward-facing act this skill's own "What Invoking This Skill + Authorizes" keeps tied to something the maintainer actually said. - A repo on the operational workflow model (registry `workflowModel: operational`) has no standing promotion PR expectation, confirm whether a promotion PR is even wanted before opening - one, per operational-vs-release-workflow's "Operational repositories" delta. + one, per operational-vs-release-workflow's "Operational repositories (the complete delta)" + section. ## What Invoking This Skill Authorizes - Naming this skill, and answering its how-far question, is the maintainer's explicit, current go-ahead for every feature -> develop squash merge the drive performs to reach that target. +- A dispatched drive answers no such question, so what stands in its place is the go-ahead the + dispatching seat holds, per "How Far to Drive" above, and the drive performs the same merges on + it. Reading this bullet list is not how such a drive establishes that, since a worker cannot + verify a grant made in a seat it has no access to. - It is never authorization to merge the develop -> main promotion PR, or to dispatch a release. Those stay in merge-and-release, invoked on its own so the maintainer keeps a checkpoint before the harder-to-reverse step. @@ -125,7 +147,10 @@ pr-review-conduct's five outcomes are the actual rule, this is the mapping to us - Real, fixable, but a value call rather than a scope boundary, or the agent genuinely does not know which of the above applies, so ask the maintainer directly, whatever the runtime's own interactive-question mechanism is, and get an explicit answer in the same turn, a plan to ask - later is resolution by silence (outcome 3). + later is resolution by silence (outcome 3). A drive that cannot reach the + maintainer directly, a dispatched one being the ordinary case, escalates to whoever dispatched + it and stops that unit of work there instead, per `pr-review-conduct`, which owns what the + receiving seat then does and how far the escalation travels. - The same finding keeps recurring against correct code, fix the class, sharpen a name, add a comment, or take the rule itself to the maintainer, rather than re-arguing the instance every round (outcome 5). diff --git a/.claude-plugin/fleet-skills/skills/local-strict-review/SKILL.md b/.claude-plugin/fleet-skills/skills/local-strict-review/SKILL.md index cecb485e..336effcc 100644 --- a/.claude-plugin/fleet-skills/skills/local-strict-review/SKILL.md +++ b/.claude-plugin/fleet-skills/skills/local-strict-review/SKILL.md @@ -57,6 +57,8 @@ Bounds: read-only. No edit, no stage, no commit, no push, no PR-hosted write of **Model tier:** the strongest tier this session can reach, per `AGENTS.md` "Match the model tier to the judgment" and "Never tier down the seat holding the judgment", applied here to the reviewer rather than the author. Run the pass on the same tier that authored the change when only one tier is reachable, a second, adversarially-prompted look still catches what the authoring pass's own "looks ready" judgment did not. +"This session can reach" means the tier this session can name when it dispatches the reviewer, rather than the tier this session is itself running on. A session deliberately tiered down for execution work, a worker dispatched by an orchestrator being the ordinary case, names a stronger tier for the reviewer where its harness lets it, since tiering down the author is the reason the reviewer must not follow it down. What a given harness and account actually permit varies, so treat this as the tier to ask for rather than one to assume. Where a dispatch reaches several tiers but exposes no way to name one, take what it gives and run the pass, on the same reasoning as the single-reachable-tier sentence above. A seat that cannot dispatch a subagent at all cannot perform this pass. Instead of pushing, it reports that it could not run the pass, to whoever dispatched it, or to the maintainer where nobody did. Either way it is a push that does not happen rather than a pass quietly skipped. The headless `run --backend` route under "Recording the Pass" is not the substitute: it runs a vendor CLI against its own review, which never carries the brief above, so it satisfies the rule this section states only where that separate route is what a capture point asked for. + ## Recording the Pass `scripts/local_review.py` is what makes this rule checkable rather than something each session has to remember. For the pass above, the engine only records that it happened, keyed on the content the reviewer actually saw, and its `run --backend ` subcommand is the separate case where a headless backend performs the review and records its own count. That receipt is what a capture point reads, the hub's own `.husky/pre-push` hook being the only one today, and a repository having none unless it adds one, since no manifest entry carries it. @@ -123,7 +125,7 @@ The digest is bound to the read for the same reason `--expect-digest` is above: ## Disposing of Findings -Every finding maps to one of `pr-review-conduct`'s five outcomes, at whichever moment this pass ran: fixed (1), evidence-disproven (2), escalated to the maintainer for an explicit call (3), filed as a deferred issue (4), or, if it keeps recurring, taken as a signal to fix the class (5). Outcome 2 is the agent's own on its own evidence, covering a finding that is not real and one that is structurally out of scope. A finding judged real and left unfixed is never the agent's alone, so outcome 3 needs the maintainer's explicit answer in the same turn, reached only once outcome 2 is ruled out, and outcomes 4 and 5 reach the maintainer too, for the deferral and for the rule itself. Running this pass is required before every push toward a pull request, per `agent-conduct`. Two claims sit next to each other here and they point opposite ways, so they are stated apart rather than in one sentence. **The pass is mandatory**, and where a capture point enforces it, a push carrying content no recorded pass covers is refused. That refusal is the gate working rather than a fault to route around. **The findings stay advisory**, and the count a pass raises gates nothing at all, since a pass records that a review ran and never that the content is clean. The disposition above is what closes each finding, the same posture local lint holds today. It posts nothing to GitHub, it only reports to the session driving the work. A finding raised here and not fixed is not thereby resolved: the same finding shape reaching a PR-hosted reviewer later still gets its own fresh disposition, per `pr-review-conduct`'s "a disposition decided on one PR does not carry to the next." +Every finding maps to one of `pr-review-conduct`'s five outcomes, at whichever moment this pass ran: fixed (1), evidence-disproven (2), escalated to the maintainer for an explicit call (3), filed as a deferred issue (4), or, if it keeps recurring, taken as a signal to fix the class (5). Outcome 2 is the agent's own on its own evidence, covering a finding that is not real and one that is structurally out of scope. A finding judged real and left unfixed is never the agent's alone, so outcome 3 needs the maintainer's explicit answer in the same turn, reached only once outcome 2 is ruled out, or, where this pass ran in a seat that cannot reach the maintainer, an escalation to whoever dispatched it that stops the work there, which stops the push this pass runs before, and outcomes 4 and 5 reach the maintainer too, for the deferral and for the rule itself. Running this pass is required before every push toward a pull request, per `agent-conduct`. Two claims sit next to each other here and they point opposite ways, so they are stated apart rather than in one sentence. **The pass is mandatory**, and where a capture point enforces it, a push carrying content no recorded pass covers is refused. That refusal is the gate working rather than a fault to route around. **The findings stay advisory**, and the count a pass raises gates nothing at all, since a pass records that a review ran and never that the content is clean. The disposition above is what closes each finding, the same posture local lint holds today. It posts nothing to GitHub, it only reports to the session driving the work. A finding raised here and not fixed is not thereby resolved: the same finding shape reaching a PR-hosted reviewer later still gets its own fresh disposition, per `pr-review-conduct`'s "a disposition decided on one PR does not carry to the next." ## When to Run It diff --git a/.claude-plugin/fleet-skills/skills/pr-review-conduct/SKILL.md b/.claude-plugin/fleet-skills/skills/pr-review-conduct/SKILL.md index 836fdb32..1073b824 100644 --- a/.claude-plugin/fleet-skills/skills/pr-review-conduct/SKILL.md +++ b/.claude-plugin/fleet-skills/skills/pr-review-conduct/SKILL.md @@ -172,6 +172,16 @@ from a hub checkout. Do not use a provider connector or reconstruct the GitHub m - A finding is judged real but should not be fixed. That decision is never the agent's alone. - An architectural redesign is proposed rather than a bug fix. +An agent that cannot reach the maintainer directly, a dispatched subagent being the ordinary case, +escalates to whoever dispatched it and stops that unit of work there. It never substitutes its own +judgment for the escalation because asking is inconvenient from where it sits, and it never resolves +the thread to keep moving. A dispatcher receiving one puts it to the maintainer at the point that +work stopped, per `GOVERNANCE.md` "Communicating with the User", and deciding it instead so the +dispatcher's own work keeps moving is the same resolution by silence this skill's own +ask-the-maintainer outcome forbids, one seat further from the maintainer. The escalation may travel through several seats, and what stays stopped is the +escalated unit of work, in whichever seat holds it, until the answer arrives. A dispatcher's other +work is not stopped by it. + ## Mechanics Live Elsewhere This skill is the provider-agnostic contract. Use `scripts/pr_review.py` from a hub checkout for diff --git a/.github/skills/backlog-burndown/SKILL.md b/.github/skills/backlog-burndown/SKILL.md new file mode 100644 index 00000000..47c14be1 --- /dev/null +++ b/.github/skills/backlog-burndown/SKILL.md @@ -0,0 +1,509 @@ +--- +name: backlog-burndown +description: >- + Burns a ptr727/ProjectTemplate fleet repository's open-issue backlog down by rounds: rank the + open issues, group them so no two groups touch the same file, dispatch one subagent per group to + drive its own feature -> develop pull request to merge, open at most one develop -> main + promotion pull request per round for the maintainer to merge, then re-rank and go again, + because every review round files new issues that change what the next round should pick. Use + this whenever asked to work the backlog, burn the backlog down, clear the open issues, resolve + or cull the backlog, or run issues in parallel until they are gone, and whenever the ask is a + standing one rather than a single named issue. Triggers even when the backlog looks small enough + to work by hand, because the failure it exists to prevent is two agents editing the same + prose-heavy Markdown file in the same round, which surfaces as a merge conflict long after both + branches are already deep in review. Drives one repository, the one the session is in, never a + fleet-wide sweep. Ends when a re-rank finds nothing left it can act on, and never merges main, + which stays the maintainer's own step through merge-and-release. +--- + +# Backlog Burndown + +## Why This Exists + +Asking for one issue to be fixed is `drive-pr`'s job and needs no skill above it. Asking for a +whole backlog to be worked down is a different problem, and three things about it are not obvious. +Parallelism is bounded by file overlap rather than by agent count, so the grouping decides the +throughput. The backlog is not a fixed list, since every review round files deferral issues that +belong in the next round's ranking, so a plan made once is stale by its second round. And a +prose-heavy repository conflicts on content rather than on syntax, so two agents rewording the +same section produce a conflict no tool resolves and no reviewer catches early. + +## The Two Seats + +Everything below turns on which seat is acting, so both are named once here. + +- **The orchestrator** is the session this skill runs in. It ranks, groups, dispatches, and drives + the promotion pull request. It opens no feature branch and fixes no issue itself, which is what + keeps it out of every worker's files. It does write: it comments on issues, it drives and + amends the promotion pull request, and it owns worktree and branch cleanup, which "Dispatching a + Worker" states in full. +- **A worker** is one dispatched subagent holding one group, one worktree, and one feature branch, + which is `AGENTS.md` "Session Scope"'s one-branch-one-deliverable rule applied as written. It + drives its own pull request into develop and ends there. + +## Scope + +One repository, the one the session is in, resolved from its own `origin`. Reads are unrestricted +per `GOVERNANCE.md` "Repository Boundaries and Write Safety", so reading another repository's +issues breaks no rule. Working them is out of this skill's scope, and a fleet-wide backlog +sweep is a different request. That section bounds writes to the owner of +this repository rather than to this repository alone, and a run staying inside the one repository +it was invoked for is narrower than the rule requires, deliberately. + +## What Invoking This Skill Authorizes + +- Naming this skill is the maintainer's explicit go-ahead for the feature -> develop squash merges + this run performs, in every round of it. A per-round merge question would idle every agent at + every boundary, which is the thing this skill exists to avoid. +- **The grant is bounded by the session it was named in.** A run interrupted and resumed in a new + session needs the skill named again, which costs one sentence and is the difference between a + grant and a mode. A grant read back from a note is one nobody gave. +- The grant does not weaken the `pr-review-conduct` Merge Gate. It answers that gate's item 5 for + this run's feature -> develop merges and nothing else, so a pull request with one open finding + still does not merge. +- It is never authorization to merge a develop -> main promotion pull request, to dispatch a + release, to close an issue on judgment, or to touch another repository. Each stays the + maintainer's, and merging a promotion pull request is `merge-and-release`, invoked on its own. + +## The Round + +A round is the unit. Each one runs these steps in order. + +1. **Rank** every open issue, per "Ranking". +2. **Group** the top of that ranking, per "Grouping and File Claims". +3. **Verify** each group's predicted file set against everything in flight before dispatching + anything. A group whose files are already claimed waits for the next round. +4. **Dispatch** at most four workers, one per group, per "Dispatching a Worker". +5. **Collect** each worker's outcome: merged to develop, stopped on a question only the + maintainer can answer, parked behind another group's file claim, or abandoned, which is what + the adjudication in "Grouping and File Claims" and a confirmed-gone worker both produce. Bound + this wait per "Bounding the Wait on a Worker". +6. **Clean up** the worktrees, local branches, and merged remote branches of every group that has + finished or been abandoned, per "Dispatching a Worker". +7. **Promote**, per "The Promotion Boundary". +8. **Re-rank from scratch**, and note that the next round prepares under the freeze "The Promotion + Boundary" describes whenever a promotion pull request is still waiting on the maintainer, so it + ranks, groups, and verifies claims, and dispatches nothing until that merge lands. Do not carry + the previous round's ranking forward. The deferral issues this round's reviews filed are now + open issues with a claim on the next round's attention, and an issue that ranked low last round + can rank high once a sibling fix lands. + +## Ranking + +Where the repository carries no priority label, and the hub does not, the ranking is the +orchestrator's judgment against stated criteria rather than a field read off the issue. Where a +repository does carry one, that label is the first input and these criteria order what it leaves +tied. Write the ranking, and the reason for the top of it, into the report this skill makes at +each round boundary, per "Ending the Run". + +Rank on these, highest first where they conflict: + +- **It blocks other work.** An issue whose fix changes a rule, a gate, or a shared contract that + other issues' fixes must then obey is worth doing before them, not after. +- **It is a correctness or safety defect** in something that runs, over an improvement to + something that reads. +- **It is a root cause rather than a leaf.** A parent issue grouping several filed symptoms is + worth more than any one of its children, and fixing it may close them. +- **It is new.** An issue filed by a recent review round is evidence of something the current + content actually got wrong, and it is the freshest context anyone has on it. +- **It is small and self-contained**, as a tie-break only. Size breaks a tie between two issues of + equal value, and it never promotes a trivial issue over a real defect. + +An issue that asks a question rather than states a defect is not ranked and is never guessed at. +It has no group, no worker, and no claim, so nothing in "Raising a Blocked Question" applies to it +except how the question travels. It goes to the maintainer at the end of +ranking, in the same prompt as any other question the run is sending at that moment and in one of +its own otherwise, rather than waiting for a stop that may not come. It stays unranked until +answered. + +## Grouping and File Claims + +Group so that **no file is claimed by two live groups at once.** This is the rule the skill exists +for, and it binds harder than any throughput target. + +- **Group by the files a fix will touch**, not by the issues' subject matter. Two issues that read + as unrelated but both edit a shared governance file are one group. Two issues that read as near + duplicates but touch different files are two groups. +- **Genuine duplicates are one group.** Never close an issue during triage on the orchestrator's + own judgment. Comment to cross-link the pair, and let the fix close both. +- **Closing keywords go on the promotion pull request**, not the feature pull request, per + `operational-vs-release-workflow`. A feature pull request merging into develop fires no + auto-close, so a `Fixes #N` line there closes nothing. **The feature pull request body instead + carries a line reading `Closes on promotion: #N`**, listing every issue that pull request + actually fixes and nothing it merely mentions, which is the line the promotion body is assembled + from, per "The Promotion Boundary". Without that line nothing records which issues a merged pull + request closes, since the closing keyword is deliberately absent and a body's other issue + references are not the same set. +- **Predict each group's file set** by reading the issues, not by guessing from their titles. +- **Record every claim on the issue itself**, as a comment naming the predicted file set **and the + branch that holds it**, before dispatching. The branch name is what lets a later session walk + from a worktree it found back to the claim explaining it, which is the direction the next bullet + actually travels. Working notes do not survive the session, so a claim living only in them is + invisible to the round that has to respect it, and recording it on the issue is what makes the + next bullet a read of durable state rather than of the orchestrator's memory. +- **Verify the prediction before dispatching**, against everything in flight, which is wider than + this round: the files changed by every open **feature** pull request on this repository (`gh pr + diff --name-only` per open pull request), and the claim comments of every group still + holding a branch, parked groups from earlier rounds included. `git worktree list` reports the + registered worktrees and the branch checked out in each, which is not the same as every branch + that exists, so pair it with `git branch -r`, after `git fetch --prune origin`, for one that was + pushed and whose worktree is already gone, and with `git branch` for one that was never pushed + and whose worktree is already gone. That third read is not optional here: the worktree-only + disposition retires a tree and leaves its branch standing, so this skill produces exactly that + state, and a local branch holding commits no remote has is invisible to both other reads. Prune + rather than plain fetch because `--prune` is what drops a remote-tracking ref whose branch is + gone from the remote, deleted there by another session or through the web interface, and a + plain fetch leaves that ref in `git branch -r` to defer valid groups forever. Stop and report a + failed fetch rather than reading `git branch -r` anyway: the remote-tracking refs still resolve + from what the last successful fetch left, so the scan returns a confident answer about a remote + it did not reach, missing a branch pushed since and keeping one deleted since. The round + stops there and reports, rather than dispatching against a stale answer, and stopping rather + than deferring is what the cleanup and promotion steps need too, since both read the same + remote. + Those three enumerate the branches, with one gap: a branch in the standalone clone `repo-worktree` allows as a fallback is + reached only once it is pushed, since `git branch -r` inventories the remote rather than this + repository's checkouts. The claim comments are what say which files each branch holds, since a branch name says nothing about a file set, an unpushed branch has no + pull request diff to read, and no diff of any branch reports the predicted set a claim records + before the work is committed. **Exclude the open promotion pull request from that enumeration.** + Its diff is all of `origin/main..origin/develop`, so counting it claims nearly every file any + earlier round touched, and a round run during the freeze would defer every group it formed. A + predicted claim that collides with a real one is a group deferred to the next round, never one + dispatched hoping the overlap stays small. +- **A branch no claim comment covers still has to yield a file set.** The maintainer's own + worktree and a hand-driven task's branch are both enumerated above and neither carries a claim + comment, so reading only the comments records them as holding nothing, which is the collision + this section exists to prevent rather than the absence of one. Read the branch itself instead: + `git diff --name-only origin/develop...` for what it has committed, and, for a + registered worktree, `git -C status --porcelain` for what it holds uncommitted. Where + neither read is available the set is unknown rather than empty, and an unknown set collides with + every group, so ask the maintainer what that branch holds per "Raising a Blocked Question". +- **Re-verify when a worker reports that its real file set grew** beyond its claim. A worker + needing a file another group holds stops and reports rather than editing it, and the + orchestrator decides which group keeps the file, then tells the loser which of three things to + do rather than leaving it to choose: narrow its change to drop that file, park until the holder + merges, or abandon its branch and return the issue to the next round's ranking. Where the loser + has already opened a pull request, say whether it closes or waits, since one left open on an + abandoned branch reads to every later round as a live claim. **Update the claim comment whenever + the adjudication changes what a group holds**, in either direction, or the durable record drifts + from the real claim it exists to report. +- **Cap the round at four workers**, whatever the grouping allows. + +## Bounding a Prose Group + +A group whose files are prose-heavy Markdown bloats in a way a code group does not, and it needs +its own bound stated in the worker's brief. + +- **The change stays inside the units the issue names.** Rewording an adjacent section because it + now reads inconsistently is the next issue, filed, not this one's diff. +- **Deleting a claim beats qualifying it.** Where a review disproves something a rule leaned on, + remove the rule that leaned on it. A narrowed qualifier is where a new false claim gets + introduced, and it is the most common way a prose round produces the finding the following round + then fixes. +- **Set a review-round budget before the first push.** A whole-unit prose review can run many + rounds where a finding was introduced by the previous round's fix, so state a number in the + brief, and when it is reached, land what is correct and file the remainder rather than churning. + +## Dispatching a Worker + +Brief on `AGENTS.md` "Context and Delegation Discipline"'s subagent shape. + +- **The worker drives its group to a develop merge**, by invoking `drive-pr` with the target + stated as develop only. That skill owns the review loop, the finding disposition, and the merge, + so brief the group and the bounds rather than restating the loop. +- **The worker creates its own worktree**, always, as `drive-pr` step 1 and `repo-worktree`'s + task-start mandate already require of the task itself. No worker inherits another's worktree, + which is why "Bounding the Wait on a Worker" either removes a dead worker's tree and its + branch or leaves that tree untouched for the maintainer, and never passes it on. +- **The worker does no cleanup**, which is this skill's one stated override of `drive-pr` step 4 + and of `repo-worktree`'s post-merge procedure. Say so in the brief, because a worker following + either alone will clean up. The worker still performs step 4's merge itself, and what the override + moves is that step's two cleanup halves, the worktree procedure and the verify-then-delete of the + merged remote branch, **both** rather than only the first. "Cleanup Is the Orchestrator's" below, in this + same section, says why and what it covers. +- **The worker runs `local-strict-review` before every push**, including one that only fixes a + review finding. That pass dispatches a reviewer of its own, so a harness where a subagent cannot + dispatch one leaves the worker unable to run it and unable to push. It reports that rather than + pushing, and its worktree is then retired, since git refuses to attach that branch anywhere else + while the reporting tree holds it. The branch is left standing for its own reason, that the + commits it already carries are what the re-dispatched worker continues from. This is the + worktree-only disposition "Cleanup Is the Orchestrator's" separates out, so a clean tree is the + whole test. A clean + tree is retired and the group re-dispatched to a seat that can dispatch. A dirty one is left + exactly as it stands and the group stopped for the maintainer, as is a group for which no seat + that can dispatch exists. That retire-and-re-dispatch case presumes the branch is reachable from + this repository, which the standalone clone `repo-worktree` allows as a fallback breaks: a worker that never + pushed holds its commits only in that clone, where this repository has no ref to hand a + replacement and nothing to retire, so re-dispatching loses the work rather than continuing it. + That group stops for the maintainer with the clone named, and no seat this skill defines resumes + it, since a worker never inherits another's checkout and the orchestrator opens no branch and + edits nothing. Neither the worker nor the orchestrator pushes around the missing pass. +- **The brief names the branch the worker will use**, which is what lets the claim comment record + it before dispatch. The worker still creates its own worktree, on that named branch rather than + one of its choosing, since a claim naming a branch nobody used points at nothing. +- **The brief requires the `Closes on promotion:` line** in the pull request body, listing exactly + the issues this group fixes. A worker never reads this skill, and `drive-pr` does not ask for the + line, so a brief that omits it produces a pull request nothing can derive a closing set from. +- **The brief names the files this group owns and the files it must not touch.** A subagent never + reads this skill, so a claim it was never given is a claim it cannot respect. State this group's + claimed set, state that any other group's file is out of bounds, and state the duty that makes + the re-verification path work: a worker needing a file outside its claim stops and reports rather + than editing it, and waits for the orchestrator to adjudicate. +- **The worker never merges to main** and never resolves a thread it did not actually dispose of. + +### Cleanup Is the Orchestrator's + +`repo-worktree`'s post-merge procedure returns the base clone to current develop before proving +the cleanup, and `operational-vs-release-workflow` states that requirement independently. Four workers doing +that concurrently mutate one shared checkout, which `GOVERNANCE.md` "Repository Boundaries and +Write Safety" forbids outright by giving each task its own checkout. A worker also cannot +finish the procedure from inside its own worktree, since removing that worktree leaves it with no +working directory in which to delete its branch. + +So the whole procedure moves to the orchestrator, which runs it from the base clone at the round's +cleanup step, while no worker is live in a tree it touches. It carries `drive-pr` step 4's remote +half too, verifying the merged branch's tip against the pull request's `headRefOid` before +`git push origin --delete`, since taking that step from the worker without naming a new owner +would leave a live remote branch behind every group. It covers every group that is done with its tree, +which is the finished ones **and the abandoned ones**: a group told to abandon its branch keeps a +registered worktree until something removes it, and that worktree holds a live claim that would +collide with the very group the next round re-forms for the same issue. For a merged group the +procedure is deferred rather than changed: `repo-worktree`'s verify-before-removing and +prove-the-cleanup steps run unchanged, just later and in one seat. + +**Retiring a worktree and deleting its branch are two dispositions with two tests**, and citing +one for the other is how a removal that discards nothing gets routed to the maintainer, or a +removal that discards commits gets waved through. Retiring a worktree alone, the branch left +standing, risks only what is uncommitted in it: a clean tree is the whole test, the branch's own +contents do not enter it, and a tree that is not clean is left exactly as it stands while the +group goes to the maintainer per "Raising a Blocked Question". Deleting the branch as well risks what is committed, so it carries +whichever branch check the group's state calls for, `repo-worktree`'s verify-before-removing for a +group whose pull request merged and the no-merge substitute below for one whose has not. Which +check that is matters: a squash merge never makes the feature tip an ancestor of develop, so the +substitute would fail on every merged group if it were read as covering them. Every disposition in +this skill names which of the two it is. + +An abandoned group, and a dead worker's clean tree, have no merged pull request for +`repo-worktree`'s verify-before-removing step to read, so the check that step gives way to here is +what it exists to establish, that nothing unmerged is +being thrown away: confirm the branch carries no commit that is not already on develop, and that +its worktree is clean. Both hold, and the worktree and branch go the same way a merged group's do, +with no remote branch to delete where none was pushed. Either fails, and cleanup stops there and +the group goes to the maintainer per "Raising a Blocked Question", since past that point removal +discards work. A worker still live, a promotion fix included, keeps its worktree until the next +round's cleanup step. + +### Choosing the Worker's Model Tier + +`AGENTS.md` "Delegation" owns the model-tier rule. Read it there. This is only what it leaves to +judgment here: the tier is chosen per group rather than defaulted, because a stronger tier +produces better work up front and takes fewer review rounds to land it, which often costs less +than a cheaper worker looping. Three kinds of group are never tiered down: + +- One touching **carried canonical content**: rule text, a Skill, or anything else this repository + authors and other repositories carry, since a wrong rule propagates to every carrier. +- One touching **a gate, a ruleset, a release condition, or a carried governance section**, which + is `AGENTS.md`'s own list of what counts as a design change however small the diff looks. +- One whose issues are **complex or entangled**, where the fix depends on reasoning across several + files or on a contract not stated in the file being edited. + +State the chosen tier and its reason in the round's report. + +## Bounding the Wait on a Worker + +`AGENTS.md` requires a wait to separate its outcomes and to be bounded, so this one is. A worker +reports merged, parked, or stopped. A worker that reports nothing at all is the case needing a +bound, since it is indistinguishable from a slow one and dying mid-drive is ordinary here. + +The bound is a state read rather than a clock: when the other workers in the round have reported, +read the silent worker's branch and pull request directly, `git log` on that branch and +`gh pr view `, passing the branch as the positional argument that command takes, since a +bare `gh pr view` resolves the pull request of whatever branch the caller is standing on and never +the worker's. Let what they show decide. A pull request that is merged, or a branch whose work is +complete, means the worker died after doing the work and the group is finished. + +Anything else needs one thing established before anything is touched: whether that worker is gone +or merely slow. No git read answers that, and the two call for opposite actions, so the answer +comes from the dispatch mechanism itself, which knows whether the subagent it started is still +running. Nothing about the worktree is acted on while the answer is "still running", however long +that is. Waiting costs a round's latency and guessing costs another task's uncommitted work. + +Once the worker is confirmed gone, its worktree decides what follows. **A clean one** is cleaned up +as an abandoned group's is, per "Cleanup Is the Orchestrator's", which confirms the branch carries +no commit that is not already on develop before anything is removed. A worker that committed its +fix and then died leaves a clean tree standing over commits develop has never seen, so that check +is what separates the two. It holds, and the issue returns to the next round's ranking to be +dispatched fresh, its claim comment released with the worktree. It fails, and cleanup stops there +and the group goes to the maintainer, since past that point removal discards work. **A dirty one is left exactly as it stands** +and the group is stopped for the maintainer per "Raising a Blocked Question", naming the worktree +and what is uncommitted in it. The orchestrator does not commit that work, hand the tree to a +replacement to commit, or remove it: reaching into a tree a task was live in is what +`GOVERNANCE.md` "Repository Boundaries and Write Safety" forbids, and doing it by proxy is still +doing it. Where no other worker remains to bound the wait, the same liveness answer bounds it +alone. + +## Raising a Blocked Question + +A group reaching a question only the maintainer can answer stops that group and nothing else. +`pr-review-conduct`'s "Escalate to the maintainer when" list is what makes a finding a question +rather than a decision. + +- **The group stops, and nothing about it is disposed of.** No thread is resolved, no finding is + answered on the orchestrator's own judgment, and no pull request merges. +- **The other groups keep driving.** One stopped group never idles the round. +- **The question travels worker to orchestrator to maintainer, and reaches the maintainer at the + point the work stops.** A worker escalates to whoever dispatched it, per `pr-review-conduct`, + since a dispatched subagent is not the seat that can prompt anyone. The orchestrator is that + seat, and it asks then and there through the interface's own prompt mechanism, per + `GOVERNANCE.md` "Communicating with the User". Holding the question for a round boundary is the + handoff-buried-in-a-paragraph that section forbids, and a boundary can be a long way off or, + for a group blocking the promotion pull request, never arrive at all. Where several groups stop + close together, their questions go in one prompt, which is batching without deferral. +- **The question is also written on its issue**, so it survives the session that asked it. +- **A stopped group keeps its branch and its claim**, and its worktree is left exactly as it + stands while the question is open, since the answer may be that the work in it continues. Say in + the prompt whether that worktree holds uncommitted work, because what becomes of it is part of + what is being asked rather than something to settle while waiting. +- **Resuming retires that worktree first, then dispatches a fresh worker.** Git refuses to attach a + branch already checked out somewhere, per `repo-worktree`, so a fresh worker cannot take the + branch while the stopped tree holds it. Once the answer is in, remove that worktree if it is + clean, or apply what the answer said about its uncommitted work and then remove it, and only then + dispatch. This is the same retire-then-dispatch shape "Bounding the Wait on a Worker" uses, and + no worker ever inherits another's tree. + +## The Promotion Boundary + +Each round ends with at most one develop -> main promotion pull request, driven to green and left +for the maintainer, so that one carries a single round rather than accumulating several. + +**This section assumes the release workflow model**, where feature work reaches develop through +squash-merged pull requests and a promotion pull request carries develop to main. A repository +whose registry `workflowModel` reads `operational` differs on both counts, per +`operational-vs-release-workflow`: it commits to develop directly, and it opens a promotion pull +request only occasionally rather than per round, so confirm with the maintainer whether one is +wanted at all there. + +Neither difference changes how this run's own work is read. Every worker invokes `drive-pr` +whatever the model, so this run's fixes still arrive as squash-merged feature pull requests +carrying the `Closes on promotion:` line, and the two hops still read them. What the model adds is +a second kind of commit in the same range, a direct push that never had a pull request, whose +issues are recoverable only from the commit message itself. Read both, the pull requests for this +run's work and the commit messages for the direct pushes, since reading either alone returns a +partial set, and the range rather than this round is still what covers earlier work no promotion +has carried. + +1. **Open it whenever develop is ahead of main**, which `git fetch origin` and then + `git rev-list --count origin/main..origin/develop` answers, and this round's own outcome does + not. Fetch first every time: a stale remote-tracking ref reports zero and the round would report + nothing to promote while develop carries work. A round in which every group deferred or parked + can still owe a promotion pull request, for work an earlier round landed and no promotion has + yet carried. A count of zero is the only case with nothing to promote, and the round reports + that instead of attempting one. +2. Drive its review loop per `drive-pr` steps 5 through 8, **with a review-round budget set before + the first one**, the same discipline "Bounding a Prose Group" applies to a feature branch. That + loop repeats until the promotion pull request carries no open finding, and nothing in it + terminates on its own, so when the budget is reached, stop and put the state to the maintainer + rather than continuing to spend the run's only forward gear on one pull request. +3. Put the ready pull request to the maintainer through the interface's own prompt mechanism, + naming the merge as the action that unblocks the run. The maintainer's merge is the run's clock, so one + reported in a closing paragraph and never actually asked about stalls every round behind it. + Do not merge it. +4. **While it waits, develop takes only what that pull request itself needs.** A finding against + it lands as its own feature -> develop pass, and that landing moving its head is expected, since + its head **is** develop. **That pass is dispatched as a worker like any other**, which is the + one push the freeze permits and the reason the orchestrator still opens no branch of its own. + `drive-pr` step 6 sends the seat driving a promotion pull request back through its own steps 1 + to 4 for such a fix, and here that seat dispatches rather than drives it. +5. **A promotion fix outranks any file claim.** A group holding a file it needs yields, because the + promotion pull request is what the whole run is queued behind. A holder that is merely parked + yields by handing the file over. A holder that already pushed and has an open pull request + yields by having that pull request wait, its branch untouched, and by the promotion fix taking + the file, since the two must not be in flight on one file at once. Once the fix lands, that + pull request waits untouched until the freeze lifts. Reconciling its content is the next round's + worker's job rather than the orchestrator's, which opens no branch and edits nothing. The + orchestrator retires that group's worktree, the branch and its pull request left standing, + which is the worktree-only disposition "Cleanup Is the Orchestrator's" separates out and the + retire-then-dispatch shape "Raising a Blocked Question" uses, and then dispatches a fresh + worker on that same branch, briefed either to merge develop in to pick the fix up or to narrow + the change to drop the file. Never rebase it: + its branch is already pushed, so a rebase needs the force-push `git-commit-conventions` forbids + outright. +6. **Nothing else pushes, and nothing else is dispatched.** The promotion fix of step 4 is the one + exception to both, and everything in this step is said of the next round's work rather than of + it. That round's preparation is orchestrator work and continues: rank, group, and verify claims. + Its dispatch waits, because a worker has exactly one procedure, `drive-pr`, whose second step + pushes and opens a pull request, so a next-round worker dispatched under the freeze would either + break it or sit in a state that procedure does not describe. None is left running across the + wait either, since a worker held idle for an unbounded maintainer wait is one doing nothing at a + cost, and dispatching it after the merge starts it against the state that merge produced rather + than the state it was briefed on. +7. The merge unfreezes the run, and the prepared round dispatches then. + +The run advances no faster than the maintainer merges promotion pull requests. That is the human +gate, stated plainly rather than left for a stalled round to reveal. + +### Assembling the Promotion Body + +The body carries one `Fixes #N` per issue whose fix is on develop and not yet on main. Two hops +are needed rather than one, because the commits in `origin/main..origin/develop` are squash merges +whose subjects carry the **pull request** number and not the issue number, and this skill +deliberately keeps the closing keyword off the feature pull request, so nothing in the range names +an issue directly. Read the pull request numbers out of that range, freshly fetched, then read each +of those pull requests for its `Closes on promotion:` line, the one "Grouping and File Claims" +requires every feature pull request to carry and every worker brief to ask for. + +**That line exists because the set has to be stated rather than inferred**, distinct from any +issue a body merely mentions. A body routinely references an issue it +does not fix, the deferral issues its own review round filed most of all, and those have to stay +open as the next round's ranking input. Sweeping in everything a body mentions would close them at +the promotion merge and delete the next round's backlog, so the promotion body reads the explicit +line and never the mentions. An issue named nowhere is one nothing closes, which is a missed +closure a later round notices, where the opposite error destroys work. + +Deriving the set from the range rather than from what this round dispatched is what covers a group +that deferred or parked, contributing none, and an earlier round's work that no promotion has yet +carried. A fix landing during the freeze adds its issue to a body already written, so amend the +body when it lands rather than leaving the issue to be closed by hand. + +## Run State + +- **Working notes outside the repository hold the round**: the ranking, the working groups, the + tier choices, and the worker assignments. A scratch file the harness gives a session serves + where there is one, and any note kept out of the tree serves where there is not. It is working + state, and nothing about it is committed. +- **GitHub holds what outlives the session.** A claim comment records a group's file set, a pull + request body records what a round carried, a `Fixes #N` line records what the promotion closes, a + deferral issue records what was put off and why, a thread reply records how a finding was + disposed of, and a stopped group's question is a comment on its issue. +- **No tracker file is committed for the run.** A committed tracker is a file every round rewrites, + which is the contention the file-claim rule exists to prevent. + +## Ending the Run + +The run ends at either of two points, and they are different endings. + +- **The backlog is worked out**, meaning a full re-rank finds no open issue this skill can act on. + That is not the same as zero open issues, since a backlog of nothing but maintainer questions is + a finished run. Report it as finished, with the questions put to the maintainer. +- **The session ends**, for a context limit or because the maintainer stops it. The run ends with + it, since the merge authorization was bounded to that session. What the rounds already landed + stands on its own in GitHub, and the branches, claim comments, and questions left behind are + what a later run reads to pick the work up. That later run is a new run, named again, not this + one continuing. + +Report at every round boundary and at either ending: what merged to develop, what the promotion +pull request carries, what was newly filed, what is stopped and on which question, and what the +next round would pick. + +## Mechanics Live Elsewhere + +- The review loop, the Merge Gate, the five finding outcomes, and when a finding is a question: + `pr-review-conduct`. +- Driving one pull request, and the promotion-pull-request wrinkle: `drive-pr`. +- Worktree isolation, the base branch, and the cleanup procedure this skill re-seats: + `repo-worktree`. +- Closing keywords, branch protection, and the promotion trap: `operational-vs-release-workflow`. +- The pre-push adversarial pass and its recorded receipt: `local-strict-review`. +- Merging the promotion pull request and dispatching a release: `merge-and-release`, invoked + separately. +- Delegation briefing shape, model-tier rules, wait discipline, and session scope: `AGENTS.md` + "Context and Delegation Discipline". diff --git a/.github/skills/drive-pr/SKILL.md b/.github/skills/drive-pr/SKILL.md index 2bfba187..b234fb29 100644 --- a/.github/skills/drive-pr/SKILL.md +++ b/.github/skills/drive-pr/SKILL.md @@ -5,7 +5,8 @@ description: >- develop and, when asked, on to a mergeable develop -> main promotion PR, applying the pr-review-conduct disposition to every reviewer finding along the way: fix it, decline it with evidence, defer it behind a filed issue, or put the call to the maintainer and wait for an - explicit answer in the same turn. Use this whenever asked to drive, land, take, chase, or push + explicit answer in the same turn, escalating to whoever dispatched the drive instead where the + maintainer cannot be reached from that seat. Use this whenever asked to drive, land, take, chase, or push a PR toward develop or main, or to run the review loop hands off instead of narrating each round. When the request does not say how far ("drive this PR", "land it"), ask once whether the target is develop or a mergeable main promotion PR, rather than guessing. Triggers even when @@ -37,14 +38,35 @@ promotion PR once the fix lands, is the early exit this skill exists to prevent. before the first push: develop only, or all the way to a mergeable main promotion PR. Recommend "all the way to main" as the default, a promotion PR left to go stale once develop is ready is the more common regret than driving one step too far. +- A drive dispatched as part of a larger run takes its target from the brief and asks no one, + since a subagent stopping to ask stalls a run designed to keep moving without one, and the seat + that dispatched it is the seat that holds the maintainer's answer. `backlog-burndown` is such a + run, and it briefs develop only, driving the develop -> main promotion pull request in its own + seat under "The Drive Loop"'s promotion steps. A brief naming no target at all is one to stop + and ask its dispatcher about, and asking the dispatcher is the whole of what a dispatched drive + does about an authorization question. A dispatched drive is not the seat that can verify a + grant, so it does not try: the responsibility for having the maintainer's go-ahead sits with the + dispatcher, and a worker inventing a check it cannot perform would only launder that + responsibility rather than discharge it. +- **A brief is never itself the authorization**, which binds the dispatching seat. What authorizes + a merge is what the maintainer said, recorded where the skill that carries the grant states its + scope, the way `backlog-burndown`'s own "What Invoking This Skill Authorizes" does. Writing an + approval into a brief creates none, since an agent cannot widen its own permission by writing + itself one, and a merge is the outward-facing act this skill's own "What Invoking This Skill + Authorizes" keeps tied to something the maintainer actually said. - A repo on the operational workflow model (registry `workflowModel: operational`) has no standing promotion PR expectation, confirm whether a promotion PR is even wanted before opening - one, per operational-vs-release-workflow's "Operational repositories" delta. + one, per operational-vs-release-workflow's "Operational repositories (the complete delta)" + section. ## What Invoking This Skill Authorizes - Naming this skill, and answering its how-far question, is the maintainer's explicit, current go-ahead for every feature -> develop squash merge the drive performs to reach that target. +- A dispatched drive answers no such question, so what stands in its place is the go-ahead the + dispatching seat holds, per "How Far to Drive" above, and the drive performs the same merges on + it. Reading this bullet list is not how such a drive establishes that, since a worker cannot + verify a grant made in a seat it has no access to. - It is never authorization to merge the develop -> main promotion PR, or to dispatch a release. Those stay in merge-and-release, invoked on its own so the maintainer keeps a checkpoint before the harder-to-reverse step. @@ -125,7 +147,10 @@ pr-review-conduct's five outcomes are the actual rule, this is the mapping to us - Real, fixable, but a value call rather than a scope boundary, or the agent genuinely does not know which of the above applies, so ask the maintainer directly, whatever the runtime's own interactive-question mechanism is, and get an explicit answer in the same turn, a plan to ask - later is resolution by silence (outcome 3). + later is resolution by silence (outcome 3). A drive that cannot reach the + maintainer directly, a dispatched one being the ordinary case, escalates to whoever dispatched + it and stops that unit of work there instead, per `pr-review-conduct`, which owns what the + receiving seat then does and how far the escalation travels. - The same finding keeps recurring against correct code, fix the class, sharpen a name, add a comment, or take the rule itself to the maintainer, rather than re-arguing the instance every round (outcome 5). diff --git a/.github/skills/local-strict-review/SKILL.md b/.github/skills/local-strict-review/SKILL.md index cecb485e..336effcc 100644 --- a/.github/skills/local-strict-review/SKILL.md +++ b/.github/skills/local-strict-review/SKILL.md @@ -57,6 +57,8 @@ Bounds: read-only. No edit, no stage, no commit, no push, no PR-hosted write of **Model tier:** the strongest tier this session can reach, per `AGENTS.md` "Match the model tier to the judgment" and "Never tier down the seat holding the judgment", applied here to the reviewer rather than the author. Run the pass on the same tier that authored the change when only one tier is reachable, a second, adversarially-prompted look still catches what the authoring pass's own "looks ready" judgment did not. +"This session can reach" means the tier this session can name when it dispatches the reviewer, rather than the tier this session is itself running on. A session deliberately tiered down for execution work, a worker dispatched by an orchestrator being the ordinary case, names a stronger tier for the reviewer where its harness lets it, since tiering down the author is the reason the reviewer must not follow it down. What a given harness and account actually permit varies, so treat this as the tier to ask for rather than one to assume. Where a dispatch reaches several tiers but exposes no way to name one, take what it gives and run the pass, on the same reasoning as the single-reachable-tier sentence above. A seat that cannot dispatch a subagent at all cannot perform this pass. Instead of pushing, it reports that it could not run the pass, to whoever dispatched it, or to the maintainer where nobody did. Either way it is a push that does not happen rather than a pass quietly skipped. The headless `run --backend` route under "Recording the Pass" is not the substitute: it runs a vendor CLI against its own review, which never carries the brief above, so it satisfies the rule this section states only where that separate route is what a capture point asked for. + ## Recording the Pass `scripts/local_review.py` is what makes this rule checkable rather than something each session has to remember. For the pass above, the engine only records that it happened, keyed on the content the reviewer actually saw, and its `run --backend ` subcommand is the separate case where a headless backend performs the review and records its own count. That receipt is what a capture point reads, the hub's own `.husky/pre-push` hook being the only one today, and a repository having none unless it adds one, since no manifest entry carries it. @@ -123,7 +125,7 @@ The digest is bound to the read for the same reason `--expect-digest` is above: ## Disposing of Findings -Every finding maps to one of `pr-review-conduct`'s five outcomes, at whichever moment this pass ran: fixed (1), evidence-disproven (2), escalated to the maintainer for an explicit call (3), filed as a deferred issue (4), or, if it keeps recurring, taken as a signal to fix the class (5). Outcome 2 is the agent's own on its own evidence, covering a finding that is not real and one that is structurally out of scope. A finding judged real and left unfixed is never the agent's alone, so outcome 3 needs the maintainer's explicit answer in the same turn, reached only once outcome 2 is ruled out, and outcomes 4 and 5 reach the maintainer too, for the deferral and for the rule itself. Running this pass is required before every push toward a pull request, per `agent-conduct`. Two claims sit next to each other here and they point opposite ways, so they are stated apart rather than in one sentence. **The pass is mandatory**, and where a capture point enforces it, a push carrying content no recorded pass covers is refused. That refusal is the gate working rather than a fault to route around. **The findings stay advisory**, and the count a pass raises gates nothing at all, since a pass records that a review ran and never that the content is clean. The disposition above is what closes each finding, the same posture local lint holds today. It posts nothing to GitHub, it only reports to the session driving the work. A finding raised here and not fixed is not thereby resolved: the same finding shape reaching a PR-hosted reviewer later still gets its own fresh disposition, per `pr-review-conduct`'s "a disposition decided on one PR does not carry to the next." +Every finding maps to one of `pr-review-conduct`'s five outcomes, at whichever moment this pass ran: fixed (1), evidence-disproven (2), escalated to the maintainer for an explicit call (3), filed as a deferred issue (4), or, if it keeps recurring, taken as a signal to fix the class (5). Outcome 2 is the agent's own on its own evidence, covering a finding that is not real and one that is structurally out of scope. A finding judged real and left unfixed is never the agent's alone, so outcome 3 needs the maintainer's explicit answer in the same turn, reached only once outcome 2 is ruled out, or, where this pass ran in a seat that cannot reach the maintainer, an escalation to whoever dispatched it that stops the work there, which stops the push this pass runs before, and outcomes 4 and 5 reach the maintainer too, for the deferral and for the rule itself. Running this pass is required before every push toward a pull request, per `agent-conduct`. Two claims sit next to each other here and they point opposite ways, so they are stated apart rather than in one sentence. **The pass is mandatory**, and where a capture point enforces it, a push carrying content no recorded pass covers is refused. That refusal is the gate working rather than a fault to route around. **The findings stay advisory**, and the count a pass raises gates nothing at all, since a pass records that a review ran and never that the content is clean. The disposition above is what closes each finding, the same posture local lint holds today. It posts nothing to GitHub, it only reports to the session driving the work. A finding raised here and not fixed is not thereby resolved: the same finding shape reaching a PR-hosted reviewer later still gets its own fresh disposition, per `pr-review-conduct`'s "a disposition decided on one PR does not carry to the next." ## When to Run It diff --git a/.github/skills/pr-review-conduct/SKILL.md b/.github/skills/pr-review-conduct/SKILL.md index 836fdb32..1073b824 100644 --- a/.github/skills/pr-review-conduct/SKILL.md +++ b/.github/skills/pr-review-conduct/SKILL.md @@ -172,6 +172,16 @@ from a hub checkout. Do not use a provider connector or reconstruct the GitHub m - A finding is judged real but should not be fixed. That decision is never the agent's alone. - An architectural redesign is proposed rather than a bug fix. +An agent that cannot reach the maintainer directly, a dispatched subagent being the ordinary case, +escalates to whoever dispatched it and stops that unit of work there. It never substitutes its own +judgment for the escalation because asking is inconvenient from where it sits, and it never resolves +the thread to keep moving. A dispatcher receiving one puts it to the maintainer at the point that +work stopped, per `GOVERNANCE.md` "Communicating with the User", and deciding it instead so the +dispatcher's own work keeps moving is the same resolution by silence this skill's own +ask-the-maintainer outcome forbids, one seat further from the maintainer. The escalation may travel through several seats, and what stays stopped is the +escalated unit of work, in whichever seat holds it, until the answer arrives. A dispatcher's other +work is not stopped by it. + ## Mechanics Live Elsewhere This skill is the provider-agnostic contract. Use `scripts/pr_review.py` from a hub checkout for diff --git a/AGENTS.md b/AGENTS.md index 18191e54..addacaa0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -36,6 +36,7 @@ An agent session is billed on the context it carries, not the work it does. Ever - **One deliverable, one session.** A session covers one branch and one deliverable, and ends when that work merges. A multi-step task is one deliverable and stays in one session. Two unrelated tasks are two sessions even when they run back to back. - **End a session at any of these, without being asked:** the branch changes, the pull request merges, or the next task is unrelated to the last. A review round is none of them. A loop still producing findings is the deliverable in progress, and a round count is not a reason to leave one open. +- **A session orchestrating dispatched work is an exception, and a narrow one.** Its deliverable is the run rather than any branch, so it spans many branches and many merges by construction, and ending it at the first dispatched merge would end the run. The triggers above land on each dispatched task instead, one branch and one deliverable each, which is this rule applied rather than waived. What keeps the exception narrow is that such a session holds no branch of its own and authors none of the work it dispatches, so the file context every other session accumulates is context it never takes on, and it re-derives each round's state from live sources rather than holding it, per "Re-derive state, do not carry it" below. A session that starts editing the files a dispatched task would have edited is an ordinary one again and ends on the triggers above. - **Hand off in a file, never in context.** Close a session by writing at most 2 KB to a scratch file: branch, pull request link, what is done, the next command. A summary held in context is re-billed until the session ends, and a summary on disk is read once by whoever needs it. - **Re-derive state, do not carry it.** "This session already has the context" is the signal to split, not to continue. Context that has gone stale is worse than absent, because a file read hundreds of requests ago no longer describes the file. - **Compaction is a fallback, not the strategy.** It restarts context from a floor and climbs again, where a fresh session starts from zero. @@ -109,6 +110,6 @@ Some of the rules above are also packaged as Claude Code / opencode / Codex Skil Adding or changing a managed host tool is packaged as `add-host-tool`. It keeps the cross-platform contract, installer, documentation, test, and native-verification surfaces together. -Driving a pull request through its review loop, from a feature branch into `develop` and, when asked, on to a mergeable `develop -> main` promotion PR, disposing of every reviewer finding along the way per `pr-review-conduct`, is packaged as `drive-pr`, new content rather than a rule extracted from a section. Merging a ready promotion PR and dispatching the release it unblocks, refreshing this machine's installed Skills first when the repo is this hub, is `merge-and-release`, its own new-content package, invoked separately from `drive-pr` so the promotion merge and the release dispatch each keep their own explicit go-ahead. +Driving a pull request through its review loop, from a feature branch into `develop` and, when asked, on to a mergeable `develop -> main` promotion PR, disposing of every reviewer finding along the way per `pr-review-conduct`, is packaged as `drive-pr`, new content rather than a rule extracted from a section. Merging a ready promotion PR and dispatching the release it unblocks, refreshing this machine's installed Skills first when the repo is this hub, is `merge-and-release`, its own new-content package, invoked separately from `drive-pr` so the promotion merge and the release dispatch each keep their own explicit go-ahead. Working a whole open-issue backlog down by rounds, ranking the issues, grouping them so no two groups touch the same file, dispatching one subagent per group to drive its own pull request into `develop`, opening at most one `develop -> main` promotion pull request per round, and re-ranking from scratch afterwards because each round's reviews file new issues, is `backlog-burndown`, also new content rather than a rule extracted from a section. It orchestrates `drive-pr` rather than replacing it, and it scopes to the repository the session is in, and a fleet-wide issue sweep is a different request. Running one read-only, adversarial review pass against a branch's current diff against its target branch, full file context included, on the strongest model tier the session can reach, before a unit of PR-bound work is pushed toward a pull request or claimed done, is packaged as `local-strict-review`, new content rather than a rule extracted from a section. `drive-pr`, `pr-review-conduct`, and `agent-conduct` each reference it at the moment they already govern, rather than restating what it does. The rule itself lives in [`GOVERNANCE.md`](./GOVERNANCE.md) "Verification Discipline", the hub-hosted `scripts/local_review.py` is the engine that records a pass so a capture point can check one, and a repository carrying a `.husky/pre-push` hook enforces it at the push itself, the skill staying the primary and agent-agnostic layer with the hook a bypassable backstop under it. That skill carries a second pass under the same rule, over canonical content this repository authors and others carry, read one whole unit at a time rather than as a diff, because a diff-scoped read leaves the first real review of a rule to whichever repository carries it next, which is the one repository that cannot act on what it finds. `scripts/canonical_review.py` is that pass's engine, and the backlog it has yet to reach is `reports/canonical-review.md` in the hub, not a repo-relative link here since that path is hub-local like the Skills tree above. diff --git a/reports/canonical-review.json b/reports/canonical-review.json index 2333021f..5de0fc7c 100644 --- a/reports/canonical-review.json +++ b/reports/canonical-review.json @@ -17,6 +17,134 @@ "hubCommit": "52b0c550ab07ab58b940a353a83247d7451e76e5", "stamp": "2026-09-02T01:11:56Z" }, + { + "unit": ".agents/skills/backlog-burndown/SKILL.md > (preamble)", + "digest": "sha256:75ac6fdd150652041b8cf1c002d40d7e64903e394983f6ae0c62bec07e200d42", + "reviewer": "agent-skill", + "findings": 0, + "hubCommit": "136661da1b978f793fd5bbd7daa1d95b7338a919", + "stamp": "2026-09-03T19:44:45Z" + }, + { + "unit": ".agents/skills/backlog-burndown/SKILL.md > Bounding a Prose Group", + "digest": "sha256:b94ebeced663da22a9a5b0b32f494d6dbe2045bb8d54ecab5484eb049e9a52d7", + "reviewer": "agent-skill", + "findings": 0, + "hubCommit": "136661da1b978f793fd5bbd7daa1d95b7338a919", + "stamp": "2026-09-03T19:44:46Z" + }, + { + "unit": ".agents/skills/backlog-burndown/SKILL.md > Bounding the Wait on a Worker", + "digest": "sha256:6af6a85a17f9671f6ba50f622a34769e7c8c6fce2100496cd2f9330523755daa", + "reviewer": "agent-skill", + "findings": 5, + "hubCommit": "16f2e32a037857dfcd68b114f006ca2d0cf12ada", + "stamp": "2026-09-04T00:04:25Z" + }, + { + "unit": ".agents/skills/backlog-burndown/SKILL.md > Dispatching a Worker", + "digest": "sha256:bd825f92555e4d0a762afe4a2de5ac6a9873b14eed1221a4c2f87d9a70f71489", + "reviewer": "agent-skill", + "findings": 3, + "hubCommit": "16f2e32a037857dfcd68b114f006ca2d0cf12ada", + "stamp": "2026-09-04T02:41:40Z" + }, + { + "unit": ".agents/skills/backlog-burndown/SKILL.md > Ending the Run", + "digest": "sha256:94b4e3978520d4c14d99e345a8ee526764c6f7e5ea210f0aefd0bd134a8c57f7", + "reviewer": "agent-skill", + "findings": 0, + "hubCommit": "136661da1b978f793fd5bbd7daa1d95b7338a919", + "stamp": "2026-09-03T19:44:48Z" + }, + { + "unit": ".agents/skills/backlog-burndown/SKILL.md > Grouping and File Claims", + "digest": "sha256:c6c315f4141dd986741e51c290867a8721f3313087e9ff62554681d4b56ca2b1", + "reviewer": "agent-skill", + "findings": 4, + "hubCommit": "16f2e32a037857dfcd68b114f006ca2d0cf12ada", + "stamp": "2026-09-04T02:41:40Z" + }, + { + "unit": ".agents/skills/backlog-burndown/SKILL.md > Mechanics Live Elsewhere", + "digest": "sha256:1bdd392da31f3429179c4046103c2a8729ca1808f05b1117e7cbd16a2f3d9eab", + "reviewer": "agent-skill", + "findings": 0, + "hubCommit": "136661da1b978f793fd5bbd7daa1d95b7338a919", + "stamp": "2026-09-03T19:44:49Z" + }, + { + "unit": ".agents/skills/backlog-burndown/SKILL.md > Raising a Blocked Question", + "digest": "sha256:86282c469754ae2c1b5ba5e6694f7d54325e19b2978d5fbef3b50a95a6831c74", + "reviewer": "agent-skill", + "findings": 7, + "hubCommit": "16f2e32a037857dfcd68b114f006ca2d0cf12ada", + "stamp": "2026-09-03T23:11:32Z" + }, + { + "unit": ".agents/skills/backlog-burndown/SKILL.md > Ranking", + "digest": "sha256:e58cd0555581811e82a9796072cefc5c3ef14f265f5e4791104a71935cd1de15", + "reviewer": "agent-skill", + "findings": 3, + "hubCommit": "16f2e32a037857dfcd68b114f006ca2d0cf12ada", + "stamp": "2026-09-04T02:06:41Z" + }, + { + "unit": ".agents/skills/backlog-burndown/SKILL.md > Run State", + "digest": "sha256:6b0b57e0f06237d6f134af15a356aa3ec21bb54c5f2e6bd8460087f392ed20c8", + "reviewer": "agent-skill", + "findings": 0, + "hubCommit": "136661da1b978f793fd5bbd7daa1d95b7338a919", + "stamp": "2026-09-03T19:44:50Z" + }, + { + "unit": ".agents/skills/backlog-burndown/SKILL.md > Scope", + "digest": "sha256:1db5bb5cdd672089c306c59c2776b672ba09541b093fe9e05f4e713c4bc8b530", + "reviewer": "agent-skill", + "findings": 0, + "hubCommit": "136661da1b978f793fd5bbd7daa1d95b7338a919", + "stamp": "2026-09-03T19:44:50Z" + }, + { + "unit": ".agents/skills/backlog-burndown/SKILL.md > The Promotion Boundary", + "digest": "sha256:4b802ff18e55a38f6027b443bebe63c0721e84bf0234b7262aaad726dc005f77", + "reviewer": "agent-skill", + "findings": 8, + "hubCommit": "16f2e32a037857dfcd68b114f006ca2d0cf12ada", + "stamp": "2026-09-04T00:33:58Z" + }, + { + "unit": ".agents/skills/backlog-burndown/SKILL.md > The Round", + "digest": "sha256:4261479196b88e112bcbd14f9f745be62b09a2b6b56106b2f20ab6111ba6eca4", + "reviewer": "agent-skill", + "findings": 2, + "hubCommit": "16f2e32a037857dfcd68b114f006ca2d0cf12ada", + "stamp": "2026-09-04T02:06:41Z" + }, + { + "unit": ".agents/skills/backlog-burndown/SKILL.md > The Two Seats", + "digest": "sha256:988e9cc3aee1ab6aa7c9ada41827a4903f7ae6d06f8051178456888a39c9cb82", + "reviewer": "agent-skill", + "findings": 0, + "hubCommit": "136661da1b978f793fd5bbd7daa1d95b7338a919", + "stamp": "2026-09-03T19:44:51Z" + }, + { + "unit": ".agents/skills/backlog-burndown/SKILL.md > What Invoking This Skill Authorizes", + "digest": "sha256:00d0aff29a7a73bf5575aa0753aac923fdc1364c17968d8e87c96f3658e3ef77", + "reviewer": "agent-skill", + "findings": 0, + "hubCommit": "136661da1b978f793fd5bbd7daa1d95b7338a919", + "stamp": "2026-09-03T19:44:52Z" + }, + { + "unit": ".agents/skills/backlog-burndown/SKILL.md > Why This Exists", + "digest": "sha256:def8e540d75e1b5e40804f0eb708ff868bf834a77440a45ace0eb93b3be49b2c", + "reviewer": "agent-skill", + "findings": 0, + "hubCommit": "136661da1b978f793fd5bbd7daa1d95b7338a919", + "stamp": "2026-09-03T19:44:52Z" + }, { "unit": ".agents/skills/comment-and-doc-style/SKILL.md > Docker lint authorization", "digest": "sha256:b61bea3f7010cafc475ce582e234e79f1e55e4bd0e5332dbe2b05f4fb5bb03ff", @@ -49,13 +177,29 @@ "hubCommit": "68be56e977269486eeeca14bdc4982a0adbdb6b3", "stamp": "2026-09-02T13:41:40Z" }, + { + "unit": ".agents/skills/drive-pr/SKILL.md > (preamble)", + "digest": "sha256:5c6af4cd25e9aeaafbfe460691d87bbddf80b4c164f29791fe42df931749e5bb", + "reviewer": "agent-skill", + "findings": 4, + "hubCommit": "16f2e32a037857dfcd68b114f006ca2d0cf12ada", + "stamp": "2026-09-04T00:58:37Z" + }, { "unit": ".agents/skills/drive-pr/SKILL.md > Disposing of Every Finding", - "digest": "sha256:97b70e52bc5aebff405200e8f102b3b551d1bc28d93cbbe8aa6667207f95765b", + "digest": "sha256:ae3a4a85828fbe73e68906cecfab20bf0c801c22482126c3e16f530aa8b71737", "reviewer": "agent-skill", - "findings": 3, - "hubCommit": "0e6aacf45334149f6158aa23b8132d6aee1d1eb0", - "stamp": "2026-09-01T16:05:59Z" + "findings": 4, + "hubCommit": "16f2e32a037857dfcd68b114f006ca2d0cf12ada", + "stamp": "2026-09-04T00:58:38Z" + }, + { + "unit": ".agents/skills/drive-pr/SKILL.md > How Far to Drive", + "digest": "sha256:56b2c85c5b41b3558459646410782718a7167577024a6983c06f0d6133bf4da8", + "reviewer": "agent-skill", + "findings": 7, + "hubCommit": "16f2e32a037857dfcd68b114f006ca2d0cf12ada", + "stamp": "2026-09-03T23:11:35Z" }, { "unit": ".agents/skills/drive-pr/SKILL.md > The Drive Loop", @@ -65,6 +209,14 @@ "hubCommit": "dded739ff0ae8614b52b187a95b00460c0fad8ce", "stamp": "2026-09-01T17:57:39Z" }, + { + "unit": ".agents/skills/drive-pr/SKILL.md > What Invoking This Skill Authorizes", + "digest": "sha256:5f9593e43214ba87a2bba04c47f870d507e313f1a5dc8383cb3ce863c836fa4e", + "reviewer": "agent-skill", + "findings": 2, + "hubCommit": "16f2e32a037857dfcd68b114f006ca2d0cf12ada", + "stamp": "2026-09-04T00:04:25Z" + }, { "unit": ".agents/skills/local-strict-review/SKILL.md > (preamble)", "digest": "sha256:e607d4a5c7ad14232516261ab7f0941de11a4312415a65dc054b394a645a106d", @@ -75,11 +227,11 @@ }, { "unit": ".agents/skills/local-strict-review/SKILL.md > Disposing of Findings", - "digest": "sha256:8a02f13222ded9278a0611d305f263ad8c67e1fded86100cc47d3b23a4fc86a8", + "digest": "sha256:568da8c763e51795bff9e294a07976fd1479b93b48d383e6d5b60fe23901d6b3", "reviewer": "agent-skill", - "findings": 2, - "hubCommit": "0e6aacf45334149f6158aa23b8132d6aee1d1eb0", - "stamp": "2026-09-01T16:37:57Z" + "findings": 5, + "hubCommit": "16f2e32a037857dfcd68b114f006ca2d0cf12ada", + "stamp": "2026-09-04T00:58:38Z" }, { "unit": ".agents/skills/local-strict-review/SKILL.md > Mechanics Live Elsewhere", @@ -97,6 +249,14 @@ "hubCommit": "a5ab8f6515de9fea49a961e88afefcbf46377a79", "stamp": "2026-09-01T16:45:44Z" }, + { + "unit": ".agents/skills/local-strict-review/SKILL.md > Running It", + "digest": "sha256:612b1fed21acfd0fd5d1adf78109b4abcb4fe497218b30fbd99e3263f7b197d3", + "reviewer": "agent-skill", + "findings": 2, + "hubCommit": "16f2e32a037857dfcd68b114f006ca2d0cf12ada", + "stamp": "2026-09-04T02:11:36Z" + }, { "unit": ".agents/skills/local-strict-review/SKILL.md > The Carried-Content Pass", "digest": "sha256:1bd1396389709aa388ba5b984cd11cc1335bb7b13da107c32bcc336feb816154", @@ -169,6 +329,14 @@ "hubCommit": "0e6aacf45334149f6158aa23b8132d6aee1d1eb0", "stamp": "2026-09-01T16:37:57Z" }, + { + "unit": ".agents/skills/pr-review-conduct/SKILL.md > Escalate to the maintainer when", + "digest": "sha256:7d335c2fa172dc2b3f966eb5540ad357d23feca4ba6156b83610cc208da595f2", + "reviewer": "agent-skill", + "findings": 7, + "hubCommit": "16f2e32a037857dfcd68b114f006ca2d0cf12ada", + "stamp": "2026-09-03T23:11:36Z" + }, { "unit": ".agents/skills/pr-review-conduct/SKILL.md > Every finding ends in one of five outcomes", "digest": "sha256:f7292e072c812adc3d9f3d7302181978eaf5c313c6c60efd6c7deece6578b1da", @@ -385,13 +553,21 @@ "hubCommit": "f0ff674ec0d59bb2c9ec3736ef22d9de205059c8", "stamp": "2026-09-02T03:21:55Z" }, + { + "unit": "AGENTS.md > Context and Delegation Discipline", + "digest": "sha256:fc4911dfd1b597565eff5b1783493295f5f95197c8452a1410c1499deec21e53", + "reviewer": "agent-skill", + "findings": 3, + "hubCommit": "16f2e32a037857dfcd68b114f006ca2d0cf12ada", + "stamp": "2026-09-04T00:58:39Z" + }, { "unit": "AGENTS.md > Where the Rules Live", - "digest": "sha256:2526a341afe0c6f1e2d2e1fe954855398742474a816e2bcba640672d1363766f", + "digest": "sha256:ee899615c15fae90078977de131abbbbc4f461ec6a751db26e5a63eaf8aa1961", "reviewer": "agent-skill", - "findings": 14, - "hubCommit": "df5e2493b48aa45d80f586db68af0c21be97ea21", - "stamp": "2026-09-01T05:58:03Z" + "findings": 0, + "hubCommit": "136661da1b978f793fd5bbd7daa1d95b7338a919", + "stamp": "2026-09-03T19:44:54Z" }, { "unit": "AUDIT.md > 10. Converge: Apply the Fixes", diff --git a/reports/canonical-review.md b/reports/canonical-review.md index 783d2ddc..f0f9bb5e 100644 --- a/reports/canonical-review.md +++ b/reports/canonical-review.md @@ -6,10 +6,10 @@ A unit is what a reviewer reads whole, decided by the carry manifest rather than ## Coverage -- units: 288 -- covered: 65 +- units: 304 +- covered: 87 - stale: 0 -- never read here: 223 +- never read here: 217 ## Burn-down @@ -131,11 +131,8 @@ A unit is what a reviewer reads whole, decided by the carry manifest rather than ### .agents/skills/drive-pr/SKILL.md -- **(preamble)** - never -- **How Far to Drive** - never - **Mechanics Live Elsewhere** - never - **Stop and Ask, Beyond the How-Far Question** - never -- **What Invoking This Skill Authorizes** - never - **Why This Exists** - never ### .agents/skills/fleet-conformance-check/SKILL.md @@ -165,7 +162,6 @@ A unit is what a reviewer reads whole, decided by the carry manifest rather than ### .agents/skills/local-strict-review/SKILL.md -- **Running It** - never - **Why This Exists** - never ### .agents/skills/merge-and-release/SKILL.md @@ -205,7 +201,6 @@ A unit is what a reviewer reads whole, decided by the carry manifest rather than ### .agents/skills/pr-review-conduct/SKILL.md - **(preamble)** - never -- **Escalate to the maintainer when** - never - **Merge Gate, check this before merging or enabling auto-merge** - never - **Triaging findings** - never - **Why this exists** - never @@ -325,7 +320,6 @@ A unit is what a reviewer reads whole, decided by the carry manifest rather than ### AGENTS.md -- **Context and Delegation Discipline** - never - **Fleet Bootstrap** - never ### AUDIT.md