Star Colonel, the sweep is complete - every in-scope file read, every claimed convention traced across all four providers and both runtimes. The report, per contract:

## Verdict

`VERDICT: DO-NOT-SHIP (19 findings)` - the architecture is sound and the seven requirements broadly hold, but the wire contract carries fiction fields and one misnamed parameter (the DEF-3 class survives), and a substantial layer of zero-implementor seams and dead server surface shipped under the appearance of extensibility. My recommendations leave the system net **SMALLER** - deletions dominate; only one fix adds code (a small identity check), one is a decision for the requirements owner.

## Inconsistencies / defects

- **[MAJOR] Core asks the server for bg decorations with the wrong parameter name** - `panel.ts:1056-1057` sends `include_bg=1` when `hasBgAgents`; the server reads only `include_extras` (`routes.py:384`). It works today solely because claude's hook independently sends the right name (`claude.ts:99`). The core-level flag is dead on the wire and any future bg-agents provider relying on the descriptor alone silently gets no `bg_id`s. Fix: panel sends `include_extras: '1'`, delete claude's `branchQuery` hook, and delete the `branchQuery` seam itself (`types.ts:315`) - its only user vanishes. Net smaller.

- **[MAJOR] Gemini's `comm_name: "node-MainThread"` claims every Node process** - `gemini.py:449`; `_tree_assistant` (`routes.py:134-148`) matches on comm alone, so a terminal running `npm run dev` in a gemini-registered project answers `running: true` with no session id. The reuse ladder rejects it (as the comment says), but the colour loop does not: `terminals.ts:301-304` falls back to cwd matching, tints the unrelated terminal, and the write-back (`terminals.ts:311-320`) records user-set tab colours into gemini's colour store under a cwd-guessed session id. Fix: a per-store `owns_pid()` (default: comm match) that gemini overrides to also require `gemini` in the argv - one small method, closes a real requirement-6-class identity leak.

- **[MAJOR] Store-computed colour discarded, then recomputed expensively per row** - claude's store emits `"color"` from the already-read transcript tail (`claude.py:720`); `SessionsHandler.get` pops it and recomputes via `_effective_colour` → `default_colour` (`routes.py:311-317`, `claude.py:1044-1054`), which globs `*/<sid>.jsonl` across the whole projects root - once per row, per provider, per 30s poll. DEF-3's "British spelling throughout" closure held only on the wire; the store still speaks American and its value is dead work. Fix: stores emit `colour` and `_effective_colour` takes the row's own value as the native default; delete the per-row glob. Smaller and O(N) instead of O(N²).

- **[MAJOR] Dead disposal path: `scope: "parallel"` and the entire `cleanup()` chain** - no client ever sends `scope` (the panel deletes explicit ids, `panel.ts:637-650`), yet the route branch (`routes.py:347-352`), the abstract method (`store.py:194-196`), and four implementations (`claude.py:860-874`, `codex.py:648-661`, `kimi.py:502-519`, `gemini.py:735-754`) plus their tests all exist to serve it. Two implementations of one operation. Fix: delete the scope branch, `cleanup()`, all four overrides and their tests. What breaks if deleted: nothing - no requirement traces to it.

- **[MAJOR] Wire-contract fiction fields (requirement-3 survivors)** - `IRemoveResponse.removed` and `IDisposalReport.failed_count` (`types.ts:167-177`) and `IForkResponse.forked_from` (`types.ts:209-212`) are never emitted: the server sends only `{"removed_count"}` (`routes.py:365`) and `{"session_id"}` (`routes.py:562`). Codex even computes `failed_count` (`codex.py:625,633`) and the route discards it. These are exactly the type-promises-the-server-doesn't-keep class that produced DEF-3/DEF-5. Fix: delete the three fields (smallest); if the shortfall count is wanted on screen, that is a new requirement, not a type annotation.

- **[MAJOR] Dead server fork for native-flag, plus a comment that contradicts the client** - `SERVER_MINTED_FORKS` includes `native-flag` and claims "the store mints the id" (`registry.py:43-49`); `BranchHandler` serves it (`routes.py:543-547`) and `ClaudeStore.fork` mints a uuid (`claude.py:910-926`) - but the shipped client mints the fork id itself (`panel.ts:818`) and never POSTs `branch` for native-flag. Two competing minting paths, one dead. Worse, the dead route's `inherit_colour` (`routes.py:556-561`) writes into a native provider's colour store, bypassing `ColoursHandler`'s own refusal (`routes.py:484-486`) - and such an entry renders differently per side, because the client ladder puts user-set above native (`colour.ts:84-88`) while the server puts native above the store (`routes.py:200-203`). Fix: drop `native-flag` from `SERVER_MINTED_FORKS`, delete `ClaudeStore.fork`, correct the comment, and guard `inherit_colour` with the same native check.

- **[MAJOR] Zero-implementor seams (speculative structure)** - four TS hooks have no provider using them: `rowBadges` (`types.ts:308`, consumed `panel.ts:1299-1304`), `menuItems` with its whole `IMenuContribution` type and after-anchoring machinery (`types.ts:287-298,312`; `panel.ts:1689-1703,1769-1774,1803-1807`) and `SHARED_ICONS`, which exists only for it (`icons.ts:106-117`), `derivedColour` (`types.ts:323`, threaded through `colour.ts:91`, `terminals.ts:335,355` - kimi, the one derived-colour provider, explicitly doesn't need it), and `launchExtras` (`types.ts:326`, `terminals.ts:158`). Python side: `SessionStore.on_launched` has zero overrides (`store.py:253-267`, called `routes.py:703-708`) and `Capabilities.has_remote_control` is set four times and read by nothing (`registry.py:73`; `claude.py:1069` et al.). What requirement breaks if deleted: none. Delete the lot - roughly 100 lines of core surface and one wire-adjacent contract method gone.

- **[MAJOR] Four stores, three failure disciplines for the same destructive operation** - kimi calls `dispose_path` unwrapped in `remove`/`cleanup`/`delete_branches` (`kimi.py:498,516,546`) and claude in `cleanup`/`delete_branches` (`claude.py:872,893`): one unwritable file raises OSError → a tornado 500 mid-loop, partial deletion, no colour cleanup, no index prune. Gemini wraps per item and counts (`gemini.py:749-752,785-789`); codex logs each refusal (`codex.py:585-599`). Loggers exist only in codex and core - claude/kimi/gemini degrade in total silence. Fix: unify on gemini's per-item try + count, one `logging.getLogger(__name__)` per provider module. Related: `dispose_path` silently converts a trash request into a permanent delete on any send2trash failure (`store.py:118-125`) while the dialogs promise "recoverable" (`popup.ts:112-114`, `panel.ts:546`) - log it at minimum, and declare `send2trash` in `pyproject.toml` rather than riding jupyter_server's transitive dependency.

- **[MAJOR] Popup bulk delete never confirms** - `popup.ts:344-352` deletes the selection on one click, with a comment explaining a stacked Lumino dialog renders detached. Requirement 6 of record says destructive ops confirm first; the two-step select-then-delete plus live count is a stand-in, not a confirmation. JUDGEMENT: this may be an accepted deviation - surface it to the requirements owner; if confirm is required, the diff-scale remedy is a two-click arming state on the Delete button, not a second dialog.

- **[MINOR] Copy-pasted helpers across providers** - the `--flag value`/`--flag=value` parser exists three times (`claude.py:994-1003`, `kimi.py:156-165`, `gemini.py:327-337`), `_git_branch` twice verbatim (`kimi.py:199-214`, `gemini.py:364-379`), `_now_iso_z` twice (`kimi.py:103-109`, `gemini.py:121-127`), `_process_comm` twice (`routes.py:64-71`, `codex.py:348-354`). Two-plus real call sites each - fold into `core/store.py`.

- **[MINOR] `LegacySource.state_file` follows three conventions** - claude hardcodes `~/.claude/...` ignoring `CLAUDE_CONFIG_DIR` (`claude.py:1073`), codex bakes `codex_home()` at import time so the env read freezes at first import (`codex.py:761` - also a cross-test staleness hazard with the cached module), kimi uses a literal (`kimi.py:746`). Pick one: literal home-relative string, expanded at use.

- **[MINOR] Kimi's mode token is a bare literal, twice** - `"yoloMode"` at `kimi.py:709` and `kimi.py:739`; claude, codex and gemini all name theirs (`claude.py:60`, `codex.py:65`, `gemini.py:107`). Name it once.

- **[MINOR] `routes.py` shim has zero importers and a fictional docstring** - the 11-line shim claims to keep "the import path the server extension has always used"; this repo has one commit and `__init__.py` imports `core.routes` directly. Delete the file.

- **[MINOR] Two registration authorities on the TS side** - runtime registers via the barrel (`providers/index.ts`), but `generate-schema.mjs:83-90` and `test_descriptor_parity.py` glob `lib/providers/` - a module dropped from the barrel still gets a settings toggle and still passes parity. Cheapest fix: the generator asserts its module count equals the barrel's export count.

- **[MINOR] `GeminiStore.__init__(root)` is a dead knob** - zero callers pass it, tests included (`gemini.py:451-458`; tests use `GeminiStore()` + HOME). Delete. Kimi's twin (`kimi.py:256-263`) is test-used but redundant with `KIMI_CODE_HOME` - two override mechanisms where one suffices.

- **[MINOR] Gemini parses an unconfined path from `/proc` cmdline** - `parse_session_id` reads whatever `--session-file` path a process advertises and JSON-parses it (`gemini.py:900-904`). Same-user trust boundary, but requirement 4 wants every path join gated: confine to `root/tmp` before `read_chat_meta`.

- **[MINOR] Log prefix spelled three ways** - `index.ts:30` names `LOG_PREFIX`; `registry.ts:17` and `panel.ts:496` re-spell the literal. Export the one constant.

- **[MINOR] Schema overpromises the approval ladder** - `approvalMode` description says "passed to every launch" (`schema/plugin.json:75`, generated from `gemini.ts:71`), but the payload carries at most one token (`panel.ts:685-700`): the ladder is dropped whenever `yoloMode` fires and when left at default. Reword the description; do not widen the payload.

- **[MINOR] Template leftovers** - `src/__tests__/jupyterlab_ai_code_assistants_extension.spec.ts` is the scaffold's `1+1` placeholder; `package.json` devDependencies carry `@types/react`, `@types/react-addons-linked-state-mixin`, `yjs` (plus the `lib0` resolution) with no React or Yjs anywhere in `src/`. Delete.

## Convention census

| Convention | Occurrences | Canonical |
|---|---|---|
| Fork-id minting | client (`panel.ts:818`), server (`claude.py:926` dead), server-copy stores (`kimi.py:578`, `gemini.py:820`) | client for native-flag, store for server-copy; delete the dead one |
| Branch-extras query param | `include_extras` (routes, claude hook), `include_bg` (panel core - dead) | `include_extras` |
| Disposal failure handling | raise (claude/kimi), per-item skip+count (gemini), CLI+log (codex) | gemini's, plus logging |
| Python logging | `getLogger(__name__)` (registry, migrate, codex) - absent in claude/kimi/gemini | `getLogger(__name__)` everywhere degradation is swallowed |
| Mode-token constants | named (claude/codex/gemini), bare literal ×2 (kimi) | named constant |
| Store root override | env-at-call (claude/codex/kimi), HOME (gemini), plus ctor params (kimi used-by-tests, gemini dead) | env-at-call; drop the ctor params |
| Wire colour spelling | British on the wire (routes enforces), American inside claude store (discarded) | British end to end |
| Colour precedence, native provider | server: native wins (`routes.py:200-203`); client: user-set wins (`colour.ts:84-88`) | server's - make writes impossible instead of divergently interpreted |

## What is already consistent

- **Core neutrality genuinely holds, on both sides** - no assistant name in `src/core/` or python `core/` outside comments, enforced by a source-scanning test (`core-neutrality.spec.ts`) plus a cross-runtime descriptor parity test; adding gemini demonstrably touched no core file
- **Path-traversal gating is uniform and layered** - `is_safe_segment` plus per-store charset regex plus `resolve().relative_to()` at every encoded-path join (`claude.py:525-540`, `kimi.py:267-286`, `gemini.py:462-482`), and pins are charset-gated before any caller sees them (`state.py:99-115`)
- **The three route gates run identically for every provider verb** (`routes.py:209-226`), state files are strictly per-provider, and the unsafe launch modes are all off by default, under each assistant's own terminology, mapped one token to one flag
- **Atomic writes with fsync everywhere extension state is persisted** (`store.py:63-76`), and the frontend's fire-and-forget promises are consistently caught - both `dialog.launch()` teardown rejections are handled where they arise (`terminals.ts:45`, `popup.ts:422`)

Not logging to journal: review-only sweep, no artefacts modified.
