---
name: peaks-slice-decompose
description: Run multi-pass slice decomposition on a PRD-ready request and emit a v2 envelope with passes, cross-pass edges, and an LLM-arbitration trace. Use when a PRD is ready and the caller needs service-level and/or file-level cuts in one artifact.
---

<!-- peaks:loop-hygiene — HAND-MAINTAINED block, NOT generated; byte-identical in every SKILL.md, enforced by tests/unit/skills/loop-hygiene-block.test.ts. Edit one, edit all 23. -->

## Loop hygiene (every turn — MANDATORY)

**Skill header.** While this skill is active, open every turn with
`Peaks-Loop Skill: <skill> | Peaks-Loop Role: <role> | Peaks-Loop Gate: <gate> | Peaks-Loop Context: <ratio%> (<zone>) | Next: <one short action>`.
Every turn, not only the first — it is how the user knows which skill is driving.
`Role` and `Context` are machine facts: copy them from `peaks skill presence --json`
(`data.role`, `data.context.ratioPct`, `data.context.action`) — never invent them.

**Context is this skill's own business.** Run `peaks skill presence --json` every turn and
read its `context` block. When `action` is `auto-fire`, `pre-compact`, or `red-line`,
run `peaks code auto-compact --project .` **yourself**, then continue. Tell the user the
ratio in one line if it helps, but never hand them the compaction step: asking the user to
run `/compact` is the regression the zero-pause contract forbids. This holds in **every
mode** — standard *and* 24h. The mode moves the threshold, never the obligation.

**Read before you edit.** Read a file before your first `Edit` / `Write` / `MultiEdit` on
it — the normal way to work here — for every path outside `.peaks/**` (source, tests, docs,
config); `.peaks/**` writes are exempt.

**Expect one denial per file anyway — and do NOT pre-empt it.** A `PreToolUse` gate (ECC's
"Fact-Forcing Gate") denies the FIRST edit a session makes to any given path, once, by
design. Reading does NOT prevent it: the gate keys on the path's first touch, not on whether
you read it. **Do not recite its four questions before every edit** — it asks when it wants
them, and reciting unprompted burns a round-trip per file for nothing. Answer only when a
denial actually arrives.

**When one does arrive:** a denial is not a failure and the tool is not broken — your edit
was NOT applied. State the facts it asks for (importers, affected API, data schemas if any,
the user's verbatim instruction), then retry the SAME operation. The retry is allowed. Do not
switch tools, do not give up, do not retry blindly. One more thing worth knowing: an idle gap
of ~30 minutes clears the gate's "already passed" list, so a file you cleared earlier can be
denied again after a long pause. That is the gate resetting, not you regressing.
<!-- /peaks:loop-hygiene -->

## Single-scope-axis naming convention

> **Read once at the top of this file; the rest of the skill is written against it.**

The `.peaks/` workspace is partitioned by a **single scope axis** (session-id, at `.peaks/_runtime/<sessionId>/...`) with a nested **sub-agent axis** under `.peaks/_sub_agents/<sessionId>/...`. Use `<sessionId>` placeholders (NEVER bare `<sid>`). The peaks-loop change-id axis was removed in slice `2026-06-29-change-id-root-removal`; reviewable artifacts now live under `.peaks/_runtime/<sessionId>/<role>/...` only. OpenSpec's independent `openspec/changes/<change-id>/` vocabulary (L4) is preserved untouched. CLI mapping: session-id → `peaks session *`; sub-agent → `peaks sub-agent *`. Normative by documentation only — the guard that once asserted (a) zero bare `<sid>`, (b) an axis label on every runtime path, and (c) this callout's presence was deleted in `457b9a87`. Comply by convention.

## Read via `SchemaRouter.readResult()` — never parse the file directly

> **BLOCKING for any consumer of the v2 envelope.**

The decomposition file at `.peaks/sc/slice-decomposition/<rid>.json` may be either v1 (no `schemaVersion` field) or v2 (`schemaVersion: 'v2'`). Callers MUST use `SchemaRouter.readResult(<path>)` from `src/services/slice/schema-router.ts` and narrow on the discriminator:

```ts
import { readResult } from '../../services/slice/schema-router.js';

const parsed = readResult(outPath);
if (parsed.schemaVersion === 'v2') {
  handleV2(parsed); // DecompositionResultV2
} else {
  handleV1(parsed); // DecompositionResult
}
```

Do NOT call `JSON.parse(readFileSync(...))` directly. Unknown `schemaVersion` values throw `UnknownSchemaVersionError` with code `UNKNOWN_SCHEMA_VERSION` — surface that to the CLI as exit-code-mapped failure, do not silently fall back to v1.

# Peaks-Loop slice-decompose

`peaks-slice-decompose` wraps the v2 multi-pass orchestrator (`MultiPassOrchestrator.decompose`) and the v1/v2 dual-read SchemaRouter. It owns the artifact shape emitted by `peaks slice decompose` when a non-default `--granularity` is passed.

## Trigger conditions

Invoke when ALL of the following hold:

- a PRD body exists for `<rid>` (under `.peaks/2026/prd/requests/`, `.peaks/prd/requests/`, or `.peaks/_runtime/*/prd/requests/`);
- the request is **PRD-ready** (the PRD has acceptance criteria and is past `peaks-prd`'s draft gate);
- the caller needs one or both of:
  - service-level cuts (Pass 1) AND/OR file-level cuts (Pass 2) in a single artifact, OR
  - cross-pass edges (type-shares / fixture-shares / import-re-export / llm-arbitrated) connecting the two granularity levels.

Do NOT invoke for:

- v1-only consumers still on the 6-stage path (use the default `--granularity both` and the v1 envelope);
- re-deriving the PRD — this skill consumes PRD output, it does not produce it.

## Preconditions

- `peaks codegraph index` has been run at least once (the orchestrator reads codegraph status during Pass 1 / Pass 2);
- the audit-goal envelope for the project exists (`.peaks/_runtime/<sid>/audit-goal/<rid>.md` or equivalent) — without it, downstream `peaks-rd` dispatch cannot read the LLM-call budget context;
- `--granularity` value is one of `service`, `file`, `both`, `auto`. The CLI rejects other values with `code: SLICE_DECOMPOSE_FAILED` BEFORE any I/O. Default `both` keeps the v1 path — for v2, pass an explicit non-default value.

## Invocation

```bash
peaks slice decompose <rid> --granularity <service|file|both|auto> [--project <path>] [--refresh]
```

| Flag | Type | Default | Effect |
|---|---|---|---|
| `--granularity` | enum | `both` | `service` → Pass 1 only. `file` → Pass 2 only (no parent subdivision). `both` → Pass 1 + Pass 2 per parent, no filtering. `auto` → Pass 1 + Pass 2 only for parents where `shouldSubdivide()` is not explicitly false. |
| `--project` | path | `.` | Target project root. |
| `--refresh` | flag | `false` | Re-run `peaks codegraph index` before reading. |

Exit code is non-zero only on `SLICE_DECOMPOSE_FAILED` (PRD not found, codegraph not initialised, invalid granularity, etc.). A successful run always emits the v2 envelope on disk; partial failures set `partial: true` rather than failing.

## Output

```text
.peaks/sc/slice-decomposition/<rid>.json
```

The envelope is a `DecompositionResultV2` (see `references/v2-schema.md`):

| Top-level field | Purpose |
|---|---|
| `schemaVersion` | Literal `'v2'`. Discriminator vs v1. |
| `rid` | The request id passed in. |
| `generatedAt` | ISO 8601 UTC. |
| `passes` | Ordered `PassResult[]` — Pass 1 first, then Pass 2 (one entry per qualifying parent). |
| `crossPassEdges` | Edges spanning passes (see `references/cross-pass-edge-interpretation.md`). |
| `llmArbitrations` | Trace of every LLM call made during decomposition (capped at 2 per invocation). |
| `codegraph` | `CodegraphEnvelope` — nodes/edges/dbMB/freshness/affectedCrossFile/note. |
| `partial` | `true` iff any pass failed to complete. |

The file is pretty-printed (`JSON.stringify(result, null, 2)`). Size scales with `passes.length * slices.length` plus the arbitration trace; typical runs are 5–80 KB.

## How peaks-rd / peaks-qa / peaks-final-review consume this

- `peaks-rd` reads `passes[*].slices` to build the dispatch graph; `crossPassEdges` become the `dependsOn` set in `peaks request init`.
- `peaks-rd` reads `llmArbitrations[*].callId` (referenced by `CrossPassEdge.arbitratedBy`) to audit any LLM-inferred relationship.
- `peaks-qa` reads `passes[*].partial` and the top-level `partial` flag to detect incomplete decomposition before launching the verification fan-out.
- `peaks-final-review` reads `passes[*].granularity` to confirm the slice topology matches the PRD's stated scope.

Consumers MUST go through `SchemaRouter.readResult()`. v1 envelopes still on disk (legacy) are read transparently as `DecompositionResult`; the router narrows on `schemaVersion`.

## Surgical-change contract

This skill does NOT modify source code. It documents the v2 envelope produced by the W2 orchestrator modules (`multi-pass-orchestrator.ts`, `granularity-decider.ts`, `cross-pass-edge-merger.ts`, `slice-topology-types.ts`, `schema-router.ts`). Schema changes require a `schemaVersion` bump and a router update.

## References

| File | Coverage |
|---|---|
| `references/v2-schema.md` | Field-by-field table for `DecompositionResultV2`, `PassResult`, `SliceV2`, `CrossPassEdge.arbitratedBy`, `LlmArbitration`. |
| `references/granularity-decision.md` | Decision tree for the 4 `--granularity` modes; `shouldSubdivide()` thresholds + tie-break semantics. |
| `references/cross-pass-edge-interpretation.md` | The 4 edge kinds and how `peaks-rd` should use them for dispatch ordering; `LlmArbitration` trace reader. |

---

## SC template hard rules (sediment 2026-07-09)

These rules are added in response to **D-009a + D-009c** discovered while running RID 003-add-zcode-adapter. They prevent the same class of mistake from re-occurring in future SC drafts.

### Rule 1 — Pre-flight: detect ESM vs CJS before writing export syntax (D-009c)

Before specifying `--caller-id` / export guard syntax in any SC task, run:

```bash
head -3 package.json | grep '"type"'
```

- `"type": "module"` → ESM. Use `export const FOO = ...` for any constant that tests need to import. **Do NOT** write `if (typeof module !== 'undefined' && module.exports) { module.exports = { FOO }; }` — that pattern is CJS-only.
- No `"type"` field (or `"type": "commonjs"`) → CJS. Use the `module.exports` guard.

Why: RID-003 SC §3.5 originally specified the CJS guard for `scripts/install-skills.mjs`. The script is actually ESM (`"type": "module"`). The RD agent had to backtrack and the SC lost 5 minutes of spec clarity.

### Rule 2 — Whitelist fixture sync is NOT collateral (D-009a)

When a slice adds a new entry to a **whitelist fixture** (a test that asserts an exact set size, e.g. `expect(adapters).toHaveLength(7)`, and must be bumped when the 8th adapter is added), that fixture sync is a **primary task**, not a "by the way" footnote.

Mandatory structure for any slice that adds to a known set:

```markdown
### Tasks

| Task | File | Change |
|---|---|---|
| T-1 | `src/services/ide/ide-types.ts` | Add `'zcode'` to IdeId |
| T-2 | `src/services/ide/ide-registry.ts` | Register ZCODE_ADAPTER |
| T-3 | the adapter-registry whitelist fixture | Update whitelist: 7 → 8 adapters, add 'zcode' |
| T-4 | the install-skills platform whitelist fixture | Update whitelist: 5 → 6 platforms, add 'zcode' |
```

If you find a whitelist fixture during execution but didn't pre-list it as a task, treat it as a **NEW TASK** and run the SC ↔ QA repair loop, not as "necessary collateral". Pre-listing prevents surprises at Gate 1.

### Rule 3 — Adapter interface optional fields (D-009b reciprocal)

When extending `IdeAdapter` (or any plugin-interface in `src/services/**/ide/`), prefer **optional fields** over required fields so existing implementations don't need stub updates:

```ts
// ✅ Optional — back-compat safe
interface IdeAdapter {
  readonly detectCurrentModel?: () => Promise<string | undefined>;
}

// ❌ Required — every existing adapter must add a stub
interface IdeAdapter {
  readonly detectCurrentModel: () => Promise<string | undefined>;
}
```

If a field MUST be required, document in the SC why each existing implementation must be updated inline, and pre-list those updates as primary tasks (Rule 2 pattern).

---

**Sediment source:** `.peaks/_runtime/2026-07-08-session-17918f/qa/003-add-zcode-adapter/{slice-A-completion,slice-B-completion}.md` D-009a/b/c sections. Master lesson: `.peaks/memory/peaks-code-runbook-4-0-0-beta-6-skill-md-cli-d-001-d-002-d-003-d-010.md`.