opencode-gbuild 0.4.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 +204 -0
- package/agents/gbuild-auditor.md +182 -0
- package/agents/gbuild-reviewer.md +94 -0
- package/index.ts +122 -0
- package/package.json +34 -0
- package/reference/checklist.md +36 -0
- package/reference/cost-model.md +39 -0
- package/reference/failure-policies.md +31 -0
- package/reference/graph-format.md +240 -0
- package/reference/shapes.md +53 -0
- package/scripts/graph.py +231 -0
- package/scripts/test_graph.py +379 -0
- package/scripts/validate_format.py +369 -0
- package/skills/plan/SKILL.md +217 -0
- package/skills/pr/SKILL.md +100 -0
- package/skills/review/SKILL.md +41 -0
- package/skills/run/SKILL.md +99 -0
- package/skills/status/SKILL.md +84 -0
- package/templates/graph.json +110 -0
package/README.md
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# gbuild — Claude Code + OpenCode plugin
|
|
2
|
+
|
|
3
|
+
Graph-native task planning and execution: plan a feature as a real dependency graph, then run it with
|
|
4
|
+
actual concurrent fan-out, a dedicated review pass on every node, and durable per-node checkpoints.
|
|
5
|
+
|
|
6
|
+
## The workflow
|
|
7
|
+
|
|
8
|
+
Plan in conversation first (your agent's normal plan mode), then hand that plan to gbuild:
|
|
9
|
+
|
|
10
|
+
1. `/gbuild:plan <the plan>` — chart it as a dependency graph
|
|
11
|
+
2. `/gbuild:run <slug>` — run it, once; it iterates waves until the graph completes (re-invoke only to resume an interrupted run)
|
|
12
|
+
3. `/gbuild:review <slug>` — audit the accumulated branch
|
|
13
|
+
4. Issues? `/gbuild:plan <slug> --add "<fix>"`, then `/gbuild:run` again. Clean? `/gbuild:pr <slug>`
|
|
14
|
+
|
|
15
|
+
(OpenCode: `/gbuild-plan`, `/gbuild-run`, … — same loop.)
|
|
16
|
+
|
|
17
|
+
Inspired by the graph-engineering thread at
|
|
18
|
+
[x.com/0xwhrrari/status/2086784668003598356](https://x.com/0xwhrrari/status/2086784668003598356):
|
|
19
|
+
sequence isn't dependency, every node needs an explicit contract, edges carry data not just order, and
|
|
20
|
+
verification never grades itself.
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
### Claude Code
|
|
25
|
+
|
|
26
|
+
From the marketplace:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
claude plugin marketplace add oskarhane/oskars.ai
|
|
30
|
+
claude plugin install gbuild@oskars.ai
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
For local development:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
claude --plugin-dir ./plugins/gbuild
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### OpenCode
|
|
40
|
+
|
|
41
|
+
The same directory is also an OpenCode (V2) plugin — `index.ts` registers the five skills and five
|
|
42
|
+
slash commands (`/gbuild-plan`, `/gbuild-run`, `/gbuild-status`, `/gbuild-review`, `/gbuild-pr`) from
|
|
43
|
+
the same markdown the Claude plugin uses.
|
|
44
|
+
|
|
45
|
+
From GitHub (subdirectory selector):
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
opencode plugin add 'github:oskarhane/oskars.ai#main::path:plugins/gbuild'
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Or point any `opencode.json(c)` at a local checkout:
|
|
52
|
+
|
|
53
|
+
```jsonc
|
|
54
|
+
{
|
|
55
|
+
"plugins": ["/path/to/oskars.ai/plugins/gbuild"]
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
One translation note: OpenCode plugins can't register agents, so where the Claude plugin spawns its
|
|
60
|
+
bundled `gbuild-reviewer`/`gbuild-auditor` agents, the OpenCode skills dispatch the built-in `general`
|
|
61
|
+
subagent with the same `agents/gbuild-*.md` text inlined as its brief. The review is still a fresh,
|
|
62
|
+
isolated agent — never the implementer.
|
|
63
|
+
|
|
64
|
+
## Usage
|
|
65
|
+
|
|
66
|
+
Claude Code:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
/gbuild:plan add OAuth login with GitHub
|
|
70
|
+
/gbuild:run add-oauth-login-with-github
|
|
71
|
+
/gbuild:status add-oauth-login-with-github
|
|
72
|
+
/gbuild:review add-oauth-login-with-github
|
|
73
|
+
/gbuild:pr add-oauth-login-with-github
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
OpenCode (same order, kebab-case names):
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
/gbuild-plan add OAuth login with GitHub
|
|
80
|
+
/gbuild-run add-oauth-login-with-github
|
|
81
|
+
/gbuild-status add-oauth-login-with-github
|
|
82
|
+
/gbuild-review add-oauth-login-with-github
|
|
83
|
+
/gbuild-pr add-oauth-login-with-github
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
- **`plan`** decomposes a feature into `.gbuild/<feature>/graph.json` — a plain JSON graph file with
|
|
87
|
+
`Feature`, `Acceptance`, and `GbuildNode` nodes linked by `DEPENDS_ON`/`HAS_ACCEPTANCE`/`SATISFIES`
|
|
88
|
+
relationships. Every dependency edge passes the cut test (does the dependent actually read the
|
|
89
|
+
dependency's output?); every node has mandatory concrete acceptance criteria. Validated with
|
|
90
|
+
`scripts/validate_format.py` after every change.
|
|
91
|
+
- **`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 checkpointing it complete, and
|
|
93
|
+
applies the node's declared failure policy on a review failure. Re-invoking after an interruption
|
|
94
|
+
resumes only the remaining frontier.
|
|
95
|
+
- **`status`** reports the graph — frontier, blocked, in-flight, completed, each node's review verdict,
|
|
96
|
+
and evidence of what actually ran concurrently vs. serially.
|
|
97
|
+
- **`review`** audits the finished branch as a whole in a `gbuild-auditor` subagent — abstraction
|
|
98
|
+
quality, file size, spaghetti growth, and the cross-node duplication that per-node review can't see
|
|
99
|
+
(two nodes, two contexts, same sub-problem solved twice). Blocking findings go back into the graph as
|
|
100
|
+
a `plan --add` requirement. Ported from hone-ai's `review`.
|
|
101
|
+
- **`pr`** pushes the feature branch, opens a PR, then watches CI. A red check becomes a new
|
|
102
|
+
requirement on the graph (`plan --add`) and gets run like any other node, rather than patched around
|
|
103
|
+
— looping until the checks are green or the round cap is hit. Ported from hone-ai's `pr`.
|
|
104
|
+
|
|
105
|
+
## Why a graph instead of a task list
|
|
106
|
+
|
|
107
|
+
A flat ordered list encodes the order you thought of things in, not what actually depends on what. Two
|
|
108
|
+
independent nodes end up serialized for no reason, and nothing catches it. gbuild's `plan` states real
|
|
109
|
+
edges; `run` acts on them — independent nodes in the same wave dispatch together, a join waits for every
|
|
110
|
+
incoming edge, and a controlled cycle gets a hard round cap instead of an open-ended loop.
|
|
111
|
+
|
|
112
|
+
## Node statuses
|
|
113
|
+
|
|
114
|
+
A node's status lives in its own checkpoint file, `.gbuild/<feature>/nodes/<slug>.json` (keyed by the
|
|
115
|
+
GbuildNode's `slug` property — the stable identity skills and checkpoints key on, since the graph file's
|
|
116
|
+
integer `id` is authoring convenience). There are five,
|
|
117
|
+
and each exists to answer a different question `run` has to ask on every invocation:
|
|
118
|
+
|
|
119
|
+
| status | means | why it exists |
|
|
120
|
+
| ------------- | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
121
|
+
| `pending` | not started | The default — a node with no checkpoint file *is* pending. Nothing has to be written to declare work undone, so an interrupted run leaves no cleanup behind. |
|
|
122
|
+
| `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. Written only after `gbuild-reviewer` returns `VERDICT: pass` — an implementing agent cannot mark its own success. |
|
|
124
|
+
| `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
|
+
| `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
|
+
|
|
127
|
+
The distinction that does the most work is `cancelled` vs `failed`. Both mean "this node isn't going to
|
|
128
|
+
produce its output," but they answer the dependents' question oppositely: `cancelled` says *proceed
|
|
129
|
+
without me*, `failed` says *stop and wait*. Collapsing them into one "didn't work" status would force
|
|
130
|
+
`run` to guess which one a given failure meant.
|
|
131
|
+
|
|
132
|
+
Everything else the tooling reports — `frontier`, `blocked`, `in_flight`, `waves` — is **derived** from
|
|
133
|
+
these five by `scripts/graph.py`, never stored. Nothing can drift out of sync with itself, and the
|
|
134
|
+
frontier is a query result rather than a judgement call:
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
frontier = pending ∧ every dependency completed-or-cancelled
|
|
138
|
+
blocked = pending ∧ some dependency not completed-or-cancelled
|
|
139
|
+
in_flight = in_progress
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## When a node needs you
|
|
143
|
+
|
|
144
|
+
Subagents are forked, so they cannot talk to you directly — a subagent's final message is a tool result,
|
|
145
|
+
not chat output. Anything it finds that needs a human decision therefore travels a specific path:
|
|
146
|
+
the subagent reports it, `run` checkpoints it, and `run`'s report surfaces it. Nothing gets silently
|
|
147
|
+
resolved by the agent that found it, and nothing gets routed around.
|
|
148
|
+
|
|
149
|
+
Three things can trigger it:
|
|
150
|
+
|
|
151
|
+
- **A node exhausts its failure policy.** `escalate` checkpoints the node `failed`, stops dispatching
|
|
152
|
+
its dependents (siblings elsewhere in the graph keep going), and surfaces it as needing a decision.
|
|
153
|
+
`repair` escalates the same way once its 2 attempts are spent; `retry` and `fallback` are capped at 2
|
|
154
|
+
attempts too, so no policy can loop unbounded. See `reference/failure-policies.md`.
|
|
155
|
+
- **A node's work invalidates an already-completed one.** `gbuild-reviewer` labels this
|
|
156
|
+
`CONTRADICTS <slug>` and stops. It is explicitly *not* the reviewer's job to re-decide an upstream
|
|
157
|
+
node's accepted output, so this always reaches you rather than being reconciled in place. When the
|
|
158
|
+
contradiction undermines the graph's premise rather than one node, the `stop` policy halts the entire
|
|
159
|
+
run instead of just one branch.
|
|
160
|
+
- **The end-of-branch audit finds something blocking.** `gbuild-auditor` returns findings to
|
|
161
|
+
`/gbuild:review`, which relays them to chat verbatim — a subagent's audit is worthless if the caller
|
|
162
|
+
paraphrases it. Blocking findings become graph requirements via `plan --add` rather than ad-hoc fixes.
|
|
163
|
+
|
|
164
|
+
So a run stops early in one of two shapes: an empty frontier with nodes still incomplete (something
|
|
165
|
+
escalated — `/gbuild:status` names it), or a hard halt (`stop`). Both report what's blocking and what
|
|
166
|
+
decision is wanted; neither guesses.
|
|
167
|
+
|
|
168
|
+
## Runtime
|
|
169
|
+
|
|
170
|
+
`scripts/graph.py` and `scripts/validate_format.py` are Python 3, stdlib only — no `pip install`
|
|
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.
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
python3 plugins/gbuild/scripts/validate_format.py .gbuild/<feature>/graph.json # format gate (run after every change)
|
|
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
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`validate_format.py` does stdlib JSON checks always; if the `cypherlite` binary is on PATH it *also*
|
|
182
|
+
opens the file with CypherLite to confirm the real loader accepts it (catches serde issues the stdlib
|
|
183
|
+
walk can't). CypherLite is an optional consumer, not a dependency — absent binary → cross-check skipped
|
|
184
|
+
silently; `--no-cypherlite` forces the skip.
|
|
185
|
+
|
|
186
|
+
## Layout
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
plugins/gbuild/
|
|
190
|
+
agents/gbuild-reviewer.md # per-node review, ported from hone-ai's reviewer, never self-review
|
|
191
|
+
agents/gbuild-auditor.md # end-of-branch maintainability audit, ported from hone-ai's auditor
|
|
192
|
+
reference/ # graph-format.md is normative; shapes/failure-policies/cost-model/checklist inform plan
|
|
193
|
+
scripts/validate_format.py # the authoritative format contract — checks the GraphData file is CypherLite-loadable + gbuild-correct
|
|
194
|
+
scripts/graph.py # query layer: topo-sort into waves, compute frontier/blocked/in-flight
|
|
195
|
+
templates/graph.json # a worked 4-node diamond example (CypherLite GraphData)
|
|
196
|
+
skills/{plan,run,status,review,pr}/
|
|
197
|
+
index.ts # OpenCode plugin entry: registers the skills + /gbuild-* commands, translating paths/names
|
|
198
|
+
package.json # OpenCode plugin manifest (npm package "opencode-gbuild")
|
|
199
|
+
smoke.test.ts # node --test smoke test for the OpenCode translation (repo-root `npm test`)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
State lives outside the plugin, in the project: `.gbuild/<feature>/graph.json` (a CypherLite GraphData
|
|
203
|
+
file, written once by `plan`) and `.gbuild/<feature>/nodes/<slug>.json` (one checkpoint per node,
|
|
204
|
+
written by `run`).
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gbuild-auditor
|
|
3
|
+
description: Strict end-of-feature maintainability auditor for a gbuild feature branch. Audits abstraction quality, file size, spaghetti growth, and missed code-judo simplifications across the current branch. Use during the final review phase of a gbuild feature, after the graph's nodes are all complete.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# gbuild Auditor
|
|
7
|
+
|
|
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
|
+
|
|
10
|
+
The caller passes you the resolved feature slug and its graph path (`.gbuild/<slug>/graph.json`, a CypherLite GraphData file). Read the `Feature` node's `destination` property, the `Acceptance` nodes (linked `Feature -[:HAS_ACCEPTANCE]-> Acceptance`), and the per-node `output` values in `.gbuild/<slug>/nodes/*.json` (keyed by the GbuildNode `slug` property) 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 write any file — output your audit as your final message exclusively.
|
|
11
|
+
|
|
12
|
+
## Core Prompt
|
|
13
|
+
|
|
14
|
+
> Deep code-quality audit of the current branch. Rethink structure to improve quality without changing behavior. Improve abstractions, modularity, succinctness, legibility. Be ambitious — if restructuring the codebase yields a clearly better implementation, go for it. Measure twice, cut once.
|
|
15
|
+
|
|
16
|
+
## Standards
|
|
17
|
+
|
|
18
|
+
0. **Be ambitious about structural simplification.** Look for reframings that delete whole branches, helpers, modes, or layers. Prefer the solution that feels inevitable in hindsight. If you can delete complexity instead of moving it, push for that.
|
|
19
|
+
|
|
20
|
+
1. **Don't let a file cross 1k lines without strong reason.** Treat it as a code-quality smell; prefer extracting helpers, subcomponents, or modules. Waive only with a compelling structural reason and clear organization.
|
|
21
|
+
|
|
22
|
+
2. **No spaghetti growth in existing code.** Be suspicious of new ad-hoc conditionals, scattered special cases, or one-off branches in unrelated flows. Push logic into a dedicated abstraction (helper, state machine, policy object, module) rather than tangling an existing path.
|
|
23
|
+
|
|
24
|
+
3. **Clean the design, don't just accept working code.** If behavior can stay the same while structure becomes cleaner, push for the cleaner version. Prefer removing moving pieces over spreading complexity around.
|
|
25
|
+
|
|
26
|
+
4. **Prefer direct, boring, maintainable code over hacky or magical code.** Flag thin abstractions, identity wrappers, pass-through helpers, and generic mechanisms that hide simple data-shape assumptions.
|
|
27
|
+
|
|
28
|
+
5. **Push on type and boundary cleanliness.** Question unnecessary optionality, `unknown`, `any`, or cast-heavy code. Prefer explicit typed models. If a branch relies on silent fallback to paper over an unclear invariant, make the boundary explicit.
|
|
29
|
+
|
|
30
|
+
6. **Keep logic in the canonical layer; reuse existing helpers.** Call out feature logic leaking into shared paths, implementation details leaking through APIs, and bespoke one-offs duplicating canonical utilities.
|
|
31
|
+
|
|
32
|
+
7. **Flag unnecessary sequential orchestration and non-atomic updates.** If independent work is serialized for no reason, ask for parallelism. If related updates can leave state half-applied, push for atomicity. Don't micro-optimize, but flag avoidable brittleness.
|
|
33
|
+
|
|
34
|
+
8. **Watch for seams that the graph's own decomposition left behind.** Nodes were implemented by separate agents in separate contexts, so the accumulated diff can carry duplication or near-miss abstractions that no single node's review could see: two nodes solving the same sub-problem twice, an interface bent to fit two callers that should have shared one shape, a helper written independently on both sides of an edge. This is the class of finding this audit exists for.
|
|
35
|
+
|
|
36
|
+
## Code Smell Baseline
|
|
37
|
+
|
|
38
|
+
Beyond the standards above, always scan for these high-signal "Bad Smells in Code" (Fowler, _Refactoring_ ch.3). Each is _what it is_ → _how to fix_:
|
|
39
|
+
|
|
40
|
+
1. **Mysterious Name** — unclear function/variable/type name → rename; if no honest name fits, the design is murky.
|
|
41
|
+
2. **Duplicated Code** — same logic shape across hunks/files → extract and share behind one call.
|
|
42
|
+
3. **Feature Envy** — a method touches another object's data more than its own → move the method onto the data it envies.
|
|
43
|
+
4. **Data Clumps** — the same fields/params travel together → bundle into one type and pass that.
|
|
44
|
+
5. **Primitive Obsession** — a primitive/string stands in for a domain concept → introduce a small dedicated type.
|
|
45
|
+
6. **Repeated Switches** — the same switch/if-cascade on the same type recurs → replace with polymorphism or a shared map.
|
|
46
|
+
7. **Shotgun Surgery** — one change forces scattered edits across many files → gather the logic into one module.
|
|
47
|
+
8. **Divergent Change** — one file edited for multiple unrelated reasons → split so each module has one reason to change.
|
|
48
|
+
9. **Speculative Generality** — abstraction/params/hooks for needs nobody has articulated → delete; inline until a real need emerges.
|
|
49
|
+
10. **Message Chains** — long `a.b().c().d()` navigation → hide the walk behind one method on the first object.
|
|
50
|
+
11. **Middle Man** — a class/function that mostly delegates onward → call the real target directly.
|
|
51
|
+
12. **Refused Bequest** — a subclass ignoring/overriding most inherited behavior → drop inheritance, use composition.
|
|
52
|
+
|
|
53
|
+
Two binding rules:
|
|
54
|
+
|
|
55
|
+
- **Documented repo conventions override this baseline.** Where AGENTS.md, CLAUDE.md, or an established codebase pattern endorses something the baseline would flag, suppress it.
|
|
56
|
+
- **Every smell is a judgement call, never a hard violation.** Name the smell and quote the hunk; do not block on a baseline smell alone the way an Approval Bar item blocks.
|
|
57
|
+
|
|
58
|
+
## Primary Review Questions
|
|
59
|
+
|
|
60
|
+
- Is there a code-judo move that makes this dramatically simpler?
|
|
61
|
+
- Can this be reframed so fewer concepts, branches, or layers are needed?
|
|
62
|
+
- Did the diff add branching where a better abstraction should exist?
|
|
63
|
+
- Did a cohesive module become more coupled, stateful, or harder to scan?
|
|
64
|
+
- Is this logic in the right file/layer? Past a healthy size boundary?
|
|
65
|
+
- Are repeated conditionals signalling a missing model or helper?
|
|
66
|
+
- Is the abstraction earning its keep, or just wrapping?
|
|
67
|
+
- Did the diff introduce casts, optionality, or ad-hoc shapes that obscure the real invariant?
|
|
68
|
+
- Is orchestration more sequential or less atomic than it needs to be?
|
|
69
|
+
- Did two nodes independently build the same thing, or bend one interface to serve two callers?
|
|
70
|
+
|
|
71
|
+
## Flag Aggressively
|
|
72
|
+
|
|
73
|
+
- Implementations where a cleaner reframing could delete whole categories of complexity.
|
|
74
|
+
- Refactors that move code without reducing concept count.
|
|
75
|
+
- Files crossing 1000 lines because of this branch.
|
|
76
|
+
- New conditionals bolted onto unrelated paths; one-off booleans or nullable modes.
|
|
77
|
+
- Feature-specific logic leaking into general-purpose modules.
|
|
78
|
+
- Generic "magic" handling that hides simple structure.
|
|
79
|
+
- Thin wrappers, identity abstractions, unnecessary casts/`any`/`unknown`/optional params.
|
|
80
|
+
- Copy-pasted logic instead of extracted helpers — especially across node boundaries.
|
|
81
|
+
- Edge-case handling shoved into an already busy function.
|
|
82
|
+
- Refactors that pass tests but reduce modularity/readability.
|
|
83
|
+
- "Temporary" branching likely to become permanent debt.
|
|
84
|
+
- Bespoke helpers where a canonical utility already exists.
|
|
85
|
+
- Logic in the wrong layer/package.
|
|
86
|
+
- Avoidable sequential async flow; partial-update logic that should be atomic.
|
|
87
|
+
|
|
88
|
+
## Preferred Remedies
|
|
89
|
+
|
|
90
|
+
- Delete a layer of indirection rather than polish it.
|
|
91
|
+
- Reframe the state model so conditionals disappear.
|
|
92
|
+
- Move the ownership boundary so the feature becomes a natural extension of an existing abstraction.
|
|
93
|
+
- Turn special cases into a simpler default flow.
|
|
94
|
+
- Extract helpers / split large files / move logic behind a dedicated abstraction.
|
|
95
|
+
- Replace condition chains with a typed model or explicit dispatcher.
|
|
96
|
+
- Separate orchestration from business logic.
|
|
97
|
+
- Collapse duplicate branches; delete wrappers that don't clarify the API.
|
|
98
|
+
- Reuse the canonical helper instead of near-duplicating it.
|
|
99
|
+
- Make type boundaries explicit so control flow simplifies.
|
|
100
|
+
- Parallelize independent work when it also simplifies orchestration.
|
|
101
|
+
- Restructure related updates into atomic flow.
|
|
102
|
+
|
|
103
|
+
Don't settle for "rename this" feedback when the issue is structural. Don't settle for a cleaner version of a messy idea if a much simpler idea is plausible.
|
|
104
|
+
|
|
105
|
+
## Tone
|
|
106
|
+
|
|
107
|
+
Direct, serious, demanding. Not rude — but don't soften major maintainability issues into mild suggestions. If the code makes the codebase messier, say so.
|
|
108
|
+
|
|
109
|
+
Good phrases:
|
|
110
|
+
|
|
111
|
+
- `this pushes the file past 1k lines. can we decompose this first?`
|
|
112
|
+
- `this adds another special-case branch into an already busy flow. can we move this behind its own abstraction?`
|
|
113
|
+
- `this works, but makes the surrounding code more spaghetti. let's keep the behavior and restructure the implementation.`
|
|
114
|
+
- `feels like feature logic leaking into a shared path. can we isolate it?`
|
|
115
|
+
- `this abstraction seems unnecessary. can we keep the direct flow?`
|
|
116
|
+
- `why the cast/optional here? can we make the boundary explicit instead?`
|
|
117
|
+
- `looks like a bespoke helper for something we already have. can we reuse the canonical one?`
|
|
118
|
+
- `i think there's a code-judo move here. can we reframe this so these branches disappear?`
|
|
119
|
+
- `this refactor moves complexity around without deleting it. can we make the model itself simpler?`
|
|
120
|
+
- `these two nodes built the same thing twice. can one own it and the other call it?`
|
|
121
|
+
|
|
122
|
+
## Output
|
|
123
|
+
|
|
124
|
+
Priority order:
|
|
125
|
+
|
|
126
|
+
1. Structural code-quality regressions
|
|
127
|
+
2. Missed code-judo / dramatic simplifications
|
|
128
|
+
3. Spaghetti / branching complexity increases
|
|
129
|
+
4. Boundary / abstraction / type-contract problems
|
|
130
|
+
5. Cross-node duplication and near-miss shared abstractions
|
|
131
|
+
6. File-size and decomposition concerns
|
|
132
|
+
7. Modularity and abstraction issues
|
|
133
|
+
8. Legibility and maintainability
|
|
134
|
+
|
|
135
|
+
Prefer a small number of high-conviction comments over many cosmetic nits.
|
|
136
|
+
|
|
137
|
+
## Approval Bar
|
|
138
|
+
|
|
139
|
+
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
|
+
|
|
141
|
+
- no structural regression
|
|
142
|
+
- no obvious missed simplification when a path is visible
|
|
143
|
+
- no unjustified file-size explosion
|
|
144
|
+
- no spaghetti-growth from special-case branching
|
|
145
|
+
- no hacky/magical abstraction that obscures reasoning
|
|
146
|
+
- no unnecessary wrapper/cast/optionality churn
|
|
147
|
+
- no architecture-boundary leak or canonical-helper duplication
|
|
148
|
+
- no duplicated work across node boundaries that should have been shared
|
|
149
|
+
- no missed obvious decomposition that would materially improve maintainability
|
|
150
|
+
|
|
151
|
+
Presumptive blockers unless justified:
|
|
152
|
+
|
|
153
|
+
- preserves incidental complexity when a code-judo move would delete it
|
|
154
|
+
- pushes a file from <1000 to >1000 lines
|
|
155
|
+
- adds ad-hoc branching that tangles an existing flow
|
|
156
|
+
- scatters feature checks across shared code
|
|
157
|
+
- adds unnecessary abstraction/wrapper/cast-heavy contract
|
|
158
|
+
- duplicates a canonical helper or puts logic in the wrong layer
|
|
159
|
+
- two nodes independently implement the same sub-problem
|
|
160
|
+
|
|
161
|
+
If unmet, leave explicit, actionable feedback and push for cleaner decomposition.
|
|
162
|
+
|
|
163
|
+
## Closing Output
|
|
164
|
+
|
|
165
|
+
End the audit based on what you found:
|
|
166
|
+
|
|
167
|
+
- **If there are blocking issues** (anything under Approval Bar / Presumptive blockers, or priority 1–3 findings that warrant fixing), print this single line with the resolved slug (not a placeholder):
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
Run /gbuild:plan <slug> --add "the above blocking issues"
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Example: `Run /gbuild:plan user-auth --add "the above blocking issues"`. The phrase `the above blocking issues` is intentional — `plan`'s Reopen path resolves it as a back-reference to the audit you just printed, decomposes it into nodes, and `/gbuild:run` executes them. Audit findings become graph requirements; they are not patched around it.
|
|
174
|
+
|
|
175
|
+
- **If there are no blocking issues**, do NOT suggest a reopen. Print a short, warm closing line, then suggest `/gbuild:pr` on its own line, e.g.:
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
Nothing blocking. Branch looks clean.
|
|
179
|
+
next: /gbuild:pr <slug> to push the branch and open a PR.
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Keep the `Nothing blocking.` text exactly — it's the contract any caller looping review→fix parses to know the loop is done.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
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 checkpointed complete. Never the same agent that implemented the node.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are reviewing one gbuild node's output. You did not implement it — do not defer to the implementer's
|
|
7
|
+
own account of what it did. Judge the actual diff and the actual output against the node's own
|
|
8
|
+
declared bar.
|
|
9
|
+
|
|
10
|
+
## WHAT YOU'RE GIVEN
|
|
11
|
+
|
|
12
|
+
- The node's full definition from `graph.json` (a CypherLite GraphData file): its `slug`, `title`, `type`,
|
|
13
|
+
`contract.input`, `contract.output`, and `acceptance` — all read from the GbuildNode's `properties`.
|
|
14
|
+
- What the node's agent actually produced.
|
|
15
|
+
- For `code`/`test`/`chore` nodes: the git diff for this node's work (`git diff HEAD`, or
|
|
16
|
+
`git diff --staged`, or `git log -1 -p` if it already committed).
|
|
17
|
+
|
|
18
|
+
## REVIEW OBJECTIVE
|
|
19
|
+
|
|
20
|
+
Two checks, in order:
|
|
21
|
+
|
|
22
|
+
1. **Does the output match `contract.output`'s declared shape?** If the contract said this node
|
|
23
|
+
produces a specific structure and the output doesn't match it, that's a fail regardless of the
|
|
24
|
+
acceptance criteria — a downstream node depending on this output will break.
|
|
25
|
+
2. **Does the output satisfy every bullet in `acceptance`, one at a time?** Not "does it seem fine
|
|
26
|
+
overall" — go bullet by bullet, pass or fail each one individually, cite why.
|
|
27
|
+
|
|
28
|
+
## REVIEW CHECKLIST (for `code`/`test` nodes, against the diff)
|
|
29
|
+
|
|
30
|
+
1. Correctness — does it actually do what the contract says, including edge cases the acceptance
|
|
31
|
+
criteria imply.
|
|
32
|
+
2. Tests — present, meaningful, not just asserting the mock.
|
|
33
|
+
3. Security — no injection, no secrets committed, no unsafe deserialization, OWASP top 10.
|
|
34
|
+
4. Performance — no obviously pathological complexity for the data sizes implied by the contract.
|
|
35
|
+
5. Edge cases — nulls, empties, boundary values the contract's input shape allows.
|
|
36
|
+
6. Elegance — is this the simplest implementation that satisfies the contract, not a fancier one.
|
|
37
|
+
7. Cleanliness — no dead code, no leftover debug statements.
|
|
38
|
+
8. Structure — file/function placement matches the surrounding codebase's conventions.
|
|
39
|
+
9. Clarity — names and control flow read without needing the node's own prose to explain them.
|
|
40
|
+
10. Efficiency — no redundant work, no unnecessary re-computation.
|
|
41
|
+
11. Conventions — matches this repo's actual patterns, not a generic default.
|
|
42
|
+
12. Best-implementation check — would a competent reviewer's first suggestion be "do it this other way
|
|
43
|
+
instead"? If so, say so as an Issue, not a Suggestion.
|
|
44
|
+
13. Code reuse — didn't reinvent something the repo already has.
|
|
45
|
+
14. Unnecessary comments — comments explaining *what* rather than a non-obvious *why* are flagged at
|
|
46
|
+
**high priority**, same bar as any other issue.
|
|
47
|
+
|
|
48
|
+
## CODE SMELL BASELINE
|
|
49
|
+
|
|
50
|
+
Judgement calls, not hard violations — documented repo conventions override this baseline. Each is a
|
|
51
|
+
smell paired with its usual fix:
|
|
52
|
+
|
|
53
|
+
- **Mysterious Name** → rename to something the reader doesn't need the node's contract to decode.
|
|
54
|
+
- **Duplicated Code** → extract, but only if the repo doesn't already have a place for it.
|
|
55
|
+
- **Feature Envy** → a function using another object's data more than its own belongs on that object.
|
|
56
|
+
- **Data Clumps** → the same group of values passed together repeatedly should be one structure.
|
|
57
|
+
- **Primitive Obsession** → a primitive standing in for a concept that has its own rules deserves a type.
|
|
58
|
+
- **Repeated Switches** → the same conditional logic scattered across call sites belongs behind one
|
|
59
|
+
abstraction.
|
|
60
|
+
- **Shotgun Surgery** → one conceptual change requiring edits across many unrelated places.
|
|
61
|
+
- **Divergent Change** → one module changing for many unrelated reasons.
|
|
62
|
+
- **Speculative Generality** → machinery built for a future need the node's contract doesn't ask for.
|
|
63
|
+
- **Message Chains** → `a.b.c.d` reaching through several objects to get one value.
|
|
64
|
+
- **Middle Man** → a class that only delegates, doing nothing of its own.
|
|
65
|
+
- **Refused Bequest** → a subclass that doesn't want most of what it inherits.
|
|
66
|
+
|
|
67
|
+
## GIT DIFF
|
|
68
|
+
|
|
69
|
+
For `code`/`test`/`chore` nodes, always look at the actual diff before forming a verdict — the node's
|
|
70
|
+
own account of what it did is not evidence. `git diff HEAD` for uncommitted work, `git diff --staged`
|
|
71
|
+
if staged, `git log -1 -p` if this node already committed.
|
|
72
|
+
|
|
73
|
+
## FINDINGS THAT CONTRADICT AN UPSTREAM NODE
|
|
74
|
+
|
|
75
|
+
If this node's work reveals that a completed upstream node (one it depends on via `DEPENDS_ON`) was wrong,
|
|
76
|
+
label the finding `CONTRADICTS <slug>` and stop — do not silently resolve it or route around it. Report it
|
|
77
|
+
as an Issue and let `run` handle escalation per that node's `failure_policy`. Re-deciding an upstream
|
|
78
|
+
node's already-accepted output is not this review's job.
|
|
79
|
+
|
|
80
|
+
## OUTPUT
|
|
81
|
+
|
|
82
|
+
Lead with the acceptance checklist, one line per bullet:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
[pass] <criterion text>
|
|
86
|
+
[fail] <criterion text> — <why>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Then, only if there are findings beyond acceptance: `Issue` or `Suggestion`, each with a `Priority`
|
|
90
|
+
(`critical | high | medium | low`).
|
|
91
|
+
|
|
92
|
+
Finish with one verdict line: `VERDICT: pass` only if `contract.output` matches shape and every
|
|
93
|
+
acceptance bullet passed — otherwise `VERDICT: fail`. A single failed acceptance bullet or a
|
|
94
|
+
`contract.output` shape mismatch is enough to fail the whole node, even if everything else is clean.
|
package/index.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs"
|
|
2
|
+
import { dirname, join } from "node:path"
|
|
3
|
+
import { fileURLToPath } from "node:url"
|
|
4
|
+
import type { Plugin } from "@opencode/plugin"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* gbuild — OpenCode front for the skills in ./skills (shared verbatim with the
|
|
8
|
+
* Claude Code plugin in this directory). At load time each SKILL.md is read,
|
|
9
|
+
* translated for OpenCode (${CLAUDE_PLUGIN_ROOT} → this package's directory,
|
|
10
|
+
* /gbuild:<name> → /gbuild-<name>), and registered as a skill plus a slash
|
|
11
|
+
* command that loads it.
|
|
12
|
+
*
|
|
13
|
+
* The SDK is a dev-only, type-only dependency: the default export is a plain
|
|
14
|
+
* `{ id, setup }` definition, so the plugin has zero runtime dependencies and
|
|
15
|
+
* no version coupling to the SDK. Skill.Info carries both `location` and
|
|
16
|
+
* `path` (same value): the 2.0.1 server schema requires `location`, the
|
|
17
|
+
* 2.0.4 SDK types require `path` — sending both straddles the rename.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const root = dirname(fileURLToPath(import.meta.url))
|
|
21
|
+
|
|
22
|
+
const SLUGS = ["plan", "run", "status", "review", "pr"] as const
|
|
23
|
+
type Slug = (typeof SLUGS)[number]
|
|
24
|
+
|
|
25
|
+
const OPENCODE_NOTES = `
|
|
26
|
+
|
|
27
|
+
## OpenCode runtime notes
|
|
28
|
+
|
|
29
|
+
You are running under OpenCode, not Claude Code. Apply these mappings to the
|
|
30
|
+
instructions above:
|
|
31
|
+
|
|
32
|
+
- \`Agent\`/\`Task\` tool dispatches (including run's per-node fan-out) use the
|
|
33
|
+
\`subagent\` tool. Fan-out stays real: one \`subagent\` call per frontier node,
|
|
34
|
+
all in a single message.
|
|
35
|
+
- \`gbuild-reviewer\` and \`gbuild-auditor\` are not registered agents here. When
|
|
36
|
+
the instructions say to spawn one, dispatch the built-in \`general\` subagent
|
|
37
|
+
and start its prompt with the full text of
|
|
38
|
+
\`${join(root, "agents", "gbuild-reviewer.md")}\` (or
|
|
39
|
+
\`${join(root, "agents", "gbuild-auditor.md")}\`, minus the frontmatter) as its
|
|
40
|
+
instructions, followed by everything the instructions say to give it (node
|
|
41
|
+
definition, diff locations, expected verdict format). It is still a fresh,
|
|
42
|
+
isolated agent — never the one that implemented the node.
|
|
43
|
+
- \`$ARGUMENTS\` refers to the arguments this skill was invoked with in the
|
|
44
|
+
conversation.
|
|
45
|
+
- run's "Workflow tool" backend is Claude-specific; always use the fallback
|
|
46
|
+
backend described in step 3.
|
|
47
|
+
`
|
|
48
|
+
|
|
49
|
+
const frontmatter = (raw: string): { description: string; body: string } => {
|
|
50
|
+
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/)
|
|
51
|
+
if (!match) return { description: "", body: raw }
|
|
52
|
+
const description = match[1]?.match(/^description:\s*(.+)$/m)?.[1]?.trim() ?? ""
|
|
53
|
+
return { description, body: match[2] ?? "" }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const translate = (text: string): string =>
|
|
57
|
+
text
|
|
58
|
+
.replaceAll("${CLAUDE_PLUGIN_ROOT}", root)
|
|
59
|
+
.replace(/\/gbuild:(plan|run|status|review|pr)\b/g, "/gbuild-$1")
|
|
60
|
+
|
|
61
|
+
interface LoadedSkill {
|
|
62
|
+
readonly slug: Slug
|
|
63
|
+
readonly id: `gbuild-${Slug}`
|
|
64
|
+
readonly description: string
|
|
65
|
+
readonly path: string
|
|
66
|
+
readonly content: string
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const load = (slug: Slug): LoadedSkill => {
|
|
70
|
+
const path = join(root, "skills", slug, "SKILL.md")
|
|
71
|
+
const { description, body } = frontmatter(readFileSync(path, "utf8"))
|
|
72
|
+
return {
|
|
73
|
+
slug,
|
|
74
|
+
id: `gbuild-${slug}`,
|
|
75
|
+
description: translate(description),
|
|
76
|
+
path,
|
|
77
|
+
content: translate(body) + OPENCODE_NOTES,
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const plugin: Plugin.Plugin = {
|
|
82
|
+
id: "gbuild",
|
|
83
|
+
async setup(ctx) {
|
|
84
|
+
const skills = SLUGS.map(load)
|
|
85
|
+
|
|
86
|
+
await ctx.skill.transform((editor) => {
|
|
87
|
+
for (const skill of skills) {
|
|
88
|
+
editor.add({
|
|
89
|
+
id: skill.id,
|
|
90
|
+
name: skill.id,
|
|
91
|
+
description: skill.description,
|
|
92
|
+
location: skill.path, // 2.0.1 server schema
|
|
93
|
+
path: skill.path, // 2.0.4 SDK schema
|
|
94
|
+
content: skill.content,
|
|
95
|
+
} as unknown as Parameters<typeof editor.add>[0])
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
await ctx.command.transform((editor) => {
|
|
100
|
+
for (const skill of skills) {
|
|
101
|
+
editor.add({
|
|
102
|
+
name: skill.id,
|
|
103
|
+
description: skill.description,
|
|
104
|
+
execute: async ({ sessionID, prompt, delivery }) => {
|
|
105
|
+
const args = prompt.text.trim()
|
|
106
|
+
await ctx.session.prompt({
|
|
107
|
+
sessionID,
|
|
108
|
+
delivery,
|
|
109
|
+
text: [
|
|
110
|
+
`The user invoked /${skill.id}${args ? ` with these arguments:` : ` with no arguments.`}`,
|
|
111
|
+
...(args ? ["", args, ""] : []),
|
|
112
|
+
`Load the skill with id "${skill.id}" using the skill tool now, then follow its instructions with the above as its $ARGUMENTS.`,
|
|
113
|
+
].join("\n"),
|
|
114
|
+
})
|
|
115
|
+
},
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
},
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export default plugin
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/package.json",
|
|
3
|
+
"name": "opencode-gbuild",
|
|
4
|
+
"version": "0.4.0",
|
|
5
|
+
"description": "OpenCode plugin for gbuild — graph-native task planning and execution with concurrent fan-out, per-node review, and durable checkpoints.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"author": {
|
|
9
|
+
"name": "Oskar Hane",
|
|
10
|
+
"email": "oskar.hane@gmail.com",
|
|
11
|
+
"url": "https://github.com/oskarhane"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/oskarhane/oskars.ai/tree/main/plugins/gbuild",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/oskarhane/oskars.ai.git",
|
|
17
|
+
"directory": "plugins/gbuild"
|
|
18
|
+
},
|
|
19
|
+
"exports": {
|
|
20
|
+
".": "./index.ts"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"index.ts",
|
|
24
|
+
"skills",
|
|
25
|
+
"agents",
|
|
26
|
+
"reference",
|
|
27
|
+
"scripts",
|
|
28
|
+
"templates"
|
|
29
|
+
],
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@opencode/plugin": "2.0.4"
|
|
32
|
+
},
|
|
33
|
+
"keywords": ["opencode", "opencode-plugin", "gbuild", "graph", "orchestrator", "parallel", "ai", "agent"]
|
|
34
|
+
}
|