opencode-gbuild 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -37
- package/agents/gbuild-auditor.md +1 -1
- package/agents/gbuild-reviewer.md +17 -10
- package/cypher/attention.cypher +20 -0
- package/cypher/dispatch.cypher +31 -0
- package/cypher/feature.cypher +16 -0
- package/cypher/node.cypher +41 -0
- package/cypher/progress.cypher +25 -0
- package/cypher/record.cypher +38 -0
- package/cypher/schema.cypher +45 -0
- package/cypher/set-status.cypher +14 -0
- package/cypher/status.cypher +33 -0
- package/cypher/validate.cypher +105 -0
- package/package.json +2 -2
- package/reference/checklist.md +16 -16
- package/reference/cypher.md +107 -0
- package/reference/failure-policies.md +6 -6
- package/reference/graph-format.md +129 -196
- package/reference/queries.md +37 -0
- package/reference/shapes.md +10 -9
- package/skills/plan/SKILL.md +115 -102
- package/skills/pr/SKILL.md +5 -3
- package/skills/review/SKILL.md +25 -7
- package/skills/run/SKILL.md +113 -67
- package/skills/status/SKILL.md +48 -46
- package/templates/example.cypher +57 -0
- package/scripts/__pycache__/graph.cpython-314.pyc +0 -0
- package/scripts/__pycache__/test_graph.cpython-314.pyc +0 -0
- package/scripts/__pycache__/validate_format.cpython-314.pyc +0 -0
- package/scripts/graph.py +0 -239
- package/scripts/test_graph.py +0 -403
- package/scripts/validate_format.py +0 -378
- package/templates/graph.json +0 -110
package/reference/checklist.md
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
# Ship checklist
|
|
2
2
|
|
|
3
|
-
`plan` runs this checklist against the graph it just wrote, before committing
|
|
4
|
-
|
|
3
|
+
`plan` runs this checklist against the graph it just wrote, before committing the store. Any item that
|
|
4
|
+
fails means revise the graph (with Cypher), not add a caveat in prose.
|
|
5
5
|
|
|
6
6
|
1. **Risky assumptions are resolved, not deferred.** Every assumption that would invalidate nodes or
|
|
7
7
|
edges if wrong has either been confirmed against the codebase/docs, or is charted as a `research`
|
|
8
8
|
node that answers it. An assumption left as "probably fine" is a hole.
|
|
9
|
-
2. **Every edge passes the cut test.** For each `DEPENDS_ON
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
9
|
+
2. **Every edge passes the cut test.** For each `DEPENDS_ON`, the dependent reads one of the
|
|
10
|
+
dependency's `OUTPUT` fields through a `FROM` edge. `validate.cypher` rejects an edge with no data
|
|
11
|
+
behind it and data with no edge behind it — but an agent can still invent a field just to satisfy it,
|
|
12
|
+
so ask honestly whether the dependent *needs* that value.
|
|
13
|
+
3. **Independent nodes are actually in the same wave.** Read `wave` from `status.cypher` and confirm
|
|
14
|
+
nodes with no real edge between them land together, not in a chain.
|
|
15
|
+
4. **Every node has non-empty, concrete acceptance criteria.** The schema requires a list and
|
|
16
|
+
`validate.cypher` rejects blanks; `plan` itself is responsible for concrete — no `"looks correct"`, no
|
|
17
|
+
criterion a fresh context couldn't check unaided.
|
|
18
|
+
5. **Every `OUTPUT` field has a precise `shape`**, not free text. Downstream `INPUT` fields read it
|
|
19
|
+
through `FROM` rather than re-describing it.
|
|
19
20
|
6. **Every `Acceptance` node is `SATISFIES`-covered** by at least one GbuildNode. An uncovered
|
|
20
|
-
feature-level acceptance bullet means a node is missing. (`
|
|
21
|
+
feature-level acceptance bullet means a node is missing. (`validate.cypher` enforces this.)
|
|
21
22
|
7. **No node restates a global acceptance bullet verbatim as its own criterion.** Node-level acceptance
|
|
22
23
|
is the local, observable slice; the `Acceptance` nodes keep the whole-feature bar.
|
|
23
24
|
8. **Every controlled cycle has a hard round cap and a dedup rule**, stated in the node(s) that form the
|
|
24
25
|
cycle — not left implicit.
|
|
25
|
-
9. **`DEPENDS_ON` is acyclic.** `
|
|
26
|
-
they don't.
|
|
26
|
+
9. **`DEPENDS_ON` is acyclic.** `validate.cypher` rejects a cycle; confirm it doesn't.
|
|
27
27
|
10. **Every `chore` node is depended on by at least one other node.** A chore nobody depends on isn't in
|
|
28
|
-
scope — it's either wired wrong or shouldn't exist. (`
|
|
28
|
+
scope — it's either wired wrong or shouldn't exist. (`validate.cypher` enforces this.)
|
|
29
29
|
11. **`failure_policy` fits the node type**, per `reference/failure-policies.md` — not defaulted to
|
|
30
30
|
`retry` everywhere without thinking about it.
|
|
31
31
|
12. **`model_tier` is `strong` only where judgement, not throughput, is the bottleneck** — see
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Talking to the gbuild store
|
|
2
|
+
|
|
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 full operating
|
|
5
|
+
manual; the skills that only run plugin scripts carry the short version of these rules inline.
|
|
6
|
+
|
|
7
|
+
## Requirement
|
|
8
|
+
|
|
9
|
+
`cypherlite` 0.7.0 or newer must be on PATH. Check first, in every skill:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
cypherlite --version
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
If the command is missing, stop and tell the user gbuild needs it:
|
|
16
|
+
`curl -sSfL https://neo4j-labs.github.io/cypherlite/install.sh | bash`. Never fall back to reading or
|
|
17
|
+
editing `graph.json` by hand.
|
|
18
|
+
|
|
19
|
+
## One writer, one command at a time
|
|
20
|
+
|
|
21
|
+
CypherLite takes an exclusive lock on the store while a process has it open. A second process — reader
|
|
22
|
+
or writer — fails with `storage locked` (exit 1). So:
|
|
23
|
+
|
|
24
|
+
- **Only the skill running in the main context runs `cypherlite` against the store.** Subagents never
|
|
25
|
+
do: give them what they need in their prompt, have them report results in their final message, and
|
|
26
|
+
record those results yourself.
|
|
27
|
+
- **Never run two `cypherlite` commands against the same store at once** — no parallel tool calls on it.
|
|
28
|
+
Fan-out is for agents, not for store access.
|
|
29
|
+
- On `storage locked`, another process holds the store (another gbuild command, a REPL left open). Wait a
|
|
30
|
+
few seconds and retry, up to 3 times; then stop and report. Never delete `.cypherlite.lock`.
|
|
31
|
+
|
|
32
|
+
## Invocation
|
|
33
|
+
|
|
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`.
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
# a plugin script
|
|
39
|
+
cypherlite DB --mode jsonl < ${CLAUDE_PLUGIN_ROOT}/cypher/progress.cypher
|
|
40
|
+
|
|
41
|
+
# a plugin script with parameters
|
|
42
|
+
cypherlite DB --mode jsonl --param 'slug="b-consume-doubled"' < ${CLAUDE_PLUGIN_ROOT}/cypher/node.cypher
|
|
43
|
+
|
|
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"
|
|
46
|
+
|
|
47
|
+
# a write you author (plan, reopen, fixes) — quoted heredoc delimiter, so the shell expands nothing
|
|
48
|
+
cypherlite DB <<'CYPHER'
|
|
49
|
+
.begin
|
|
50
|
+
MATCH (n:GbuildNode {slug: 'b-consume-doubled'}) SET n.title = 'Consume the value, double it';
|
|
51
|
+
.commit
|
|
52
|
+
.checkpoint
|
|
53
|
+
CYPHER
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Rules
|
|
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.
|
|
61
|
+
- **Parameters are JSON.** Every `--param` value is JSON, single-quoted for the shell: strings in double
|
|
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.
|
|
64
|
+
- **String literals** in Cypher you author: single quotes by default; double quotes when the text
|
|
65
|
+
contains an apostrophe (`"gbuild's"`); backslash-escape (`\'`, `\"`) when it contains both.
|
|
66
|
+
- **Authored writes are atomic and checkpointed.** Wrap them in `.begin` / `.commit` — if any statement
|
|
67
|
+
fails, the whole transaction is discarded — and end with `.checkpoint` so `graph.json` on disk (what
|
|
68
|
+
git commits) is always current. The plugin write scripts already do both.
|
|
69
|
+
- **Values are scalars or lists of scalars.** CypherLite cannot store a map as a property value;
|
|
70
|
+
JSON-encode anything nested into a string.
|
|
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.
|
|
73
|
+
- **Exit code 1 is a failure** — lock, constraint violation (`ConstraintValidationFailed`), syntax. Read
|
|
74
|
+
stderr, fix, retry the whole transaction.
|
|
75
|
+
|
|
76
|
+
## Creating a store
|
|
77
|
+
|
|
78
|
+
Only `plan` does this, once per feature:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
mkdir -p .gbuild/<feature>/db
|
|
82
|
+
cypherlite .gbuild/<feature>/db --snapshot-format json < ${CLAUDE_PLUGIN_ROOT}/cypher/schema.cypher
|
|
83
|
+
printf '*\n!.gitignore\n!graph.json\n' > .gbuild/<feature>/db/.gitignore
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`--snapshot-format json` belongs on this first call only (it applies to an empty directory). It installs
|
|
87
|
+
the schema constraints and pins the JSON codec, so `graph.json` exists from the start.
|
|
88
|
+
|
|
89
|
+
## Script catalog
|
|
90
|
+
|
|
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`.
|
|
@@ -6,10 +6,10 @@ returns `fail`, or the node's own agent errors out.
|
|
|
6
6
|
| policy | `run` does |
|
|
7
7
|
| ---------- | ----------------------------------------------------------------------------------------------------- |
|
|
8
8
|
| `retry` | Re-dispatch the same node, same contract, up to 2 more attempts. Same agent role, fresh context. |
|
|
9
|
-
| `fallback` | On failure, dispatch a named alternate approach (`plan` records it
|
|
10
|
-
| `skip` | Mark `cancelled
|
|
11
|
-
| `repair` | Feed the reviewer's `
|
|
12
|
-
| `escalate` | Stop dispatching this node's dependents,
|
|
9
|
+
| `fallback` | On failure, dispatch a named alternate approach (`plan` records it as an external `INPUT` field named `fallback`, whose `shape` describes the approach) instead of retrying the same approach. |
|
|
10
|
+
| `skip` | Mark `cancelled` (`set-status.cypher`), not `failed`. Downstream nodes treat it as satisfied (see the status lifecycle in `graph-format.md`). Use only when the node is genuinely optional to the destination. |
|
|
11
|
+
| `repair` | Feed the reviewer's failed criteria (the latest `Review`'s `failed_criteria`) back into a repair pass targeting just those criteria, then re-review. Bounded: 2 repair attempts, then escalate. |
|
|
12
|
+
| `escalate` | Stop dispatching this node's dependents, set it `failed` (`set-status.cypher`), and surface it to the user in `/gbuild:status` as needing a decision. `run` does not guess a fix. |
|
|
13
13
|
| `stop` | Halt the entire run — not just this node's downstream. Reserved for failures that put the whole graph's premise in doubt (e.g. a `decision` node's answer contradicts an already-completed node). |
|
|
14
14
|
|
|
15
15
|
## Choosing a policy at plan time
|
|
@@ -25,7 +25,7 @@ returns `fail`, or the node's own agent errors out.
|
|
|
25
25
|
|
|
26
26
|
## Bounds are mandatory
|
|
27
27
|
|
|
28
|
-
`retry`, `repair`, and `fallback` all have hard caps enforced by `run
|
|
29
|
-
|
|
28
|
+
`retry`, `repair`, and `fallback` all have hard caps enforced by `run`: 2 attempts, counted from the
|
|
29
|
+
node's `Review` nodes. No policy allows an unbounded loop — that's what turns a bounded
|
|
30
30
|
retry into the "controlled cycle" shape's job (`reference/shapes.md`), which has its own explicit round
|
|
31
31
|
cap and dedup rule.
|
|
@@ -1,131 +1,78 @@
|
|
|
1
|
-
#
|
|
1
|
+
# gbuild graph model — normative
|
|
2
2
|
|
|
3
3
|
This file is normative. Where a skill disagrees with this doc, this doc wins.
|
|
4
4
|
|
|
5
|
-
One
|
|
5
|
+
One CypherLite store per feature: `.gbuild/<feature>/db/`. `plan` creates and charts it, `run` records
|
|
6
|
+
progress in it, and every skill reads it — **all through Cypher**. No skill reads or writes `graph.json`
|
|
7
|
+
directly; it is CypherLite's snapshot of the store. How to talk to the store (the single-writer rule,
|
|
8
|
+
invocation, quoting, the script catalog) is in `reference/cypher.md`.
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
`templates/example.cypher` is a complete worked example — a 4-node diamond — written exactly the way
|
|
11
|
+
`plan` writes a new feature.
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
cypherlite .gbuild/<feature>/db "MATCH (n:GbuildNode) RETURN n.slug, n.type"
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
The `db/` folder contains `graph.json` and nothing else that CypherLite cares about. Checkpoints live in the sibling `.gbuild/<feature>/nodes/` directory (see [State](#state-not-part-of-graphjson)) — deliberately *outside* `db/`, because CypherLite reads a directory named `nodes` as Parquet shards and would reject the store as "mixed snapshot codecs". Opening `db/` also makes CypherLite create a `db/wal/` directory; that is expected. **Only run read-only `MATCH … RETURN` Cypher against the plan** — a writing query could checkpoint and rewrite the write-once `graph.json`.
|
|
14
|
-
|
|
15
|
-
### Interoperability with CypherLite
|
|
16
|
-
|
|
17
|
-
`graph.json` is the *same* JSON snapshot CypherLite reads and writes — there is one store file, not a gbuild copy and a CypherLite copy. Two consequences:
|
|
18
|
-
|
|
19
|
-
- **The store must be the JSON codec.** gbuild always writes `graph.json`, so the store is JSON by construction. A Parquet store directory is not readable by gbuild's stdlib parsers.
|
|
20
|
-
- **A CypherLite checkpoint normalizes the file.** If a writing Cypher command is followed by `.checkpoint` (or the WAL rotates), CypherLite rewrites `graph.json` in full — adding `value_encoding` — and leaves `manifest.json` and `wal/` beside it. gbuild's parsers read only `nodes` / `relationships` / `next_node_id` / `next_rel_id` and ignore the rest, so the checkpointed snapshot remains valid and queryable by `graph.py` / `validate_format.py` unchanged.
|
|
21
|
-
|
|
22
|
-
CypherLite can *read* every property gbuild writes, including the `contract` map. It cannot **write** map property values via Cypher (`InvalidPropertyType`), so a node authored purely through Cypher cannot carry `contract` today — direct JSON authoring remains the way `plan` writes it.
|
|
23
|
-
|
|
24
|
-
JSON, not YAML — `scripts/graph.py` and `scripts/validate_format.py` are stdlib-only and Python's stdlib has no YAML parser. Comments are lost; that's fine, the file is agent-authored, not hand-typed. `/gbuild:status` is the human-facing view.
|
|
25
|
-
|
|
26
|
-
**Validate after every change.** `scripts/validate_format.py <path>` is the format gate — run it after the initial write, after any reopen edit, after any hand-edit, before doing anything else with the file (including committing). It exits 0 + `valid: <feature>` on success, 1 + every error joined on failure. Fix the file and re-run until it passes; never hand off or run an invalid graph.
|
|
27
|
-
|
|
28
|
-
The validator does its stdlib JSON checks always. If the `cypherlite` binary is on PATH, it *also* opens the file with CypherLite and runs a trivial `MATCH` — confirming CypherLite itself loads it, not just that the stdlib walk approves the shape. CypherLite is an optional consumer, not a dependency: if the binary is absent the cross-check is skipped silently (`--no-cypherlite` forces the skip too).
|
|
29
|
-
|
|
30
|
-
## Top level — the GraphData envelope
|
|
31
|
-
|
|
32
|
-
```jsonc
|
|
33
|
-
{
|
|
34
|
-
"nodes": [ /* Feature, Acceptance, GbuildNode — see below */ ],
|
|
35
|
-
"relationships": [ /* DEPENDS_ON, HAS_ACCEPTANCE, SATISFIES */ ],
|
|
36
|
-
"indexes": [],
|
|
37
|
-
"constraints": [],
|
|
38
|
-
"next_node_id": 8, // max(node.id) + 1 (1 if empty) — CypherLite trusts this
|
|
39
|
-
"next_rel_id": 10 // max(rel.id) + 1 (1 if empty)
|
|
40
|
-
}
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
`indexes` and `constraints` are empty for gbuild graphs — kept only because CypherLite's loader expects them. `next_node_id`/`next_rel_id` are the id allocator counters; they must equal `max(id)+1` for each kind, and the validator enforces this.
|
|
44
|
-
|
|
45
|
-
## Node labels
|
|
46
|
-
|
|
47
|
-
Three node kinds, distinguished by label:
|
|
48
|
-
|
|
49
|
-
| label | meaning | count per graph |
|
|
50
|
-
| ------------ | ---------------------------------------- | --------------- |
|
|
51
|
-
| `Feature` | the one feature this graph is for | exactly 1 |
|
|
52
|
-
| `Acceptance` | one feature-level acceptance bullet | one per bullet |
|
|
53
|
-
| `GbuildNode` | one unit of work in the dependency graph | one per node |
|
|
54
|
-
|
|
55
|
-
### `Feature` node (id 1)
|
|
13
|
+
## Store layout
|
|
56
14
|
|
|
57
|
-
```jsonc
|
|
58
|
-
{
|
|
59
|
-
"id": 1,
|
|
60
|
-
"labels": ["Feature"],
|
|
61
|
-
"properties": {
|
|
62
|
-
"feature": "gbuild-mvp", // slug, matches the .gbuild/<feature>/ dir
|
|
63
|
-
"destination": "<one line — what \"done\" looks like>",
|
|
64
|
-
"context": "<why this exists, one or two lines>",
|
|
65
|
-
"out_of_scope": ["<bullet>"]
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
15
|
```
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
```jsonc
|
|
77
|
-
{
|
|
78
|
-
"id": 2,
|
|
79
|
-
"labels": ["Acceptance"],
|
|
80
|
-
"properties": { "id": "a-1", "text": "<bullet>" }
|
|
81
|
-
}
|
|
16
|
+
.gbuild/<feature>/db/
|
|
17
|
+
graph.json CypherLite JSON snapshot — committed; always current (every write ends with .checkpoint)
|
|
18
|
+
.gitignore "*", "!.gitignore", "!graph.json" — committed
|
|
19
|
+
manifest.json engine files — ignored
|
|
20
|
+
wal/
|
|
21
|
+
.cypherlite.lock
|
|
82
22
|
```
|
|
83
23
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
24
|
+
The store is created with the JSON codec (`--snapshot-format json`, see `reference/cypher.md`) so
|
|
25
|
+
`graph.json` diffs are reviewable in a PR, and a clone holding only `graph.json` is a complete store —
|
|
26
|
+
data **and** constraints.
|
|
87
27
|
|
|
88
|
-
|
|
89
|
-
{
|
|
90
|
-
"id": 4,
|
|
91
|
-
"labels": ["GbuildNode", "Code"], // PascalCase type label, always alongside GbuildNode
|
|
92
|
-
"properties": {
|
|
93
|
-
"slug": "01-scan-existing-runners", // filename-safe kebab-case, immutable, the stable identity
|
|
94
|
-
"title": "<string>",
|
|
95
|
-
"type": "code", // lowercase enum — kept as a property too (matches the label)
|
|
96
|
-
"contract": { "input": {}, "output": {} }, // structured shapes, not prose
|
|
97
|
-
"acceptance": [ // node-level local criteria (mandatory, concrete)
|
|
98
|
-
"<concrete, checkable-by-a-fresh-context criterion>"
|
|
99
|
-
],
|
|
100
|
-
"verify": null, // slug of an *additional* verify-type node, or null
|
|
101
|
-
"failure_policy": "retry | fallback | skip | repair | escalate | stop",
|
|
102
|
-
"model_tier": "cheap | strong"
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
```
|
|
28
|
+
## Nodes
|
|
106
29
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
- **no `satisfies` property** — which global acceptance ids this node serves is expressed as `SATISFIES` edges, not an array. The validator rejects a stray `satisfies` property.
|
|
30
|
+
| label | meaning | count |
|
|
31
|
+
| ------------ | -------------------------------------------------- | ----------------- |
|
|
32
|
+
| `Feature` | the one feature this store is for | exactly 1 |
|
|
33
|
+
| `Acceptance` | one feature-level acceptance bullet | one per bullet |
|
|
34
|
+
| `GbuildNode` | one unit of work (always with a PascalCase type label) | one per node |
|
|
35
|
+
| `Field` | one named input or output of a GbuildNode's contract | one per field |
|
|
36
|
+
| `Review` | one `gbuild-reviewer` verdict on a GbuildNode | one per review attempt |
|
|
115
37
|
|
|
116
|
-
###
|
|
38
|
+
### `Feature`
|
|
117
39
|
|
|
118
|
-
|
|
40
|
+
| property | type | meaning |
|
|
41
|
+
| -------------- | -------------- | ---------------------------------------------------- |
|
|
42
|
+
| `feature` | string | the slug — matches the `.gbuild/<feature>/` dir, immutable |
|
|
43
|
+
| `destination` | string | one line: what is true once this is done |
|
|
44
|
+
| `context` | string | why this exists, one or two lines |
|
|
45
|
+
| `out_of_scope` | list of string | bullets, may be empty |
|
|
119
46
|
|
|
120
|
-
|
|
47
|
+
Keep these short — bullets, not paragraphs. Detail lives on the nodes. There is no `acceptance` property:
|
|
48
|
+
the feature-level bar is `Acceptance` nodes.
|
|
121
49
|
|
|
122
|
-
|
|
50
|
+
### `Acceptance`
|
|
123
51
|
|
|
124
|
-
|
|
52
|
+
`id` (string, `a-1`, `a-2`, …, unique) and `text` (string, non-blank). Each is linked
|
|
53
|
+
`(:Feature)-[:HAS_ACCEPTANCE]->(:Acceptance)` and covered by at least one
|
|
54
|
+
`(:GbuildNode)-[:SATISFIES]->(:Acceptance)`.
|
|
125
55
|
|
|
126
|
-
|
|
56
|
+
### `GbuildNode` — a unit of work
|
|
127
57
|
|
|
128
|
-
|
|
58
|
+
Labels: `GbuildNode` plus exactly one type label matching `type` — `(:GbuildNode:Code {...})`. The
|
|
59
|
+
universal label is for `MATCH (n:GbuildNode)`; the type label enables `MATCH (n:Code)`.
|
|
60
|
+
|
|
61
|
+
| property | type | meaning |
|
|
62
|
+
| ---------------- | -------------- | -------------------------------------------------------------- |
|
|
63
|
+
| `slug` | string | kebab-case, unique, immutable — the identity every skill keys on |
|
|
64
|
+
| `title` | string | what the user reads; refer to nodes by title in conversation |
|
|
65
|
+
| `type` | string | `research \| decision \| code \| test \| verify \| chore` |
|
|
66
|
+
| `acceptance` | list of string | node-level criteria — mandatory, non-empty, concrete |
|
|
67
|
+
| `verify` | string, optional | slug of an *additional* verify-type node; usually absent |
|
|
68
|
+
| `failure_policy` | string | `retry \| fallback \| skip \| repair \| escalate \| stop` — `reference/failure-policies.md` |
|
|
69
|
+
| `model_tier` | string | `cheap \| strong` — `reference/cost-model.md` |
|
|
70
|
+
| `status` | string | `pending` at creation — see [Status lifecycle](#status-lifecycle) |
|
|
71
|
+
| `started_at` | string, optional | implementing agent's own UTC start time, set on completion |
|
|
72
|
+
| `completed_at` | string, optional | implementing agent's own UTC finish time, set on completion |
|
|
73
|
+
|
|
74
|
+
The contract is not a property — it is the node's `Field`s (below). Which feature criteria the node
|
|
75
|
+
serves is `SATISFIES` edges, not a property.
|
|
129
76
|
|
|
130
77
|
| type | label | produces |
|
|
131
78
|
| ---------- | ---------- | -------------------------------------------------------------------- |
|
|
@@ -136,116 +83,102 @@ Every node — regardless of `type` — goes through `gbuild-reviewer` before it
|
|
|
136
83
|
| `verify` | `Verify` | a check on another node's output — never the same agent, never self |
|
|
137
84
|
| `chore` | `Chore` | mechanical work with no behavior change |
|
|
138
85
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
86
|
+
**`acceptance` must be concrete** — checkable by a fresh context with no other information. Not
|
|
87
|
+
"looks correct" or "works well": name a file, a command's exit code, a specific behavior, or a literal
|
|
88
|
+
output value.
|
|
142
89
|
|
|
143
|
-
|
|
90
|
+
**`verify` is not the review.** Every node goes through `gbuild-reviewer` before it can be completed
|
|
91
|
+
(`agents/gbuild-reviewer.md`). `verify` is for something *beyond* that: a fact-checker, an adversarial
|
|
92
|
+
multi-vote, a human-approval gate.
|
|
144
93
|
|
|
145
|
-
|
|
94
|
+
### `Field` — one contract field
|
|
146
95
|
|
|
147
|
-
|
|
96
|
+
| property | type | meaning |
|
|
97
|
+
| -------- | ------ | ----------------------------------------------------------------------- |
|
|
98
|
+
| `name` | string | unique per node and direction |
|
|
99
|
+
| `shape` | string | what the value is — `<integer>`, `<list of changed file paths>`, `<commit sha>` |
|
|
100
|
+
| `value` | scalar or list of scalars, optional | outputs only — recorded when the node completes |
|
|
148
101
|
|
|
149
|
-
|
|
102
|
+
Output values must be scalars (string, number, boolean) or lists of scalars; CypherLite cannot store a
|
|
103
|
+
map as a property value. JSON-encode anything nested into a string and say so in `shape`.
|
|
150
104
|
|
|
151
|
-
### `
|
|
105
|
+
### `Review` — one reviewer verdict
|
|
152
106
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
107
|
+
`attempt` (integer, 1-based, assigned by `record.cypher`), `verdict` (`pass | fail`),
|
|
108
|
+
`failed_criteria` (list of the acceptance criteria the reviewer failed; empty on a pass), `at` (string).
|
|
109
|
+
Reviews are never updated or deleted — the repair history stays visible to `status`.
|
|
156
110
|
|
|
157
|
-
|
|
111
|
+
## Relationships
|
|
158
112
|
|
|
159
|
-
|
|
113
|
+
| type | from → to | meaning |
|
|
114
|
+
| ---------------- | -------------------------- | ------------------------------------------------- |
|
|
115
|
+
| `HAS_ACCEPTANCE` | `Feature` → `Acceptance` | the criterion belongs to the feature |
|
|
116
|
+
| `SATISFIES` | `GbuildNode` → `Acceptance`| the node covers the criterion |
|
|
117
|
+
| `DEPENDS_ON` | `GbuildNode` → `GbuildNode`| dependent → dependency; must be acyclic |
|
|
118
|
+
| `INPUT` | `GbuildNode` → `Field` | the node reads this field |
|
|
119
|
+
| `OUTPUT` | `GbuildNode` → `Field` | the node produces this field |
|
|
120
|
+
| `FROM` | `Field` → `Field` | an input field reads an upstream node's output field |
|
|
121
|
+
| `REVIEWED` | `GbuildNode` → `Review` | a verdict on the node |
|
|
160
122
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
```
|
|
123
|
+
No other edge shapes are valid. `MATCH (n)-[:DEPENDS_ON]->(d)` is "what does n need";
|
|
124
|
+
`MATCH (n)<-[:DEPENDS_ON]-(d)` is "what waits on n".
|
|
164
125
|
|
|
165
|
-
|
|
126
|
+
### The contract: `INPUT`, `OUTPUT`, `FROM`
|
|
166
127
|
|
|
167
|
-
|
|
128
|
+
A node's contract is its fields. Every node declares at least one `OUTPUT` — a node that produces
|
|
129
|
+
nothing structured gives `gbuild-reviewer` nothing to check. An `INPUT` field with a `FROM` edge reads
|
|
130
|
+
that upstream output: when the upstream completes, the value flows to the consumer through the edge
|
|
131
|
+
(`node.cypher` resolves it). An `INPUT` without `FROM` is external — the codebase, the environment, the
|
|
132
|
+
user — and its `shape` says where it comes from.
|
|
168
133
|
|
|
169
|
-
```
|
|
170
|
-
|
|
134
|
+
```cypher
|
|
135
|
+
(b:GbuildNode)-[:DEPENDS_ON]->(a:GbuildNode)
|
|
136
|
+
(a)-[:OUTPUT]->(v:Field {name: 'value', shape: '<integer>'})
|
|
137
|
+
(b)-[:INPUT]->(:Field {name: 'value', shape: '<integer>'})-[:FROM]->(v)
|
|
171
138
|
```
|
|
172
139
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
## ID authoring rules
|
|
140
|
+
### The cut test, machine-checked
|
|
176
141
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
- On reopen: append new `Acceptance`/`GbuildNode` nodes with the next available ids, add their edges, bump the counters, then re-run `validate_format.py`.
|
|
142
|
+
Before adding `B -[:DEPENDS_ON]-> A`, ask: does B actually read something A produced? If B would run
|
|
143
|
+
exactly the same way with A deleted, it is not a dependency — it is the order you thought of them in.
|
|
144
|
+
Sequence ≠ dependency; nodes with no real edge between them belong in the same wave, not a chain.
|
|
181
145
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
`.gbuild/<feature>/nodes/<slug>.json`, one file per GbuildNode, written by `run`:
|
|
185
|
-
|
|
186
|
-
```jsonc
|
|
187
|
-
{
|
|
188
|
-
"status": "pending | in_progress | completed | failed | cancelled",
|
|
189
|
-
"output": {}, // matches the node's contract.output shape
|
|
190
|
-
"review": {
|
|
191
|
-
"verdict": "pass | fail",
|
|
192
|
-
"acceptance_results": [{ "criterion": "<text>", "passed": true }]
|
|
193
|
-
},
|
|
194
|
-
"started_at": "<ISO8601>",
|
|
195
|
-
"completed_at": "<ISO8601>"
|
|
196
|
-
}
|
|
197
|
-
```
|
|
146
|
+
In this model the question has a checkable answer, and `validate.cypher` enforces both directions:
|
|
198
147
|
|
|
199
|
-
|
|
148
|
+
- every `DEPENDS_ON` carries at least one `FROM` from B's inputs to A's outputs — no edge without data
|
|
149
|
+
(`dependency-without-from`);
|
|
150
|
+
- every `FROM` points at an output of a node B `DEPENDS_ON` — no data without an edge
|
|
151
|
+
(`from-without-dependency`).
|
|
200
152
|
|
|
201
|
-
##
|
|
153
|
+
## Status lifecycle
|
|
202
154
|
|
|
203
|
-
`
|
|
155
|
+
`status` is stored on the node; `frontier` and `blocked` are derived by `status.cypher`, never stored.
|
|
204
156
|
|
|
205
157
|
```
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
A `cancelled` dependency counts as satisfied — cancelling a node must not permanently block its dependents.
|
|
213
|
-
|
|
214
|
-
## Querying with Cypher
|
|
215
|
-
|
|
216
|
-
Because `graph.json` is a CypherLite GraphData file, the *plan structure* is queryable directly with Cypher (no conversion). Runtime status is not — it lives in sidecar checkpoint files, so `frontier`/`blocked`/`completed` come from `graph.py --status`, not pure Cypher. Dependency-derived questions are pure Cypher:
|
|
217
|
-
|
|
218
|
-
```cypher
|
|
219
|
-
-- the whole plan
|
|
220
|
-
MATCH (n:GbuildNode) RETURN n.slug, n.type, n.title
|
|
158
|
+
pending ──dispatch──▶ in_progress ──record (pass)──▶ completed
|
|
159
|
+
│
|
|
160
|
+
├──set-status──▶ failed (escalate, exhausted retry/repair, stop)
|
|
161
|
+
└──set-status──▶ cancelled (skip)
|
|
221
162
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
-- what does node d depend on (direct)
|
|
226
|
-
MATCH (n {slug:'d-join-and-sum'})-[:DEPENDS_ON]->(d) RETURN d.slug
|
|
227
|
-
|
|
228
|
-
-- full transitive dependency chain
|
|
229
|
-
MATCH (n {slug:'d-join-and-sum'})-[:DEPENDS_ON*]->(d) RETURN DISTINCT d.slug
|
|
230
|
-
|
|
231
|
-
-- what waits on a given node
|
|
232
|
-
MATCH (n)<-[:DEPENDS_ON]-(dependent) WHERE n.slug='a-produce-shared-value' RETURN dependent.slug
|
|
233
|
-
|
|
234
|
-
-- nodes with no dependencies (first wave / roots)
|
|
235
|
-
MATCH (n:GbuildNode) WHERE NOT (n)-[:DEPENDS_ON]->() RETURN n.slug
|
|
163
|
+
frontier = pending ∧ every DEPENDS_ON target completed-or-cancelled
|
|
164
|
+
blocked = pending ∧ some DEPENDS_ON target not completed-or-cancelled
|
|
165
|
+
```
|
|
236
166
|
|
|
237
|
-
|
|
238
|
-
|
|
167
|
+
A `cancelled` dependency counts as satisfied — abandoning an optional node must not wedge its
|
|
168
|
+
dependents. A `failed` one does not — dependents stay blocked rather than build on a broken foundation.
|
|
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.
|
|
239
171
|
|
|
240
|
-
|
|
241
|
-
MATCH p=(n:GbuildNode)-[:DEPENDS_ON*]->(m:GbuildNode) RETURN length(p) AS d, p ORDER BY d DESC LIMIT 1
|
|
172
|
+
## Who enforces what
|
|
242
173
|
|
|
243
|
-
|
|
244
|
-
|
|
174
|
+
| layer | enforces |
|
|
175
|
+
| ------------------------- | ------------------------------------------------------------------------ |
|
|
176
|
+
| engine constraints (`cypher/schema.cypher`) | required properties and their types; unique `slug` and `Acceptance.id` — a violating write never lands |
|
|
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` claims only frontier nodes; `record` completes a node only on a pass with every output; `set-status` never sets `completed` |
|
|
245
179
|
|
|
246
|
-
|
|
247
|
-
MATCH (a:Acceptance)<-[:SATISFIES]-(n:GbuildNode) RETURN a.id, n.slug
|
|
180
|
+
## Querying
|
|
248
181
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
+
```
|