opencode-gbuild 0.6.0 → 0.6.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.
package/README.md CHANGED
@@ -186,11 +186,16 @@ Every skill reads and writes the graph with `cypherlite` and nothing else — th
186
186
  direct JSON editing. The plugin ships the queries as `.cypher` files; skills pipe them into the store:
187
187
 
188
188
  ```bash
189
- cypherlite .gbuild/<feature>/db -json < plugins/gbuild/cypher/validate.cypher # format gate: [] = valid
190
- cypherlite .gbuild/<feature>/db -json < plugins/gbuild/cypher/status.cypher # frontier/blocked/waves
191
- cypherlite .gbuild/<feature>/db -json "MATCH (n:GbuildNode) RETURN n.slug, n.status" # anything else
189
+ cypherlite .gbuild/<feature>/db --mode jsonl < plugins/gbuild/cypher/validate.cypher # format gate: no output = valid
190
+ cypherlite .gbuild/<feature>/db --mode jsonl < plugins/gbuild/cypher/progress.cypher # one row per wave: counts + open nodes
191
+ cypherlite .gbuild/<feature>/db --mode jsonl "MATCH (n:GbuildNode {status: 'failed'}) RETURN n.slug" # anything else
192
192
  ```
193
193
 
194
+ Reads are deliberately narrow so a run doesn't pay for the whole graph on every step: `run` claims a
195
+ wave (`dispatch.cypher` returns only that wave's nodes, with their inputs resolved) and records it
196
+ (`record.cypher`, one batched call) — two store calls per wave, regardless of graph size. The full
197
+ per-node view (`status.cypher`) is only for the human-facing `/gbuild:status`.
198
+
194
199
  Required properties and their types are engine constraints, so an invalid write never lands; the rest of
195
200
  the contract (enums, edge shapes, coverage, acyclicity, the cut test) is `validate.cypher`. CypherLite
196
201
  locks a store while a process has it open, so only the main-context skill touches it, one command at a
@@ -198,7 +203,8 @@ time — subagents get their inputs in their prompt and report back. `reference/
198
203
  operating rules.
199
204
 
200
205
  Tests (repo-root `npm test`) run the Cypher layer against the real binary: `cypher.test.ts` drives the
201
- worked example through a full run and checks every validator rule and write guard.
206
+ worked example through a full run, checks every validator rule and write guard, and holds a token budget
207
+ — a 40-node run must take two store calls per wave and stay under a fixed number of bytes read.
202
208
 
203
209
  ## Layout
204
210
 
@@ -206,8 +212,8 @@ worked example through a full run and checks every validator rule and write guar
206
212
  plugins/gbuild/
207
213
  agents/gbuild-reviewer.md # per-node review, ported from hone-ai's reviewer, never self-review
208
214
  agents/gbuild-auditor.md # end-of-branch maintainability audit, ported from hone-ai's auditor
209
- reference/ # graph-format.md (normative model) + cypher.md (how to talk to the store); shapes/failure-policies/cost-model/checklist inform plan
210
- cypher/ # schema, validate, status/node/feature reads, and run's write scripts
215
+ reference/ # graph-format.md (normative model), cypher.md (how to talk to the store), queries.md (ad-hoc recipes); shapes/failure-policies/cost-model/checklist inform plan
216
+ cypher/ # schema, validate, progress/attention/status/node/feature reads, and run's dispatch/record/set-status
211
217
  templates/example.cypher # a worked 4-node diamond, written the way plan writes a feature
212
218
  skills/{plan,run,status,review,pr}/
213
219
  index.ts # OpenCode plugin entry: registers the skills + /gbuild-* commands, translating paths/names
@@ -7,7 +7,7 @@ description: Strict end-of-feature maintainability auditor for a gbuild feature
7
7
 
8
8
  Unusually strict review of implementation quality, maintainability, and abstraction health. Push for **ambitious** restructurings — "code judo" moves that preserve behavior while making the code dramatically simpler, smaller, and more direct. Delete complexity; don't rearrange it.
9
9
 
10
- The caller passes you the resolved feature slug and the feature context it read from the gbuild store: the `destination`, the acceptance bar (each criterion with the nodes covering it), and every node's recorded output values. Use it for scoping context only — you are auditing the branch's code, not re-grading individual nodes (`gbuild-reviewer` already did that per node, against each node's own contract; your job is the thing no per-node pass can see — what the accumulated diff did to the codebase as a whole). Do not open `.gbuild/` or run `cypherlite` — the caller owns the store. Do NOT write any file — output your audit as your final message exclusively.
10
+ The caller passes you the resolved feature slug and the feature context it read from the gbuild store: the `destination` and the acceptance bar (each criterion with the nodes covering it). Use it for scoping context only — you are auditing the branch's code, not re-grading individual nodes (`gbuild-reviewer` already did that per node, against each node's own contract; your job is the thing no per-node pass can see — what the accumulated diff did to the codebase as a whole). Do not open `.gbuild/` or run `cypherlite` — the caller owns the store. Do NOT write any file — output your audit as your final message exclusively.
11
11
 
12
12
  ## Core Prompt
13
13
 
@@ -0,0 +1,20 @@
1
+ // Only the nodes that need a decision — no rows means nothing does.
2
+ // failed escalated or exhausted; `detail` = the last review's failed criteria
3
+ // in_progress claimed but not recorded; if no run is active, an interrupted wave left it
4
+ // blocked-by-failed pending behind a failed node, so it can never unblock on its own;
5
+ // `detail` = the failed nodes it waits on
6
+ // cypherlite .gbuild/<slug>/db --mode jsonl < cypher/attention.cypher
7
+ CALL {
8
+ MATCH (n:GbuildNode {status: 'failed'})
9
+ RETURN n.slug AS slug, 'failed' AS reason,
10
+ COLLECT { MATCH (n)-[:REVIEWED]->(r:Review) WITH r ORDER BY r.attempt DESC LIMIT 1 UNWIND r.failed_criteria AS c RETURN c } AS detail
11
+ UNION ALL
12
+ MATCH (n:GbuildNode {status: 'in_progress'})
13
+ RETURN n.slug AS slug, 'in_progress' AS reason, [] AS detail
14
+ UNION ALL
15
+ MATCH (n:GbuildNode {status: 'pending'})-[:DEPENDS_ON*]->(f:GbuildNode {status: 'failed'})
16
+ WITH n, f ORDER BY f.slug
17
+ RETURN n.slug AS slug, 'blocked-by-failed' AS reason, collect(DISTINCT f.slug) AS detail
18
+ }
19
+ RETURN slug, reason, detail
20
+ ORDER BY reason, slug;
@@ -1,13 +1,31 @@
1
- // Mark frontier nodes in_progress just before run fans them out. Only nodes
2
- // that are pending AND whose every DEPENDS_ON target is completed or cancelled
3
- // are moved; anything else is silently skipped, so compare `dispatched`
4
- // against what you asked for.
5
- // cypherlite .gbuild/<slug>/db -json --param 'slugs=["a","b"]' < cypher/dispatch.cypher
6
- UNWIND $slugs AS s
7
- MATCH (n:GbuildNode {slug: s})
8
- WHERE n.status = 'pending'
9
- AND NOT EXISTS { MATCH (n)-[:DEPENDS_ON]->(d:GbuildNode) WHERE NOT d.status IN ['completed', 'cancelled'] }
1
+ // Claim the next wave: move every frontier node (pending, every DEPENDS_ON
2
+ // target completed or cancelled) to in_progress, and return exactly what its
3
+ // implementing agent needs — nothing about the rest of the graph. An input
4
+ // with a `value` was read from an upstream node; one with a null value is
5
+ // external, and its `shape` says where to find it. No rows = nothing is
6
+ // ready (the graph is done or stuck — ask progress.cypher / attention.cypher).
7
+ // For a router, cancel the unselected branches with set-status.cypher first.
8
+ // cypherlite .gbuild/<slug>/db --mode jsonl < cypher/dispatch.cypher
9
+ MATCH (n:GbuildNode {status: 'pending'})
10
+ WHERE NOT EXISTS { MATCH (n)-[:DEPENDS_ON]->(d:GbuildNode) WHERE NOT d.status IN ['completed', 'cancelled'] }
10
11
  SET n.status = 'in_progress'
11
- RETURN n.slug AS dispatched
12
- ORDER BY dispatched;
12
+ RETURN n.slug AS slug,
13
+ n.title AS title,
14
+ n.type AS type,
15
+ n.acceptance AS acceptance,
16
+ n.failure_policy AS failure_policy,
17
+ n.model_tier AS model_tier,
18
+ n.verify AS verify,
19
+ COLLECT {
20
+ MATCH (n)-[:INPUT]->(i:Field)
21
+ OPTIONAL MATCH (i)-[:FROM]->(o:Field)
22
+ WITH i, o ORDER BY i.name
23
+ RETURN {name: i.name, shape: i.shape, value: o.value}
24
+ } AS inputs,
25
+ COLLECT {
26
+ MATCH (n)-[:OUTPUT]->(f:Field)
27
+ WITH f ORDER BY f.name
28
+ RETURN {name: f.name, shape: f.shape}
29
+ } AS outputs
30
+ ORDER BY slug;
13
31
  .checkpoint
@@ -1,7 +1,8 @@
1
- // Feature-level context: destination, the acceptance bar with the nodes that
2
- // cover each criterion, and every node's recorded output values. What review
3
- // hands the auditor and what pr builds the description from.
4
- // cypherlite .gbuild/<slug>/db -json < cypher/feature.cypher
1
+ // Feature-level context: the destination and the acceptance bar, with the
2
+ // nodes that cover each criterion. What review hands the auditor and what pr
3
+ // builds the description from. Per-node output values are not included — get
4
+ // one node's with node.cypher if you actually need it.
5
+ // cypherlite .gbuild/<slug>/db --mode jsonl < cypher/feature.cypher
5
6
  MATCH (f:Feature)
6
7
  RETURN f.feature AS feature,
7
8
  f.destination AS destination,
@@ -12,10 +13,4 @@ RETURN f.feature AS feature,
12
13
  WITH a ORDER BY a.id
13
14
  RETURN {id: a.id, text: a.text,
14
15
  covered_by: COLLECT { MATCH (a)<-[:SATISFIES]-(n:GbuildNode) WITH n ORDER BY n.slug RETURN n.slug }}
15
- } AS acceptance,
16
- COLLECT {
17
- MATCH (n:GbuildNode)
18
- WITH n ORDER BY n.slug
19
- RETURN {slug: n.slug, title: n.title, status: n.status,
20
- outputs: COLLECT { MATCH (n)-[:OUTPUT]->(o:Field) WITH o ORDER BY o.name RETURN {name: o.name, value: o.value} }}
21
- } AS nodes;
16
+ } AS acceptance;
@@ -1,7 +1,9 @@
1
1
  // Everything needed to dispatch or review one node, with inputs resolved
2
2
  // through FROM edges to the upstream output values. An input with no FROM is
3
3
  // external (from the codebase / environment): its `from` and `value` are null.
4
- // cypherlite .gbuild/<slug>/db -json --param slug=<node-slug> < cypher/node.cypher
4
+ // Used for reopen and ad-hoc inspection; run gets its definitions from
5
+ // dispatch.cypher instead.
6
+ // cypherlite .gbuild/<slug>/db --mode jsonl --param 'slug="<node-slug>"' < cypher/node.cypher
5
7
  MATCH (n:GbuildNode {slug: $slug})
6
8
  RETURN n.slug AS slug,
7
9
  n.title AS title,
@@ -0,0 +1,25 @@
1
+ // One compact row per wave: node counts by state, and `open` — the slugs in
2
+ // that wave not yet completed or cancelled. A wave with `open: []` is done;
3
+ // the graph is done when every row's `open` is empty. `wave` is the longest
4
+ // DEPENDS_ON chain below a node (0 = no dependencies).
5
+ // cypherlite .gbuild/<slug>/db --mode jsonl < cypher/progress.cypher
6
+ MATCH (n:GbuildNode)
7
+ OPTIONAL MATCH p = (n)-[:DEPENDS_ON*]->()
8
+ WITH n, coalesce(max(length(p)), 0) AS wave
9
+ WITH n, wave,
10
+ CASE
11
+ WHEN n.status <> 'pending' THEN n.status
12
+ WHEN EXISTS { MATCH (n)-[:DEPENDS_ON]->(d:GbuildNode) WHERE NOT d.status IN ['completed', 'cancelled'] } THEN 'blocked'
13
+ ELSE 'frontier'
14
+ END AS state
15
+ ORDER BY n.slug
16
+ RETURN wave,
17
+ count(*) AS total,
18
+ sum(CASE WHEN state = 'completed' THEN 1 ELSE 0 END) AS completed,
19
+ sum(CASE WHEN state = 'cancelled' THEN 1 ELSE 0 END) AS cancelled,
20
+ sum(CASE WHEN state = 'in_progress' THEN 1 ELSE 0 END) AS in_progress,
21
+ sum(CASE WHEN state = 'failed' THEN 1 ELSE 0 END) AS failed,
22
+ sum(CASE WHEN state = 'frontier' THEN 1 ELSE 0 END) AS frontier,
23
+ sum(CASE WHEN state = 'blocked' THEN 1 ELSE 0 END) AS blocked,
24
+ [s IN collect(CASE WHEN NOT state IN ['completed', 'cancelled'] THEN n.slug END) WHERE s IS NOT NULL] AS open
25
+ ORDER BY wave;
@@ -0,0 +1,38 @@
1
+ // Record a whole wave's review verdicts in one atomic call. Each result adds a
2
+ // Review (attempt = previous reviews + 1). A pass with a value for every
3
+ // OUTPUT field also completes the node: output values stored on its fields,
4
+ // the implementing agent's own times on the node. A pass missing outputs is
5
+ // recorded as a fail listing them, so the review history stays truthful.
6
+ // Only in_progress nodes are recorded: compare the returned slugs with what
7
+ // you sent. Output values must be scalars or lists of scalars — one nested
8
+ // value rejects the whole batch (InvalidPropertyType); JSON-encode it and
9
+ // resend.
10
+ // cypherlite .gbuild/<slug>/db --mode jsonl --param 'results=[
11
+ // {"slug": "a", "verdict": "pass", "failed_criteria": [], "outputs": {"value": 7},
12
+ // "started_at": "<iso>", "completed_at": "<iso>"},
13
+ // {"slug": "b", "verdict": "fail", "failed_criteria": ["<criterion>"]}
14
+ // ]' < cypher/record.cypher
15
+ UNWIND $results AS r
16
+ MATCH (n:GbuildNode {slug: r.slug})
17
+ WHERE n.status = 'in_progress' AND r.verdict IN ['pass', 'fail']
18
+ WITH n, r,
19
+ COUNT { (n)-[:REVIEWED]->(:Review) } + 1 AS attempt,
20
+ CASE WHEN r.verdict = 'pass'
21
+ THEN [k IN COLLECT { MATCH (n)-[:OUTPUT]->(f:Field) RETURN f.name } WHERE r.outputs[k] IS NULL]
22
+ ELSE [] END AS missing
23
+ WITH n, r, attempt, missing, (r.verdict = 'pass' AND size(missing) = 0) AS ok
24
+ CREATE (n)-[:REVIEWED]->(review:Review {
25
+ attempt: attempt,
26
+ verdict: CASE WHEN ok THEN 'pass' ELSE 'fail' END,
27
+ failed_criteria: coalesce(r.failed_criteria, []) + [k IN missing | 'missing output value: ' + k],
28
+ at: toString(datetime())
29
+ })
30
+ WITH n, r, attempt, missing, ok, review
31
+ OPTIONAL MATCH (n)-[:OUTPUT]->(f:Field) WHERE ok
32
+ SET f.value = r.outputs[f.name]
33
+ WITH DISTINCT n, r, attempt, missing, ok, review
34
+ FOREACH (_ IN CASE WHEN ok THEN [1] ELSE [] END |
35
+ SET n.status = 'completed', n.started_at = r.started_at, n.completed_at = r.completed_at)
36
+ RETURN n.slug AS slug, attempt, review.verdict AS verdict, ok AS completed, missing AS missing_outputs
37
+ ORDER BY slug;
38
+ .checkpoint
@@ -1,11 +1,14 @@
1
- // Move a node to a non-completed status: `cancelled` (skip policy), `failed`
2
- // (escalate, or an exhausted retry/repair), or `pending` (reset a node an
3
- // interrupted run left in_progress). `completed` is rejected here — only
4
- // complete.cypher may set it, so it can enforce the review and outputs.
5
- // cypherlite .gbuild/<slug>/db -json --param slug=<node-slug> --param status=cancelled \
1
+ // Move nodes to a non-completed status in one call: `cancelled` (skip policy,
2
+ // or a router's unselected branches), `failed` (escalate, an exhausted
3
+ // retry/repair, stop), or `pending` (reset nodes an interrupted run left
4
+ // in_progress). `completed` is rejected — only record.cypher may set it, so it
5
+ // can require a passing review and every output value.
6
+ // cypherlite .gbuild/<slug>/db --mode jsonl --param 'slugs=["a","b"]' --param 'status="cancelled"' \
6
7
  // < cypher/set-status.cypher
7
- MATCH (n:GbuildNode {slug: $slug})
8
+ UNWIND $slugs AS s
9
+ MATCH (n:GbuildNode {slug: s})
8
10
  WHERE $status IN ['pending', 'in_progress', 'failed', 'cancelled']
9
11
  SET n.status = $status
10
- RETURN n.slug AS slug, n.status AS status;
12
+ RETURN n.slug AS slug, n.status AS status
13
+ ORDER BY slug;
11
14
  .checkpoint
@@ -4,7 +4,7 @@
4
4
  // otherwise = the stored status (in_progress, completed, failed, cancelled)
5
5
  // `wave` is the longest DEPENDS_ON chain below the node (0 = no dependencies),
6
6
  // so nodes sharing a wave are safe to run concurrently.
7
- // cypherlite .gbuild/<slug>/db -json < cypher/status.cypher
7
+ // cypherlite .gbuild/<slug>/db --mode jsonl < cypher/status.cypher
8
8
  MATCH (n:GbuildNode)
9
9
  OPTIONAL MATCH p = (n)-[:DEPENDS_ON*]->()
10
10
  WITH n, coalesce(max(length(p)), 0) AS wave
@@ -1,5 +1,5 @@
1
1
  // gbuild format gate. Zero rows = valid; every row is one violation.
2
- // cypherlite .gbuild/<slug>/db -json < cypher/validate.cypher
2
+ // cypherlite .gbuild/<slug>/db --mode jsonl < cypher/validate.cypher
3
3
  // Required properties, their types and slug/acceptance-id uniqueness are
4
4
  // engine constraints (schema.cypher) — a write that breaks them never lands.
5
5
  // This query checks everything the engine can't: enums, label/type agreement,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "opencode-gbuild",
4
- "version": "0.6.0",
4
+ "version": "0.6.1",
5
5
  "description": "OpenCode plugin for gbuild — graph-native task planning and execution with concurrent fan-out, per-node review, and durable checkpoints.",
6
6
  "type": "module",
7
7
  "license": "MIT",
@@ -1,8 +1,8 @@
1
1
  # Talking to the gbuild store
2
2
 
3
3
  Every gbuild skill reads and writes the feature store (`.gbuild/<feature>/db/`, modelled in
4
- `reference/graph-format.md`) with the `cypherlite` CLI and nothing else. This file is the operating
5
- manual; follow it exactly.
4
+ `reference/graph-format.md`) with the `cypherlite` CLI and nothing else. This file is the full operating
5
+ manual; the skills that only run plugin scripts carry the short version of these rules inline.
6
6
 
7
7
  ## Requirement
8
8
 
@@ -31,17 +31,18 @@ or writer — fails with `storage locked` (exit 1). So:
31
31
 
32
32
  ## Invocation
33
33
 
34
- Always pass `-json` when you need the result. `DB` below is `.gbuild/<feature>/db`.
34
+ Always pass `--mode jsonl`: one compact JSON object per row, one row per line, and no output at all when
35
+ there are no rows. `DB` below is `.gbuild/<feature>/db`.
35
36
 
36
37
  ```
37
38
  # a plugin script
38
- cypherlite DB -json < ${CLAUDE_PLUGIN_ROOT}/cypher/status.cypher
39
+ cypherlite DB --mode jsonl < ${CLAUDE_PLUGIN_ROOT}/cypher/progress.cypher
39
40
 
40
41
  # a plugin script with parameters
41
- cypherlite DB -json --param 'slug="b-consume-doubled"' < ${CLAUDE_PLUGIN_ROOT}/cypher/node.cypher
42
+ cypherlite DB --mode jsonl --param 'slug="b-consume-doubled"' < ${CLAUDE_PLUGIN_ROOT}/cypher/node.cypher
42
43
 
43
- # an ad-hoc read
44
- cypherlite DB -json "MATCH (n:GbuildNode) RETURN n.slug, n.status ORDER BY n.slug LIMIT 100"
44
+ # an ad-hoc read — return only the columns you need, and keep a LIMIT on anything that can grow
45
+ cypherlite DB --mode jsonl "MATCH (n:GbuildNode {status: 'failed'}) RETURN n.slug LIMIT 20"
45
46
 
46
47
  # a write you author (plan, reopen, fixes) — quoted heredoc delimiter, so the shell expands nothing
47
48
  cypherlite DB <<'CYPHER'
@@ -54,10 +55,12 @@ CYPHER
54
55
 
55
56
  ## Rules
56
57
 
58
+ - **Read narrowly.** Prefer the smallest script that answers the question — `progress` for "how far
59
+ along / is it done", `attention` for "what needs a decision", `dispatch` for "what runs next". The full
60
+ per-node `status` is for the human-facing `/gbuild:status` report, not for loops.
57
61
  - **Parameters are JSON.** Every `--param` value is JSON, single-quoted for the shell: strings in double
58
- quotes (`--param 'slug="01-scan-runners"'`), lists and objects as JSON (`--param 'slugs=["a","b"]'`,
59
- `--param 'outputs={"value": 7}'`). Bare values that start with a digit — timestamps, `01-…` slugs —
60
- are rejected, so always quote.
62
+ quotes (`--param 'slug="01-scan-runners"'`), lists and objects as JSON (`--param 'slugs=["a","b"]'`).
63
+ Bare values that start with a digit — timestamps, `01-…` slugs — are rejected, so always quote.
61
64
  - **String literals** in Cypher you author: single quotes by default; double quotes when the text
62
65
  contains an apostrophe (`"gbuild's"`); backslash-escape (`\'`, `\"`) when it contains both.
63
66
  - **Authored writes are atomic and checkpointed.** Wrap them in `.begin` / `.commit` — if any statement
@@ -65,12 +68,10 @@ CYPHER
65
68
  git commits) is always current. The plugin write scripts already do both.
66
69
  - **Values are scalars or lists of scalars.** CypherLite cannot store a map as a property value;
67
70
  JSON-encode anything nested into a string.
68
- - **Read the result.** The plugin write scripts are guarded: a write that did nothing returns `[]` (or
69
- `completed: false` with the reason). Never assume a write landed.
71
+ - **Read the result.** The plugin write scripts are guarded and return what they actually changed.
72
+ Compare it with what you asked for; never assume a write landed.
70
73
  - **Exit code 1 is a failure** — lock, constraint violation (`ConstraintValidationFailed`), syntax. Read
71
74
  stderr, fix, retry the whole transaction.
72
- - A one-shot write without `.checkpoint` still lands (reads replay the WAL), but `graph.json` stays stale
73
- until the next checkpoint — so always end writes with it.
74
75
 
75
76
  ## Creating a store
76
77
 
@@ -87,16 +88,20 @@ the schema constraints and pins the JSON codec, so `graph.json` exists from the
87
88
 
88
89
  ## Script catalog
89
90
 
90
- All in `${CLAUDE_PLUGIN_ROOT}/cypher/`. Reads print one JSON array; writes end with `.checkpoint`.
91
-
92
- | script | params | returns | used by |
93
- | -------------------- | ---------------------------------------- | -------------------------------------------------------------- | ------- |
94
- | `schema.cypher` | — | nothing (installs constraints) | plan |
95
- | `validate.cypher` | — | one row per violation (`check`, `detail`); `[]` = valid | every skill |
96
- | `status.cypher` | — | one row per node: `slug`, `title`, `type`, `status`, `state` (`frontier`/`blocked`/stored status), `wave`, `waiting_on`, `review_attempts`, `last_verdict`, `last_failed_criteria`, `started_at`, `completed_at` | every skill |
97
- | `node.cypher` | `slug` | one node: properties, `inputs` (resolved through `FROM`: `name`, `shape`, `from`, `value`), `outputs`, `depends_on`, `satisfies`, `reviews` | run |
98
- | `feature.cypher` | — | `destination`, `context`, `out_of_scope`, `acceptance` (with `covered_by`), every node's `outputs` | review, pr |
99
- | `dispatch.cypher` | `slugs` (list) | `dispatched` — only pending nodes whose dependencies are satisfied | run |
100
- | `record-review.cypher` | `slug`, `verdict` (`pass`/`fail`), `failed_criteria` (list) | `attempt`, `verdict` | run |
101
- | `complete.cypher` | `slug`, `outputs` (object), `started_at`, `completed_at` | `completed` (bool), `status`, `has_passing_review`, `missing_outputs` | run |
102
- | `set-status.cypher` | `slug`, `status` (`pending`/`in_progress`/`failed`/`cancelled`) | `slug`, `status` | run |
91
+ All in `${CLAUDE_PLUGIN_ROOT}/cypher/`. Writes end with `.checkpoint`.
92
+
93
+ | script | params | returns | used by |
94
+ | -------------------- | ------------------------- | -------------------------------------------------------------- | ------- |
95
+ | `schema.cypher` | — | nothing (installs constraints) | plan |
96
+ | `validate.cypher` | — | one row per violation (`check`, `detail`); no rows = valid | every skill |
97
+ | `progress.cypher` | — | one row per wave: `total`, counts per state, `open` (slugs not yet completed or cancelled) | every skill |
98
+ | `attention.cypher` | — | only nodes needing a decision: `slug`, `reason` (`failed` / `in_progress` / `blocked-by-failed`), `detail` | run, status |
99
+ | `dispatch.cypher` | — | claims the whole frontier (→ `in_progress`); per claimed node: `slug`, `title`, `type`, `acceptance`, `failure_policy`, `model_tier`, `verify`, `inputs` (`name`, `shape`, `value`), `outputs` (`name`, `shape`) | run |
100
+ | `record.cypher` | `results` — list of `{slug, verdict, failed_criteria, outputs, started_at, completed_at}` | per recorded node: `attempt`, `verdict`, `completed`, `missing_outputs` | run |
101
+ | `set-status.cypher` | `slugs` (list), `status` (`pending` / `in_progress` / `failed` / `cancelled`) | `slug`, `status` per changed node | run |
102
+ | `status.cypher` | — | one row per node: `state`, `wave`, `waiting_on`, review summary, timestamps | status (human report) |
103
+ | `node.cypher` | `slug` | one node in full: properties, `inputs` (with `from`), `outputs`, `depends_on`, `satisfies`, `reviews` | plan (reopen), ad hoc |
104
+ | `feature.cypher` | — | `destination`, `context`, `out_of_scope`, `acceptance` (with `covered_by`) | review, pr |
105
+
106
+ Ready-made ad-hoc queries (lineage, critical path, repair history, …) are in
107
+ `${CLAUDE_PLUGIN_ROOT}/reference/queries.md`.
@@ -104,7 +104,7 @@ map as a property value. JSON-encode anything nested into a string and say so in
104
104
 
105
105
  ### `Review` — one reviewer verdict
106
106
 
107
- `attempt` (integer, 1-based, assigned by `record-review.cypher`), `verdict` (`pass | fail`),
107
+ `attempt` (integer, 1-based, assigned by `record.cypher`), `verdict` (`pass | fail`),
108
108
  `failed_criteria` (list of the acceptance criteria the reviewer failed; empty on a pass), `at` (string).
109
109
  Reviews are never updated or deleted — the repair history stays visible to `status`.
110
110
 
@@ -155,7 +155,7 @@ In this model the question has a checkable answer, and `validate.cypher` enforce
155
155
  `status` is stored on the node; `frontier` and `blocked` are derived by `status.cypher`, never stored.
156
156
 
157
157
  ```
158
- pending ──dispatch──▶ in_progress ──complete──▶ completed
158
+ pending ──dispatch──▶ in_progress ──record (pass)──▶ completed
159
159
  │
160
160
  ├──set-status──▶ failed (escalate, exhausted retry/repair, stop)
161
161
  └──set-status──▶ cancelled (skip)
@@ -166,8 +166,8 @@ blocked = pending ∧ some DEPENDS_ON target not completed-or-cancelled
166
166
 
167
167
  A `cancelled` dependency counts as satisfied — abandoning an optional node must not wedge its
168
168
  dependents. A `failed` one does not — dependents stay blocked rather than build on a broken foundation.
169
- `completed` can only be set by `complete.cypher`, which refuses unless the node is `in_progress`, has a
170
- passing `Review`, and every `OUTPUT` has a value.
169
+ `completed` can only be set by `record.cypher`, and only for an `in_progress` node whose review passed
170
+ with a value for every `OUTPUT` — a pass missing outputs is recorded as a failed review naming them.
171
171
 
172
172
  ## Who enforces what
173
173
 
@@ -175,42 +175,10 @@ passing `Review`, and every `OUTPUT` has a value.
175
175
  | ------------------------- | ------------------------------------------------------------------------ |
176
176
  | engine constraints (`cypher/schema.cypher`) | required properties and their types; unique `slug` and `Acceptance.id` — a violating write never lands |
177
177
  | `cypher/validate.cypher` | exactly one `Feature`; enums; type label matches `type`; non-blank acceptance; `verify` target exists; acceptance linked and covered; allowed edge shapes only; acyclic `DEPENDS_ON`; every chore depended on; every node has an output; field ownership and uniqueness; the `FROM` cut test; completed nodes have a passing review and all output values |
178
- | write scripts (`cypher/*.cypher`) | `dispatch` moves only frontier nodes; `complete` needs a passing review and every output; `set-status` never sets `completed` |
178
+ | write scripts (`cypher/*.cypher`) | `dispatch` claims only frontier nodes; `record` completes a node only on a pass with every output; `set-status` never sets `completed` |
179
179
 
180
180
  ## Querying
181
181
 
182
- The plan and its progress are one graph, so any question is a Cypher query. Run ad-hoc reads with
183
- `cypherlite .gbuild/<feature>/db -json "<query>"` (see `reference/cypher.md`); keep a `LIMIT` on anything
184
- that could grow.
185
-
186
- ```cypher
187
- -- the whole plan with progress
188
- MATCH (n:GbuildNode) RETURN n.slug, n.type, n.status, n.title ORDER BY n.slug
189
-
190
- -- nodes by type
191
- MATCH (n:Code) RETURN n.slug, n.title
192
-
193
- -- full transitive dependency chain of a node
194
- MATCH (n:GbuildNode {slug: 'd-join-and-sum'})-[:DEPENDS_ON*]->(d) RETURN DISTINCT d.slug
195
-
196
- -- what waits on a node
197
- MATCH (n:GbuildNode {slug: 'a-produce-shared-value'})<-[:DEPENDS_ON]-(w) RETURN w.slug
198
-
199
- -- where a node's inputs come from
200
- MATCH (:GbuildNode {slug: 'd-join-and-sum'})-[:INPUT]->(i)-[:FROM]->(o)<-[:OUTPUT]-(u)
201
- RETURN i.name, u.slug, o.name, o.value
202
-
203
- -- everything downstream of a node's output field (data lineage)
204
- MATCH (:GbuildNode {slug: 'a-produce-shared-value'})-[:OUTPUT]->(o)<-[:FROM]-(i)<-[:INPUT]-(c)
205
- RETURN o.name, c.slug, i.name
206
-
207
- -- critical path (longest dependency chain)
208
- MATCH p = (n:GbuildNode)-[:DEPENDS_ON*]->(m:GbuildNode) RETURN [x IN nodes(p) | x.slug] AS path
209
- ORDER BY length(p) DESC LIMIT 1
210
-
211
- -- which nodes cover each feature criterion
212
- MATCH (a:Acceptance)<-[:SATISFIES]-(n:GbuildNode) RETURN a.id, collect(n.slug)
213
-
214
- -- nodes that needed a repair pass
215
- MATCH (n:GbuildNode)-[:REVIEWED]->(r:Review {verdict: 'fail'}) RETURN n.slug, count(r) AS failed_reviews
216
- ```
182
+ The plan and its progress are one graph, so any question is a Cypher query. The plugin scripts cover
183
+ what the skills need (`reference/cypher.md` § Script catalog); ready-made ad-hoc queries are in
184
+ `reference/queries.md`.
@@ -0,0 +1,37 @@
1
+ # Ad-hoc queries
2
+
3
+ The plugin scripts (`reference/cypher.md` § Script catalog) cover everything the skills do. These are for
4
+ the questions in between. Run them with `cypherlite .gbuild/<feature>/db --mode jsonl "<query>"`, one at a
5
+ time; return only the columns you need and keep a `LIMIT` on anything that can grow.
6
+
7
+ ```cypher
8
+ -- the whole plan with progress
9
+ MATCH (n:GbuildNode) RETURN n.slug, n.type, n.status, n.title ORDER BY n.slug
10
+
11
+ -- nodes by type
12
+ MATCH (n:Code) RETURN n.slug, n.title
13
+
14
+ -- full transitive dependency chain of a node
15
+ MATCH (n:GbuildNode {slug: 'd-join-and-sum'})-[:DEPENDS_ON*]->(d) RETURN DISTINCT d.slug
16
+
17
+ -- what waits on a node
18
+ MATCH (n:GbuildNode {slug: 'a-produce-shared-value'})<-[:DEPENDS_ON]-(w) RETURN w.slug
19
+
20
+ -- where a node's inputs come from
21
+ MATCH (:GbuildNode {slug: 'd-join-and-sum'})-[:INPUT]->(i)-[:FROM]->(o)<-[:OUTPUT]-(u)
22
+ RETURN i.name, u.slug, o.name, o.value
23
+
24
+ -- everything downstream of a node's output field (data lineage)
25
+ MATCH (:GbuildNode {slug: 'a-produce-shared-value'})-[:OUTPUT]->(o)<-[:FROM]-(i)<-[:INPUT]-(c)
26
+ RETURN o.name, c.slug, i.name
27
+
28
+ -- critical path (longest dependency chain)
29
+ MATCH p = (n:GbuildNode)-[:DEPENDS_ON*]->(m:GbuildNode) RETURN [x IN nodes(p) | x.slug] AS path
30
+ ORDER BY length(p) DESC LIMIT 1
31
+
32
+ -- which nodes cover each feature criterion
33
+ MATCH (a:Acceptance)<-[:SATISFIES]-(n:GbuildNode) RETURN a.id, collect(n.slug)
34
+
35
+ -- nodes that needed a repair pass
36
+ MATCH (n:GbuildNode)-[:REVIEWED]->(r:Review {verdict: 'fail'}) RETURN n.slug, count(r) AS failed_reviews
37
+ ```
@@ -163,20 +163,21 @@ slug) fails the transaction and writes nothing — fix the script and run it aga
163
163
  ### 9. Validate
164
164
 
165
165
  ```
166
- cypherlite .gbuild/<slug>/db -json < ${CLAUDE_PLUGIN_ROOT}/cypher/validate.cypher
166
+ cypherlite .gbuild/<slug>/db --mode jsonl < ${CLAUDE_PLUGIN_ROOT}/cypher/validate.cypher
167
167
  ```
168
168
 
169
- `[]` means valid. Every row is a violation (`check`, `detail`): fix it with Cypher — a `.begin` …
170
- `.commit` … `.checkpoint` script of `MATCH … SET/CREATE/DELETE` — and re-run until it prints `[]`. Never
169
+ No output means valid. Every row is a violation (`check`, `detail`): fix it with Cypher — a `.begin` …
170
+ `.commit` … `.checkpoint` script of `MATCH … SET/CREATE/DELETE` — and re-run until it prints nothing. Never
171
171
  hand off or commit a graph that fails validation, and never edit `graph.json` directly.
172
172
 
173
173
  Then confirm run-readiness:
174
174
 
175
175
  ```
176
- cypherlite .gbuild/<slug>/db -json < ${CLAUDE_PLUGIN_ROOT}/cypher/status.cypher
176
+ cypherlite .gbuild/<slug>/db --mode jsonl < ${CLAUDE_PLUGIN_ROOT}/cypher/progress.cypher
177
177
  ```
178
178
 
179
- The `wave` column is the decomposition; rows with `state: frontier` are what `run` dispatches first.
179
+ One row per wave: each row's `open` slugs are the decomposition, and the `frontier` counts are what
180
+ `run` claims first. Nodes you expected to run together should share a wave.
180
181
 
181
182
  ### 10. Commit and stop
182
183
 
@@ -192,7 +193,8 @@ Report the branch, the waves, and the frontier. Close with `next: /gbuild:run <s
192
193
 
193
194
  0. Check out the feature's existing branch if you're not on it. Never chart a reopen onto a different
194
195
  branch than the one the graph was charted on.
195
- 1. Read the current graph (`status.cypher`, and `node.cypher` for any node the requirement touches).
196
+ 1. Read the current graph narrowly: `progress.cypher` for its shape and what's done, and `node.cypher`
197
+ only for the nodes the requirement touches.
196
198
  2. Design the delta: a new `Acceptance` node (next `a-` id) linked from the `Feature`, and whatever new
197
199
  GbuildNodes it needs, with their `Field`s. A new node may depend on an already-completed one — wire
198
200
  its `INPUT` `FROM` that node's `OUTPUT` like any other; its value is already recorded. Re-run the
@@ -214,7 +216,7 @@ Report the branch, the waves, and the frontier. Close with `next: /gbuild:run <s
214
216
  CYPHER
215
217
  ```
216
218
 
217
- 4. **Re-run `validate.cypher`** until it prints `[]`, then `status.cypher`. Commit
219
+ 4. **Re-run `validate.cypher`** until it prints nothing, then `progress.cypher`. Commit
218
220
  (`<slug>: reopen graph — <what was added>`), report the new frontier.
219
221
 
220
222
  If the requirement needs no new nodes (an existing node's contract already covers it), say so and stop
@@ -14,7 +14,7 @@ This skill MUST run in the **main conversation context**. It MUST NOT be invoked
14
14
 
15
15
  - **slug**: the first token that isn't a flag. If absent, infer it — the current branch commonly ends in the slug (`plan` checks out `<prefix>/<slug>`), otherwise fall back to the store whose `.gbuild/*/db/graph.json` was most recently modified. If neither resolves, leave `<slug>` unresolved (the auto-fix step degrades gracefully — see Step 4).
16
16
 
17
- When `<slug>` resolved, read `${CLAUDE_PLUGIN_ROOT}/reference/cypher.md` and run its version check — every graph read below goes through `cypherlite`, one command at a time.
17
+ When `<slug>` resolved, every graph read below goes through `cypherlite`: run `cypherlite --version` first (missing → stop; gbuild needs it), one command at a time, always `--mode jsonl` (no output = no rows), and on `storage locked` wait and retry up to 3 times. Anything else: `${CLAUDE_PLUGIN_ROOT}/reference/cypher.md`.
18
18
  - **max_rounds**: the value of `--max-rounds N` (also accept `--max-rounds=N`). Default `3` when absent.
19
19
 
20
20
  ## Step 1: Preconditions
@@ -27,7 +27,7 @@ Verify the environment before touching the remote. Stop with a clear, actionable
27
27
  4. **Not on base.** Get the current branch (`git branch --show-current`). If it equals `<base>`, stop — there is nothing to open a PR for.
28
28
  5. **Clean tree.** Check for uncommitted changes (`git status --porcelain`). `/gbuild:run` commits each node's work as it goes, so the tree should be clean. If there are uncommitted changes, warn the user and ask whether to proceed (they may want to commit first).
29
29
  6. **Commits ahead of base.** Confirm the branch has commits the base lacks (`git log <base>..HEAD --oneline`). If empty, stop — nothing to push.
30
- 7. **Graph finished.** When `<slug>` resolved, run `cypherlite .gbuild/<slug>/db -json < ${CLAUDE_PLUGIN_ROOT}/cypher/status.cypher`. If any row has `state: frontier` or `in_progress`, warn that the graph isn't finished and ask whether to open the PR anyway. If any node is `failed`, name it — a PR built on an escalated node is usually premature.
30
+ 7. **Graph finished.** When `<slug>` resolved, run `cypherlite .gbuild/<slug>/db --mode jsonl < ${CLAUDE_PLUGIN_ROOT}/cypher/progress.cypher`. If any row's `open` is non-empty, warn that the graph isn't finished (name the open nodes) and ask whether to open the PR anyway. If any row has `failed` > 0, get their names from `attention.cypher` and name them — a PR built on an escalated node is usually premature.
31
31
 
32
32
  ## Step 2: Determine the remote & push
33
33
 
@@ -47,7 +47,7 @@ Gather context from:
47
47
 
48
48
  - Commit subjects: `git log <base>..HEAD --oneline`.
49
49
  - File overview: `git diff <base>...HEAD --stat`.
50
- - When `<slug>` resolved: `cypherlite .gbuild/<slug>/db -json < ${CLAUDE_PLUGIN_ROOT}/cypher/feature.cypher` — the `destination`, the acceptance bar (with the nodes covering each criterion), and every node's recorded output values.
50
+ - When `<slug>` resolved: `cypherlite .gbuild/<slug>/db --mode jsonl < ${CLAUDE_PLUGIN_ROOT}/cypher/feature.cypher` — the `destination` and the acceptance bar (with the nodes covering each criterion).
51
51
 
52
52
  Derive a concise, conventional-commit-style title from the feature/branch and the changes (e.g. `feat(auth): add OAuth login`).
53
53
 
@@ -15,24 +15,34 @@ branch commonly ends in the slug (`plan` checks out `<prefix>/<slug>`), otherwis
15
15
  whose `.gbuild/*/db/graph.json` was most recently modified. Do NOT write any file — this skill outputs to
16
16
  chat exclusively.
17
17
 
18
- Read `${CLAUDE_PLUGIN_ROOT}/reference/cypher.md` and run its version check. Then, one after the other,
19
- run `validate.cypher` and `status.cypher` against `.gbuild/<slug>/db`. If the frontier is non-empty or
20
- nodes are still `in_progress`, say the graph isn't finished and ask whether to audit anyway — auditing a
21
- half-built branch produces findings that the remaining nodes were going to address.
18
+ Store rules: run `cypherlite --version` first (missing → stop; gbuild needs it). One `cypherlite`
19
+ command at a time, always `--mode jsonl` (no output = no rows); on `storage locked` wait and retry up to
20
+ 3 times. Anything else: `${CLAUDE_PLUGIN_ROOT}/reference/cypher.md`.
21
+
22
+ Check the graph is finished — one row per wave, cheap:
23
+
24
+ ```
25
+ cypherlite .gbuild/<slug>/db --mode jsonl < ${CLAUDE_PLUGIN_ROOT}/cypher/progress.cypher
26
+ ```
27
+
28
+ If any row's `open` is non-empty, say the graph isn't finished (name the open nodes) and ask whether to
29
+ audit anyway — auditing a half-built branch produces findings that the remaining nodes were going to
30
+ address.
22
31
 
23
32
  ## Run the audit
24
33
 
25
34
  Fetch the feature context:
26
35
 
27
36
  ```
28
- cypherlite .gbuild/<slug>/db -json < ${CLAUDE_PLUGIN_ROOT}/cypher/feature.cypher
37
+ cypherlite .gbuild/<slug>/db --mode jsonl < ${CLAUDE_PLUGIN_ROOT}/cypher/feature.cypher
29
38
  ```
30
39
 
31
40
  Launch the `gbuild-auditor` subagent (agents/gbuild-auditor.md — this plugin's own copy, gbuild does not
32
41
  depend on hone-ai being installed). Pass it:
33
42
 
34
- - The resolved slug and the `feature.cypher` result verbatim (destination, acceptance bar with the nodes
35
- covering each criterion, every node's recorded outputs) — the auditor never opens the store itself.
43
+ - The resolved slug and the `feature.cypher` result verbatim (destination and the acceptance bar with
44
+ the nodes covering each criterion) — the auditor never opens the store itself, and reads the code from
45
+ the branch diff.
36
46
  - Tell it to audit the current branch.
37
47
 
38
48
  The sub-agent runs the full audit in its own fresh context and returns the audit as its final message.
@@ -2,37 +2,41 @@
2
2
  description: Executes a gbuild graph — dispatches every ready-and-independent node in the current wave concurrently, reviews each node's output with gbuild-reviewer before recording it complete, and resumes only the remaining frontier on re-invocation. Use after /gbuild:plan has charted the graph.
3
3
  ---
4
4
 
5
- Run the graph in `.gbuild/$ARGUMENTS/db` (the feature slug is `$ARGUMENTS`).
5
+ Run the graph in `.gbuild/$ARGUMENTS/db` (the feature slug is `$ARGUMENTS`; `DB` below).
6
6
 
7
7
  **CRITICAL: this skill must run in the main context, not inside a forked agent.** It dispatches its own
8
8
  subagents per node; a subagent cannot itself fan out further subagents reliably. It is also the store's
9
9
  **only writer**: subagents never run `cypherlite` — they get what they need in their prompt and report
10
- back in their final message, and this skill records it. Read `${CLAUDE_PLUGIN_ROOT}/reference/cypher.md`
11
- first and run its version check; `DB` below is `.gbuild/<feature>/db`.
10
+ back in their final message, and this skill records it.
12
11
 
13
- ## Step 0: Load and query
12
+ ## Store rules
14
13
 
15
- **Confirm the graph is well-formed:**
14
+ - `cypherlite --version` first. If it's missing, stop: gbuild needs it
15
+ (`curl -sSfL https://neo4j-labs.github.io/cypherlite/install.sh | bash`).
16
+ - One `cypherlite` command at a time against `DB` — never parallel tool calls on it, never from a subagent.
17
+ - Always `--mode jsonl`: one JSON object per row, no output = no rows. Every `--param` value is JSON,
18
+ single-quoted for the shell (`--param 'slugs=["a","b"]'`).
19
+ - `storage locked` → another process holds the store: wait a few seconds, retry up to 3 times, then stop.
20
+ Exit code 1 = failure; read stderr. Anything beyond the scripts below: `${CLAUDE_PLUGIN_ROOT}/reference/cypher.md`.
21
+ - Never re-read what you already hold. The claimed wave's definitions stay valid for its retries.
16
22
 
17
- ```
18
- cypherlite DB -json < ${CLAUDE_PLUGIN_ROOT}/cypher/validate.cypher
19
- ```
20
-
21
- Anything but `[]` → stop and report the rows; do not run a graph `plan` didn't finish correctly. Then:
23
+ ## Step 0: Check and recover
22
24
 
23
25
  ```
24
- cypherlite DB -json < ${CLAUDE_PLUGIN_ROOT}/cypher/status.cypher
26
+ cypherlite DB --mode jsonl < ${CLAUDE_PLUGIN_ROOT}/cypher/validate.cypher
27
+ cypherlite DB --mode jsonl < ${CLAUDE_PLUGIN_ROOT}/cypher/attention.cypher
25
28
  ```
26
29
 
27
- - **Rows with `state: in_progress`** at this point are left over from an interrupted run (this skill is
28
- the only writer, and it records every node before finishing). Check `git log` for commits the node's
29
- agent already made, reset each to `pending` with `set-status.cypher` (`--param 'status="pending"'`) so
30
- it re-dispatches, and mention it in the report.
31
- - The rows with `state: frontier` are the set ready right now — not a judgement call; the query already
32
- excludes anything blocked, in flight, or done.
33
- - If there is no frontier and every row is `completed` or `cancelled`, the graph is done — report that
34
- and stop. If there is no frontier but nodes remain incomplete, there's a stuck cluster (likely an
35
- `escalate`d or `stop`ped node, now `failed`) — report what's blocking and stop.
30
+ - `validate` printing anything → stop and report the rows; do not run a graph `plan` didn't finish
31
+ correctly.
32
+ - `attention` rows with reason `in_progress` are left over from an interrupted run (this skill is the
33
+ only writer and records every node before finishing). Check `git log` for commits their agents already
34
+ made, then reset them all in one call so they re-dispatch, and mention it in the report:
35
+
36
+ ```
37
+ cypherlite DB --mode jsonl --param 'slugs=["<slug>", ...]' --param 'status="pending"' \
38
+ < ${CLAUDE_PLUGIN_ROOT}/cypher/set-status.cypher
39
+ ```
36
40
 
37
41
  ## Step 1: Establish VCS facts
38
42
 
@@ -41,100 +45,102 @@ Default to `git`. `git check-ignore -q .gbuild/` tells you whether to commit the
41
45
  ## Step 2: Pick a backend
42
46
 
43
47
  **Workflow tool**, if this session has opted into multi-agent orchestration (ultracode, or the user
44
- asked for a workflow explicitly) — translate the frontier into a `pipeline()`/`parallel()` script: each
45
- node is an `agent()` implement call, immediately followed by a `gbuild-reviewer` `agent()` review call
46
- scoped to that node's outputs + `acceptance`, using `schema` for the pass/fail verdict. Code nodes
48
+ asked for a workflow explicitly) — translate each claimed wave into a `pipeline()`/`parallel()` script:
49
+ each node is an `agent()` implement call, immediately followed by a `gbuild-reviewer` `agent()` review
50
+ call scoped to that node's outputs + `acceptance`, using `schema` for the pass/fail verdict. Code nodes
47
51
  touching files that could conflict with a concurrent sibling get `isolation: 'worktree'`.
48
52
  Controlled-cycle clusters become a bounded `while` loop per `${CLAUDE_PLUGIN_ROOT}/reference/shapes.md`'s
49
53
  round cap. Route `model_tier: strong` nodes via `opts.model` where the harness supports an override. The
50
- script never touches the store: collect every implement and review result, then record them from the
51
- main context exactly as step 3.4 describes.
54
+ script never touches the store: collect the results and record them from the main context (step 3.4).
52
55
 
53
56
  **Fallback** (default — no opt-in required, and always what step 3 below assumes): fire one `Agent`
54
- tool call per frontier node, **all in a single message** — this is what makes the fan-out real rather
57
+ tool call per claimed node, **all in a single message** — this is what makes the fan-out real rather
55
58
  than claimed. Do not dispatch them one at a time across separate messages; that's exactly the serial
56
59
  behavior this plugin replaces.
57
60
 
58
- ## Step 3: One wave (fallback backend)
61
+ ## Step 3: One wave
59
62
 
60
- 1. **MARK.** Move the whole frontier to `in_progress` in one call and check that `dispatched` lists every
61
- slug you passed:
63
+ 1. **CLAIM.**
62
64
 
63
65
  ```
64
- cypherlite DB -json --param 'slugs=["<slug>", ...]' < ${CLAUDE_PLUGIN_ROOT}/cypher/dispatch.cypher
66
+ cypherlite DB --mode jsonl < ${CLAUDE_PLUGIN_ROOT}/cypher/dispatch.cypher
65
67
  ```
66
68
 
67
- Then fetch each node's definition — `node.cypher` with `--param 'slug="<slug>"'`, one call per node,
68
- one after the other. Its `inputs` are already resolved: each input read from another node carries
69
- the upstream `value`.
69
+ It moves the whole frontier to `in_progress` and prints one row per claimed node with everything its
70
+ agent needs: `slug`, `title`, `type`, `acceptance`, `failure_policy`, `model_tier`, `verify`,
71
+ `inputs` (an input with a `value` came from an upstream node; one without is external — its `shape`
72
+ says where to find it), and `outputs`. **No rows** → nothing is ready: go to step 5.
70
73
 
71
- 2. **IMPLEMENT.** In one message, spawn one `Agent` per node. Give each the node's `slug`, `title`,
72
- `type`, `inputs` (name, shape, value — or for an input with no `from`, where to find it),
73
- `outputs` (name, shape), and `acceptance`. Tell it to:
74
+ 2. **IMPLEMENT.** In one message, spawn one `Agent` per claimed node with its row. Tell it to:
74
75
  - actually do the work — for `code`/`test`/`chore` nodes make the change in the working tree (not
75
76
  describe it) and commit it, message `<feature>/<slug>: <what changed>`;
76
77
  - produce a value for every output field: a scalar or a list of scalars, JSON-encoding anything
77
78
  nested into a string;
78
79
  - record its own UTC start and finish (`date -u +%Y-%m-%dT%H:%M:%SZ`) and end its final message with
79
- a JSON object `{"started_at": "...", "completed_at": "...", "outputs": {...}}`;
80
+ `{"started_at": "...", "completed_at": "...", "outputs": {...}}`;
80
81
  - **not** run `cypherlite` or touch `.gbuild/`.
81
82
 
82
- 3. **REVIEW.** Once IMPLEMENT returns, spawn a `gbuild-reviewer` agent per node, again all in one message
83
+ 3. **REVIEW.** Once IMPLEMENT returns, spawn a `gbuild-reviewer` per node, again all in one message
83
84
  (`agents/gbuild-reviewer.md` — this plugin's own copy, gbuild does not depend on hone-ai being
84
- installed). Give it the node definition from `node.cypher`, the implementing agent's reported
85
- outputs, and where the diff is. Never let the implementing agent review its own work.
85
+ installed). Give it the node's row, the agent's reported outputs, and where the diff is. Never let
86
+ the implementing agent review its own work.
86
87
 
87
- 4. **RECORD** — sequentially, one `cypherlite` call at a time, after the agents returned. For each node:
88
+ 4. **RECORD** the whole wave in one call — one result per node, `verdict` from the reviewer's
89
+ `VERDICT:` line, `failed_criteria` from its `[fail]` lines:
88
90
 
89
91
  ```
90
- cypherlite DB -json --param 'slug="<slug>"' --param 'verdict="pass"' --param 'failed_criteria=[]' \
91
- < ${CLAUDE_PLUGIN_ROOT}/cypher/record-review.cypher
92
+ cypherlite DB --mode jsonl --param 'results=[
93
+ {"slug": "<slug>", "verdict": "pass", "failed_criteria": [], "outputs": {...},
94
+ "started_at": "<iso>", "completed_at": "<iso>"},
95
+ {"slug": "<slug>", "verdict": "fail", "failed_criteria": ["<criterion>"]}
96
+ ]' < ${CLAUDE_PLUGIN_ROOT}/cypher/record.cypher
92
97
  ```
93
98
 
94
- (`verdict="fail"` and the reviewer's failed criteria as `failed_criteria` on a fail.)
95
-
96
- - **Pass** (`VERDICT: pass`): record it complete with the agent's outputs and times.
97
-
98
- ```
99
- cypherlite DB -json --param 'slug="<slug>"' --param 'outputs={...}' \
100
- --param 'started_at="<iso>"' --param 'completed_at="<iso>"' < ${CLAUDE_PLUGIN_ROOT}/cypher/complete.cypher
101
- ```
102
-
103
- Check `completed: true`. If it is `false`, `missing_outputs` names the output fields the agent didn't
104
- produce — treat that as a failed review with those as the failed criteria. An `InvalidPropertyType`
105
- error means an output value was a map: JSON-encode it into a string and record again.
106
- - **Fail**: apply the node's `failure_policy` (`${CLAUDE_PLUGIN_ROOT}/reference/failure-policies.md`).
107
- `retry`/`repair` re-dispatch implement → review (bounded: 2 attempts — `review_attempts` in
108
- `status.cypher` counts them) with the reviewer's failed criteria fed back in; the node stays
109
- `in_progress`. `fallback` dispatches the named alternate approach. `skip` sets it `cancelled`,
110
- `escalate` sets it `failed` (its dependents stay blocked; siblings elsewhere keep going), both with
111
- `set-status.cypher`. `stop` sets it `failed` and halts the entire run.
99
+ Each returned row says whether that node `completed`. A slug missing from the output was not
100
+ recorded (it wasn't `in_progress`) — find out why before moving on. `InvalidPropertyType` means an
101
+ output value was a map; the whole batch was rejected — JSON-encode it and send the batch again.
102
+
103
+ 5. **FAILURES.** For each row with `completed: false` (a failed review, or a pass `record` downgraded
104
+ because `missing_outputs` is non-empty), apply the node's `failure_policy`
105
+ (`${CLAUDE_PLUGIN_ROOT}/reference/failure-policies.md`), using its `attempt` as the count:
106
+ - `retry` / `repair` → re-dispatch implement → review with the failed criteria fed back in, reusing
107
+ the row you already hold, then record just those nodes. Bounded: 2 attempts.
108
+ - `fallback` → dispatch the named alternate approach the same way.
109
+ - `skip` → `cancelled`; `escalate` (or an exhausted retry/repair) → `failed`, its dependents stay
110
+ blocked while siblings elsewhere keep going; `stop` → `failed` and halt the entire run. One
111
+ `set-status.cypher` call per status, with every slug that gets it.
112
+ - A completed router (`decision` choosing a branch) → cancel the unselected branches with
113
+ `set-status.cypher` before the next claim.
112
114
 
113
115
  Nodes in the same wave never read each other's outputs, so true concurrent dispatch is safe by
114
- construction — only the recording is serialized.
116
+ construction — only the store calls are serialized.
115
117
 
116
118
  ## Step 4: Next wave
117
119
 
118
120
  If `.gbuild/` isn't ignored, commit the store: `git add .gbuild/<feature>/db` and
119
- `git commit -m "<feature>: record wave <n>"`. Re-run `status.cypher`. If the wave that just finished
120
- unblocked new frontier nodes, repeat step 3 for them. Keep going until there is no frontier (done, or
121
- stuck — see step 0's exit conditions).
121
+ `git commit -m "<feature>: record wave <n>"`. Then claim again (step 3). Keep going until a claim
122
+ returns no rows.
122
123
 
123
124
  ## Step 5: Report and stop
124
125
 
125
- Report: which nodes ran this invocation, which passed review outright vs. needed a repair pass, what's
126
- newly on the frontier or still blocked, and any `failed` nodes needing a decision.
126
+ ```
127
+ cypherlite DB --mode jsonl < ${CLAUDE_PLUGIN_ROOT}/cypher/progress.cypher
128
+ cypherlite DB --mode jsonl < ${CLAUDE_PLUGIN_ROOT}/cypher/attention.cypher
129
+ ```
130
+
131
+ Every `progress` row with `open: []` → the graph is done. Otherwise `attention` names what's stuck
132
+ (`failed` nodes with their last failed criteria, and what they block).
127
133
 
128
- Close with the next step:
134
+ Report: which nodes ran this invocation, which passed review outright vs. needed a repair pass, and any
135
+ `failed` nodes needing a decision. Close with the next step:
129
136
 
130
- - **Graph fully done** (every node `completed` or `cancelled`) → say so plainly, then
131
- `next: /gbuild:review <feature>` — the per-node reviews graded each node against its own contract;
132
- nothing has yet looked at what the accumulated diff did to the codebase.
133
- - **Work remains** (frontier non-empty, or nodes `failed` awaiting a decision) →
134
- `next: /gbuild:status <feature>`.
137
+ - **Graph fully done** → say so plainly, then `next: /gbuild:review <feature>` — the per-node reviews
138
+ graded each node against its own contract; nothing has yet looked at what the accumulated diff did to
139
+ the codebase.
140
+ - **Work remains** (nodes `failed` awaiting a decision) → `next: /gbuild:status <feature>`.
135
141
 
136
142
  ## Resuming
137
143
 
138
- Re-invoking `/gbuild:run <feature>` after an interruption re-runs step 0 fresh: completed and cancelled
139
- nodes are excluded from the frontier automatically, and nodes an interrupted wave left `in_progress` are
140
- reset and re-dispatched. Nothing needs to be told what already finished — the store is the memory.
144
+ Re-invoking `/gbuild:run <feature>` after an interruption re-runs step 0 fresh: nodes an interrupted
145
+ wave left `in_progress` are reset, and the next claim picks up exactly the remaining frontier. Nothing
146
+ needs to be told what already finished — the store is the memory.
@@ -2,10 +2,16 @@
2
2
  description: Reports a gbuild feature's graph — frontier, blocked, in-flight, completed, each node's review verdict, and the cluster shapes with evidence of what actually ran concurrently. Use to check progress or diagnose a stuck run.
3
3
  ---
4
4
 
5
- Report status for `$ARGUMENTS`.
5
+ Report status for `$ARGUMENTS`. This skill only reads the store; `DB` below is `.gbuild/<feature>/db`.
6
6
 
7
- Read `${CLAUDE_PLUGIN_ROOT}/reference/cypher.md` first and run its version check. This skill only reads
8
- the store; `DB` below is `.gbuild/<feature>/db`.
7
+ ## Store rules
8
+
9
+ - `cypherlite --version` first. If it's missing, stop: gbuild needs it
10
+ (`curl -sSfL https://neo4j-labs.github.io/cypherlite/install.sh | bash`).
11
+ - One `cypherlite` command at a time — never parallel tool calls on the same store.
12
+ - Always `--mode jsonl`: one JSON object per row, no output = no rows.
13
+ - `storage locked` → another process (likely a running `/gbuild:run`) holds the store: wait a few
14
+ seconds, retry up to 3 times, then say so. Anything else: `${CLAUDE_PLUGIN_ROOT}/reference/cypher.md`.
9
15
 
10
16
  ## Mode
11
17
 
@@ -16,17 +22,18 @@ the store; `DB` below is `.gbuild/<feature>/db`.
16
22
 
17
23
  ## Single feature
18
24
 
25
+ This is the human-facing report, so it's the one place that reads every node.
26
+
19
27
  ### 1. Load
20
28
 
21
29
  ```
22
- cypherlite DB -json < ${CLAUDE_PLUGIN_ROOT}/cypher/status.cypher
23
- cypherlite DB -json < ${CLAUDE_PLUGIN_ROOT}/cypher/validate.cypher
30
+ cypherlite DB --mode jsonl < ${CLAUDE_PLUGIN_ROOT}/cypher/status.cypher
31
+ cypherlite DB --mode jsonl < ${CLAUDE_PLUGIN_ROOT}/cypher/validate.cypher
24
32
  ```
25
33
 
26
34
  One call after the other. `status.cypher` gives one row per node with its derived `state`
27
35
  (`frontier` / `blocked` / `in_progress` / `completed` / `cancelled` / `failed`), `wave`, `waiting_on`,
28
- review summary and timestamps — don't re-derive any of these by hand. This is a small, bounded read —
29
- status doesn't scale with graph size the way loading full node prose would.
36
+ review summary and timestamps — don't re-derive any of these by hand.
30
37
 
31
38
  ### 2. Render the graph
32
39
 
@@ -64,11 +71,17 @@ remaining, `in_progress` nodes when no run is active — an interrupted wave `ru
64
71
 
65
72
  `validate.cypher` is the invariant check — it covers non-empty acceptance, acceptance coverage,
66
73
  acyclicity, completed nodes without a passing review or missing an output value, orphan chores, and the
67
- `FROM` cut test. Report every row it returned, numbered. If it returned `[]`, say so in one line — don't
68
- let silence be ambiguous between "checked, clear" and "not checked."
74
+ `FROM` cut test. Report every row it returned, numbered. If it returned nothing, say so in one line —
75
+ don't let silence be ambiguous between "checked, clear" and "not checked."
69
76
 
70
77
  ## All-features mode
71
78
 
72
- For each `.gbuild/<slug>/db` found, run `status.cypher` — one store at a time — and print one line: slug,
73
- node counts by state, and whether the frontier is empty (done or stuck) or has ready work. Point at
74
- `/gbuild:status <slug>` for detail.
79
+ Don't read every node of every feature. For each `.gbuild/<slug>/db` found, one store at a time:
80
+
81
+ ```
82
+ cypherlite .gbuild/<slug>/db --mode jsonl < ${CLAUDE_PLUGIN_ROOT}/cypher/progress.cypher
83
+ ```
84
+
85
+ Print one line per feature: slug, node counts by state summed over its waves, and whether it's done
86
+ (every `open` empty), has ready work (`frontier` > 0), or is stuck. Point at `/gbuild:status <slug>` for
87
+ detail.
@@ -1,23 +0,0 @@
1
- // Record a node as completed: store its output values on its OUTPUT fields
2
- // and the implementing agent's own start/finish times. Guarded — it writes
3
- // nothing unless the node is in_progress, has a passing Review, and $outputs
4
- // has a value for every declared output. Check `completed` in the result;
5
- // `missing_outputs` / `has_passing_review` say why when it is false.
6
- // Output values must be scalars or lists of scalars; JSON-encode anything
7
- // nested into a string.
8
- // cypherlite .gbuild/<slug>/db -json --param slug=<node-slug> \
9
- // --param 'outputs={"value": 7}' --param started_at=<iso> --param completed_at=<iso> \
10
- // < cypher/complete.cypher
11
- MATCH (n:GbuildNode {slug: $slug})
12
- WITH n,
13
- [k IN COLLECT { MATCH (n)-[:OUTPUT]->(f:Field) RETURN f.name } WHERE $outputs[k] IS NULL] AS missing,
14
- EXISTS { (n)-[:REVIEWED]->(:Review {verdict: 'pass'}) } AS passed
15
- WITH n, missing, passed, (n.status = 'in_progress' AND passed AND size(missing) = 0) AS ok
16
- OPTIONAL MATCH (n)-[:OUTPUT]->(f:Field) WHERE ok
17
- SET f.value = $outputs[f.name]
18
- WITH DISTINCT n, missing, passed, ok
19
- FOREACH (_ IN CASE WHEN ok THEN [1] ELSE [] END |
20
- SET n.status = 'completed', n.started_at = $started_at, n.completed_at = $completed_at)
21
- RETURN n.slug AS slug, ok AS completed, n.status AS status,
22
- passed AS has_passing_review, missing AS missing_outputs;
23
- .checkpoint
@@ -1,17 +0,0 @@
1
- // Record one gbuild-reviewer verdict as a Review node. `attempt` is assigned
2
- // here (previous reviews + 1), so repair history is never lost.
3
- // failed_criteria is the list of acceptance criteria the reviewer failed
4
- // (pass `[]` on a pass).
5
- // cypherlite .gbuild/<slug>/db -json --param slug=<node-slug> --param verdict=pass \
6
- // --param 'failed_criteria=[]' < cypher/record-review.cypher
7
- MATCH (n:GbuildNode {slug: $slug})
8
- WHERE $verdict IN ['pass', 'fail']
9
- WITH n, COUNT { (n)-[:REVIEWED]->(:Review) } + 1 AS attempt
10
- CREATE (n)-[:REVIEWED]->(r:Review {
11
- attempt: attempt,
12
- verdict: $verdict,
13
- failed_criteria: $failed_criteria,
14
- at: toString(datetime())
15
- })
16
- RETURN n.slug AS slug, r.attempt AS attempt, r.verdict AS verdict;
17
- .checkpoint