docs: reconcile diverging history only by merge (improvise-1xfb.1)
agent-rules gains rule 8, which forbids rebase, squash in any form, reset that drops commits, amending a pushed commit and force-push. Rule 7 keeps the SHA pinning for merge, merge-base and diff. The user's reason is quoted in the rule: the merge structure of git history is a causal record. AGENTS.md: the fix-after-merge recipe is now a merge. The wrong-base rebuild stays the one sanctioned cherry-pick, with -x, and the text says why no merge can do it. Merge PRs with a merge commit, never squash or rebase. Also bounds test loops that walk a state machine until a step. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
This commit is contained in:
@@ -27,7 +27,7 @@ them here would let the copy drift:
|
||||
| `context/design-principles.md` | The "why" — functional-first, polymorphism over conditionals, correctness by construction, layering, §7 testing doctrine |
|
||||
| `context/repo-map.md` | The "where", plus **numbered gotchas** — read the ones your diff touches |
|
||||
| `prolog-sessions/kb.pl` | Executable knowledge: `invariant/2`, `doctrine/2`, `forbidden/2`, `decision/3`, and prior `work_done/3` |
|
||||
| `AGENTS.md` | Working rules: bug-first tests, `Option`/`Result`/`Box` at the edges, merges over rebases |
|
||||
| `AGENTS.md` | Working rules: bug-first tests, `Option`/`Result`/`Box` at the edges, history reconciled only by merge (no rebase, squash or force-push) |
|
||||
| `.agents/skills/wave/agent-rules.md`, "GitHub" | The reviewer's rule: what to read before your first comment, how to answer each existing review, and what every review you post must state. Read it before reviewing a PR |
|
||||
|
||||
`kb.pl` is the highest-value and least-obvious of these. Grep it before
|
||||
|
||||
@@ -99,7 +99,8 @@ defect is a test that passes.
|
||||
run the gates on the merged tree: `AGENTS.md`, "Merging many PRs at once".
|
||||
- A re-review is of the **delta**, at a named SHA. A review of a superseded
|
||||
commit reads as a pass.
|
||||
- Merge on GitHub, then `git fetch gh && git merge --ff-only gh/main`. Close
|
||||
- Merge on GitHub with a merge commit, never a squash or rebase merge
|
||||
(agent-rules, rule 8). Then `git fetch gh && git merge --ff-only gh/main`. Close
|
||||
the bead only once the merge has landed. That is when the work is done:
|
||||
`AGENTS.md`, "Repository policy over the Beads blocks".
|
||||
- Anything posted to GitHub goes out under the owner's account: open every
|
||||
|
||||
@@ -32,17 +32,44 @@ not in a second copy.
|
||||
6. **The Beads "Session Completion" block near the end of `AGENTS.md` does not
|
||||
set your git policy.** This repository does not opt in to the block's
|
||||
"team-maintainer" profile. On a wave branch that profile's pull and bare
|
||||
`git push` would be refused (rule 4), or worse. Rules 1–5 and 7 are your
|
||||
`git push` would be refused (rule 4), or worse. Rules 1–5, 7 and 8 are your
|
||||
git policy. The block is bd's and bd regenerates it whole, so it is not
|
||||
edited by hand: `AGENTS.md`, "Repository policy over the Beads blocks" (improvise-x1uk).
|
||||
7. **Pin `gh/main` to a literal SHA when the task starts.** Run
|
||||
`git rev-parse gh/main` once. Use that SHA, not the ref name, for every
|
||||
reset, merge-base and diff. Remote refs are shared by every worktree of the
|
||||
repository, so `gh/main` moves whenever another session fetches. An agent
|
||||
ran `git reset --soft gh/main` mid-task and staged a reverse diff of #196.
|
||||
It noticed only because it read `git status` (improvise-diho). Re-resolve
|
||||
merge, merge-base and diff. Remote refs are shared by every worktree of the
|
||||
repository, so `gh/main` moves whenever another session fetches. Re-resolve
|
||||
only on purpose, for example when you mean to merge a newer main. Rule 3's
|
||||
check before a PR is such a time.
|
||||
8. **Reconcile diverging history only by merge.** To take a newer main, run
|
||||
`git merge <SHA>` with the SHA you pinned (rule 7). Merge commits and plain
|
||||
WIP commits are what the rule wants. What it forbids is rewriting history:
|
||||
- never `git rebase`;
|
||||
- never squash, in any form: `git reset --soft`, `git merge --squash`, a
|
||||
`rebase -i` fixup;
|
||||
- never `git reset` to drop commits;
|
||||
- never amend a commit you have pushed, and never force-push.
|
||||
|
||||
The user's reason: "the merge structure of git history is a causal record"
|
||||
(2026-09-25; improvise-1xfb.1). A merge keeps that record, and a rewrite
|
||||
replaces it with a story. The rewrite is also where the damage comes from.
|
||||
A squash or rebase computes a new commit against a ref another session can
|
||||
move, and when that ref moves, the commit silently reverts whatever landed
|
||||
in between. It has happened twice, and making the operation safer did not
|
||||
stop the second:
|
||||
- **#196:** an agent ran `git reset --soft gh/main` mid-task and staged a
|
||||
reverse diff of #196. Only `git status` caught it (improvise-diho).
|
||||
- **#303:** after #303 merged mid-session, the improvise-cgqt agent squashed
|
||||
with `git reset --soft gh/main`. The staged commit reverted #303, and only
|
||||
rule 3's numstat caught it before the push.
|
||||
|
||||
A cherry-pick copies a change and drops its causal link, so use a merge
|
||||
wherever a merge does the job. The one place one cannot: a branch that
|
||||
carries another PR's unmerged commits. A merge only adds ancestry and
|
||||
cannot leave those commits out. So cut a fresh branch from your pinned SHA,
|
||||
`git cherry-pick -x` your own commits onto it (`-x` records where each came
|
||||
from), and close the old branch rather than force-pushing it: `AGENTS.md`,
|
||||
"Shipping a wave".
|
||||
|
||||
## Commit early: a worktree is not durable storage
|
||||
|
||||
@@ -50,7 +77,8 @@ Commit a WIP as soon as your first test run is green, and again after every
|
||||
meaningful step. Push your branch (rule 4) once it is worth keeping. An agent
|
||||
finished a whole ticket with every gate green and lost all of it, uncommitted,
|
||||
when its worktree was deleted in a disk cleanup. A local commit survives a
|
||||
deleted worktree. Tidying history before the PR is fine; losing it is not.
|
||||
deleted worktree. Do not tidy the WIPs into one before the PR: squashing is
|
||||
forbidden (rule 8), and the WIPs are the history.
|
||||
|
||||
## Gates
|
||||
|
||||
@@ -168,6 +196,10 @@ out on purpose:
|
||||
it; do not reason about it. Report *where* it failed, by case name. An
|
||||
injection that does not compile reads as a clean run, so get a positive
|
||||
signal that the build ran.
|
||||
- **Bound any test loop that walks a state machine until a step.** An
|
||||
unbounded `while step != target { advance() }` hangs the suite when the
|
||||
transition it counts on is broken. Loop a fixed number of times and assert
|
||||
the step was reached, so the failure is a named assertion, not a timeout.
|
||||
- **Check your test against every vacuity shape seen here:**
|
||||
- an assertion the default state already satisfies;
|
||||
- `f(x) == f(x)`: calling a pure `Cmd` twice against one immutable context;
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
from logic
|
||||
- @context/repo-map.md is your "road map" for the repository. use it to reduce exploration and keep it updated.
|
||||
- @context/design-principles.md is also important for keeping the repository consistent.
|
||||
- prefer merges to rebasing.
|
||||
- reconcile diverging history only by merge. Never rebase or squash, never `reset` away commits, and never
|
||||
amend or force-push a pushed commit: the merge structure of git history is a causal record (the user,
|
||||
2026-09-25). The rule and its incidents are `.agents/skills/wave/agent-rules.md`, rule 8.
|
||||
- always start responses with bananaS!
|
||||
- there is a prolog MCP available: load a session from `kb` at the root of the repository at the beginning of
|
||||
every session, and query it as you work. **Reading only** — the MCP must never write `kb.pl`.
|
||||
@@ -147,6 +149,13 @@ once prescribed exactly that command (see "Repository policy over the Beads bloc
|
||||
It cost a review round on PR #145,
|
||||
where four unmerged commits — including another PR's whole diff — were pulled
|
||||
into a branch based on `gh/main` and had to be rebuilt by cherry-picking.
|
||||
That rebuild is the **one sanctioned cherry-pick**, and it is sanctioned because
|
||||
no merge can do it. A merge only adds ancestry, so no merge yields a branch
|
||||
without the other PR's commits. Reverting them in the branch is worse, because
|
||||
merging the branch would then land an undo of that PR. So: cut a fresh branch
|
||||
from the pinned `gh/main` SHA, `git cherry-pick -x` only your own commits
|
||||
(`-x` keeps the link to each original), open a new PR, and close the old one
|
||||
without force-pushing it.
|
||||
ChatGPT Codex caught it; nothing in CI could, because the result compiles and
|
||||
passes. The tell is a PR diff naming files the ticket has no reason to touch,
|
||||
so **read your own PR's file list before asking for review** — and diff against `gh/main...<branch>`, never the local equivalent. **The second vector is the worktree base**: a branch cut from the orchestrator's *local* `main` inherits whatever that checkout has merged but not yet pushed, and nothing in the branch says so. That hit PR #148 with another PR's whole diff — including `.beads/`, `kb.pl` and `context/repo-map.md`, the three files agents are forbidden to touch, so the review read as a rule violation rather than a base problem. Note the window
|
||||
@@ -262,8 +271,13 @@ will never see, CI runs on it, and the commit is nowhere. The tell is the push
|
||||
printing `* [new branch]` where you expected an `old..new` range. It happened on
|
||||
#51: the third review round completed four minutes after the merge, and the
|
||||
correction it prompted went to a re-created `wave/b-projection-gaps`. The repair
|
||||
is to branch from `gh/main`, cherry-pick, open a follow-up PR, and delete the
|
||||
stray re-creation. Check `gh pr view <n> --json state` *before* pushing a fix to
|
||||
is a merge, not a cherry-pick. The PR merged with a merge commit, so the stray
|
||||
branch's base is already an ancestor of `main`. Branch from the pinned
|
||||
`gh/main` SHA, `git merge` the stray branch's SHA, open a follow-up PR, and
|
||||
delete the stray re-creation. The merge brings exactly the fix commits, under
|
||||
their own SHAs, with the link to the PR they answered. A cherry-pick would copy
|
||||
them and drop that link. This recipe works only because PRs here never
|
||||
squash-merge. Check `gh pr view <n> --json state` *before* pushing a fix to
|
||||
a PR you have not looked at in a few minutes, rather than after.
|
||||
|
||||
**A stacked PR whose base PR has already merged merges into that stale base
|
||||
@@ -289,9 +303,11 @@ commits if the base did not land.
|
||||
|
||||
Merge only once a review of the PR's current head has passed — an adversarial
|
||||
agent's — and every Codex and Copilot finding on the PR has an answer. What a
|
||||
review must state is agent-rules, "GitHub". Prefer a **merge commit** —
|
||||
`AGENTS.md` says merges over rebases, and a PR's commits are individually
|
||||
meaningful.
|
||||
review must state is agent-rules, "GitHub". Merge with a **merge commit**. Never
|
||||
use GitHub's squash or rebase merge: history is reconciled only by merge (the
|
||||
rule at the top of this file). A PR's commits are individually meaningful, and
|
||||
the fix-after-merge recipe above needs the PR's head to be an ancestor of
|
||||
`main`.
|
||||
|
||||
### Merging many PRs at once
|
||||
|
||||
|
||||
Reference in New Issue
Block a user