feat(plan-rollout): compress SKILL.md.tmpl (296 → 204 lines, -31%)

Tighten step prose. All 8 steps + self-check + limits preserved
semantically. Behavior unchanged — same bash commands, same priority
order in slice ranking, same verdict-first design.

Combined with the docs/ compressions, total substantive diff drops
701 → 414 lines (-41%).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Manas Srivastava 2026-05-11 11:33:46 +05:30
parent 673665d165
commit cfc14e4ac5
2 changed files with 190 additions and 370 deletions

View File

@ -5,11 +5,10 @@ interactive: true
version: 0.1.0 version: 0.1.0
description: | description: |
Decomposition-as-artifact. Given a real working diff (and `SYSTEM.md` if Decomposition-as-artifact. Given a real working diff (and `SYSTEM.md` if
present), produces a written `decomposition.md` with per-slice file lists, present), produces a `decomposition.md` with per-slice file lists,
reader-time estimates, dependency edges, and contract-graph reconciliation reader-time estimates, dependency edges, and contract-graph reconciliation
flags. Runs after a diff exists — it analyzes actual files, not intentions. flags. Runs after a diff exists. Use when asked to "decompose the diff",
Use when asked to "decompose the diff", "write a decomposition.md", or "write a decomposition.md", or "plan-rollout". (gstack)
"plan-rollout". (gstack)
Voice triggers (speech-to-text aliases): "decompose the diff", "write a decomposition", "plan-rollout". Voice triggers (speech-to-text aliases): "decompose the diff", "write a decomposition", "plan-rollout".
allowed-tools: allowed-tools:
- Read - Read
@ -749,54 +748,35 @@ PLAN MODE EXCEPTION — always allowed (it's the plan file).
# /plan-rollout: Decomposition-as-Artifact # /plan-rollout: Decomposition-as-Artifact
You read a working diff (plus `SYSTEM.md` if present) and write You read a working diff (plus `SYSTEM.md` if present) and write
`decomposition.md` — a per-slice breakdown a tired reviewer can pick up. `decomposition.md`. You never write code, never split branches, never
You never write code, never split branches, never run `/ship`. The output run `/ship`. The output is the artifact and only the artifact.
is the artifact and only the artifact.
## When to invoke this skill ## When to invoke
Run when a diff already exists (committed or working tree) and either: Run when a diff already exists (committed or working tree). Don't run on
- The user explicitly asks for `decomposition.md`. single-component, sub-30-min-reader-time diffs — produce the one-line
- The user has decided the work should ship as a stack and wants an "this is one PR" verdict and stop. False slicing is worse than no slicing.
analysis artifact tied to the actual files.
- A reviewer asked to see the change broken down before they read it.
If invoked before any code has been written, stop and say so: there is If invoked before any code has been written, stop and say so: nothing
nothing to decompose. Suggest writing the smallest end-to-end slice first to decompose, re-run against a real diff.
and re-running the skill against the real diff.
Don't run on single-component, sub-30-min-reader-time diffs unless the user Out of v1: `rollout.md`, spill-check, `/ship` and `/review` integration,
overrides — produce the one-line "this is one PR" verdict and stop. False SYSTEM.md scaffolding.
slicing is worse than no slicing.
Out of scope for v1: writing `rollout.md` (rollout/rollback strategy),
running spill-check against in-progress diffs, integrating with `/ship`
or `/review`, scaffolding `SYSTEM.md`. This skill produces decomposition.md
and stops.
## Step 0 — Detect repo state ## Step 0 — Detect repo state
Detect, in this order: 1. **Repo root:** `git rev-parse --show-toplevel`. Ask via
AskUserQuestion if not in a git repo; stop if unavailable.
2. **Base branch:** try `gh pr view --json baseRefName -q .baseRefName`,
then `origin/main`, then `origin/master`. Ask if unresolved.
3. **Head ref:** `git rev-parse --abbrev-ref HEAD`. If detached or on
the base, ask whether to use a description-only input.
4. **Plan source:** in order, (a) plan-file path from args; (b)
`~/.gstack/projects/<slug>/...-design-*.md` for this branch
(mirror `plan-eng-review`'s lookup); (c) AskUserQuestion for
paste / path / diff-only.
1. **Repo root.** `git rev-parse --show-toplevel`. If not a git repo, ask State each detected value back in one line. Facts, not prose.
the user for the project root path via AskUserQuestion and stop if they
can't provide one.
2. **Base branch.** Try in order: `gh pr view --json baseRefName -q .baseRefName`,
then `git rev-parse --verify origin/main`, then `origin/master`. If none
resolve, ask via AskUserQuestion.
3. **Head ref.** Current branch via `git rev-parse --abbrev-ref HEAD`. If
detached or on the base branch itself, you have no diff to decompose —
ask the user whether they want to plan from a description-only input
instead.
4. **Plan source.** Three possibilities:
- User passed a plan-file path as an argument → read it.
- A `~/.gstack/projects/<slug>/...-design-*.md` exists for this branch
(mirror the lookup pattern in `plan-eng-review`) → read it.
- Neither → AskUserQuestion: paste the plan text, point to a file, or
proceed with diff-only.
State each detected value back to the user in one line each. No prose
narration — just facts.
## Step 1 — Read SYSTEM.md if present ## Step 1 — Read SYSTEM.md if present
@ -804,208 +784,138 @@ narration — just facts.
test -f SYSTEM.md && cat SYSTEM.md || echo "(no SYSTEM.md — using path heuristics)" test -f SYSTEM.md && cat SYSTEM.md || echo "(no SYSTEM.md — using path heuristics)"
``` ```
If SYSTEM.md exists: If present: parse YAML frontmatter, build a path→component map
- Parse the YAML frontmatter. Components without a `path` field are skipped (longest-path-wins), build the contract graph (`rollout-edge: hard`
with a one-line warning. edges drive coordinated-deploy warnings).
- Build a path → component map. Longest path wins (so `src/auth/session/` resolves
to the more-specific component if both `src/auth` and `src/auth/session` are declared).
- Build the contract graph. `rollout-edge: hard` edges drive coordinated-deploy
warnings later.
If SYSTEM.md does not exist: If absent: fall back to one slice per top-level directory touched.
- Fall back to "top-level dir of change = component." Never invent a SYSTEM.md from heuristics.
- One slice per top-level directory touched by the diff. Never invent
a SYSTEM.md from heuristics — leave that to a future scaffolder.
## Step 2 — Enumerate the diff (committed + working tree + untracked) ## Step 2 — Enumerate the diff
The "diff to decompose" usually includes a mix of committed work and Capture committed + staged + unstaged + untracked work:
uncommitted work. Capture all of it. Use these commands (substitute the
base branch detected in Step 0):
```bash ```bash
# Tracked changes (committed + staged + unstaged) vs base. # Tracked changes vs base. NO triple-dot — `git diff <base>` compares
# Note: NO triple-dot. `git diff <base>` compares working tree to <base> # the working tree to <base> and includes everything not yet pushed.
# and includes everything that's not yet pushed.
git diff --name-status "<base>" git diff --name-status "<base>"
git diff --numstat "<base>" git diff --numstat "<base>"
git ls-files --others --exclude-standard # untracked, treat as fully added
# Untracked files (new files not yet `git add`-ed).
git ls-files --others --exclude-standard
``` ```
Union the two lists; treat each untracked file as fully added (count its Union and deduplicate by path. If empty, print "No changes between
line count via `wc -l`). Deduplicate by path. If you also want the
committed-only delta for context, run `git diff --name-status <base>...HEAD`
separately — but the working-tree view is what `decomposition.md` should
describe, because that's what the eventual PR will contain.
If the union is empty, exit early: print "No changes detected between
<base> and the current working state. Nothing to decompose." and stop. <base> and the current working state. Nothing to decompose." and stop.
For >200 files, warn and ask before proceeding.
For very large diffs (>200 files), warn the user and ask whether to Bucket each file: with SYSTEM.md, via the path map (unmatched → `(unmapped)`
proceed or abort — decomposing thousand-file diffs is not what this skill bucket, flagged in output). Without, by top-level directory.
is for.
Bucket each file: ## Step 3 — Light-touch import discovery
- If SYSTEM.md exists: file → component via the path map (Step 1). Files
outside any declared component path go to a `(unmapped)` bucket and
surface in the output as a flag.
- If no SYSTEM.md: file → top-level directory.
## Step 3 — Discover import edges (light-touch)
For each file in the diff, grep for imports/requires:
```bash ```bash
# TypeScript / JavaScript grep -E "^import .* from |^const .* = require\(" <file> # TS/JS
grep -E "^import .* from |^const .* = require\(" <file> grep -E "^(from |import )" <file> # Python
# Python grep -E "^import " <file> # Go
grep -E "^(from |import )" <file>
# Go
grep -E "^import " <file>
``` ```
Resolve each import to a component (or top-level dir) using the same Resolve each import to a component (or top-level dir). Record directed
path map. Record the directed edge `<file's component> → <imported component>`. edges `<file's component> → <imported component>`. Unresolvable imports
(external packages, ambiguous paths) are no-ops — log, don't fail.
These edges are how you order slices: a slice that imports another slice
must ship after it. For the MVP, treat unresolvable imports (external
packages, ambiguous paths) as no-ops — log them, don't fail.
## Step 4 — Propose the slice stack ## Step 4 — Propose the slice stack
Generate the stack with these rules, in priority order: Rules in priority order:
1. **Honor `rollout-edge: hard`** (if SYSTEM.md): if a contract with 1. **`rollout-edge: hard`** (SYSTEM.md): if BOTH sides of a hard contract
`rollout-edge: hard` connects two components and BOTH have changed have changed files, those files merge into one slice tagged
files, those files merge into a single slice with the annotation "coordinated deploy required — <breaks-if reason>".
"coordinated deploy required — <breaks-if reason>." 2. **Topological by import edges:** a slice ships after every slice it
2. **Topological by import edges:** a slice must come after every slice imports from. Cycles flagged + merged.
it imports from. Cycles get flagged and merged. 3. **`rollout-order`** breaks ties (lower first).
3. **`rollout-order` from SYSTEM.md** breaks ties: lower numbers ship first. 4. **`leaf-util` / `types-only`** float to slice 0.
4. **`leaf-util` / `types-only` components** float to slice 0 (foundational, 5. **Alphabetical** on remaining ties. Predictable > clever.
ship first, no contract dependents).
5. **Fall-through tie-break:** alphabetical by component name. Predictable
beats clever.
For each slice, compute: Per slice, compute: file list, lines +/-, dependencies, reader-time
- **Files included** (full list). (`ceil(lines/80) + ceil(files/5)` min; cap each slice at 30 min — split
- **Lines added / removed.** or flag if exceeded), reader guide (2-4 sentences, tired-reviewer voice).
- **Import dependencies on earlier slices.**
- **Reader-time estimate.** Heuristic: `ceil(lines_added / 80) + ceil(files / 5)` minutes,
doubled for files matching `*.test.*` or `test/*` (test bodies skim faster but
reviewers verify they cover the right surface). Cap a single slice at 30 min
reviewer-time; if it exceeds, the slice is too big and you must propose a
further split or flag it explicitly.
- **Reader guide.** Two to four sentences answering: what's in this slice,
why it ships first/middle/last, what to look for. No marketing voice —
it's a working note for a tired reviewer.
If the entire diff fits comfortably in one slice (<= 1 component touched, **One-PR escape:** if the diff is ≤1 component, ≤30 min reader time,
<= 30 min reader time, no hard edges), say so plainly: "This is one PR. no hard edges, write a one-line decomposition.md ("This is one PR. No
No decomposition needed." Output a one-line decomposition.md confirming decomposition needed.") and exit.
that and exit.
## Step 5 — Reconciliation flags (informational) ## Step 5 — Reconciliation flags (informational)
If SYSTEM.md was present, compute: With SYSTEM.md present, compute and print (never blocking):
- `import-without-contract`: components A and B have an import edge but
no declared contract. - `import-without-contract`: A imports B but no contract declared.
- `contract-without-imports`: a contract is declared with no supporting - `contract-without-imports`: contract declared with no supporting
import edge AND no `note: runtime-only`. import edge AND no `note: runtime-only`.
- `rollout-order-inversion`: a slice with lower rollout-order imports - `rollout-order-inversion`: declared order disagrees with discovered.
from a slice with higher rollout-order (i.e., declared order disagrees
with discovered order).
These are PRINTED in the output, never blocking. Resolve in a follow-up. ## Step 6 — Artifact location
## Step 6 — Choose artifact location AskUserQuestion:
Use AskUserQuestion to ask where `decomposition.md` should be written:
| Option | Path | | Option | Path |
|--------|------| |--------|------|
| In-repo (committed to branch) | `.gstack/plan-rollout/<branch-slug>-decomposition.md` | | In-repo | `.gstack/plan-rollout/<branch-slug>-decomposition.md` |
| User scope (uncommitted) | `~/.gstack/projects/<repo-slug>/<branch-slug>-decomposition.md` | | User scope | `~/.gstack/projects/<repo-slug>/<branch-slug>-decomposition.md` |
Default recommendation: in-repo when the branch already has other planning Recommend in-repo when other `.gstack/` planning artifacts exist on the
artifacts under `.gstack/`, user scope otherwise. State your recommendation branch; user scope otherwise.
and let the user pick.
## Step 7 — Write decomposition.md ## Step 7 — Write decomposition.md
Layout:
```markdown ```markdown
# Decomposition: <branch-name> # Decomposition: <branch-name>
**Base:** <base-branch> **Head:** <head-ref> **Diff:** <N files, +A / -D lines> **Base:** <base> **Head:** <head> **Diff:** <N files, +A / -D>
**SYSTEM.md:** <present | absent — heuristics used> **SYSTEM.md:** <present | absent — heuristics used>
**Generated:** <ISO timestamp> **By:** /plan-rollout vX.Y.Z **Generated:** <ISO timestamp> **By:** /plan-rollout vX.Y.Z
## Verdict ## Verdict
<One paragraph. Either: "This is one PR — no decomposition needed." <One paragraph. "This is one PR — no decomposition needed." | "Ship as
Or: "Ship as N PRs in this order. Total reviewer time: ~M minutes." N PRs in this order. Total reviewer time: ~M min." | "Stop. <issue>. Do
Or: "Stop. This diff has <unresolvable issue>. Do <X> first."> <X> first.">
## Slices ## Slices
### Slice 1: <component-or-dir name> ### Slice 1: <name>
**Files (<n>):** <list>
**Files (<n>):** <bulleted, full paths> **Diff:** +A / -D **Reader time:** ~M min **Depends on:** none | Slice K
**Diff:** +A / -D lines **Coordinated deploy:** <only if hard-edge applies>
**Reader time:** ~M min
**Depends on:** none | Slice K
**Coordinated deploy:** <only if a hard edge applies, else omit>
**Reader guide.** <2-4 sentences> **Reader guide.** <2-4 sentences>
### Slice 2: ... ### Slice 2: ...
...
## Reconciliation flags (informational) ## Reconciliation flags (informational)
- `import-without-contract` between auth and middleware (auth imports
middleware/types but no contract declared)
- ... - ...
(Emit only if SYSTEM.md present and ≥1 flag fired.)
(Only emit this section if SYSTEM.md was present and at least one flag fired.)
## What's NOT in this decomposition ## What's NOT in this decomposition
<Excluded on purpose. "All changed files allocated." if none.>
<Anything excluded on purpose: out-of-scope files, deferred slices, etc.
If everything in the diff is covered, say "All changed files allocated.">
``` ```
Write the file, print its path, and stop. Do not implement, do not split Write the file, print its path, stop. Do not implement, do not split
the branch, do not run `/ship`. The user reviews the decomposition and the branch, do not run `/ship`.
decides whether to act on it.
## Self-check before exit ## Self-check before exit
Before you write the final file, verify: 1. Every changed file in exactly one slice (or flagged `(unmapped)`).
1. Every changed file appears in exactly one slice (or in the `(unmapped)`
bucket with an explicit flag).
2. No slice depends on a later slice (no cycles). 2. No slice depends on a later slice (no cycles).
3. The verdict line is honest — if reader time totals 4 minutes and the 3. Verdict matches the math — if reader time is 4 min on 2 files, the
diff touched 2 files, the verdict is "this is one PR," not a 3-slice stack. verdict is "one PR," not a stack.
4. If SYSTEM.md was used, every slice maps to a real component name. 4. With SYSTEM.md, every slice maps to a real component name.
If any check fails, fix the decomposition before writing. If you can't If a check fails and you can't fix the decomposition, write the file
fix it, write the file with the failure flagged in the verdict and tell with the failure flagged in the verdict.
the user what you couldn't resolve.
## Limits ## Limits
- This skill does NOT enforce or split branches. It produces a doc. - Does NOT enforce or split branches — produces a doc.
- It does NOT validate `breaks-if` claims in SYSTEM.md — that's a human - Does NOT validate `breaks-if` claims — human judgment.
judgment. - Does NOT scaffold SYSTEM.md (deferred to v2).
- It does NOT scaffold SYSTEM.md (deferred to v2). - Reader-time estimates are heuristic; v1 has no calibration data.
- Reader-time estimates are heuristic. Calibrate against real reviewer - Mostly-one-component diffs with stray files: call them one PR with a
feedback over time; v1 has no calibration data yet. "stray files" flag. Don't force a 2-slice stack.
- For diffs that are mostly in one component but with a few stray files
in another, do NOT force a 2-slice stack — call it one PR with a
"stray files" flag instead. False slicing is worse than no slicing.

View File

@ -5,11 +5,10 @@ interactive: true
version: 0.1.0 version: 0.1.0
description: | description: |
Decomposition-as-artifact. Given a real working diff (and `SYSTEM.md` if Decomposition-as-artifact. Given a real working diff (and `SYSTEM.md` if
present), produces a written `decomposition.md` with per-slice file lists, present), produces a `decomposition.md` with per-slice file lists,
reader-time estimates, dependency edges, and contract-graph reconciliation reader-time estimates, dependency edges, and contract-graph reconciliation
flags. Runs after a diff exists — it analyzes actual files, not intentions. flags. Runs after a diff exists. Use when asked to "decompose the diff",
Use when asked to "decompose the diff", "write a decomposition.md", or "write a decomposition.md", or "plan-rollout". (gstack)
"plan-rollout". (gstack)
voice-triggers: voice-triggers:
- "decompose the diff" - "decompose the diff"
- "write a decomposition" - "write a decomposition"
@ -32,54 +31,35 @@ triggers:
# /plan-rollout: Decomposition-as-Artifact # /plan-rollout: Decomposition-as-Artifact
You read a working diff (plus `SYSTEM.md` if present) and write You read a working diff (plus `SYSTEM.md` if present) and write
`decomposition.md` — a per-slice breakdown a tired reviewer can pick up. `decomposition.md`. You never write code, never split branches, never
You never write code, never split branches, never run `/ship`. The output run `/ship`. The output is the artifact and only the artifact.
is the artifact and only the artifact.
## When to invoke this skill ## When to invoke
Run when a diff already exists (committed or working tree) and either: Run when a diff already exists (committed or working tree). Don't run on
- The user explicitly asks for `decomposition.md`. single-component, sub-30-min-reader-time diffs — produce the one-line
- The user has decided the work should ship as a stack and wants an "this is one PR" verdict and stop. False slicing is worse than no slicing.
analysis artifact tied to the actual files.
- A reviewer asked to see the change broken down before they read it.
If invoked before any code has been written, stop and say so: there is If invoked before any code has been written, stop and say so: nothing
nothing to decompose. Suggest writing the smallest end-to-end slice first to decompose, re-run against a real diff.
and re-running the skill against the real diff.
Don't run on single-component, sub-30-min-reader-time diffs unless the user Out of v1: `rollout.md`, spill-check, `/ship` and `/review` integration,
overrides — produce the one-line "this is one PR" verdict and stop. False SYSTEM.md scaffolding.
slicing is worse than no slicing.
Out of scope for v1: writing `rollout.md` (rollout/rollback strategy),
running spill-check against in-progress diffs, integrating with `/ship`
or `/review`, scaffolding `SYSTEM.md`. This skill produces decomposition.md
and stops.
## Step 0 — Detect repo state ## Step 0 — Detect repo state
Detect, in this order: 1. **Repo root:** `git rev-parse --show-toplevel`. Ask via
AskUserQuestion if not in a git repo; stop if unavailable.
2. **Base branch:** try `gh pr view --json baseRefName -q .baseRefName`,
then `origin/main`, then `origin/master`. Ask if unresolved.
3. **Head ref:** `git rev-parse --abbrev-ref HEAD`. If detached or on
the base, ask whether to use a description-only input.
4. **Plan source:** in order, (a) plan-file path from args; (b)
`~/.gstack/projects/<slug>/...-design-*.md` for this branch
(mirror `plan-eng-review`'s lookup); (c) AskUserQuestion for
paste / path / diff-only.
1. **Repo root.** `git rev-parse --show-toplevel`. If not a git repo, ask State each detected value back in one line. Facts, not prose.
the user for the project root path via AskUserQuestion and stop if they
can't provide one.
2. **Base branch.** Try in order: `gh pr view --json baseRefName -q .baseRefName`,
then `git rev-parse --verify origin/main`, then `origin/master`. If none
resolve, ask via AskUserQuestion.
3. **Head ref.** Current branch via `git rev-parse --abbrev-ref HEAD`. If
detached or on the base branch itself, you have no diff to decompose —
ask the user whether they want to plan from a description-only input
instead.
4. **Plan source.** Three possibilities:
- User passed a plan-file path as an argument → read it.
- A `~/.gstack/projects/<slug>/...-design-*.md` exists for this branch
(mirror the lookup pattern in `plan-eng-review`) → read it.
- Neither → AskUserQuestion: paste the plan text, point to a file, or
proceed with diff-only.
State each detected value back to the user in one line each. No prose
narration — just facts.
## Step 1 — Read SYSTEM.md if present ## Step 1 — Read SYSTEM.md if present
@ -87,208 +67,138 @@ narration — just facts.
test -f SYSTEM.md && cat SYSTEM.md || echo "(no SYSTEM.md — using path heuristics)" test -f SYSTEM.md && cat SYSTEM.md || echo "(no SYSTEM.md — using path heuristics)"
``` ```
If SYSTEM.md exists: If present: parse YAML frontmatter, build a path→component map
- Parse the YAML frontmatter. Components without a `path` field are skipped (longest-path-wins), build the contract graph (`rollout-edge: hard`
with a one-line warning. edges drive coordinated-deploy warnings).
- Build a path → component map. Longest path wins (so `src/auth/session/` resolves
to the more-specific component if both `src/auth` and `src/auth/session` are declared).
- Build the contract graph. `rollout-edge: hard` edges drive coordinated-deploy
warnings later.
If SYSTEM.md does not exist: If absent: fall back to one slice per top-level directory touched.
- Fall back to "top-level dir of change = component." Never invent a SYSTEM.md from heuristics.
- One slice per top-level directory touched by the diff. Never invent
a SYSTEM.md from heuristics — leave that to a future scaffolder.
## Step 2 — Enumerate the diff (committed + working tree + untracked) ## Step 2 — Enumerate the diff
The "diff to decompose" usually includes a mix of committed work and Capture committed + staged + unstaged + untracked work:
uncommitted work. Capture all of it. Use these commands (substitute the
base branch detected in Step 0):
```bash ```bash
# Tracked changes (committed + staged + unstaged) vs base. # Tracked changes vs base. NO triple-dot — `git diff <base>` compares
# Note: NO triple-dot. `git diff <base>` compares working tree to <base> # the working tree to <base> and includes everything not yet pushed.
# and includes everything that's not yet pushed.
git diff --name-status "<base>" git diff --name-status "<base>"
git diff --numstat "<base>" git diff --numstat "<base>"
git ls-files --others --exclude-standard # untracked, treat as fully added
# Untracked files (new files not yet `git add`-ed).
git ls-files --others --exclude-standard
``` ```
Union the two lists; treat each untracked file as fully added (count its Union and deduplicate by path. If empty, print "No changes between
line count via `wc -l`). Deduplicate by path. If you also want the
committed-only delta for context, run `git diff --name-status <base>...HEAD`
separately — but the working-tree view is what `decomposition.md` should
describe, because that's what the eventual PR will contain.
If the union is empty, exit early: print "No changes detected between
<base> and the current working state. Nothing to decompose." and stop. <base> and the current working state. Nothing to decompose." and stop.
For >200 files, warn and ask before proceeding.
For very large diffs (>200 files), warn the user and ask whether to Bucket each file: with SYSTEM.md, via the path map (unmatched → `(unmapped)`
proceed or abort — decomposing thousand-file diffs is not what this skill bucket, flagged in output). Without, by top-level directory.
is for.
Bucket each file: ## Step 3 — Light-touch import discovery
- If SYSTEM.md exists: file → component via the path map (Step 1). Files
outside any declared component path go to a `(unmapped)` bucket and
surface in the output as a flag.
- If no SYSTEM.md: file → top-level directory.
## Step 3 — Discover import edges (light-touch)
For each file in the diff, grep for imports/requires:
```bash ```bash
# TypeScript / JavaScript grep -E "^import .* from |^const .* = require\(" <file> # TS/JS
grep -E "^import .* from |^const .* = require\(" <file> grep -E "^(from |import )" <file> # Python
# Python grep -E "^import " <file> # Go
grep -E "^(from |import )" <file>
# Go
grep -E "^import " <file>
``` ```
Resolve each import to a component (or top-level dir) using the same Resolve each import to a component (or top-level dir). Record directed
path map. Record the directed edge `<file's component> → <imported component>`. edges `<file's component> → <imported component>`. Unresolvable imports
(external packages, ambiguous paths) are no-ops — log, don't fail.
These edges are how you order slices: a slice that imports another slice
must ship after it. For the MVP, treat unresolvable imports (external
packages, ambiguous paths) as no-ops — log them, don't fail.
## Step 4 — Propose the slice stack ## Step 4 — Propose the slice stack
Generate the stack with these rules, in priority order: Rules in priority order:
1. **Honor `rollout-edge: hard`** (if SYSTEM.md): if a contract with 1. **`rollout-edge: hard`** (SYSTEM.md): if BOTH sides of a hard contract
`rollout-edge: hard` connects two components and BOTH have changed have changed files, those files merge into one slice tagged
files, those files merge into a single slice with the annotation "coordinated deploy required — <breaks-if reason>".
"coordinated deploy required — <breaks-if reason>." 2. **Topological by import edges:** a slice ships after every slice it
2. **Topological by import edges:** a slice must come after every slice imports from. Cycles flagged + merged.
it imports from. Cycles get flagged and merged. 3. **`rollout-order`** breaks ties (lower first).
3. **`rollout-order` from SYSTEM.md** breaks ties: lower numbers ship first. 4. **`leaf-util` / `types-only`** float to slice 0.
4. **`leaf-util` / `types-only` components** float to slice 0 (foundational, 5. **Alphabetical** on remaining ties. Predictable > clever.
ship first, no contract dependents).
5. **Fall-through tie-break:** alphabetical by component name. Predictable
beats clever.
For each slice, compute: Per slice, compute: file list, lines +/-, dependencies, reader-time
- **Files included** (full list). (`ceil(lines/80) + ceil(files/5)` min; cap each slice at 30 min — split
- **Lines added / removed.** or flag if exceeded), reader guide (2-4 sentences, tired-reviewer voice).
- **Import dependencies on earlier slices.**
- **Reader-time estimate.** Heuristic: `ceil(lines_added / 80) + ceil(files / 5)` minutes,
doubled for files matching `*.test.*` or `test/*` (test bodies skim faster but
reviewers verify they cover the right surface). Cap a single slice at 30 min
reviewer-time; if it exceeds, the slice is too big and you must propose a
further split or flag it explicitly.
- **Reader guide.** Two to four sentences answering: what's in this slice,
why it ships first/middle/last, what to look for. No marketing voice —
it's a working note for a tired reviewer.
If the entire diff fits comfortably in one slice (<= 1 component touched, **One-PR escape:** if the diff is ≤1 component, ≤30 min reader time,
<= 30 min reader time, no hard edges), say so plainly: "This is one PR. no hard edges, write a one-line decomposition.md ("This is one PR. No
No decomposition needed." Output a one-line decomposition.md confirming decomposition needed.") and exit.
that and exit.
## Step 5 — Reconciliation flags (informational) ## Step 5 — Reconciliation flags (informational)
If SYSTEM.md was present, compute: With SYSTEM.md present, compute and print (never blocking):
- `import-without-contract`: components A and B have an import edge but
no declared contract. - `import-without-contract`: A imports B but no contract declared.
- `contract-without-imports`: a contract is declared with no supporting - `contract-without-imports`: contract declared with no supporting
import edge AND no `note: runtime-only`. import edge AND no `note: runtime-only`.
- `rollout-order-inversion`: a slice with lower rollout-order imports - `rollout-order-inversion`: declared order disagrees with discovered.
from a slice with higher rollout-order (i.e., declared order disagrees
with discovered order).
These are PRINTED in the output, never blocking. Resolve in a follow-up. ## Step 6 — Artifact location
## Step 6 — Choose artifact location AskUserQuestion:
Use AskUserQuestion to ask where `decomposition.md` should be written:
| Option | Path | | Option | Path |
|--------|------| |--------|------|
| In-repo (committed to branch) | `.gstack/plan-rollout/<branch-slug>-decomposition.md` | | In-repo | `.gstack/plan-rollout/<branch-slug>-decomposition.md` |
| User scope (uncommitted) | `~/.gstack/projects/<repo-slug>/<branch-slug>-decomposition.md` | | User scope | `~/.gstack/projects/<repo-slug>/<branch-slug>-decomposition.md` |
Default recommendation: in-repo when the branch already has other planning Recommend in-repo when other `.gstack/` planning artifacts exist on the
artifacts under `.gstack/`, user scope otherwise. State your recommendation branch; user scope otherwise.
and let the user pick.
## Step 7 — Write decomposition.md ## Step 7 — Write decomposition.md
Layout:
```markdown ```markdown
# Decomposition: <branch-name> # Decomposition: <branch-name>
**Base:** <base-branch> **Head:** <head-ref> **Diff:** <N files, +A / -D lines> **Base:** <base> **Head:** <head> **Diff:** <N files, +A / -D>
**SYSTEM.md:** <present | absent — heuristics used> **SYSTEM.md:** <present | absent — heuristics used>
**Generated:** <ISO timestamp> **By:** /plan-rollout vX.Y.Z **Generated:** <ISO timestamp> **By:** /plan-rollout vX.Y.Z
## Verdict ## Verdict
<One paragraph. Either: "This is one PR — no decomposition needed." <One paragraph. "This is one PR — no decomposition needed." | "Ship as
Or: "Ship as N PRs in this order. Total reviewer time: ~M minutes." N PRs in this order. Total reviewer time: ~M min." | "Stop. <issue>. Do
Or: "Stop. This diff has <unresolvable issue>. Do <X> first."> <X> first.">
## Slices ## Slices
### Slice 1: <component-or-dir name> ### Slice 1: <name>
**Files (<n>):** <list>
**Files (<n>):** <bulleted, full paths> **Diff:** +A / -D **Reader time:** ~M min **Depends on:** none | Slice K
**Diff:** +A / -D lines **Coordinated deploy:** <only if hard-edge applies>
**Reader time:** ~M min
**Depends on:** none | Slice K
**Coordinated deploy:** <only if a hard edge applies, else omit>
**Reader guide.** <2-4 sentences> **Reader guide.** <2-4 sentences>
### Slice 2: ... ### Slice 2: ...
...
## Reconciliation flags (informational) ## Reconciliation flags (informational)
- `import-without-contract` between auth and middleware (auth imports
middleware/types but no contract declared)
- ... - ...
(Emit only if SYSTEM.md present and ≥1 flag fired.)
(Only emit this section if SYSTEM.md was present and at least one flag fired.)
## What's NOT in this decomposition ## What's NOT in this decomposition
<Excluded on purpose. "All changed files allocated." if none.>
<Anything excluded on purpose: out-of-scope files, deferred slices, etc.
If everything in the diff is covered, say "All changed files allocated.">
``` ```
Write the file, print its path, and stop. Do not implement, do not split Write the file, print its path, stop. Do not implement, do not split
the branch, do not run `/ship`. The user reviews the decomposition and the branch, do not run `/ship`.
decides whether to act on it.
## Self-check before exit ## Self-check before exit
Before you write the final file, verify: 1. Every changed file in exactly one slice (or flagged `(unmapped)`).
1. Every changed file appears in exactly one slice (or in the `(unmapped)`
bucket with an explicit flag).
2. No slice depends on a later slice (no cycles). 2. No slice depends on a later slice (no cycles).
3. The verdict line is honest — if reader time totals 4 minutes and the 3. Verdict matches the math — if reader time is 4 min on 2 files, the
diff touched 2 files, the verdict is "this is one PR," not a 3-slice stack. verdict is "one PR," not a stack.
4. If SYSTEM.md was used, every slice maps to a real component name. 4. With SYSTEM.md, every slice maps to a real component name.
If any check fails, fix the decomposition before writing. If you can't If a check fails and you can't fix the decomposition, write the file
fix it, write the file with the failure flagged in the verdict and tell with the failure flagged in the verdict.
the user what you couldn't resolve.
## Limits ## Limits
- This skill does NOT enforce or split branches. It produces a doc. - Does NOT enforce or split branches — produces a doc.
- It does NOT validate `breaks-if` claims in SYSTEM.md — that's a human - Does NOT validate `breaks-if` claims — human judgment.
judgment. - Does NOT scaffold SYSTEM.md (deferred to v2).
- It does NOT scaffold SYSTEM.md (deferred to v2). - Reader-time estimates are heuristic; v1 has no calibration data.
- Reader-time estimates are heuristic. Calibrate against real reviewer - Mostly-one-component diffs with stray files: call them one PR with a
feedback over time; v1 has no calibration data yet. "stray files" flag. Don't force a 2-slice stack.
- For diffs that are mostly in one component but with a few stray files
in another, do NOT force a 2-slice stack — call it one PR with a
"stray files" flag instead. False slicing is worse than no slicing.