The Bottom Line
Most developers who try /draft:decompose for the first time treat it like a planning tool. It isn’t. It’s an architectural guardrail that forces module boundaries to exist before your AI assistant gets a chance to invent random tasks. The output isn’t a to-do list — it’s a dependency graph, an implementation order, and (when complexity is high) a per-module contract.
Below is a real run on a 14-file track that went from a tangled task list to four cleanly-phased module builds, with a circular dependency caught and broken before any code was written.
The Track: “Add Multi-Provider Payment Support”
Existing system: a Node.js checkout service with a single hardcoded Stripe integration. ~6,000 lines, no payment abstraction, retries hardcoded, audit logs sprinkled across handlers. The spec landed in draft/tracks/payments-multi-provider/spec.md:
# Multi-Provider Payment Support
## Problem
Single Stripe integration is blocking enterprise deals
that require Adyen and PayPal. Refund flow is buggy.
Webhooks have no signature verification.
## Acceptance
- Stripe, Adyen, PayPal supported behind one provider interface
- Idempotent charge requests (retry-safe)
- Signed webhook verification per provider
- Refunds work for all three providers
- Full audit trail (who, what, when, response)
- Zero-downtime migration from existing Stripe path
A senior engineer would call this two sprints. Most AI assistants will write a 25-task plan in two minutes that misses three of those concerns.
What Happens If You Skip Decomposition
Run /draft:implement directly on the spec above without decomposition, and you get something like:
- [ ] Task 1.1: Add Stripe adapter
- [ ] Task 1.2: Add Adyen adapter
- [ ] Task 1.3: Add PayPal adapter
- [ ] Task 1.4: Add database column for provider
- [ ] Task 1.5: Add webhook endpoint
- [ ] Task 1.6: Implement signature verification
- [ ] Task 1.7: Add refund logic
- [ ] Task 1.8: Add audit logging
... (17 more tasks)
This plan is buildable, but every task touches every other module. There are no clean test boundaries. The “audit logging” task ends up scattered across 11 files. The “refund logic” task hits the same handler six times across the build. By task 14 the plan starts contradicting tasks 3–7.
The problem isn’t the AI. The problem is that nobody drew the modules first.
Running /draft:decompose --lld
The skill loads product.md, tech-stack.md, the track’s spec.md, and the existing knowledge graph (draft/graph/module-graph.jsonl). It scans the existing checkout codebase, identifies seven candidate modules, and prints a checkpoint:
═══════════════════════════════════════════════════════════
MODULE BREAKDOWN
═══════════════════════════════════════════════════════════
Scope: Track: payments-multi-provider
MODULE 1: provider-adapter
Responsibility: Translate generic payment requests to
provider-specific API calls.
Files: src/payments/providers/{stripe,adyen,paypal}.ts
API: ProviderAdapter.charge(), .refund(), .verifySignature()
Dependencies: provider-types
Complexity: Medium
MODULE 2: provider-types
Responsibility: Shared types for charge/refund/webhook payloads.
Files: src/payments/types.ts
API: ChargeRequest, ChargeResult, WebhookEvent
Dependencies: none
Complexity: Low
MODULE 3: payment-router
Responsibility: Pick a provider per merchant/region/currency
and route the call.
Files: src/payments/router.ts
API: PaymentRouter.charge(), .refund()
Dependencies: provider-adapter, provider-types,
payment-status-port
Complexity: High
MODULE 4: webhook-dispatcher
Responsibility: Verify signature, normalize event, dispatch.
Files: src/payments/webhook.ts
API: WebhookDispatcher.handle()
Dependencies: provider-adapter, provider-types,
refund-orchestrator
Complexity: Medium
MODULE 5: refund-orchestrator
Responsibility: Multi-step refund with reconciliation.
Files: src/payments/refund.ts, src/payments/refund-state.ts
API: RefundOrchestrator.start(), .complete()
Dependencies: payment-router, audit-log
Complexity: High
MODULE 6: audit-log
Responsibility: Append-only structured payment audit trail.
Files: src/payments/audit.ts
API: AuditLog.record()
Dependencies: provider-types
Complexity: Low
MODULE 7: idempotency-store
Responsibility: Persist client idempotency keys with TTL.
Files: src/payments/idempotency.ts
API: IdempotencyStore.claim(), .lookup()
Dependencies: none
Complexity: Low
═══════════════════════════════════════════════════════════
Two modules are High complexity — payment-router and refund-orchestrator. That auto-triggers LLD generation. The skill notifies:
One or more modules are High complexity — generating LLD automatically.
I made one edit in dialogue: I asked the skill to make audit-log a hard dependency of every state-changing module rather than treating it as a side concern. The skill regenerated the table and the dependency edges.
The Cycle — and How /draft:decompose Breaks It
When the skill ran dependency analysis (Step 4), it printed:
DEPENDENCY ANALYSIS
─────────────────────────────────────────────────────────
⚠ Circular dependency detected:
payment-router ──> webhook-dispatcher
│
▼
refund-orchestrator
│
▼
payment-router (cycle)
Reason:
- payment-router needs webhook-dispatcher to know
when async charges settle.
- webhook-dispatcher delegates refund webhooks to
refund-orchestrator.
- refund-orchestrator calls payment-router to
actually issue the refund.
Proposed break:
Extract `payment-status-port` (interface module).
- Both payment-router and webhook-dispatcher depend
on the port.
- refund-orchestrator depends only on payment-router.
- The port is a 30-line file, no business logic.
Apply break? [y/N]
This is the part nobody wants to discover at task 17. I accepted the break. The skill added an eighth module (payment-status-port) and re-ran topological sort.
Generated Mermaid Topology
The track-architecture file got a component diagram. Mermaid lives inline in architecture.md, so when /draft:implement reads context later, it sees the topology before writing code:
flowchart TD
subgraph "in scope"
R[payment-router]
A[provider-adapter]
W[webhook-dispatcher]
F[refund-orchestrator]
P[payment-status-port]
I[idempotency-store]
L[audit-log]
T[provider-types]
end
subgraph "existing"
C[checkout-service]
end
subgraph "external"
S[(Stripe API)]
Y[(Adyen API)]
PP[(PayPal API)]
end
C --> R
R --> A
R --> P
W --> P
W --> F
F --> R
F --> L
R --> I
A --> S
A --> Y
A --> PP
And a sequence diagram for the happy-path charge:
sequenceDiagram
participant C as Checkout
participant R as payment-router
participant I as idempotency-store
participant A as provider-adapter
participant L as audit-log
C ->> R: charge(req)
R ->> I: claim(idempotency_key)
I -->> R: claimed
R ->> A: charge(req, provider=stripe)
A ->> Stripe: POST /charges
Stripe -->> A: 200 OK
A -->> R: ChargeResult
R ->> L: record(charge.success)
R -->> C: ChargeResult
This isn’t decoration. It’s input the AI reads before writing handlers.
Implementation Order
1. provider-types (leaf)
2. idempotency-store (leaf)
3. audit-log (leaf)
4. payment-status-port (leaf, extracted to break cycle)
5. provider-adapter (depends on: provider-types)
6. payment-router (depends on: provider-adapter,
payment-status-port,
idempotency-store, audit-log)
7. webhook-dispatcher (depends on: provider-adapter,
payment-status-port)
8. refund-orchestrator (depends on: payment-router,
audit-log)
Parallel opportunities:
• Phase 1: provider-types, idempotency-store, audit-log,
payment-status-port (all independent — 4-way parallel)
• Phase 3: webhook-dispatcher and refund-orchestrator
can start in parallel after payment-router lands
Eight modules. Four phases. Two parallel windows. Nothing invented.
The LLD: Contracts for the High-Complexity Modules
Because two modules were marked High, --lld populated §6 of architecture.md. For payment-router:
// §6.1 — payment-router API Contract
interface PaymentRouter {
/**
* Pre: req.idempotency_key is non-empty
* Pre: req.amount > 0
* Post: ChargeResult.id is unique
* Invariant: idempotent — repeat calls with the same
* key return the original ChargeResult
* Errors:
* - ProviderError (transient, retry w/ backoff)
* - SignatureError (permanent, do not retry)
* - IdempotencyConflict (permanent, return original)
*/
charge(req: ChargeRequest): Promise<ChargeResult>
}
§6.3 documents retry policy per error class — exponential backoff for transient, no retry for permanent, circuit breaker after 5 consecutive transient errors per provider. /draft:implement later compiles tests from these contracts — the AI doesn’t get to invent a different error taxonomy mid-build.
plan.md, Restructured
Step 6 of the skill regenerated plan.md with phases mapped to modules:
## Phase 1: Foundation (parallelizable)
- [ ] Task 1.1: provider-types
- [ ] Task 1.2: idempotency-store
- [ ] Task 1.3: audit-log
- [ ] Task 1.4: payment-status-port
## Phase 2: Adapters
- [ ] Task 2.1: provider-adapter (Stripe)
- [ ] Task 2.2: provider-adapter (Adyen)
- [ ] Task 2.3: provider-adapter (PayPal)
## Phase 3: Router
- [ ] Task 3.1: payment-router (charge path)
- [ ] Task 3.2: payment-router (refund path)
## Phase 4: Async (parallelizable)
- [ ] Task 4.1: webhook-dispatcher
- [ ] Task 4.2: refund-orchestrator
Eleven tasks. Each lives inside one module. Each module has a contract. Each module is testable in isolation.
What This Buys You
- No mid-build surprises. The cycle was caught before code existed. Discovering it at task 17 would have meant redoing four files.
- Real test boundaries.
provider-adaptermocks are stable because the contract is fixed in §6.1. Tests don’t churn every time the implementation changes. - Parallel work. Two engineers (or two AI passes) implement Phase 1 leaves simultaneously. Same for Phase 4.
- A reviewable architecture diff.
architecture.mdis checked in. PRs that change module boundaries change it visibly — reviewers get architecture review for free.
The biggest unlock isn’t the diagram or the LLD. It’s that /draft:implement later reads architecture.md as context. The AI no longer invents a 25-task plan; it implements one module at a time against a contract that already passed your review.
Try It
# In a Draft-initialized repo with an active track
/draft:decompose --lld
# Or for a project-wide refresh after major refactors
/draft:decompose project
The output is two files: draft/tracks/<id>/architecture.md (or draft/architecture.md for project scope) and a restructured plan.md. Read them. Edit them. Then run /draft:implement.
Decomposition isn’t extra paperwork. It’s the first thing your AI assistant should be reading.