Add a high-level, intent-named tool — working name tbd_ship — that performs the canonical trunk-based loop in one call: branch from base → write/commit change → open PR → wait for CI → auto-merge to main when green → clean up the branch. With conventions enforced inside the tool.
Why
The primitives already exist (file_write_branch, pr_create, pr_merge, branch_list, branch_delete, repo_status, workflow_run_status). What's missing is an intent verb for the act we actually do most: "ship this small, low-risk change to trunk, safely." Today an agent must orchestrate 4–5 mechanism-named calls and remember the conventions each time — so even a one-file change becomes a branch+PR ceremony left dangling for human merge, which is the opposite of trunk-based for a tiny safe change.
This is the same disease just diagnosed for the brain interface (hyperguild #43): mechanism-named primitives with no verb for the actual intent. Same fix, different repo — name the act, encode the convention, enforce the safety.
The load-bearing design choice: CI-green replaces human review
Trunk-based doesn't skip safety — it moves it from a human reading a PR to a machine running checks. So the entire value of tbd_ship depends on the CI gate being real and fail-closed:
Merge only when the head SHA's workflow runs are all completed + success (workflow_run_list / workflow_run_status against the PR head SHA).
Checks pending/queued/in_progress → do not merge; return PR URL + run status, let caller poll or re-invoke. Never merge on incomplete.
Checks red → do not merge; return the failing run. Never merge.
No checks configured / no runs for the SHA → fail closed to PR-only. Do NOT silently merge. "No CI" must never collapse into "commit straight to main with no safety" — that's precisely the confidently-wrong-agent failure mode the platform exists to prevent (cf. the UFW lockdown and ip rule flush incidents). The trunk shortcut is only safe because CI is the wall; if there's no wall, fall back to ceremony.
Respect branch protection: probe branch_protection_get at call time; if protection requires reviews/approvals that auto-merge can't satisfy, fail closed to PR-only with a clear message. Don't assume a fixed config across repos.
Proposed surface
tool: tbd_ship
params:
owner, name : repo
path, content : the change (single-file to start; multi-file is a follow-up)
message : commit message
base : default "main"
branch : optional; default derive short-lived name e.g. tbd/<slug>-<shortsha>
pr_title, pr_body : PR metadata
merge_strategy : default "squash" (tiny PRs → linear history)
delete_branch : default true (short-lived branches are the norm)
ci_timeout_seconds : optional; if set, poll up to timeout then return pending state
returns:
{ merged: bool, pr_url, merge_sha?, ci_status, reason_if_not_merged }
changing any repo's actual branch-protection config (that's an infra/per-repo decision, not this tool's job)
rebase/conflict resolution (if base moved and merge isn't clean → fail closed, return for human)
Acceptance criteria
tbd_ship lands a single-file change to main via branch+PR+auto-merge when CI is green
Pending CI → returns PR + pending status, does NOT merge
Red CI → returns failing run, does NOT merge
No CI configured → falls back to PR-only, does NOT merge (explicit test)
Branch protection requiring review → PR-only fallback with clear message
Short-lived branch auto-named and deleted on successful merge
Squash default; linear history on main
Unmergeable (base moved, conflict) → fail closed, return for human
All four no-merge paths (pending/red/no-ci/protected) covered by tests
Related
hyperguild #43 — same intent-vs-mechanism interface problem, brain repo
existing primitives this composes: file_write_branch, pr_create, pr_merge, workflow_run_status, branch_protection_get, repo_status
Note on naming
tbd_ship is a working name. If the convention surface grows (e.g. a separate tbd_hotfix, or a tbd_pr for the no-auto-merge case), consider a small tbd_* family rather than overloading flags. Out of scope to decide now.
## What
Add a high-level, intent-named tool — working name `tbd_ship` — that performs the canonical trunk-based loop in one call: branch from base → write/commit change → open PR → **wait for CI → auto-merge to main when green** → clean up the branch. With conventions enforced inside the tool.
## Why
The primitives already exist (`file_write_branch`, `pr_create`, `pr_merge`, `branch_list`, `branch_delete`, `repo_status`, `workflow_run_status`). What's missing is an **intent verb** for the act we actually do most: "ship this small, low-risk change to trunk, safely." Today an agent must orchestrate 4–5 mechanism-named calls and remember the conventions each time — so even a one-file change becomes a branch+PR ceremony left dangling for human merge, which is the *opposite* of trunk-based for a tiny safe change.
This is the same disease just diagnosed for the brain interface (hyperguild #43): mechanism-named primitives with no verb for the actual intent. Same fix, different repo — name the act, encode the convention, enforce the safety.
## The load-bearing design choice: CI-green replaces human review
Trunk-based doesn't *skip* safety — it *moves* it from a human reading a PR to a machine running checks. So the entire value of `tbd_ship` depends on the CI gate being real and fail-closed:
- Merge **only** when the head SHA's workflow runs are all `completed` + success (`workflow_run_list` / `workflow_run_status` against the PR head SHA).
- Checks **pending/queued/in_progress** → do not merge; return PR URL + run status, let caller poll or re-invoke. Never merge on incomplete.
- Checks **red** → do not merge; return the failing run. Never merge.
- **No checks configured / no runs for the SHA** → **fail closed to PR-only.** Do NOT silently merge. "No CI" must never collapse into "commit straight to main with no safety" — that's precisely the confidently-wrong-agent failure mode the platform exists to prevent (cf. the UFW lockdown and `ip rule flush` incidents). The trunk shortcut is only safe *because* CI is the wall; if there's no wall, fall back to ceremony.
- Respect branch protection: probe `branch_protection_get` at call time; if protection requires reviews/approvals that auto-merge can't satisfy, fail closed to PR-only with a clear message. Don't assume a fixed config across repos.
## Proposed surface
```
tool: tbd_ship
params:
owner, name : repo
path, content : the change (single-file to start; multi-file is a follow-up)
message : commit message
base : default "main"
branch : optional; default derive short-lived name e.g. tbd/<slug>-<shortsha>
pr_title, pr_body : PR metadata
merge_strategy : default "squash" (tiny PRs → linear history)
delete_branch : default true (short-lived branches are the norm)
ci_timeout_seconds : optional; if set, poll up to timeout then return pending state
returns:
{ merged: bool, pr_url, merge_sha?, ci_status, reason_if_not_merged }
```
Convention enforcement inside the tool:
- short-lived branch naming (reject/normalize long-lived-looking names)
- never commit directly to `base` — always via branch+PR even on the auto-merge path (the PR is the CI trigger + audit record, not a review gate)
- squash-merge default for linear trunk history
## Scope
In:
- `tbd_ship` composite assembled from existing primitives (no new infra)
- CI-gate logic (the fail-closed table above)
- branch-protection probe + graceful PR-only fallback
- single-file change to start
Out:
- multi-file atomic ship (follow-up — needs a tree/commit API, bigger change)
- changing any repo's actual branch-protection config (that's an infra/per-repo decision, not this tool's job)
- rebase/conflict resolution (if base moved and merge isn't clean → fail closed, return for human)
## Acceptance criteria
- [ ] `tbd_ship` lands a single-file change to main via branch+PR+auto-merge when CI is green
- [ ] Pending CI → returns PR + pending status, does NOT merge
- [ ] Red CI → returns failing run, does NOT merge
- [ ] **No CI configured → falls back to PR-only, does NOT merge** (explicit test)
- [ ] Branch protection requiring review → PR-only fallback with clear message
- [ ] Short-lived branch auto-named and deleted on successful merge
- [ ] Squash default; linear history on main
- [ ] Unmergeable (base moved, conflict) → fail closed, return for human
- [ ] All four no-merge paths (pending/red/no-ci/protected) covered by tests
## Related
- hyperguild #43 — same intent-vs-mechanism interface problem, brain repo
- existing primitives this composes: `file_write_branch`, `pr_create`, `pr_merge`, `workflow_run_status`, `branch_protection_get`, `repo_status`
## Note on naming
`tbd_ship` is a working name. If the convention surface grows (e.g. a separate `tbd_hotfix`, or a `tbd_pr` for the no-auto-merge case), consider a small `tbd_*` family rather than overloading flags. Out of scope to decide now.
tbd_ship = branch from base → write the file → open a PR → auto-merge (squash) → delete the branch, in one call. The safety is a pure, fail-closed gate (evaluateShipGate): merge=trueonly when every workflow run for the PR head commit is completed+successand the base isn't review-protected. Everything else fails closed to PR-only with a reason.
Acceptance criteria → tests:
✅ lands a single-file change via branch+PR+auto-merge when CI green — TestTBDShip_GreenCI_Merges
✅ Pending CI → no merge — gate matrix (in_progress/queued/mixed) in TestEvaluateShipGate
✅ Red CI → no merge — TestTBDShip_RedCI_FailsClosed (+ cancelled in gate)
✅No CI configured → PR-only, no merge — TestTBDShip_NoCI_FailsClosed
✅ Short-lived branch auto-named (tbd/<slug>-<hash>) + deleted on merge — asserted in the green test
✅ Squash default (Do:"squash"), linear history
✅ Unmergeable (409) → fail closed, PR left open — TestTBDShip_MergeConflict_FailsClosed
✅ All four no-merge paths covered
task check green. Also handles new-vs-existing files (fetches the blob sha for updates) and probes branch_protection_get up front (real error fails closed). ci_timeout_seconds polls the head commit's runs to completion; default 0 = snapshot (returns pending immediately after opening the PR, since CI hasn't started yet — set a timeout, e.g. 300, to actually auto-merge in one call).
Brain corroboration for the fail-closed design: agentsquad#36 (non-compiling code, reviewer-approved, merged straight to main with no CI wall).
Out of scope (as stated): multi-file atomic ship, branch-protection config changes, rebase/conflict resolution.
Follow-up filed: re-invoke-to-merge collides on the deterministic branch (a second call with the same change hits "branch exists"). Filing an idempotent resume path separately.
Shipped in v0.6.0 (`internal/tools/tbd_ship.go`).
`tbd_ship` = branch from base → write the file → open a PR → auto-merge (squash) → delete the branch, in one call. The safety is a **pure, fail-closed gate** (`evaluateShipGate`): `merge=true` **only** when every workflow run for the PR head commit is `completed`+`success` **and** the base isn't review-protected. Everything else fails closed to PR-only with a reason.
**Acceptance criteria → tests:**
- ✅ lands a single-file change via branch+PR+auto-merge when CI green — `TestTBDShip_GreenCI_Merges`
- ✅ Pending CI → no merge — gate matrix (`in_progress`/`queued`/mixed) in `TestEvaluateShipGate`
- ✅ Red CI → no merge — `TestTBDShip_RedCI_FailsClosed` (+ `cancelled` in gate)
- ✅ **No CI configured → PR-only, no merge** — `TestTBDShip_NoCI_FailsClosed`
- ✅ Branch protection requiring review → PR-only — `TestTBDShip_ReviewProtected_FailsClosed`
- ✅ Short-lived branch auto-named (`tbd/<slug>-<hash>`) + deleted on merge — asserted in the green test
- ✅ Squash default (`Do:"squash"`), linear history
- ✅ Unmergeable (409) → fail closed, PR left open — `TestTBDShip_MergeConflict_FailsClosed`
- ✅ All four no-merge paths covered
`task check` green. Also handles new-vs-existing files (fetches the blob sha for updates) and probes `branch_protection_get` up front (real error fails closed). `ci_timeout_seconds` polls the head commit's runs to completion; default 0 = snapshot (returns `pending` immediately after opening the PR, since CI hasn't started yet — set a timeout, e.g. 300, to actually auto-merge in one call).
Brain corroboration for the fail-closed design: agentsquad#36 (non-compiling code, reviewer-approved, merged straight to main with no CI wall).
**Out of scope (as stated):** multi-file atomic ship, branch-protection config changes, rebase/conflict resolution.
**Follow-up filed:** re-invoke-to-merge collides on the deterministic branch (a second call with the same change hits "branch exists"). Filing an idempotent resume path separately.
Closing #40.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What
Add a high-level, intent-named tool — working name
tbd_ship— that performs the canonical trunk-based loop in one call: branch from base → write/commit change → open PR → wait for CI → auto-merge to main when green → clean up the branch. With conventions enforced inside the tool.Why
The primitives already exist (
file_write_branch,pr_create,pr_merge,branch_list,branch_delete,repo_status,workflow_run_status). What's missing is an intent verb for the act we actually do most: "ship this small, low-risk change to trunk, safely." Today an agent must orchestrate 4–5 mechanism-named calls and remember the conventions each time — so even a one-file change becomes a branch+PR ceremony left dangling for human merge, which is the opposite of trunk-based for a tiny safe change.This is the same disease just diagnosed for the brain interface (hyperguild #43): mechanism-named primitives with no verb for the actual intent. Same fix, different repo — name the act, encode the convention, enforce the safety.
The load-bearing design choice: CI-green replaces human review
Trunk-based doesn't skip safety — it moves it from a human reading a PR to a machine running checks. So the entire value of
tbd_shipdepends on the CI gate being real and fail-closed:completed+ success (workflow_run_list/workflow_run_statusagainst the PR head SHA).ip rule flushincidents). The trunk shortcut is only safe because CI is the wall; if there's no wall, fall back to ceremony.branch_protection_getat call time; if protection requires reviews/approvals that auto-merge can't satisfy, fail closed to PR-only with a clear message. Don't assume a fixed config across repos.Proposed surface
Convention enforcement inside the tool:
base— always via branch+PR even on the auto-merge path (the PR is the CI trigger + audit record, not a review gate)Scope
In:
tbd_shipcomposite assembled from existing primitives (no new infra)Out:
Acceptance criteria
tbd_shiplands a single-file change to main via branch+PR+auto-merge when CI is greenRelated
file_write_branch,pr_create,pr_merge,workflow_run_status,branch_protection_get,repo_statusNote on naming
tbd_shipis a working name. If the convention surface grows (e.g. a separatetbd_hotfix, or atbd_prfor the no-auto-merge case), consider a smalltbd_*family rather than overloading flags. Out of scope to decide now.Shipped in v0.6.0 (
internal/tools/tbd_ship.go).tbd_ship= branch from base → write the file → open a PR → auto-merge (squash) → delete the branch, in one call. The safety is a pure, fail-closed gate (evaluateShipGate):merge=trueonly when every workflow run for the PR head commit iscompleted+successand the base isn't review-protected. Everything else fails closed to PR-only with a reason.Acceptance criteria → tests:
TestTBDShip_GreenCI_Mergesin_progress/queued/mixed) inTestEvaluateShipGateTestTBDShip_RedCI_FailsClosed(+cancelledin gate)TestTBDShip_NoCI_FailsClosedTestTBDShip_ReviewProtected_FailsClosedtbd/<slug>-<hash>) + deleted on merge — asserted in the green testDo:"squash"), linear historyTestTBDShip_MergeConflict_FailsClosedtask checkgreen. Also handles new-vs-existing files (fetches the blob sha for updates) and probesbranch_protection_getup front (real error fails closed).ci_timeout_secondspolls the head commit's runs to completion; default 0 = snapshot (returnspendingimmediately after opening the PR, since CI hasn't started yet — set a timeout, e.g. 300, to actually auto-merge in one call).Brain corroboration for the fail-closed design: agentsquad#36 (non-compiling code, reviewer-approved, merged straight to main with no CI wall).
Out of scope (as stated): multi-file atomic ship, branch-protection config changes, rebase/conflict resolution.
Follow-up filed: re-invoke-to-merge collides on the deterministic branch (a second call with the same change hits "branch exists"). Filing an idempotent resume path separately.
Closing #40.