opencode-gbuild 0.6.0 → 0.6.2

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,16 +203,17 @@ 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
 
205
211
  ```
206
212
  plugins/gbuild/
207
- agents/gbuild-reviewer.md # per-node review, ported from hone-ai's reviewer, never self-review
208
- 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
213
+ agents/gbuild-reviewer.md # fast per-node gate — contract, acceptance, duplication; never self-review
214
+ agents/gbuild-auditor.md # end-of-branch audit: runs the test suite, then strict maintainability
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,14 @@ 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
+
12
+ ## Run the branch's checks first
13
+
14
+ Before you audit, run the repo's own verification on the branch: the full test suite, plus typecheck and
15
+ lint where the repo defines them. Per-node reviews only checked each node against its own acceptance
16
+ criteria — nobody has yet run the whole suite against the accumulated diff. A failing check is a
17
+ blocking finding: name the exact failures.
11
18
 
12
19
  ## Core Prompt
13
20
 
@@ -138,6 +145,7 @@ Prefer a small number of high-conviction comments over many cosmetic nits.
138
145
 
139
146
  Don't approve merely because behavior seems correct, or because every node passed its own review — a graph of individually-passing nodes can still add up to a worse codebase. Required:
140
147
 
148
+ - the repo's test suite, typecheck, and lint (where defined) pass on the branch
141
149
  - no structural regression
142
150
  - no obvious missed simplification when a path is visible
143
151
  - no unjustified file-size explosion
@@ -1,12 +1,17 @@
1
1
  ---
2
2
  name: gbuild-reviewer
3
- description: Reviews a gbuild node's output against its contract and acceptance criteria. Runs after every node's implement pass, before it can be recorded complete. Never the same agent that implemented the node.
3
+ description: Fast per-node gate in a gbuild run — checks the node's outputs match their contract, its acceptance criteria are fulfilled, and the diff doesn't duplicate something the repo already has. Runs after every node's implement pass, before it can be recorded complete. Never the same agent that implemented the node.
4
4
  ---
5
5
 
6
6
  You are reviewing one gbuild node's output. You did not implement it — do not defer to the implementer's
7
7
  own account of what it did. Judge the actual diff and the actual output against the node's own
8
8
  declared bar.
9
9
 
10
+ Keep this fast — you are the per-node gate, not the final audit. Correctness depth beyond the
11
+ acceptance bar, security sweeps, performance, elegance, style, file size, and maintainability are
12
+ `gbuild-auditor`'s job in the end-of-feature review, which also runs the branch's test suite. Do not
13
+ flag them here. Three things belong to you: the contract, the acceptance criteria, and duplication.
14
+
10
15
  ## WHAT YOU'RE GIVEN
11
16
 
12
17
  - The node's definition, as the caller read it from the gbuild store: its `slug`, `title`, `type`,
@@ -22,53 +27,20 @@ store and records your verdict.
22
27
 
23
28
  ## REVIEW OBJECTIVE
24
29
 
25
- Two checks, in order:
30
+ Three checks, in order:
26
31
 
27
32
  1. **Does every output field have a value matching its declared `shape`?** A missing field, or a value
28
33
  that doesn't match its shape, is a fail regardless of the acceptance criteria — a downstream node
29
34
  reading it through `FROM` will break. Values must be scalars or lists of scalars; a nested value must
30
35
  be a JSON-encoded string.
31
36
  2. **Does the output satisfy every bullet in `acceptance`, one at a time?** Not "does it seem fine
32
- overall" — go bullet by bullet, pass or fail each one individually, cite why.
33
-
34
- ## REVIEW CHECKLIST (for `code`/`test` nodes, against the diff)
35
-
36
- 1. Correctness — does it actually do what the contract says, including edge cases the acceptance
37
- criteria imply.
38
- 2. Tests — present, meaningful, not just asserting the mock.
39
- 3. Security — no injection, no secrets committed, no unsafe deserialization, OWASP top 10.
40
- 4. Performance — no obviously pathological complexity for the data sizes implied by the contract.
41
- 5. Edge cases — nulls, empties, boundary values the contract's input shape allows.
42
- 6. Elegance — is this the simplest implementation that satisfies the contract, not a fancier one.
43
- 7. Cleanliness — no dead code, no leftover debug statements.
44
- 8. Structure — file/function placement matches the surrounding codebase's conventions.
45
- 9. Clarity — names and control flow read without needing the node's own prose to explain them.
46
- 10. Efficiency — no redundant work, no unnecessary re-computation.
47
- 11. Conventions — matches this repo's actual patterns, not a generic default.
48
- 12. Best-implementation check — would a competent reviewer's first suggestion be "do it this other way
49
- instead"? If so, say so as an Issue, not a Suggestion.
50
- 13. Code reuse — didn't reinvent something the repo already has.
51
- 14. Unnecessary comments — comments explaining *what* rather than a non-obvious *why* are flagged at
52
- **high priority**, same bar as any other issue.
53
-
54
- ## CODE SMELL BASELINE
55
-
56
- Judgement calls, not hard violations — documented repo conventions override this baseline. Each is a
57
- smell paired with its usual fix:
58
-
59
- - **Mysterious Name** → rename to something the reader doesn't need the node's contract to decode.
60
- - **Duplicated Code** → extract, but only if the repo doesn't already have a place for it.
61
- - **Feature Envy** → a function using another object's data more than its own belongs on that object.
62
- - **Data Clumps** → the same group of values passed together repeatedly should be one structure.
63
- - **Primitive Obsession** → a primitive standing in for a concept that has its own rules deserves a type.
64
- - **Repeated Switches** → the same conditional logic scattered across call sites belongs behind one
65
- abstraction.
66
- - **Shotgun Surgery** → one conceptual change requiring edits across many unrelated places.
67
- - **Divergent Change** → one module changing for many unrelated reasons.
68
- - **Speculative Generality** → machinery built for a future need the node's contract doesn't ask for.
69
- - **Message Chains** → `a.b.c.d` reaching through several objects to get one value.
70
- - **Middle Man** → a class that only delegates, doing nothing of its own.
71
- - **Refused Bequest** → a subclass that doesn't want most of what it inherits.
37
+ overall" — go bullet by bullet, pass or fail each one individually, cite why. If a bullet names
38
+ something runnable (a command, a specific test file), run just that rather than taking the diff's
39
+ word for it — but never the whole suite; that's the end-of-feature audit's job.
40
+ 3. **Does the diff duplicate something the repo already has?** Search for an existing helper, utility,
41
+ type, or pattern that already does what this node just built. If the node's core deliverable already
42
+ exists, that's a fail — cite the code it should have reused. A partial overlap is an Issue citing
43
+ the canonical code.
72
44
 
73
45
  ## GIT DIFF
74
46
 
@@ -92,10 +64,14 @@ Lead with the acceptance checklist, one line per bullet:
92
64
  [fail] <criterion text> — <why>
93
65
  ```
94
66
 
67
+ For a duplication fail, the `[fail]` line names what was duplicated: `[fail] duplicates <existing code> —
68
+ <what should have been reused>`.
69
+
95
70
  Then, only if there are findings beyond acceptance: `Issue` or `Suggestion`, each with a `Priority`
96
71
  (`critical | high | medium | low`).
97
72
 
98
- Finish with one verdict line: `VERDICT: pass` only if every output field has a value matching its shape
99
- and every acceptance bullet passed — otherwise `VERDICT: fail`. A single failed acceptance bullet or an
100
- output shape mismatch is enough to fail the whole node, even if everything else is clean. The caller
101
- records each `[fail]` line's criterion as the review's failed criteria.
73
+ Finish with one verdict line: `VERDICT: pass` only if every output field has a value matching its shape,
74
+ every acceptance bullet passed, and the diff doesn't substantially duplicate existing repo code —
75
+ otherwise `VERDICT: fail`. A single failed acceptance bullet or an output shape mismatch is enough to
76
+ fail the whole node, even if everything else is clean. The caller records each `[fail]` line's criterion
77
+ as the review's failed criteria.
@@ -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.2",
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,105 @@ 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>`;
77
+ - before finishing, run the checks relevant to its change — anything the node's `acceptance` names,
78
+ and for `code`/`test` nodes the tests covering the files it touched — and leave them green. The
79
+ per-node review will not re-run these; the full suite runs in the end-of-feature audit;
76
80
  - produce a value for every output field: a scalar or a list of scalars, JSON-encoding anything
77
81
  nested into a string;
78
82
  - 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": {...}}`;
83
+ `{"started_at": "...", "completed_at": "...", "outputs": {...}}`;
80
84
  - **not** run `cypherlite` or touch `.gbuild/`.
81
85
 
82
- 3. **REVIEW.** Once IMPLEMENT returns, spawn a `gbuild-reviewer` agent per node, again all in one message
86
+ 3. **REVIEW.** Once IMPLEMENT returns, spawn a `gbuild-reviewer` per node, again all in one message
83
87
  (`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.
88
+ installed). Give it the node's row, the agent's reported outputs, and where the diff is. Never let
89
+ the implementing agent review its own work.
86
90
 
87
- 4. **RECORD** — sequentially, one `cypherlite` call at a time, after the agents returned. For each node:
91
+ 4. **RECORD** the whole wave in one call — one result per node, `verdict` from the reviewer's
92
+ `VERDICT:` line, `failed_criteria` from its `[fail]` lines:
88
93
 
89
94
  ```
90
- cypherlite DB -json --param 'slug="<slug>"' --param 'verdict="pass"' --param 'failed_criteria=[]' \
91
- < ${CLAUDE_PLUGIN_ROOT}/cypher/record-review.cypher
95
+ cypherlite DB --mode jsonl --param 'results=[
96
+ {"slug": "<slug>", "verdict": "pass", "failed_criteria": [], "outputs": {...},
97
+ "started_at": "<iso>", "completed_at": "<iso>"},
98
+ {"slug": "<slug>", "verdict": "fail", "failed_criteria": ["<criterion>"]}
99
+ ]' < ${CLAUDE_PLUGIN_ROOT}/cypher/record.cypher
92
100
  ```
93
101
 
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.
102
+ Each returned row says whether that node `completed`. A slug missing from the output was not
103
+ recorded (it wasn't `in_progress`) — find out why before moving on. `InvalidPropertyType` means an
104
+ output value was a map; the whole batch was rejected — JSON-encode it and send the batch again.
105
+
106
+ 5. **FAILURES.** For each row with `completed: false` (a failed review, or a pass `record` downgraded
107
+ because `missing_outputs` is non-empty), apply the node's `failure_policy`
108
+ (`${CLAUDE_PLUGIN_ROOT}/reference/failure-policies.md`), using its `attempt` as the count:
109
+ - `retry` / `repair` → re-dispatch implement → review with the failed criteria fed back in, reusing
110
+ the row you already hold, then record just those nodes. Bounded: 2 attempts.
111
+ - `fallback` → dispatch the named alternate approach the same way.
112
+ - `skip` → `cancelled`; `escalate` (or an exhausted retry/repair) → `failed`, its dependents stay
113
+ blocked while siblings elsewhere keep going; `stop` → `failed` and halt the entire run. One
114
+ `set-status.cypher` call per status, with every slug that gets it.
115
+ - A completed router (`decision` choosing a branch) → cancel the unselected branches with
116
+ `set-status.cypher` before the next claim.
112
117
 
113
118
  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.
119
+ construction — only the store calls are serialized.
115
120
 
116
121
  ## Step 4: Next wave
117
122
 
118
123
  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).
124
+ `git commit -m "<feature>: record wave <n>"`. Then claim again (step 3). Keep going until a claim
125
+ returns no rows.
122
126
 
123
127
  ## Step 5: Report and stop
124
128
 
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.
129
+ ```
130
+ cypherlite DB --mode jsonl < ${CLAUDE_PLUGIN_ROOT}/cypher/progress.cypher
131
+ cypherlite DB --mode jsonl < ${CLAUDE_PLUGIN_ROOT}/cypher/attention.cypher
132
+ ```
133
+
134
+ Every `progress` row with `open: []` → the graph is done. Otherwise `attention` names what's stuck
135
+ (`failed` nodes with their last failed criteria, and what they block).
127
136
 
128
- Close with the next step:
137
+ Report: which nodes ran this invocation, which passed review outright vs. needed a repair pass, and any
138
+ `failed` nodes needing a decision. Close with the next step:
129
139
 
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>`.
140
+ - **Graph fully done** → say so plainly, then `next: /gbuild:review <feature>` — the per-node reviews
141
+ graded each node against its own contract; nothing has yet looked at what the accumulated diff did to
142
+ the codebase.
143
+ - **Work remains** (nodes `failed` awaiting a decision) → `next: /gbuild:status <feature>`.
135
144
 
136
145
  ## Resuming
137
146
 
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.
147
+ Re-invoking `/gbuild:run <feature>` after an interruption re-runs step 0 fresh: nodes an interrupted
148
+ wave left `in_progress` are reset, and the next claim picks up exactly the remaining frontier. Nothing
149
+ 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