Summary
Triggered when docs/plans/<workflow_id>.md is present and approved. Reads the plan's slice list, derives a dependency DAG in two explicit phases, and writes the extended slice list back into the same plan doc. Phase (a) is deterministic set-intersection over touched_paths — no LLM judgment. Phase (b) adds semantic edges with reason fields. The validator rejects any output that drops a phase-(a) edge, contains a cycle, or references a non-existent slice id.
Procedure
Step 1 — Verify input
Check that docs/plans/<workflow_id>.md exists. If absent, stop immediately and output:
Cannot start decompose: docs/plans/<workflow_id>.md not found.
Run plan-synth first to produce the plan doc, then obtain approval before running decompose.
Do not proceed until the file exists.
Step 2 — Parse the slice list
Read docs/plans/<workflow_id>.md. Locate the fenced yaml block under ## Slices. Parse the YAML. Extract:
- Each slice's
id - Each slice's
touched_pathsarray
Confirm every slice has semantic_depends_on: [] (as produced by plan-synth). If any slice already has non-empty semantic_depends_on, stop and ask the user whether the plan doc was already partially processed by a previous decompose run. Do not overwrite existing edges silently.
Step 3 — Phase (a): Derive file-overlap edges (mechanical, no LLM reasoning)
This phase is purely algorithmic. No LLM judgment about file semantics is used.
3a. Compute pairwise path intersections
For every ordered pair of distinct slices (A, B):
- Compute
overlap = intersection(A.touched_paths, B.touched_paths). - Use exact string equality on the glob strings themselves (do not expand globs — compare the written glob patterns as strings).
- If
overlapis non-empty, record an undirected overlap relationship betweenAandB, noting the shared paths.
3b. Assign edge direction
For each overlapping pair (A, B), assign a directed dependency edge. Apply this decision rule in order:
- Creation vs. mutation: If one slice's
touched_pathsincludes a database migration or schema file (e.g.,src/db/migrations/**,*.sql,*.prisma,schema.*) and the other does not, the schema slice is the dependency (the non-schema slice depends on it). - Model vs. service: If one slice's
touched_pathsincludes a model or entity file (e.g.,src/models/**,*.entity.*) and the other touches only service or controller files, the model slice is the dependency. - Mutual overlap (neither rule applies): If neither rule resolves direction, record both slices as candidates for direction assignment and flag the pair for phase (b). In the phase (a) output, pick the alphabetically earlier slice id as the dependency — this is a stable placeholder; phase (b) may override the direction but must preserve the edge.
3c. Build the phase-(a) edge list
Collect all directed edges as an ordered list:
phase_a_edges = [
{ from: "<slice_id>", to: "<dep_slice_id>", shared_paths: ["..."] },
...
]
This list is the invariant that the validator checks. Every edge in phase_a_edges must appear in the final output's semantic_depends_on with no reason field.
3d. Cycle check after phase (a)
Before proceeding to phase (b), verify the directed edges from step 3b form an acyclic graph. Run a topological sort (Kahn's algorithm or DFS-based). If a cycle exists, stop and report:
Phase (a) produced a cycle. This means two or more slices share touched_paths in a way that
creates a circular dependency. The plan must be revised before decompose can proceed.
Cycle: <slice_id> → <dep_id> → ... → <slice_id>
Shared paths involved: <paths>
Options:
1. Edit the plan to separate the conflicting paths into a new slice.
2. Re-run decompose after plan approval.
Do not proceed to phase (b) if a cycle exists after phase (a).
Step 4 — Phase (b): Add semantic edges (agent judgment, additive only)
Phase (b) may only ADD edges to semantic_depends_on. It must not remove, modify, or reassign any edge established in phase (a).
Review the full slice list — titles, acceptance criteria, and touched_paths — and consider whether any slices have dependencies not captured by file overlap. Examples of semantic (non-file) dependencies:
- Slice B calls an API or function defined by slice A, even if they touch different files.
- Slice B's implementation requires knowledge of an interface, type, or contract authored in slice A.
- Slice B can only be tested correctly once slice A's behavior is observable in the running system.
- Slice B's acceptance criterion references an entity or concept created by slice A.
For each semantic dependency identified:
- Verify the dependency id references a real slice id in this plan. If not, skip it — do not create a dangling reference.
- Verify adding the edge does not create a cycle. If it would, skip it and note the conflict.
- Add the edge to
semantic_depends_onwith areasonfield that explains the non-file dependency in one sentence.
Do not add a semantic edge between two slices just because they are related in theme. The edge must represent a real ordering constraint: slice B cannot proceed (or cannot be correctly tested) until slice A is complete.
4a. Direction resolution for flagged pairs
For any pair flagged in step 3c (mutual overlap, alphabetical placeholder direction), revisit the direction now. Use your understanding of each slice's acceptance criteria and implementation intent to assign the correct direction. The edge must exist in the final output — direction change is permitted, but edge removal is not.
4b. Phase (b) cycle check
After adding all semantic edges, run the topological sort again. If a semantic edge introduced a cycle, remove that edge and note it in the output:
Semantic edge <from> → <to> was skipped: it would create a cycle.
Reason attempted: "<reason>"
Step 5 — Integrity check
Before writing output, verify:
- All phase-(a) edges present: For every edge in
phase_a_edges, the final slice list contains asemantic_depends_onentry with matchingidand noreasonfield. - Semantic edges have reason: Every edge added in phase (b) has a non-empty
reasonfield. - No dangling ids: Every
semantic_depends_on[*].idin the final output references a real sliceidin this plan. - DAG is acyclic: Topological sort of the final edge set succeeds.
- Slice ids unique and unchanged: No slice
idwas altered; all original slices are present. - No other slice fields modified: Only
semantic_depends_onarrays were changed. All other fields (title,acceptance_criteria,touched_paths,out_of_scope) are identical to the plan doc's original values.
If any check fails, correct the issue before writing output. Do not write a partial or invalid DAG.
Step 6 — Write output
Update docs/plans/<workflow_id>.md in place:
- Keep all frontmatter and prose sections unchanged.
- Replace only the YAML slice list in the
## Slicesfenced block with the updated slice list. - The updated slice list must be valid YAML parseable as a
slices:array.
Output format within the fenced block:
```yaml
slices:
- id: <id>
title: <title>
acceptance_criteria:
- "<criterion>"
touched_paths:
- <path>
semantic_depends_on:
- id: <dep_id>
# file-overlap edges have no reason field
- id: <dep_id>
reason: "<why this slice depends on the other>"
out_of_scope:
- <guard>
```
After writing, confirm the output to the user:
decompose complete.
Plan: docs/plans/<workflow_id>.md
Slices: <N>
Phase (a) edges: <count> (file-overlap, no reason field)
Phase (b) edges: <count> (semantic, with reason field)
Topological batches:
Batch 1 (no dependencies): <slice ids>
Batch 2 (depends on batch 1): <slice ids>
...
The plan doc is now ready for the build phase, which consumes the DAG to derive topological batches for parallel execution.
Constraints
- No substrate access. This skill reads only the plan doc. Substrate signals were incorporated by
plan-synth. Do not open any substrate file. - Two phases are mandatory and explicit. Phase (a) must complete (including its cycle check) before phase (b) begins. The phases must not be merged or collapsed.
- Phase (a) is purely mechanical. No LLM reasoning about file semantics in phase (a). Only set intersection on
touched_pathsstrings and the three-rule direction algorithm. - Phase (b) is additive only. The agent may add edges and may correct placeholder direction from step 3c, but must not remove any edge that phase (a) established.
- File-overlap edges have no
reasonfield. If an edge was derived in phase (a), its entry insemantic_depends_onmust contain onlyid:. Adding areasonfield to a file-overlap edge is a schema violation. - Semantic edges must have a
reasonfield. Every edge added in phase (b) must have a non-emptyreasonexplaining the non-file dependency. - Output must be acyclic. If the final DAG contains a cycle, do not write it — report the cycle and stop.
- All dep ids must be real. No dangling references. Check every
idinsemantic_depends_onagainst the slice list before writing. - Extend in place. Do not create a new file. Update
docs/plans/<workflow_id>.mdin place, changing only the YAML in the## Slicesfenced block. - Never mention any specific harness. Skills compose via shared artifact paths; the composition graph is the harness's concern.