Compare commits

...
Author SHA1 Message Date
logan 7838f45ed4 fix(loop): refresh branches before merge and watch post-merge main CI
CI / check (push) Skipped
CI / e2e (push) Skipped
CI / check (pull_request) Successful in 3m16s
CI / e2e (pull_request) Successful in 11m30s
Adopting the six v0-era PRs surfaced two conflict-shaped cases the merge
leg handled only by luck. Behind-main branches are refused outright by
the repo's block_on_outdated_branch protection, so the leg now refreshes
every branch against origin/main before merging — which is also where a
textual conflict should surface, as diff text the loop resolves only
where it authored the hunks, otherwise abandoning the PR to a human
with a comment. And the one guard no mergeability check provides is the
push run on main after the merge: three PRs touching the same file can
merge cleanly and contradict each other, so a red main now halts the
loop instead of the tick reporting merged and moving on.

Closes #238
2026-09-03 09:38:05 -04:00
logan 2453d717cf Merge pull request 'feat(loop): autonomous backlog loop — tracker to merged main, scheduled' (#237) from feat/236-autonomous-backlog-loop into main
CI / check (push) Successful in 3m6s
CI / e2e (push) Successful in 11m37s
default
2026-09-03 03:54:15 +00:00
2 changed files with 44 additions and 7 deletions
+30 -7
View File
@@ -163,7 +163,22 @@ Merge when, and only when, **all** hold:
- the protection contexts `CI / check` and `CI / e2e` are green on the - the protection contexts `CI / check` and `CI / e2e` are green on the
PR's head, read from the API, not from the PR page's badge; PR's head, read from the API, not from the PR page's badge;
- the PR reports mergeable; - the PR reports mergeable;
- the critique leg ran and no open blocker stands. - the critique leg ran and no open blocker stands;
- the branch is **not behind `origin/main`** — the protection's
`block_on_outdated_branch: true` refuses it anyway; never
`force_manually_merged` around it.
**Refresh before every merge.** In the loop worktree: fetch, then
`git merge origin/main` on the PR branch, push. A textual conflict
stops the leg there — as diff text, not as a failed merge click: hunks
the loop authored are resolved by the loop; anything else is left with
`⟦loop⟧` comment for a human, never forced. After any refresh push,
re-poll the PR's own required contexts on the **new head** before
merging.
**Merges happen one at a time**, each re-reading state — the previous
merge moved `main`, and the next PR's mergeability is recomputed at
its own turn.
``` ```
curl -sS -X POST -H "Authorization: token $GITEA_TOKEN" \ curl -sS -X POST -H "Authorization: token $GITEA_TOKEN" \
@@ -172,12 +187,20 @@ curl -sS -X POST -H "Authorization: token $GITEA_TOKEN" \
-d '{"Do":"merge","merge_message_field":"default","force_manually_merged":false}' -d '{"Do":"merge","merge_message_field":"default","force_manually_merged":false}'
``` ```
Afterwards: `scripts/issue.sh list --state open` and check the footer **Afterwards watch the `push` run on `main`** — the CI the merge
took. Close stragglers with `issue.sh close`, naming the merge commit. started. A red main after a loop merge is a **halt**: comment what is
`unclaim.yml` handles the label; it is not instant; reopening does not known on the offending PR, mark the state file, stop taking new issues.
restore it. Merging fans out to nothing (releases are the manual That run is the only thing between a clean textual merge of
`release.yml`, which the loop never runs) — the criticism stands before independently-written PRs and a self-contradicting main; no
the merge because nothing stands after it. mergeability check sees it. Only a green main lets the tick proceed (to
footer verification, below).
Footer verification: `scripts/issue.sh list --state open` and check
the footer took. Close stragglers with `issue.sh close`, naming the
merge commit. `unclaim.yml` handles the label; it is not instant;
reopening does not restore it. Merging fans out to nothing (releases
are the manual `release.yml`, which the loop never runs) — the
criticism stands before the merge because nothing stands after it.
## Rails — the loop's absolute rules ## Rails — the loop's absolute rules
@@ -140,6 +140,20 @@ from a concurrent session is caught before the first edit.
- **Only PRs the loop opened.** A collaborator's PR is never merged, never - **Only PRs the loop opened.** A collaborator's PR is never merged, never
commented on for pressure, never touched. commented on for pressure, never touched.
- **Every branch is refreshed against main before its merge**, in the
loop worktree — the refresh is where a textual conflict surfaces, as
diff text: hunks the loop authored are resolved there, anything else
is left to a human with a `⟦loop⟧` comment. The protection's
`block_on_outdated_branch` makes the refresh mandatory for adopted
(pre-loop) branches: behind `main`, a PR cannot merge at all.
Required contexts are re-polled on the refreshed head.
- **Merges are one at a time**, each re-reading state — the previous
merge moved `main`, and the next PR's mergeability is recomputed at
its own turn.
- **Post-merge, the `push` run on `main` is watched.** A red main after
a loop merge halts the loop. That run is the only guard against the
class no mergeability check sees: two PRs touching the same file,
merging cleanly, contradicting each other.
- The gate is the protection rule itself, read from the API: contexts - The gate is the protection rule itself, read from the API: contexts
`CI / check*` and `CI / e2e*` green, PR mergeable. (Required approvals `CI / check*` and `CI / e2e*` green, PR mergeable. (Required approvals
is 0 today; if a second person changes protection rules, the merge is 0 today; if a second person changes protection rules, the merge