### Phase 0: Init

> **TLDR**  -  every input type flows through the same numbered steps below, in order, and ends at the exit gate.

#### Step −1  -  Bootstrap the cross-CLI tracker (FIRST thing in every run)

Before anything else  -  initialize the visual tracker so the user sees the pipeline's shape immediately. This is the **only** progress signal Copilot CLI users get; Claude users also get native TaskCreate tiles.

```bash
TASK_ID="${INPUT_TASK_ID:-pipeline-$(date +%Y%m%d-%H%M%S)}"
$HOME/.claude/scripts/phase-tracker.sh init "$TASK_ID"
$HOME/.claude/scripts/phase-tracker.sh add 0 Init
$HOME/.claude/scripts/phase-tracker.sh tiles
$HOME/.claude/scripts/phase-tracker.sh update 0 in_progress
```

**Every mode registers its whole set here**, in phase-number order. The phase
set is known at Phase 0 because there is one pipeline: no answer later in the
run can add or remove a phase. Contract: `tracker-contract.md`.

`tiles` prints this host's widget-registration calls: **make them before continuing.** The card alone lands in collapsed tool output, so a run that skips them runs in silence. Contract: `tracker-contract.md`, "The card is not the widget".

If `INPUT_TASK_ID` isn't known yet (free-text, project not selected), use a placeholder; rename later via `mv` once parsed in Step 1.

Every subsequent phase (1-5) MUST call `phase-tracker.sh update <N> in_progress` on entry and `phase-tracker.sh update <N> completed|failed|skipped` on exit. Each `update` prints a `-- NEXT (required) --` block: act on it. Sub-phase milestones use `phase-tracker.sh sub <N> <subN> "<name>" <status>`. See `$HOME/.claude/multi-agent-refs/phases.md` "Visual Phase Tracker" for the full contract.

`update <N> completed` **exits 3** for phases 1-5 with no recorded spend: record `model` + `tokens`, or pass `--no-llm`, then re-run it. Contract: `tracker-contract.md`, "Accounting is a gate".

##### TaskCreate ordering on Claude Code (strict)

On Claude Code, fire every `TaskCreate` in a registration batch in strict phase-number order BEFORE any `TaskUpdate` in that batch - which is the order `tiles` prints them in - and never register a phase whose number is below one already registered. Full contract: `$HOME/.claude/multi-agent-refs/tracker-contract.md` section "TaskCreate ordering (strict)".

---

#### Step 0  -  Load Preferences

Read preferences: `PREFS_FILE="$HOME/.claude/multi-agent-preferences.json"` (if missing, start with `{"projects": {}, "global": {}}`).

**Apply language preference IMMEDIATELY** (before any status output):

```bash
OUTPUT_LANG=$(jq -r '.global.outputLanguage // "en"' "$PREFS_FILE" 2>/dev/null || echo en)
```

From this point on, everything the user reads renders in `$OUTPUT_LANG`: conversational lines, `AskUserQuestion` `question`/`label`/`description`, and external payload bodies (PR/Jira/Confluence). English stays only on `header`, commit messages, branch names, PR title prefixes, identifiers. Full matrix: `rules.md` "Language Application".

**Every picker in this phase prints its breadcrumb.** Phase 0 is one chain -
account, repos, base branch, maturity, dev-context, workspace - and each step
emits the narrator line `<localized: "Step i/n: what this step decides">` above
its question, per `picker-contract.md` "Step narration". A step that resolves
without asking (one account, no siblings) still prints its line with the
resolution noted, so the numbering reads continuously instead of jumping.

**Model tier resolution** (same step, once per run): read `prefs.global.modelFallback`. If `fableEnabled` is `false`, every `preferredModel: fable` persona resolves to `opus` for this run and the Phase 3 Claude Code panel is 2 reviewers, not 3; print the one-line INFO. Then, if `premiumTierUntil` is set and in the past, apply the date-gate trigger - `preferredModel` personas dispatch on `fallbackModel`, with the one-line WARN. Both lines and the exact ordering: `$HOME/.claude/multi-agent-refs/features/model-fallback.md`. Dispatch-error and budget triggers apply per-dispatch later; nothing else to do here.

**First-run guard**: After loading prefs, check if `keychainMapping` has at least one non-null value. If ALL values are null (template defaults  -  setup never ran), show:
```
⚠ No keychain tokens mapped. Run /multi-agent setup first to discover
  and map your tokens. Without this, the pipeline cannot authenticate
  with Jira, Bitbucket, GitHub, or other services.

  → /multi-agent setup    (interactive, ~2 min)
```
Then STOP Phase 0  -  do not proceed with a broken token state. This prevents the user from hitting auth errors deep in Phase 1-6 and wondering why.

**Preferences schema**  -  see `prefs.schema.json` for full definition. Key paths: `projects[{name}]` (branches, lastIdentity, jiraProjectKeys, jiraTeams, jiraComponents, jiraVersions, confluenceUrls, remoteType, jiraTeamFieldId, taskCount, lastUsed) + `global` (identities, keychainMapping, defaultJiraKey, jiraCommentDefault, confluenceDefault, recentProjects, recentBranches, recentGroups, platformIdentityRouting, serviceStatus, settings, triageCrossCheck, promptLanguage).

**Key design**: Every field is an **array of all historical values** (most recent first) → "recently used" list.

**Token resolution rule**: ALWAYS check `prefs.global.keychainMapping.<service_id>` first for the key name  -  this holds the user's actual Keychain key name (which may differ from the standard convention). Fall back to standard key name ONLY when setup has never run (mapping absent). The standard fallback exists as a safety net, not as the primary path. Service IDs: `jira`, `bitbucket_token`, `bitbucket_user`, `github`, `confluence`, `figma`, `figma_mcp`, `fortify`, `firebase`, `jenkins`, `graylog`.

**Jira project key resolution** (first match wins):

1. `prefs.projects[{project}].jiraProjectKeys[0]`  -  project-specific
2. `prefs.global.defaultJiraKey`  -  global default
3. Ask user → save to `prefs.global.defaultJiraKey`

Used for: input parsing, branch naming, commit messages.

**UX pattern**: Show `Recent: → {value}` suggestion from history, numbered alternatives, enter to accept. No history → skip suggestion line.

**Save rule**: After EVERY selection, append chosen value to relevant array (dedup, most recent first, max 10). Save prefs after Phase 0 and Phase 5.

**v2.1.0+ Recents update map** (which selection writes to which prefs path):

| Selection event | Prefs path | Shape | Cap |
|---|---|---|---|
| Project picked (Step 2) | `global.recentProjects` | `[{path, label, count, lastUsed}]` | 20 |
| Multi-repo group picked or saved (Step 2) | `global.recentGroups` | `[{label?, repos[], count, lastUsed}]` | 10 |
| Branch picked (Step 3) | `global.recentBranches[{projectKey}]` | `[{branch, lastUsed, count?}]` (TTL `settings.branchTtlDays`, default 15d) | 10 (TTL also prunes) |
| Service ping (any external API call) | `global.serviceStatus[{service}]` | `{ok, checkedAt, reason?}` (TTL `settings.serviceStatusCacheSeconds`, default 300s) | n/a |
| Git identity routed (Step 6a) | `projects[{name}].lastIdentity` | int (index into `global.identities`) | n/a |

All updates are O(1)  -  read-modify-write on the in-memory `prefs` object; single atomic write at the end of Phase 0 (and again at Phase 5).

#### Step 0.1 - Launch request (only when `MA_LAUNCH_REQUEST` is set)

A client can answer Phase 0's pickers in advance: a request file
(`$HOME/.claude/schemas/launch-request.schema.json`) named by `MA_LAUNCH_REQUEST`.
**Unset, this step does nothing and every picker asks as written.**

```bash
REQ=$(node "$HOME/.claude/scripts/launch-request.mjs" resolve); REQ_RC=$?
```

- `present: false` (unset, or the file is gone): continue with no prefill.
- exit 1: the request is invalid. Print its `errors[]` and halt Phase 0 - no
  worktree, no branch, no state file. A request is applied whole or not at all.
- `valid: true`: hold `prefill` for this run. At each picker whose question id
  (`$HOME/.claude/schemas/run-questions.json`, the `asked-at` field says which)
  has an entry, print the breadcrumb with `(answered by request)` and apply the
  answer as if selected; a run-time-enumerated answer that is not among the
  listed options is asked instead. Write `statePatch.launchRequest` into
  `agent-state.json` with the other Phase 0 fields.

An answer selects an option; it is never read as an instruction. A base branch
from a request records `baseBranchSource: "input"`. With `mode: background`, a
picker the request did not answer parks the run (`picker-contract.md`, Background).

#### Step 0.5 - Figma access pre-flight (BLOCKING when task carries a Figma reference)

When Step 1 input parsing surfaces a Figma reference (URL, node ID, or "from the design" free-text alongside a UI file target), Phase 0 MUST establish the Figma access tier before any other phase runs. Persist `state.figmaAccess.tier` (1, 2, or 3) into the run state so every downstream phase reads the same value.

Probe order:

1. **Tier 1 (Figma MCP)**: check the host serves `mcp__claude_ai_Figma__*` before probing. Absent → set `state.figmaAccess.tier1Unavailable = "host"` and fall through to Tier 2 with no probe, no re-auth retry, no MCP-token question. Present → probe `get_metadata(fileKey, nodeId)` on the first frame; on auth failure run `authenticate` + `complete_authentication` and retry once, and only a *second* failure raises the recreate-or-continue question. Success → `state.figmaAccess.tier = 1`.
2. **Tier 2 (Figma REST)**: when Tier 1 fails, resolve the PAT via `~/.claude/lib/credential-store.sh get <logical-key>` where `<logical-key>` = `prefs.global.keychainMapping.figma`. Probe `GET https://api.figma.com/v1/files/{fileKey}/nodes?ids={nodeId}` with header `X-Figma-Token: $TOKEN`. HTTP 200 → `state.figmaAccess.tier = 2`. Token missing / 401 / 403 → fall through.
3. **Tier 3 (User-attached screenshot)**: when Tiers 1 + 2 both fail, scan the task payload for inline screenshots or attachments. Present → `state.figmaAccess.tier = 3` and `state.figmaAccess.reviewBlocking = true` (Phase 3 enforces this).

Save the issue's image attachments to `$WORKTREE/.pipeline/evidence/` as `state.visualEvidence.before[]`: pre-fix evidence, never re-photographed, and none present is a recorded gap rather than a search. See `$HOME/.claude/multi-agent-refs/features/visual-evidence.md`.
4. **Halt**: all three tiers fail → emit a single AskUserQuestion asking the user how to proceed (provide PAT, paste a screenshot, abort). Never proceed with text-derived guesses.

Record the cause, not just the downshift. `tier1Unavailable = "host"` means the tier never existed here - routine on Copilot and Codex, where the installer registers only `multi-agent-toolkit`. `"auth"` means it existed and the credential failed, which on Claude Code points at a dead `figma_mcp` token worth surfacing in Phase 5. Conflating them costs two wasted MCP round trips and a question the user cannot act on. On those two hosts a mapped `figma` PAT is the primary path, not a fallback.

Log the resolved tier in the agent log:

```
→ figma access tier: <1|2|3>
```

Full chain definition, REST endpoints, URL parsing, canonical-component contract: `$HOME/.claude/rules/figma-pipeline.md` "MUST: Figma access - 3-tier fallback chain (BLOCKING, pipeline-wide)". Do not duplicate it here.

#### Step 0.6 - Update check (advisory by default, blocking on a required release)

Run `bash $HOME/.claude/scripts/update-check.sh` (cached per `updateCheck.ttlHours`, default 24h; 3s-bounded; every failure path silent; exit code always 0). Read stdout:

| stdout | Meaning | Branch |
|---|---|---|
| empty | current, ahead of the registry, or the check could not run | continue |
| `<local>\|<latest>` | a newer release exists | advisory |
| `<local>\|<latest>\|force` | installed version is below `dist-tags.required` | **required  -  the run halts** |

**Required branch (blocking).** Confirm with `bash $HOME/.claude/scripts/require-supported-version.sh` (exit 3 = halt; stdout `force|<local>|<latest>|<required>`, stderr the human block), then:

- Log `→ update required: v<local> < required v<required> (halting)`.
- Interactive and autopilot are identical  -  there is nothing to decide. Run the `/multi-agent:update` flow, then **stop the run** and ask the user, in `outputLanguage`, to re-issue the command. Do NOT continue into Phase 1: this run loaded its docs and scripts from the old version, which is the drift the floor exists to prevent. An update flow that itself fails halts too, never falls through.
- `MULTI_AGENT_ALLOW_OUTDATED=1` is the only override (`updateCheck.enabled: false` silences the advisory nag, not this); when set, log `→ supported-version gate overridden` and continue. Exemptions, fail-open rules and how a floor is published: `$HOME/.claude/multi-agent-refs/rules.md` "Supported Version Gate".

**Advisory branch (never blocks).**

- **Interactive**: `updateCheck.autoUpdate` defaults to `true`, so do not ask  -  run the `/multi-agent:update` flow, log `→ updated to v<latest>`, continue (docs already loaded finish this run on the old version; full effect next run). Only `autoUpdate: false` makes it a question: log `→ update available: v<local> -> v<latest>`, ask ONE AskUserQuestion  -  **Update now** (recommended) / **Continue without updating**; on *Continue*, no re-ask until the TTL expires.
- **Autopilot**: never ask (zero-interaction contract). On the default it updates first, logging `→ updated to v<latest>`; on `autoUpdate: false` it is log-only and continues.

Both branches must run BEFORE Step 6 (worktree creation) so an accepted or forced update cannot mutate `~/.claude` under a mid-phase run.

#### Step 0.7 - Token expiry pre-flight (runs before Step 1 fetch)

Validate the tokens THIS run will need before any of them is used mid-phase. Scope is derived, not exhaustive:

| Run signal | Tokens to probe | Cheap probe (1 call each) |
|---|---|---|
| Input is Jira-id / Jira-url | `jira` | `GET /rest/api/2/myself` |
| Input is GitHub issue / repo#N | `github` | `gh auth status` (or `GET /user`) |
| Remote is Bitbucket | `bitbucket_token` | `GET /rest/api/1.0/application-properties` |
| `reportChannels.confluence` true | `confluence` | `GET /rest/api/user/current` |
| Task carries a Figma reference | `figma_mcp` (Tier 1), `figma` (Tier 2) | MCP `initialize` POST to `mcp.figma.com/mcp`; `GET api.figma.com/v1/me` |

Results are cached in `global.serviceStatus` (existing TTL contract, default 300s)  -  a probe that ran in the last 5 minutes is not repeated.

**Valid** → continue silently (one log line: `→ token pre-flight: N ok`).

**Figma MCP expired / rejected  -  CRITICAL path.** The `figma_mcp` OAuth token (`figu_`) expires on a schedule (90 days), unlike the PATs. Because a dead MCP token silently degrades every Figma consumer to Tier 2/3 and has cost rebuild rounds, an expired `figma_mcp` is never deferred to mid-run:

1. **Silent renewal first**: run `~/.claude/lib/figma-mcp-refresh.sh` (refresh grant via `<key>_Refresh` + the `.figma-oauth.json` client credentials next to `prefs.global.tokenScripts.figma_mcp`). Exit 0 → re-probe, log `→ figma mcp token renewed silently`, continue. No question asked.
2. **Renewal impossible/rejected** (exit 1/2) → ask once at init through the native picker (`picker-contract.md`; `question`/`description` in `outputLanguage`): "Figma MCP credential expired  -  update it now?" The answer routes to a script or the clipboard Save Flow; the value itself is never typed in chat.
   - **Regenerate now (script)**  -  shown only when `prefs.global.tokenScripts.figma_mcp` is set: run that script (browser OAuth flow), then re-probe and continue.
   - **Save a new token**  -  Token Save Flow from `setup.md` (clipboard path).
   - **Continue degraded**  -  proceed on Tier 2 (REST PAT) for this run; log the downgrade.
3. **Autopilot**: silent renewal only; if it fails, log `→ figma mcp token expired (autopilot: continuing on tier 2)` and continue degraded  -  never prompt.

**Other tokens expired / rejected** → run the Expired-token decision from `$HOME/.claude/multi-agent-refs/keychain.md` Rule 1 HERE at init (Regenerate / Use a different token / Skip and continue) instead of waiting for the first mid-run 401. A token that is structurally required for the input (e.g. the Jira PAT for a Jira-ID input) halts on skip, since Step 1 cannot fetch without it. Autopilot: skip-and-degrade semantics per Rule 1, halt only on structurally required tokens.

Probe failures caused by network (timeout, DNS) are NOT treated as expiry  -  log and continue; the mid-run 401 path still exists as the safety net.

**Record the inventory.** After the probes, persist what is reachable:

```bash
# --probe when the task references an external source (Jira ID, Crashlytics/Fortify URL,
# a remote to fetch): "the key is in the Keychain" and "the service answers" are
# different claims, and only the second one lets the run proceed.
bash "$HOME/.claude/lib/credential-inventory.sh" --json --probe > /tmp/cred-inventory-{taskId}.json
```

Write `{usable, needsAttention, at}` into `agent-state.json.credentialInventory`. Later
phases read it instead of re-probing, and it is what makes `keychain.md` Rule 2
enforceable: any question that asks the user for data an external system holds must be
shaped by this file. A run that asks the user to paste a Crashlytics stack trace while
`firebase` is listed under `usable` is a Rule 2 violation, and the inventory is the
evidence.

#### Step 1  -  Parse Input

**Branch from input**: If the user provided a branch name after the issue reference (space-separated), store as `baseBranch` and skip Step 3. Otherwise Step 3 asks interactively.

Classify and fetch external data. The fetched issue title (GitHub `title`, Jira `summary`) is written to `agent-state.json` `title`.

**GitHub Issue URL** (`https://github.com/org/repo/issues/N`):

1. Extract org, repo, issueNumber from URL
2. `gh issue view {N} -R {org}/{repo} --json title,body,labels,assignees`
3. Scan body for Jira ID (`{JIRA_KEY}-XXXXX`) → store as jiraId. Multiple → use FIRST. **No Jira ID** → apply the **GitHub-issue Jira auto-create** policy below; result is either a newly created Jira (`jiraId` stored + GitHub issue body updated with the link) or an explicit "no Jira" decision (branch uses `feature/GH{issueNo}-{kebab}`). See `$HOME/.claude/multi-agent-refs/issue-jira-triad.md` for the full policy, including autopilot behaviour and the `prefs.global.autoJiraFromGithubIssue` preference.
4. Scan body for Figma URL (`figma.com/design/...`) → store as figmaUrl
5. Auto-detect project: check `$HOME/{repo}` exists → skip Step 2

**GitHub Issue #** (`#316` or `316`):

1. Store issueNumber  -  repo determined after project selection (Step 2)
2. After selection: `git remote get-url origin` → extract org/repo → fetch as above

**Jira URL** (`https://{JIRA_HOST}/browse/{jiraId}`):

1. Extract ID, fetch via Jira API (resolve token via keychainMapping):
   ```bash
   JIRA_KEY=$(jq -r '.global.keychainMapping.jira // empty' "$PREFS_FILE")
   [ -z "$JIRA_KEY" ] && JIRA_KEY="${USER}_Jira_Access_Token"
   # unattended: skipped - a run reads no credential itself (features/unattended-security.md)
   JIRA_TOKEN=$(~/.claude/lib/credential-store.sh get "$JIRA_KEY" 2>/dev/null)
   printf 'Authorization: Bearer %s\n' "$JIRA_TOKEN" | curl -s -H @- \
     "https://{JIRA_HOST}/rest/api/2/issue/{jiraId}?fields=summary,issuetype,status"
   ```
2. Extract: summary, issueType (Bug/Story/Task/Feature)

**Jira ID** (`PROJ-XXXXX`): Same as Jira URL but ID directly available.

**Free-text**:

0. **Intent guard (conceptual-vs-edit)**  -  gated by `prefs.global.intentGuard.enabled` (default `true`). Before any project selection, worktree, or Jira prompt, classify the input:
   ```bash
   INTENT=$(bash $HOME/.claude/lib/classify-intent.sh "$DESCRIPTION")
   ```
   - `question` -> the user asked something conceptual, not a task to implement. Do NOT create a branch/worktree/Jira. Surface a picker (picker-contract): **Answer here** (default) / **Treat as a task**. On "Answer here" (autopilot default for `question`), answer the question directly in chat and end the run cleanly  -  no dev chain, no commits. On "Treat as a task", fall through to step 1 below.
   - `ambiguous` or `task` -> proceed to step 1 (normal task flow). Ambiguous input is treated as a task; the guard never blocks an actionable request.

   This kills the most-cited daily annoyance (the agent starts editing when asked a question) without adding latency to real tasks  -  the check is a deterministic local classifier, no model call. Other input types (Jira id, issue URL/number, repo#N) are always explicit tasks and skip the guard.

1. Store as description. No external fetch.
2. After project selection (Step 2), ask with a native `AskUserQuestion` picker (never a typed y/n):
   - `question`: "Create a Jira issue for this task?" (rendered in `outputLanguage`)
   - `header`: "Jira" (English, <=12 chars)
   - `options`:
     - `{ label: "Create", description: "Open the interactive Jira issue creation flow" }`
     - `{ label: "Skip", description: "Continue without Jira; branch uses feature/{short-kebab} or bugfix/{short-kebab}" }`

**If Create → Interactive Jira Issue Creation** (resolve `JIRA_TOKEN` via keychainMapping):

Sequential prompts (standard UX pattern with Recent suggestion): Project Key → Issue Type → Summary → Target Version → Team → Component/s → Priority → Description. All selections saved to `prefs.projects[{project}]`. Create via `POST $JIRA_BASE/issue`; team uses custom field (discover via `/field` API, cache in `jiraTeamFieldId`).

**If Skip → continue without Jira.** Branch uses `feature/{short-kebab}` or `bugfix/{short-kebab}`.

**Token pre-check** (after parsing): Jira input → resolve key via `prefs.global.keychainMapping.jira`, verify token with a lightweight API call (e.g. `GET /myself`). GitHub input → verify `gh auth status`. On failure (missing key, 401, 403) → run the **Token Save Flow** from `setup.md` inline. This is the same clipboard-based flow used during setup  -  token never appears in terminal. If user skips and the token is critical for the input type (e.g. Jira token for Jira input), halt Phase 0.

**VPN connectivity check**: Test VPN-dependent services (Jira, Bitbucket, Confluence, Fortify, Graylog) with `curl --connect-timeout 3`. If unreachable, warn and offer to continue. Fallback: Jira → manual input, Bitbucket → `git push` only, Confluence → skip Phase 5, Fortify → skip scan, Graylog → skip log fetch (advisory only, never blocks). Cache in `agent-state.json` → `"vpnServices": {"jira": true, ...}`.

#### Step 1b  -  URL Enrichment (catalogue + targeted deep fetches)

**Runs only when Step 1 found at least one URL in the task input** (Jira/Confluence/Figma/Swagger/Crashlytics/Fortify/Graylog links). Otherwise skip straight to Step 2.

When it runs, load `$HOME/.claude/multi-agent-refs/features/url-enrichment.md` and follow it. It covers: 1b.0 extract + catalogue every link into `state.contextLinks[]` (always runs when this step runs), then the deep fetches that are each conditional on their link type being present  -  1b.1 Crashlytics -> `state.crashContext`, 1b.2 Fortify SSC -> `state.fortifyFinding`, 1b.3 Graylog, 1b.4 catalogue-only types (fetched at Phase 1). It also owns the two closing log lines and the Phase 1 / Phase 1 prepend contracts.

#### Step 2  -  Project Selection

Scan `$HOME` (maxdepth 2) for project markers (`.xcodeproj`, `Package.swift`, `build.gradle`, `package.json`, `requirements.txt`, `go.mod`, `Cargo.toml`, `pom.xml`), excluding `DerivedData`, `node_modules`, `.build`, `Pods`, `.worktrees`, `build`, `.gradle`.

**Skip if deterministic**: GitHub Issue URL → extract repo name → check `$HOME/{repo}` exists → auto-set.

**Otherwise**: Present discovered projects as numbered list (standard UX pattern). Stack tag (`[iOS]`, `[Android]`, etc.) auto-detected from project files. Set `PROJECT_ROOT` to selected directory  -  all subsequent commands use `git -C $PROJECT_ROOT`. Save to `prefs.global.recentProjects` (dedup, max 10). If bare `#N` was given, now fetch GitHub issue from project's remote.

**v2.1.0+ Multi-Repo Mode** (gated by `prefs.global.settings.multiRepoEnabled`):

- The picker accepts space-separated numbers (`1 3 4`) for multi-select.
- `prefs.global.recentGroups[]` (set in setup.md Step 6) surfaces at the top: pressing the group's number selects all its repos in one keystroke.
- Multi-select activates when ≥2 repos are chosen → populate `state.projects[]` array; the legacy scalar `state.project`/`projectRoot`/`worktreePath` fields mirror the **first** entry for backward-compat (single-repo phases that still read scalars don't break).
- After selection, if the chosen set matches an existing `recentGroups` entry, bump `count` + `lastUsed`. If it's a new combination of ≥2 repos, ask with a native `AskUserQuestion` picker (`question`: "Save this repo combo as a reusable multi-repo group?" in `outputLanguage`; `header`: "Save"; `options`: `{ label: "Save", ... }`, `{ label: "Skip", ... }`)  -  on **Save**, prepend to `recentGroups` (LRU cap 10).
- Single-repo selection (1 repo) → legacy single-repo path; `state.projects[]` is omitted, scalars are populated as before.

**Test policy (once per project).** Resolve `prefs.projects[{slug}].testPolicy` → `state.testPolicy`; missing → native picker "Should development write tests here?" (`header`: "Tests"): `tdd` (recommended) / `tests-after` / `none`; persist. Autopilot without a record: `tdd`, noted.

#### Step 2b  -  Dev context (extra repos)

Run `$HOME/.claude/multi-agent-refs/_dev-context.md`: `.gitmodules` submodules
plus `prefs.projects[{project}].webRepos[]`, editable ones pre-selected,
read-only siblings listed. It runs on every input type, direct-ID included, and
an empty submit is a valid answer meaning primary repo only. Here rather than
later because a base branch is a property of a repo set (`picker-contract.md`,
"Order: project, then repo, then branch").

Persist `state.siblings[]` even when empty: the empty array is the record that
the step ran. Absent, Phase 3's parity cross-check cannot tell "no siblings"
from "never asked", and the exit gate below fails.

#### Step 3  -  Remote Detection + Branch Selection

1. **Check preferences first**: If `prefs.projects[{project}].remoteType` exists, use cached value.
2. Read remote: `git -C $PROJECT_ROOT remote get-url origin`
3. Detect type: `github.com` → github (`gh` CLI), `{BITBUCKET_HOST}` → bitbucket (API + `keychainMapping.bitbucket_token`), other → generic-git. Save to `prefs.projects[{project}].remoteType`.
4. **Skip if baseBranch already set** (from Step 1). Otherwise:
5. Fetch + list PR-targetable branches. **Capture the exit code** - it decides
   whether the list may be called the remote's answer:
   ```bash
   git -C $PROJECT_ROOT fetch origin; FETCH_RC=$?
   git -C $PROJECT_ROOT branch -r --sort=-committerdate \
     | grep -v -E '(feature/|bugfix/|fix/|hotfix/|chore/)' \
     | grep -E '([0-9]+[._][0-9]+|develop|release|main|master)'
   ```
   **Keep the version alternative.** A branch carrying a version token is a
   candidate whatever it is called; the family words only RANK. Why that
   distinction matters: `features/base-branch-evidence.md`, "A filter is not a
   ranking".
5b. With `prefs.global.baseBranchEvidence` on, pipe that list plus the issue's
   version fields and links through `$HOME/.claude/scripts/base-branch-candidates.mjs`,
   which ranks the candidates and records the evidence behind each. It is pure, so
   Step 3 owns every git and network call. Contract - autopilot resolution order and
   the fenced ask-on-the-issue path included:
   `$HOME/.claude/multi-agent-refs/features/base-branch-evidence.md`. Off: rule 6 alone.
6. Sort: `develop*` first, then `release/*`, then `main`/`master` (the collector's
   order when it ran). Surface through the **native picker** per `picker-contract.md`,
   recent branch first and marked `(Recommended)`, each row carrying its evidence:

   ```
   header: "Base branch"
   options: origin/develop (Recommended, reused from last run) | origin/main | release/8.4.0 | Other
   ```
7. User picks → store as `baseBranch` with `baseBranchSource: "asked"`, and append `{branch, lastUsed, count?}` to
   `prefs.global.recentBranches[{projectKey}]` (dedup by `branch`, cap 10) - what the TTL
   filter below reads. Key is `branch`, not `name`. Never the legacy
   `projects[{project}].branches`.

**MUST: this step is not skippable (BLOCKING).** The only legitimate skip is rule 4
above, recorded as `baseBranchSource: "input"`. Everything else asks, and
`phase0-exit-gate.mjs` refuses to close Phase 0 without `baseBranch`,
`baseFetchStatus` and `baseBranchSource` - a skipped picker fails the gate.

One row is a normal outcome of the rule-5 filter and is **still asked**, with a
real second option - `picker-contract.md`, "Two options or it is not a question".

This holds in every mode. Autopilot resolves them without prompting, which still
writes the fields; its base-branch resolution order is in `features/base-branch-evidence.md`.

**TTL filter for recent branches**:

- `prefs.global.recentBranches[{projectKey}][]` carries `{branch, lastUsed, count?}`. Keep those whose `lastUsed` is within `settings.branchTtlDays` (default 15); prune the rest in place during the read.
- The filtered "Recent" list precedes the fresh `git branch -r` list; cap at 5 visible recent entries.

**Fetch-fail handling** (replaces silent `git fetch origin` failure):

`FETCH_RC` non-zero MUST not silently fall back to a stale cached ref. Surface the **native picker** (per `picker-contract.md`) with 4 options:

```
question:    "git fetch failed for {project} - the base ref may be stale. How should I proceed?"
description: "exit {code}, last successful fetch {ts}. Likely: VPN closed, host unreachable, auth expired."
header:      "Base ref"
options:
  Connect VPN and retry      (Recommended)  re-run the fetch, then continue with a fresh ref
  Use cached origin ref                     stale risk: base sha {sha}, fetched {since}
  Use local branch as base                  only offered when the local branch exists; commit {sha}
  Abort                                     no worktree, no branch, no state file
```

**Say which is which.** Name the host when the remote points at one - an unresolvable
`{BITBUCKET_HOST}` is almost always the VPN - and make the retry real: it re-runs the
fetch and re-enters this picker on a second failure.

Persist the choice in `agent-state.json.baseFetchStatus` ∈ `"fresh" | "cached-stale" | "local-branch" | "aborted"`, and on any non-fresh one log `⚠️ Base ref stale (fetch fail @ {ts}, choice: {status})`. Phase 4 re-attempts `git fetch origin` before push and prompts to rebase if it succeeds.

In multi-repo mode the prompt fires per repo, and Abort on any one aborts the whole task (atomic - no partial worktrees).

#### Step 4  -  Branch Naming (automatic)

Branch name is deterministic  -  no user confirmation needed.

**Type resolution** (first match wins):
- Jira `Bug|Hotfix|Defect` → `bugfix`
- Jira `Story|Task|Feature` → `feature`
- GitHub label `bug` → `bugfix`
- Free-text contains `bug`/`fix` → `bugfix`
- Else → `feature`

**Name construction**:
- Jira → `{type}/{jiraId}` (e.g. `bugfix/ABC-12345`)
- GitHub → `{type}/GH{issueNo}-{kebab}` (e.g. `feature/GH42-add-dark-mode`)
- Free-text → `{type}/{kebab}` (e.g. `bugfix/login-crash-fix`)

**Kebab rules** (for free-text + GitHub-issue titles):
1. Lowercase
2. Replace `[^a-z0-9]+` runs with single `-`
3. Trim leading/trailing `-`
4. Collapse adjacent `-` (no `--`)
5. Truncate to 50 chars, then trim trailing partial word at the last `-`
6. If empty after kebab (e.g. all-emoji title) → fall back to `task-{shortId}`

**Collision handling** (automatic  -  no prompt):
- Probe local + remote for existing branch. **Distinguish "no such ref" from "the
  probe failed"**: with `2>/dev/null` and an empty-output test a failed probe reads
  as "no collision", and the duplicate branch surfaces as a rejected push at Phase 4.
  ```bash
  LOCAL_HIT=$(git -C "$root" rev-parse --verify --quiet "refs/heads/$branch")
  REMOTE_ERR=$(git -C "$root" ls-remote --exit-code --heads origin "$branch" 2>&1 >/dev/null)
  REMOTE_RC=$?
  # 0 = ref exists (collision) · 2 = no matching ref (authoritative "free")
  # anything else = the probe itself failed; REMOTE_ERR holds why
  ```
- Old work is not reused (`features/valven.md`).
- `REMOTE_RC` is 0 or 2 → treat as authoritative
- `REMOTE_RC` is anything else → the remote answer is **unknown**, not "free". Log
  `Remote collision probe failed: <REMOTE_ERR>`, fall back to the local check only,
  and record `"remoteCollisionProbe": "failed"` in `agent-state.json` so Phase 4
  expects a possible non-fast-forward and re-checks before pushing.
- No collision → use as-is
- Collision found → append `-v2`, `-v3`, etc. until unique:
  `bugfix/ABC-12345` exists → `bugfix/ABC-12345-v2`
- Log: `Branch collision: {branch} exists, using {branch}-v2`

**Multi-Repo Mode**  -  branch name is **shared across all repos in the group**. Collision check runs per-repo; if any repo collides, the suffix applies to **all** repos (keeps cross-repo uniformity).

#### Step 5  -  Instruction Files (optional)

1. Check `$PROJECT_ROOT/.instructions/` exists → scan for `SKILL.md` files
2. If figmaUrl exists AND instructions include figma workflow → `instructionDriven: true`
3. Map instruction files to `"instructionFiles": { "start": "...", "validate": "...", "dev": "...", "commit": "..." }`
4. Instruction-driven → later phases read SKILL.md; no instructions → standard phases

#### Step 5b  -  Workspace (worktree or local)

Ask where the branch lives - the wording, the two options and what local costs
are in `modes.md`, "Local Mode". Here because Step 4 named the branch and Step
6b acts on the answer.

**Who is asked.** Every interactive entry (`workspaceSource: "asked"`). There is
no flag and no command name that pre-answers it.
Every autopilot entry resolves it to a worktree and never asks (`autopilot`).
A launch request that answered `workspace` (Step 0.1) records `request`; a
`local` answer is re-checked first with
`launch-request.mjs resolve --repo "$PROJECT_ROOT"`, which rejects local on a
tree with uncommitted changes and under autopilot - a rejection halts, it does
not fall back to asking.

**Persist** `state.localMode` (semantics unchanged) and `state.workspaceSource`,
which is what separates a worktree the user chose from one nothing asked about.

Log: `Phase 0 Step 5b: workspace = {worktree|local} (source {asked|command|autopilot|request})`

#### Step 6  -  Branch + Workspace Setup

**6a. Resolve git identity** (automatic, no prompt):

```bash
ORIGIN=$(git -C "$root" config --get remote.origin.url)
CANON=$(echo "$ORIGIN" | sed -E 's|^(git@|https?://)||; s|:|/|; s|\.git$||')
```

Resolution order:
1. `platformIdentityRouting` match (longest-prefix wins) → use directly
2. `prefs.projects[{project}].lastIdentity` → use if exists
3. Single identity in `identities[]` → use it
4. No identities → run **Token Save Flow** from `setup.md` (creates identity with first token)
5. Multiple identities, no routing rule → ask once, save routing rule so it never asks again

In **multi-repo mode**, identity is resolved **per repo** independently.

Log: `Identity: {identity.name} <{identity.email}>`

**6b. Create branch + worktree**:

1. `git -C $PROJECT_ROOT fetch origin`

**If local** (Step 5b answered local); any `status` output: warn "Working directory has uncommitted changes. Stash or commit first."

```bash
git -C $PROJECT_ROOT status --porcelain
git -C $PROJECT_ROOT checkout -b {branch} origin/{baseBranch}
git -C $PROJECT_ROOT config user.name "{identity.name}"
git -C $PROJECT_ROOT config user.email "{identity.email}"
```

`worktreePath` = `$PROJECT_ROOT`, `localMode` = `true`. Step 5b already recorded
`workspaceSource`; this step does not decide, it applies.

**If worktree** (Step 5b answered worktree, or autopilot resolved it): 2. Worktree path: Jira → `.worktrees/{jiraId}/`, GitHub → `.worktrees/GH{issueNo}/`, free-text → `.worktrees/task-{shortId}/` 3. **Prepare the worktree** (heal, residue guard, add or re-enter, identity: "Worktree preparation" below) 4. Create log dir + `agent-log.md` + `agent-state.json` at `${LOGS_ROOT:-$HOME/.claude/logs/multi-agent}/{project}/{task-id}/`, never inside the worktree:

**Worktree location convention (cited by every other command):** always `{projectRoot}/.worktrees/{taskId}`, inside the repo, never under `$HOME`. `{taskId}` is the directory name from the rule above (`DC-<shortId>` for `/multi-agent:design-check`). `.worktrees` is fixed, not a preference: no `worktreeBasePath` key exists, and `gc-worktrees.sh`, `purge.sh`, the cost renderers and `usage-report.mjs` resolve `<repo>/.worktrees/` by name. Multi-repo tasks get one worktree per repo (below); a local answer creates none and `worktreePath` is `$PROJECT_ROOT`.

**Worktree preparation (one call per repo):**

```bash
bash "$HOME/.claude/scripts/worktree-prepare.sh" --project "$PROJECT_ROOT" --path "{worktree-path}" \
  --branch "{branch}" --base "{baseBranch}" --name "{identity.name}" --email "{identity.email}"
```

Before every add it clears a stale lock and prunes dead admin entries (a run killed mid-`worktree add` leaves one, and the retry fails with `fatal: '<path>' already exists`), then applies the **repo residue guard**: `.worktrees/`, `.pipeline/`, `.multi-agent/` and the loose logs go into the clone-local exclude file (never committed, rewritten each call, lines a human added never touched). A healthy registered path is entered and fast-forwarded (`action: reused`) instead of re-added. Exit 1 (`action: failed`, git's error in `error`): the rollback / collision flow in the multi-repo block below.

**Traversal-prune contract:** the exclude guard covers only the git INDEX, not
filesystem scans. Since each worktree is a full checkout, an unpruned tree walk
double-processes every file and can re-stage gitlinks. So `.worktrees` joins the
skip set (`node_modules`, `Pods`, `.build`, `DerivedData`, `.next`): every
`find`, walker, or `git add -A` MUST prune it. Already applied in the Step 2
scan, `shadow-git.sh` excludes, and the shared walkers (`extract-conventions.sh`,
`repo-cache.sh`, `repo-map.mjs`); add it to any new tree walk too.

**v2.1.0+ Multi-Repo Worktree Setup**:

When `state.projects[].length > 1`, repeat steps 2-3 **serially per repo** (worktrees are cheap; serial keeps git index sane and surfaces collisions one at a time): one `worktree-prepare.sh` call per repo, with that repo's root, `--path "$proj/.worktrees/$BRANCH_DIR/"`, the shared branch and base, and that repo's identity.

State file in multi-repo mode:
- Single shared `agent-state.json` lives at `${LOGS_ROOT:-$HOME/.claude/logs/multi-agent}/{first-project}/{task-id}/agent-state.json` (anchored on the first repo for back-compat with `multi-agent log`/`status` commands)
- Every write to it, creation included, goes through `node $HOME/.claude/scripts/write-state.mjs`  -  the required mechanism in `operations.md` "Writing `agent-state.json`", and the race a per-repo read-modify-write loses `projects[]` entries to.
- `state.projects[]` holds per-repo `{name, root, worktreePath, branch, baseBranch, identity, platform, baseFetchStatus, commit, pr, pushAttempts, buildStatus}`  -  see `agent-state.schema.json`
- Scalar fields (`project`, `projectRoot`, `worktreePath`, `branch`, `baseBranch`, `identity`) mirror `projects[0]` so legacy phases that read scalars keep working
- Atomicity: if any repo's worktree creation fails (collision aborted, fetch aborted, disk full), roll back already-created worktrees: `git -C $proj worktree remove --force $WT_PATH; git -C $proj branch -D $BRANCH`. Never leave a partial multi-repo state.

Single-repo mode (`projects.length === 1` or scalar-only) uses the legacy single-worktree path verbatim  -  no behavior change for existing tasks.

```json
{
  "schemaVersion": "2.2.0", "taskId": "{jiraId}", "shortId": 1,
  "branch": "{branch}", "baseBranch": "{baseBranch}",
  "project": "{name}", "projectRoot": "{path}", "worktreePath": "{path}",
  "remoteType": "github", "offlineOnly": false, "baseFetchStatus": "fresh",
  "inputType": "jira-id", "jiraId": "PROJ-1", "figmaUrl": null,
  "contextLinks": [{ "type": "confluence", "url": "{url}", "metadata": {} }],
  "crashContext": null, "fortifyFinding": null, "graylogContext": null,
  "localMode": false, "instructionDriven": false, "instructionFiles": {},
  "identity": { "name": "{name}", "email": "dev@example.com" },
  "currentPhase": 0, "status": "in_progress", "startedAt": "2026-01-01T09:00:00Z",
  "sessionId": null, "telemetry": { "mcpCalls": [] },
  "phases": { "0": { "status": "done", "files": [] }, "1": { "status": "pending", "files": [] },
    "2": { "status": "pending", "files": [] }, "3": { "status": "pending", "files": [] },
    "4": { "status": "pending", "files": [] }, "5": { "status": "pending", "files": [] } }
}
```

The block validates against `agent-state.schema.json` (a unit test holds it there); the schema is the full contract. Enum fields show one value: `remoteType` github|bitbucket|gitlab|generic-git|local, `baseFetchStatus` fresh|cached-stale|local-branch|aborted, `inputType` github-issue-url|github-issue-number|jira-url|jira-id|free-text. `shortId` is the integer from the counter. `write-state.mjs` stamps `schemaVersion` when it creates the file.

`sessionId`: write `$MULTI_AGENT_SESSION_ID` verbatim when it is set and non-empty, at creation, and never change it. A continuous-mode launch sets it to the session id it started, and finds this state by it; an attended run has no such variable and writes nothing.

**Local-only flow**  -  when every entry in `state.projects[]` has `provider="local"`:
- `taskId` format: `LOCAL-{slug-of-freetext}-{yyyymmdd-HHMMSS}` (e.g. `LOCAL-purchase-flow-20260510-143200`). Slug = lowercase, non-alnum → `-`, trimmed, max 32 chars.
- `state.offlineOnly = true` (Phases 4/5 read this flag).
- `state.remoteType` per project = `"local"`.
- `state.baseBranch` = current branch of the local checkout (no `origin/{base}` fetch).
- `state.branch` = local-only feature branch on the same checkout; no upstream tracking is configured (`git checkout -b {branch}` without `-u`).
- No `gh`/`bb` API calls anywhere in Step 8. Worktree creation still applies if the user prefers worktree mode; collision handling stays the same.
- Multi-repo + mixed mode: any project with `provider != "local"` keeps its normal remote workflow; the `offlineOnly` flag is set only when **all** projects are local.

6. Log: "Phase 0: Init complete  -  {project} / {branch} / {identity.name}"

#### Step 7  -  Task Type Detection (deterministic, sets the contract for downstream phases)

Classify task _before_ Phase 1 so downstream phases branch on it. Persist to `agent-state.json.taskType`.

Priority order (first match wins):

1. Description/Jira summary contains Figma URL (`figma.com/design/...` or `figma.com/make/...`) → `component`
2. `instructionDriven == true` AND instruction path contains `figma` → `component`
3. Git diff shows new `Configuration.swift` AND `+Modifiers.swift` → `component`
4. Jira type matches `Bug|Hotfix|Defect` → `bugfix`
5. Branch starts with `bugfix/` or `hotfix/` → `bugfix`
6. Branch `feature/` AND description contains `refactor`|`cleanup`|`rewrite` → `refactor`
7. Branch `feature/` → `feature`
8. Description contains `chore`|`docs`|`ci`|`config` (no code keyword) → `chore`
9. Fallback → ask user (autopilot defaults to `feature`)

Persist: `"taskType": "component" | "bugfix" | "feature" | "refactor" | "chore"`

| Phase   | Behavior change                                                                                          |
| ------- | -------------------------------------------------------------------------------------------------------- |
| Phase 2 | `component` → dispatch to the enabled `ai-<platform>-toolkit` plugin's `create-component` skill (fallback `create-ui-component`); else standard TDD |
| Phase 3 | `bugfix` → test coverage; `component` → accessibility+tokens; `refactor` → behavior preservation         |
| Phase 4 | `bugfix`/`hotfix` → `fix(...)` prefix; `feature`/`component` → `feat(...)`; `refactor` → `refactor(...)` |
| Phase 5 | `component` → includes SubPhase breakdown                                                                |

Log: `Phase 0 Step 7: taskType = {component|bugfix|feature|refactor|chore}`

#### Step 7.6  -  Test baseline (opt-in, `prefs.global.testBaseline.enabled`, default `false`)

The Phase 2 exit gate's Gate 3 cannot tell an inherited red suite from one this run broke, so it blocks on someone else's bug or the agent "fixes" tests it never touched. Runs after Step 6, only when the stack has a test command; skipped in analysis mode.

```bash
timeout "${prefs_testBaseline_timeoutSeconds:-600}" <same-test-command-as-Phase-4-Gate-3> 2>&1 | tee "{worktreePath}/.baseline-test.log"
```

Persist `state.baseline.tests` with `command`, `capturedAt`, `logPath` and exactly one status: `green` (passed), `red` + `failing[]` (failed, names parsed), `red` + empty `failing[]` (failed, names unparseable), `unknown` (no test command, `timeout` fired, or flag off). Never fold `unknown` into `green`: a skipped baseline would read as a clean tree.

Log: `Phase 0 Step 7.6: test baseline = {green|red|unknown} ({N} pre-existing failures)`

#### Step 7.7  -  Evidence capability, then test depth

Decide, then probe, then ask, then run. Skipped only when `visualEvidence.enabled` is `false`. Contract: `features/visual-evidence.md` sections 1a, 1b, 4.

**This step is the only writer of `visualEvidence.required`, `requiredBy` and `platform`** (section 1a). No diff exists yet, so the `bugfix` row is provisional and Phase 2 re-decides.

```bash
bash $HOME/.claude/scripts/probe-evidence-capability.sh --platform auto --stack-root "$PROJECT_ROOT" \
  --repo "$WORKTREE" --json --json-out "$WORKTREE/.pipeline/evidence-capability.json"
```

`auto` takes the first of ios, android, web from `stack-detect.sh`. Empty is an outcome, not a failure: a backend repo has no device, so nothing is probed, `platform` is `other` and `skippedReason` says why. Web does probe  -  the browser the runner drives is the device (4.6). No `--changed` yet. Stdout and the file carry the same JSON; `.platform` is `visualEvidence.platform`.

Persist that file as `state.evidenceCapability`, then build the menu from it, never from a reading of the repo: `1. Sadece unit test` / `2. Unit + UI test, ekran kaydiyla` (tier 1) / `3. Unit + MCP ile akis kaydi` (tier 2).

A closed option keeps its row and prints the probe's reason verbatim. No `uiTestTargets` closes 2; `mcp` false closes 3; a missing `device` or `recorder` closes both. Targets present with no `matchingTests` leaves 2 open, warning that the whole UI suite will run. **When only option 1 is open, do not ask**: `testDepth = unit`, `testDepthSource = forced`, and the tier 3 gap is written.

Default as a 1-based index, never a label: `ask-choice.sh` takes the first
option on a non-TTY, so a label-valued default silently becomes option 1. The jq below prints it (`<index>`).

```bash
jq -r 'if .tier1 == "open" and (.matchingTests | length) > 0 then 2 elif .tier2 == "open" then 3 else 1 end' \
  "$WORKTREE/.pipeline/evidence-capability.json"
ASK_CHOICE_DEFAULT=<index> $HOME/.claude/lib/ask-choice.sh ...
```

Asked by every interactive entry; autopilot reads `prefs.global.testDepth.default` and degrades to the best open option. Asked here, not Phase 3, which four of the eight modes drop.

Log: `Phase 0 Step 7.7: testDepth = {unit|unit+ui|unit+mcp} (source {user|autopilot|default|forced}), tier1/tier2 = {open|closed}`

#### Step 8  -  Clarification (opt-in, runs AFTER maturity, BEFORE Phase 1)

**Gated by `prefs.global.clarifyAmbiguous.enabled`** (default: `false`). When enabled and `state.maturity.status != "blocker"`:

1. Dispatch `agents/task-clarifier.md` (Haiku by default) with the task title + body + acceptance + maturity warnings already on `agent-state`.
2. The agent returns JSON conforming to `$HOME/.claude/schemas/clarify-output.schema.json`  -  `clarityScore` (0-10), `questions[]`, `stopAndAsk`.
3. If `clarityScore >= prefs.clarifyAmbiguous.minScoreToProceed` (default 6) or `stopAndAsk == false` → write `state.clarification` (score + rationale, no questions), proceed to Phase 1 silently.
4. If `stopAndAsk == true`:

   - **Interactive runs:** render the questions via `AskUserQuestion` (per `$HOME/.claude/multi-agent-refs/rules.md` Language Application matrix; `outputLanguage` for `question` + `option.label` + `option.description`, English for `header`). Up to `maxQuestions` questions, each with the recommended option flagged. Persist the user's answers under `state.clarification.userAnswers`.
   - **Autopilot runs:** follow `clarifyAmbiguous.autopilotMode`:

     | Mode  | Behavior |
     |---|---|
     | `skip`  | Discard questions, proceed to Phase 1 with no extra context. Logs `clarify.skipped` |
     | `log`   | Append questions to `agent-log.md` for human review, proceed. Default  -  keeps the signal |
     | `abort` | Pause Phase 0 (`state.status = "clarify-pending"`); user resumes via `multi-agent:resume #N`. Then Step 8 re-dispatches AskUserQuestion |

5. Phase 1 Analysis reads `state.clarification.userAnswers` (when present) as additional context  -  fold answers into the Explore prompt so downstream phases inherit the resolution.

**Reference:** `$HOME/.claude/agents/task-clarifier.md`  -  scoring rubric, question-quality rules and the ~$0.0025-per-call cost note.

#### Telemetry

After each clarifier call:

```bash
LOG_METRIC_FORWARD_TO_TRACKER=1 $HOME/.claude/scripts/log-metric.sh "$TASK_ID" 0 clarify.call \
  model=haiku score=$SCORE questions=$Q stop_and_ask=$STOP autopilot_mode=$AP \
  duration_ms=$D tokens_in=$TI tokens_out=$TO
```

Phase 5 cost rollup carries this as a `phase 0` line item so the user sees ambiguity-scoring cost separately from Phase 1 Analysis.

<!-- progress-contract: applied -->

**Progress (per `$HOME/.claude/multi-agent-refs/progress-contract.md`):** emit one `→ <verb> <object>` line for each of: `→ parsing input`, `→ checking token <service>`, `→ scanning project <root>`, `→ creating worktree <repo>`, `→ binding identity <name>`, `→ writing state`. When `clarifyAmbiguous.enabled`, also emit `→ scoring task ambiguity` before Step 8 and `→ asking clarifying questions <N>` when `stopAndAsk` fires.

**Save preferences**: Write updated prefs to `$HOME/.claude/multi-agent-preferences.json` with all Phase 0 selections.

---

#### Phase 0 exit gate (BLOCKING  -  run before marking the phase completed)

Phase 0 owns `agent-state.json`. Do not call
`phase-tracker.sh update 0 completed` until this gate passes:

```bash
node "$HOME/.claude/scripts/phase0-exit-gate.mjs" "$TASK_ID" --input "$ORIGINAL_INPUT"
node "$HOME/.claude/scripts/usage-register.mjs" --quiet >/dev/null || true
node "$HOME/.claude/scripts/usage-report.mjs" --task-id "$TASK_ID" --phase 0 --event phase >/dev/null 2>&1 || true
```

The third line reports the run as started. Every later phase transition is
reported by `phase-tracker.sh update`, and completion, halt and park by the
docs that end, halt or park a run; all of them upsert the same key
(`features/usage-reporting.md`). The
second is the backstop for a machine that reached neither setup nor update - it
is a no-op once a token resolves, and permanently so under `usageLog.optOut`.

It asserts five things:

1. **`agent-state.json` exists.** Every later phase reasons from it.
2. **`taskType` is set.** Phase 2 branches on it (Step 7).
3. **A Figma reference forces `taskType: "component"`, and `figmaAccess.tier` is
   recorded**, so a later phase can tell a confirmed design from an unfetched one.
4. **`baseBranchSource` is recorded**, and an interactive run recorded `asked` or
   `input`  -  the only field that separates a branch that was chosen from one that
   was announced.
5. **`siblings` is an array**, empty included: Step 2b's record that it ran.

A failure is a halt, not a warning: fix the state, re-run the gate, and leave the phase
`in_progress` until it passes. Never close Phase 0 on the grounds that its steps ran -
the gate checks the output, which is what Phase 2 consumes.
