Files
branchgraph/docs/design.md
Jean-Gabriel Gill-Couture e03a92c570 Remove dead code, merge duplicated logic, match docs to the code
- Drop fields and functions nothing reads: job and proc tips, fetch time,
  PR url/author (and their GraphQL fields), spine summaries, diff::commit,
  unread data attributes, unused CSS and tokens.
- One home each for state wording (naming main as the repo does), lane
  lookup, unseen, outdated, main's tip, the stop wait and the token header.
- Narrow visibility; rename render::View to Filter; queue ranking moves
  to the CLI, its only user.
- Token test covers every route; shared checkout snapshot in the fixture.
- Client: errors outside Run/Send are toasts, history only after a good
  load, a drag stays in one file, the agent marker is a ring.
- design.md, the skill and AGENTS.md describe what is built.
2026-10-09 12:24:24 -04:00

230 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Branch Graph — product design
2026-10-09, revision 5. Revision 3 (the live graph, now built) is in git
history.
## Value
A lead has ten branches open, by people and by agents. Each one has to end
merged, fixed, or sent back with clear instructions. Today that takes a
GitHub review, a copied link, a paragraph to the agent explaining what to do
with it, a checkout and a run, for every branch.
Branch Graph shortens that loop to one screen per branch: see what the branch
changes (`diff --stat` first, then the diff), run it in its own worktree with
one key, write comments on lines, files or commits, and send them to a coding
agent working in that worktree. The graph of branches stays, as navigation.
Revision 3 answered "where are the branches". It drew topology well and
stopped at "copy this command". The owner's review found the gaps: no
branch-level diff, no `diff --stat`, no way to run a branch, no path from a
comment to an agent.
## User and loop
The user is a git CLI power user who runs coding agents (opencode, Claude
Code) in parallel. The loop, per branch:
1. **Pick:** the queue shows which branches need them: new commits since they
last looked, then conflicts or failing CI, then recency.
2. **Understand:** `diff --stat` against the fork point, grouped by directory,
then the diff of any file; the commits if the story matters.
3. **Try:** a worktree for the branch, created on demand, never touching the
user's own checkout; the project's run command started there, output on
the page.
4. **Act:** comment on a line range, a file, a commit or the branch. The
comments form the branch's brief. Send the brief to the agent, which works
in the same worktree. Or check the merge preview and merge from the label's
menu.
5. **Follow:** the agent's commits appear live in the branch; the brief marks
which comments were sent.
## Screen
One screen, review-first. Layout at 1440 × 900:
- **Top bar, 48 px:** repository, **Base** selector, filter field (name or
`@author`), live dot, fetch. Base is the
branch "All" compares with: main, or any open branch, then the stat and
diff show what the branch adds since it split from that one, as a pull
request into it would. It stays while the user moves between branches.
Off main it turns amber, and a line above the stat says "Compared with
feat/x, not main · use main": a review against the wrong base is costly.
- **Rail, 400 px, left: the commit graph**, as gitk or `git log --graph`
draws it, so the user sees how every branch ties back to main. One row per
commit, newest first; main in the first column, each branch in its own
column, forks and merges drawn. Main between fork points and merges folds
into a count (`+37`); a branch shows its newest 30 commits. Branch tips
carry a label: state on its border (red conflicts, amber CI failing, green
ready), an accent dot for commits not seen yet. Lines stay neutral; the
selected branch's rows and nodes take the accent. A label opens its menu,
as gitk's branch heads do: Review, Check out, Merge into (the branch
checked out in the user's checkout), Preview merge, Compare the open
branch with this, Copy name; a modified click opens the branch in a tab;
a commit opens it narrowed to that commit; `j`/`k` move between branch
labels. The graph waits while the pointer is on it. Hollow nodes are not
pushed. "3 merged or stale · show" at the bottom; the filter field (name or
`@author`) redraws the graph for the matching branches.
- **Stat, 300 px:**
- Header: branch name; one muted line: author · PR #182 draft (or no PR) ·
worktree path when there is one. Two commands on the name line: Run and
Send (n), n the number of draft comments.
- Toggle "All vs <base> | Since last review". "Since last review" is the
default when the branch moved since the user last reviewed it, typically
after an agent pushed.
- The changed files against the merge base, grouped by directory: `+a −r`
per file, a thin bar on directory rows, churn order inside a directory,
lock files in a muted group at the end.
- A commit picked in the graph narrows stat and diff to it, with
"a11c09e · clear" above the files.
- **Diff, rest of the width: every file, one scroll**, in the stat's order,
each under a sticky header (path, counts, comment on file). A file clicked
in the stat scrolls to the top of the pane; the stat marks the file being
read as the pane scrolls; `[`/`]` step between files. The pane holds the
first 200 KB of the diff; a file past that shows "Load diff", which loads it
alone (up to 2 MB). Drag down the line numbers to select lines; the comment
box opens on release. Drafts sit inline under their lines. `⌘↵` saves a
draft; `esc` closes the box and keeps the text.
- **Drawer, under stat and diff:** closed, one 28 px status line (the run
command, "running", "port 20431" as a link to the app, "agent idle"), hidden
until something ran. Open (`o`), 280 px: Run and Agent tabs on the left, the
output on the right. Stop is the Run button in the stat header, which turns
into Stop while the run lives.
- **Send dialog, 760 px, centred:** the review as it will go out, compact
like a submitted GitHub review. Header: branch, "5 comments on 3 files",
base and the tip reviewed. Tabs: Review, Markdown (the exact brief).
Review: the instruction, then the comments grouped by file in line order,
each with its spot (`L40–52`, file, commit), a short excerpt and its text;
branch and commit comments last. Every text is editable in place and saves
as you type; each comment can be deleted. Actions: **Copy brief** (paste it
into a running agent session), then **Mark as sent** so it does not go out
twice (the page's way to do what `branchgraph brief --ack` does for an
agent); **Send to agent** (`⌘↵`) runs the agent command in the worktree. The
footer says where the agent runs. It refuses while a comment box in the
diff is still open, or an agent job runs in that worktree.
- Comments written on an older tip show "outdated".
### Keys
`j`/`k` branch, `[`/`]` file, `r` run, `s` send, `o` drawer, `m` merge
preview, `p` pause the live graph, `/` filter, `?` keys; `⌘↵` saves a comment
or sends, `esc` closes a box, dialog or menu.
## Actions
| Action | What it does |
| --- | --- |
| Worktree (implicit) | Run and Send need one: reuse the worktree where the branch is checked out, else create `<clone>.worktrees/<slug>` with gix (a remote-only branch gets a local tracking branch). On creation, copy the untracked files named by `branchgraph.copy` (for example `.env*`) from the main worktree, then run `branchgraph.setup` once. |
| Run | `branchgraph.run` in the worktree, one per branch, `BG_PORT` stable per branch. Output in the drawer, last 2 000 lines. Stop sends SIGTERM to the main process only, SIGKILL 10 s later; the command (a `dev.sh`) decides what else stops. |
| Comment | On a line range, a file, a commit or the branch; a draft until sent. |
| Send | Writes the brief, runs `branchgraph.agent` in the worktree, marks the drafts sent. One agent job per worktree at a time. |
| Merge preview | `m`: the merge preview, with copy-commands. |
| Check out | From a label's menu: `git switch` in the user's checkout (a remote-only branch gets a tracking branch). Refused over modified tracked files, from a detached HEAD (its commits would be left unnamed) and for a branch checked out in another worktree. Says so when the local branch is not at the tip shown. |
| Merge into | From a label's menu: `git merge` of the tip the page shows into the branch checked out in the user's checkout. Refused over modified tracked files or on a detached HEAD; a conflict is aborted at once and its files listed, so the checkout is never left mid-merge. Never pushed. |
### Commands: the branch's `.branchgraph`
Settings travel with the code, in `.branchgraph` at the repository root,
git-config syntax:
```
[branchgraph]
run = cd recordum && bash localdev.sh
setup = cargo fetch
copy = .env*
agent = opencode run -c -f "$BG_BRIEF" "Apply the review in the attached brief."
verify = cargo test
```
Read for the branch being run: the file in its worktree (an edit counts
before it is committed), else as committed on the branch, else main's. No
git-config source, so a setting is never one person's machine only.
**Settings** (the gear in the branch header) opens the file in an editor and
saves it into the branch's worktree (made if needed). It is the only file
branchgraph edits; the user commits and pushes it.
A branch can change what Run executes. When its commands differ from main's,
Run (or Send) shows them in the drawer and asks once; "Run it" approves what
was shown for that branch, so a command that arrived since is asked about
again. Copying files into a new worktree (`copy`) counts as a command.
Commands the user saved in the editor count as approved.
Each command runs with `sh -c` in the worktree. Values reach it only as
environment variables: `BG_BRANCH`, `BG_SLUG`, `BG_WORKTREE`, `BG_BRIEF`,
`BG_PORT`. The default agent command continues the worktree's last opencode
session, which is the agent that wrote the branch, not a stranger.
The server prints every command it runs, its directory, its output (one line
per output line, prefixed with the branch) and its exit status to the
terminal it was started from. A refused Run or Send shows its reason in the
drawer.
### Brief
A Markdown file an agent acts on without a follow-up question:
- branch, base, merge-base sha and the tip sha reviewed, so it can detect
drift;
- the overall instruction, what done means, the verify command
(`branchgraph.verify`; without it, "run the project's tests"), and the
push rule (default: do not push);
- each comment: anchor (`src/fleet/vehicle.rs:40-52 @ a11c09e2`), code
excerpt, text;
- rules: one commit per comment citing its anchor; skip an ambiguous comment
and say why rather than ask.
## As an agent skill
`skill/branchgraph/SKILL.md` lets opencode or Claude Code use the tool. The
CLI reads the repository and the state directory directly; `open` and
`run-log` need the server.
- `branchgraph open [branch]` starts the server for the repository if none
runs (detached, port file in the state directory) and prints the URL, so
an agent can end with "review here: URL".
- `branchgraph queue` prints the queue with each branch's stat.
- `branchgraph brief [branch]` prints the unsent brief; the branch defaults
to the one checked out in the current directory. `--ack` marks it sent once
the agent has acted.
- `branchgraph run-log [branch]` prints the run output, so the agent reads
the error without the user copying it.
## Trust boundaries
- Commands come from the branch's `.branchgraph`; one that main does not run
is asked about once before it runs, so a teammate's branch cannot run new
code on a click without the user seeing it.
- Run executes the branch's code; it starts only on the user's click or key.
- Every route needs a per-launch token: the printed URL carries it once and
the browser keeps it as an HttpOnly, SameSite=Strict cookie named per port.
The server binds 127.0.0.1, where it also checks the Host name; `--bind
0.0.0.0` listens on the network over plain HTTP, accepts any Host, still
requires the token and a same-origin Origin, and warns at start.
- Writes to the clone: fetch, worktree creation and the local tracking branch
it needs, and Check out or Merge in the user's checkout from a branch's
menu, on that click only. Never a rebase, push or branch delete. Agents
write in their worktree; that is their job.
- Briefs, review state and logs live in `$XDG_STATE_HOME/branchgraph/<repo
id>/` (default `~/.local/state`), written atomically.
## Data
| Data | Source | Bound |
| --- | --- | --- |
| Branch stat | Tree diff of merge base and tip, line counts per file | 1 000 files; blobs over 1 MB counted as changed, not diffed |
| File diff | Blob diff, every file loads with the page | 200 KB for the pane; 2 MB for one file loaded alone |
| Queue rank | Seen commits (state dir), conflicts, PR state | — |
| Run output | Child process | 2 000 lines per run, one run per branch |
| Agent job | Child process | Last job per branch, 2 000 lines |
## Scope of this milestone
In: the one-screen review (rail, stat, diff, drawer, send sheet), comments and
brief, implicit worktrees with copy and setup, Run, Send through the command
template, the commit graph in the rail, the CLI subcommands and the skill
file.
Not yet (owner, 2026-10-08): posting reviews to GitHub. Later: replies from
the agent inline, several agent targets per branch, `n`/`p` hunk keys.