opencode-gbuild 0.8.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -35,4 +35,8 @@ MATCH (a:Acceptance)<-[:SATISFIES]-(n:GbuildNode) RETURN a.id, collect(n.slug)
35
35
 
36
36
  -- nodes that needed a repair pass
37
37
  MATCH (n:GbuildNode)-[:REVIEWED]->(r:Review {verdict: 'fail'}) RETURN n.slug, count(r) AS failed_reviews
38
+
39
+ -- notes by topic, newest first (topics: setup, testing, conventions, gotcha)
40
+ MATCH (nt:Note) WHERE nt.topic IN ['setup'] RETURN nt.topic AS topic, nt.text AS text, nt.by_node AS by
41
+ ORDER BY nt.at DESC, text LIMIT 20
38
42
  ```
@@ -7,9 +7,9 @@ Plan the work described in `$ARGUMENTS` as a graph.
7
7
  ## The loop
8
8
 
9
9
  The intended workflow: plan in ordinary conversation first, then chart that plan here. After this
10
- skill: `/gbuild:run <slug>` once — it iterates waves until the graph completes. Then
11
- `/gbuild:review <slug>`; if it finds issues, reopen with `/gbuild:plan <slug> --add "<fix>"` and
12
- `/gbuild:run` again; if clean, `/gbuild:pr <slug>`.
10
+ skill: `/gbuild:run <slug>` once — it starts each node as its dependencies complete, until the graph
11
+ completes. Then `/gbuild:review <slug>`; if it finds issues, reopen with
12
+ `/gbuild:plan <slug> --add "<fix>"` and `/gbuild:run` again; if clean, `/gbuild:pr <slug>`.
13
13
 
14
14
  Read `${CLAUDE_PLUGIN_ROOT}/reference/graph-format.md` (the normative graph model) and
15
15
  `${CLAUDE_PLUGIN_ROOT}/reference/cypher.md` (how to talk to the store) before doing anything, and run
@@ -117,6 +117,9 @@ For each GbuildNode decide (full model in `graph-format.md`):
117
117
 
118
118
  - `slug` — kebab-case, immutable; what skills and the store key on. `title` — what the user reads.
119
119
  - `type` — `research | decision | code | test | verify | chore`, plus the matching PascalCase label.
120
+ `research`, `decision` and `verify` nodes get no worktree and edit no files — they deliver through
121
+ their `OUTPUT` values only. Anything that must land in the repo (an ADR, a doc, a config file) belongs
122
+ to a `code` node that reads those outputs through `FROM`.
120
123
  - **`OUTPUT` fields** — every node declares at least one, each with a precise `shape`
121
124
  (`<integer>`, `<list of changed file paths>`, `<commit sha>`). Values will be scalars or lists of
122
125
  scalars; for anything nested, the shape says it is a JSON-encoded string.
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: Executes a gbuild graph — dispatches every ready-and-independent node in the current wave concurrently, each file-touching node in its own git worktree, reviews each node's output with gbuild-reviewer before merging its branch and recording it complete, and resumes only the remaining frontier on re-invocation. Use after /gbuild:plan has charted the graph.
2
+ description: Executes a gbuild graph as dataflow — each node starts the moment its own dependencies complete and runs its own implement → review → land → record pipeline in background subagents (each file-touching node in its own git worktree), handing agents only slugs and paths, recording a node complete only after gbuild-reviewer passes it, and resuming only the remaining frontier on re-invocation. Use after /gbuild:plan has charted the graph.
3
3
  ---
4
4
 
5
5
  Run the graph for the feature `$ARGUMENTS` (the slug; step 0 resolves `DB`, the store — the feature
@@ -7,21 +7,26 @@ branch lives in its own worktree, so this skill never changes the branch of the
7
7
 
8
8
  **CRITICAL: this skill must run in the main context, not inside a forked agent.** It dispatches its own
9
9
  subagents per node; a subagent cannot itself fan out further subagents reliably. It is also the store's
10
- **only writer**: subagents never run `cypherlite` — they get what they need in their prompt and report
11
- back in their final message, and this skill records it.
10
+ **only writer**: agents read their brief from the store themselves and leave their result in the inbox;
11
+ this skill passes them slugs and paths, reads their one-line replies, and records.
12
12
 
13
13
  ## Store rules
14
14
 
15
15
  - `cypherlite --version` first. If it's missing, stop: gbuild needs it
16
16
  (`curl -sSfL https://neo4j-labs.github.io/cypherlite/install.sh | bash`).
17
- - One `cypherlite` command at a time against `DB` — never parallel tool calls on it, never from a subagent.
17
+ - You are the only writer: `dispatch`, `set-status` and `record` run only here, one `cypherlite` command
18
+ at a time against `DB` — never parallel tool calls on it. Subagents may run `cypherlite` only with
19
+ `--read-only` (a read opens beside the writer); their agent files say what they read.
18
20
  - Always `--mode jsonl`: one JSON object per row, no output = no rows. Every `--param` value is JSON,
19
- single-quoted for the shell (`--param 'slugs=["a","b"]'`).
20
- - Pure reads (validate, attention, progress below) open the store `--read-only`; the writes (dispatch,
21
- set-status, record) stay writable — the CLI checkpoints a writable store on exit itself.
22
- - `storage locked` → another process holds the store: wait a few seconds, retry up to 3 times, then stop.
23
- Exit code 1 = failure; read stderr. Anything beyond the scripts below: `${CLAUDE_PLUGIN_ROOT}/reference/cypher.md`.
24
- - Never re-read what you already hold. The claimed wave's definitions stay valid for its retries.
21
+ single-quoted for the shell (`--param 'slugs=["a","b"]'`); write a `'` inside it as `\u0027`.
22
+ - Pure reads (validate, attention, progress below) open the store `--read-only`; the writes
23
+ (dispatch, set-status, record) stay writable — the CLI checkpoints a writable store on exit itself.
24
+ - `storage locked` (exit 5) → another writer holds the store: wait a few seconds, retry up to 3 times,
25
+ then stop. Exit code 1 = failure; read stderr. Anything beyond the scripts below:
26
+ `${CLAUDE_PLUGIN_ROOT}/reference/cypher.md`.
27
+ - Hold only what routes: dispatch rows, one reply line per agent, record rows. Never open a result file,
28
+ a review file or a diff, and never type acceptance text or output values — the store and the inbox
29
+ carry them.
25
30
 
26
31
  ## Step 0: Establish VCS facts, then check and recover
27
32
 
@@ -40,6 +45,8 @@ Default to `git`. Resolve these facts first, absolute paths, and reuse them thro
40
45
  4. Else → stop: there is no feature branch for `$ARGUMENTS` — chart it with `/gbuild:plan` first.
41
46
  - `DB` = `WT/db` — the feature's store, beside the worktree: machine-local, never committed (a gbuild ≤ 0.7.1
42
47
  store needs the move in `${CLAUDE_PLUGIN_ROOT}/reference/cypher.md`).
48
+ - `INBOX` = `WT/inbox` — beside `DB`, outside every worktree: each implementer writes `<slug>.json`
49
+ there and each reviewer `<slug>.review.json`; only `record.cypher` reads them, on your behalf.
43
50
 
44
51
  Then check and recover:
45
52
 
@@ -61,30 +68,40 @@ cypherlite query --read-only --database DB --mode jsonl < ${CLAUDE_PLUGIN_ROOT}/
61
68
  < ${CLAUDE_PLUGIN_ROOT}/cypher/set-status.cypher
62
69
  ```
63
70
 
64
- - Sweep the worktree root `WT` for leaks from an interruption that struck mid-cleanup: any worktree
65
- under `WT` — **except `FT` itself (`WT/tree`)** — whose slug is *not* among the slugs just reset does
66
- not belong to live work — remove it
67
- (`git worktree remove --force`) and delete its branch (`git branch -D`). An unmerged branch is
68
- unaccepted work, which must never land — that is what the isolation is for.
71
+ - Sweep the worktree root `WT` for leaks from an interruption that struck mid-cleanup: any registered
72
+ worktree under `WT` (`git worktree list --porcelain` — `db/` and `inbox/` are not worktrees) — **except
73
+ `FT` itself (`WT/tree`)** — whose slug is *not* among the slugs just reset does not belong to live
74
+ work — remove it (`git worktree remove --force`) and delete its branch (`git branch -D`). An unmerged
75
+ branch is unaccepted work, which must never land — that is what the isolation is for.
76
+ - Create the inbox empty. This discards the files of the nodes just reset — an interrupted attempt is
77
+ never recorded; they re-dispatch from scratch on top of their surviving worktrees:
78
+
79
+ ```
80
+ mkdir -p WT/inbox
81
+ find WT/inbox -type f -delete
82
+ ```
69
83
 
70
84
  ## Step 1: Pick a backend
71
85
 
72
86
  **Workflow tool**, if this session has opted into multi-agent orchestration (ultracode, or the user
73
- asked for a workflow explicitly) — translate each claimed wave into a `pipeline()`/`parallel()` script:
74
- each node is an `agent()` implement call, immediately followed by a `gbuild-reviewer` `agent()` review
75
- call scoped to that node's outputs + `acceptance`, using `schema` for the pass/fail verdict. Every
76
- file-touching (`code`/`test`/`chore`) node gets `isolation: 'worktree'`.
77
- Controlled-cycle clusters become a bounded `while` loop per `${CLAUDE_PLUGIN_ROOT}/reference/shapes.md`'s
78
- round cap. Route `model_tier: strong` nodes via `opts.model` where the harness supports an override. The
79
- script never touches the store: collect the results, land each accepted node's branch from the main
80
- context (step 2.4), then record (step 2.5).
81
-
82
- **Fallback** (default — no opt-in required, and always what step 2 below assumes): fire one `Agent`
83
- tool call per claimed node, **all in a single message** — this is what makes the fan-out real rather
84
- than claimed. Do not dispatch them one at a time across separate messages; that's exactly the serial
85
- behavior this plugin replaces.
86
-
87
- ## Step 2: One wave
87
+ asked for a workflow explicitly) — one `pipeline()` per claimed node: its implement `agent()`, then its
88
+ `gbuild-reviewer` `agent()`, prompted exactly as in step 2.2 and read as in step 2.3, so a review starts
89
+ the moment its implementer finishes and no node waits on another. Nodes use the worktrees step 2.2
90
+ creates (no `isolation` option: the prompt names the worktree). Controlled-cycle clusters become a
91
+ bounded `while` loop per `${CLAUDE_PLUGIN_ROOT}/reference/shapes.md`'s round cap. Route
92
+ `model_tier: strong` nodes via `opts.model` where the harness supports an override. The script never
93
+ touches the store: as each pipeline returns, land and record that node from the main context (steps
94
+ 2.4–2.6), then claim again (step 3) and start pipelines for the new rows.
95
+
96
+ **Fallback** (default — no opt-in required, and always what step 2 below assumes): one background
97
+ subagent call per agent. When several nodes become ready at once, dispatch them all in a single
98
+ message — never one per message, which is exactly the serial behavior this plugin replaces.
99
+
100
+ ## Step 2: One node's pipeline
101
+
102
+ Every claimed node runs this pipeline on its own — implement → review → land → record — and different
103
+ nodes are at different steps at the same time. Handle each completion the moment it arrives; never hold
104
+ a node back for another.
88
105
 
89
106
  1. **CLAIM.**
90
107
 
@@ -92,103 +109,122 @@ behavior this plugin replaces.
92
109
  cypherlite query --database DB --mode jsonl < ${CLAUDE_PLUGIN_ROOT}/cypher/dispatch.cypher
93
110
  ```
94
111
 
95
- It moves the whole frontier to `in_progress` and prints one row per claimed node with everything its
96
- agent needs: `slug`, `title`, `type`, `acceptance`, `failure_policy`, `model_tier`, `verify`,
97
- `inputs` (an input with a `value` came from an upstream node; one without is external — its `shape`
98
- says where to find it), and `outputs`. **No rows** → nothing is ready: go to step 4.
112
+ It moves every pending node whose dependencies are all completed or cancelled to `in_progress` and
113
+ prints one routing row per claimed node: `slug`, `type`, `model_tier`, `failure_policy`. That is all
114
+ you need — agents read the rest themselves. **No rows** → nothing newly ready (step 3).
99
115
 
100
- 2. **IMPLEMENT.** First, give every claimed `code`/`test`/`chore` node its own worktree — one shell
101
- loop over the wave's slugs, idempotent so an interrupted or re-dispatched node keeps its earlier
102
- work (`research`/`decision` nodes touch no files and get no worktree):
116
+ 2. **IMPLEMENT.** A `code`/`test`/`chore` node first gets its own worktree (`research`/`decision`/`verify`
117
+ nodes edit no files and get none) — idempotent, so a reset or re-dispatched node keeps its earlier work:
103
118
 
104
119
  ```
105
- mkdir -p WT
106
120
  git worktree list --porcelain | grep -q "^worktree .*/<slug>$" \
107
121
  || git worktree add WT/<slug> -b gbuild/<feature>/<slug> BRANCH 2>/dev/null \
108
122
  || git worktree add WT/<slug> gbuild/<feature>/<slug>
109
123
  ```
110
124
 
111
- (skip if the worktree already exists; else fork a fresh branch off `BRANCH` — just a ref here, the
112
- cwd doesn't matter; if the branch already
113
- exists from an earlier attempt, attach to it instead — nothing is redone). Then, in one message,
114
- spawn one `Agent` per claimed node with its row. Tell it to:
115
- - actually do the work — for `code`/`test`/`chore` nodes make the change **in its worktree**
116
- (`WT/<slug>`), never the main working tree: every edit, every check, and every git command runs
117
- there, and it commits on the node branch, message `<feature>/<slug>: <what changed>`. A worktree
118
- is a fresh checkout — if the project needs installed dependencies to build or test
119
- (`node_modules`, `.venv`, …), install or symlink them in the worktree first;
120
- - before finishing, run the checks relevant to its change — anything the node's `acceptance` names,
121
- and for `code`/`test` nodes the tests covering the files it touched — and leave them green. The
122
- per-node review will not re-run these; the full suite runs in the end-of-feature audit;
123
- - produce a value for every output field: a scalar or a list of scalars, JSON-encoding anything
124
- nested into a string;
125
- - record its own UTC start and finish (`date -u +%Y-%m-%dT%H:%M:%SZ`) and end its final message with
126
- `{"started_at": "...", "completed_at": "...", "outputs": {...}}`;
127
- - **not** run `cypherlite` or touch the store.
128
-
129
- 3. **REVIEW.** Once IMPLEMENT returns, spawn a `gbuild-reviewer` per node, again all in one message
130
- (`agents/gbuild-reviewer.md` — this plugin's own copy, gbuild does not depend on hone-ai being
131
- installed). Give it the node's row, the agent's reported outputs, and where the diff is — for a
132
- worktree'd node that is its branch, `git diff BRANCH...gbuild/<feature>/<slug>`, plus the worktree
133
- path so it can run just the checks `acceptance` names there (the worktree still exists at this
134
- point). Never let the implementing agent review its own work.
135
-
136
- 4. **LAND.** For each `pass` verdict on a `code`/`test`/`chore` node, merge its branch into the
137
- feature branch — in the feature worktree `FT`, never the user's checkout — and clean up:
125
+ (reuse an existing worktree; else fork a fresh branch off `BRANCH`, or attach the branch an earlier
126
+ attempt left — nothing is redone). Then dispatch the implementer — `Agent` tool, `subagent_type`
127
+ `gbuild-implementer` / `gbuild-reviewer` (`gbuild:`-prefixed when namespaced), description
128
+ `implement <slug>` / `review <slug>`, `run_in_background: true` — with this one prompt for both agents:
138
129
 
139
130
  ```
140
- git -C FT merge --no-ff gbuild/<feature>/<slug> \
141
- && git worktree remove --force WT/<slug> \
142
- && git branch -d gbuild/<feature>/<slug>
131
+ gbuild node `<slug>`. Store: <DB>. Worktree: <worktree>. Feature branch: <BRANCH>. Agent file: ${CLAUDE_PLUGIN_ROOT}/agents/gbuild-<implementer|reviewer>.md.
143
132
  ```
144
133
 
145
- A merge conflict means two supposedly independent nodes touched the same lines: `git -C FT merge
146
- --abort`
147
- and treat the node as review-failed, the conflict as its failed criterion — step 5's policy decides
148
- (a retry/repair re-dispatch tells the agent to rebase its branch onto `BRANCH` and resolve it).
149
- Worktrees and branches are deleted only here, after acceptance — a failed node's stays for its next
150
- attempt (step 5).
134
+ `<DB>`, `<BRANCH>` are `DB`, `BRANCH`; `<implementer|reviewer>` names the agent; `<worktree>` is
135
+ `WT/<slug>` for `code`/`test`/`chore` and `FT` (read-only) otherwise — every path absolute. The
136
+ implementer gets a model per its `model_tier` where the harness offers one
137
+ (`${CLAUDE_PLUGIN_ROOT}/reference/cost-model.md`).
138
+ Append nothing — no acceptance, inputs, outputs, notes, failed criteria or agent-file text. Retries,
139
+ repairs and fallbacks use the same prompt: the agent's brief carries the failed criteria.
140
+
141
+ 3. **REVIEW.** The implementer replies with one line; read nothing else:
142
+ - `done <slug>` and `test -s INBOX/<slug>.json` → dispatch its reviewer right away with the same
143
+ prompt naming `gbuild-reviewer.md` — a fresh agent, never the implementer.
144
+ - `done <slug>` but no file → no review; record a fail: `["implementer reported done but <slug>.json is missing"]`.
145
+ - `failed <slug>: <reason>` → no review; record a fail: `["implementer failed: <reason>"]`.
146
+
147
+ The reviewer replies `VERDICT: pass`, or `VERDICT: fail` plus one `[fail] <criterion> — <reason>`
148
+ line per failed check. It is a pass only with a line exactly `VERDICT: pass` and no `[fail]` lines;
149
+ `failed_criteria` is the text after `[fail] ` on each line, verbatim and in order. No `VERDICT` line
150
+ → re-dispatch that reviewer once in the foreground (no background flag); still none →
151
+ `["reviewer returned no verdict"]`. A `[fail] CONTRADICTS <upstream-slug>` line also hands that
152
+ upstream node to its own `failure_policy` (step 2.6). The verdict comes only from this reply — never
153
+ from `INBOX/<slug>.review.json`.
154
+
155
+ 4. **LAND.** A `pass` on a `code`/`test`/`chore` node merges its branch into the feature worktree `FT`,
156
+ never the user's checkout — one merge at a time, never two in parallel:
151
157
 
152
- 5. **RECORD** the whole wave in one call — one result per node, `verdict` from the reviewer's
153
- `VERDICT:` line, `failed_criteria` from its `[fail]` lines:
158
+ ```
159
+ git -C FT merge --no-ff gbuild/<feature>/<slug>
160
+ ```
161
+
162
+ A conflict means two supposedly independent nodes touched the same lines: list them
163
+ (`git -C FT diff --name-only --diff-filter=U`), `git -C FT merge --abort`, and record a fail instead:
164
+ `["merge conflict with BRANCH in <path>[, <path>] — rebase gbuild/<feature>/<slug> onto BRANCH and resolve"]`.
165
+
166
+ 5. **RECORD** — one call per node, as soon as its verdict is settled:
154
167
 
155
168
  ```
156
- cypherlite query --database DB --mode jsonl --param 'results=[
157
- {"slug": "<slug>", "verdict": "pass", "failed_criteria": [], "outputs": {...},
158
- "started_at": "<iso>", "completed_at": "<iso>"},
159
- {"slug": "<slug>", "verdict": "fail", "failed_criteria": ["<criterion>"]}
160
- ]' < ${CLAUDE_PLUGIN_ROOT}/cypher/record.cypher
169
+ cypherlite query --database DB --mode jsonl \
170
+ --param 'verdict={"slug":"<slug>","verdict":"pass","failed_criteria":[]}' \
171
+ --fetch-params result=INBOX/<slug>.json --fetch-params review=INBOX/<slug>.review.json \
172
+ < ${CLAUDE_PLUGIN_ROOT}/cypher/record.cypher
161
173
  ```
162
174
 
163
- Each returned row says whether that node `completed`. A slug missing from the output was not
164
- recorded (it wasn't `in_progress`) — find out why before moving on. `InvalidPropertyType` means an
165
- output value was a map; the whole batch was rejected — JSON-encode it and send the batch again.
166
-
167
- 6. **FAILURES.** For each row with `completed: false` (a failed review, an aborted merge, or a pass
168
- `record` downgraded because `missing_outputs` is non-empty), apply the node's `failure_policy`
169
- (`${CLAUDE_PLUGIN_ROOT}/reference/failure-policies.md`), using its `attempt` as the count:
170
- - `retry` / `repair` → re-dispatch implement → review with the failed criteria fed back in, reusing
171
- the row you already hold **and the node's existing worktree and branch** — the next attempt builds
172
- on them, it does not start over. Then record just those nodes. Bounded: 2 attempts.
173
- - `fallback` → dispatch the named alternate approach the same way, but remove the failed attempt's
174
- worktree and branch first (`git worktree remove --force`, `git branch -D`) — the alternate
175
- approach starts from a fresh branch off `BRANCH`.
175
+ `verdict` is all you type: `pass`, or `fail` with `failed_criteria` — the reviewer's `[fail]` lines
176
+ or one of the fixed lines in steps 2.3–2.4, never your own words. Bind `result` from its file only on
177
+ a pass (`--param 'result={}'` on every fail), and `review` from its file whenever `test -s` finds it
178
+ (else `--param 'review={}'`) — all three are always bound. record.cypher takes the outputs, times,
179
+ notes and symbols from the file itself.
180
+
181
+ No row, or another slug → it wasn't recorded (the node wasn't `in_progress`) — find out why. Exit 1
182
+ or 6 → re-run once with `--param 'review={}'`; still failing → re-run with `--param 'result={}'
183
+ --param 'review={}'` and verdict `fail`,
184
+ `["result file rejected by record.cypher: <error.kind>: <error.message, first 120 chars>"]`, which
185
+ can't fail. Then `rm -f INBOX/<slug>.*` and:
186
+ - `completed: true` → for a worktree'd node `git worktree remove --force WT/<slug>` and
187
+ `git branch -d gbuild/<feature>/<slug>` — only now, so a pass downgraded after its merge repairs in
188
+ place. Non-empty `unselected` (a router's unchosen branches) → cancel them with
189
+ `set-status.cypher`. Then claim again (step 3).
190
+ - `completed: false` (a failed review, a merge conflict, a failed implementer, or a pass downgraded
191
+ because `missing_outputs` is non-empty) → step 2.6.
192
+
193
+ 6. **FAILURES.** Apply the node's `failure_policy` (`${CLAUDE_PLUGIN_ROOT}/reference/failure-policies.md`),
194
+ using its `attempt` as the count:
195
+ - `retry` / `repair` → back to step 2.2 with the same prompt, reusing the node's existing worktree
196
+ and branch — the next attempt builds on them, it does not start over. Bounded: 2 attempts.
197
+ - `fallback` → remove the failed attempt's worktree and branch first (`git worktree remove --force`,
198
+ `git branch -D`), then step 2.2 — a fresh branch off `BRANCH`; the brief names the alternate
199
+ approach.
176
200
  - `skip` → `cancelled`; `escalate` (or an exhausted retry/repair) → `failed`, its dependents stay
177
- blocked while siblings elsewhere keep going; `stop` → `failed` and halt the entire run. One
178
- `set-status.cypher` call per status, with every slug that gets it. A node reaching a terminal
179
- status gets cleaned up: `git worktree remove --force WT/<slug>` and
201
+ blocked while everything else keeps going; `stop` → `failed` and halt the run. Set it with
202
+ `set-status.cypher`, then clean up a worktree'd node: `git worktree remove --force WT/<slug>` and
180
203
  `git branch -D gbuild/<feature>/<slug>` — its work was never accepted, so it never lands.
181
- - A completed router (`decision` choosing a branch) → cancel the unselected branches with
182
- `set-status.cypher` before the next claim.
183
204
 
184
- Nodes in the same wave never read each other's outputs, and every file-touching node works in its own
185
- worktree, so true concurrent dispatch is safe by construction — the store calls and the merges in `FT`
186
- are the only serialized points, and since `FT` belongs to this feature alone, other features (or the
187
- user) changing branches in their own checkouts cannot race them.
205
+ Then claim again (step 3) — except after `stop`: claim nothing more, finish the pipelines in flight,
206
+ then step 4.
207
+
208
+ ## Step 3: Claim again
209
+
210
+ After every `record.cypher` and `set-status.cypher` call, run dispatch.cypher (step 2.1) again and start
211
+ step 2.2 for every row it returns. That is the dataflow: a node starts the moment its own dependencies
212
+ complete, while unrelated slower nodes are still running. Between claims, wait for completion
213
+ notifications and handle each as it arrives.
214
+
215
+ A node is claimed only once everything it depends on has completed, so nodes in flight together never
216
+ read each other's outputs, and every file-touching node works in its own worktree — concurrency is safe
217
+ by construction. Your store calls and the merges into `FT` are the only serialized points; `FT` belongs
218
+ to this feature alone, so no other checkout can race them.
188
219
 
189
- ## Step 3: Next wave
220
+ **Lost notifications.** Claude Code can drop a completion notification when 3 or more background agents
221
+ run at once. When an agent stays silent well past ones started after it, or you wake with nothing new to
222
+ handle, check instead of waiting: an implementer whose `INBOX/<slug>.json` exists counts as
223
+ `done <slug>` (it writes `.tmp` then renames, so presence means complete); otherwise, and for a
224
+ reviewer, fetch the reply by task id (`TaskOutput`) where the harness offers it. A reviewer whose reply
225
+ still can't be had is re-dispatched once in the foreground (step 2.3). Never take a verdict from a file.
190
226
 
191
- Claim again (step 2). Keep going until a claim returns no rows.
227
+ Stop when a claim returns no rows and no agent is in flight: step 4.
192
228
 
193
229
  ## Step 4: Report and stop
194
230
 
@@ -211,6 +247,7 @@ Report: which nodes ran this invocation, which passed review outright vs. needed
211
247
  ## Resuming
212
248
 
213
249
  Re-invoking `/gbuild:run <feature>` after an interruption re-runs step 0 fresh: the feature worktree is
214
- resolved or re-attached, nodes an interrupted wave left `in_progress` are reset (their worktrees and any
215
- commits on their branches survive — the re-dispatch reuses them), and the next claim picks up exactly
216
- the remaining frontier. Nothing needs to be told what already finished — the store is the memory.
250
+ resolved or re-attached, nodes an interrupted run left `in_progress` are reset (their worktrees and any
251
+ commits on their branches survive — the re-dispatch reuses them; their inbox files are discarded), and
252
+ the next claim picks up exactly the remaining frontier. Nothing needs to be told what already finished —
253
+ the store is the memory.
@@ -14,9 +14,9 @@ the feature is gone (or was never charted here); say so.
14
14
  (`curl -sSfL https://neo4j-labs.github.io/cypherlite/install.sh | bash`).
15
15
  - One `cypherlite` command at a time — never parallel tool calls on the same store.
16
16
  - Always `--read-only`: status never writes, and a missing store fails fast instead of being created.
17
+ It takes no lock, so it reads beside a running `/gbuild:run`.
17
18
  - Always `--mode jsonl`: one JSON object per row, no output = no rows.
18
- - `storage locked` → another process (likely a running `/gbuild:run`) holds the store: wait a few
19
- seconds, retry up to 3 times, then say so. Anything else: `${CLAUDE_PLUGIN_ROOT}/reference/cypher.md`.
19
+ - Any error: `${CLAUDE_PLUGIN_ROOT}/reference/cypher.md`.
20
20
 
21
21
  ## Mode
22
22
 
@@ -38,7 +38,7 @@ cypherlite query --read-only --database DB --mode jsonl < ${CLAUDE_PLUGIN_ROOT}/
38
38
 
39
39
  One call after the other. `status.cypher` gives one row per node with its derived `state`
40
40
  (`frontier` / `blocked` / `in_progress` / `completed` / `cancelled` / `failed`), `wave`, `waiting_on`,
41
- review summary and timestamps — don't re-derive any of these by hand.
41
+ review summary, timestamps and `last_dependency_at` — don't re-derive any of these by hand.
42
42
 
43
43
  ### 2. Render the graph
44
44
 
@@ -61,16 +61,24 @@ attempts. For `failed` nodes, `last_failed_criteria` verbatim — this is what a
61
61
 
62
62
  ### 4. Concurrency evidence
63
63
 
64
- For each wave with more than one completed node, compare the implementing agents' `started_at` /
65
- `completed_at` intervals. Report whether they actually overlapped (real concurrent dispatch) or ran
66
- back-to-back despite being in the same wave (a sign the fallback backend was invoked one node at a time —
67
- a bug in how `run` was driven, not a graph problem).
64
+ `run` schedules as dataflow: a node is claimed the moment its last dependency completes, not when its
65
+ wave does. Nodes sharing a `wave` need not overlap — a short chain can finish before a slow node beside
66
+ it — so don't judge by overlap; check dispatch timing instead, from the `status.cypher` rows of step 1.
67
+
68
+ For each node with both `started_at` and `last_dependency_at` (the latest `completed_at` among its
69
+ completed dependencies), report the gap from `last_dependency_at` to `started_at`. A few minutes is
70
+ the dependency's review, merge and record — dataflow working. A much longer gap, e.g. a start that
71
+ waited for an unrelated slower node to complete, means the node sat ready while something else held it
72
+ back (`run` driven wave by wave, or a lost completion notification) — a bug in how `run` was driven,
73
+ not a graph problem. A node with repair attempts (`review_attempts` > 1) started late by design: its
74
+ times are the passing attempt's. Nodes with no dependencies should have started together at the
75
+ beginning of the run.
68
76
 
69
77
  ### 5. Next action
70
78
 
71
79
  One priority-ordered list: what `/gbuild:run <feature>` would do next (the current frontier), and
72
80
  anything needing a human decision first (`failed` nodes, an empty frontier with incomplete nodes
73
- remaining, `in_progress` nodes when no run is active — an interrupted wave `run` will reset).
81
+ remaining, `in_progress` nodes when no run is active — interrupted work `run` will reset).
74
82
 
75
83
  ### 6. Invariant checks
76
84