---
title: Dev Pipeline panel (part 2)
---

# Dev Pipeline panel (part 2)

## What it is

This is part 2 of the [Dev Pipeline panel](dev-pipeline-panel.md) page. That page covers what the panel is, where it lives, and the first four of its rail entries — **Live**, **Timing**, **Agent Board** and the **Worktrees** half of the worktree view. This half carries the remaining three rail entries — **Auto-lander**, **QA Fleet** and **Setup** — and, for anyone working on the code, how the panel is put together.

## Where to find it

Both halves are the same surface: the built-in **Dev Pipeline** virtual project, which docks in the main panel beside the projects sidebar and opens from its own row in the Omniscio sidebar. On a phone the rail becomes a row of tabs across the top, so the three entries described here are reached the same way remotely.

## How it behaves

**Auto-lander.** Its **own tab** on the rail is where you watch it: live daemon status, the
queue of branches currently marked ready-to-merge, and the **land history** — every branch it
has **landed**, handed back on a **conflict**, or **set aside** (couldn't land), kept across app
restarts, plus two quick actions: pause/resume the whole lander, and pause/resume one
repository. Full write-up: [auto-lander-dashboard.md](auto-lander-dashboard.md).
Its **switches live in Setup**: turn it on or off, and choose which repositories it
watches (setting a repo to _observe-only_ first). The auto-lander lands branches you've
marked ready into your main branch automatically and locally; it only ever moves
things forward and **never pushes**. **A branch you mark ready is trusted as finished** — the lander merges it even if the dev-pipeline's process record was never marked complete (that check is informational, so a finished branch never sits stuck waiting for a record that was never updated). **If a branch you marked ready gets more commits after tagging — or is rebased onto a newer main — the auto-lander now re-checks and re-tags it for you so it still lands, instead of stranding it behind a "moved after tagging" alert; you're only notified if that automatic re-check genuinely can't land it.** **When a branch lands you also see it in two
more places:** the session that produced it gets a green **"Landed … to master"** note
(with a check) just below its final message — even after that session has finished (it's
never reopened or re-run) — and the run is marked **"Landed ✓"** in the Timing tab
instead of just quietly disappearing when its worktree is cleaned up. If the auto-lander's integration
checkout ever drifts out of sync it quietly re-syncs it — this self-heal runs
**silently** (logged for diagnostics, never an inbox notice) and always snapshots a
backup first, so nothing is lost.

Expanding a **conflict** row lists the conflicting files. That list is bounded and says so:
the first 20 with a **"+N more"** tail, and — because only the first 50 conflicting paths are
ever recorded per conflict — a plain note when you're at that recording limit, so a 90-file
conflict never masquerades as a complete 50-file one.

**Automatic master catch-up.** The other thing in this panel that can move your local `master`
on its own, which is exactly why it sits beside the auto-lander: a background check that fetches
the remote and merges it into your local `master` for you — about five minutes after the app
starts, then every six hours. **Off by default**, and it only applies when you run Omniscio from
source. Off does **not** mean silent: you are still **told** when you have fallen behind, with a
one-click catch-up — nothing moves your branch unless you ask.

Beside it is a **paid** sibling, also off by default: an hourly job that finishes a catch-up
`origin/master` conflicts have blocked, by buying a capped AI session for whatever the free path
cannot resolve. It is a **separate** switch on purpose — one of these moves a branch and the other
spends money, so arming the free one must never quietly arm the paid one. Neither implies the
other. Both are the sort of switch a person has to flip: they are on the CLI's patch denylist, so
no agent token can turn them on for you.

**QA Fleet.** The QA fleet's results, in the panel — what the background QA runs found, read
from the ledger they write locally (`~/.amc/qa-fleet/ledger.db`). Its rail entry (a flask
icon, sitting between Auto-lander and Setup) shows an **amber badge with the number of open
findings** whenever there is at least one, so you can see at a glance whether there is
anything to look at without opening the tab.

Five totals lead the view — **Open Findings**, **High/Critical Open**, **E2E Passing**,
**E2E Failing** and **Features Covered** — followed by a **14-day open-findings trend**, one
bar per day showing how many findings were still open at the end of it. Below that sit three
lists:

- **Findings** — each open one with its severity, the file it lives in, and how many times it
  has been seen, with three inline actions: **Resolve**, **Dismiss** and **Reopen**. A
  filter tabs between **Open (n)** and **Resolved/Dismissed (n)**, so a finding you closed is
  still reachable rather than gone. Closing one fires straight from the row with no confirm
  step — it is fully reversible, and taxing every correct click to catch the rare wrong one
  would be the wrong trade — and a banner above the list carries an **Undo** for the finding
  you just changed, right where you are already looking.
- **E2E Coverage** — each feature with a **PASS** / **FAIL** mark.
- **Least Recently Exercised** — what the fleet has not touched in a while, or **Never**.

Everything refetches on a **30-second** timer while the tab is open (the fleet produces
results on a minutes-to-hours cadence, so a mount-only read would go stale the moment it
landed), and there is a manual **Refresh** beside an **"Updated \<ago\>"** cue. Two different
empty states say which problem you have: _"QA fleet has not run on this machine yet"_ versus
_"QA fleet results could not be read"_ — a fleet that never ran is not the same as one whose
records came back unreadable, and neither is dressed up as a reassuring zero.

**Worktree cleanup.** A live view of the automatic cleanup that reclaims disk by
retiring finished git worktrees. You see a **status card** (when it last ran, a
**countdown to the next run** — _"in 1h 20m"_ — and a warning if it hasn't run in a while),
four **headline tiles** (removed / stale entries / kept / disk reclaimed), the **run history**
(each cycle's "removed" and "stale entries cleared" counts), a **grid of every worktree** with
a plain reason it was kept or removed and how long ago it was touched, a **recover** button
that restores a mistakenly-removed worktree's branch, and a **Run cleanup now** button. Your click on
that button always runs, even while the computer is busy — it is a person asking — whereas a cleanup an
agent requests through the control server is held until the load eases, so the app never slows you down
to tidy worktrees. The restore list
shows only the reaps that can **actually** be brought back right now — one you have already
restored, or one whose commit git no longer holds, drops off the list instead of offering a
button that would do nothing.

All four tiles describe the **same** run — the one the status card names — including
**Kept**, which is that run's own count of worktrees it left alone because they held
unlanded or uncommitted work. (Kept is deliberately _not_ a tally of the verdict grid: the
grid is the latest _analyze_ output rather than the latest run, and every amber **Review**
row would have counted as "Kept".) The Keep-vs-Review distinction lives where you can see
it, on each row's verdict pill. In that grid, "last touched" is a plain local **"14h ago"**,
the same as the Worktrees section — the underlying tool writes a UTC wall clock, which is
parsed to a real instant before it reaches the screen. The reaping itself is done by the same battle-tested cleanup
scripts, which Omniscio now runs automatically on a schedule from **inside the app** — plus a
catch-up when Omniscio starts, and it waits for a quiet moment if your machine is busy (so it
never causes a stutter). There is **no Windows scheduled task to install**: Omniscio owns the
schedule, removes the old task once, and auto-creates the stranded-worktree triage helper.
When cleanup is on you also get two **customization** controls: **How often to tidy** (a
dropdown — every 1, 2, 4, 6, or 12 hours; every hour by default), and an **AI triage for stuck
worktrees** switch (on by default) that occasionally spends a little on a short AI session to
judge worktrees that look stuck — turn it off to keep the free cleanup but skip the paid
triage. This panel makes it visible and gives you the controls; it never re-implements the
cleanup, so every safety protection is preserved.
Windows only; off by default (turn it on with the _worktree cleanup_ toggle in
**Performance**, or here).

**Bypass the testing gates.** One Setup switch that stands down the machine's testing gates
all at once, for when a broken or slow test system is what is blocking you rather than a real
finding. Two things make it safe to have at all. First, the safety rules are **not** in the
set it can reach: the rules that stop a commit to `master`, a destructive stash, or a deleted
worktree stay armed and are never affected. Second, nothing it stands down is ever recorded as
passing — **every skipped check says out loud that it did not run**, and reports BYPASSED, so a
green after a bypass can never be mistaken for a real green.

It is **off by default**, it affects **this machine only**, and it has **no expiry** — nothing
turns it back off for you. While it is on, the card shows a permanent, deliberately
non-dismissible line — _"Nothing is being verified right now. This stays on until you switch it
off."_ — because how long it has been on is the only thing standing between a quick flip and a
repo that quietly stopped being tested. It is stored in its own file (`~/.amc/dev-bypass.json`)
rather than in your settings, since scripts and git hooks outside the app read it too. Turn it
off and every gate comes back exactly as it was.

**Where checks run.** The card beside it holds the machine's one testing setting: **Cloud first**
(heavy checks run in the cloud; while the cloud is down a few at a time run here), **Cloud only**
(heavy checks never run here) or **This computer** (everything runs here, no cloud needed). Small
test runs always stay here. Clicking a value saves it at once; under the choices the card says
where the next heavy check will run and why, whether the cloud is answering, who set the value,
any approved window, and which version of the check service is running. It shows exactly what
`npm run testing` prints at a terminal. Agents cannot change it — they ask, and you approve a
card. Full detail: [Where checks run](where-checks-run.md).

**Session-based cleanup (beta).** A separate, **cross-platform** option that goes further
than the free reaper: on an intelligent cadence it removes safe worktrees for you — including
branches **merged via a squash PR**, which the plain reaper can't recognize — and, for anything
it isn't sure about, spawns **one short, conservative AI investigation** that scans for stray
worktrees and flags anything real to your inbox, then tidies itself away if it found nothing. It
runs only on your **active dev-pipeline repos** (plus a slower sweep for stray worktrees
anywhere), never deletes uncommitted or in-use work, and is bounded by a hard **daily session +
spend cap** so it can't run up a bill. Off by default; enable it and set the caps in this section.
It runs **alongside** the free reaper, never replacing it.

## For agents

- **Virtual project** `__dev_pipeline_panel__`, integration id `dev-pipeline-panel`
  (manifest: `src/shared/integrations/dev-pipeline-panel.ts`). Distinct from the
  `dev-pipeline` skill-bundle integration (which installs the CLI skill).
- **Gating — the reveal and the Dev Pipeline switch are DIFFERENT settings.** The panel's
  visibility comes from the `dev-pipeline-panel` entry in `src/shared/unreleased-features.ts`;
  its `status: 'shipped'` makes `computeVisibility()` return true BEFORE it ever looks at a
  setting, so its `settingKey` (`devPipelinePanelEnabled`) does **not** gate the panel — there
  is no Lab row (a shipped entry is filtered out of `listInDevelopmentFeatures()`, so it
  generates no reveal toggle and no derived settings-search entry), no file under
  `src/renderer` references it at all, and `AMC_SHOW_DEV_PIPELINE_PANEL` is inert. The
  sidebar row is gated by `UNRELEASED_PROJECT_GATES` in `project-visibility.ts`.
  `filterVisibleProjects` ALSO reveals it when `settings.devPipelineSkillEnabled`
  (the dev-pipeline skill-bundle's `featureFlag`) is on — a cross-feature coupling
  so enabling the skill surfaces its dashboard.
  **`devPipelinePanelEnabled` is the Dev Pipeline's own opt-in, and it defaults to `false`**
  (`session-behavior-settings.ts`). It is read directly — never through the registry gate — by
  four behaviours: whether the auto-lander is armed at all (`auto-lander-repos.ts`, and
  `resolveAutoLanderRepos` returns `[]` while it is off), the worktree-mutation lockdown
  (`dev-pipeline-repos.ts`), daily maintenance (`daily-maintenance-supervisor.ts`) and
  git-store maintenance (`git-store-maintenance.ts`). The only in-repo writer is the
  developer-machine profile (`developer-setup-settings.ts`), so a standard install has it
  off and none of the four run.
  **Gate any NEW dev-pipeline-panel surface through `isUnreleasedFeatureVisible`** as usual —
  never the raw setting — but do not add gating on the assumption the panel is hidden. It
  isn't.
- **Mobile / Web Access:** the manifest declares `mobile: { status: 'ready' }`, so
  the panel shows + mounts on mobile via the same `filterSidebarOnlyVirtualProjects`
  gate + `panelOwnsLayout` routing the desktop uses (its IPC channels proxy over Web
  Access). The desktop-only set (declared `mobile: { status: 'desktop-only' }`) is exactly
  THIRTEEN ids — `browser`, `native-browser`, `running-apps`, `screen-recordings`,
  `screenshots` (the five with a real `<webview>` / `desktopCapturer` / keyboard constraint),
  plus `journal`, `gauntlet-loop`, `phone-control`, `hooks`, `teach-recorder`,
  `contextdock-native`, `composio` and `memory`, which are desktop-only for PRODUCT reasons
  rather than a platform one — `contextdock-native` and `memory` share their cause (both read
  the `CONTEXTDOCK_STORE_*` channels, which the mobile/web WS bridge blocks), while `composio`
  opens a DESKTOP browser sign-in and wires the resulting tools into locally-spawned Claude
  sessions. Do NOT reason from an older four-, five- or twelve-member list; the authoritative
  inventory is the `ui-registry.test.ts` guard, whose test name reads "exactly the THIRTEEN
  known desktop-only integrations declare mobile: desktop-only", mirrored in
  `dev-pipeline-panel-contract.md` `available-on-mobile-and-web-access`. (`shared-sessions`
  moved OFF this list on 2026-09-10 — mobile reached full parity and its WS channels came off
  the denylist — so do not re-add it.)
- **Layout (renderer):** `DevPipelinePanel` renders a category rail (`CategoryRail` —
  Live / Timing / Agent Board / **Auto-lander** / **QA Fleet** / Setup; the `DevPipelineCategory`
  union is `'live' | 'timing' | 'board' | 'autolander' | 'qafleet' | 'setup'`, from
  `DEV_PIPELINE_CATEGORIES` in `src/shared/dev-pipeline-panel.ts`) + a content pane showing the
  active category. **Auto-lander** (`AutoLanderView`) and **QA Fleet** (`QaFleetView`) are BOTH
  first-class rail entries rendered UNCONDITIONALLY — see
  [auto-lander-dashboard.md](auto-lander-dashboard.md) for the first; `CategoryRail` gates only
  `board` behind its optional `showBoard` prop.
  **Agent Board** (shown only when `agentStatusBoardEnabled`, via `CategoryRail`'s optional
  `showBoard` prop) renders the shared `AgentBoardList` — the same board body the global
  Agent Board view uses — and jumps to a session via `navigateToSessionInProject`; the panel
  resets `category` to Live if the feature is turned off while the tab is open (see
  `agent-status-board.md` + `dev-pipeline-panel-contract.md` L1). **Live** is
  `LiveView`, which unions the flat runs list with the already-per-project worktrees via the
  pure `buildProjectGroups` (`pipeline-live-grouping.ts`) into one collapsible group per
  project, reusing `RunRow` + `RepoGroup` (`worktree-rows.tsx`) verbatim and one
  `useWorktreeRemoval` hook + dialog. Gate/phase labels are humanized + overflow-hardened in
  `pipeline-labels.ts` (`gateLabel` recovers the canonical `GATE_n` from a noisy value and
  caps an unknown one; the Waiting pill is width-capped + truncates). **Timing** is
  `TimingView` — a five-tile summary grid (Working · Waiting on you · Stuck · Avg wait ·
  Spend) + a biggest-stall line, over a list of expandable `RecentRunRow`s, each a proportional
  green/amber/red/idle `SplitBar` that opens to a per-phase working-time-and-spend + per-gate
  wait `RunBreakdown`. See `dev-pipeline-panel-contract.md` L1–L3.
- **Active runs** are derived on demand by `derivePipelineRuns()`
  (`src/main/services/dev-pipeline/dev-pipeline-panel-service.ts`), which reads each
  non-terminal session's worktree `.claude/pipeline/state.md` with the same
  parser the Agent Status Board uses — so it does NOT depend on the (default-off)
  Agent Status Board feature. It also carries each run's `statusChangedAt` /
  `needsYouAt` so the row can render the live `waiting <time>` counter. IPC: `PIPELINE_RUNS_GET`.
- **Run timing** (the Timing tab) is captured by Omniscio itself, NOT by the skill:
  `registerPipelineRunTimeline` (`src/main/services/dev-pipeline/pipeline-run-timeline-service.ts`)
  subscribes once (at handler registration → always-on) to the internal `sessionEvents`
  'statusChanged' bus that `SessionStatusManager.updateStatus` fires on **every** committed
  transition (`process-manager.ts` `notifyStatusChanged`), and appends a `pipeline_run_events`
  row ONLY for dev-pipeline runs (same phase/gate test as `derivePipelineRuns`) — so the table
  is naturally scoped and capture works whether or not the panel is open (deliberately NOT the
  default-off status-board tick, NOT the ~165-call-site DB chokepoint). Capture is best-effort
  (guarded, can't perturb the status path) + idempotent (an in-memory last-tuple cache,
  restart-seeded from the DB via `getLatestPipelineRunTuple`). The pure reducer
  `derivePipelineRunTimeline` (`src/shared/pipeline-run-timeline.ts`, caller passes `nowIso`)
  splits each run into working / gate-wait / stuck (deterministic) + per-phase (best-effort),
  and `summarizePipelineRuns` builds the strip. History is a SEPARATE read
  `derivePipelineRunHistory` (`.../pipeline-run-history-service.ts`, IPC `PIPELINE_RUN_HISTORY_GET`)
  over the durable events joined to sessions (incl. finished runs), with an inline 90-day prune
  on write. No AI calls → zero cost. See `dev-pipeline-panel-contract.md` RT1–RT5.
- **Worktrees** are derived on demand by `deriveRepoWorktrees()`
  (`src/main/services/worktree/dev-pipeline-worktrees-service.ts`): per real-repo project it reads
  `git worktree list` + branch descriptions + each worktree's `state.md`, cross-referenced
  with the sessions table — no stored registry, so it never goes stale and surfaces
  orphans. Removal runs the pure `evaluateWorktreeRemovable` guard (precedence
  `main-checkout` > `not-a-worktree` > **`keep-marker`** > `in-use` > `uncommitted` >
  `unlanded` — six reasons, not five; `keep-marker` is an ABSOLUTE veto for a branch whose
  DESCRIPTION carries a do-not-delete directive, so a clean/landed/orphaned-but-PINNED
  worktree is still refused. It reads fail-CLOSED at remove time — an unreadable branch
  description counts as marked. Kill switch `AMC_DISABLE_WT_KEEP_MARKER_VETO`), RE-gathered with real
  git reads in Main before any delete; live ownership includes `agentDetectedWorkdir` (how
  a self-created worktree is tracked), and deletion goes through the junction-safe
  `removeWorktree`. IPC: `WORKTREES_GET` (read), `WORKTREE_REMOVE_ONE` /
  `WORKTREE_REMOVE_SAFE_BULK` (destructive). Both removals are ALSO CLI control-server
  routes — `POST /project/:id/worktrees/remove` (`{ worktreePath }`) and
  `/remove-safe` — so an agent can clean up worktrees headlessly; they reuse the same
  service verbatim, so the same guard runs and a kept worktree comes back as a normal
  `200 { removed: false, blockedReason }` (`cli-server-project-ops-routes.ts`;
  documented in the omniscio-control `projects.md` spoke). See
  `dev-pipeline-panel-contract.md` W1–W7.
- **Run cost** is captured by Omniscio, not the skill: each `pipeline_run_events` row snapshots
  the owning session's cumulative `cost_usd` (nullable, additive migration), and the pure
  `derivePipelineRunCost` (`src/shared/pipeline-run-timeline.ts`) turns consecutive
  snapshots into run + per-phase deltas — run-scoped by construction (a session's total
  includes pre-pipeline chat), clamped ≥ 0, `null` = unknown (never a fabricated 0). The
  Live list closes each open run against the session's current cost via ONE batched
  `getFirstPipelineCostSnapshots` read (never N+1). See `dev-pipeline-panel-contract.md` RT6.
- **Stale runs + Adopt**: a worktree is a _stale run_ when it carries a non-complete
  pipeline state, no live session owns it, and its recorded `## Owner` session is gone or
  terminal (`isStalePipelineRun`). `adoptStalePipelineRun` re-verifies everything at click
  time, serializes concurrent adopts (in-flight set), transfers the state file's `## Owner`
  to a RESERVED session id BEFORE spawning (a failed spawn stays stale → retryable), then
  spawns via `createSessionWithPrompt` with `reuseWorktree` (runs INSIDE the stranded
  worktree; fails closed pre-insert if the path vanished) and a `/dev-pipeline` resume
  prompt — the skill's own STEP-0 discovery matches the new owner and resumes. IPC:
  `PIPELINE_ADOPT_RUN`, also the CLI route `POST /project/:id/worktrees/adopt-run`
  (`{ worktreePath }`). Because adopting spawns a real (paid) session, the CLI kind
  `pipeline.adopt_run` is **NON_TOGGLEABLE + IRREVERSIBLE**: the route only ENQUEUES an
  inbox approval (no in-app-session bypass — an AI can never self-approve it) and
  requires a source session; the `pipeline-adopt-run-handler` runs `adoptStalePipelineRun`
  only on approval. See `dev-pipeline-panel-contract.md` A1–A5.
- **Load-failure behavior**: a failed panel read toasts once (while un-hydrated) and shows
  the empty state instead of an infinite "Loading…"; the panel's own interval keeps
  retrying, and a transient failure after a good load keeps the last-good data. See
  `dev-pipeline-panel-contract.md` L8.
- **Gate auto-approval** is per-phase: five independent gate toggles (Plan · Red
  Team · Build · Elegance · Docs) + a select-all master, backed by
  `pipelineGateAutoApprove` and resolved by `resolveGateAutoApprove` (fallback to the
  deprecated `pipelineAutoAdvanceEnabled` / `pipelineAutoApprovePlanGate`; see
  `pipeline-auto-advance-contract.md`). This panel and Settings → Features render the
  SAME controls (`GateAutoApprovalControls`), so their wording can't drift.
- **The two always-on standards gates (🚦/🔬) also appear as toggles here** (panel-only —
  Settings → Features shows the built-in five). They persist in a separate setting
  `pipelineStandardsGateAutoApprove` (NOT `pipelineGateAutoApprove`), default auto-approve;
  the panel routes their on/off through the same row list and the setting flows through the
  manifest so a gate set manual holds the run for you. See `dev-pipeline-panel-contract.md`
  L9. (The ops-only `DEV_PIPELINE_DISABLE_BUNDLED_STANDARDS` kill switch is honored by the
  pipeline but not reflected in this panel's display.)
- **Custom phases show up here too** (when **Enable custom phases** is on). The panel
  resolves your phase manifest (`phases.json`) and feeds it to `GateAutoApprovalControls`,
  so BOTH the flow diagram AND the switch list weave each custom phase in at its insert point
  (with its emoji) — each custom **gate** gets its own auto-approve switch at its true pipeline
  position (e.g. 🚦 Standards Check between Red Team and Build), not appended after the built-in
  five, so the switch order matches the flow diagram above it. One list controls every stop.
  Settings → Features (no manifest passed) keeps the plain built-in six. See
  `dev-pipeline-panel-contract.md` L6/L7.
- **AI code review — a review step after any phase.** The **AI code review** card offers one
  switch per phase (after Plan, Red Team, Build, Elegance, Docs); each one you turn on inserts a
  real 🧭 **AI Code Review** phase right after that phase, where fresh AI reviewers read the work
  and report what they find. Off everywhere by default, so an untouched install is unchanged.
  **Every step that runs gets a row — including one your repo sets for everyone**, which the card
  labels as such; its switch turns it off for you alone, leaving your team's setup untouched.
  These gates get NO auto-approve switch in the list above, because there is no third state for a
  switch to select: the **review's own verdict** decides. A clean review closes green and the run
  moves straight on; a review that found something worth your attention parks for you. Each point
  you turn on spends
  reviewer tokens on every run. Your choice is saved alongside your custom phases and your own
  phases are never disturbed by these switches; it applies whether or not **Enable custom
  phases** is on. See `dev-pipeline-panel-contract.md` L11 (ops kill switch:
  `DEV_PIPELINE_DISABLE_BUNDLED_AI_REVIEW`).
  Above those switches, **Run AI code review steps** (on by default; the `devPipelineAiReviewEnabled`
  setting) turns every review step off at once, including in runs already under way: agents stop
  being given review steps, a reviewer asked for from a review step is refused, and a session already
  waiting at one still moves on. While it is off, the per-step rows hide and the flow diagram shows no
  review step; the per-step choices stay saved for when it goes back on. It is the one AI-review
  control stored as a setting, because the app applies it itself — see
  `dev-pipeline-panel-contract.md` `ai-review-switch-reaches-running-work`.
  Under the switches, **How each AI has done** is the reviewer scorecard: one row per AI model with
  how many reviews it ran (and how many got no answer), the **serious** and **minor** problems it
  found that the pipeline checked and confirmed, its **false alarms**, how often the pipeline
  **approved** of its reviews (shown as "75% approved" once any review is rated — never 0% before
  that), and what it cost in total and per review. Cost is read from each reviewer's own session,
  so it is exact, and it stays on the scorecard even after you delete that session; a dash means
  the cost isn't reported (a reviewer that ran inside the pipeline's own session is billed with it,
  and some AI companies don't report cost to Omniscio yet). Below it, **Whose work was reviewed**
  lists each AI whose work the reviews looked at — its reviews, the serious problems found in it,
  and serious problems per review — so you can see which AIs need more review. Both stay empty
  until a review step runs. See `ai-review-scorecard-contract.md`.
- **Who writes the code — pick a cheaper model for the build step.** The **Who writes the code**
  card sets which model actually writes code during the build phase, and whether it runs as a
  helper inside the run (the default, cheapest) or as **its own session** you can watch in
  Mission Control. Picking a cheaper writer is safe because it turns the pipeline's existing
  cheap-implementer _preference_ into a **rule**: the smart model becomes an overseer that writes
  the failing test, hands the work over, reviews every change that comes back, sends work back
  with specific fixes, and keeps only what it accepts. Nothing a cheaper model writes is kept
  unreviewed, and the build gate has to report which model built it **and how many changes were
  accepted versus sent back** — a run that quietly skipped the loop has no counts to show. You
  store a **speed level** (Genius / Smart / Worker / Basic), never a specific model name, so your
  choice never goes stale when a new model ships. Default: **Worker** (Sonnet-level), as a helper
  in the session. Own-session mode starts a real session per build and costs money — turning it
  on is what allows the pipeline to start them. Saved alongside your custom phases in
  `phases.json`, which is the one file both Omniscio and the Claude skill read — a normal setting
  would be invisible to the run it configures. See `dev-pipeline-panel-contract.md`
  `build-model-lives-in-the-manifest-not-settings`.
- **What you set is what runs — the editor stays in sync with the project copy.** The editor
  saves to your **personal** manifest (`~/.claude/dev-pipeline/phases.json`), but a run resolves
  **project-over-personal**, so an edit to a phase a repo ALSO commits
  (`<repo>/.claude/dev-pipeline/phases.json`) would otherwise be silently overridden. The panel
  bridges both directions (`phase-manifest-service.ts`): on **save** it writes the edit THROUGH
  into every repo that already commits that phase (`syncCustomPhasesToProjectManifests` — your
  fresh edit wins; it never adds a personal-only phase to a repo, never deletes a project phase,
  and never commits/pushes — you commit the file), and on **read** it shows the **effective**
  value (`overlayProjectValuesOntoPersonal` — a phase a repo commits displays that project value,
  so a switch can't read "on" while "off" is what actually runs). Both are fail-safe. A
  **personal-only** phase (no repo commits it) stays yours; a committed **project** manifest
  applies to every dev on that project (see [dev-pipeline.md](dev-pipeline.md) "Project-level phases").
  **AI Code Review steps are the exception both ways:** a repo's review steps are the team default,
  and once you change any review setting for a step, YOUR settings are what run on your machine —
  so the editor shows your own values for it, and a save never writes a review step into a repo's
  shared file. A step you switch on without changing anything keeps (and shows) the shared
  settings, and a save never quietly turns that copy into your own.
- **Auto-lander** reuses `AutoLanderSettings` (per-repo config, force-revealed via
  its `embedded` prop) + `MergeDaemonStatusCard` (live status). The land history
  is a new `auto_lander_events` table written fail-open by the auto-lander
  supervisor's per-tick sink (the three terminal outcomes only — landed / conflict /
  unlanded — deduped + retention-pruned);
  IPC: `AUTO_LANDER_EVENTS_GET`, refetched on the existing
  `AUTO_LANDER_STATUS_UPDATED` push. See `auto-lander-contract.md`.
- **QA Fleet** (`QaFleetView`) is a read + triage surface over the QA fleet ledger at
  `~/.amc/qa-fleet/ledger.db`. Two IPC channels: `QA_FLEET_SUMMARY_GET` (`qa-fleet:summary`)
  for the totals / trend / findings / E2E / coverage read, and `QA_FLEET_FINDING_STATUS_SET`
  (`qa-fleet:finding-status-set`) for the resolve / dismiss / reopen triage write. The
  `CategoryRail` badge counts open findings and renders ONLY when the count is above zero, so a
  clean fleet shows no pill. It is an UNCONDITIONAL rail entry — it carries no
  `unreleasedFeatureId`, no `visibleWhen` and no setting, so it ships to every user of this
  shipped panel; if that is ever meant to change, the missing gate is the fix, not a doc. The
  richer developer-facing driver for the same fleet is `internal/qa-fleet-desktop-routes.md`,
  which is repo-only (the `internal/` tree is excluded from the published mirror, from
  `extraResources` and from the packaged app), so this page is deliberately the customer's only
  route to it.
- **Landed-session attribution + note.** On a `landed`, the sink ALSO resolves the
  dev-pipeline session that authored the branch — via the SAME owner-resolver the
  conflict hand-back uses (`worktree_branch` → the branch's git worktree dir →
  `agent_detected_workdir`), so a self-created worktree is found and terminated
  authors count — and stamps it onto a new nullable `auto_lander_events.session_id`
  (migration `20260716130010`). Two surfaces read it: the supervisor appends a
  PASSIVE kinded `notable-system` "landed" note to that session
  (`landed-session-notify.ts` → `addMessage` + `SESSION_OUTPUT` push — fail-open,
  idempotent on a FRESH insert, and it NEVER wakes / reopens / spawns the session,
  unlike the conflict hand-back's `deliverPeerMessage`; kill switch
  `AMC_DISABLE_LANDED_SESSION_NOTIFY`), and `derivePipelineRunHistory` marks the
  Timing run **"Landed ✓"** via a single `listLandedSessionIds` join. Runs after the
  status push, before the worktree retire. See `auto-lander-contract.md` (S26) +
  `dev-pipeline-panel-contract.md` (RT7).
- **Worktree cleanup** (`WorktreeCleanupSection`) is a dashboard over the vendored
  PowerShell cleanup scripts (`scripts/worktree-maintenance/`, self-contained via
  `$PSScriptRoot`). A Windows-gated Main service (`src/main/services/worktree-cleanup/`)
  READS the tool outputs (per-run `summary.json`, the `worktrees-*.tsv` verdict grid, the
  `wt-deleted-recovery.tsv` restore log — read from the out-dir root AND every `run-<ts>/` dir,
  since every app-driven reap writes it into the run dir; tail-bounded with a row cap like the
  sibling append-only readers, ref probes pinned with `-C <resolveMergeRepoRoot()>` to the same repo
  `recoverWorktree` restores into, and the `recoverable` flag applied at the render site), and a
  5-min supervisor BOTH ingests completed runs
  into `worktree_cleanup_runs` (pushing status on-change + raising a deduped stale alert)
  AND is the **in-app scheduler** — it fires the reap on a **user-configurable cadence** + a
  startup catch-up, load-gated via `isBoxHeavyOrSettling`, decided by the pure `decideScheduledRun`
  (`worktree-cleanup-schedule.ts`). The cadence is `worktreeCleanupIntervalHours` (default 1 —
  `WORKTREE_CLEANUP_INTERVAL_HOURS` in `src/shared/worktree-cleanup.ts`, lowered from 2 on
  2026-09-02 because idle unlanded worktrees filled a drive overnight; the reap retires a capped
  batch per run, so cadence is the throughput lever) read
  through `resolveCleanupIntervalHours` (clamp `[1, 24]`, bad value → default), and the stale window
  scales with it (`resolveStaleThresholdHours`) so a long interval never false-alarms. There is
  **no OS Scheduled Task**: `worktree-cleanup-ostask.ts` removes the legacy one — only while the
  in-app replacement is active (toggle on + available; fail-safe skips), re-checked every boot
  and cleanup-settings save so a re-created task is re-detected (no permanent marker) — and
  `worktree-triage-cron.ts` auto-seeds the stranded-worktree triage-dispatch cron — active iff
  `isWorktreeTriageActive(settings)` (= `worktreeCleanupEnabled && worktreeTriageEnabled`), so the
  paid AI triage is an independent on/off while the free cleanup keeps running. The status card's
  "next run" is derived from the in-app cadence (`scheduledTaskState` is null; a load-deferred run
  reads "waiting", not "wedged"; the healthy label is generic, not a fixed interval). The runner is
  the only mutating spawn surface (a NON-detached argv-array spawn of the wrapper for the reap — a detached spawn silently never runs the script on Windows; `git branch`
  for recover — never `shell:true`). The reaping stays entirely in the scripts, so all their safety
  invariants are preserved. Flags `worktreeCleanupEnabled` / `worktreeTriageEnabled` /
  `worktreeCleanupIntervalHours`; IPC `WT_CLEANUP_*`. See `worktree-cleanup-contract.md`.
- **Session-based cleanup** (`worktreeSessionCleanupEnabled`, cross-platform, default off) is a
  SEPARATE `createPeriodicTask` scheduler (`src/main/services/worktree-cleanup/session-cleanup-*.ts`)
  from the reaper above — it runs alongside it. Each tick, for a qualifying repo (active
  dev-pipeline OR a broad stray-worktree sweep — pure `decideRepoCleanup` in `session-cleanup-decide.ts`)
  it removes every orphaned candidate through the guarded `removeOneWorktree` — now **PR-aware**:
  `prMergedConfirmed` (gathered authoritatively at remove time via `checkBranchHasPR`, fail-safe
  false) relaxes ONLY the `unlanded` refusal so a **squash-merged** branch cleans up, while every
  higher veto (main-checkout / in-use / uncommitted) stays absolute. For a REFUSED residual it
  spawns ONE **read-only** investigation session (`createSessionWithPrompt`, `isolationOverride:false`
  → repo main checkout, `source:'worktree-session-cleanup'`) that fs-scans for husks, raises one
  deduped inbox alert, and **self-archives on empty** — the session NEVER deletes. Feature daily
  **session + spend caps** (`resolveDailyCaps`, read-time clamped) sit on top of the global daily
  budget cap; boot start + a teardown task + a settings-apply reactor mirror the reaper. Settings
  `worktreeSessionCleanupEnabled` / `...MaxSessionsPerDay` / `...DailyBudgetUsd`; kill switch
  `AMC_DISABLE_DEVPIPE_WT_CLEANUP`. See `worktree-session-cleanup-contract.md` (its invariants) +
  `dev-pipeline-panel-contract.md` W4.

## Related

[Dev Pipeline panel](dev-pipeline-panel.md) is the first half — the panel's overview and its Live, Timing, Agent Board and Worktrees views. [Dev Pipeline](dev-pipeline.md) documents the workflow these controls drive.
