Skip to content
Merged
2 changes: 1 addition & 1 deletion scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ Every failure is a stop rather than a fallback, because each alternative closes

What this trades away is stated rather than glossed. A mutation spelled as a `gh` command in a shell is read by the `gh-write-guard` hook and one this script performs is not. The script removes that failure at its source instead: every node ID comes from a live query in the same run. The owner check is also enforced in-process. The whole-source test guards against every other state-changing call and counts each reviewed mutation document.

`cleanup` deletes the head branch of a merged pull request, the verify-then-delete `drive-pr` and `merge-and-release` prescribe after a merge into `develop`. It is the one place holding the delete's checks, and no skill prescribes a hand-typed delete beside it. A delete typed by hand cannot be relied on to run from an agent seat in auto mode. The `gh-write-guard` hook denies `git push origin --delete` from a primary checkout, and Claude Code's auto-mode classifier refuses the ref API delete as a destructive git operation on some days and allows it on others. A command matching a narrow `permissions.allow` rule is settled before that classifier runs, and the host-safety installer already registers `Bash(python3 scripts/pr_review.py:*)`, so the delete runs here under that rule with no settings change. That rule approves any arguments, so every check lives in the subcommand. It takes the pull request number and `--repo`, never a branch name, and reads the branch from the pull request. It refuses unless the pull request is merged, its base is `develop`, its head repository is `--repo` itself, and the head branch is neither `develop`, `main`, nor the default branch read live. Past the same owner check `comment` and `reply` make, `handoff.py`'s own write-scope check, imported rather than copied, binds the owner to `registry/repos.json` and admits a repository only where it is registered or a fork. A branch any ruleset or branch protection covers is refused, since the hook's per-branch rule check never sees a delete made from inside this script. A branch that another open pull request in `--repo` has as its head or base is refused too, since the delete would close or retarget it. Every open pull request in `--repo` with the branch as its head is named, read page by page. A page that does not answer stops at exit `2`. Up to twenty with the branch as base are named, and any past that are counted. The ref is read by its exact qualified name, and the delete is sent only where that read's tip is the pull request's `headRefOid`, by the node id the same read returned. The delete carries no expected tip of its own, so this is verify-then-delete rather than a compare-and-swap. The delete is confirmed by reading the ref back rather than by the mutation's own answer, so a delete that reports a failure and landed still reads as done, and one that reports success and left the ref reads as unconfirmed. Exit `0` is the branch deleted and confirmed gone, or already gone. Exit `1` is a refusal, the registry check's among them, and so is a tip that moved after the merge, which names both commits. Exit `2` is a read that did not answer, the rule read included, which never reads as no rules, or a delete the read back could not confirm. A usage error also exits `2`, argparse's own code. Exit `64` is the owner check `comment` and `reply` make against this checkout's `origin`, which also exits `64` where that `origin` cannot be read, run before the registry and before any read. No `--dry-run` exists, since no other subcommand here has one.
`cleanup` deletes the head branch of a merged pull request, the verify-then-delete `drive-pr` and `merge-and-release` prescribe after a merge into `develop`. It is the one place holding the delete's checks, and no skill prescribes a hand-typed delete beside it. A delete typed by hand cannot be relied on to run from an agent seat in auto mode. The `gh-write-guard` hook denies `git push origin --delete` from a primary checkout, and Claude Code's auto-mode classifier refuses the ref API delete as a destructive git operation on some days and allows it on others. A command matching a narrow `permissions.allow` rule is settled before that classifier runs, and the host-safety installer already registers `Bash(python3 scripts/pr_review.py:*)`, so the delete runs here under that rule with no settings change. That rule approves any arguments, so every check lives in the subcommand. It takes the pull request number and `--repo`, never a branch name, and reads the branch from the pull request. It refuses unless the pull request is merged, its base is `develop`, its head repository is `--repo` itself, and the head branch is neither `develop`, `main`, nor the default branch read live. Past the same owner check `comment` and `reply` make, `handoff.py`'s own write-scope check, imported rather than copied, binds the owner to `registry/repos.json` and admits a repository only where it is registered or a fork. A branch any ruleset or branch protection covers is refused, since the hook's per-branch rule check never sees a delete made from inside this script. A branch that another open pull request in `--repo` has as its head or base is refused too, since the delete would close or retarget it. Every open pull request in `--repo` with the branch as its head is named, read page by page. A page that does not answer stops at exit `2`. Up to twenty with the branch as base are named, and any past that are counted. The ref is read by its exact qualified name, and the delete is sent only where that read's tip is the pull request's `headRefOid`, by the node id the same read returned. The delete carries no expected tip of its own, so this is verify-then-delete rather than a compare-and-swap. The delete is confirmed by reading the ref back rather than by the mutation's own answer, so a delete that reports a failure and landed still reads as done, and one that reports success and left the ref reads as unconfirmed. Exit `0` is the branch deleted and confirmed gone, or already gone. Exit `1` is a refusal, the registry check's among them, and so is a tip that moved after the merge, which names both commits. Exit `2` is a read that did not answer, the rule read included, which never reads as no rules, or a delete the read back could not confirm. A usage error also exits `2`, argparse's own code. Exit `64` is the owner check `comment` and `reply` make against this checkout's `origin`, which also exits `64` where that `origin` cannot be read, run before the registry and before any read. With `--checkout` set to a primary clone, a local half follows a remote half that exited `0`. The raw git steps it replaces are not on the permission allowlist, and this script is. Every refusal is decided before anything is deleted, the remote branch included. That includes the primary leaving the merged branch. It also includes a rebase or bisect paused on it elsewhere, and a tip that is neither the merged head nor behind it. A re-check after the delete can still refuse when a concurrent session changed the branch or a worktree in between. The output then names what already happened. A worktree entry whose directory is gone is removed. The local half removes the worktrees holding the branch without force. It deletes the local branch with `git branch -D` only at the merged head or an ancestor of it. A tip re-read must show it unchanged first. It switches the primary to `develop` only off the merged branch, then fast-forwards `develop`. Base outcomes are reported and never fail the run. `cleanup --sweep --checkout <clone>` takes no pull request. It reports what is provably finished and removes it only with `--apply`. The report fetches and prunes the remote-tracking refs of `origin` and writes nothing else. An item must be clean, idle for `--min-age-hours` (default 24), and not mid-operation. A branch is finished when a pull request from `--repo` merged it at its exact tip or past it. It is also finished when `origin/develop` or `origin/main` contains it. A detached worktree is finished when any `origin` ref contains its commit. For a merged `develop` pull request the sweep also deletes the remote branch, and the report runs the same checks and names the result. The sweep never touches the primary except to fast-forward a tracked-clean `develop` to `origin/develop`, and the report names that step. It never touches permanent branches, locked worktrees, or worktrees with submodules.

`claims` checks that a description does not contradict its own branch, and it exists for the same reason `sha-pin` now resolves: a reference that points at nothing is a silent failure caught by a reviewer or not at all. Three stale descriptions in one session generated six review findings between them, each a reviewer noticing that the body named a commit or a behavior the branch no longer carried. It reads the commits a body **claims** the branch carries and the `uses:` refs it quotes, confirms the head tree still carries each, and exits `70` where one does not. Prose claims stay **out of scope**, since judging those needs a similarity heuristic, which [`spec/section-model.md`][section-model] rejects for exactly the reason it would fail here.

Expand Down
Loading
Loading