cc113c732f
- repo-stats reads the gotcha list from context/gotchas.md (the digest in repo-map.md no longer measures the doc-growth signal) and reports it as `docs.gotchas`; the test fixture and its derived expectations follow. - AGENTS.md and the wave agent-rules protect gotchas.md and file-inventory.md like the repo map they were split from. - doc-site.md's publication table lists the two new pages. - file-inventory.md's Context docs rows describe the three files correctly. - the code-review skill points at gotchas.md for the long form. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
416 lines
25 KiB
Markdown
416 lines
25 KiB
Markdown
# Standing rules for a wave agent
|
||
|
||
Every work-unit agent reads this **in full before its first command**. A brief
|
||
points here and adds only what is specific to its wave: the expected base
|
||
commit, the agent's ground, its `BEADS_ACTOR`, and its tickets. Every rule below
|
||
was bought by a failure.
|
||
|
||
This file holds each agent-side rule **once**. The brief checklist is
|
||
`SKILL.md` beside it. Where `AGENTS.md` ("The Wave Workflow") carries a longer
|
||
history, the rule here points there. Change a rule here, not in a brief, and
|
||
not in a second copy.
|
||
|
||
## Base, branch, push
|
||
|
||
1. **Verify your base against the commit your brief names, first.** Run
|
||
`git log --oneline -1` and compare. Worktrees do not reliably branch from
|
||
the orchestrator's HEAD: one was created three merges behind, and it was
|
||
only noticed because the agent was told to look.
|
||
2. **Branch from `gh/main`, never `main` or `origin/main`.** `git fetch gh &&
|
||
git checkout -b <branch> gh/main`. A worktree handed to you may already sit
|
||
on unpushed commits. Why: `AGENTS.md`, "Shipping a wave" (the two remotes
|
||
both spelled `main`, and the worktree-base vector).
|
||
3. **Before opening a PR, check `git diff --numstat gh/main...<branch>`.** Do
|
||
not use `gh pr view --json files`: that is the union of your commits' files,
|
||
not the effective diff.
|
||
4. **Push with an explicit refspec:** `git push gh HEAD:refs/heads/<branch>`. A
|
||
branch cut from `gh/main` tracks `gh/main`, so a bare push targets `main` and
|
||
is refused.
|
||
5. **Before pushing a fix to an existing PR, check `gh pr view <n> --json
|
||
state`.** GitHub deletes a merged PR's branch. A push to it then prints
|
||
`* [new branch]` and lands nowhere. Why: `AGENTS.md`, "Shipping a wave".
|
||
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, 7, 8 and 9 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
|
||
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,
|
||
re-pin on purpose first (rule 7): `git fetch gh`, then `git rev-parse
|
||
gh/main`. Then run `git merge <new SHA>`. Merge commits and new
|
||
commits are what the rule wants.
|
||
|
||
**The default is a new commit.** The user: "As a general rule, you shuold
|
||
just add a new conventional commit. Small adjustments can ammend HEAD, but
|
||
when in doubt don't." [sic] (2026-09-25; improvise-1xfb.2). That narrows an
|
||
earlier ruling: "I don't mind amending on a branch that hasn't been pushed,
|
||
but don't do anything that requires a force push" (2026-09-25;
|
||
improvise-79h5).
|
||
- **Amend only HEAD, only for a small adjustment, and only while HEAD is
|
||
unpushed.** A typo, a file left out of the commit you just made, a
|
||
formatter's fix to it. When in doubt, add a new commit.
|
||
- **Never reorder, fold or squash commits, pushed or not**, and never move
|
||
them onto another base: no `git rebase` onto main and no `git reset
|
||
--soft gh/main`. Both incidents below were exactly that, on commits that
|
||
had not been pushed.
|
||
- **Once a commit is pushed**, nothing may rewrite it: no rebase, squash
|
||
(`git reset --soft`, `git merge --squash`, a `rebase -i` fixup), amend or
|
||
`git reset` that drops it. Anything that would need a force push is
|
||
forbidden, and so is the force push itself.
|
||
|
||
**Messages are Conventional Commits**: `type(scope): subject`, with the
|
||
scope optional. The types are Conventional Commits' own: `feat`, `fix`,
|
||
`refactor`, `perf`, `style`, `test`, `docs`, `ci` and `chore`. Do not
|
||
invent new ones. History also has ad-hoc prefixes (`tooling:`, `context:`,
|
||
`clippy:`, `checkers:`); write those as a standard type with a scope
|
||
instead, e.g. `chore(tooling):`. A ticket id goes at the end of the subject
|
||
in parentheses, as most recent conventional commits carry it: `fix(tui): the grid and the tile bar scroll
|
||
by the width they are drawn in (improvise-gtn7.1)`. A merge commit keeps
|
||
the message git writes for it. The message says what changed. The argument
|
||
for the change lives in `context/`, never only in the message
|
||
(`context/design-principles.md` §6).
|
||
|
||
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. That includes a branch that carries another
|
||
PR's unmerged commits, when that PR is open and will land as a merge
|
||
commit. Either stack on that PR, or wait for it to merge, re-pin, and merge
|
||
the new SHA. The foreign commits then leave your diff.
|
||
|
||
The one place a merge cannot help is commits that will never reach main as
|
||
they are. That means an abandoned or rebuilt PR, or commits that exist only
|
||
on a mirror or a local main (#148). Only then, cut a fresh branch from your
|
||
pinned SHA and `git cherry-pick -x` your own commits onto it (`-x` records
|
||
where each came from). Close the old branch rather than force-pushing it:
|
||
`AGENTS.md`, "Shipping a wave".
|
||
9. **Merge main as soon as you can, and keep merging it.** The user:
|
||
"Agents should merge main asap" and, when asked why, "Reduces conflicts"
|
||
(2026-10-09 UTC; bd memory `agents-merge-main-asap`, written by the
|
||
orchestrator; PR #335). Those two quotations are the user's words. The
|
||
timing below is the orchestrator's operationalisation of them.
|
||
- **When.** `git fetch gh`, then merge the new `gh/main` SHA (rule 7's
|
||
re-pin, done on purpose) at the start of a task, before the first review
|
||
request, before every push to a PR under review, and every time you
|
||
resume. Main only gets further from your base, and a conflict met early
|
||
is small. Never `origin/main`, never rebase, never squash (rule 8).
|
||
Commit the merge through `scripts/agent-git`.
|
||
- **After every merge of main, clean or not**, re-run the gates (see
|
||
"Gates") on the merged tree, not on each side, and diff the test names
|
||
against your pre-merge HEAD to see that none was lost. A clean merge is
|
||
the dangerous case: it can delete a test module or break a shared test
|
||
file with no conflict marker.
|
||
- **Re-measure your baseline** after a mid-task merge, and report it
|
||
against the merged SHA, so main's changes are not read as yours.
|
||
- **Resolve a conflict by hand.** For each conflicted file, diff the
|
||
result against both parents and the merge base, so a side's
|
||
non-conflicting changes are not lost to a whole-file `--ours` or
|
||
`--theirs`.
|
||
- **A stacked PR merges its base branch, not `gh/main`.** GitHub diffs it
|
||
against that base, so merging a `gh/main` ahead of the base puts main's
|
||
commits in its diff (the #145/#148 symptom, which rule 3's numstat
|
||
cannot see). The orchestrator retargets it to main once the base lands.
|
||
- **The exception is narrow.** A durability push of WIP (see "Commit
|
||
early") need not wait for a merge of main that conflicts. If you cannot
|
||
resolve the conflict yet, push the WIP to a separate wip branch, never
|
||
to the PR.
|
||
- **Each merge-and-push moves the head SHA** an in-flight review is aimed
|
||
at. Reviewers re-fetch the tip before posting, but do not merge main
|
||
only to push a reply.
|
||
- The mechanics are `AGENTS.md`, "Shipping a wave" (merge `gh/main`, never
|
||
`origin/main`) and "Merging many PRs at once" (diff each parent; two
|
||
clean changes to one test file can still fail together). Not restated here.
|
||
|
||
## Commit early: a worktree is not durable storage
|
||
|
||
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. Make each step its own conventional commit, and leave it
|
||
that way: steps are not reordered or folded together afterwards. A small fix
|
||
to the step you just committed may amend it while it is unpushed. Anything
|
||
more, or any doubt, is a new commit (rule 8).
|
||
|
||
## Gates
|
||
|
||
```
|
||
direnv exec . ./scripts/fmt-workspaces.sh # NOT cargo fmt --all; exit 2 = the gate did not run
|
||
direnv exec . ./scripts/check-harness-lock.sh # offline; a Cargo.toml change can stale harness/opfs-contract's lock
|
||
direnv exec . cargo clippy --workspace --all-targets -- -D warnings
|
||
direnv exec . cargo test --workspace --no-fail-fast
|
||
direnv exec . python3 scripts/check-comment-runs.py --self-test
|
||
direnv exec . python3 scripts/check-comment-runs.py --check-evidence # also answers the comment cap
|
||
direnv exec . python3 scripts/check-ci-graph.py --self-test
|
||
direnv exec . python3 scripts/check-ci-graph.py
|
||
direnv exec . ./scripts/checkers.sh # every suite in scripts/, its own self-test included
|
||
direnv exec . tpl prolog-sessions/kb.pl -g "(kb_consistent -> halt(0) ; (kb_report, halt(1)))"
|
||
direnv exec . ./scripts/node-test.sh 'web/tests/*.test.js'
|
||
```
|
||
|
||
This list is the `test`, `js` and `kb` jobs of `.github/workflows/ci.yml`,
|
||
step for step, and this file is the one place it is written down. Change it in
|
||
the same PR that changes CI. The other CI jobs are conditional, below, or left
|
||
out on purpose:
|
||
|
||
- **`wasm`**: when you touch a crate it checks (next bullet).
|
||
- **`harness-lint`**: when you touch `harness/opfs-contract`, run
|
||
`cargo clippy --target wasm32-unknown-unknown --all-targets -- -D warnings`
|
||
from that directory. Its format half is already `fmt-workspaces.sh`.
|
||
- **`build` and `browser`**: when you touch `web/`, `harness/`, or the surface
|
||
the page calls in `improvise-wasm-client` or `improvise-worker-server`
|
||
(third bullet).
|
||
- **`pages-deploy`** is left out. It deploys, and it runs only on a push to
|
||
`main`. What guards it locally is `check-ci-graph.py`, which is in the list.
|
||
|
||
- **Never `cargo fmt --all`.** It reaches only **one** of this repo's two
|
||
workspace roots, because `harness/opfs-contract` declares its own
|
||
`[workspace]`. The obvious repair is worse: `cargo fmt -p opfs-contract` from
|
||
the repo root exits **1**, *"not a member of the workspace"*, which looks
|
||
exactly like a real formatting failure (improvise-b4ks). Both spellings fail
|
||
in the direction that looks like success.
|
||
- **Run every gate under `direnv exec .`.** `./scripts/checkers.sh` scrubs the
|
||
environment to an allowlist, so a bare run drops `tpl` off `PATH` and reports
|
||
spurious failures.
|
||
- **Never pipe a gate.** `$?` must be the gate's own. And
|
||
`check-comment-runs.py --check-evidence` prints two lines with the comment
|
||
cap **first**, so `| tail -1` silently drops half of it.
|
||
`fmt-workspaces.sh` is the one most often lost this way: an unformatted tree
|
||
exits **1** and prints a diff, and `| tail` shows the diff's last lines and
|
||
exits 0. Read its exit status, or run it with `--fix` and commit what it
|
||
rewrites.
|
||
- **CI takes every tool from `nix develop .#ci`, and so should your scripts**:
|
||
no `/bin/…` or `/usr/bin/…` paths, and no bash-version or tool-version
|
||
probes (`AGENTS.md`, "CI takes its tools from the dev shell"). A test that
|
||
needs a restricted `PATH` builds one on purpose. It does not assume the
|
||
runner's `/bin` holds anything, because on the NixOS runners it holds only
|
||
`env` and `sh`.
|
||
- **`--no-fail-fast`**, because plain `cargo test --workspace` stops at the
|
||
first failing binary. Use it for any claim about blast radius.
|
||
- If you touch a browser-bundle crate (core, formula, io, protocol, command,
|
||
wasm-client, worker-server) or `improvise-gp`, also run
|
||
`cargo check --target wasm32-unknown-unknown -p <crate>` **for each crate**.
|
||
CI checks each one separately, so feature unification cannot hide a leak.
|
||
- If you touch `web/`, `harness/` or the page's wasm surface, run
|
||
`./harness/build.sh`, then
|
||
`./scripts/node-test.sh 'web/tests/bundle/*.test.js'`, then `npm ci` and
|
||
`npx playwright test` in `harness/`. The bundle tests sit outside the
|
||
`web/tests/*.test.js` glob and read the built `web/pkg`, so the standard list
|
||
never runs them. They failed only in CI once. A fresh worktree has no
|
||
`harness/node_modules`, and Playwright then reports a missing
|
||
`@playwright/test`, which reads like a broken checkout.
|
||
- **Playwright serves `harness/serve/`, not `web/`.** `harness/build.sh`
|
||
assembles that tree and it is gitignored, so after any change to `web/*.js`
|
||
— yours, or one a merge brought in — re-run `./harness/build.sh` before the
|
||
specs. Otherwise they test the old page. That failure looks exactly like a
|
||
real regression (`doctrine(rebuild_the_assembled_site_after_merging_web)`).
|
||
CI runs Chromium and Firefox only (`--project=chromium --project=firefox`),
|
||
so run those two locally.
|
||
- **If you change the grid's row or column count, also grep
|
||
`harness/fixtures/*.improv` and `harness/tests/*.spec.js`.** On PR #176 a
|
||
Playwright spec pinned the old collapsed-group row count. `cargo test` cannot
|
||
see that.
|
||
- **A PR that conflicts with its base gets no CI run.** So anything "left to
|
||
CI" is unchecked until the PR is mergeable (PR #177).
|
||
- **Measure your own baseline** before changing anything, and report it with
|
||
the machine it was measured on. A figure from the brief is a reference, not
|
||
your baseline.
|
||
- **Merge `gh/main` and re-run every gate before calling a PR green.** Then
|
||
check that no test *disappeared*. Diff test **names**, not counts: a
|
||
textually clean merge can delete a test module with no conflict marker, and
|
||
the other side's new tests mask the loss in a count.
|
||
- **Build each side's test list in a fresh `CARGO_TARGET_DIR`, every time.** If
|
||
a parent's tree comes from `git archive`, a reused target dir gives a wrong
|
||
answer, not just a slow one. Archive stamps every file with its commit time,
|
||
so cargo judges the sources older than the last build and lists the previous
|
||
tree's tests. On #188 that made three of the branch's own tests look
|
||
merge-only.
|
||
- **The shell guard refusing a compound command means NEITHER part ran.** Split
|
||
the command. After a refusal, re-check the state it was meant to produce.
|
||
- **Re-run the full suite after reverting an injected fault.** A revert applied
|
||
to the wrong occurrence once shipped two faults past every other gate.
|
||
|
||
## Discipline per ticket
|
||
|
||
- **Claim first:** `export BEADS_ACTOR=<your actor>` in every shell, then
|
||
`bd update <id> --claim`. Exit 13 means another actor holds it. Pick another;
|
||
do not retry.
|
||
- **Author every commit as `Claude (<BEADS_ACTOR>)`.** The user: "agents
|
||
should override git author name, I think to include an agent identifier
|
||
(matching BEADS_ACTOR)" (2026-09-25; improvise-1xfb.3). Make every commit
|
||
through the wrapper, merge commits included:
|
||
`BEADS_ACTOR=<your actor> scripts/agent-git commit …`, and the same for
|
||
`merge`. The wrapper sets the author name for that one command, and it refuses (exit 2) when `BEADS_ACTOR` is unset or empty. It
|
||
reads `BEADS_ACTOR` from its own environment, so the prefix works in a shell
|
||
that keeps nothing between commands.
|
||
- **The author email, the committer and the `Co-Authored-By` trailer stay as
|
||
they are.** The email is the owner's configured one, so GitHub still links
|
||
the commit.
|
||
- **Never set `user.name` with `git config`.** Worktrees share
|
||
`.git/config`, so it renames every agent at once. Do not use
|
||
`git -c user.name=…` either: `user.name` names the committer as well as
|
||
the author. Why the wrapper uses `GIT_AUTHOR_NAME`:
|
||
`context/test-evidence/tooling.md`.
|
||
- A plain `git commit --amend` keeps the author it amends, so amend with
|
||
plain git or with the wrapper.
|
||
- **A cherry-pick keeps its source's author**, through the wrapper or not:
|
||
git ignores `GIT_AUTHOR_NAME` there. That is right, because rule 8 allows
|
||
only `cherry-pick -x` of commits that keep whoever wrote them.
|
||
- Before you push, check `git log --format='%an <%ae> | %cn' <pinned
|
||
SHA>..HEAD`. Every commit **you made** must name your actor. A
|
||
cherry-picked commit, or one from a PR you stack on, keeps its own author.
|
||
- **The format is a proposal.** `Claude (<BEADS_ACTOR>)` is the default
|
||
proposed on improvise-1xfb.3's PR. The user may change it there. The format
|
||
lives in `scripts/agent-git` and nowhere else.
|
||
- **`bd` needs `.envrc` allowed in your worktree.** Without it you get a
|
||
**version mismatch**, not a clean failure. Run `direnv allow` in the worktree,
|
||
or rely on the `[whitelist] prefix` in `~/.config/direnv/direnv.toml`.
|
||
`direnv status` reports `Found RC allowed 0`, and `0` means **allowed** (the
|
||
enum is 0=Allowed, 1=NotAllowed). It reads like the opposite.
|
||
- **Reproduce at HEAD before fixing.** A dozen tickets were stale-open in one
|
||
campaign, one fixed two days before it was filed. If it does not reproduce,
|
||
find the fixing commit, confirm with `git merge-base --is-ancestor <sha> HEAD`,
|
||
report the SHA, and close nothing.
|
||
- **Write a failing test first, then inject a rival and watch it fail.** Run
|
||
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;
|
||
- a disjunctive assertion whose other branch is the bug's own symptom;
|
||
- a rival that fails the *setup*, which proves the neighbouring question
|
||
(e.g. setup and subject share an `EffectKind`);
|
||
- a fixture that is right by accident (expected value 1, group at row 0,
|
||
bystander already on the relayed value);
|
||
- a fixture or stub that lacks the structure the hazard needs, or fails in a
|
||
wider shape than the real fault;
|
||
- hand-picked fixture cases where the set could be **enumerated**: four
|
||
vacuities in `fmt-workspaces-test.sh` were each a case nobody thought of
|
||
(improvise-90ea);
|
||
- a rendered-output test at a width where the thing asserted is never drawn;
|
||
- a fold test that batches differently from the real producer (a
|
||
`Binding::Sequence` is ONE effect vec);
|
||
- **the cursor at index 0**: "the item under the cursor" and "the first item"
|
||
are indistinguishable there, and that was four of five findings in one
|
||
review. Seat the cursor at a nonzero index of a list with at least two
|
||
elements;
|
||
- a determinism check against ONE context instance: every `HashMap` iterates
|
||
the same way twice. Build two equal contexts independently
|
||
(improvise-hzjo);
|
||
- two derivations of one fact where the fixture makes them agree (drawn vs
|
||
committed, edit text vs value, `1200` vs `1.23456789`). Use a value where
|
||
they disagree.
|
||
- **Record each bug in `context/test-evidence/<area>.md`**, keyed by test name
|
||
(module-qualify the name when the leaf is not unique). It is append-at-EOF, so
|
||
**expect a conflict**. Keep both sides and verify line by line against both
|
||
parents: `--check-evidence` cannot see a dropped entry.
|
||
- **Never resolve a conflict with `git checkout --ours` or `--theirs`.** Either
|
||
one takes the whole file from one side and silently drops the other side's
|
||
*non*-conflicting changes to it. That is how a `ci.yml` step vanished while
|
||
the merge message said it was unchanged (improvise-dndu). After resolving,
|
||
diff the merge against each parent and against the merge base
|
||
(`AGENTS.md`, "Merging many PRs at once").
|
||
- **Comment runs are at most two lines.** Reasoning goes in `context/`, not in
|
||
comments (`context/design-principles.md` §6).
|
||
- **If the acceptance criteria look wrong, argue it** rather than following
|
||
them silently. Three agents inverted theirs in one campaign, and all three
|
||
were right.
|
||
|
||
## Files you must not write
|
||
|
||
`.beads/**` (never commit `issues.jsonl`, never `git checkout` it),
|
||
`prolog-sessions/kb.pl`, `context/repo-map.md`, `context/gotchas.md`,
|
||
`context/file-inventory.md`: `AGENTS.md`, "Who owns what".
|
||
Also `AGENTS.md`, unless your brief grants you a named section. Report proposed
|
||
text for any of these in your hand-back.
|
||
|
||
## GitHub
|
||
|
||
- `gh` is shell-aliased to a different tool. Use
|
||
`nix shell nixpkgs#gh --command gh …`, and pass bodies with `-F body=@file`.
|
||
- **Everything posted goes out under the repository owner's account.** Open
|
||
every PR body and comment with the attribution line your brief gives you,
|
||
verbatim. It names your agent and model, which is how our posts are told
|
||
apart from an automated Codex review (`AGENTS.md`, "Shipping a wave").
|
||
- The attribution line, commit trailers and PR footer all come from your brief.
|
||
They name the agent and the session, so this shared file cannot hold them.
|
||
- Read reviews with `./scripts/pr-reviews <n>`, and do not pipe it. It also
|
||
shows inline comments, which is where Copilot puts most of its findings.
|
||
Copilot reviews here again (2026-09-24), so answer its findings as you answer
|
||
Codex's. How to tell a Codex review from ours: `AGENTS.md`, "Shipping a
|
||
wave".
|
||
- **As a PR's author, reply on the PR to every review finding you address.**
|
||
Name the commit that fixes it, or say why you declined it. Putting it only in
|
||
your hand-back to the orchestrator is not enough: the PR thread is where the
|
||
next reader looks.
|
||
- **As a reviewer, read the existing reviews first**, with
|
||
`./scripts/pr-reviews <n>`, unpiped. Reply on its thread to every Codex
|
||
review: fixed (name the commit), declined (say why), or already one of our
|
||
tickets (name it). Every review you post states the head SHA it reviewed.
|
||
- **Answer a review even if the PR has already merged.** Reviews arrive late,
|
||
and the thread is still where the next reader looks.
|
||
- **Do not merge, and do not close beads.** Your part ends at an open PR plus
|
||
your hand-back. What "done" means is in `AGENTS.md`, "Repository policy over
|
||
the Beads blocks". Avoid stacked PRs. If you must stack, say in the PR body which PR it is
|
||
stacked on. Once that base PR merges, check the stacked PR's `baseRefName`
|
||
(`gh pr view <n> --json baseRefName`) and, unless it is `main`, retarget it
|
||
with `gh pr edit <n> --base main` before anyone merges it: merging it
|
||
otherwise lands it in the stale base branch, not in `main`. Fifteen PRs did
|
||
that on 2026-09-24 (`AGENTS.md`, "Shipping a wave").
|
||
|
||
## Hand-back, per ticket
|
||
|
||
Report:
|
||
|
||
- your branch, head SHA and PR number. Review is dispatched against the SHA;
|
||
- what changed, per ticket;
|
||
- the claim result;
|
||
- your measured baseline;
|
||
- whether the ticket reproduced (the SHA if it did not);
|
||
- the rival and where it failed;
|
||
- what you could not verify, and why;
|
||
- proposed repo-map and `kb.pl` text;
|
||
- **what the ticket did not predict**, consistently the most valuable part.
|
||
|
||
## Disk
|
||
|
||
Each worktree's `target/` grows to 3–6G, and a full disk (ENOSPC) once stopped
|
||
every agent. **When you hand back, delete your own build output, and only
|
||
yours.**
|
||
|
||
Run `pwd` and `git rev-parse --show-toplevel` first, and read the answer. Then
|
||
delete by **literal absolute path**, typed out:
|
||
`rm -rf /Users/.../.claude/worktrees/agent-<you>/target`.
|
||
|
||
**Never interpolate a computed path into `rm -rf`.** One agent's worktree had
|
||
been removed, so `$(git rev-parse --show-toplevel)` printed nothing and the
|
||
command became `rm -rf "/target"`. A guarded one-liner was tried, but the
|
||
worktree shell guard refuses it as too complex, which pushed agents back to an
|
||
unguarded form. A literal path is the version that is both allowed and safe.
|
||
|
||
Keep your branch and any uncommitted work. Delete only `target/` (and
|
||
`harness/opfs-contract/target` if you built it). If you see ENOSPC, stop and
|
||
report rather than deleting anything outside your worktree.
|