@@ -5,11 +5,10 @@ interactive: true
55version : 0.1.0
66description : |
77 Decomposition-as-artifact. Given a real working diff (and `SYSTEM.md` if
8- present), produces a written `decomposition.md` with per-slice file lists,
8+ present), produces a `decomposition.md` with per-slice file lists,
99 reader-time estimates, dependency edges, and contract-graph reconciliation
10- flags. Runs after a diff exists — it analyzes actual files, not intentions.
11- Use when asked to "decompose the diff", "write a decomposition.md", or
12- "plan-rollout". (gstack)
10+ flags. Runs after a diff exists. Use when asked to "decompose the diff",
11+ "write a decomposition.md", or "plan-rollout". (gstack)
1312 Voice triggers (speech-to-text aliases): "decompose the diff", "write a decomposition", "plan-rollout".
1413allowed-tools :
1514 - Read
@@ -749,263 +748,174 @@ PLAN MODE EXCEPTION — always allowed (it's the plan file).
749748# /plan-rollout: Decomposition-as-Artifact
750749
751750You read a working diff (plus ` SYSTEM.md ` if present) and write
752- ` decomposition.md ` — a per-slice breakdown a tired reviewer can pick up.
753- You never write code, never split branches, never run ` /ship ` . The output
754- is the artifact and only the artifact.
751+ ` decomposition.md ` . You never write code, never split branches, never
752+ run ` /ship ` . The output is the artifact and only the artifact.
755753
756- ## When to invoke this skill
754+ ## When to invoke
757755
758- Run when a diff already exists (committed or working tree) and either:
759- - The user explicitly asks for ` decomposition.md ` .
760- - The user has decided the work should ship as a stack and wants an
761- analysis artifact tied to the actual files.
762- - A reviewer asked to see the change broken down before they read it.
756+ Run when a diff already exists (committed or working tree). Don't run on
757+ single-component, sub-30-min-reader-time diffs — produce the one-line
758+ "this is one PR" verdict and stop. False slicing is worse than no slicing.
763759
764- If invoked before any code has been written, stop and say so: there is
765- nothing to decompose. Suggest writing the smallest end-to-end slice first
766- and re-running the skill against the real diff.
760+ If invoked before any code has been written, stop and say so: nothing
761+ to decompose, re-run against a real diff.
767762
768- Don't run on single-component, sub-30-min-reader-time diffs unless the user
769- overrides — produce the one-line "this is one PR" verdict and stop. False
770- slicing is worse than no slicing.
771-
772- Out of scope for v1: writing ` rollout.md ` (rollout/rollback strategy),
773- running spill-check against in-progress diffs, integrating with ` /ship `
774- or ` /review ` , scaffolding ` SYSTEM.md ` . This skill produces decomposition.md
775- and stops.
763+ Out of v1: ` rollout.md ` , spill-check, ` /ship ` and ` /review ` integration,
764+ SYSTEM.md scaffolding.
776765
777766## Step 0 — Detect repo state
778767
779- Detect, in this order:
780-
781- 1 . ** Repo root.** ` git rev-parse --show-toplevel ` . If not a git repo, ask
782- the user for the project root path via AskUserQuestion and stop if they
783- can't provide one.
784- 2 . ** Base branch.** Try in order: ` gh pr view --json baseRefName -q .baseRefName ` ,
785- then ` git rev-parse --verify origin/main ` , then ` origin/master ` . If none
786- resolve, ask via AskUserQuestion.
787- 3 . ** Head ref.** Current branch via ` git rev-parse --abbrev-ref HEAD ` . If
788- detached or on the base branch itself, you have no diff to decompose —
789- ask the user whether they want to plan from a description-only input
790- instead.
791- 4 . ** Plan source.** Three possibilities:
792- - User passed a plan-file path as an argument → read it.
793- - A ` ~/.gstack/projects/<slug>/...-design-*.md ` exists for this branch
794- (mirror the lookup pattern in ` plan-eng-review ` ) → read it.
795- - Neither → AskUserQuestion: paste the plan text, point to a file, or
796- proceed with diff-only.
797-
798- State each detected value back to the user in one line each. No prose
799- narration — just facts.
768+ 1 . ** Repo root:** ` git rev-parse --show-toplevel ` . Ask via
769+ AskUserQuestion if not in a git repo; stop if unavailable.
770+ 2 . ** Base branch:** try ` gh pr view --json baseRefName -q .baseRefName ` ,
771+ then ` origin/main ` , then ` origin/master ` . Ask if unresolved.
772+ 3 . ** Head ref:** ` git rev-parse --abbrev-ref HEAD ` . If detached or on
773+ the base, ask whether to use a description-only input.
774+ 4 . ** Plan source:** in order, (a) plan-file path from args; (b)
775+ ` ~/.gstack/projects/<slug>/...-design-*.md ` for this branch
776+ (mirror ` plan-eng-review ` 's lookup); (c) AskUserQuestion for
777+ paste / path / diff-only.
778+
779+ State each detected value back in one line. Facts, not prose.
800780
801781## Step 1 — Read SYSTEM.md if present
802782
803783``` bash
804784test -f SYSTEM.md && cat SYSTEM.md || echo " (no SYSTEM.md — using path heuristics)"
805785```
806786
807- If SYSTEM.md exists:
808- - Parse the YAML frontmatter. Components without a ` path ` field are skipped
809- with a one-line warning.
810- - Build a path → component map. Longest path wins (so ` src/auth/session/ ` resolves
811- to the more-specific component if both ` src/auth ` and ` src/auth/session ` are declared).
812- - Build the contract graph. ` rollout-edge: hard ` edges drive coordinated-deploy
813- warnings later.
787+ If present: parse YAML frontmatter, build a path→component map
788+ (longest-path-wins), build the contract graph (` rollout-edge: hard `
789+ edges drive coordinated-deploy warnings).
814790
815- If SYSTEM.md does not exist:
816- - Fall back to "top-level dir of change = component."
817- - One slice per top-level directory touched by the diff. Never invent
818- a SYSTEM.md from heuristics — leave that to a future scaffolder.
791+ If absent: fall back to one slice per top-level directory touched.
792+ Never invent a SYSTEM.md from heuristics.
819793
820- ## Step 2 — Enumerate the diff (committed + working tree + untracked)
794+ ## Step 2 — Enumerate the diff
821795
822- The "diff to decompose" usually includes a mix of committed work and
823- uncommitted work. Capture all of it. Use these commands (substitute the
824- base branch detected in Step 0):
796+ Capture committed + staged + unstaged + untracked work:
825797
826798``` bash
827- # Tracked changes (committed + staged + unstaged) vs base.
828- # Note: NO triple-dot. `git diff <base>` compares working tree to <base>
829- # and includes everything that's not yet pushed.
799+ # Tracked changes vs base. NO triple-dot — `git diff <base>` compares
800+ # the working tree to <base> and includes everything not yet pushed.
830801git diff --name-status " <base>"
831802git diff --numstat " <base>"
832-
833- # Untracked files (new files not yet `git add`-ed).
834- git ls-files --others --exclude-standard
803+ git ls-files --others --exclude-standard # untracked, treat as fully added
835804```
836805
837- Union the two lists; treat each untracked file as fully added (count its
838- line count via ` wc -l ` ). Deduplicate by path. If you also want the
839- committed-only delta for context, run ` git diff --name-status <base>...HEAD `
840- separately — but the working-tree view is what ` decomposition.md ` should
841- describe, because that's what the eventual PR will contain.
842-
843- If the union is empty, exit early: print "No changes detected between
806+ Union and deduplicate by path. If empty, print "No changes between
844807<base > and the current working state. Nothing to decompose." and stop.
808+ For >200 files, warn and ask before proceeding.
845809
846- For very large diffs (>200 files), warn the user and ask whether to
847- proceed or abort — decomposing thousand-file diffs is not what this skill
848- is for.
849-
850- Bucket each file:
851- - If SYSTEM.md exists: file → component via the path map (Step 1). Files
852- outside any declared component path go to a ` (unmapped) ` bucket and
853- surface in the output as a flag.
854- - If no SYSTEM.md: file → top-level directory.
810+ Bucket each file: with SYSTEM.md, via the path map (unmatched → ` (unmapped) `
811+ bucket, flagged in output). Without, by top-level directory.
855812
856- ## Step 3 — Discover import edges (light-touch)
857-
858- For each file in the diff, grep for imports/requires:
813+ ## Step 3 — Light-touch import discovery
859814
860815``` bash
861- # TypeScript / JavaScript
862- grep -E " ^import .* from |^const .* = require\(" < file>
863- # Python
864- grep -E " ^(from |import )" < file>
865- # Go
866- grep -E " ^import " < file>
816+ grep -E " ^import .* from |^const .* = require\(" < file> # TS/JS
817+ grep -E " ^(from |import )" < file> # Python
818+ grep -E " ^import " < file> # Go
867819```
868820
869- Resolve each import to a component (or top-level dir) using the same
870- path map. Record the directed edge ` <file's component> → <imported component> ` .
871-
872- These edges are how you order slices: a slice that imports another slice
873- must ship after it. For the MVP, treat unresolvable imports (external
874- packages, ambiguous paths) as no-ops — log them, don't fail.
821+ Resolve each import to a component (or top-level dir). Record directed
822+ edges ` <file's component> → <imported component> ` . Unresolvable imports
823+ (external packages, ambiguous paths) are no-ops — log, don't fail.
875824
876825## Step 4 — Propose the slice stack
877826
878- Generate the stack with these rules, in priority order:
879-
880- 1 . ** Honor ` rollout-edge: hard ` ** (if SYSTEM.md): if a contract with
881- ` rollout-edge: hard ` connects two components and BOTH have changed
882- files, those files merge into a single slice with the annotation
883- "coordinated deploy required — <breaks-if reason >."
884- 2 . ** Topological by import edges:** a slice must come after every slice
885- it imports from. Cycles get flagged and merged.
886- 3 . ** ` rollout-order ` from SYSTEM.md** breaks ties: lower numbers ship first.
887- 4 . ** ` leaf-util ` / ` types-only ` components** float to slice 0 (foundational,
888- ship first, no contract dependents).
889- 5 . ** Fall-through tie-break:** alphabetical by component name. Predictable
890- beats clever.
891-
892- For each slice, compute:
893- - ** Files included** (full list).
894- - ** Lines added / removed.**
895- - ** Import dependencies on earlier slices.**
896- - ** Reader-time estimate.** Heuristic: ` ceil(lines_added / 80) + ceil(files / 5) ` minutes,
897- doubled for files matching ` *.test.* ` or ` test/* ` (test bodies skim faster but
898- reviewers verify they cover the right surface). Cap a single slice at 30 min
899- reviewer-time; if it exceeds, the slice is too big and you must propose a
900- further split or flag it explicitly.
901- - ** Reader guide.** Two to four sentences answering: what's in this slice,
902- why it ships first/middle/last, what to look for. No marketing voice —
903- it's a working note for a tired reviewer.
904-
905- If the entire diff fits comfortably in one slice (<= 1 component touched,
906- <= 30 min reader time, no hard edges), say so plainly: "This is one PR.
907- No decomposition needed." Output a one-line decomposition.md confirming
908- that and exit.
827+ Rules in priority order:
828+
829+ 1 . ** ` rollout-edge: hard ` ** (SYSTEM.md): if BOTH sides of a hard contract
830+ have changed files, those files merge into one slice tagged
831+ "coordinated deploy required — <breaks-if reason >".
832+ 2 . ** Topological by import edges:** a slice ships after every slice it
833+ imports from. Cycles flagged + merged.
834+ 3 . ** ` rollout-order ` ** breaks ties (lower first).
835+ 4 . ** ` leaf-util ` / ` types-only ` ** float to slice 0.
836+ 5 . ** Alphabetical** on remaining ties. Predictable > clever.
837+
838+ Per slice, compute: file list, lines +/-, dependencies, reader-time
839+ (` ceil(lines/80) + ceil(files/5) ` min; cap each slice at 30 min — split
840+ or flag if exceeded), reader guide (2-4 sentences, tired-reviewer voice).
841+
842+ ** One-PR escape:** if the diff is ≤1 component, ≤30 min reader time,
843+ no hard edges, write a one-line decomposition.md ("This is one PR. No
844+ decomposition needed.") and exit.
909845
910846## Step 5 — Reconciliation flags (informational)
911847
912- If SYSTEM.md was present, compute:
913- - ` import-without-contract ` : components A and B have an import edge but
914- no declared contract.
915- - ` contract-without-imports ` : a contract is declared with no supporting
916- import edge AND no ` note: runtime-only ` .
917- - ` rollout-order-inversion ` : a slice with lower rollout-order imports
918- from a slice with higher rollout-order (i.e., declared order disagrees
919- with discovered order).
848+ With SYSTEM.md present, compute and print (never blocking):
920849
921- These are PRINTED in the output, never blocking. Resolve in a follow-up.
850+ - ` import-without-contract ` : A imports B but no contract declared.
851+ - ` contract-without-imports ` : contract declared with no supporting
852+ import edge AND no ` note: runtime-only ` .
853+ - ` rollout-order-inversion ` : declared order disagrees with discovered.
922854
923- ## Step 6 — Choose artifact location
855+ ## Step 6 — Artifact location
924856
925- Use AskUserQuestion to ask where ` decomposition.md ` should be written :
857+ AskUserQuestion:
926858
927859| Option | Path |
928860| --------| ------|
929- | In-repo (committed to branch) | ` .gstack/plan-rollout/<branch-slug>-decomposition.md ` |
930- | User scope (uncommitted) | ` ~/.gstack/projects/<repo-slug>/<branch-slug>-decomposition.md ` |
861+ | In-repo | ` .gstack/plan-rollout/<branch-slug>-decomposition.md ` |
862+ | User scope | ` ~/.gstack/projects/<repo-slug>/<branch-slug>-decomposition.md ` |
931863
932- Default recommendation: in-repo when the branch already has other planning
933- artifacts under ` .gstack/ ` , user scope otherwise. State your recommendation
934- and let the user pick.
864+ Recommend in-repo when other ` .gstack/ ` planning artifacts exist on the
865+ branch; user scope otherwise.
935866
936867## Step 7 — Write decomposition.md
937868
938- Layout:
939-
940869``` markdown
941870# Decomposition: <branch-name >
942871
943- ** Base:** <base-branch > ** Head:** <head-ref > ** Diff:** <N files, +A / -D lines >
872+ ** Base:** <base > ** Head:** <head > ** Diff:** <N files, +A / -D>
944873** SYSTEM.md:** <present | absent — heuristics used>
945874** Generated:** <ISO timestamp > ** By:** /plan-rollout vX.Y.Z
946875
947876## Verdict
948877
949- <One paragraph. Either: "This is one PR — no decomposition needed."
950- Or: "Ship as N PRs in this order. Total reviewer time: ~ M minutes."
951- Or: "Stop. This diff has < unresolvable issue >. Do <X > first.">
878+ <One paragraph. "This is one PR — no decomposition needed." | "Ship as
879+ N PRs in this order. Total reviewer time: ~ M min." | "Stop. < issue >. Do
880+ <X > first.">
952881
953882## Slices
954883
955- ### Slice 1: <component-or-dir name >
956-
957- ** Files (<n >):** <bulleted, full paths>
958- ** Diff:** +A / -D lines
959- ** Reader time:** ~ M min
960- ** Depends on:** none | Slice K
961- ** Coordinated deploy:** <only if a hard edge applies, else omit>
962-
884+ ### Slice 1: <name >
885+ ** Files (<n >):** <list >
886+ ** Diff:** +A / -D ** Reader time:** ~ M min ** Depends on:** none | Slice K
887+ ** Coordinated deploy:** <only if hard-edge applies >
963888** Reader guide.** <2-4 sentences>
964889
965890### Slice 2: ...
966891
967- ...
968-
969892## Reconciliation flags (informational)
970-
971- - ` import-without-contract ` between auth and middleware (auth imports
972- middleware/types but no contract declared)
973893- ...
974-
975- (Only emit this section if SYSTEM.md was present and at least one flag fired.)
894+ (Emit only if SYSTEM.md present and ≥1 flag fired.)
976895
977896## What's NOT in this decomposition
978-
979- <Anything excluded on purpose: out-of-scope files, deferred slices, etc.
980- If everything in the diff is covered, say "All changed files allocated.">
897+ <Excluded on purpose. "All changed files allocated." if none.>
981898```
982899
983- Write the file, print its path, and stop. Do not implement, do not split
984- the branch, do not run ` /ship ` . The user reviews the decomposition and
985- decides whether to act on it.
900+ Write the file, print its path, stop. Do not implement, do not split
901+ the branch, do not run ` /ship ` .
986902
987903## Self-check before exit
988904
989- Before you write the final file, verify:
990- 1 . Every changed file appears in exactly one slice (or in the ` (unmapped) `
991- bucket with an explicit flag).
905+ 1 . Every changed file in exactly one slice (or flagged ` (unmapped) ` ).
9929062 . No slice depends on a later slice (no cycles).
993- 3 . The verdict line is honest — if reader time totals 4 minutes and the
994- diff touched 2 files, the verdict is "this is one PR," not a 3-slice stack.
995- 4 . If SYSTEM.md was used , every slice maps to a real component name.
907+ 3 . Verdict matches the math — if reader time is 4 min on 2 files, the
908+ verdict is "one PR," not a stack.
909+ 4 . With SYSTEM.md, every slice maps to a real component name.
996910
997- If any check fails, fix the decomposition before writing. If you can't
998- fix it, write the file with the failure flagged in the verdict and tell
999- the user what you couldn't resolve.
911+ If a check fails and you can't fix the decomposition, write the file
912+ with the failure flagged in the verdict.
1000913
1001914## Limits
1002915
1003- - This skill does NOT enforce or split branches. It produces a doc.
1004- - It does NOT validate ` breaks-if ` claims in SYSTEM.md — that's a human
1005- judgment.
1006- - It does NOT scaffold SYSTEM.md (deferred to v2).
1007- - Reader-time estimates are heuristic. Calibrate against real reviewer
1008- feedback over time; v1 has no calibration data yet.
1009- - For diffs that are mostly in one component but with a few stray files
1010- in another, do NOT force a 2-slice stack — call it one PR with a
1011- "stray files" flag instead. False slicing is worse than no slicing.
916+ - Does NOT enforce or split branches — produces a doc.
917+ - Does NOT validate ` breaks-if ` claims — human judgment.
918+ - Does NOT scaffold SYSTEM.md (deferred to v2).
919+ - Reader-time estimates are heuristic; v1 has no calibration data.
920+ - Mostly-one-component diffs with stray files: call them one PR with a
921+ "stray files" flag. Don't force a 2-slice stack.
0 commit comments