diff --git a/00-META/process/00-overview.md b/00-META/process/00-overview.md index c0a4f145..379725e2 100644 --- a/00-META/process/00-overview.md +++ b/00-META/process/00-overview.md @@ -56,7 +56,8 @@ the expensive half. | [04](04-build-handoff.md) | Build handoff | A design is ready to be built | | [05](05-constitution-sync.md) | Constitution sync | `how-we-build.md` changed a rule the mesh enforces | | [06](06-writing-a-module.md) | Writing a module | Something that runs today must run on the mesh | -| [07](07-feature-branches.md) | Feature branches across repos | Work that changes code, in one repo or several at once | +| [07](07-feature-branches.md) | Features | Work that changes code across issues or repositories: prepared context, local development, one review, one check, one delivery | +| [08](08-small-fixes.md) | Small fixes | One issue in one repository, about an hour of work: one review, one push, one delivery | ## The cycle is checked diff --git a/00-META/process/07-feature-branches.md b/00-META/process/07-feature-branches.md index 68c29501..beb75993 100644 --- a/00-META/process/07-feature-branches.md +++ b/00-META/process/07-feature-branches.md @@ -1,244 +1,175 @@ -# Playbook 07 — Feature branches across repos +# Playbook 07 — Features -**Trigger.** Work that changes code — in one code repo or in several at once (`mesh-sdk`, -`mesh-controller`, `mesh-catalog`, `mesh-host`, `mesh-lab`, and `hq` when a decision rides along). +**Trigger.** A feature: work that changes code in one repository or several (`mesh-sdk`, `mesh-controller`, +`mesh-catalog`, `mesh-host`, `mesh-lab`, and `hq` when a design rides along), too large to be one small fix. +A small fix (one repository, one issue, about an hour of work) follows +[playbook 08](08-small-fixes.md) instead. -**Who runs it.** Anyone who writes code, engineers and agents alike. Agents follow it exactly — -it is the guard against the failure it was written for. +**Who runs it.** Anyone who writes code, engineers and agents alike. Agents follow it exactly. -## The failure it prevents +## The failures it prevents -A feature was worked as a branch-and-MR per *unit of thought* — one per decision, one per -stacked increment — and each MR was treated as finished when it was *opened*, not when it was -*merged*. Across repos the same feature took a different branch name in each. The MRs piled up -unmerged: one session left **sixteen** stacked intermediate MRs that had to be consolidated and -closed by hand. An MR is a review checkpoint, not a scratchpad. +- **Pull requests as scratchpads.** A feature worked as one pull request per unit of thought left sixteen + stacked pull requests unmerged, consolidated by hand afterwards. A pull request is the end of a feature, + not a step in it. +- **Waiting on checks mid-feature.** A feature worked as one pull request per issue into protected + branches spent most of a day waiting for merge checks while the code was ready. +- **Context bought again and again.** A fresh agent per small issue spends most of its run reading the + playbook, the issue and the code before it writes a line. On 2026-10-14 an issue sized for five to ten + minutes took forty-one. +- **Finished work that never ships.** A feature that kept taking new issues held thirteen landed issues + undelivered for a day: an issue done at 21:09 was still not on the machines at 15:00 the next day. +- **Work that waits and nobody notices.** Most lost time was not work at all, but a check, a delivery plan + or an agent waiting on something that had stopped, unseen. -It failed the other way too. A large feature was worked as one pull request per issue into protected -integration branches, each waiting for the forge's merge check, again whenever another merge moved the -branch, and for a review. Most of a day went to waiting on checks while the code itself was ready. +## The shape -## The rule +One feature is **one branch name**, **one worktree per repository**, **one pull request per repository**, +opened **once**, after the review. It runs in five phases, and its time is bounded: -One feature is **one branch name**, **one worktree per repo**, **one MR per repo**, opened -**once, at the end**. +| Phase | What happens | Bound | +|---|---|---| +| 0. Prepare | the lead settles the open questions and writes one context file per repository | 15 minutes | +| 1. Develop | one fixing agent per repository builds every issue locally, unit tests only | 1 to 2 hours | +| 2. Review | one reviewer reads all of it; reports go both ways; at most 3 rounds | 15 minutes | +| 3. Check | push; the forge's merge checks run the integration tests; no further review | one check run | +| 4. Deliver | merge; the delivery plan builds, publishes and sends the whole feature | its delivery plan | -1. **Name the feature once.** `feat/`. The *same* branch name in every repo the feature - touches — never a different name per repo, never a fresh branch per increment within the - feature. +A feature that cannot be developed in about two hours is split into **steps**. Each step runs all five +phases on its own; the next step starts only when the previous step's delivery plan is done. A step is the +unit that ships, so nothing finished waits for work that is not. - The feature branch is never protected and takes no pull request until step 4: a protected - branch forces a merge check on every merge into it, which is the waiting this playbook avoids. +**Scope is fixed at phase 0.** An issue found during a feature goes to the next step or the next feature, +never into the running one, unless it blocks the running one. -2. **Isolate each repo.** One git worktree per touched repo under `.work//`, branched - off `main`: - ``` - git worktree add .work// -b feat/ origin/main - ``` - Parallel features never collide, and no shared checkout is edited. +## Phase 0 — Prepare (the lead, larger model) - **Add the untouched siblings the lab reads.** The lab beds find the other repositories by - sibling path from the lab checkout (`../mesh-tools/module.json`, `../mesh-sdk`, …), the way - the main layout has them. A `.work//` directory holding only the touched repos fails a - bed at once with `no manifest for mesh-tools at …/.work//mesh-tools/module.json`, after - genesis has already passed. Give the directory those repos as **detached worktrees on `main`** - — never symlinks: - ``` - git worktree add --detach .work//mesh-tools main - ``` -3. **Commit as you go — locally.** Increments land on the one branch. Nothing is pushed and no - MR is opened mid-feature. +1. **Name the feature once:** `feat/`, the same branch name in every repository it touches. The + branch is never protected. +2. **Make the worktrees:** one per touched repository under `.work//`, branched off `main` + (`git worktree add .work// -b feat/ origin/main`). The lab beds find the other + repositories by sibling path, so add the untouched siblings the lab reads as detached worktrees on + `main` (`git worktree add --detach .work//mesh-tools main`), never symlinks. +3. **Settle every open question** in the feature's issues before any agent starts: on the issue, through + the knowledge base, or by asking the operator. A fixing agent that meets an undecided design question + stops, and its run is spent again. +4. **Write one context file per repository**, `.work//.context.md`. It is what a fixing agent + reads instead of rediscovering the code, and it is reused by every agent and every round: + - the issues it builds, in order, each with: the files and functions it changes, the decisions it builds + with (quoted, with where each was decided), the test to write first, done-when in one sentence; + - the repository's map for this feature: the packages and files involved, with one line each on what they + hold, and the patterns to copy (file:line); + - what not to touch, and what lands beside it in other repositories; + - the local facts: the worktree, the branch, the check commands, a temporary directory for tests outside + the home (`TMPDIR`), a throwaway database server if the store tests need one. + When the reading is large, a read-only agent on the smaller model answers the lead's exact questions with + file:line, and the lead writes the file from its answers. +5. **Move the issues to `fixing`**, `by` naming the feature's agent for that repository. - The unit of work is an **issue**. Each commit message names the issue it fixes, such as - "Keep the assignments on the bus (issue 652)". No pull request, merge check, review or delivery - per issue. +## Phase 1 — Develop (one fixing agent per repository, smaller model) -4. **Finish, review, then publish.** When the whole feature is done — every repo, tests green — - each repository's change gets one independent review **of the local commits, before anything - is pushed**. A push starts the forge's checks (building the modules and running every test, up - to a quarter of an hour), so a review finding fixed after a push costs another full run. The - reviewer reads the feature worktree. Once the review approves, push every branch and open - **one MR per touched repo**, together; the checks run once, on those MRs, and the feature is - delivered as one delivery plan. MRs in two or more repositories that share the branch name are - one delivery group (ADR 0239 §2); a feature in one repository is one delivery. A small change - outside a feature (a playbook, a design) goes the same way: review the local commit, then push - once. +- **One agent per repository, kept for the whole feature.** It reads its context file once and builds the + issues one after another, committing each locally with a message naming the issue ("Keep the assignments + on the bus (issue 652)"). It is not replaced per issue: the lead continues the same agent (by its id) with + the next instruction, so its context is paid for once. +- **Unit tests only.** Each issue's first test fails before its change and passes after; `gofmt`, `go vet` + and the repository's unit tests stay green. The integration tests (`merge-check.sh` with the store and the + bus) wait for phase 3. +- **Nothing is pushed** and no pull request is opened in this phase. +- **An agent that finds the code differs from its context file stops and reports** (file:line); the lead + settles it, updates the context file, and continues the agent. +- **Two repositories that depend on each other** keep their order in the context files: the reader before the + writer, the controller's new manifest word before the catalogue manifest that uses it. +- **Several agents in one repository** (only for a large repository with independent areas): each works on a + local branch of its own from the feature branch's head and lands by fast-forward under a lock + (`flock .lock git -C merge --ff-only `), after its unit + tests pass; if another landed first, it merges again and repeats. In a shared clone, fetch the feature + branch by name before branching from it. Never resolve a conflict with a blind `git merge-file --union`. - **One exception: a catalogue change that uses a new manifest word.** The catalogue's merge check - reads manifests with the controller the mesh runs — the delivered one, not `main` and not the - group's own branch — so such a feature is delivered in two steps, controller first: merge and - deliver the controller's pull request, then ask `mesh-delivery.recheck` for the catalogue's, and - merge it on the new verdict. +## Phase 2 — Review (one reviewer, larger model; at most 3 rounds, about 15 minutes) -5. **Merge promptly, once approved.** Every merge into `main` is notified and approved - (ADR 0023); once it is, merge — - do not leave it sitting. The branch is deleted on merge. -6. **Leave nothing behind.** After the MRs merge, no `feat/` branch and no `.work/` - worktree survive. +1. **Bring every feature branch up to the current trunk** (fetch by name, then merge), so the reviewed tree is + the tree that is pushed. +2. **One reviewer reads every repository's local commits** together, against the context files and the + decisions. Its report is per repository: each finding as file:line, the failure it causes, and the fix. + It says which round it is in ("round 1 of at most 3"). +3. **The report goes to the same fixing agents** that wrote the code, continued by their ids, so they fix + with the context they already hold. Each answers the reviewer: what it changed, per finding, or why not. +4. **The reviewer reads the answers and the new commits**, and approves or reports again. From round 2 it + asks changes only for a finding that makes the feature wrong; smaller points become issues of their own. + After round 3 the lead stops and brings the operator what is left, with a recommendation. -## Working one feature with several agents +## Phase 3 — Check (no review) -A large feature (a design's whole work) is fixed by several agents at once. The **lead** is the -session that runs the feature: it holds the feature branch, sends an agent per issue, and pushes, -reviews and delivers at the end. The agents never push. +1. **Push every feature branch and open one pull request per repository, together.** Pull requests in two or + more repositories that share the branch name are one delivery group (ADR 0239 §2). +2. **The forge's merge checks run the integration tests once** (`mesh/merge-gate`, `mesh/repo-check`). No + second review: phase 2 reviewed this tree. +3. **A check that fails** goes back to the reviewer with the failure, for **one more cycle** of phase 2 with + the same fixing agents; then push once again. +4. **Mark each pull request ready** (`mesh/ready` and a comment `ready at `) once the operator's word + for the feature is given; mesh-delivery merges it when its checks pass (ADR 0364). -- **One feature worktree per repository, held by the lead.** Each agent works on a local branch of - its own, in a worktree of its own, branched from the feature branch's current head. -- **Land by fast-forward, under a lock.** An agent brings its branch up to the feature branch, - runs the repository's tests and `merge-check.sh` in its own worktree, then fast-forwards the - feature branch under a lock (`flock .lock git -C merge - --ff-only `). If another agent landed first, it merges again and repeats. -- **The lead decides, a fixing agent builds.** Before an issue goes to an agent, the lead reads it and - the code it touches, finds every open question and settles it: decided on the issue, through the - knowledge base, or by the operator. An agent that meets an undecided design question stops, and its - whole run is spent again when it restarts. The issue the agent gets then says, in its own text: - - the repository and the files it changes: one repository and one change, sized so an agent finishes in - five to ten minutes. The repository's own check is a floor under that: mesh-controller's, with its - store, took 6 minutes 51 seconds on 2026-10-15 (the merge check of mesh-controller #342). A change that needs readers in one repository before a writer in another is two issues, - the second depending on the first, not one agent through three repositories: each extra repository - means merging the feature head and running every check again while other agents land; - - the decisions it builds with, quoted, never "read notes 8 to 12"; - - the test to write first, which fails before the change; - - its done-when, in one sentence; - - what it must not touch, and what is landing beside it. - An agent that finds the code differs from its issue stops and reports; it does not design around it. -- **Spend the operator's allowance on judgement.** Every agent the mesh runs draws on one weekly - allowance shared by every session, and the larger model (Opus) spends it many times faster than the - smaller one (Sonnet). Fixing agents and read-only diagnosis agents run on Sonnet. The larger model is - for the lead's own judgement, the independent review of each repository before the push, and - decision records, and as few of its subagents as possible. -- **Up to five Sonnet agents at once, at most two subagents on the larger model** (the lead not counted). - What sets the limit is how many agents the lead can settle and follow, and their token cost, not the - number of issues: an issue may take one agent or several, one after another. When one lands, the next ready - issue starts at once; an issue is ready when every issue its `depends-on` links name has landed on the - feature branch and the lead has settled its open questions. (Five agents on the larger model at once, - with restarts, spent a week's allowance in a night.) -- **An issue partly done goes back to `open`** with a note naming the commits that landed and what - is left, linked `depends-on` to what it waits for. An issue is never left in `fixing` while - nobody works on it. A finished issue's agent is gone, so the lead takes it over in `fixing` (its - `by` names the lead) and keeps working it: for an agent that finished without committing, the lead - commits, merges the feature head, runs the checks and lands it; for an agent that stopped on its - stop rule, the lead settles the question first — then sends the issue again or lands it itself — - or takes the question out of the feature as its own issue, saying on the issue which of the two it - did. It is resolved, with `fixed-by` naming the feature's MRs, once the feature's delivery is done. -- **Two issues in one file:** keep both sides' behaviour; where both added tests or list lines in - one place, keep all of them. Never resolve code with a blind `git merge-file --union`: it can - interleave two functions' braces. Build and test after every resolution. -- **Fetch by name.** In a shared clone a plain `git fetch` may update only `main`. Fetch the - feature branch with an explicit refspec before branching from it or comparing against it, or an - agent merges a stale copy. -- **Never land on a red check.** Run the checks, read the result, then land in a separate step; a - command that checks and lands anyway is how a red branch lands. A manifest claiming a new seat or - a new manifest word lands only after the controller change that understands it is delivered. -- **Review the tree that is pushed.** Before the local review, bring each feature branch up to the - current trunk (fetch by name, then merge or rebase), so the reviewed tree is the pushed tree. - Where an agent session pushes through the forge tool `gitea_commit` (it puts one commit on a branch - through the forge, as step 4's push), that commit is a new one on the forge's current base: the - reviewer confirms the forge commit's tree equals the reviewed one before posting its verdict on the - pull request. -- **Three to five review rounds.** Tell every reviewer which round it is in ("round N of at most 5"): a - reviewer that does not know keeps finding the next smaller thing. From round 3 it asks changes only for a - finding that makes the change wrong, and lists smaller points as follow-ups that do not block. If it - still asks changes at round 5, stop revising and bring the operator the findings with a recommendation: - block, or follow as an issue of its own. The same holds for a decision record's review (issue 784). -- **Report every ten minutes, and at once when something lands or needs the operator,** short, as a tree or a table: the issues done (of the total), each - issue being fixed with its active time (minutes since its agent was sent, from a start time - written down then, never guessed), the issues ready next, the ones waiting, and anything that - needs the operator, and the allowance used (`anthropic-licence-manager.usage`: the week's and the - session's share). Times are local and absolute. -- **When this playbook meets a problem,** the lesson is added here, so the next feature avoids it. +**One exception: a catalogue change that uses a new manifest word.** The catalogue's merge check reads +manifests with the controller the mesh runs, so the controller's pull request is merged and delivered first; +then `mesh-delivery.recheck` the catalogue's and merge it on the new verdict. Plan such a feature as two steps. -## The ready note +## Phase 4 — Deliver -The form of what "The lead decides, a fixing agent builds" (above) asks the issue to say: a note on the issue -headed exactly "Ready to build". If any line cannot be filled in, the issue is not ready. +The merge starts the delivery plan: build, publish, send. The lead follows it to done +(`mesh-controller.plans` with `wait` and `until: done`), then resolves the feature's issues with `fixed-by` +naming its pull requests, and removes what is left: no `feat/` branch and no `.work/` worktree +survive. -``` -Ready to build (,