agentleFS
metalogicametalogica/substrate/skills/graph-spec/SKILL.md

graph-spec

Skill0 starsChanged 2 months agoMITView on GitHub

Graph the Spec — decompose a written SDD spec into a directed acyclic graph of tbd beads so it can be executed in parallel. Invoke with a spec path (docs/tasks/ongoing/<slug>/<slug>-spec.md), or run with no args to discover the ongoing spec. Parses the spec's Prompt Execution Strategy (phases → steps), turns each unit into a bead, infers blocked-by edges from which files/symbols a step consumes vs. creates (folding behavior-invalidated tests into the changing bead's write-scope), cycle-checks via Kahn, partitions the DAG into context-budget windows by file-adjacency (group:<window-N> labels — the unit the orchestrator dispatches), stamps doctrine:<id> labels where a bead's write-scope intersects the doctrine manifest's paths: globs, and persists an epic + child beads under the canonical label epic:<slug>. Prints the wave shape via docs/scripts/bead-graph.sh. Called automatically as architect-spec's final step, or standalone to (re)graph any existing spec. Produces the DAG only — the parallel-execution doctrine's orchestrator consumes it; this skill does not execute.

  • Commits and pushes

The file

Show its rules and text

We split this skill into the rules it gives an agent

19 rules. About 0 are common, 7 are distinctive, and 12 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈7

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 12

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    If a path was passed, use it.

    line 43
  2. Distinctive:

    Read the spec. Confirm it has a Prompt Execution Strategy section.

    line 50
  3. Very specific:

    If .substrate/config.json sets "bead-tracker", honor it (tbd | none | other).

    line 56
  4. Distinctive:

    Layer backstop: If two beads share a file in their creates set, serialize them with an edge (later phase blocked-by earlier) even absent a …

    line 79
  5. Very specific:

    When the running total would cross context-budget (as a fraction of one agent's usable window), close the current window and open the next.

    line 94
  6. Very specific:

    Flag under-decomposition: If a *single* bead's cost alone exceeds context-budget, do

    line 100
  7. Very specific:

    Silent no-op when there is nothing to bind: No manifest, or no entry declares paths: → stamp nothing and say nothing.

    line 120
  8. Distinctive:

    If the spec somehow lacks a Doctrine Reconciliation phase, synthesize this node anyway — it is mandatory per the contract.

    line 162
  9. Very specific:

    Do not tbd sync here — batch sync stays the orchestrator's call at epic close (parallel-execution doctrine, Policy 3).

    line 177
  10. Very specific:

    Branch B — none: write each bead to docs/tasks/ongoing/<slug>/beads/<bead-slug>.md with blocked-by:, epic: <slug>, group: <window-N> (its …

    line 179
  11. Very specific:

    MUST derive <slug> from the spec directory and tag every bead (epic + children) with epic:<slug>.

    line 214
  12. Very specific:

    MUST emit exactly one terminal doctrine-reconciliation node per epic (Step 4.6): tagged kind: doctrine-reconciliation, blocked-by every …

    line 215
  13. Distinctive:

    MUST cycle-check via Kahn before persisting and REFUSE on a cycle.

    line 216
  14. Distinctive:

    MUST be the single writer to tbd (parallel-execution doctrine Policy 1).

    line 217
  15. Very specific:

    MUST NOT tbd sync — batch sync is the orchestrator's, at epic close.

    line 218
  16. Distinctive:

    MUST NOT execute the beads, write feature code, or open worktrees.

    line 219
  17. Very specific:

    MUST NOT double-create: if an epic for <slug> already exists, render the existing DAG and offer to add only missing beads.

    line 220
  18. Very specific:

    MUST clean up every tempfile it renders (unlink even on partial failure).

    line 221
  19. Distinctive:

    SHOULD keep bead granularity at one-step-one-bead unless steps are file-coupled and share a Verify block — over-splitting inflates the DAG, …

    line 225

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

/substrate:graph-spec

Turn a finished spec into a bead DAG: one epic + child beads wired by blocked-by: edges, all tagged with the canonical epic label epic:<slug>. The DAG is the input to substrate's parallel-execution doctrine — beads in the same topological wave touch no shared blocker and can be dispatched to worktree-isolated subagents at once.

This skill produces the graph; it does not run it. Execution is the parallel-execution orchestrator's job (docs/doctrine/agents-parallel-execution-doctrine.md), or a plain phase-by-phase /substrate:execute pass.

Arguments

<spec-path> — path to a spec at docs/tasks/ongoing/<slug>/<slug>-spec.md. If omitted, discover it (see Step 1).

When to run

  • A spec exists and its Prompt Execution Strategy section is filled (phases → steps → verify → gate).
  • You want the work decomposed into independently-dispatchable beads before execution — especially when phases contain file-disjoint steps that could run in parallel.
  • Invoked automatically as the last step of /substrate:architect-spec, or by hand on any existing spec.

When to REFUSE

Fail fast — abort with a one-line explanation, never fall back to a guess.

SignalAction
No spec path given AND none discoverable under docs/tasks/ongoing/**/*-spec.mdAsk the user for the spec path.
Spec has no "Prompt Execution Strategy" section (per execution-format.md)Abort: "This spec has no execution strategy to decompose. Run /substrate:architect-spec to produce one."
Bead-tracker unresolvable (see Step 2)Abort: state whether tbd or a markdown fallback is expected, and why neither resolved.
An epic already exists for this slug (tbd list --type epic --label epic:<slug> non-empty)Do NOT double-create. Skip to Step 6 and render the existing DAG; offer to add only the missing beads.

Protocol you operate under

  • Execution grammar: docs/protocol/sdd/execution-format.md (phases → steps → verify → gate) — the structure you decompose.
  • DAG algorithm: mirrors /substrate:synthesize-session Step 8 (pairwise blocked-by inference + Kahn cycle detection). Same "bead DAG" dialect on purpose — one graph vocabulary across the plugin.
  • Consumer: docs/doctrine/agents-parallel-execution-doctrine.md (single-writer tracker, integration branch, file-disjoint waves).

Workflow

Step 1 — Resolve the spec

If a path was passed, use it. Else glob docs/tasks/ongoing/**/*-spec.md:

  • exactly one match → use it;
  • several → list them and ask which;
  • none → REFUSE per the table.

Derive <slug> from the containing directory name (docs/tasks/ongoing/<slug>/...). That slug is the whole coordination key: the epic label is epic:<slug>, deterministically, so /substrate:synthesize-session later files follow-up beads under the same label without any handshake.

Read the spec. Confirm it has a Prompt Execution Strategy section; if not, REFUSE.

Step 2 — Resolve the bead-tracker

Same resolution as /substrate:synthesize-session (keep them identical):

  1. If .substrate/config.json sets "bead-tracker", honor it (tbd | none | other).
  2. Else auto-detect tbd: .tbd/config.yml exists AND a tbd binary is callable — command -v tbd succeeds, OR npx --no-install get-tbd --version succeeds. Else none.
  • tbd → beads are canonical; this skill creates them via tbd create. Substitute npx --no-install get-tbd for tbd throughout if no global binary is on PATH.
  • none → markdown is canonical; beads live at docs/tasks/ongoing/<slug>/beads/<bead-slug>.md. blocked-by: in frontmatter is the only edge record (no tbd dep).

Step 3 — Decompose the spec into bead candidates

Walk the Prompt Execution Strategy. Each step (#### Step N.M) is one bead candidate; collapse trivially-coupled sibling steps only when they edit the same file and share a single Verify block. For each candidate capture, in memory (no writes yet):

  • title — imperative, scoped (e.g. "Add requireStoreOwner guard to convex/stores.ts").
  • phase / layer — the owning phase and its layer-hint (domain / backend / frontend / infra / cross-cutting).
  • creates — files, symbols, exports, tables the step introduces or modifies (read the step body + its Verify block).
  • consumes — files/symbols/commands the step depends on existing.
  • invalidated tests (reconcile) — existing tests that assert behavior this bead changes (distinct from any new tests the step adds). A behavior change to an existing symbol / contract / exported member / required-dependency almost always breaks a test that pinned the old behavior, yet the step body rarely names that test. Reverse-scan the test tree for tests referencing the changed symbol/file/contract and fold each matching test file into this bead's creates (its write-scope) with a one-line reconcile: note ("asserts prior behavior of X"). Skipping this is the single most common cause of a mid-run stall: the test lives outside the bead's declared Files, so the group-runner can't touch it and the break surfaces only at the integrated re-gate. Purely-additive beads (new files, no change to an existing symbol) have no invalidated tests — leave this empty.
  • gate — the step's Verify commands, inlined (a subagent runs these; per the parallel-execution doctrine it never touches tbd or git). Compare it to substrate.yaml's gate.{compile,test,lint}: if the bead's gate is a strict subset of gate.*, or omits a suite the bead's layer is actually covered by (e.g. a frontend bead that runs only tsc, not the vitest suite), tag the bead gate-scope: partial in its body. That tells the orchestrator its per-bead green is a fast pre-check only, and that the wave's union re-gate — not this narrow gate — authorizes the merge (agents-parallel-execution-doctrine.md §Supporting → Re-run the gate on the integrated branch). A gate that equals or supersets gate.* needs no tag.
  • acceptance criterion — binary pass/fail lifted from the step + its gate.

Step 4 — Build the DAG

Mirror /substrate:synthesize-session Step 8:

  1. Pairwise scan. For each ordered pair (A, B), mark B blocked-by A iff B's consumes references a file/symbol/export/table in A's creates. This is richer than the linear phase order: two steps in the same phase that touch disjoint files land with no edge between them → same wave → parallel. Record a one-line reason per edge.
  2. Layer backstop. If two beads share a file in their creates set, serialize them with an edge (later phase blocked-by earlier) even absent a symbol dependency — the parallel-execution doctrine forbids two beads editing one file in a wave.
  3. Cycle detection (Kahn). Compute in-degrees; peel zero-in-degree nodes. If any remain → a cycle exists. REFUSE: print the cycle (A → B → C → A) and ask the user to split or drop a bead. Never emit a cyclic DAG.
  4. Encode blocked-by: on each bead.

Step 4.5 — Partition into context-budget windows

The DAG is now cycle-free; cut it into agent-sized windows so no group-runner's context rots or auto-compacts. Read execution.context-budget from substrate.yaml (default 0.4 if absent — a deviatable prior, not a hard gate). Then:

  1. Estimate per-bead cost. Walk beads in topological order. For each, estimate cost = Σ(bytes of its Files/creates+consumes) + heavy-ref surcharge (schema / contract / migration reads the bead must load) + gate-log weight + effort(XS…L). This is a heuristic prior, not a measurement — round generously.
  2. Accumulate into windows. Open window-1; add beads in topological order, summing cost. When the running total would cross context-budget (as a fraction of one agent's usable window), close the current window and open the next. Snap boundaries to file-adjacency: never split a chain of co-edited beads (overlapping Files) across two windows — co-edited beads share a warm worktree, so they belong to the same window; file-disjoint chains fall into separate windows (isolation, parallel where edges allow). Adjacency wins over the raw cost cut when they conflict.
  3. Flag under-decomposition. If a single bead's cost alone exceeds context-budget, do not silently over-fill a window: warn and recommend splitting that bead (it is too heavy for one runner), then place it in its own window so the run can still proceed.

The partition is a deviatable prior: the orchestrator MAY re-batch at dispatch time (logging the deviation). See agents-parallel-execution-doctrine.md §Grouping & windows.

Step 4.55 — Bind doctrine to write-scope (doctrine:<id> labels)

Windows exist and every bead has a write-scope. Bind each bead to the doctrines that govern the files it will edit, at graph time, so a dispatcher can push the right doctrine into the worker's prompt instead of hoping the worker goes looking. Only the manifest's paths key answers the file question — triggers matches a brief (prose) at spec time, paths matches a write-scope (files) at graph time (docs/doctrine/agents-doctrine.md §3.1).

  1. Read the manifest at docs/doctrine/doctrine-manifest.yaml. Take each entry's id and its optional paths:, parsed in inline-list form only — paths: [docs/doctrine/**, x/y.sh], one line — exactly how doctrine-lint.sh parses pointers. A block sequence (paths: then - a/**) is not the declared form and the tooling cannot read it: report it as a manifest bug, do not accommodate it.
  2. Silent no-op when there is nothing to bind. No manifest, or no entry declares paths: → stamp nothing and say nothing. Not a warning, not an abort: substrate must keep graphing specs in repos that never adopted the key, and a warning would fire on every run in every one of them.
  3. Intersect. A bead's write-scope is creates ∪ modifies — Step 3's creates set, which has already absorbed the invalidated-test files. consumes is read-only and does not bind. A bead is bound to <id> iff any path in its write-scope matches any glob in that entry's paths:. Match relative to the repo root and permissively — a * may cross / (the same reading doctrine-lint.sh uses for its rot guard), so docs/doctrine/** matches docs/doctrine/agents-doctrine.md, and a bead whose declared scope is itself a directory glob (convex/**) matches a doctrine glob rooted there.
  4. Stamp doctrine:<id> — one label per matching entry; a bead may carry zero, one, or several. <id> is the manifest's id field verbatim (not the filename, not the path). It is the key downstream consumers resolve with bash docs/scripts/doctrine-digest.sh <id>; any other spelling breaks that chain silently.
  5. Exempt the terminal node. The doctrine-reconciliation bead Step 4.6 shapes (the spec's Phase N: Doctrine Reconciliation step, or the node you synthesize when the spec lacks one) gets no generic doctrine:<id> labels — even though its docs/doctrine/** write-scope would match every doctrine that governs doctrine files. Its whole brief is reconciliation against the fully integrated epic; stamping it would bind it to a near-arbitrary subset and drown the signal. Step 4.6's contract is the only one it carries.

Worked example, against this repo's manifest (agents → paths: [docs/doctrine/**, docs/scripts/doctrine-lint.sh]; agents-parallel-execution → paths: [docs/scripts/bead-graph.sh]):

beadwrite-scopelabels stamped
Add rule 6 to the linterdocs/scripts/doctrine-lint.shdoctrine:agents
Teach bead-graph.sh a flow viewdocs/scripts/bead-graph.shdoctrine:agents-parallel-execution
Document paths: in §3docs/doctrine/agents-doctrine.md, docs/doctrine/doctrine-manifest.yamldoctrine:agents (via docs/doctrine/**)
Rename a Convex tableconvex/schema.ts(none — no entry's paths: covers it)
Terminal kind: doctrine-reconciliationdocs/doctrine/**none — exempt per (5), though the globs would match

Step 4.6 — Force the terminal doctrine-reconciliation node

Every spec ends with Phase N: Doctrine Reconciliation (per spec-template.md). Its bead is not an ordinary node — it is the epic's mandatory terminal node that applies the ratify-only doctrine change the feature earned, in-epic. It never queues a doctrine-amendment and there is no downstream sink. Shape it explicitly, overriding the generic decomposition:

  1. Tag it kind: doctrine-reconciliation (an additive tag alongside epic:<slug>), so the orchestrator can identify the terminal node.
  2. Edge it blocked-by every other bead in the epic. It must see the fully integrated feature — no other bead may land after it. This is deliberately the one node that collapses parallelism at the end; that's correct.
  3. Write-scope = docs/doctrine/**. Put docs/doctrine/** in its creates/Files. Editing doctrine files is ordinary working-tree change a group-runner may make in its worktree — it is not a tracker write, so the single-writer invariant is untouched.
  4. Solo terminal window. Give it its own group:<window-N> as the last window (it is blocked-by everything, so it is structurally its own final wave regardless). Never fold it into a feature window.
  5. Ratify-only gate. Its gate is the epic's full union gate re-run on the integrated tip — green proves the mutation only codified what the code already did. Do not tag it gate-scope: partial.

If the spec somehow lacks a Doctrine Reconciliation phase, synthesize this node anyway — it is mandatory per the contract; warn that the spec was missing it.

Step 5 — Persist the epic + beads

Preview the full bead list in DAG order inline, then ask: Create 1 epic + N beads under epic:<slug> now? (Y / n / select) — default Y (binary gate, no default-escape suffix). select enters a per-bead y / n / skip loop.

Branch A — tbd (single-writer; you are the only writer):

  1. Epic bead: tbd create "Epic: <spec title>" --type epic -l "epic:<slug>" --file <spec-ref> (a tempfile holding the spec path + one-line summary; mktemp, unlink after). Capture its id as <epic-id>.
  2. Child beads, in DAG order so blocked-by: resolves to already-assigned ids:
    • render the bead body (acceptance criterion + inlined gate + state-transfer prompt) to a tempfile,
    • tbd create "<title>" --type task --parent <epic-id> -l "epic:<slug>" -l "group:<window-N>" [-l "doctrine:<id>" …] --file <tmp>, where <window-N> is the bead's window from Step 4.5 and the zero-or-more doctrine:<id> flags are its Step-4.55 bindings, one -l each (add -l "kind:doctrine-reconciliation" for the terminal node from Step 4.6 — which carries no doctrine: labels); capture the id, unlink the tempfile (unconditional cleanup, even on failure),
    • stamp the spec back-link so a cold runner can re-open context: include spec: <spec-path>#<owning-phase-or-step> in the bead body (and, when the tracker supports it, tbd update <bead-id> --spec <spec-path>),
    • for each blocker: tbd dep add <bead-id> <blocker-id>.
  3. --label epic:<slug> is the canonical grouping — the label, not the parent link, is the join key /substrate:synthesize-session and bead-graph.sh rely on. --parent is the nicety on top. group:<window-N> is the partition membership the orchestrator reads (and MAY re-batch) per agents-parallel-execution-doctrine.md §Grouping & windows. doctrine:<id> is the graph-time binding dispatchers push on: the orchestrator inlines the window's digests via doctrine-digest.sh <id>, and a daemon lane reads the label-named doctrines first.
  4. Do not tbd sync here — batch sync stays the orchestrator's call at epic close (parallel-execution doctrine, Policy 3).

Branch B — none: write each bead to docs/tasks/ongoing/<slug>/beads/<bead-slug>.md with blocked-by:, epic: <slug>, group: <window-N> (its Step-4.5 window), doctrine: [<id>, …] (its Step-4.55 bindings — omit the key entirely when there are none), and spec: <spec-path>#<owning-phase-or-step> in frontmatter. The markdown file is the bead.

Step 6 — Show the shape

Render the DAG so the user (and any future agent) can see parallel vs. sequential structure:

bash docs/scripts/bead-graph.sh --epic <slug>                 # topological waves (default)
bash docs/scripts/bead-graph.sh --epic <slug> --format mermaid  # paste-able graph TD

Print the waves view inline. Under tbd, the script reads the beads you just created; under none, tell the user the waves view requires tbd and print the DAG from your in-memory layering instead.

Step 7 — Hand off

Print verbatim:

✔ Spec graphed into a bead DAG.

Epic label: epic:<slug>   ·   1 epic + N beads   ·   W waves (see above)

Inspect any time:
  bash docs/scripts/bead-graph.sh --epic <slug>
  bash docs/scripts/bead-graph.sh --epic <slug> --format mermaid

To execute in parallel, hand this DAG to the orchestrator per
docs/doctrine/agents-parallel-execution-doctrine.md (single-writer tracker,
integration branch, file-disjoint waves). For a simple sequential pass:

  claude /substrate:execute docs/tasks/ongoing/<slug>/<slug>-spec.md

Constraints

  • MUST derive <slug> from the spec directory and tag every bead (epic + children) with epic:<slug>. The label is the canonical epic identity — it is the contract with /substrate:synthesize-session and bead-graph.sh. Do not skip it.
  • MUST emit exactly one terminal doctrine-reconciliation node per epic (Step 4.6): tagged kind: doctrine-reconciliation, blocked-by every other bead, docs/doctrine/** in its write-scope, in its own solo terminal group:<window-N>. This is the in-epic sink for doctrine change — the graph MUST NOT rely on a downstream doctrine-amendment queue.
  • MUST cycle-check via Kahn before persisting and REFUSE on a cycle. A cyclic "DAG" is a bug, not a plan.
  • MUST be the single writer to tbd (parallel-execution doctrine Policy 1). Never hand the tbd CLI or git push to a subagent.
  • MUST NOT tbd sync — batch sync is the orchestrator's, at epic close.
  • MUST NOT execute the beads, write feature code, or open worktrees. This skill only produces the graph.
  • MUST NOT double-create: if an epic for <slug> already exists, render the existing DAG and offer to add only missing beads.
  • MUST clean up every tempfile it renders (unlink even on partial failure).
  • MUST run the invalidated-tests reverse-scan for every behavior-changing bead and fold the affected existing test files into that bead's Files/write-scope with a reconcile: note. A behavior change whose broken test sits outside the bead's Files is a guaranteed mid-run stall — the group-runner can't touch what isn't in scope.
  • MUST stamp doctrine:<id> (Step 4.55) on every bead whose write-scope intersects a manifest entry's paths: globs, using the manifest's id verbatim so doctrine-digest.sh <id> resolves it downstream — and MUST leave the terminal kind: doctrine-reconciliation node unstamped. When no manifest exists or no entry declares paths:, the step is a silent no-op — never a warning, never an abort.
  • MUST tag a bead gate-scope: partial when its inlined gate is a strict subset of substrate.yaml's gate.* (or omits a suite its layer is covered by), so the orchestrator treats the per-bead green as a pre-check and relies on the wave's union re-gate to authorize the merge.
  • SHOULD keep bead granularity at one-step-one-bead unless steps are file-coupled and share a Verify block — over-splitting inflates the DAG, under-splitting kills parallelism.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.