feat(worktree): keep a per-worktree merge flow that scripts read

Every worktree can carry a deterministic merge flow in <git-dir>/pkgmgr/flow.yml (never committed, gone with the worktree): pull lists the refs merged into the branch, in order, before a gate, deploy or merge; push lists the branches the finished branch is merged into, in order, each then pushed by the operator.

- pkgmgr worktree flow [PATH] [--json] shows it; flow set (--pull REF.. | --no-pull) (--push BRANCH.. | --no-push) replaces both lists atomically (0600 temp file, os.replace, pkgmgr/ of mode 0700); flow clear removes it. JSON: worktree, git_dir, branch, flow_branch, pull, push, source (file, none, invalid), warnings.
- pkgmgr worktree up gains repeatable --pull/--push; without them pull is the base and push its local branch (origin/main gives main).
- pkgmgr branch tree carries "flow": {pull, push, source} or null on every item with a worktree, read from the linked worktree's metadata dir without running git inside the worktree; the Agent Orchestrator shows it.

The file and the arguments may come from an agent: every entry must equal its git check-ref-format --branch output (scan mode, cwd /) and must not start with -; the reader opens pkgmgr/ and flow.yml without following links, non-blocking, refuses anything but a regular file, caps at 64 KiB, uses yaml.safe_load, rejects unknown keys, wrong types and bool versions, and never raises into callers: a bad file reads as source invalid with empty lists and a warning.

Tests: 30 new (unit and integration on real repositories), unit suite 500 OK, integration 254 with the two known sandbox signing errors of test_publish_integration only.

🎯 Fix: 92 %
💥 Break: 4 %
💸 Cost: 97 %
⏱️ Time: 93 %
🛡️ Security: 100 %
🏆 Score: 98
🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 %

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kevin Veen-BirkenbachandClaude Opus 5.5 committed 2026-11-07 15:11:43 +00:00
1 parent 4c8b137449
commit 7fb229688b
12 files changed
+1196 -10

No files matched your search

+44 -1
View File
@@ -74,7 +74,10 @@ versioning features it can drive **end-to-end workflows**:
| Command | Does |
| --- | --- |
| `pkgmgr worktree up <branch> [--base REF] [--path DIR]` | Checks the branch out under `<directories.worktrees>/<host>/<owner>/<repo>/<branch>` or at `--path`, assigns a free slot and runs the pinned `worktree-init` hook |
| `pkgmgr worktree up <branch> [--base REF] [--path DIR] [--pull REF ...] [--push BRANCH ...]` | Checks the branch out under `<directories.worktrees>/<host>/<owner>/<repo>/<branch>` or at `--path`, assigns a free slot, records the merge flow and runs the pinned `worktree-init` hook |
| `pkgmgr worktree flow [PATH] [--json]` | Shows the merge flow of the worktree at `PATH` (default: `.`) |
| `pkgmgr worktree flow set [PATH] (--pull REF ... \| --no-pull) (--push BRANCH ... \| --no-push) [--json]` | Replaces both lists of the merge flow |
| `pkgmgr worktree flow clear [PATH] [--json]` | Removes the merge flow |
| `pkgmgr worktree cleanup [--apply]` | Removes clean worktrees whose commits are merged or pushed: pinned `worktree-fini` hook, compose stack, checkout; the branch stays |
| `pkgmgr branch cleanup [--apply]` | Removes merged branches that no worktree holds, locally and on the remotes |
| `pkgmgr branch tree [--format json\|yaml] [--output FILE]` | Writes every managed repository's branches and worktrees with their cleanup state, without fetching |
@@ -108,6 +111,46 @@ Every cleanup is a dry run until `--apply`, and `--json` prints the plan for
other tools. The [agent skills](https://github.com/kevinveenbirkenbach/skills)
wrap these commands; see [AGENTS.md](AGENTS.md) for the split.
#### Merge flow
Every worktree can carry a merge flow that scripts and skills read: `pull`
lists the refs merged into the branch, in order, before a gate, deploy or
merge; `push` lists the branches the branch is merged into, in order, when it
is finished, each then pushed by the operator. It lives in
`pkgmgr/flow.yml` inside the worktree's own git directory (`.git` of the main
worktree, `<common>/worktrees/<id>` of a linked one), so it is never committed
and goes with the worktree:
```yaml
version: 1
branch: feature/x
pull:
- origin/main
- store-i18n
push:
- feature/iam-profile-rbac
- production
```
`branch` is the branch the flow was set on; another current branch shows as a
warning. `worktree up` records `--pull` and `--push`; without them pull is
the base the worktree came from and push that base's local branch
(`origin/main` gives `main`). `flow set` needs each list or
its `--no-pull`/`--no-push`, and writes atomically into a `pkgmgr/` of mode
0700. Every entry must pass `git check-ref-format --branch` and must not start
with `-`; a remote-tracking name such as `origin/main` passes. An invalid name
exits 2 and writes nothing. A flow file that is a symbolic link, no regular
file, larger than 64 KiB or malformed reads as `invalid`, with empty lists and
a warning, and is never followed.
`flow --json` prints `worktree` and `git_dir` (absolute paths), `branch` (the
current branch, `null` when detached), `flow_branch` (the file's `branch`),
`pull`, `push`, `source` (`file`, `none` or `invalid`) and `warnings`; it
exits 0 also without a flow, 2 when `PATH` is not a git worktree. In
`pkgmgr branch tree` every item carries `flow`: `{"pull", "push", "source"}`,
or `null` without a worktree or a flow; flow warnings join the repository's
`warnings`.
---
## Architecture & Setup Map 🗺️