330 lines
16 KiB
Markdown
330 lines
16 KiB
Markdown
---
|
||
name: graph
|
||
description: "Graph engineering for parallel task execution: convert a task, PRD, SPEC, or issue set into a dependency graph (DAG), layer it into supersteps, then implement each independent node concurrently with subagents — each node runs /goal → /review-it → /ship-it in an isolated git worktree, with a fan-in barrier between waves. Triggers on: graph, graph engineering, build a graph, task graph, dependency graph, DAG, parallel implement, 并发实现, 并行实现, 任务图, 把任务变成图, fan-out fan-in, superstep, dynamic workflow."
|
||
user-invocable: true
|
||
allowed-tools:
|
||
- Bash(git:*)
|
||
- Bash(gh:*)
|
||
- Bash(cat:*)
|
||
- Bash(mkdir:*)
|
||
- Bash(grep:*)
|
||
- Bash(python3:*)
|
||
---
|
||
|
||
# graph — Task/PRD to Parallel Execution Graph
|
||
|
||
Turn a task (or PRD / SPEC / issue set) into a **directed acyclic graph** of work units, layer it into **supersteps (waves)**, and implement each wave's independent nodes **concurrently** using subagents. Each node runs the full `/goal → /review-it → /ship-it` pipeline inside its **own git worktree**, so parallel nodes never clobber each other's working tree. Between waves, a **fan-in barrier** merges results and re-plans the next wave.
|
||
|
||
This is the parallel sibling of `/loop-it`. `/loop-it` is strictly sequential (one worktree, one issue at a time). `/graph` fans out every independent node in a wave at once.
|
||
|
||
---
|
||
|
||
## Mental Model (borrowed from LangGraph / graph engineering)
|
||
|
||
| Concept | Here |
|
||
|---------|------|
|
||
| **Node** | One implementable unit of work (an issue / subtask) |
|
||
| **Edge** | A dependency: `B depends on A` → edge `A → B` |
|
||
| **Superstep / wave** | A set of nodes whose deps are all satisfied — run concurrently |
|
||
| **Fan-out** | Dispatch one subagent per node in the current wave |
|
||
| **Fan-in (barrier)** | Wait for **all** nodes in the wave before starting the next |
|
||
| **State channel** | `.graph_state` — shared checkpoint, rewritten between waves (resume source) |
|
||
| **Live tracker** | `graph.html` — Claude-style light-theme dashboard, re-rendered from `.graph_state` at every checkpoint |
|
||
| **Dynamic re-plan** | After a wave, revise the graph if new work/deps emerged |
|
||
|
||
**Core principle:** Independent nodes in the same wave have *no shared state and no ordering dependency*, so they can run in true parallel. Dependencies define the *only* ordering. Everything else runs at once.
|
||
|
||
---
|
||
|
||
## Overview
|
||
|
||
```
|
||
Input (task / PRD / SPEC / issues)
|
||
│
|
||
▼
|
||
1. Decompose into nodes ─────────► nodes = {id, title, deps, criteria, scope}
|
||
│
|
||
▼
|
||
2. Build DAG + validate ─────────► detect cycles, orphan deps
|
||
│
|
||
▼
|
||
3. Topological layering ─────────► waves = [[n1,n2,n3], [n4,n5], [n6]]
|
||
│
|
||
▼
|
||
4. Render graph + confirm with user
|
||
│
|
||
▼ (write .graph_state + graph.html — open graph.html to watch live)
|
||
┌──────────── per wave (superstep) ────────────┐
|
||
│ │
|
||
│ FAN-OUT: 1 subagent per node (parallel) │
|
||
│ each subagent, in its own git worktree: │
|
||
│ /goal (inline implement) → /review-it │
|
||
│ → /ship-it │
|
||
│ │
|
||
│ FAN-IN barrier: wait for ALL nodes │
|
||
│ integrate, update .graph_state │
|
||
│ re-render graph.html │
|
||
│ re-plan next wave if graph changed │
|
||
│ │
|
||
└───────────────────────────────────────────────┘
|
||
│
|
||
▼
|
||
All waves done → final summary
|
||
```
|
||
|
||
---
|
||
|
||
## Step 1: Locate & Decompose Input
|
||
|
||
Accept any of: a free-form task description, a PRD/SPEC file, or an existing issue set (GitHub / local `.md` / iCafe).
|
||
|
||
- **PRD/SPEC** → reuse `/to-issues` decomposition rules (one node per User Story; split large, merge tiny).
|
||
- **Existing issues** → each issue is a node; parse dependencies from issue bodies (`Depends on: #3`, `Dependencies: #3, #5`).
|
||
- **Free-form task** → break into the smallest independently-shippable units yourself.
|
||
|
||
Each node MUST have:
|
||
|
||
```
|
||
Node #N
|
||
title: short imperative title
|
||
deps: [list of node ids] or []
|
||
criteria: acceptance criteria (checklist) — how the subagent knows it's done
|
||
type: backend | frontend | fullstack | ui | infra | docs
|
||
scope_hint: which files/dirs this node is expected to touch (for conflict analysis)
|
||
```
|
||
|
||
`scope_hint` matters: two nodes with no dependency edge but overlapping file scope are **not** truly independent — see Step 3.
|
||
|
||
---
|
||
|
||
## Step 2: Build the DAG & Validate
|
||
|
||
Construct edges from `deps`. Then validate:
|
||
|
||
| Check | Action on failure |
|
||
|-------|-------------------|
|
||
| **Cycle** (`A → B → A`) | Print `⚠️ 循环依赖: #A ↔ #B`. Break by node id order, warn user, ask to confirm or fix. |
|
||
| **Dangling dep** (`#7 depends on #99`, no such node) | Print warning, drop the phantom edge. |
|
||
| **Scope collision** (two dep-free nodes edit same files) | Add a *soft edge* to serialize them (lower id first), OR flag for user. Never let two parallel worktrees fight over the same files. |
|
||
|
||
**Hot-file exception:** A shared *wiring* file that nearly every node must touch (e.g. `router.go`, `main.go`, `mod.rs`, a DI container, an `__init__` re-export) does NOT count as a scope collision — treating it as one would serialize the entire graph into a chain. For such files, assume append-only edits merge cleanly, and prefer one of: (a) designate a single node that *owns* wiring and have others expose a registration hook, or (b) do a tiny follow-up "wire everything" node in the last wave. Reserve the collision rule for nodes that edit the *same logic* in the same file (e.g. two handlers rewriting the same function).
|
||
|
||
---
|
||
|
||
## Step 3: Topological Layering into Waves
|
||
|
||
Compute waves via Kahn's algorithm:
|
||
|
||
1. **Wave 0** = all nodes with `deps == []` and no scope collision among themselves.
|
||
2. Remove wave-0 nodes; **Wave 1** = nodes whose deps are now all satisfied.
|
||
3. Repeat until all nodes placed.
|
||
4. Within a wave, if two nodes edit the **same logic in the same file** (real collision, per the hot-file exception in Step 2), push the higher-id one to the next wave. Bare wiring-file overlap does not trigger this.
|
||
|
||
**ID conventions (used consistently):** lower id wins — cycles break by lowest id first (Step 2), and scope collisions serialize with the lower id first (higher id deferred to the next wave).
|
||
|
||
Print the layered plan:
|
||
|
||
```
|
||
📊 Graph: 6 nodes, 3 waves
|
||
|
||
Wave 0 (parallel ×3): #1 db schema #2 config loader #3 logging util
|
||
Wave 1 (parallel ×2): #4 API handler (deps #1) #5 CLI flags (deps #2)
|
||
Wave 2 (parallel ×1): #6 integration (deps #4,#5)
|
||
|
||
Max parallelism: 3 subagents in Wave 0.
|
||
```
|
||
|
||
Also emit a Mermaid diagram for the user:
|
||
|
||
````
|
||
```mermaid
|
||
graph LR
|
||
n1[#1 db schema] --> n4[#4 API handler]
|
||
n2[#2 config loader] --> n5[#5 CLI flags]
|
||
n3[#3 logging util]
|
||
n4 --> n6[#6 integration]
|
||
n5 --> n6
|
||
```
|
||
````
|
||
|
||
**Wait for user confirmation** before dispatching any subagent. Let them adjust nodes, deps, or the max-parallelism cap.
|
||
|
||
---
|
||
|
||
## Step 4: Pre-flight Checks
|
||
|
||
Before the first wave (same spirit as `/loop-it`):
|
||
|
||
```bash
|
||
git rev-parse --is-inside-work-tree # in a repo?
|
||
git status --porcelain # clean tree? (dirty → stash/abort)
|
||
git branch --show-current # on main/master?
|
||
git ls-remote --heads origin # remote reachable?
|
||
gh auth status # if shipping to GitHub
|
||
```
|
||
|
||
Any hard failure → print the error and stop. Confirm a **max concurrency cap** with the user (default 3–4 parallel subagents; more risks rate limits and review noise).
|
||
|
||
Then **initialize the state channel + live tracker** (do this once, right after the plan is confirmed and before the first fan-out):
|
||
|
||
```bash
|
||
# 1. Write the initial checkpoint (all nodes pending, current_wave 0).
|
||
cat > .graph_state <<'JSON'
|
||
{ "version": 1, "task": "...", "repo": "owner/repo",
|
||
"waves": [[1,2,3],[4,5],[6]], "current_wave": 0,
|
||
"nodes": { "1": {"title":"...","deps":[],"status":"pending","wave":0}, ... } }
|
||
JSON
|
||
|
||
# 2. Keep it out of git.
|
||
grep -qxF '.graph_state' .gitignore || printf '.graph_state\ngraph.html\n' >> .gitignore
|
||
|
||
# 3. Render the Claude-style light-theme dashboard.
|
||
python3 skills/graph/scripts/render_graph_html.py .graph_state graph.html
|
||
```
|
||
|
||
Tell the user: **open `graph.html` in a browser** — it auto-refreshes every 5s, so it tracks execution live (waves, node statuses, progress bar, and a Mermaid DAG colored by status). Re-run the render command at every checkpoint (see Step 5b) to push updates.
|
||
|
||
---
|
||
|
||
## Step 5: Execute Wave by Wave (fan-out → fan-in)
|
||
|
||
For each wave, in order:
|
||
|
||
### 5a. FAN-OUT — one subagent per node, in parallel
|
||
|
||
**Dispatch all nodes of the wave in a single response** (multiple Agent/subagent calls in one message = concurrent). Each subagent works in its **own git worktree** so parallel file edits never collide:
|
||
|
||
```bash
|
||
# The orchestrator creates a worktree per node BEFORE dispatching:
|
||
git worktree add -b feat/node-{N}-{slug} ../.graph-worktrees/node-{N} main
|
||
```
|
||
|
||
Each subagent receives a **self-contained** prompt (it does NOT inherit orchestrator context):
|
||
|
||
```markdown
|
||
You are implementing ONE node of a task graph, working in an ISOLATED git worktree.
|
||
|
||
Worktree: ../.graph-worktrees/node-{N} (already created on branch feat/node-{N}-{slug})
|
||
Node #{N}: {title}
|
||
Type: {type}
|
||
Scope: {scope_hint} — stay within these files; do not touch other nodes' scope
|
||
|
||
Acceptance criteria (all must pass):
|
||
- [ ] {criterion 1}
|
||
- [ ] {criterion 2}
|
||
|
||
Context (deps already merged into main, pull first):
|
||
{summaries of dependency nodes' outputs, or the referenced PRD/SPEC excerpt}
|
||
|
||
Your pipeline (run all three, in order):
|
||
1. IMPLEMENT (inline /goal): read the node + any referenced PRD/SPEC, read adjacent
|
||
code, implement to satisfy EVERY acceptance criterion, run build + tests + lint
|
||
(e.g. go build ./... && go vet ./... && go test ./...). Iterate until all green.
|
||
2. REVIEW (/review-it): run code review on your changes, apply accepted findings,
|
||
re-run focused tests, repeat until review is clean (max 2 rounds).
|
||
3. SHIP (/ship-it): commit (message references the node/issue), push branch,
|
||
create PR, merge, close the issue.
|
||
|
||
Constraints:
|
||
- Work ONLY inside your worktree. Do NOT edit files outside {scope_hint}.
|
||
- Do NOT try to call `goal` via the Skill tool (it's a UI command, not a skill) —
|
||
"implement" means you write the code yourself. /review-it and /ship-it ARE skills.
|
||
- If you cannot satisfy a criterion, STOP and report what's blocking — don't fake it.
|
||
|
||
Return: node id, PASS/FAIL, PR/commit refs, files changed, and — if you discovered new required work or a dependency the graph didn't capture — a `NEW_WORK:` line describing it (title + which nodes it blocks). Emit `NEW_WORK: none` if there's nothing.
|
||
```
|
||
|
||
> **Why worktrees, not branches alone:** `/goal` mutates the working tree. Two subagents editing the same checkout would corrupt each other. A worktree per node gives each its own filesystem checkout on its own branch — that's what makes the wave genuinely parallel and safe.
|
||
|
||
### 5b. FAN-IN — barrier, integrate, re-plan
|
||
|
||
Wait for **every** subagent in the wave to return (BSP barrier — the next wave cannot start until this one commits). Then:
|
||
|
||
1. Read each subagent's summary. Mark node `shipped` or `failed`.
|
||
2. `git checkout main && git pull` — dependency outputs are now on main for the next wave.
|
||
3. Remove finished worktrees: `git worktree remove ../.graph-worktrees/node-{N}` (keep failed ones for investigation).
|
||
4. Write checkpoint to `.graph_state`, then re-render the tracker:
|
||
`python3 skills/graph/scripts/render_graph_html.py .graph_state graph.html` (the open `graph.html` picks it up on its next auto-refresh).
|
||
5. **Dynamic re-plan** (LangGraph-style conditional edge): scan each subagent's `NEW_WORK:` line. If any is not `none`, add the new node(s)/edge(s) and re-layer the *remaining* nodes before starting the next wave. Show the user the delta.
|
||
6. If any node in the wave **failed**, mark all nodes that depend on it as `blocked` and skip them (their inputs aren't ready).
|
||
|
||
Proceed to the next wave.
|
||
|
||
---
|
||
|
||
## State File: `.graph_state` (+ live tracker `graph.html`)
|
||
|
||
`.graph_state` lives at the repo root and **must be in `.gitignore`**. It's the single source of truth: checkpoint it after every wave so a crash resumes at the wave boundary, and re-render `graph.html` from it so the browser dashboard stays live. `graph.html` is a *derived* view — never hand-edit it; regenerate it from `.graph_state`.
|
||
|
||
```json
|
||
{
|
||
"version": 1,
|
||
"updated_at": "2026-07-21T10:30:00Z",
|
||
"task": "Add user auth",
|
||
"repo": "owner/repo",
|
||
"waves": [[1, 2, 3], [4, 5], [6]],
|
||
"current_wave": 1,
|
||
"nodes": {
|
||
"1": { "title": "db schema", "deps": [], "status": "shipped", "branch": "feat/node-1-db-schema", "pr": 43, "wave": 0 },
|
||
"2": { "title": "config loader", "deps": [], "status": "shipped", "wave": 0 },
|
||
"3": { "title": "logging util", "deps": [], "status": "failed", "wave": 0, "error": "test TestLog failed", "attempts": 2 },
|
||
"4": { "title": "API handler", "deps": [1], "status": "in_progress", "wave": 1 },
|
||
"6": { "title": "integration", "deps": [4, 5], "status": "blocked", "wave": 2, "reason": "depends on #3 (failed)" }
|
||
}
|
||
}
|
||
```
|
||
|
||
Status values: `pending | in_progress | shipped | failed | blocked | skipped`. Each node carries `title` + `deps` so `graph.html` can draw the DAG and cards straight from the checkpoint.
|
||
|
||
Render the tracker any time with:
|
||
|
||
```bash
|
||
python3 skills/graph/scripts/render_graph_html.py .graph_state graph.html
|
||
```
|
||
|
||
On resume: read `.graph_state`, skip `shipped`, ask about `failed` (retry/skip), re-derive remaining waves, and re-render `graph.html`.
|
||
|
||
---
|
||
|
||
## Safety Guards
|
||
|
||
- **Worktree isolation is mandatory** — never run two parallel `/goal` sessions in the same checkout.
|
||
- **Fan-in barrier is mandatory** — never start wave N+1 before every node in wave N returns and merges.
|
||
- **Scope collisions serialize** — dep-free nodes touching the same files go in different waves.
|
||
- **Never skip /review-it** before `/ship-it`.
|
||
- **Cap concurrency** — default 3–4; more invites rate limits and merge contention.
|
||
- **Never force-push to main.** Each node ships via its own branch/PR.
|
||
- **Failed node blocks its dependents** — don't ship on top of unmet inputs.
|
||
- **Max retries per node** — reuse `/loop-it`'s error classes; don't loop forever.
|
||
- **Confirm the plan** before the first fan-out.
|
||
|
||
---
|
||
|
||
## Common Mistakes
|
||
|
||
| Mistake | Fix |
|
||
|---------|-----|
|
||
| Dispatching subagents in separate responses | One response, multiple calls = parallel. Separate = sequential. |
|
||
| No worktree → parallel edits corrupt the tree | One `git worktree` per node. |
|
||
| Two "independent" nodes edit the same file | Add a soft edge; put them in different waves. |
|
||
| Starting the next wave before all nodes merge | Enforce the fan-in barrier. |
|
||
| Over-decomposing into 20 trivial nodes | Merge tiny units; a node should be a meaningful shippable unit. |
|
||
| Ignoring a failed node's dependents | Mark them `blocked`, skip them. |
|
||
|
||
---
|
||
|
||
## Relationship to Other Skills
|
||
|
||
```
|
||
/prd → /prd-to-spec → /to-issues ─┬─► /loop-it (sequential: one node at a time)
|
||
└─► /graph (parallel: whole wave at once)
|
||
│
|
||
each node: inline /goal → /review-it → /ship-it (in its own worktree)
|
||
```
|
||
|
||
- **`/to-issues`** — decomposition rules reused for building nodes.
|
||
- **`/loop-it`** — sequential counterpart; use it when nodes heavily share files or serial safety matters.
|
||
- **`/graph`** — this skill; use it when the DAG has genuine parallelism (independent subsystems).
|
||
- **`/review-it`, `/ship-it`** — real skills each node's subagent invokes.
|
||
```
|