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
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Pushes the current gbuild feature branch to the appropriate remote, opens a pull request with a concise description that classifies the change (new feature, fix, optimization, etc.) and notes any user-facing impact, then monitors CI checks in the background and auto-triggers a graph reopen + run if any fail. Use after /gbuild:review reports nothing blocking, to publish the finished feature branch.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Publish a finished feature branch: push it to the right remote, open a well-formed pull request, then watch the PR's CI checks and drive a graph reopen automatically when a check goes red — looping until the checks are green or a round cap is hit.
|
|
6
|
+
|
|
7
|
+
## CRITICAL: main-context requirement
|
|
8
|
+
|
|
9
|
+
This skill MUST run in the **main conversation context**. It MUST NOT be invoked as a forked subagent (e.g. via the Agent/Task tool). The auto-fix step runs `/gbuild:run`, which dispatches one Agent per node — and forked agents cannot nest, so a nested fork will fail. If you are already running inside a forked agent, stop immediately and report that `/gbuild:pr` must be run from the top-level conversation.
|
|
10
|
+
|
|
11
|
+
## Step 0: Parse arguments
|
|
12
|
+
|
|
13
|
+
`$ARGUMENTS` is free-form text: an optional feature slug and an optional `--max-rounds N` flag.
|
|
14
|
+
|
|
15
|
+
- **slug**: the first token that isn't a flag. If absent, infer it — the current branch commonly ends in the slug (`plan` checks out `<prefix>/<slug>`), otherwise fall back to the most recently modified `.gbuild/*/graph.json`. If neither resolves, leave `<slug>` unresolved (the auto-fix step degrades gracefully — see Step 4).
|
|
16
|
+
- **max_rounds**: the value of `--max-rounds N` (also accept `--max-rounds=N`). Default `3` when absent.
|
|
17
|
+
|
|
18
|
+
## Step 1: Preconditions
|
|
19
|
+
|
|
20
|
+
Verify the environment before touching the remote. Stop with a clear, actionable message if any check fails:
|
|
21
|
+
|
|
22
|
+
1. **Git repo.** This skill is git + GitHub specific. If the repo is not git, stop and explain that `/gbuild:pr` only supports git + GitHub (`gh`).
|
|
23
|
+
2. **GitHub CLI.** Confirm `gh` is installed and authenticated (`gh auth status`). If not, stop and tell the user to install/authenticate `gh`.
|
|
24
|
+
3. **Base branch.** Determine the upstream default branch: `gh repo view --json defaultBranchRef -q .defaultBranchRef.name`. Call it `<base>`.
|
|
25
|
+
4. **Not on base.** Get the current branch (`git branch --show-current`). If it equals `<base>`, stop — there is nothing to open a PR for.
|
|
26
|
+
5. **Clean tree.** Check for uncommitted changes (`git status --porcelain`). `/gbuild:run` commits each node's work as it goes, so the tree should be clean. If there are uncommitted changes, warn the user and ask whether to proceed (they may want to commit first).
|
|
27
|
+
6. **Commits ahead of base.** Confirm the branch has commits the base lacks (`git log <base>..HEAD --oneline`). If empty, stop — nothing to push.
|
|
28
|
+
7. **Graph finished.** When `<slug>` resolved, run `python3 ${CLAUDE_PLUGIN_ROOT}/scripts/graph.py .gbuild/<slug>/graph.json --status`. If the frontier is non-empty or nodes are still `in_progress`, warn that the graph isn't finished and ask whether to open the PR anyway. If any node is `failed`, name it — a PR built on an escalated node is usually premature.
|
|
29
|
+
|
|
30
|
+
## Step 2: Determine the remote & push
|
|
31
|
+
|
|
32
|
+
Choose the push remote from the project's actual remotes:
|
|
33
|
+
|
|
34
|
+
1. List remotes (`git remote -v`).
|
|
35
|
+
2. **Exactly one remote** → use it.
|
|
36
|
+
3. **Multiple remotes** → look for a push-target hint in `AGENTS.md` or `CLAUDE.md` (e.g. an explicit statement that the project pushes to a fork). If a clear hint exists, use it. Otherwise, use `AskUserQuestion` to let the user pick which remote to push to (e.g. `origin` vs a fork remote).
|
|
37
|
+
|
|
38
|
+
Push the current branch and set upstream: `git push -u <remote> <branch>`.
|
|
39
|
+
|
|
40
|
+
## Step 3: Open the pull request
|
|
41
|
+
|
|
42
|
+
Build a **short, well-formed** description, then open the PR.
|
|
43
|
+
|
|
44
|
+
Gather context from:
|
|
45
|
+
|
|
46
|
+
- Commit subjects: `git log <base>..HEAD --oneline`.
|
|
47
|
+
- File overview: `git diff <base>...HEAD --stat`.
|
|
48
|
+
- When present: the `Feature` node's `destination` property and the `Acceptance` nodes (linked `Feature -[:HAS_ACCEPTANCE]-> Acceptance`) from `.gbuild/<slug>/graph.json` (a CypherLite GraphData file), and the per-node `output` values in `.gbuild/<slug>/nodes/*.json` (keyed by GbuildNode `slug`).
|
|
49
|
+
|
|
50
|
+
Derive a concise, conventional-commit-style title from the feature/branch and the changes (e.g. `feat(auth): add OAuth login`).
|
|
51
|
+
|
|
52
|
+
Write the body to this template. Keep it tight — bullets, not essays. Classify what kind of change this is (new feature, fix, optimization, replacement, refactor, or other) and lead with that. Most changes are not user-facing — only include the **User-facing impact** and **Example usage** sections when something a user actually interacts with (API, CLI, config, UI, output) changed; omit them entirely otherwise. When you include them, the **Example usage** section goes right after **User-facing impact** and shows concrete sample input and the resulting output. Omit any section that has nothing:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
## Summary
|
|
56
|
+
<1–2 sentences: what changed & why, and the kind of change — new feature / fix / optimization / replacement / refactor / other>
|
|
57
|
+
|
|
58
|
+
## Changes
|
|
59
|
+
- <bullet per notable change — what it does, not a play-by-play>
|
|
60
|
+
|
|
61
|
+
## User-facing impact
|
|
62
|
+
<Only when the change is user-facing: what users will now see or do differently (inputs and/or outputs). Drop this whole section for internal-only changes.>
|
|
63
|
+
|
|
64
|
+
## Example usage
|
|
65
|
+
<Only when the change is user-facing, immediately after User-facing impact: concrete before/after — sample input and the resulting output. Use fenced code blocks. Drop this whole section for internal-only changes.>
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
This description was auto generated
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The trailing `This description was auto generated` line is **mandatory** — it always ends the body, after every other section, and is never dropped or reworded.
|
|
72
|
+
|
|
73
|
+
Create the PR: `gh pr create --base <base> --title "<title>" --body "<body>"`. Let `gh` resolve the head branch (it handles fork `owner:branch` head refs automatically). Capture the PR URL and number (`<pr>`).
|
|
74
|
+
|
|
75
|
+
## Step 4: Monitor checks in the background → auto-fix loop
|
|
76
|
+
|
|
77
|
+
Watch the PR's CI checks and react when they fail. Run for at most `max_rounds` rounds:
|
|
78
|
+
|
|
79
|
+
1. **Launch the watch in the background.** Run `gh pr checks <pr> --watch --fail-fast` via the Bash tool with `run_in_background: true`. It exits `0` when all checks pass and non-zero when a check fails; the harness re-invokes this skill when the command exits, so continue from the result.
|
|
80
|
+
2. **Checks green (exit 0):** report success and go to Step 5.
|
|
81
|
+
3. **Checks red (non-zero exit):**
|
|
82
|
+
- Collect the failure detail: `gh pr checks <pr>` for the failed-check list, and `gh run view <run-id> --log-failed` for the failing logs.
|
|
83
|
+
- **If `<slug>` is unresolved** (the branch wasn't produced by gbuild, so there's no graph to reopen): do not auto-fix. Print which checks failed with their logs and suggest the user fix manually, then go to Step 5 with an `unresolved` status.
|
|
84
|
+
- **Otherwise, fix automatically** — via the graph, not by patching around it. gbuild has no separate fix skill; a red check is a new requirement:
|
|
85
|
+
1. Read `${CLAUDE_PLUGIN_ROOT}/skills/plan/SKILL.md` and execute its **Reopen** path inline: `--add "<requirement>"` where the requirement is the failing check stated as a concrete outcome (e.g. `CI check "lint" passes — <the actual error>`). Re-read that file and follow it verbatim rather than paraphrasing it here, so this phase auto-syncs when `plan` changes. One requirement per distinct failing check.
|
|
86
|
+
2. Run it unattended. `plan`'s Reopen asks nothing, but if executing it would prompt the user (an ambiguous decomposition), resolve it yourself from the failing logs and note the choice in the final report — `/gbuild:pr` runs without a human present.
|
|
87
|
+
3. If Reopen concludes the requirement needs no new nodes, stop the loop and go to Step 5 with an `unresolved` status — the failure isn't something the graph can act on, so surface the logs to the user.
|
|
88
|
+
4. Read `${CLAUDE_PLUGIN_ROOT}/skills/run/SKILL.md` and execute it inline against `<slug>` to run the newly-unblocked frontier. Suppress its trailing `next: /gbuild:status…` line — this skill owns the transition.
|
|
89
|
+
- After the run completes, push the new commits (`git push`) and **re-launch the watch** (next round).
|
|
90
|
+
|
|
91
|
+
4. **Round cap reached while still red:** stop, print the still-failing checks and their logs, and tell the user to inspect manually. Go to Step 5 with an `unresolved` status.
|
|
92
|
+
|
|
93
|
+
## Step 5: Final output
|
|
94
|
+
|
|
95
|
+
Report the outcome:
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
PR: <url> (checks: green | fixed after <rounds> round(s) | unresolved after <max_rounds> round(s))
|
|
99
|
+
next: /gbuild:status <slug>
|
|
100
|
+
```
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Performs a strict end-of-feature maintainability audit of the current gbuild branch — abstraction quality, file size, spaghetti growth, cross-node duplication, missed code-judo simplifications. Runs the audit in the gbuild-auditor sub-agent and relays its result to chat. Use after /gbuild:run finishes a graph, before /gbuild:pr.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Run the strict end-of-feature maintainability audit in a dedicated sub-agent, then relay its result.
|
|
6
|
+
|
|
7
|
+
This is not a second pass at what `gbuild-reviewer` already did. That agent grades one node against one
|
|
8
|
+
contract; this one grades what the whole accumulated diff did to the codebase — the duplication and
|
|
9
|
+
missed abstractions that only exist *between* nodes, which no per-node review can see.
|
|
10
|
+
|
|
11
|
+
## Arguments
|
|
12
|
+
|
|
13
|
+
`$ARGUMENTS` is optional. If present, treat it as the feature slug. If absent, infer it — the current
|
|
14
|
+
branch commonly ends in the slug (`plan` checks out `<prefix>/<slug>`), otherwise fall back to the most
|
|
15
|
+
recently modified `.gbuild/*/graph.json`. Do NOT write any file — this skill outputs to chat exclusively.
|
|
16
|
+
|
|
17
|
+
Run `python3 ${CLAUDE_PLUGIN_ROOT}/scripts/validate_format.py .gbuild/<slug>/graph.json` then
|
|
18
|
+
`python3 ${CLAUDE_PLUGIN_ROOT}/scripts/graph.py .gbuild/<slug>/graph.json --status` first. If the frontier is
|
|
19
|
+
non-empty or nodes are still `in_progress`, say the graph isn't finished and ask whether to audit anyway
|
|
20
|
+
— auditing a half-built branch produces findings that the remaining nodes were going to address.
|
|
21
|
+
|
|
22
|
+
## Run the audit
|
|
23
|
+
|
|
24
|
+
Launch the `gbuild-auditor` subagent (agents/gbuild-auditor.md — this plugin's own copy, gbuild does not
|
|
25
|
+
depend on hone-ai being installed). Pass it:
|
|
26
|
+
|
|
27
|
+
- The resolved slug and its `.gbuild/<slug>/graph.json` path.
|
|
28
|
+
- Tell it to audit the current branch.
|
|
29
|
+
|
|
30
|
+
The sub-agent runs the full audit in its own fresh context and returns the audit as its final message.
|
|
31
|
+
That message is a tool result — it is NOT shown to the user. So once it returns:
|
|
32
|
+
|
|
33
|
+
**Relay the sub-agent's entire output to chat verbatim, preserving its closing line exactly.** Do not
|
|
34
|
+
summarize, reorder, or rewrite it. The closing line is a contract:
|
|
35
|
+
|
|
36
|
+
- `Run /gbuild:plan <slug> --add "the above blocking issues"` — `plan`'s Reopen path resolves
|
|
37
|
+
`the above blocking issues` as a back-reference to the relayed audit.
|
|
38
|
+
- `Nothing blocking.` followed by `next: /gbuild:pr <slug>` — that exact `Nothing blocking.` text is
|
|
39
|
+
what any caller looping review→fix parses to end the loop.
|
|
40
|
+
|
|
41
|
+
Both must appear verbatim in the chat output for the rest of the chain to work.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Executes a gbuild graph — dispatches every ready-and-independent node in the current wave concurrently, reviews each node's output with gbuild-reviewer before checkpointing it complete, and resumes only the remaining frontier on re-invocation. Use after /gbuild:plan has written graph.json.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Run `.gbuild/$ARGUMENTS/graph.json` (the feature slug is `$ARGUMENTS`).
|
|
6
|
+
|
|
7
|
+
**CRITICAL: this skill must run in the main context, not inside a forked agent.** It dispatches its own
|
|
8
|
+
subagents per node; a subagent cannot itself fan out further subagents reliably.
|
|
9
|
+
|
|
10
|
+
## Step 0: Load and query
|
|
11
|
+
|
|
12
|
+
Load `.gbuild/<feature>/graph.json` (a CypherLite GraphData file — nodes keyed by integer `id`, with a
|
|
13
|
+
`slug` property; dependencies are `DEPENDS_ON` relationships, feature-level acceptance is `Acceptance`
|
|
14
|
+
nodes linked by `HAS_ACCEPTANCE`/`SATISFIES`). **Confirm it's well-formed first:**
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/validate_format.py .gbuild/<feature>/graph.json
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
If it prints `invalid: ...`, stop and report — do not attempt to run a graph `plan` didn't finish
|
|
21
|
+
correctly. Then take the frontier:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/graph.py .gbuild/<feature>/graph.json --status
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Take the `frontier` list (node **slugs**) from the status report. This is the set of nodes ready
|
|
28
|
+
right now — not a judgement call, the query already excludes anything blocked, in-flight, or done.
|
|
29
|
+
|
|
30
|
+
If `frontier` is empty and `completed` + `cancelled` covers every node, the graph is done — report that
|
|
31
|
+
and stop. If `frontier` is empty but nodes remain incomplete, there's a stuck cluster (likely an
|
|
32
|
+
`escalate`d or `stop`ped node) — report what's blocking per the last checkpoint and stop.
|
|
33
|
+
|
|
34
|
+
## Step 1: Establish VCS facts
|
|
35
|
+
|
|
36
|
+
Default to `git`. `git check-ignore -q .gbuild/` tells you whether to stage anything under it at all.
|
|
37
|
+
|
|
38
|
+
## Step 2: Pick a backend
|
|
39
|
+
|
|
40
|
+
**Workflow tool**, if this session has opted into multi-agent orchestration (ultracode, or the user
|
|
41
|
+
asked for a workflow explicitly) — translate `frontier` into a `pipeline()`/`parallel()` script: each
|
|
42
|
+
node is an `agent()` implement call, immediately followed by a `gbuild-reviewer` `agent()` review call
|
|
43
|
+
scoped to that node's `contract.output` + `acceptance`, using `schema` for the pass/fail verdict. Code
|
|
44
|
+
nodes touching files that could conflict with a concurrent sibling get `isolation: 'worktree'`.
|
|
45
|
+
Controlled-cycle clusters become a bounded `while` loop per `${CLAUDE_PLUGIN_ROOT}/reference/shapes.md`'s round cap. Route
|
|
46
|
+
`model_tier: strong` nodes via `opts.model` where the harness supports an override.
|
|
47
|
+
|
|
48
|
+
**Fallback** (default — no opt-in required, and always what step 3 below assumes): fire one `Agent`
|
|
49
|
+
tool call per node in the current `frontier`, **all in a single message** — this is what makes the fan-
|
|
50
|
+
out real rather than claimed. Do not dispatch them one at a time across separate messages; that's
|
|
51
|
+
exactly the serial behavior this plugin replaces.
|
|
52
|
+
|
|
53
|
+
## Step 3: Per-node dispatch (fallback backend)
|
|
54
|
+
|
|
55
|
+
For every node in the current wave's `frontier`, in one message, spawn one `Agent` call per node:
|
|
56
|
+
|
|
57
|
+
1. **IMPLEMENT.** Give the agent the node's `slug`, `title`, `type`, `contract.input` (resolved: for
|
|
58
|
+
each `DEPENDS_ON` target, read the actual `output` values from its checkpoint
|
|
59
|
+
`.gbuild/<feature>/nodes/<dep-slug>.json`), and `acceptance`. Tell it to produce `contract.output`'s
|
|
60
|
+
declared shape and, for `code`/`test`/`chore` nodes, to actually make the change in the working tree
|
|
61
|
+
(not describe it) and commit it — message `<feature>/<slug>: <what changed>`.
|
|
62
|
+
2. **REVIEW.** Once IMPLEMENT returns, spawn a `gbuild-reviewer` agent (agents/gbuild-reviewer.md — this
|
|
63
|
+
plugin's own copy, gbuild does not depend on hone-ai being installed) against the actual diff and
|
|
64
|
+
output. Never let the implementing agent review its own work.
|
|
65
|
+
3. **PASS or FAIL:**
|
|
66
|
+
- **Pass** (`VERDICT: pass`): write `.gbuild/<feature>/nodes/<slug>.json` with `status: completed`,
|
|
67
|
+
the `output`, the review verdict, and timestamps.
|
|
68
|
+
- **Fail**: apply the node's `failure_policy` (`${CLAUDE_PLUGIN_ROOT}/reference/failure-policies.md`) — `retry`/`repair`
|
|
69
|
+
re-dispatch (bounded, 2 attempts) with the reviewer's per-criterion failures fed back in;
|
|
70
|
+
`fallback` dispatches the named alternate approach; `skip` checkpoints `cancelled`; `escalate`
|
|
71
|
+
checkpoints `failed` and stops that node's downstream only; `stop` halts the entire run.
|
|
72
|
+
|
|
73
|
+
Each node's checkpoint write is independent — nodes in the same wave never touch each other's files, so
|
|
74
|
+
true concurrent dispatch is safe by construction.
|
|
75
|
+
|
|
76
|
+
## Step 4: Next wave
|
|
77
|
+
|
|
78
|
+
Re-run `graph.py --status`. If the wave that just finished unblocked new frontier nodes, repeat step 3
|
|
79
|
+
for them. Keep going until `frontier` is empty (done, or stuck — see step 0's exit conditions).
|
|
80
|
+
|
|
81
|
+
## Step 5: Report and stop
|
|
82
|
+
|
|
83
|
+
Report: which nodes ran this invocation, which passed review outright vs. needed a repair pass, what's
|
|
84
|
+
newly in the frontier or still blocked, and any `failed`/`escalate`d nodes needing a decision.
|
|
85
|
+
|
|
86
|
+
Close with the next step:
|
|
87
|
+
|
|
88
|
+
- **Graph fully done** (every node `completed` or `cancelled`) → say so plainly, then
|
|
89
|
+
`next: /gbuild:review <feature>` — the per-node reviews graded each node against its own contract;
|
|
90
|
+
nothing has yet looked at what the accumulated diff did to the codebase.
|
|
91
|
+
- **Work remains** (frontier non-empty, or nodes `failed`/`escalate`d awaiting a decision) →
|
|
92
|
+
`next: /gbuild:status <feature>`.
|
|
93
|
+
|
|
94
|
+
## Resuming
|
|
95
|
+
|
|
96
|
+
Re-invoking `/gbuild:run <feature>` after an interruption re-runs step 0 fresh: anything with a
|
|
97
|
+
`completed` or `cancelled` checkpoint is excluded from `frontier` automatically, so only the genuinely
|
|
98
|
+
remaining work re-dispatches. Nothing needs to be told what already finished — the checkpoint files are
|
|
99
|
+
the memory.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Reports a gbuild feature's graph — frontier, blocked, in-flight, completed, each node's review verdict, and the cluster shapes with evidence of what actually ran concurrently. Use to check progress or diagnose a stuck run.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Report status for `$ARGUMENTS`.
|
|
6
|
+
|
|
7
|
+
## Mode
|
|
8
|
+
|
|
9
|
+
| arguments | mode |
|
|
10
|
+
| ------------------ | -------------------------------------- |
|
|
11
|
+
| empty | every feature under `.gbuild/` |
|
|
12
|
+
| a feature slug | that feature only |
|
|
13
|
+
|
|
14
|
+
## Single feature
|
|
15
|
+
|
|
16
|
+
### 1. Load
|
|
17
|
+
|
|
18
|
+
`.gbuild/<feature>/graph.json` (a CypherLite GraphData file — GbuildNodes have a `slug` property and are
|
|
19
|
+
linked by `DEPENDS_ON` relationships; feature-level acceptance is `Acceptance` nodes linked by
|
|
20
|
+
`HAS_ACCEPTANCE`/`SATISFIES`) and every `.gbuild/<feature>/nodes/*.json` checkpoint (keyed by slug). This
|
|
21
|
+
is a small, bounded read — status doesn't scale with graph size the way loading full node prose would.
|
|
22
|
+
|
|
23
|
+
### 2. Compute
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/graph.py .gbuild/<feature>/graph.json --status
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Gives `frontier` / `blocked` / `in_flight` / `completed` / `cancelled` / `failed` / `waves` (all as slug
|
|
30
|
+
lists) directly — don't re-derive these by hand.
|
|
31
|
+
|
|
32
|
+
### 3. Render the graph
|
|
33
|
+
|
|
34
|
+
Draw the DAG with a marker per node status:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
✓ completed ◐ in_progress ○ pending (frontier) ● pending (blocked) ✗ failed
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Annotate blocked nodes with what they're waiting on: `● d-join-and-sum ← b-consume-doubled, c-consume-squared`.
|
|
41
|
+
|
|
42
|
+
Group by wave (from `--waves`) so fan-out is visible in the layout, not just in the dependency list.
|
|
43
|
+
|
|
44
|
+
### 4. Review verdicts
|
|
45
|
+
|
|
46
|
+
For every `completed` node, one line: pass outright, or passed after N repair attempts, from its
|
|
47
|
+
checkpoint's `review` field. For `failed` nodes, the last review's failed acceptance bullets verbatim —
|
|
48
|
+
this is what a human needs to make the `escalate`/`stop` call.
|
|
49
|
+
|
|
50
|
+
### 5. Concurrency evidence
|
|
51
|
+
|
|
52
|
+
For each wave with more than one node, compare `started_at` timestamps across that wave's checkpoints.
|
|
53
|
+
Report whether they actually overlapped (real concurrent dispatch) or ran sequentially despite being in
|
|
54
|
+
the same wave (a sign the fallback backend was invoked one node at a time — a bug in how `run` was
|
|
55
|
+
driven, not a graph problem).
|
|
56
|
+
|
|
57
|
+
### 6. Next action
|
|
58
|
+
|
|
59
|
+
One priority-ordered list: what `/gbuild:run <feature>` would do next (the current frontier), and
|
|
60
|
+
anything needing a human decision first (`failed`/`escalate`d nodes, an empty frontier with incomplete
|
|
61
|
+
nodes remaining).
|
|
62
|
+
|
|
63
|
+
### 7. Invariant checks
|
|
64
|
+
|
|
65
|
+
Numbered, only reporting violations (silence on an item means it passed, not that it was skipped):
|
|
66
|
+
|
|
67
|
+
1. Every GbuildNode has non-empty `acceptance` (should already be caught by `validate_format.py`'s own
|
|
68
|
+
validation — report if a node manages to lack it anyway, since that means the file was hand-edited or
|
|
69
|
+
written by something other than `plan`).
|
|
70
|
+
2. Every `Acceptance` node is `SATISFIES`-covered by at least one GbuildNode (enforced by
|
|
71
|
+
`validate_format.py` — report if one slips through anyway).
|
|
72
|
+
3. `DEPENDS_ON` is acyclic (validated by `validate_format.py`/`graph.py` already; report if it somehow
|
|
73
|
+
wasn't checked).
|
|
74
|
+
4. No `completed` node's checkpoint is missing a `review` field with `verdict: pass` — a node marked
|
|
75
|
+
complete without a passing review means `run` skipped the review step.
|
|
76
|
+
5. Every `chore`-type GbuildNode is the target of at least one `DEPENDS_ON` edge.
|
|
77
|
+
|
|
78
|
+
If all five are clear, say so in one line — don't let silence be ambiguous between "checked, clear" and
|
|
79
|
+
"not checked."
|
|
80
|
+
|
|
81
|
+
## All-features mode
|
|
82
|
+
|
|
83
|
+
For each `.gbuild/<slug>/graph.json` found, one line: slug, node counts by status, and whether the
|
|
84
|
+
frontier is empty (done or stuck) or has ready work. Point at `/gbuild:status <slug>` for detail.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
{
|
|
2
|
+
"nodes": [
|
|
3
|
+
{
|
|
4
|
+
"id": 1,
|
|
5
|
+
"labels": ["Feature"],
|
|
6
|
+
"properties": {
|
|
7
|
+
"feature": "example-diamond",
|
|
8
|
+
"destination": "A tiny worked example: A produces a shared value, B and C each consume it independently, D joins both.",
|
|
9
|
+
"context": "Ships with the plugin as the toy graph used in gbuild's own verification steps. Not a real feature.",
|
|
10
|
+
"out_of_scope": ["Anything beyond demonstrating fan-out and join scheduling."]
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"id": 2,
|
|
15
|
+
"labels": ["Acceptance"],
|
|
16
|
+
"properties": { "id": "a-1", "text": "run dispatches B and C concurrently, not one after the other" }
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": 3,
|
|
20
|
+
"labels": ["Acceptance"],
|
|
21
|
+
"properties": { "id": "a-2", "text": "D does not start until both B and C are checkpointed complete" }
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"id": 4,
|
|
25
|
+
"labels": ["GbuildNode", "Chore"],
|
|
26
|
+
"properties": {
|
|
27
|
+
"slug": "a-produce-shared-value",
|
|
28
|
+
"title": "Produce the shared value B and C both need",
|
|
29
|
+
"type": "chore",
|
|
30
|
+
"contract": { "input": {}, "output": { "value": "<integer>" } },
|
|
31
|
+
"acceptance": ["writes a JSON object with a single integer field 'value' to its checkpoint output"],
|
|
32
|
+
"verify": null,
|
|
33
|
+
"failure_policy": "retry",
|
|
34
|
+
"model_tier": "cheap"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"id": 5,
|
|
39
|
+
"labels": ["GbuildNode", "Code"],
|
|
40
|
+
"properties": {
|
|
41
|
+
"slug": "b-consume-doubled",
|
|
42
|
+
"title": "Consume the shared value, produce its double",
|
|
43
|
+
"type": "code",
|
|
44
|
+
"contract": {
|
|
45
|
+
"input": { "value": "<integer, from a-produce-shared-value.output.value>" },
|
|
46
|
+
"output": { "doubled": "<integer>" }
|
|
47
|
+
},
|
|
48
|
+
"acceptance": ["output.doubled equals a's output.value multiplied by 2"],
|
|
49
|
+
"verify": null,
|
|
50
|
+
"failure_policy": "repair",
|
|
51
|
+
"model_tier": "cheap"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"id": 6,
|
|
56
|
+
"labels": ["GbuildNode", "Code"],
|
|
57
|
+
"properties": {
|
|
58
|
+
"slug": "c-consume-squared",
|
|
59
|
+
"title": "Consume the shared value, produce its square",
|
|
60
|
+
"type": "code",
|
|
61
|
+
"contract": {
|
|
62
|
+
"input": { "value": "<integer, from a-produce-shared-value.output.value>" },
|
|
63
|
+
"output": { "squared": "<integer>" }
|
|
64
|
+
},
|
|
65
|
+
"acceptance": ["output.squared equals a's output.value raised to the power of 2"],
|
|
66
|
+
"verify": null,
|
|
67
|
+
"failure_policy": "repair",
|
|
68
|
+
"model_tier": "cheap"
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"id": 7,
|
|
73
|
+
"labels": ["GbuildNode", "Code"],
|
|
74
|
+
"properties": {
|
|
75
|
+
"slug": "d-join-and-sum",
|
|
76
|
+
"title": "Join B and C, produce their sum",
|
|
77
|
+
"type": "code",
|
|
78
|
+
"contract": {
|
|
79
|
+
"input": {
|
|
80
|
+
"doubled": "<integer, from b-consume-doubled.output.doubled>",
|
|
81
|
+
"squared": "<integer, from c-consume-squared.output.squared>"
|
|
82
|
+
},
|
|
83
|
+
"output": { "sum": "<integer>" }
|
|
84
|
+
},
|
|
85
|
+
"acceptance": [
|
|
86
|
+
"output.sum equals b's output.doubled plus c's output.squared",
|
|
87
|
+
"only runs after both b-consume-doubled and c-consume-squared are checkpointed completed"
|
|
88
|
+
],
|
|
89
|
+
"verify": null,
|
|
90
|
+
"failure_policy": "repair",
|
|
91
|
+
"model_tier": "cheap"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
],
|
|
95
|
+
"relationships": [
|
|
96
|
+
{ "id": 1, "type": "HAS_ACCEPTANCE", "start_node": 1, "end_node": 2, "properties": {} },
|
|
97
|
+
{ "id": 2, "type": "HAS_ACCEPTANCE", "start_node": 1, "end_node": 3, "properties": {} },
|
|
98
|
+
{ "id": 3, "type": "SATISFIES", "start_node": 5, "end_node": 2, "properties": {} },
|
|
99
|
+
{ "id": 4, "type": "SATISFIES", "start_node": 6, "end_node": 2, "properties": {} },
|
|
100
|
+
{ "id": 5, "type": "SATISFIES", "start_node": 7, "end_node": 3, "properties": {} },
|
|
101
|
+
{ "id": 6, "type": "DEPENDS_ON", "start_node": 5, "end_node": 4, "properties": {} },
|
|
102
|
+
{ "id": 7, "type": "DEPENDS_ON", "start_node": 6, "end_node": 4, "properties": {} },
|
|
103
|
+
{ "id": 8, "type": "DEPENDS_ON", "start_node": 7, "end_node": 5, "properties": {} },
|
|
104
|
+
{ "id": 9, "type": "DEPENDS_ON", "start_node": 7, "end_node": 6, "properties": {} }
|
|
105
|
+
],
|
|
106
|
+
"indexes": [],
|
|
107
|
+
"constraints": [],
|
|
108
|
+
"next_node_id": 8,
|
|
109
|
+
"next_rel_id": 10
|
|
110
|
+
}
|