opencode-gbuild 0.4.0 → 0.6.0
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 +59 -41
- package/agents/gbuild-auditor.md +1 -1
- package/agents/gbuild-reviewer.md +17 -10
- package/cypher/complete.cypher +23 -0
- package/cypher/dispatch.cypher +13 -0
- package/cypher/feature.cypher +21 -0
- package/cypher/node.cypher +39 -0
- package/cypher/record-review.cypher +17 -0
- package/cypher/schema.cypher +45 -0
- package/cypher/set-status.cypher +11 -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 +102 -0
- package/reference/failure-policies.md +6 -6
- package/reference/graph-format.md +148 -172
- package/reference/shapes.md +10 -9
- package/skills/plan/SKILL.md +113 -99
- package/skills/pr/SKILL.md +5 -3
- package/skills/review/SKILL.md +15 -7
- package/skills/run/SKILL.md +97 -56
- package/skills/status/SKILL.md +35 -45
- package/templates/example.cypher +57 -0
- package/templates/graph.json.lock +0 -0
- package/scripts/graph.py +0 -231
- package/scripts/test_graph.py +0 -379
- package/scripts/validate_format.py +0 -369
- package/templates/graph.json +0 -110
package/README.md
CHANGED
|
@@ -21,6 +21,8 @@ verification never grades itself.
|
|
|
21
21
|
|
|
22
22
|
## Install
|
|
23
23
|
|
|
24
|
+
gbuild needs the `cypherlite` CLI on PATH — see [Runtime](#runtime).
|
|
25
|
+
|
|
24
26
|
### Claude Code
|
|
25
27
|
|
|
26
28
|
From the marketplace:
|
|
@@ -38,17 +40,17 @@ claude --plugin-dir ./plugins/gbuild
|
|
|
38
40
|
|
|
39
41
|
### OpenCode
|
|
40
42
|
|
|
41
|
-
The same directory is also an OpenCode (V2) plugin
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
From GitHub (subdirectory selector):
|
|
43
|
+
The same directory is also an OpenCode (V2) plugin, published to npm as
|
|
44
|
+
[`opencode-gbuild`](https://www.npmjs.com/package/opencode-gbuild) — `index.ts` registers the five
|
|
45
|
+
skills and five slash commands (`/gbuild-plan`, `/gbuild-run`, `/gbuild-status`, `/gbuild-review`,
|
|
46
|
+
`/gbuild-pr`) from the same markdown the Claude plugin uses.
|
|
46
47
|
|
|
47
48
|
```bash
|
|
48
|
-
opencode plugin add
|
|
49
|
+
opencode plugin add opencode-gbuild
|
|
49
50
|
```
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
`opencode plugin update opencode-gbuild` moves to the latest published version. For local
|
|
53
|
+
development, point any `opencode.json(c)` at a checkout instead:
|
|
52
54
|
|
|
53
55
|
```jsonc
|
|
54
56
|
{
|
|
@@ -56,6 +58,14 @@ Or point any `opencode.json(c)` at a local checkout:
|
|
|
56
58
|
}
|
|
57
59
|
```
|
|
58
60
|
|
|
61
|
+
#### Releasing
|
|
62
|
+
|
|
63
|
+
The npm package is published from this directory:
|
|
64
|
+
|
|
65
|
+
1. Bump `version` in `package.json` — keep `.claude-plugin/plugin.json`'s version in sync.
|
|
66
|
+
2. `npm publish` (2FA on the account; contents are whitelisted via `files` + `.npmignore`).
|
|
67
|
+
3. Users pick it up with `opencode plugin update opencode-gbuild`.
|
|
68
|
+
|
|
59
69
|
One translation note: OpenCode plugins can't register agents, so where the Claude plugin spawns its
|
|
60
70
|
bundled `gbuild-reviewer`/`gbuild-auditor` agents, the OpenCode skills dispatch the built-in `general`
|
|
61
71
|
subagent with the same `agents/gbuild-*.md` text inlined as its brief. The review is still a fresh,
|
|
@@ -83,13 +93,13 @@ OpenCode (same order, kebab-case names):
|
|
|
83
93
|
/gbuild-pr add-oauth-login-with-github
|
|
84
94
|
```
|
|
85
95
|
|
|
86
|
-
- **`plan`**
|
|
87
|
-
`
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
96
|
+
- **`plan`** charts a feature into a CypherLite graph store at `.gbuild/<feature>/db/` — `Feature`,
|
|
97
|
+
`Acceptance`, and `GbuildNode` nodes, each node's contract as `INPUT`/`OUTPUT` `Field` nodes, and
|
|
98
|
+
`FROM` edges carrying data from one node's output to the next node's input. Every dependency edge
|
|
99
|
+
passes the cut test — and because data flow is explicit, the validator checks it; every node has
|
|
100
|
+
mandatory concrete acceptance criteria. Written and validated entirely in Cypher.
|
|
91
101
|
- **`run`** dispatches every node in the current ready wave concurrently — not one at a time — reviews
|
|
92
|
-
each node's output with a dedicated `gbuild-reviewer` subagent before
|
|
102
|
+
each node's output with a dedicated `gbuild-reviewer` subagent before recording it complete, and
|
|
93
103
|
applies the node's declared failure policy on a review failure. Re-invoking after an interruption
|
|
94
104
|
resumes only the remaining frontier.
|
|
95
105
|
- **`status`** reports the graph — frontier, blocked, in-flight, completed, each node's review verdict,
|
|
@@ -111,16 +121,15 @@ incoming edge, and a controlled cycle gets a hard round cap instead of an open-e
|
|
|
111
121
|
|
|
112
122
|
## Node statuses
|
|
113
123
|
|
|
114
|
-
A node's status
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
and each exists to answer a different question `run` has to ask on every invocation:
|
|
124
|
+
A node's status is its `status` property in the store, next to its outputs and its review history
|
|
125
|
+
(`Review` nodes, one per attempt). There are five, and each exists to answer a different question `run`
|
|
126
|
+
has to ask on every invocation:
|
|
118
127
|
|
|
119
128
|
| status | means | why it exists |
|
|
120
129
|
| ------------- | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
121
|
-
| `pending` | not started |
|
|
130
|
+
| `pending` | not started | Every node is charted `pending`. Interrupted work goes back to `pending` when the next run resets it, so nothing is left half-declared. |
|
|
122
131
|
| `in_progress` | dispatched, no verdict yet | Distinguishes "running right now" from "never started", so a resumed run doesn't double-dispatch a node whose agent was killed mid-flight. |
|
|
123
|
-
| `completed` | reviewed and passed | The only status that satisfies a dependent's edge on merit.
|
|
132
|
+
| `completed` | reviewed and passed | The only status that satisfies a dependent's edge on merit. The write refuses without a passing `Review` and a value for every output — an implementing agent cannot mark its own success. |
|
|
124
133
|
| `cancelled` | deliberately abandoned, dependents may proceed | The `skip` failure policy's landing spot. Counts as satisfied, so abandoning an optional node doesn't permanently wedge everything downstream. |
|
|
125
134
|
| `failed` | escalated, dependents held | Records that the node is genuinely stuck and needs a human. Does **not** satisfy dependents — they stay blocked rather than building on a broken foundation. |
|
|
126
135
|
|
|
@@ -130,7 +139,7 @@ without me*, `failed` says *stop and wait*. Collapsing them into one "didn't wor
|
|
|
130
139
|
`run` to guess which one a given failure meant.
|
|
131
140
|
|
|
132
141
|
Everything else the tooling reports — `frontier`, `blocked`, `in_flight`, `waves` — is **derived** from
|
|
133
|
-
these five by `
|
|
142
|
+
these five by `cypher/status.cypher`, never stored. Nothing can drift out of sync with itself, and the
|
|
134
143
|
frontier is a query result rather than a judgement call:
|
|
135
144
|
|
|
136
145
|
```
|
|
@@ -143,12 +152,12 @@ in_flight = in_progress
|
|
|
143
152
|
|
|
144
153
|
Subagents are forked, so they cannot talk to you directly — a subagent's final message is a tool result,
|
|
145
154
|
not chat output. Anything it finds that needs a human decision therefore travels a specific path:
|
|
146
|
-
the subagent reports it, `run`
|
|
155
|
+
the subagent reports it, `run` records it, and `run`'s report surfaces it. Nothing gets silently
|
|
147
156
|
resolved by the agent that found it, and nothing gets routed around.
|
|
148
157
|
|
|
149
158
|
Three things can trigger it:
|
|
150
159
|
|
|
151
|
-
- **A node exhausts its failure policy.** `escalate`
|
|
160
|
+
- **A node exhausts its failure policy.** `escalate` sets the node `failed`, stops dispatching
|
|
152
161
|
its dependents (siblings elsewhere in the graph keep going), and surfaces it as needing a decision.
|
|
153
162
|
`repair` escalates the same way once its 2 attempts are spent; `retry` and `fallback` are capped at 2
|
|
154
163
|
attempts too, so no policy can loop unbounded. See `reference/failure-policies.md`.
|
|
@@ -167,21 +176,29 @@ decision is wanted; neither guesses.
|
|
|
167
176
|
|
|
168
177
|
## Runtime
|
|
169
178
|
|
|
170
|
-
|
|
171
|
-
required. That's also why the graph file is `graph.json` rather than YAML: Python's stdlib has no YAML
|
|
172
|
-
parser, and adding PyYAML would defeat the zero-extra-dependencies goal. The file is written and read by
|
|
173
|
-
agents, not hand-edited, so losing comments costs little — `/gbuild:status` is the human-facing view.
|
|
179
|
+
gbuild **requires the [CypherLite](https://github.com/neo4j-labs/cypherlite) CLI**, 0.7.0 or newer:
|
|
174
180
|
|
|
175
181
|
```bash
|
|
176
|
-
|
|
177
|
-
python3 plugins/gbuild/scripts/graph.py .gbuild/<feature>/graph.json --status # frontier/blocked/waves
|
|
178
|
-
python3 -m unittest plugins/gbuild/scripts/test_graph.py
|
|
182
|
+
curl -sSfL https://neo4j-labs.github.io/cypherlite/install.sh | bash
|
|
179
183
|
```
|
|
180
184
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
+
Every skill reads and writes the graph with `cypherlite` and nothing else — there is no Python and no
|
|
186
|
+
direct JSON editing. The plugin ships the queries as `.cypher` files; skills pipe them into the store:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
cypherlite .gbuild/<feature>/db -json < plugins/gbuild/cypher/validate.cypher # format gate: [] = valid
|
|
190
|
+
cypherlite .gbuild/<feature>/db -json < plugins/gbuild/cypher/status.cypher # frontier/blocked/waves
|
|
191
|
+
cypherlite .gbuild/<feature>/db -json "MATCH (n:GbuildNode) RETURN n.slug, n.status" # anything else
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Required properties and their types are engine constraints, so an invalid write never lands; the rest of
|
|
195
|
+
the contract (enums, edge shapes, coverage, acyclicity, the cut test) is `validate.cypher`. CypherLite
|
|
196
|
+
locks a store while a process has it open, so only the main-context skill touches it, one command at a
|
|
197
|
+
time — subagents get their inputs in their prompt and report back. `reference/cypher.md` has the full
|
|
198
|
+
operating rules.
|
|
199
|
+
|
|
200
|
+
Tests (repo-root `npm test`) run the Cypher layer against the real binary: `cypher.test.ts` drives the
|
|
201
|
+
worked example through a full run and checks every validator rule and write guard.
|
|
185
202
|
|
|
186
203
|
## Layout
|
|
187
204
|
|
|
@@ -189,16 +206,17 @@ silently; `--no-cypherlite` forces the skip.
|
|
|
189
206
|
plugins/gbuild/
|
|
190
207
|
agents/gbuild-reviewer.md # per-node review, ported from hone-ai's reviewer, never self-review
|
|
191
208
|
agents/gbuild-auditor.md # end-of-branch maintainability audit, ported from hone-ai's auditor
|
|
192
|
-
reference/ # graph-format.md
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
templates/graph.json # a worked 4-node diamond example (CypherLite GraphData)
|
|
209
|
+
reference/ # graph-format.md (normative model) + cypher.md (how to talk to the store); shapes/failure-policies/cost-model/checklist inform plan
|
|
210
|
+
cypher/ # schema, validate, status/node/feature reads, and run's write scripts
|
|
211
|
+
templates/example.cypher # a worked 4-node diamond, written the way plan writes a feature
|
|
196
212
|
skills/{plan,run,status,review,pr}/
|
|
197
213
|
index.ts # OpenCode plugin entry: registers the skills + /gbuild-* commands, translating paths/names
|
|
198
214
|
package.json # OpenCode plugin manifest (npm package "opencode-gbuild")
|
|
199
|
-
|
|
215
|
+
cypher.test.ts # node --test contract tests for the Cypher layer (repo-root `npm test`)
|
|
216
|
+
smoke.test.ts # node --test smoke test for the OpenCode translation
|
|
200
217
|
```
|
|
201
218
|
|
|
202
|
-
State lives outside the plugin, in the project:
|
|
203
|
-
|
|
204
|
-
|
|
219
|
+
State lives outside the plugin, in the project: one CypherLite store per feature at
|
|
220
|
+
`.gbuild/<feature>/db/`. It holds the plan, every node's status and output values, and the full review
|
|
221
|
+
history. Only `db/graph.json` (CypherLite's JSON snapshot, kept current by every write) and the store's
|
|
222
|
+
own `.gitignore` are committed.
|
package/agents/gbuild-auditor.md
CHANGED
|
@@ -7,7 +7,7 @@ description: Strict end-of-feature maintainability auditor for a gbuild feature
|
|
|
7
7
|
|
|
8
8
|
Unusually strict review of implementation quality, maintainability, and abstraction health. Push for **ambitious** restructurings — "code judo" moves that preserve behavior while making the code dramatically simpler, smaller, and more direct. Delete complexity; don't rearrange it.
|
|
9
9
|
|
|
10
|
-
The caller passes you the resolved feature slug and
|
|
10
|
+
The caller passes you the resolved feature slug and the feature context it read from the gbuild store: the `destination`, the acceptance bar (each criterion with the nodes covering it), and every node's recorded output values. Use it for scoping context only — you are auditing the branch's code, not re-grading individual nodes (`gbuild-reviewer` already did that per node, against each node's own contract; your job is the thing no per-node pass can see — what the accumulated diff did to the codebase as a whole). Do not open `.gbuild/` or run `cypherlite` — the caller owns the store. Do NOT write any file — output your audit as your final message exclusively.
|
|
11
11
|
|
|
12
12
|
## Core Prompt
|
|
13
13
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gbuild-reviewer
|
|
3
|
-
description: Reviews a gbuild node's output against its contract and acceptance criteria. Runs after every node's implement pass, before it can be
|
|
3
|
+
description: Reviews a gbuild node's output against its contract and acceptance criteria. Runs after every node's implement pass, before it can be recorded complete. Never the same agent that implemented the node.
|
|
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
|
|
@@ -9,19 +9,25 @@ declared bar.
|
|
|
9
9
|
|
|
10
10
|
## WHAT YOU'RE GIVEN
|
|
11
11
|
|
|
12
|
-
- The node's
|
|
13
|
-
`
|
|
14
|
-
|
|
12
|
+
- The node's definition, as the caller read it from the gbuild store: its `slug`, `title`, `type`,
|
|
13
|
+
`inputs` (each with `name`, `shape`, and — when read from another node — the upstream `value`),
|
|
14
|
+
`outputs` (each with `name` and `shape`), and `acceptance`. The contract is those input and output
|
|
15
|
+
fields.
|
|
16
|
+
- What the node's agent actually produced, including the `outputs` object it reported.
|
|
15
17
|
- For `code`/`test`/`chore` nodes: the git diff for this node's work (`git diff HEAD`, or
|
|
16
18
|
`git diff --staged`, or `git log -1 -p` if it already committed).
|
|
17
19
|
|
|
20
|
+
Everything you need is in your prompt — do not open `.gbuild/` or run `cypherlite`; the caller owns the
|
|
21
|
+
store and records your verdict.
|
|
22
|
+
|
|
18
23
|
## REVIEW OBJECTIVE
|
|
19
24
|
|
|
20
25
|
Two checks, in order:
|
|
21
26
|
|
|
22
|
-
1. **Does
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
1. **Does every output field have a value matching its declared `shape`?** A missing field, or a value
|
|
28
|
+
that doesn't match its shape, is a fail regardless of the acceptance criteria — a downstream node
|
|
29
|
+
reading it through `FROM` will break. Values must be scalars or lists of scalars; a nested value must
|
|
30
|
+
be a JSON-encoded string.
|
|
25
31
|
2. **Does the output satisfy every bullet in `acceptance`, one at a time?** Not "does it seem fine
|
|
26
32
|
overall" — go bullet by bullet, pass or fail each one individually, cite why.
|
|
27
33
|
|
|
@@ -89,6 +95,7 @@ Lead with the acceptance checklist, one line per bullet:
|
|
|
89
95
|
Then, only if there are findings beyond acceptance: `Issue` or `Suggestion`, each with a `Priority`
|
|
90
96
|
(`critical | high | medium | low`).
|
|
91
97
|
|
|
92
|
-
Finish with one verdict line: `VERDICT: pass` only if
|
|
93
|
-
acceptance bullet passed — otherwise `VERDICT: fail`. A single failed acceptance bullet or
|
|
94
|
-
|
|
98
|
+
Finish with one verdict line: `VERDICT: pass` only if every output field has a value matching its shape
|
|
99
|
+
and every acceptance bullet passed — otherwise `VERDICT: fail`. A single failed acceptance bullet or an
|
|
100
|
+
output shape mismatch is enough to fail the whole node, even if everything else is clean. The caller
|
|
101
|
+
records each `[fail]` line's criterion as the review's failed criteria.
|
|
@@ -0,0 +1,23 @@
|
|
|
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
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Mark frontier nodes in_progress just before run fans them out. Only nodes
|
|
2
|
+
// that are pending AND whose every DEPENDS_ON target is completed or cancelled
|
|
3
|
+
// are moved; anything else is silently skipped, so compare `dispatched`
|
|
4
|
+
// against what you asked for.
|
|
5
|
+
// cypherlite .gbuild/<slug>/db -json --param 'slugs=["a","b"]' < cypher/dispatch.cypher
|
|
6
|
+
UNWIND $slugs AS s
|
|
7
|
+
MATCH (n:GbuildNode {slug: s})
|
|
8
|
+
WHERE n.status = 'pending'
|
|
9
|
+
AND NOT EXISTS { MATCH (n)-[:DEPENDS_ON]->(d:GbuildNode) WHERE NOT d.status IN ['completed', 'cancelled'] }
|
|
10
|
+
SET n.status = 'in_progress'
|
|
11
|
+
RETURN n.slug AS dispatched
|
|
12
|
+
ORDER BY dispatched;
|
|
13
|
+
.checkpoint
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Feature-level context: destination, the acceptance bar with the nodes that
|
|
2
|
+
// cover each criterion, and every node's recorded output values. What review
|
|
3
|
+
// hands the auditor and what pr builds the description from.
|
|
4
|
+
// cypherlite .gbuild/<slug>/db -json < cypher/feature.cypher
|
|
5
|
+
MATCH (f:Feature)
|
|
6
|
+
RETURN f.feature AS feature,
|
|
7
|
+
f.destination AS destination,
|
|
8
|
+
f.context AS context,
|
|
9
|
+
f.out_of_scope AS out_of_scope,
|
|
10
|
+
COLLECT {
|
|
11
|
+
MATCH (f)-[:HAS_ACCEPTANCE]->(a:Acceptance)
|
|
12
|
+
WITH a ORDER BY a.id
|
|
13
|
+
RETURN {id: a.id, text: a.text,
|
|
14
|
+
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;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Everything needed to dispatch or review one node, with inputs resolved
|
|
2
|
+
// through FROM edges to the upstream output values. An input with no FROM is
|
|
3
|
+
// external (from the codebase / environment): its `from` and `value` are null.
|
|
4
|
+
// cypherlite .gbuild/<slug>/db -json --param slug=<node-slug> < cypher/node.cypher
|
|
5
|
+
MATCH (n:GbuildNode {slug: $slug})
|
|
6
|
+
RETURN n.slug AS slug,
|
|
7
|
+
n.title AS title,
|
|
8
|
+
n.type AS type,
|
|
9
|
+
n.status AS status,
|
|
10
|
+
n.acceptance AS acceptance,
|
|
11
|
+
n.verify AS verify,
|
|
12
|
+
n.failure_policy AS failure_policy,
|
|
13
|
+
n.model_tier AS model_tier,
|
|
14
|
+
COLLECT {
|
|
15
|
+
MATCH (n)-[:INPUT]->(i:Field)
|
|
16
|
+
OPTIONAL MATCH (i)-[:FROM]->(o:Field)<-[:OUTPUT]-(u:GbuildNode)
|
|
17
|
+
WITH i, o, u ORDER BY i.name
|
|
18
|
+
RETURN {name: i.name, shape: i.shape, from: u.slug + '.output.' + o.name, value: o.value}
|
|
19
|
+
} AS inputs,
|
|
20
|
+
COLLECT {
|
|
21
|
+
MATCH (n)-[:OUTPUT]->(f:Field)
|
|
22
|
+
WITH f ORDER BY f.name
|
|
23
|
+
RETURN {name: f.name, shape: f.shape, value: f.value}
|
|
24
|
+
} AS outputs,
|
|
25
|
+
COLLECT {
|
|
26
|
+
MATCH (n)-[:DEPENDS_ON]->(d:GbuildNode)
|
|
27
|
+
WITH d ORDER BY d.slug
|
|
28
|
+
RETURN d.slug
|
|
29
|
+
} AS depends_on,
|
|
30
|
+
COLLECT {
|
|
31
|
+
MATCH (n)-[:SATISFIES]->(a:Acceptance)
|
|
32
|
+
WITH a ORDER BY a.id
|
|
33
|
+
RETURN {id: a.id, text: a.text}
|
|
34
|
+
} AS satisfies,
|
|
35
|
+
COLLECT {
|
|
36
|
+
MATCH (n)-[:REVIEWED]->(r:Review)
|
|
37
|
+
WITH r ORDER BY r.attempt
|
|
38
|
+
RETURN {attempt: r.attempt, verdict: r.verdict, failed_criteria: r.failed_criteria, at: r.at}
|
|
39
|
+
} AS reviews;
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// gbuild store schema. Run once, when plan creates the store:
|
|
2
|
+
// cypherlite .gbuild/<slug>/db --snapshot-format json < cypher/schema.cypher
|
|
3
|
+
// Everything the engine can enforce at write time lives here; the rest of the
|
|
4
|
+
// contract is checked by validate.cypher.
|
|
5
|
+
CREATE CONSTRAINT feature_name FOR (n:Feature) REQUIRE n.feature IS :: STRING;
|
|
6
|
+
CREATE CONSTRAINT feature_name_exists FOR (n:Feature) REQUIRE n.feature IS NOT NULL;
|
|
7
|
+
CREATE CONSTRAINT feature_destination FOR (n:Feature) REQUIRE n.destination IS :: STRING;
|
|
8
|
+
CREATE CONSTRAINT feature_destination_exists FOR (n:Feature) REQUIRE n.destination IS NOT NULL;
|
|
9
|
+
CREATE CONSTRAINT feature_context FOR (n:Feature) REQUIRE n.context IS :: STRING;
|
|
10
|
+
CREATE CONSTRAINT feature_context_exists FOR (n:Feature) REQUIRE n.context IS NOT NULL;
|
|
11
|
+
CREATE CONSTRAINT feature_out_of_scope FOR (n:Feature) REQUIRE n.out_of_scope IS :: LIST<STRING NOT NULL>;
|
|
12
|
+
CREATE CONSTRAINT feature_out_of_scope_exists FOR (n:Feature) REQUIRE n.out_of_scope IS NOT NULL;
|
|
13
|
+
CREATE CONSTRAINT acceptance_id FOR (n:Acceptance) REQUIRE n.id IS UNIQUE;
|
|
14
|
+
CREATE CONSTRAINT acceptance_id_exists FOR (n:Acceptance) REQUIRE n.id IS NOT NULL;
|
|
15
|
+
CREATE CONSTRAINT acceptance_text FOR (n:Acceptance) REQUIRE n.text IS :: STRING;
|
|
16
|
+
CREATE CONSTRAINT acceptance_text_exists FOR (n:Acceptance) REQUIRE n.text IS NOT NULL;
|
|
17
|
+
CREATE CONSTRAINT node_slug FOR (n:GbuildNode) REQUIRE n.slug IS UNIQUE;
|
|
18
|
+
CREATE CONSTRAINT node_slug_exists FOR (n:GbuildNode) REQUIRE n.slug IS NOT NULL;
|
|
19
|
+
CREATE CONSTRAINT node_title FOR (n:GbuildNode) REQUIRE n.title IS :: STRING;
|
|
20
|
+
CREATE CONSTRAINT node_title_exists FOR (n:GbuildNode) REQUIRE n.title IS NOT NULL;
|
|
21
|
+
CREATE CONSTRAINT node_type FOR (n:GbuildNode) REQUIRE n.type IS :: STRING;
|
|
22
|
+
CREATE CONSTRAINT node_type_exists FOR (n:GbuildNode) REQUIRE n.type IS NOT NULL;
|
|
23
|
+
CREATE CONSTRAINT node_acceptance FOR (n:GbuildNode) REQUIRE n.acceptance IS :: LIST<STRING NOT NULL>;
|
|
24
|
+
CREATE CONSTRAINT node_acceptance_exists FOR (n:GbuildNode) REQUIRE n.acceptance IS NOT NULL;
|
|
25
|
+
CREATE CONSTRAINT node_failure_policy FOR (n:GbuildNode) REQUIRE n.failure_policy IS :: STRING;
|
|
26
|
+
CREATE CONSTRAINT node_failure_policy_exists FOR (n:GbuildNode) REQUIRE n.failure_policy IS NOT NULL;
|
|
27
|
+
CREATE CONSTRAINT node_model_tier FOR (n:GbuildNode) REQUIRE n.model_tier IS :: STRING;
|
|
28
|
+
CREATE CONSTRAINT node_model_tier_exists FOR (n:GbuildNode) REQUIRE n.model_tier IS NOT NULL;
|
|
29
|
+
CREATE CONSTRAINT node_status FOR (n:GbuildNode) REQUIRE n.status IS :: STRING;
|
|
30
|
+
CREATE CONSTRAINT node_status_exists FOR (n:GbuildNode) REQUIRE n.status IS NOT NULL;
|
|
31
|
+
CREATE CONSTRAINT node_verify FOR (n:GbuildNode) REQUIRE n.verify IS :: STRING;
|
|
32
|
+
CREATE CONSTRAINT node_started_at FOR (n:GbuildNode) REQUIRE n.started_at IS :: STRING;
|
|
33
|
+
CREATE CONSTRAINT node_completed_at FOR (n:GbuildNode) REQUIRE n.completed_at IS :: STRING;
|
|
34
|
+
CREATE CONSTRAINT field_name FOR (n:Field) REQUIRE n.name IS :: STRING;
|
|
35
|
+
CREATE CONSTRAINT field_name_exists FOR (n:Field) REQUIRE n.name IS NOT NULL;
|
|
36
|
+
CREATE CONSTRAINT field_shape FOR (n:Field) REQUIRE n.shape IS :: STRING;
|
|
37
|
+
CREATE CONSTRAINT field_shape_exists FOR (n:Field) REQUIRE n.shape IS NOT NULL;
|
|
38
|
+
CREATE CONSTRAINT review_attempt FOR (n:Review) REQUIRE n.attempt IS :: INTEGER;
|
|
39
|
+
CREATE CONSTRAINT review_attempt_exists FOR (n:Review) REQUIRE n.attempt IS NOT NULL;
|
|
40
|
+
CREATE CONSTRAINT review_verdict FOR (n:Review) REQUIRE n.verdict IS :: STRING;
|
|
41
|
+
CREATE CONSTRAINT review_verdict_exists FOR (n:Review) REQUIRE n.verdict IS NOT NULL;
|
|
42
|
+
CREATE CONSTRAINT review_at FOR (n:Review) REQUIRE n.at IS :: STRING;
|
|
43
|
+
CREATE CONSTRAINT review_at_exists FOR (n:Review) REQUIRE n.at IS NOT NULL;
|
|
44
|
+
CREATE CONSTRAINT review_failed_criteria FOR (n:Review) REQUIRE n.failed_criteria IS :: LIST<STRING NOT NULL>;
|
|
45
|
+
.checkpoint
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Move a node to a non-completed status: `cancelled` (skip policy), `failed`
|
|
2
|
+
// (escalate, or an exhausted retry/repair), or `pending` (reset a node an
|
|
3
|
+
// interrupted run left in_progress). `completed` is rejected here — only
|
|
4
|
+
// complete.cypher may set it, so it can enforce the review and outputs.
|
|
5
|
+
// cypherlite .gbuild/<slug>/db -json --param slug=<node-slug> --param status=cancelled \
|
|
6
|
+
// < cypher/set-status.cypher
|
|
7
|
+
MATCH (n:GbuildNode {slug: $slug})
|
|
8
|
+
WHERE $status IN ['pending', 'in_progress', 'failed', 'cancelled']
|
|
9
|
+
SET n.status = $status
|
|
10
|
+
RETURN n.slug AS slug, n.status AS status;
|
|
11
|
+
.checkpoint
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// One row per GbuildNode, in wave order. `state` is derived, never stored:
|
|
2
|
+
// frontier = pending, every DEPENDS_ON target completed or cancelled
|
|
3
|
+
// blocked = pending, some DEPENDS_ON target not completed or cancelled
|
|
4
|
+
// otherwise = the stored status (in_progress, completed, failed, cancelled)
|
|
5
|
+
// `wave` is the longest DEPENDS_ON chain below the node (0 = no dependencies),
|
|
6
|
+
// so nodes sharing a wave are safe to run concurrently.
|
|
7
|
+
// cypherlite .gbuild/<slug>/db -json < cypher/status.cypher
|
|
8
|
+
MATCH (n:GbuildNode)
|
|
9
|
+
OPTIONAL MATCH p = (n)-[:DEPENDS_ON*]->()
|
|
10
|
+
WITH n, coalesce(max(length(p)), 0) AS wave
|
|
11
|
+
OPTIONAL MATCH (n)-[:DEPENDS_ON]->(d:GbuildNode)
|
|
12
|
+
WITH n, wave, d ORDER BY d.slug
|
|
13
|
+
WITH n, wave, [x IN collect(d) WHERE NOT x.status IN ['completed', 'cancelled'] | x.slug] AS waiting_on
|
|
14
|
+
OPTIONAL MATCH (n)-[:REVIEWED]->(r:Review)
|
|
15
|
+
WITH n, wave, waiting_on, r ORDER BY r.attempt DESC
|
|
16
|
+
WITH n, wave, waiting_on, collect(r) AS reviews
|
|
17
|
+
RETURN n.slug AS slug,
|
|
18
|
+
n.title AS title,
|
|
19
|
+
n.type AS type,
|
|
20
|
+
n.status AS status,
|
|
21
|
+
CASE
|
|
22
|
+
WHEN n.status = 'pending' AND size(waiting_on) = 0 THEN 'frontier'
|
|
23
|
+
WHEN n.status = 'pending' THEN 'blocked'
|
|
24
|
+
ELSE n.status
|
|
25
|
+
END AS state,
|
|
26
|
+
wave,
|
|
27
|
+
waiting_on,
|
|
28
|
+
size(reviews) AS review_attempts,
|
|
29
|
+
reviews[0].verdict AS last_verdict,
|
|
30
|
+
reviews[0].failed_criteria AS last_failed_criteria,
|
|
31
|
+
n.started_at AS started_at,
|
|
32
|
+
n.completed_at AS completed_at
|
|
33
|
+
ORDER BY wave, slug;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// gbuild format gate. Zero rows = valid; every row is one violation.
|
|
2
|
+
// cypherlite .gbuild/<slug>/db -json < cypher/validate.cypher
|
|
3
|
+
// Required properties, their types and slug/acceptance-id uniqueness are
|
|
4
|
+
// engine constraints (schema.cypher) — a write that breaks them never lands.
|
|
5
|
+
// This query checks everything the engine can't: enums, label/type agreement,
|
|
6
|
+
// edge shapes, coverage, acyclicity, and the data-flow (FROM) cut test.
|
|
7
|
+
CALL {
|
|
8
|
+
MATCH (f:Feature) WITH count(f) AS c WHERE c <> 1
|
|
9
|
+
RETURN 'feature-count' AS check, 'expected exactly 1 Feature node, found ' + toString(c) AS detail
|
|
10
|
+
UNION ALL
|
|
11
|
+
MATCH (n) WHERE NOT (n:Feature OR n:Acceptance OR n:GbuildNode OR n:Field OR n:Review)
|
|
12
|
+
RETURN 'unknown-node' AS check, 'node with labels [' + reduce(s = '', l IN labels(n) | s + ' ' + l) + ' ] is not part of the gbuild model' AS detail
|
|
13
|
+
UNION ALL
|
|
14
|
+
MATCH (n:GbuildNode) WHERE NOT n.slug =~ '^[a-z0-9]+(-[a-z0-9]+)*$'
|
|
15
|
+
RETURN 'bad-slug' AS check, n.slug + ': slug must be kebab-case' AS detail
|
|
16
|
+
UNION ALL
|
|
17
|
+
MATCH (n:GbuildNode) WHERE NOT n.type IN ['research', 'decision', 'code', 'test', 'verify', 'chore']
|
|
18
|
+
RETURN 'bad-type' AS check, n.slug + ': invalid type ' + n.type AS detail
|
|
19
|
+
UNION ALL
|
|
20
|
+
MATCH (n:GbuildNode) WHERE NOT n.failure_policy IN ['retry', 'fallback', 'skip', 'repair', 'escalate', 'stop']
|
|
21
|
+
RETURN 'bad-failure-policy' AS check, n.slug + ': invalid failure_policy ' + n.failure_policy AS detail
|
|
22
|
+
UNION ALL
|
|
23
|
+
MATCH (n:GbuildNode) WHERE NOT n.model_tier IN ['cheap', 'strong']
|
|
24
|
+
RETURN 'bad-model-tier' AS check, n.slug + ': invalid model_tier ' + n.model_tier AS detail
|
|
25
|
+
UNION ALL
|
|
26
|
+
MATCH (n:GbuildNode) WHERE NOT n.status IN ['pending', 'in_progress', 'completed', 'failed', 'cancelled']
|
|
27
|
+
RETURN 'bad-status' AS check, n.slug + ': invalid status ' + n.status AS detail
|
|
28
|
+
UNION ALL
|
|
29
|
+
WITH {research: 'Research', decision: 'Decision', code: 'Code', test: 'Test', verify: 'Verify', chore: 'Chore'} AS label
|
|
30
|
+
MATCH (n:GbuildNode)
|
|
31
|
+
WITH n, label[n.type] AS want,
|
|
32
|
+
[l IN labels(n) WHERE l IN ['Research', 'Decision', 'Code', 'Test', 'Verify', 'Chore']] AS have
|
|
33
|
+
WHERE want IS NOT NULL AND have <> [want]
|
|
34
|
+
RETURN 'type-label' AS check, n.slug + ': type ' + n.type + ' needs exactly the type label ' + want AS detail
|
|
35
|
+
UNION ALL
|
|
36
|
+
MATCH (n:GbuildNode) WHERE size(n.acceptance) = 0 OR any(c IN n.acceptance WHERE trim(c) = '')
|
|
37
|
+
RETURN 'bad-acceptance' AS check, n.slug + ': acceptance must be a non-empty list of non-blank criteria' AS detail
|
|
38
|
+
UNION ALL
|
|
39
|
+
MATCH (n:GbuildNode) WHERE n.verify IS NOT NULL AND NOT EXISTS { MATCH (v:GbuildNode) WHERE v.slug = n.verify }
|
|
40
|
+
RETURN 'verify-target' AS check, n.slug + ': verify names unknown node ' + n.verify AS detail
|
|
41
|
+
UNION ALL
|
|
42
|
+
MATCH (a:Acceptance) WHERE NOT a.id =~ '^a-[0-9]+$' OR trim(a.text) = ''
|
|
43
|
+
RETURN 'bad-acceptance-node' AS check, a.id + ': id must look like a-1 and text must be non-blank' AS detail
|
|
44
|
+
UNION ALL
|
|
45
|
+
MATCH (a:Acceptance) WHERE NOT EXISTS { (:Feature)-[:HAS_ACCEPTANCE]->(a) }
|
|
46
|
+
RETURN 'acceptance-unlinked' AS check, a.id + ': no HAS_ACCEPTANCE from the Feature' AS detail
|
|
47
|
+
UNION ALL
|
|
48
|
+
MATCH (a:Acceptance) WHERE NOT EXISTS { (:GbuildNode)-[:SATISFIES]->(a) }
|
|
49
|
+
RETURN 'acceptance-uncovered' AS check, a.id + ': no node SATISFIES it' AS detail
|
|
50
|
+
UNION ALL
|
|
51
|
+
MATCH (x)-[r]->(y)
|
|
52
|
+
WHERE NOT ((type(r) = 'HAS_ACCEPTANCE' AND x:Feature AND y:Acceptance)
|
|
53
|
+
OR (type(r) = 'SATISFIES' AND x:GbuildNode AND y:Acceptance)
|
|
54
|
+
OR (type(r) = 'DEPENDS_ON' AND x:GbuildNode AND y:GbuildNode)
|
|
55
|
+
OR (type(r) IN ['INPUT', 'OUTPUT'] AND x:GbuildNode AND y:Field)
|
|
56
|
+
OR (type(r) = 'FROM' AND x:Field AND y:Field)
|
|
57
|
+
OR (type(r) = 'REVIEWED' AND x:GbuildNode AND y:Review))
|
|
58
|
+
RETURN 'bad-edge' AS check,
|
|
59
|
+
type(r) + ' from ' + coalesce(x.slug, x.id, x.name, '?') + ' to ' + coalesce(y.slug, y.id, y.name, '?') + ' is not an allowed edge' AS detail
|
|
60
|
+
UNION ALL
|
|
61
|
+
MATCH (n:GbuildNode) WHERE EXISTS { (n)-[:DEPENDS_ON*]->(n) }
|
|
62
|
+
RETURN 'cycle' AS check, n.slug + ': DEPENDS_ON cycle' AS detail
|
|
63
|
+
UNION ALL
|
|
64
|
+
MATCH (n:GbuildNode {type: 'chore'}) WHERE NOT EXISTS { ()-[:DEPENDS_ON]->(n) }
|
|
65
|
+
RETURN 'orphan-chore' AS check, n.slug + ': chore is not depended on by any node' AS detail
|
|
66
|
+
UNION ALL
|
|
67
|
+
MATCH (n:GbuildNode) WHERE NOT EXISTS { (n)-[:OUTPUT]->(:Field) }
|
|
68
|
+
RETURN 'no-output' AS check, n.slug + ': declares no OUTPUT field' AS detail
|
|
69
|
+
UNION ALL
|
|
70
|
+
MATCH (f:Field) WITH f, COUNT { ()-[:INPUT|OUTPUT]->(f) } AS owners WHERE owners <> 1
|
|
71
|
+
RETURN 'field-owner' AS check, f.name + ': field has ' + toString(owners) + ' owning nodes, want 1' AS detail
|
|
72
|
+
UNION ALL
|
|
73
|
+
MATCH (n:GbuildNode)-[r:INPUT|OUTPUT]->(f:Field)
|
|
74
|
+
WITH n, type(r) AS dir, f.name AS name, count(*) AS c WHERE c > 1
|
|
75
|
+
RETURN 'duplicate-field' AS check, n.slug + ': ' + dir + ' field ' + name + ' declared ' + toString(c) + ' times' AS detail
|
|
76
|
+
UNION ALL
|
|
77
|
+
MATCH (i:Field)-[:FROM]->(o:Field)
|
|
78
|
+
WHERE NOT EXISTS { (:GbuildNode)-[:INPUT]->(i) } OR NOT EXISTS { (:GbuildNode)-[:OUTPUT]->(o) }
|
|
79
|
+
RETURN 'bad-from' AS check, i.name + ' -> ' + o.name + ': FROM must run from an INPUT field to an OUTPUT field' AS detail
|
|
80
|
+
UNION ALL
|
|
81
|
+
MATCH (i:Field) WITH i, COUNT { (i)-[:FROM]->() } AS c WHERE c > 1
|
|
82
|
+
RETURN 'multi-from' AS check, i.name + ': input field has ' + toString(c) + ' FROM edges, want at most 1' AS detail
|
|
83
|
+
UNION ALL
|
|
84
|
+
MATCH (n:GbuildNode)-[:INPUT]->(i:Field)-[:FROM]->(:Field)<-[:OUTPUT]-(u:GbuildNode)
|
|
85
|
+
WHERE NOT EXISTS { (n)-[:DEPENDS_ON]->(u) }
|
|
86
|
+
RETURN 'from-without-dependency' AS check, n.slug + '.' + i.name + ' reads ' + u.slug + ' but does not DEPENDS_ON it' AS detail
|
|
87
|
+
UNION ALL
|
|
88
|
+
MATCH (n:GbuildNode)-[:DEPENDS_ON]->(u:GbuildNode)
|
|
89
|
+
WHERE NOT EXISTS { (n)-[:INPUT]->(:Field)-[:FROM]->(:Field)<-[:OUTPUT]-(u) }
|
|
90
|
+
RETURN 'dependency-without-from' AS check, n.slug + ' DEPENDS_ON ' + u.slug + ' but reads none of its outputs (cut test)' AS detail
|
|
91
|
+
UNION ALL
|
|
92
|
+
MATCH (n:GbuildNode {status: 'completed'}) WHERE NOT EXISTS { (n)-[:REVIEWED]->(:Review {verdict: 'pass'}) }
|
|
93
|
+
RETURN 'completed-without-pass' AS check, n.slug + ': completed without a passing Review' AS detail
|
|
94
|
+
UNION ALL
|
|
95
|
+
MATCH (n:GbuildNode {status: 'completed'})-[:OUTPUT]->(f:Field) WHERE f.value IS NULL
|
|
96
|
+
RETURN 'completed-missing-output' AS check, n.slug + '.' + f.name + ': completed node has no value for this output' AS detail
|
|
97
|
+
UNION ALL
|
|
98
|
+
MATCH (r:Review) WHERE NOT r.verdict IN ['pass', 'fail'] OR r.attempt < 1
|
|
99
|
+
RETURN 'bad-review' AS check, 'review attempt ' + toString(r.attempt) + ' has verdict ' + r.verdict AS detail
|
|
100
|
+
UNION ALL
|
|
101
|
+
MATCH (r:Review) WITH r, COUNT { (:GbuildNode)-[:REVIEWED]->(r) } AS c WHERE c <> 1
|
|
102
|
+
RETURN 'review-owner' AS check, 'review attempt ' + toString(r.attempt) + ' has ' + toString(c) + ' owning nodes, want 1' AS detail
|
|
103
|
+
}
|
|
104
|
+
RETURN check, detail
|
|
105
|
+
ORDER BY check, detail;
|
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.
|
|
4
|
+
"version": "0.6.0",
|
|
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",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"skills",
|
|
25
25
|
"agents",
|
|
26
26
|
"reference",
|
|
27
|
-
"
|
|
27
|
+
"cypher",
|
|
28
28
|
"templates"
|
|
29
29
|
],
|
|
30
30
|
"devDependencies": {
|