### Phase 2: Planning (Fable)

> **TLDR**  -  Fable decomposes the analysis (Opus when the fallback ladder engages) into concrete tasks with file-level targets, risk grading, and architecture review. Before Phase 3 a **Plan Approval Gate** runs in normal mode: if the Jira/issue description is ambiguous the orchestrator asks the user structured clarification questions (max 2 rounds)  -  once scope is clear it renders the plan and loops on free-text edit requests until the user approves or aborts. The gate is **skipped entirely** for a Short run and for `autopilot` (a Short run has no plan to approve; autopilot may not ask).

<!-- progress-contract: applied -->
Progress emission per `$HOME/.claude/multi-agent-refs/progress-contract.md`  -  lines for plan-draft start, clarification-ask, clarification-answer, plan render, plan-edit-request, plan-approved, plan-aborted.

## Phase 2 Pre-flight (BLOCKING, v9.0.0)

Phase 2 Planning consumes the analysis document. MCP forbidden.

1. **Analysis document presence**: read `state.analysis.docStatus`, which Phase 1 Step 4 set.
   - `produced` | `reused` -> the file is at `state.analysis.docPath[]`; continue with steps 2-4.
   - `not-applicable` -> no document by design (bugfix/chore, no Figma). Record it, skip steps 2-4, plan from `state.analysis` alone.
   - Unreadable or key missing -> `ERR: Phase 1 reported <status> but no analysis doc is readable. Resume with /multi-agent:resume #N.` Producing it is Phase 1's job; never send the user to another command.

2. **Parse YAML front-matter** into `state.analysis.frontMatter` and abort below `template_version: v3` - same contract as `phase-3-dev.md` step 2.

3. **Section coverage check**: verify these sections are non-empty (template v3 required sections per Locked 2):
   - Section 1 Summary, Section 2 Goals + Non-Goals, Section 4 User Stories, Section 9 API Contracts, Section 13 Architecture Plan, Section 14 Files to Add, Section 20 Risks, Section 21 References
   - Missing -> WARN, plan is allowed to proceed but Phase 4 reviewer flags it.

4. **Convert analysis tasks to plan**: Section 14 Files-to-Add becomes the seed task list. Carry each row's tag onto its todo as `sourceTag` (`Reuse` | `Add new` | `Modify`); it is an instruction Phase 3 follows and Phase 4 checks, not a label.

5. **MCP forbidden**: same rule as Phase 3.

#### Input contract

Phase 2 consumes the Phase 1 output object conforming to `$HOME/.claude/schemas/analysis-output.schema.json`. Read `state.analysis` (the explorer's return value) and treat its `touchedAreas` and `risks` arrays (plus `stack` and `summary`) as authoritative input  -  do not re-explore the codebase here. (Field names are exactly those in the analysis schema; the planner's own `targetFiles` belongs to `planning-output.schema.json`, not the analysis input.)

#### Step 0.9  -  Post-analysis confirmation (derive first, ask only what cannot be derived)

Runs when `state.analysis.docStatus` is `produced` or `reused`, before any planning.

**Derived is shown, not asked**: platform set, the seven convention groups, existing components (Code Connect + `uiComponents`), localization keys, analytics events, DI registration, test-method naming. **Only Section 20 rows are asked**, through `$HOME/.claude/multi-agent-refs/analysis/resolve.md` - one row, at most three source-labeled candidates, plus Defer. The engine never invents.

```
Türetildi (onay için):  platform=ios,android · 7 konvansiyon grubu (5 high, 2 medium)
                        · 12 mevcut bileşen (9 Code Connect bağlı) · 34 lokalizasyon anahtarı
Sorulacak:              4 açık soru (Bölüm 20)
```

A corrected value rewrites its Pass B footnote as `^[user-override: resolved <date>]` (Locked 24). Deferred rows stay in `state.analysis.openQuestions[]` and Phase 4 flags them `review_blocking`. Here and not Phase 4 because Phase 4 runs after development - answered there is answered too late. Autopilot skips the asking and defers every row.

#### Step 1  -  Task Decomposition

Break the work from Phase 1 analysis into discrete, implementable tasks:

```
For each identified change area:
  1. Define a task with clear scope (one file group or one logical change)
  2. Estimate complexity: trivial (1 file) / moderate (2-5 files) / complex (6+ files)
  3. Identify dependencies between tasks (which must complete before others)
```

Create tasks using TaskCreate with imperative subject, description (what/which files/expected behavior), and `addBlockedBy` for dependencies.

##### Analysis citation requirement (every UI task)

Every UI-touching task in the plan MUST cite:

- The analysis Section 6 (Bileşen Envanteri) row it implements (one task per row when a single component covers several variants), AND
- The canonical component name, sourced from the analysis doc:
  - **Code Connect mapping present**: take the component name verbatim from the matching repo `*.figma.swift` / `*.figma.kt` row referenced by analysis Section 6.
  - **Mapping absent**: cite "best-fit pending design review" and add a Risk row to the plan. The task description MUST also flag that Phase 4 will gate it as `review_blocking`.

Tasks without an analysis Section 6 citation when the doc lists UI components are rejected at the plan-approval gate; the user is asked to either re-run `/multi-agent:analysis` to extend Section 6 or rescope the task to a non-UI change. Direct Figma fetches in Phase 2 are forbidden (Locked decision 30).

Example task graph:

```
Task 1: Add new token to common module          (no deps)
Task 2: Create ButtonConfiguration.swift         (blocked by 1)
Task 3: Create ButtonView.swift                  (blocked by 2)
Task 4: Add ButtonView+Modifiers.swift           (blocked by 3)
Task 5: Write ViewInspector tests                (blocked by 3)
Task 6: Write snapshot tests                     (blocked by 3)
```

#### Step 2  -  Architecture Review (conditional)

Trigger architecture review if ANY of these are true:

- New module or package being created
- Cross-module dependency being added
- Public API surface changing
- Data model / schema change
- Navigation flow change

If triggered:

1. Launch Agent with `subagent_type: "ios-architect"` (or `architecture` skill for non-iOS)
2. Provide: task list, affected files, proposed approach
3. Agent returns: recommendation, risks, alternative approaches
4. Incorporate recommendations into task descriptions

If NOT triggered: skip, log "Phase 2: Architecture review  -  not needed (scope contained)"

#### Step 3  -  Development Approach per Task

For each task, determine:
| Approach | When | Example |
|----------|------|---------|
| **New file** | Feature doesn't exist | Create ButtonConfiguration.swift |
| **Modify** | Extending existing code | Add property to existing Configuration |
| **Refactor** | Restructuring without behavior change | Extract protocol from class |
| **Fix** | Bug correction | Fix nil crash in edge case |

Store approach in task metadata for Phase 3 agent.

#### Step 4  -  Skill Selection per Task

Based on Phase 1 `detectedStack`, assign relevant skills:

- iOS tasks -> `ai-ios-toolkit:*` SwiftUI skills, iOS patterns
- Python tasks -> `ai-backend-toolkit:fastapi-pro`, `ai-backend-toolkit:api-patterns`
- Node tasks -> `ai-backend-toolkit:nodejs-backend-patterns`
- Security-sensitive -> `ai-backend-toolkit:api-security-best-practices`
- Multi-submodule -> `ai-backend-toolkit:monorepo-architect`

#### Output contract

Phase 2 produces an object conforming to `$HOME/.claude/schemas/planning-output.schema.json`  -  `tasks[]` with `id`, `subject`, `targetFiles`, `complexity`, `blockedBy`, plus optional `architectureNotes` and `mode`. Phase 3 reads `tasks[]` in dependency order; the schema's `blockedBy` field drives the ready-task picker.

**Required: validator gate (deterministic)  -  run on the persisted file before the approval gate renders the plan; the validator's exit code decides, not the LLM turn:**

```bash
PLAN_FILE="$WORKTREE/.pipeline/plan.json"
mkdir -p "$(dirname "$PLAN_FILE")"
printf '%s' "$PLAN_JSON" > "$PLAN_FILE"
node $HOME/.claude/scripts/validate-planning.mjs "$PLAN_FILE"
```

Progress line: `    → checking validator validate-planning`

Non-zero exit fails CLOSED: emit the validator stderr + `errors[]` verbatim, attempt ONE self-correction rework (re-invoke the planner with the errors quoted, overwrite `$PLAN_FILE`), re-run the validator. If it fails again -> HALT the phase (never enter Phase 3 with an invalid plan) with recovery hint: `ERR: plan output failed validate-planning.mjs twice. Inspect $PLAN_FILE against $HOME/.claude/schemas/planning-output.schema.json, then resume with /multi-agent:resume #N.` Record `agent-state.phases["2"].validator` (`pass` | `pass-after-rework` | `halted`).

Log: "Phase 2: Plan  -  {N} tasks created, {M} with architecture review, validator:pass"

#### Step 4.5  -  Emit Plan Todo List (opt-in)

**Gated by `prefs.global.planTodos.enabled`** (default: `false`). When enabled, after the planning-output JSON validates and BEFORE the approval gate, transform `tasks[]` into a structured Todo list conforming to `$HOME/.claude/schemas/plan-todos.schema.json` and persist into `agent-state.plan`. The plan is rendered as a live, always-visible Todo list.

```bash
TODO_BLOB=$(jq '
  {
    title: .summary,
    todos: [ .tasks[] | {
      id:               .id,
      task:             .subject,
      status:           "pending",
      deps:             (.dependsOn // .blockedBy // []),
      estimatedMinutes: (.estimatedMinutes // null)
    } | with_entries(select(.value != null)) ]
  }
' <<<"$PLAN_JSON")

bash "$HOME/.claude/lib/plan-todos.sh" set "$TASK_ID" "$TODO_BLOB"
```

Phase 3 (Dev) then iterates with `plan-todos.sh next "$TASK_ID"` until empty, calling `start` before each step and `complete` (with notes) or `fail`/`skip` after. Phase 4 (Review) reads the Todo list to verify all `completed` items map to diff hunks. Phase 7 (Report) renders `list` into the agent-log + PR body.

**Why opt-in:** the existing `planning-output.schema.json` already drives Phase 3 dependency order  -  `plan.todos[]` is a richer surface (notes, durations, status transitions) but adds state writes per step. Off by default to keep the bare-bones flow unchanged; flip on for visibility into long features.

#### Step 4.8  -  Cross-artifact consistency check (required, before presenting the plan)

Before the plan is rendered for approval (Step 5b) or silently accepted (the autopilot skip path), verify it against `state.analysis` (drifted plans are the root cause of "PR does not match the ticket"):

1. **Requirement coverage**  -  every analysis requirement (`touchedAreas[]` entry, Section 14 row, acceptance criterion) maps to at least one plan task.
2. **Anchor integrity**  -  No plan task without an analysis anchor (each task cites the `touchedAreas[].path`, Section 6 row, or `risks[]` mitigation it implements).
3. **Open-question carry-over**  -  every analysis open question lands in the plan (clarification item, risk row, or explicit descope note); none silently dropped.

Progress line: `    → checking plan-vs-analysis consistency (3 checks)`

**On mismatch:** revise the plan ONCE (re-run Steps 1-4 with the gap list quoted, re-run the validator gate and this checklist). If gaps remain, do NOT loop: surface them in the Step 5b render under a `⚠️ Consistency gaps` banner (autopilot: log `plan.consistency_gaps={list}`). Persist `state.phases["2"].consistencyCheck = { "unmappedRequirements": [], "unanchoredTasks": [], "droppedOpenQuestions": [], "revised": true|false }`.

Log: "Phase 2: Consistency  -  requirements:{N/N mapped} anchors:{ok|M unanchored} open-questions:{carried|K dropped}"

#### Step 5  -  Plan Approval Gate (normal mode + autopilot safety)

**Scope guard  -  skip this step entirely when BOTH of these hold:**

- `state.autopilot === true` (autopilot contract: zero interaction)
- Autopilot safety classifier returns `recommendPause: false` (see Step 5c below)

OR:

- `state.onlyDevelop === true` (Short pipeline: direct to Phase 3, no plan)

In the skipped case, log `🧠 Phase 2: Plan  -  gate skipped ({mode}), proceeding to Phase 3` and go to Phase 3.

##### 5c  -  Autopilot safety classifier (runs before 5a/5b skip decision)

**Only relevant when `state.autopilot === true` and `prefs.global.autopilotSafetyGate !== false`.** The classifier protects autopilot's zero-interaction contract from edge cases where silent execution is genuinely dangerous (security-path touch, schema migration, many-file sprawl, delete-without-paired-test).

```bash
verdict=$(node $HOME/.claude/scripts/classify-plan-safety.mjs <(echo "$PLAN_JSON"))
pause=$(jq -r '.recommendPause' <<< "$verdict")
score=$(jq -r '.score' <<< "$verdict")
reasons=$(jq -r '.reasons[] | "• \(.rule) (+\(.weight)): \(.detail)"' <<< "$verdict")
```

| `recommendPause` | Action |
|---|---|
| `false` | Skip 5a/5b as before  -  autopilot proceeds to Phase 3. Log `🧠 Phase 2: Safety classifier  -  score {N}, autopilot proceeds`. |
| `true`  | Inject a one-time manual approval prompt even though we are in autopilot. Render the plan (5b shape) with the `reasons[]` list prepended. User sees `⚠️ Autopilot safety gate tripped (score {N})` banner and must explicitly choose Approve / Cancel (or edit via Other) in the 5b `AskUserQuestion` picker. Log `🧠 Phase 2: Safety classifier  -  score {N}, autopilot paused, reasons={rules}`. |

**Why opt-out instead of opt-in:** the asymmetry favors pausing. A pause on a high-blast-radius plan costs seconds; a silent auto-merge of a bad one costs hours of rollback or a revert PR. Users running tightly-scoped batch workflows (e.g. figma component iteration over known-safe components) can set `prefs.global.autopilotSafetyGate = false` if they've validated the task class is safe.

**Rules + weights**  -  see `classify-plan-safety.mjs` header comment for the canonical list. Summary: `file-count-high` (30) / `destructive-verb` (25) / `security-path` (35) / `delete-without-test` (30) / `schema-migration` (25) / `infrastructure` (20). Threshold: score ≥ 50 flips `recommendPause` true. Tuned so any single heavy signal or any two medium signals trigger the pause.

**Telemetry:** emit `phase.plan.safety` OTel span (when `MULTI_AGENT_OTEL_SPANS=1`) with `{score, recommendPause, rules}` so post-hoc analysis can tune weights.

Otherwise (normal mode), run the gate. The gate has **two modes** that chain: Clarification → Approval. Each round emits a progress line and persists to `agent-state.json.phases["2"]` for audit.

##### 5a  -  Clarification Mode (conditional, max 2 rounds)

Trigger if the plan Opus produced in Step 1-4 carries ANY ambiguity signal from Phase 1 analysis:

| Signal | Check |
|---|---|
| Vague acceptance | Jira/issue description < 200 chars AND no `## Acceptance Criteria` section |
| UI work, no design | Task touches `*View.swift` / `*Screen.kt` but Phase 1 captured no Figma URL |
| API work, no contract | Task touches network/repository layer but no endpoint/OpenAPI reference in Phase 1 |
| Ambiguous language | Phase 1 analysis flagged `ambiguityScore >= 2` (e.g. "improve", "fix", "update" with no object) |
| Parent-story scope drift | Sub-task covers wording from siblings of its parent story  -  child scope unclear |

If any signal trips, DO NOT render the plan yet. Render structured questions:

```
📋 Development Plan  -  {taskId}
────────────────────────────────────
⚠️  There are points that need clarifying before drafting a plan for this item:

1. {question 1}
2. {question 2}
3. {question 3}

Write your answers, then I will draft the plan.
(or tell me you want to pause  -  the task can be resumed later)
```

Wait for user response. On reply:

- **User asks to pause / cancel** (intent, in any language) → `state.status = "paused"`, log `🧠 Phase 2: Gate aborted (clarification)`, stop.
- **Free-text answer** → append to `state.phases["2"].clarificationAnswers`, bump `clarificationRounds`, re-run Steps 1-4 with answers as context, re-evaluate signals.

**Cap: 2 rounds.** If `clarificationRounds === 2` and signals still trip, do NOT ask again  -  render the plan anyway with a `⚠️ best-effort (item still unclear)` banner in Mode 5b, and let the user decide via approval/edit. This prevents infinite grooming loops on chronically under-specified tickets.

Persist to `state.phases["2"]`:

```json
{
  "clarificationRounds": 1,
  "clarificationQuestions": ["Pasaport scan sonrası...", "Backend endpoint...", "Android kapsamda mı?"],
  "clarificationAnswers": ["Silent reject. Toast yok.", "Backend hazır, v3.4 release'de.", "Sadece iOS bu task'ta."]
}
```

##### 5b  -  Approval Loop (always runs after clarification resolves or is skipped)

Render the plan:

```
📋 Development Plan  -  {taskId}          {best-effort banner if 5a capped}
────────────────────────────────────
Summary:    {1-line summary}
Approach:   {1-paragraph approach}
Risk:       {low|medium|high}  -  {reason}
Scope:      {S|M|L} ({N} files, {K} new)
Files to touch:
  - {path1}
  - {path2}

Todos ({N} total):
  1. {subject}  -  {approach}
  2. {subject}  -  {approach} (blocked by #1)
  ...

```

Then ask for the decision with a **native `AskUserQuestion` picker** (never a typed-keyword prompt):

- `question`: "Do you approve this plan?" (rendered in `outputLanguage`)
- `header`: "Plan" (English, <=12 chars)
- `options`:
  - `{ label: "Approve", description: "Proceed to development (Phase 3)" }`
  - `{ label: "Cancel", description: "Pause the task; resume later with :resume" }`

The picker's built-in **Other** field is the free-text edit channel: the user types an edit request there (e.g. "also look at the auth service but keep LoginView out of scope") instead of typing a keyword. (`option.label` stays English per the language matrix; `question` + `description` follow `outputLanguage`.)

Handle the selection:

- **Approve**:
  - Set `state.phases["2"].planApprovedAt = now()`, bump `planIterations` if not yet set (default 1)
  - Log `🧠 Phase 2: Plan approved (iterations={N}, clarificationRounds={M})`
  - Proceed to Phase 3
- **Cancel**:
  - Set `state.status = "paused"`, log `🧠 Phase 2: Plan aborted by user`, stop
- **Other (free-text edit request)**:
  - Treat the typed text as an edit request. Append to `state.phases["2"].planEditRequests`, bump `planIterations`
  - Pass the edit request + current plan to Opus; Opus revises and returns a new plan (same schema, same validator)
  - Re-render the plan (5b), loop

No hard cap on edit iterations  -  the user controls exit via the Approve / Cancel options. Between iterations, keep only the **latest plan** as canonical; previous renders are in the log for audit but do not re-enter the validator.

**Validator**: every revised plan goes through `node $HOME/.claude/scripts/validate-planning.mjs -` before re-render. If validation fails after an edit, log `⚠️ Phase 2: Plan validator failed after edit request #N  -  retrying Opus once` and retry once; on second failure surface the validator error to the user and go back to approval prompt with the pre-edit plan.

#### Step 6  -  Mode-specific short-circuit (reference)

The pipeline shapes interact with the gate as follows. This table is the source of truth for the gate's mode-awareness  -  if behavior diverges in code, fix the code, not the table.

| Mode | Clarification | Approval Loop | Safety Classifier | Notes |
|---|---|---|---|---|
| Full, interactive (`/multi-agent`, `:local`) | ✅ (max 2 rounds) | ✅ |  -  (redundant when a human approves) | Full gate |
| Short (depth picker answered Short) | ❌ | ❌ |  -  | Phase 1-2 skipped at Step 7.5; Phase 3 starts immediately |
| `autopilot`, `:local-autopilot` | ❌ | ❌ conditional | ✅ (if `autopilotSafetyGate !== false`) | Always Full, so a plan exists. Safe plans proceed silently; high-risk plans trigger a one-time manual approval. Log records the score. |

**Why autopilot now has an escape hatch:** the old "zero interaction  -  fully trust the scope" contract held well for tightly-scoped batch workflows (figma component iteration over known-safe components) but broke in edge cases  -  schema migrations auto-merging, security-path drift going silent, delete-without-test sprawls. The safety classifier (Step 5c) is opt-out so the default protects against the edge-case cost; users running known-safe workflows can flip `prefs.global.autopilotSafetyGate = false` to restore pre-v7.0 behavior.

#### Telemetry  -  token forwarding

After plan generation (and after each edit-loop iteration), forward Opus call totals so Phase 7's Cost Breakdown captures Phase 2:

```bash
LOG_METRIC_FORWARD_TO_TRACKER=1 $HOME/.claude/scripts/log-metric.sh "$TASK_ID" 2 plan.generated \
  model=opus tokens_in=$IN tokens_out=$OUT duration_ms=$DUR iteration=$N
```

Best-effort. See `$HOME/.claude/multi-agent-refs/progress-contract.md#token-telemetry-forwarding`.

---

## Token telemetry  -  invoke after every LLM call

```bash
bash $HOME/.claude/scripts/phase-tracker.sh tokens 2 <input_count> <output_count>
```

Contract and rationale: `progress-contract.md` -> Token telemetry forwarding.

