@mmerterden/multi-agent-pipeline 17.4.0 → 17.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/README.md +23 -5
  3. package/README.tr.md +23 -5
  4. package/docs/adr/0013-lsp-code-intelligence.md +102 -0
  5. package/docs/adr/README.md +1 -0
  6. package/docs/token-budget-history.md +1 -1
  7. package/install/templates/copilot-instructions.md +9 -3
  8. package/package.json +1 -1
  9. package/pipeline/commands/multi-agent/analysis/SKILL.md +3 -3
  10. package/pipeline/commands/multi-agent/autopilot/SKILL.md +3 -3
  11. package/pipeline/commands/multi-agent/autopilot-off/SKILL.md +5 -3
  12. package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +1 -1
  13. package/pipeline/commands/multi-agent/local/SKILL.md +17 -6
  14. package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +3 -3
  15. package/pipeline/lib/multi-repo-pipeline.sh +26 -0
  16. package/pipeline/multi-agent-refs/features/base-branch-evidence.md +222 -0
  17. package/pipeline/multi-agent-refs/features/code-intelligence.md +80 -0
  18. package/pipeline/multi-agent-refs/phases/modes.md +23 -3
  19. package/pipeline/multi-agent-refs/phases/phase-0-init.md +96 -71
  20. package/pipeline/multi-agent-refs/phases/phase-7-report.md +1 -1
  21. package/pipeline/multi-agent-refs/phases.md +7 -2
  22. package/pipeline/multi-agent-refs/picker-contract.md +37 -5
  23. package/pipeline/multi-agent-refs/tracker-contract.md +25 -14
  24. package/pipeline/schemas/agent-state.schema.json +88 -4
  25. package/pipeline/schemas/prefs.schema.json +22 -0
  26. package/pipeline/schemas/token-budget.json +2 -2
  27. package/pipeline/scripts/autopilot-runner.mjs +292 -45
  28. package/pipeline/scripts/base-branch-candidates.mjs +599 -0
  29. package/pipeline/scripts/gc-abandoned.sh +5 -3
  30. package/pipeline/scripts/gen-mode-dispatch.mjs +39 -16
  31. package/pipeline/scripts/phase-tracker.sh +39 -2
  32. package/pipeline/scripts/phase0-exit-gate.mjs +128 -0
  33. package/pipeline/scripts/verify-citations.mjs +84 -2
  34. package/pipeline/skills/.skill-manifest.json +2 -2
  35. package/pipeline/skills/shared/core/multi-agent/SKILL.md +1 -1
@@ -1,6 +1,6 @@
1
1
  ### Phase 0: Init
2
2
 
3
- > **TLDR** - 8 sequential steps: load prefs → parse input (Jira/GitHub/free-text) → select project → detect remote + pick base branch → optional design input → branch name → git identity → instruction files → create worktree/local branch + agent-state.json. Every input type (Jira URL, GitHub issue, free-text) flows through the same 8 steps.
3
+ > **TLDR** - every input type flows through the same numbered steps below, in order, and ends at the exit gate.
4
4
 
5
5
  #### Step −1 - Bootstrap the cross-CLI tracker (FIRST thing in every run)
6
6
 
@@ -9,13 +9,17 @@ Before anything else - initialize the visual tracker so the user sees the pipe
9
9
  ```bash
10
10
  TASK_ID="${INPUT_TASK_ID:-pipeline-$(date +%Y%m%d-%H%M%S)}"
11
11
  $HOME/.claude/scripts/phase-tracker.sh init "$TASK_ID"
12
- for p in 0:Init 1:Analysis 2:Planning 3:Dev 4:Review 5:Test 6:Commit 7:Report; do
13
- $HOME/.claude/scripts/phase-tracker.sh add "${p%%:*}" "${p#*:}"
14
- done
12
+ $HOME/.claude/scripts/phase-tracker.sh add 0 Init
15
13
  $HOME/.claude/scripts/phase-tracker.sh tiles
16
14
  $HOME/.claude/scripts/phase-tracker.sh update 0 in_progress
17
15
  ```
18
16
 
17
+ **Phase 0 only.** `/multi-agent` and `:local` do not know their phase set yet -
18
+ depth decides it, and depth is Step 7.5 - so they register the rest there rather
19
+ than drawing eight tiles the user has not chosen. Every other mode registers its
20
+ whole set here, in phase-number order. Contract: `tracker-contract.md`,
21
+ "Deferred registration".
22
+
19
23
  `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".
20
24
 
21
25
  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.
@@ -26,7 +30,7 @@ Every subsequent phase (1-7) MUST call `phase-tracker.sh update <N> in_progress`
26
30
 
27
31
  ##### TaskCreate ordering on Claude Code (strict)
28
32
 
29
- On Claude Code, fire all `TaskCreate` calls in strict phase-number order (0 → 7) BEFORE any `TaskUpdate` - which is the order `tiles` prints them in. Full contract: `$HOME/.claude/multi-agent-refs/tracker-contract.md` section "TaskCreate ordering (strict)".
33
+ 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. A deferred batch (Step 7.5) therefore appends, it does not interleave. Full contract: `$HOME/.claude/multi-agent-refs/tracker-contract.md` section "TaskCreate ordering (strict)".
30
34
 
31
35
  ---
32
36
 
@@ -261,58 +265,76 @@ Scan `$HOME` (maxdepth 2) for project markers (`.xcodeproj`, `Package.swift`, `b
261
265
 
262
266
  **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.
263
267
 
268
+ #### Step 2b - Dev context (extra repos)
269
+
270
+ Run `$HOME/.claude/multi-agent-refs/_dev-context.md`: `.gitmodules` submodules
271
+ plus `prefs.projects[{project}].webRepos[]`, editable ones pre-selected,
272
+ read-only siblings listed. It runs on every input type, direct-ID included, and
273
+ an empty submit is a valid answer meaning primary repo only. Here rather than
274
+ later because a base branch is a property of a repo set (`picker-contract.md`,
275
+ "Order: project, then repo, then branch").
276
+
277
+ Persist `state.siblings[]` even when empty: the empty array is the record that
278
+ the step ran. Absent, Phase 4's parity cross-check cannot tell "no siblings"
279
+ from "never asked", and the exit gate below fails.
280
+
264
281
  #### Step 3 - Remote Detection + Branch Selection
265
282
 
266
283
  1. **Check preferences first**: If `prefs.projects[{project}].remoteType` exists, use cached value.
267
284
  2. Read remote: `git -C $PROJECT_ROOT remote get-url origin`
268
285
  3. Detect type: `github.com` → github (`gh` CLI), `{BITBUCKET_HOST}` → bitbucket (API + `keychainMapping.bitbucket_token`), other → generic-git. Save to `prefs.projects[{project}].remoteType`.
269
286
  4. **Skip if baseBranch already set** (from Step 1). Otherwise:
270
- 5. Fetch + list PR-targetable branches:
287
+ 5. Fetch + list PR-targetable branches. **Capture the exit code** - it decides
288
+ whether the list may be called the remote's answer:
271
289
  ```bash
272
- git -C $PROJECT_ROOT fetch origin
290
+ git -C $PROJECT_ROOT fetch origin; FETCH_RC=$?
273
291
  git -C $PROJECT_ROOT branch -r --sort=-committerdate \
274
- | grep -E '(develop|release|main|master)' \
275
- | grep -v -E '(feature/|bugfix/|fix/|hotfix/|chore/)'
292
+ | grep -v -E '(feature/|bugfix/|fix/|hotfix/|chore/)' \
293
+ | grep -E '([0-9]+[._][0-9]+|develop|release|main|master)'
276
294
  ```
277
- 6. Sort: `develop*` first, then `release/*`, then `main`/`master`. Surface through the
278
- **native picker** per `picker-contract.md`, recent branch first and marked
279
- `(Recommended)`:
295
+ **Keep the version alternative.** A branch carrying a version token is a
296
+ candidate whatever it is called; the family words only RANK. Why that
297
+ distinction matters: `features/base-branch-evidence.md`, "A filter is not a
298
+ ranking".
299
+ 5b. With `prefs.global.baseBranchEvidence` on, pipe that list plus the issue's
300
+ version fields and links through `$HOME/.claude/scripts/base-branch-candidates.mjs`,
301
+ which ranks the candidates and records the evidence behind each. It is pure, so
302
+ Step 3 owns every git and network call. Contract - autopilot resolution order and
303
+ the fenced ask-on-the-issue path included:
304
+ `$HOME/.claude/multi-agent-refs/features/base-branch-evidence.md`. Off: rule 6 alone.
305
+ 6. Sort: `develop*` first, then `release/*`, then `main`/`master` (the collector's
306
+ order when it ran). Surface through the **native picker** per `picker-contract.md`,
307
+ recent branch first and marked `(Recommended)`, each row carrying its evidence:
280
308
 
281
309
  ```
282
310
  header: "Base branch"
283
311
  options: origin/develop (Recommended, reused from last run) | origin/main | release/8.4.0 | Other
284
312
  ```
285
- 7. User picks → store as `baseBranch`, and append `{branch, lastUsed, count?}` to
313
+ 7. User picks → store as `baseBranch` with `baseBranchSource: "asked"`, and append `{branch, lastUsed, count?}` to
286
314
  `prefs.global.recentBranches[{projectKey}]` (dedup by `branch`, cap 10) - what the TTL
287
315
  filter below reads. Key is `branch`, not `name`. Never the legacy
288
316
  `projects[{project}].branches`.
289
317
 
290
318
  **MUST: this step is not skippable (BLOCKING).** The only legitimate skip is rule 4
291
- above - `baseBranch` already supplied in the input. Everything else asks. A run once
292
- took a Jira ID and implemented straight onto whatever the local checkout was pointing
293
- at, with neither the project nor the branch picker ever shown; nothing failed, so
294
- nothing surfaced it. `phase0-exit-gate.mjs` now refuses to close Phase 0 unless
295
- `agent-state.json` carries `baseBranch` and `baseFetchStatus`, so a skipped picker is a
296
- gate failure rather than a silent default.
319
+ above, recorded as `baseBranchSource: "input"`. Everything else asks, and
320
+ `phase0-exit-gate.mjs` refuses to close Phase 0 without `baseBranch`,
321
+ `baseFetchStatus` and `baseBranchSource` - a skipped picker fails the gate.
297
322
 
298
- One row is a normal outcome of the rule-5 filter and is **still asked** - see
299
- `picker-contract.md` "A single candidate is still a question".
323
+ One row is a normal outcome of the rule-5 filter and is **still asked**, with a
324
+ real second option - `picker-contract.md`, "Two options or it is not a question".
300
325
 
301
326
  This holds in every mode. A Short run skips the *LLM* phases (Analysis, Planning); it
302
327
  does not skip Phase 0's pickers. Autopilot resolves them without prompting, which still
303
- writes the fields. For the base branch it reads `recentBranches[{projectKey}]` (most
304
- recent inside the TTL, still on the remote) before the rule-6 order, recording which
305
- fired in `state.baseBranchSource`.
328
+ writes the fields; its base-branch resolution order is in `features/base-branch-evidence.md`.
306
329
 
307
330
  **TTL filter for recent branches**:
308
331
 
309
- - `prefs.global.recentBranches[{projectKey}][]` carries `{branch, lastUsed, count?}`. Filter to those whose `lastUsed` is within `settings.branchTtlDays` (default 15).
310
- - Stale entries (>TTL) are pruned in-place during the read - keeps the picker uncluttered without a separate cleanup pass.
332
+ - `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.
311
333
  - The filtered "Recent" list precedes the fresh `git branch -r` list; cap at 5 visible recent entries.
312
334
 
313
335
  **Fetch-fail handling** (replaces silent `git fetch origin` failure):
314
336
 
315
- The legacy `git -C $PROJECT_ROOT fetch origin` step (line 110 above) MUST not silently fall back to a stale cached ref. On non-zero exit, surface the **native picker** (per `picker-contract.md`) with 4 options:
337
+ `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:
316
338
 
317
339
  ```
318
340
  question: "git fetch failed for {project} - the base ref may be stale. How should I proceed?"
@@ -325,19 +347,13 @@ options:
325
347
  Abort no worktree, no branch, no state file
326
348
  ```
327
349
 
328
- **Say which is which.** The question must name the corporate host when the remote points
329
- at one - a `{BITBUCKET_HOST}` remote failing to resolve is almost always the VPN, and
330
- telling the user that is the difference between a five-second fix and a run built on a
331
- month-old ref. "Connect VPN and retry" re-runs the fetch and re-enters this picker if it
332
- fails again; it is a real retry, not a label.
350
+ **Say which is which.** Name the host when the remote points at one - an unresolvable
351
+ `{BITBUCKET_HOST}` is almost always the VPN - and make the retry real: it re-runs the
352
+ fetch and re-enters this picker on a second failure.
333
353
 
334
- Persist user choice in `agent-state.json.baseFetchStatus` ∈ `"fresh" | "cached-stale" | "local-branch" | "aborted"`. On any non-fresh choice, log:
335
- ```
336
- ⚠️ Base ref stale (fetch fail @ {ts}, choice: {cached-stale|local-branch})
337
- ```
338
- Phase 6 (commit/push) MUST re-attempt `git fetch origin` before push; if successful, prompt to rebase before pushing.
354
+ 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 6 re-attempts `git fetch origin` before push and prompts to rebase if it succeeds.
339
355
 
340
- In multi-repo mode, the prompt fires per-repo. Choosing `[4] Abort` for any single repo aborts the entire task (atomic - no partial worktrees).
356
+ In multi-repo mode the prompt fires per repo, and Abort on any one aborts the whole task (atomic - no partial worktrees).
341
357
 
342
358
  #### Step 4 - Branch Naming (automatic)
343
359
 
@@ -365,10 +381,8 @@ Branch name is deterministic - no user confirmation needed.
365
381
 
366
382
  **Collision handling** (automatic - no prompt):
367
383
  - Probe local + remote for existing branch. **Distinguish "no such ref" from "the
368
- probe failed"**: with `2>/dev/null` and an empty-output test they look identical,
369
- so an auth or network failure reads as "no collision" and the run creates a
370
- branch that already exists on the remote - surfacing as a rejected push at
371
- Phase 6, far from its cause.
384
+ probe failed"**: with `2>/dev/null` and an empty-output test a failed probe reads
385
+ as "no collision", and the duplicate branch surfaces as a rejected push at Phase 6.
372
386
  ```bash
373
387
  LOCAL_HIT=$(git -C "$root" rev-parse --verify --quiet "refs/heads/$branch")
374
388
  REMOTE_ERR=$(git -C "$root" ls-remote --exit-code --heads origin "$branch" 2>&1 >/dev/null)
@@ -395,6 +409,21 @@ Branch name is deterministic - no user confirmation needed.
395
409
  3. Map instruction files to `"instructionFiles": { "start": "...", "validate": "...", "dev": "...", "commit": "..." }`
396
410
  4. Instruction-driven → later phases read SKILL.md; no instructions → standard phases
397
411
 
412
+ #### Step 5b - Workspace (worktree or local)
413
+
414
+ Ask where the branch lives - the wording, the two options and what local costs
415
+ are in `modes.md`, "Local Mode". Here because Step 4 named the branch, Step 6b
416
+ acts on the answer, and Step 7.5 needs the phase set it implies.
417
+
418
+ **Who is asked.** `/multi-agent` only (`workspaceSource: "asked"`). `:local` and
419
+ `--local` state it up front (`command`).
420
+ Every autopilot entry resolves it to a worktree and never asks (`autopilot`).
421
+
422
+ **Persist** `state.localMode` (semantics unchanged) and `state.workspaceSource`,
423
+ which is what separates a worktree the user chose from one nothing asked about.
424
+
425
+ Log: `Phase 0 Step 5b: workspace = {worktree|local} (source {asked|command|autopilot})`
426
+
398
427
  #### Step 6 - Branch + Workspace Setup
399
428
 
400
429
  **6a. Resolve git identity** (automatic, no prompt):
@@ -419,7 +448,7 @@ Log: `Identity: {identity.name} <{identity.email}>`
419
448
 
420
449
  1. `git -C $PROJECT_ROOT fetch origin`
421
450
 
422
- **If `--local` mode** (no worktree):
451
+ **If local** (Step 5b answered local, by question, by `:local` / `--local`):
423
452
 
424
453
  ```bash
425
454
  if [ -n "$(git -C $PROJECT_ROOT status --porcelain)" ]; then
@@ -430,9 +459,10 @@ git -C $PROJECT_ROOT config user.name "{identity.name}"
430
459
  git -C $PROJECT_ROOT config user.email "{identity.email}"
431
460
  ```
432
461
 
433
- `worktreePath` = `$PROJECT_ROOT`, `localMode` = `true`.
462
+ `worktreePath` = `$PROJECT_ROOT`, `localMode` = `true`. Step 5b already recorded
463
+ `workspaceSource`; this step does not decide, it applies.
434
464
 
435
- **If normal mode** (worktree - default): 2. Worktree path: Jira → `.worktrees/{jiraId}/`, GitHub → `.worktrees/GH{issueNo}/`, free-text → `.worktrees/task-{shortId}/` 3. **Heal stale admin state first** (see "Worktree stale-lock heal" below) and **apply the residue guard** (see "Worktree residue guard" below), then `git -C $PROJECT_ROOT worktree add {path} -b {branch} origin/{baseBranch}` (if exists: enter, pull) 4. Set identity: `git -C {worktree-path} config user.name/email` 5. Create log dir + `agent-log.md` + `agent-state.json` at `$HOME/.claude/logs/multi-agent/{project}/{task-id}/`, never inside the worktree:
465
+ **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. **Heal stale admin state first** (see "Worktree stale-lock heal" below) and **apply the residue guard** (see "Worktree residue guard" below), then `git -C $PROJECT_ROOT worktree add {path} -b {branch} origin/{baseBranch}` (if exists: enter, pull) 4. Set identity: `git -C {worktree-path} config user.name/email` 5. Create log dir + `agent-log.md` + `agent-state.json` at `$HOME/.claude/logs/multi-agent/{project}/{task-id}/`, never inside the worktree:
436
466
 
437
467
  **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 (the loop below); `--local` creates none and `worktreePath` is `$PROJECT_ROOT`.
438
468
 
@@ -484,7 +514,7 @@ done
484
514
 
485
515
  State file in multi-repo mode:
486
516
  - Single shared `agent-state.json` lives at `$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)
487
- - **One shared file + per-repo writers is exactly the race `write-state.mjs` exists for.** Every update to it (here and in every later phase) goes through `node $HOME/.claude/scripts/write-state.mjs` per the required mechanism in `operations.md` "Writing `agent-state.json`". A read-modify-write from two repos in the same loop drops one repo's `projects[]` entry.
517
+ - Every update to it, here and in every later phase, 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.
488
518
  - `state.projects[]` holds per-repo `{name, root, worktreePath, branch, baseBranch, identity, platform, baseFetchStatus, commit, pr, pushAttempts, buildStatus}` - see `agent-state.schema.json`
489
519
  - Scalar fields (`project`, `projectRoot`, `worktreePath`, `branch`, `baseBranch`, `identity`) mirror `projects[0]` so legacy phases that read scalars keep working
490
520
  - 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.
@@ -562,7 +592,7 @@ Ask the depth question from `$HOME/.claude/multi-agent-refs/phases/modes.md` "Pi
562
592
 
563
593
  When the intake carried an analysis document or a Figma reference, say so **inside** the question: Short skips the only two phases that would turn that document into a task breakdown, and the user should learn that before choosing, not after.
564
594
 
565
- **Pass the default explicitly, as an index.** `ask-choice.sh` picks the FIRST option on a non-TTY, so relying on option order breaks the first time someone reorders them for readability, on the host where nobody is watching. The default must be the 1-based index, never the label: labels follow `outputLanguage` (`rules.md` matrix), so `ASK_CHOICE_DEFAULT="Full"` matches nothing once the options render as `Tam` / `Kisa`, falls through, and silently takes option 1 on a non-TTY - the exact failure this step exists to prevent.
595
+ **Pass the default as a 1-based index, never a label.** `ask-choice.sh` takes the first option on a non-TTY, and a label-valued default matches nothing once the options render in `outputLanguage`. Reasoning: `modes.md`, "Pipeline depth".
566
596
 
567
597
  ```bash
568
598
  # Full is option 1, Short is option 2 (modes.md "Pipeline depth")
@@ -572,7 +602,9 @@ ASK_CHOICE_DEFAULT="$DEPTH_DEFAULT_INDEX" \
572
602
  "<localized: 'Full'>" "<localized: 'Short'>"
573
603
  ```
574
604
 
575
- **Persist.** Short sets `state.onlyDevelop = true`; Full leaves it `false`. The key is unchanged - only who sets it changed - so every downstream reader keeps working. Short also flips the Phase 1 and Phase 2 tiles to `skipped` (tracker-contract.md, "Late skip"); pre-marking is forbidden.
605
+ **Persist.** Short sets `state.onlyDevelop = true`; Full leaves it `false`. The key is unchanged - only who sets it changed - so every downstream reader keeps working.
606
+
607
+ **Now register the rest of the widget.** This answer is the first moment the phase set is known, so the remaining tiles are created here and not before - Full `1 2 3 4 5 6 7`, Short `3 4 5 6 7`, `:local` dropping 5 from either. `add` each, then `phase-tracker.sh tiles --new`, which emits TaskCreate only for phases that carry no tile yet. Contract: `tracker-contract.md`, "Deferred registration".
576
608
 
577
609
  Log: `Phase 0 Step 7.5: depth = {full|short} (recommended {full|short}, source {user|autopilot|default})`
578
610
 
@@ -585,7 +617,7 @@ BASELINE_LOG="$WORKTREE/.baseline-test.log"
585
617
  timeout "${prefs_testBaseline_timeoutSeconds:-600}" <same-test-command-as-Phase-4-Gate-3> 2>&1 | tee "$BASELINE_LOG"
586
618
  ```
587
619
 
588
- 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). Folding `unknown` into `green` would let a skipped baseline read as a clean tree, which is the failure this record exists to prevent.
620
+ 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.
589
621
 
590
622
  Log: `Phase 0 Step 7.6: test baseline = {green|red|unknown} ({N} pre-existing failures)`
591
623
 
@@ -606,9 +638,7 @@ if [ -n "$EVIDENCE_PLATFORM" ]; then
606
638
  fi
607
639
  ```
608
640
 
609
- Empty is an outcome, not a failure: backend has no device, so the probe does not run and `evidenceCapability.skippedReason` says so. Web does: the browser the runner drives is the device (4.6). No `--changed` yet, by the same token.
610
-
611
- One run, both forms: stdout is `EVIDENCE_*` (shell-quoted, so the eval is safe), and the same measurement lands as JSON.
641
+ Empty is an outcome, not a failure: backend has no device, so the probe does not run and `evidenceCapability.skippedReason` says so. Web does - the browser the runner drives is the device (4.6). No `--changed` yet. One run, both forms: stdout is `EVIDENCE_*` (shell-quoted, so the eval is safe), and the same measurement lands as JSON.
612
642
 
613
643
  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).
614
644
 
@@ -647,11 +677,7 @@ Log: `Phase 0 Step 7.7: testDepth = {unit|unit+ui|unit+mcp} (source {user|autopi
647
677
 
648
678
  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.
649
679
 
650
- **Cost:** ~$0.0025 per Haiku call. The pipeline's other expensive phases (Phase 4 reviewers, Phase 3 Sonnet codegen) far outweigh this - the value is avoiding the ~30 min wasted when Phase 3 builds the wrong thing because Phase 0 didn't ask.
651
-
652
- **Reference:** see `$HOME/.claude/agents/task-clarifier.md` for the full scoring rubric and question-quality rules.
653
-
654
- **Why this fits Phase 0 (not a new phase):** clarification doesn't change what code gets written - it changes what gets understood before code is written. Phase 0 already collects identity / project / branch / maturity; ambiguity scoring fits naturally as the last contextual gate.
680
+ **Reference:** `$HOME/.claude/agents/task-clarifier.md` - scoring rubric, question-quality rules and the ~$0.0025-per-call cost note.
655
681
 
656
682
  #### Telemetry
657
683
 
@@ -686,20 +712,19 @@ node "$HOME/.claude/scripts/usage-report.mjs" --task-id "$TASK_ID" >/dev/null 2>
686
712
  The second line reports the run as started: reporting only from Phase 7 reported
687
713
  only runs that finish, and few do. Phase 7 upserts the same key over it.
688
714
 
689
- It asserts three things, each of which has failed silently in a real run:
715
+ It asserts five things, each of which has failed silently in a real run:
690
716
 
691
- 1. **`agent-state.json` exists.** A run once reported Phase 0 `completed` with only
692
- `tracker-state.json` on disk. Every later phase then reasons from fields that are
693
- not there.
694
- 2. **`taskType` is set.** Phase 3 branches on it (Step 7). Absent, a Figma-driven
695
- screen is dispatched as generic development, skipping the stack plugin's
696
- token-compliance check, Code Connect publish and component review. That run
697
- guessed `16` where the frame said `Spacing/12`, and half its commits were rework.
717
+ 1. **`agent-state.json` exists.** Every later phase reasons from it.
718
+ 2. **`taskType` is set.** Phase 3 branches on it (Step 7).
698
719
  3. **A Figma reference forces `taskType: "component"`, and `figmaAccess.tier` is
699
- recorded.** Without the tier, a later phase cannot tell "the design was confirmed"
700
- from "the design was never fetched" - which is exactly when spacing gets guessed.
720
+ recorded**, so a later phase can tell a confirmed design from an unfetched one.
721
+ 4. **`baseBranchSource` is recorded**, and an interactive run recorded `asked` or
722
+ `input` - the only field that separates a branch that was chosen from one that
723
+ was announced.
724
+ 5. **`siblings` is an array**, empty included: Step 2b's record that it ran.
725
+
726
+ Each failed silently in a real run; the script header names which.
701
727
 
702
- A failure is a halt, not a warning. Fix the state and re-run the gate; the phase
703
- stays `in_progress` until it passes. **Never** mark Phase 0 completed on the grounds
704
- that its steps ran - the gate checks the output, and the output is what Phase 3
705
- consumes.
728
+ A failure is a halt, not a warning: fix the state, re-run the gate, and leave the phase
729
+ `in_progress` until it passes. Never close Phase 0 on the grounds that its steps ran -
730
+ the gate checks the output, which is what Phase 3 consumes.
@@ -323,7 +323,7 @@ If user does not respond at the channels multi-select menu within 30 minutes (wa
323
323
  1. Channels command aborts its interactive prompt, returns `{status: "timeout"}`.
324
324
  2. Phase 7 records `phase-tracker.sh sub 7 1 "Channels dispatch" timeout`.
325
325
  3. **Internal capture still runs** - Steps 2 + 3 write `agent-log.md` (with `channels: timeout` in summary), emit telemetry, update knowledge base.
326
- 4. Session exits cleanly. State persisted: `state.phase=7, state.waitingFor="user-channels-choice", state.channelsTimeout=true`.
326
+ 4. Session exits cleanly. State persisted: `state.currentPhase=7, state.waitingFor="user-channels-choice", state.channelsTimeout=true`.
327
327
  5. Resume contract: `/multi-agent:resume <task-id>` re-opens the channels menu with the original state bundle (pipeline log, PR metadata, prefs pre-ticks).
328
328
 
329
329
  Rationale: silently posting defaults to Jira / Confluence after a timeout would leak wrong-tone content to external systems. Hard-stop + resume is the safer policy.
@@ -90,7 +90,7 @@ Two channels run in parallel at every phase boundary. Both are required in their
90
90
 
91
91
  ### Tracker bootstrap (Phase 0, mandatory)
92
92
 
93
- Phase 0 MUST initialize the tracker and register all 8 phases:
93
+ Phase 0 MUST initialize the tracker and register the active mode's phase set:
94
94
 
95
95
  ```bash
96
96
  $HOME/.claude/scripts/phase-tracker.sh init "$TASK_ID"
@@ -101,6 +101,11 @@ done
101
101
 
102
102
  This produces an initial card stack printed by both CLIs.
103
103
 
104
+ `/multi-agent` and `/multi-agent:local` are the exception: they do not know their
105
+ set here, because depth decides it at Step 7.5. They register `0:Init` alone, then
106
+ the rest once the answer lands, and call `tiles --new` for the second batch. Full
107
+ contract: `tracker-contract.md`, "Deferred registration".
108
+
104
109
  ### Tracker updates (every phase boundary)
105
110
 
106
111
  As each phase enters/exits:
@@ -162,7 +167,7 @@ TaskUpdate({ taskId: <saved>, status: "completed" })
162
167
  bash phase-tracker.sh update <N> completed
163
168
  ```
164
169
 
165
- A phase outside the command's set gets no TaskCreate at all. Depth is different: it is not known at registration time, because the tracker boots at Step -1 and the depth question runs at Step 7.5, so a Short run registers Phases 1 and 2 like any other and flips them to `skipped` when the answer lands. Pre-marking them before Phase 0 produces visually scrambled tile stacks - see the ordering rule below.
170
+ A phase outside the command's set gets no TaskCreate at all. Depth is different: it is not known at registration time, because the tracker boots at Step -1 and the depth question runs at Step 7.5. So registration splits - Phase 0 alone at Step -1, the rest at Step 7.5 once the answer says which phases the run has. A Short run never draws an Analysis tile it will not use. Registering all eight and flipping 1 and 2 to `skipped` is what this replaced, in v17.5.0: it put an eight-tile widget on screen beside the question asking whether to run two of them - see the ordering rule below.
166
171
 
167
172
  **(strict) TaskCreate ordering**: All TaskCreate calls MUST fire in strict phase-number order BEFORE any TaskUpdate is applied. The native widget renders by creation order, not by phase number - out-of-order calls produce visually scrambled tile stacks (e.g. `1 ✓ · 2 ✓ · 4 ✓ · 0 ▶ · 3 ☐`) even when the underlying state is correct. Pre-marking phases as completed/skipped before Phase 0 starts is FORBIDDEN - register the tile in order with default `pending` status, then flip status via TaskUpdate when the phase actually short-circuits. Full contract in `$HOME/.claude/multi-agent-refs/tracker-contract.md` section "TaskCreate ordering (strict)".
168
173
 
@@ -8,6 +8,7 @@
8
8
  - [Localized labels: what the caller owns](#localized-labels-what-the-caller-owns)
9
9
  - [Order: project, then repo, then branch](#order-project-then-repo-then-branch)
10
10
  - [A single candidate is still a question](#a-single-candidate-is-still-a-question)
11
+ - [Two options or it is not a question](#two-options-or-it-is-not-a-question)
11
12
  - [Autopilot / non-interactive contract](#autopilot-non-interactive-contract)
12
13
  - [Deterministic gates note](#deterministic-gates-note)
13
14
  <!-- /toc -->
@@ -102,15 +103,46 @@ input with a second question.
102
103
  ## A single candidate is still a question
103
104
 
104
105
  The number of options never authorises a skip. A filter that leaves one row has
105
- narrowed the world; it has not decided anything, and the host's **Other** row is a real
106
- choice on every picker - a branch the filter excluded, an account the probe missed, a
107
- repo git does not know about. "There was only one option, so I picked it" is a skipped
108
- picker, and announcing the pick in prose first is the same skip with a sentence in front
109
- of it.
106
+ narrowed the world; it has not decided anything. "There was only one option, so I
107
+ picked it" is a skipped picker, and announcing the pick in prose first is the same
108
+ skip with a sentence in front of it.
110
109
 
111
110
  This is the failure that is hardest to see afterwards, because the transcript reads like
112
111
  a decision was made. Only the picker's absence records that the user was never asked.
113
112
 
113
+ The one row is asked by giving the question a real second option, not by sending a
114
+ one-row picker - see the next section for why that is not the same thing.
115
+
116
+ ## Two options or it is not a question
117
+
118
+ Claude Code's `AskUserQuestion` refuses a question with fewer than two declared
119
+ options, and it refuses the **whole call**: every other question batched with it is
120
+ discarded unasked, and the host's reply says not to retry and not to invent a filler
121
+ option. The host's **Other** row does not rescue it - the schema counts declared
122
+ options, and Other is injected afterwards.
123
+
124
+ This has already cost a run. A base-branch question with one remote candidate was
125
+ batched with the maturity, depth and workspace questions; the call was rejected, the
126
+ branch was announced in prose instead ("only candidate, continuing with it"), and the
127
+ three surviving questions had to be re-asked. The section above was followed to the
128
+ letter and produced the skip it exists to prevent.
129
+
130
+ So a one-candidate picker is asked with a genuine escape as its second option:
131
+
132
+ | One candidate | Second option that makes it a question |
133
+ |---|---|
134
+ | base branch | "Pick another branch" - re-opens with the unfiltered `git branch -r` list |
135
+ | account, repo, module | "Show all" - re-opens with the filter dropped |
136
+ | a destructive step | "Abort" |
137
+
138
+ A second option that is not a choice ("OK", "Continue") is worse than not asking: it
139
+ manufactures consent. When nothing genuine can be offered, do not ask - say which
140
+ single path is being taken and continue.
141
+
142
+ `ask-choice.sh` accepts one option and always has, so this floor is a Claude Code
143
+ fact the shell path does not share. Write the picker to the floor anyway: one spec
144
+ text drives both hosts.
145
+
114
146
  ## Autopilot / non-interactive contract
115
147
 
116
148
  In autopilot, `ask_choice` resolves to `default` (or the safe first option) without prompting - identical to how the native gates auto-proceed today. A picker is only surfaced for genuinely ambiguous or destructive decisions, matching the maturity-check model.
@@ -94,7 +94,7 @@ Phases by mode:
94
94
  | `/multi-agent:autopilot`, `/multi-agent:local-autopilot` | 0,1,2,3,4,6,7 (always Full; autopilot drops the interactive Phase 5 gate) |
95
95
  | `/multi-agent:analysis` | 0,1,2,4,6,7 (no code is written, so no Dev and no Test) |
96
96
 
97
- What changed in v16.0.0: the two picker entries (`/multi-agent`, `:local`) register their FULL set even when the run turns out to be Short, and there is a timing reason. The tracker boots at Step -1, the first thing in every run, while the depth picker cannot run before Step 7.5 - its recommendation needs `taskType`, which needs the fetched issue and the branch. So Phases 1 and 2 are registered `pending` like any other and flipped to `skipped` at 7.5 if Short is chosen. See "Late skip" below.
97
+ The two picker entries (`/multi-agent`, `:local`) register in two batches, because at Step -1 they do not yet know which set is theirs: see "Deferred registration" below. Every other mode registers its whole set at Step -1.
98
98
 
99
99
  Register each phase:
100
100
 
@@ -199,29 +199,40 @@ Mode-specific phase sets:
199
199
 
200
200
  | Mode | TaskCreate set (in order) |
201
201
  |---|---|
202
- | `/multi-agent` | 0 → 1 → 2 → 3 → 4 → 5 → 6 → 7 (all 8; 1 and 2 flip to skipped at Step 7.5 if the user picks Short) |
203
- | `:local` | 0 → 1 → 2 → 3 → 4 → 6 → 7 (same late skip; Phase 5 is not in the set at all) |
202
+ | `/multi-agent` | 0 at Step -1; then at Step 7.5 either 1 → 2 → 3 → 4 → 5 → 6 → 7 (Full) or 3 → 4 → 5 → 6 → 7 (Short) |
203
+ | `:local` | same two batches, with Phase 5 in neither |
204
204
  | `:autopilot`, `:local-autopilot` | 0 → 1 → 2 → 3 → 4 → 6 → 7 (7 phases - always Full, and the interactive Phase 5 gate is dropped) |
205
205
  | `:analysis` | 0 → 1 → 2 → 4 → 6 → 7 (6 phases - no code is written, so 3 and 5 are not in the set) |
206
206
 
207
207
  A phase outside the mode's set gets no TaskCreate at all; the `[SKIPPED]` pattern applies only to a phase that IS in the set and short-circuits at runtime. Phase 4 is in every mode's set as of v14.0.0. The authoritative per-mode set is the `for p in ...` init block in each mode's own entry doc, generated by `gen-mode-dispatch.mjs`; this table mirrors those blocks.
208
208
 
209
- #### Late skip - the depth picker
209
+ #### Deferred registration - the depth picker
210
210
 
211
- The two picker entries cannot know their phase set at Step -1, and the ordering rule above forbids pre-marking. The contract already has the answer, and it is the only permitted one: register the tile in order with the default `pending` status, then flip it when the phase actually short-circuits.
211
+ `/multi-agent` and `:local` cannot know their phase set at Step -1. Depth decides it, and the depth picker cannot run before Step 7.5: its recommendation needs `taskType`, which needs the fetched issue and the branch.
212
+
213
+ Until v17.5.0 they registered all eight anyway and flipped 1 and 2 to `skipped` at 7.5. That put a widget reading "8 tasks, 7 open - Phase 1 Analysis, Phase 2 Planning, ..." on screen *beside* the question asking whether to run Analysis and Planning at all, and a Short answer then contradicted a list the user had just been shown. The widget was asserting a shape the run had not chosen.
214
+
215
+ So registration splits at the moment the shape is known:
212
216
 
213
217
  ```text
214
- # Step -1, before anything else: all eight, in order, all pending
215
- TaskCreate(Phase 0) ... TaskCreate(Phase 7)
216
-
217
- # Step 7.5, after the depth answer. Short only:
218
- TaskUpdate(taskId₁, status="completed", activeForm="[SKIPPED]")
219
- TaskUpdate(taskId₂, status="completed", activeForm="[SKIPPED]")
220
- bash $HOME/.claude/scripts/phase-tracker.sh update 1 skipped
221
- bash $HOME/.claude/scripts/phase-tracker.sh update 2 skipped
218
+ # Step -1, first thing in the run: Phase 0 only. It is the one phase that is
219
+ # certain, and the run is never silent while Phase 0 does its work.
220
+ bash $HOME/.claude/scripts/phase-tracker.sh add 0 Init
221
+ bash $HOME/.claude/scripts/phase-tracker.sh tiles # -> TaskCreate(Phase 0)
222
+ bash $HOME/.claude/scripts/phase-tracker.sh update 0 in_progress
223
+
224
+ # Step 7.5, immediately after the depth answer:
225
+ # Full -> 1 2 3 4 5 6 7 Short -> 3 4 5 6 7
226
+ # :local drops 5 from either (no worktree to check out from)
227
+ for p in "3:Dev" "4:Review" "5:Test" "6:Commit" "7:Report"; do
228
+ bash $HOME/.claude/scripts/phase-tracker.sh add "${p%%:*}" "${p#*:}"
229
+ done
230
+ bash $HOME/.claude/scripts/phase-tracker.sh tiles --new # -> TaskCreate for the new tiles only
222
231
  ```
223
232
 
224
- Order holds because every tile was created before any update. Nothing is pre-marked: at creation time the run genuinely does not know, and the flip happens at the moment it learns.
233
+ `tiles --new` emits `TaskCreate` only for phases that carry no `tasklist_id` yet, so the Phase 0 tile is not created twice. It is the same ordering rule, applied per batch: every tile in a batch is created in ascending phase order, and a deferred batch only ever appends phases numbered above everything already registered. Nothing is pre-marked, and a phase the run will not execute never gets a tile at all.
234
+
235
+ A phase that IS registered and short-circuits later still flips with `[SKIPPED]` - autopilot suppressing Phase 5, for instance. That is a runtime outcome, not an unknown set.
225
236
 
226
237
  **Enforcement**: `smoke-tasklist-ordering.sh` scans the dispatcher (`commands/multi-agent/SKILL.md`) and every mode entry point doc (`commands/multi-agent/{autopilot,local,local-autopilot,analysis,resume-local}/SKILL.md` + the Copilot full-inline orchestrator mirror) for the explicit "in phase-number order" rule. Inventory drift fails the smoke.
227
238
 
@@ -76,10 +76,94 @@
76
76
  "type": "string",
77
77
  "description": "PR target branch (e.g. develop, main)."
78
78
  },
79
+ "baseFetchStatus": {
80
+ "type": "string",
81
+ "enum": ["fresh", "cached-stale", "local-branch", "aborted"],
82
+ "description": "What the base ref is worth. fresh = git fetch origin succeeded; cached-stale = the fetch failed and the user chose the remote-tracking cache; local-branch = the fetch failed and the user chose the local branch; aborted = the user stopped the run at the fetch-fail picker. phase0-exit-gate.mjs has required this field since v17.0, while this schema forbade it under additionalProperties: false - so a state that satisfied the gate failed validation and vice versa. Declared here as of v17.5.0."
83
+ },
79
84
  "baseBranchSource": {
80
85
  "type": "string",
81
- "enum": ["asked", "input", "remembered", "default"],
82
- "description": "How baseBranch was decided. asked = the user answered the Step 3 picker; input = it arrived with the task reference; remembered = autopilot took the most recent entry in prefs.global.recentBranches still inside the TTL and still on the remote; default = autopilot fell back to the develop/release/main sort order. An autopilot run cannot be asked anything, so recording which rule fired is what keeps it readable afterwards."
86
+ "enum": ["asked", "input", "remembered", "default", "derived"],
87
+ "description": "How baseBranch was decided. asked = the user answered the Step 3 picker; input = it arrived with the task reference; remembered = autopilot took the most recent entry in prefs.global.recentBranches still inside the TTL and still on the remote; default = autopilot fell back to the develop/release/main sort order; derived = autopilot took the top base-branch-candidates.mjs candidate, which carried issue-version or linked-release evidence and tied with nothing. The last three are autopilot resolutions: an autopilot run cannot be asked anything, so recording which rule fired is what keeps it readable afterwards, and an interactive run that records one has skipped its picker. A derived branch that a human then confirmed is still asked - the derivation is recorded in baseBranchEvidence, not in this field."
88
+ },
89
+ "baseBranchEvidence": {
90
+ "type": "object",
91
+ "additionalProperties": false,
92
+ "description": "v17.5.0+ - what Step 3 knew when it chose the base branch (refs/features/base-branch-evidence.md). Required when baseBranchSource is derived, and whenever baseFetchStatus is cached-stale or local-branch: a run may degrade to local refs, it may not report a local-only list as the remote's answer.",
93
+ "required": ["refProvenance"],
94
+ "properties": {
95
+ "refProvenance": {
96
+ "type": "string",
97
+ "enum": ["remote", "local"],
98
+ "description": "Where the candidate ref list came from. local means the fetch failed and the list is the local cache plus local heads - possibly stale, possibly incomplete."
99
+ },
100
+ "chosen": { "type": "string" },
101
+ "ambiguous": {
102
+ "type": "boolean",
103
+ "description": "Two or more candidates tied at the top score. Autopilot may not record derived when this is true."
104
+ },
105
+ "convention": {
106
+ "type": ["object", "null"],
107
+ "additionalProperties": false,
108
+ "description": "The release-branch template inferred from the refs that exist, never from a built-in table.",
109
+ "properties": {
110
+ "template": { "type": "string" },
111
+ "members": { "type": "integer", "minimum": 0 }
112
+ }
113
+ },
114
+ "candidates": {
115
+ "type": "array",
116
+ "items": {
117
+ "type": "object",
118
+ "additionalProperties": false,
119
+ "required": ["branch"],
120
+ "properties": {
121
+ "branch": { "type": "string" },
122
+ "score": { "type": "number" },
123
+ "refs": { "type": "array", "items": { "type": "string" } },
124
+ "evidence": {
125
+ "type": "array",
126
+ "items": {
127
+ "type": "object",
128
+ "additionalProperties": false,
129
+ "required": ["kind"],
130
+ "properties": {
131
+ "kind": {
132
+ "type": "string",
133
+ "enum": [
134
+ "issue-version",
135
+ "linked-release",
136
+ "version-convention",
137
+ "recent",
138
+ "repo-default",
139
+ "sort-order",
140
+ "ref-provenance"
141
+ ]
142
+ },
143
+ "detail": { "type": "string" }
144
+ }
145
+ }
146
+ }
147
+ }
148
+ }
149
+ },
150
+ "notes": { "type": "array", "items": { "type": "string" } },
151
+ "askedOnIssue": {
152
+ "type": ["object", "null"],
153
+ "additionalProperties": false,
154
+ "description": "The one comment autopilot is allowed to post when the derivation is ambiguous, gated by prefs.global.baseBranchEvidence.autopilotAsksOnIssue (default false). A question, never a state change: no transition, no close, no assignee. Posting it trips circuit-breaker trigger 6 and the run waits for resume.",
155
+ "properties": {
156
+ "target": { "type": "string" },
157
+ "url": { "type": "string" },
158
+ "at": { "type": "string", "format": "date-time" }
159
+ }
160
+ }
161
+ }
162
+ },
163
+ "workspaceSource": {
164
+ "type": "string",
165
+ "enum": ["asked", "command", "autopilot"],
166
+ "description": "Who decided where the branch lives. asked = the user answered the Step 5b workspace picker; command = :local / --local / :local-autopilot stated it up front, or a flow that only ever builds worktrees; autopilot = resolved to a worktree without asking, because an unattended commit in the user's own checkout is what worktrees prevent. localMode alone cannot say: false is both a chosen worktree and one nothing asked about."
83
167
  },
84
168
  "remoteType": {
85
169
  "type": "string",
@@ -883,7 +967,7 @@
883
967
  "circuitBreaker": {
884
968
  "type": "object",
885
969
  "additionalProperties": false,
886
- "description": "Autopilot circuit-breaker record (refs/features/autopilot-circuit-breaker.md). Written only when a trigger trips: trigger 2 by Phase 4 Step 3.8 (a mandate finding survived identicalFindingCycles rework cycles), trigger 3 by the Phase 3 re-entry hard-kill. /multi-agent:resume clears tripped and keeps counters.",
970
+ "description": "Autopilot circuit-breaker record (refs/features/autopilot-circuit-breaker.md). Written only when a trigger trips: trigger 2 by Phase 4 Step 3.8 (a mandate finding survived identicalFindingCycles rework cycles), trigger 3 by the Phase 3 re-entry hard-kill, trigger 6 by Phase 0 Step 3 when autopilot posted the base-branch question on the issue and must not answer it itself. /multi-agent:resume clears tripped and keeps counters.",
887
971
  "required": ["tripped"],
888
972
  "properties": {
889
973
  "tripped": {
@@ -892,7 +976,7 @@
892
976
  "trigger": {
893
977
  "type": ["integer", "null"],
894
978
  "minimum": 1,
895
- "maximum": 5
979
+ "maximum": 6
896
980
  },
897
981
  "detail": {
898
982
  "type": "string"