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 +14 -8
- package/agents/gbuild-auditor.md +9 -1
- package/agents/gbuild-reviewer.md +22 -46
- package/cypher/attention.cypher +20 -0
- package/cypher/dispatch.cypher +29 -11
- package/cypher/feature.cypher +6 -11
- package/cypher/node.cypher +3 -1
- package/cypher/progress.cypher +25 -0
- package/cypher/record.cypher +38 -0
- package/cypher/set-status.cypher +10 -7
- package/cypher/status.cypher +1 -1
- package/cypher/validate.cypher +1 -1
- package/package.json +1 -1
- package/reference/cypher.md +32 -27
- package/reference/graph-format.md +8 -40
- package/reference/queries.md +37 -0
- package/skills/plan/SKILL.md +9 -7
- package/skills/pr/SKILL.md +3 -3
- package/skills/review/SKILL.md +17 -7
- package/skills/run/SKILL.md +85 -76
- package/skills/status/SKILL.md +25 -12
- package/cypher/complete.cypher +0 -23
- package/cypher/record-review.cypher +0 -17
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
|
|
190
|
-
cypherlite .gbuild/<feature>/db
|
|
191
|
-
cypherlite .gbuild/<feature>/db
|
|
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
|
|
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
|
|
208
|
-
agents/gbuild-auditor.md # end-of-branch
|
|
209
|
-
reference/ # graph-format.md (normative model)
|
|
210
|
-
cypher/ # schema, validate, status/node/feature reads, and run's
|
|
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
|
package/agents/gbuild-auditor.md
CHANGED
|
@@ -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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
records each `[fail]` line's criterion
|
|
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;
|
package/cypher/dispatch.cypher
CHANGED
|
@@ -1,13 +1,31 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
12
|
-
|
|
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
|
package/cypher/feature.cypher
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
// Feature-level context: destination
|
|
2
|
-
// cover each criterion
|
|
3
|
-
//
|
|
4
|
-
//
|
|
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;
|
package/cypher/node.cypher
CHANGED
|
@@ -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
|
-
//
|
|
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
|
package/cypher/set-status.cypher
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
// Move
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
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
|
-
|
|
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
|
package/cypher/status.cypher
CHANGED
|
@@ -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
|
|
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
|
package/cypher/validate.cypher
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// gbuild format gate. Zero rows = valid; every row is one violation.
|
|
2
|
-
// cypherlite .gbuild/<slug>/db
|
|
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.
|
|
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",
|
package/reference/cypher.md
CHANGED
|
@@ -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;
|
|
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
|
|
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
|
|
39
|
+
cypherlite DB --mode jsonl < ${CLAUDE_PLUGIN_ROOT}/cypher/progress.cypher
|
|
39
40
|
|
|
40
41
|
# a plugin script with parameters
|
|
41
|
-
cypherlite DB
|
|
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
|
|
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
|
-
|
|
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
|
|
69
|
-
|
|
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/`.
|
|
91
|
-
|
|
92
|
-
| script | params
|
|
93
|
-
| -------------------- |
|
|
94
|
-
| `schema.cypher` | —
|
|
95
|
-
| `validate.cypher` | —
|
|
96
|
-
| `
|
|
97
|
-
| `
|
|
98
|
-
| `
|
|
99
|
-
| `
|
|
100
|
-
| `
|
|
101
|
-
| `
|
|
102
|
-
| `
|
|
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
|
|
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 ──
|
|
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 `
|
|
170
|
-
|
|
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`
|
|
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.
|
|
183
|
-
|
|
184
|
-
|
|
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
|
+
```
|
package/skills/plan/SKILL.md
CHANGED
|
@@ -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
|
|
166
|
+
cypherlite .gbuild/<slug>/db --mode jsonl < ${CLAUDE_PLUGIN_ROOT}/cypher/validate.cypher
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
-
|
|
170
|
-
`.commit` … `.checkpoint` script of `MATCH … SET/CREATE/DELETE` — and re-run until it prints
|
|
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
|
|
176
|
+
cypherlite .gbuild/<slug>/db --mode jsonl < ${CLAUDE_PLUGIN_ROOT}/cypher/progress.cypher
|
|
177
177
|
```
|
|
178
178
|
|
|
179
|
-
|
|
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
|
|
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
|
|
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
|
package/skills/pr/SKILL.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
package/skills/review/SKILL.md
CHANGED
|
@@ -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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
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
|
|
35
|
-
covering each criterion
|
|
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.
|
package/skills/run/SKILL.md
CHANGED
|
@@ -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.
|
|
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
|
-
##
|
|
12
|
+
## Store rules
|
|
14
13
|
|
|
15
|
-
|
|
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
|
|
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
|
-
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
|
45
|
-
node is an `agent()` implement call, immediately followed by a `gbuild-reviewer` `agent()` review
|
|
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
|
|
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
|
|
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
|
|
61
|
+
## Step 3: One wave
|
|
59
62
|
|
|
60
|
-
1. **
|
|
61
|
-
slug you passed:
|
|
63
|
+
1. **CLAIM.**
|
|
62
64
|
|
|
63
65
|
```
|
|
64
|
-
cypherlite DB
|
|
66
|
+
cypherlite DB --mode jsonl < ${CLAUDE_PLUGIN_ROOT}/cypher/dispatch.cypher
|
|
65
67
|
```
|
|
66
68
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
|
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
|
-
|
|
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`
|
|
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
|
|
85
|
-
|
|
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**
|
|
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
|
|
91
|
-
<
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
`status.cypher`
|
|
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
|
|
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>"`.
|
|
120
|
-
|
|
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
|
-
|
|
126
|
-
|
|
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
|
-
|
|
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**
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
- **Work remains** (
|
|
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:
|
|
139
|
-
|
|
140
|
-
|
|
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.
|
package/skills/status/SKILL.md
CHANGED
|
@@ -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
|
-
|
|
8
|
-
|
|
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
|
|
23
|
-
cypherlite DB
|
|
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.
|
|
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
|
|
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,
|
|
73
|
-
|
|
74
|
-
|
|
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.
|
package/cypher/complete.cypher
DELETED
|
@@ -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
|