projmux 0.14.0 → 0.14.2

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.
@@ -40,7 +40,9 @@
40
40
  - `make test`: Codex native control plane Phase 2 binds `thread/start` plus its single optional `turn/start`, or `thread/resume` with zero thread creation, to the exact Agent uid, managed Pane uid, and activation generation. App-server identity is additive schema-2 state on the Pane activation while the durable Agent `sessionRef` remains the conversation pointer; native Pane describe rows expose binding source, generation, thread, and current turn. Fake protocol and materialization tables pin one-prompt create, stored-thread resume, unavailable/unsupported fallback through the unchanged CLI/hook contract, indeterminate post-create refusal without a second lane, and zero-write rejection for another Pane, generation, or thread. Exact late hooks may refine only the current turn; native-bound hooks fail closed before tmux remap or Registry writes, while legacy/unmanaged hooks and bounded late acknowledgement retain their previous behavior.
41
41
  - `make test`: Codex empty-prompt create fallback keeps fresh native identity limited to immediately attachable input. `TestStartDefaultThreadEmptyAndPromptedRequestCounts` pins zero proxy/open/thread/turn requests plus safe fallback for empty input and exactly one thread plus turn for prompted input. `TestEmptyPromptCodexCreateUsesOnePlainCLILaneAndNoNativeBinding` and `TestEmptyPromptCodexSplitProducersKeepOnePlainCLILane` pin one unchanged plain CLI launch across canonical, provider-shortcut, and split producers; `TestEmptyPromptCodexFallbackFirstInputConvergesSessionRefAndRouting` pins the first real hook input converging the durable Agent `sessionRef` and live Pane routing. `TestCodexFanOutKeepsCurrentPlainCLILaneWithoutNativeCreate` and the prompted/stored/picker/post-mutation tests keep fan-out, exact resume, and fail-closed parity.
42
42
  - `make test` / `make test-integration` / `make test-e2e`: Codex native control plane Phase 4 makes `thread/list` the Resume Picker catalog source and keeps the exact-id `thread/read(includeTurns=false)` validation seam for Session State. Unit/fake-server goldens pin explicit `cli`/`vscode`/`appServer` source kinds, non-archived and recency filters, opaque multi-page cursor progression, repeated/empty/exhausted cursor rejection, cwd depth, exact thread id, provider name or short-id-only fallback, branch, closed runtime status, deterministic ordering/dedupe, and typed source/confidence/fallback reason. A native failure discards every partial native row before exactly one rollout scan; native-empty never merges rollout history. Picker-to-create tests require native rows to call `Resume` with the exact listed id and rollout rows to stay on the current CLI lane. Session State tests require a bound session id or persisted resume id to win without `thread/read`, a thread-only candidate to pass one exact-id read before storage, a mismatched read to write zero, and the default validator to remain probe-only so autosave never starts the shared daemon. Provider parity and negative audits keep Claude/Antigravity and snapshot recipes unchanged and keep native title inference, prompt preview, transcript turns, and XML unwrap at zero. The installed read-only catalog smoke is opt-in through `PROJMUX_CODEX_CATALOG_SMOKE_ROOT=/tmp/<isolated-root>` with matching contained `CODEX_HOME`; it strips inherited tmux identity, ensure-readies once, lists metadata, and validates the first listed exact id without turns or thread mutation.
43
- - `make test` / `make test-integration` / `make test-e2e`: Resume Picker stable-summary Phase 0 settles Codex, Claude, and Antigravity through provider-local bounded summary adapters before the picker opens, then performs one global dedupe/newest-first sort/cap and renders that immutable list from the first input frame through close. `ResumeSummary` contains only provider, exact resume id, timestamps, compact label, branch, depth-gated relative cwd, and source reference; turns, runtime state, confidence/reason explanation, pagination detail, and preview/transcript bytes cannot enter its row/search projection. Codex reads at most one native summary page under a 300 ms budget while its bounded rollout fallback walks and parses concurrently with context cancellation; native success (including native-empty) keeps authority, while native failure/timeout selects the already-settled fallback or an available empty/partial result and drops every late native result for the current invocation. At the unchanged 450 ms population cutoff, a bounded cancellation handoff collects a matching fallback partial even when its buffered send follows `ctx.Done`; that handoff remains inside the `<500ms` first-frame contract and does not extend discovery. Envelope expiry never renders `unavailable`, while a genuine provider error still does. Six provider completion permutations, exact 0/50/500 ms plus blocked-native fixtures, a large-rollout cutoff race, and an empty Codex store pin one settled first frame under 500 ms, exact native/fallback/Claude/Antigravity id and source handoff, and invariant row value/order hashes. Phase 1 removes every `status\t<provider>` item and projects each settled provider as the first line of the fixed lower footer with distinct available, empty, fallback, and unavailable states; AI Resume passes zero upper `ChromeBands`. Row/status tables require every item value to be `new` or an exact parseable resume; native picker tests pin provider state outside query/cursor/Enter cardinality and the exact lower ordering `provider line → shown count content-free MoreNotLoaded`, including independent EN/KO clipping at 80 columns. Turn enrichment and page continuation do not write the current list. Phase 2 routes focus through typed `SelectionDetail` updates whose `Items`, Header/Footer, and `MoreNotLoaded` set flags stay zero; the renderer reserves and pads the independent down detail dock from the first frame so empty/help, loading, ready, unavailable, clipped, and scrolled content cannot move the list or footer. The visible provider-neutral row grammar is `relative age → provider badge → branch → optional relative cwd → conversation title` under one width policy; turns, row-local `[fallback]`, and runtime/source/confidence/reason explanations live only in the invocation-local detail projection, while exact source remains in the frozen SearchKey and resume routing. Picker geometry goldens and a 20-row round-trip cursor sweep pin identical 80x24/120x40 origins, limits, dividers, footer positions, query bytes, and item values; app race/privacy tests pin latest-focus-wins cancellation, one read per exact `(provider, id, updatedAt)` key, preview failure isolation, and zero preview-byte persistence.
43
+ - `make test` / `make test-integration` / `make test-e2e`: Resume Picker stable-summary Phase 0 settles Codex, Claude, and Antigravity through provider-local bounded summary adapters before the picker opens, then performs one global dedupe/newest-first sort/cap and renders that immutable list from the first input frame through close. `ResumeSummary` contains only provider, exact resume id, timestamps, compact label, branch, depth-gated relative cwd, and source reference; turns, runtime state, confidence/reason explanation, pagination detail, and preview/transcript bytes cannot enter its row/search projection. Codex reads at most one native summary page under a 300 ms budget while its bounded rollout fallback walks and parses concurrently with context cancellation; native success (including native-empty) keeps authority, while native failure/timeout selects the already-settled fallback or an available empty/partial result and drops every late native result for the current invocation. At the unchanged 450 ms population cutoff, a bounded cancellation handoff collects a matching fallback partial even when its buffered send follows `ctx.Done`; that handoff remains inside the `<500ms` first-frame contract and does not extend discovery. Six provider completion permutations, exact 0/50/500 ms plus blocked-native fixtures, a large-rollout cutoff race, and an empty Codex store pin one settled first frame under 500 ms, exact native/fallback/Claude/Antigravity id and source handoff, and invariant row value/order hashes. The corrective provider projection now maps each provider to pre-global-cap discovered count (including zero), search failure, or Settings-disabled, with localized EN/KO footer text and no native/rollout/fallback transport vocabulary; disabled providers perform no discovery read and enter no selectable row. Row/status tables require every item value to be `new` or an exact parseable resume; native picker tests pin provider state outside query/cursor/Enter cardinality and the exact lower ordering `provider line → shown count · content-free MoreNotLoaded` on one localized status line, including independent EN/KO clipping at 80 columns. The recovered footer row extends only the AI Resume down `SelectionDetail` dock by exactly one row, leaving the popup, provider row, and pre-compaction MoreNotLoaded list limit fixed; generic pickers and `MoreNotLoaded=false` retain their prior budgets. Turn enrichment and page continuation do not write the current list. Focus routes through typed `SelectionDetail` updates whose `Items`, Header/Footer, and `MoreNotLoaded` set flags stay zero; the renderer reserves and pads the independent down detail dock from the first frame so empty/help, loading, ready, unavailable, clipped, and scrolled content cannot move the list or footer. The visible provider-neutral row grammar is `relative age → provider badge → branch → optional relative cwd → conversation title` under one width policy. Its label authority is exact sessionRef-bound Agent `metadata.displayName` → provider-owned non-ID title → exact Agent topic → exact stable name → localized `Untitled/제목 없음 · …last4`; full provider/conversation identity stays in Value, SearchKey, routing, and selected detail, while native/rollout source and fallback reason stay only in selected detail. `TestNativeAIResumeTrustFramesLocaleAndSizeGolden` pins actual 80x24/120x40 EN/KO frame rows and widths; the AI Resume-only detail limiter requires three real nonblank preview lines before overflow, uses the recovered row for one more real content line when continuation is present, and cannot count post-preview metadata as content. Geometry goldens and a 20-row round-trip cursor sweep pin identical origins, limits, dividers, footer positions, query bytes, and item values; app race/privacy tests pin latest-focus-wins cancellation, one read per exact `(provider, id, updatedAt)` key, preview failure isolation, and zero preview-byte persistence.
44
+ - Visibility clarification: native/rollout source is not rendered in a row or footer; it remains in the frozen hidden SearchKey for the existing routing-search contract and is visibly inspectable only in selected detail. Fallback reason is absent from both rows and SearchKey and is visible only in selected detail.
45
+ - Label-source clarification: Codex rollout and Claude transcript labels are prompt-derived by the existing summary scanners, so they are not provider-owned title authority and remain absent from both primary labels and SearchKey. No extra transcript read is introduced; exact Agent metadata or the localized untitled suffix supplies the label instead.
44
46
  - `make test` / `make test-integration` / `make test-install-smoke`: App socket marker migration Phase 0 keeps ordinary mutation fail-closed on app-owned pre-0.13 partial markers with a typed exact `config apply --socket <name>` recovery and zero writes; pins config apply's `-L` to absolute `-S` path/PID, pre-source app ownership and missing-or-exact logical policy, typed route-marker write plan, post-write reobservation, and refusal of foreign/forged/mismatch/alias/PID-drift inputs; projects missing/mismatch/unreadable Doctor findings identically in text/JSON without mutation; and recovers an installed live legacy session while preserving its exact server generation.
45
47
  - `make test` / `make test-install-smoke` / `make test-e2e-update`: App socket marker migration Phase 1 makes npm, Go, GitHub Release, and source `make install` converge the exact live route before binary publication and verify it with the published binary afterward. Stage-table tests require an exact eventual `--bin` target, stop publication after pre-apply failure, make source/publication/post-verify failure non-success with the Phase 0 exact recovery, and keep `--no-apply` pre-apply-free with zero live access plus an explicit apply-required state. The isolated install fixture pauses the atomic move and runs legacy/built/installed shell and attach consumers before/during/after while preserving the exact v0.12.2-modeled socket path, PID, and sessions without exposing a raw missing-marker error.
46
48
  - `make fmt`: repository formatting for Go, shell snippets, and generated docs where applicable.
@@ -71,7 +73,7 @@
71
73
  - `make test` / `make test-integration` / `make test-e2e`: rename/rebind transport convergence keeps Registry uid/name/root authority and writes only the corresponding exact live field after the Registry transaction: Project name → `@projmux_project_name` without session rename, Window name → `@projmux_window_name` without `displayName`/`window_name`, Pane name → `@projmux_pane_label` without `pane_title`, and Project root → `@projmux_project_path` without filesystem or uid change. Public `rename agent` adds the exact-one Agent cardinality/aliases and changes only Window-scoped `metadata.name` plus its reservation, preserving topic annotations, provider, lifecycle linkage, and managed Pane metadata with zero tmux writes. Unit tables pin field-specific call logs, offline and inventory-unavailable later convergence, duplicate UID refusal, and post-commit live-write failures that exit nonzero with the public reconcile retry while retaining durable Registry drift. Immediate projection exists only for an inherited absolute `$TMUX` socket, routes every lookup/write through exact `-S`, targets stable `$N`/`@N`/`%N` handles, and performs zero tmux calls outside tmux or for a malformed relative inherited socket. Public reconcile treats a known unique Project UID as authoritative over the stale old path after rebind, plans/guards only the path-anchor write, repeats no-op, and keeps unknown/duplicate claims fail-closed. The isolated real-tmux smoke uses inherited-environment removal plus a validated exact socket path, asserts session/window/pane runtime names and a second socket remain unchanged, exercises offline later reconcile and injected option-write failure/retry, and cleans only the contained sockets.
72
74
  - `make test` / `make test-e2e`: canonical resource create lifecycle reentrancy coverage pins the private, session-scoped create lease passed atomically through `new-session -e`, exact-socket/session lifecycle inspection, live-only deferral, stale-lease cleanup, and a second in-transaction reconciliation after explicit mirrors. Standalone `after-new-window`/`after-split-window` hooks remain synchronous. Window/Pane materialization preserves combined tmux output on non-zero exit, accepts only one exact newly observed `@N`/`%N` handle for ownership-checked reverse rollback, emits zero stdout on failure, keeps the registry byte-identical, reports ambiguous or ownership-mismatched residual drift, and never removes an unrelated object. The isolated tmux smoke exercises `--create-window`, exact returned pane IDs, registry/runtime uid agreement, and forced synchronous-hook failure cleanup; the existing Agent-create smoke covers canonical creation in a live managed Window with its stub provider.
73
75
  - `make test` / `make test-e2e`: create-window foreign-session attribution hardens the same lifecycle boundary with an atomic `Created` plus `$N`/`@N`/`%N` new-session result and exact owner verification. Before the first reconcile import, lease, mirror, or Registry write, create now requires the selected live session to carry the registered Project's exact uid and canonical root with no server-wide duplicate uid/root claimant; the check repeats against the reconciler's fresh inventory and immediately before an inner create, so outer-miss/inner-hit, first-use discovery, blank/foreign same-name, and duplicate-other-session races fail closed. New Windows accept only the exact composite `@N`/`%N` pair that is the sole global before/after addition under the stable `$N`; wrong, pre-existing, wrong-session, missing, or ambiguous handles remain unclaimed with exact residual diagnostics and byte-identical Registry/pre-existing options. Claim injection tests pin uid claim → rollback ledger → full mirror ordering for Session/Window/Pane, ownership-checked rollback, and chained name/uid selector refusal. The isolated real-tmux smoke preserves blank and foreign same-name sessions byte for byte on the exact contained socket, then runs eight concurrent same-name `--create-window` racers and requires one Window, nine unique Pane uid mirrors, exact returned `%N` handles, and unchanged pre-existing bindings.
74
- - `make test` / `make test-e2e`: Window resource deletion converges the Registry desired-topology cascade and an exact live tmux binding when one exists. Non-implicit targets (explicit references or `--all`) inventory only `-L projmux`: zero exact `@projmux_window_uid` mirrors, including a typed absent-server result, is a valid Registry-only offline cascade, while one exact mirror is killed under the owning Project's Registry-authoritative session. Duplicate, foreign, stale-session, generic inventory-failure, and preflight-to-execution race states remain fail-closed; selector-omitted active deletion still requires its exact live mirror and refuses an implicit foreign-caller socket. The plan and confirmation include the exact `@N` kill plus implicit Project-session end when present, while an offline plan explicitly reports that no tmux Window will be killed; `--dry-run` renders the complete resource/live-or-offline cascade with zero Registry transactions and zero tmux writes. Execution revalidates both resource and live signatures under the Registry lock, preserves the Project, sibling Windows, unrelated sockets, and Registry bytes on pre-mutation failure, and reports exact removed `@N`/session plus retained Registry UIDs if the store commit fails. Repeat deletion of an already-removed offline UID is a no-match with zero writes. A self-target commits and flushes the Registry result before queueing a shell-quoted exact-socket kill. The isolated real-tmux smoke covers live sibling/last-Window/self-target parity plus an offline Window with shell and Agent descendants, byte-identical offline dry-run, exact Registry-only cascade, repeat no-match, and foreign-socket preservation.
76
+ - `make test` / `make test-e2e`: Window resource deletion converges the Registry desired-topology cascade and an exact live tmux binding when one exists. Non-implicit targets (explicit references or `--all`) inventory only `-L projmux`: zero exact `@projmux_window_uid` mirrors, including a typed absent-server result, is a valid Registry-only offline cascade, while one exact mirror is killed under the owning Project's Registry-authoritative session. Duplicate, foreign, stale-session, generic inventory-failure, and preflight-to-execution race states remain fail-closed; selector-omitted active deletion still requires its exact live mirror and refuses an implicit foreign-caller socket. The plan and confirmation include the exact `@N` kill plus implicit Project-session end when present, while an offline plan explicitly reports that no tmux Window will be killed; `--dry-run` renders the complete resource/live-or-offline cascade with zero Registry transactions and zero tmux writes. Execution revalidates both resource and live signatures under the Registry lock, preserves the Project, sibling Windows, unrelated sockets, and Registry bytes on pre-mutation failure, and reports exact removed `@N`/session plus retained Registry UIDs if the store commit fails. Repeat deletion of an already-removed offline UID is a no-match with zero writes. A self-target commits and flushes the Registry result before queueing a shell-quoted exact-socket kill. The isolated real-tmux smoke covers live sibling/last-Window/self-target parity plus an offline Window with shell and Agent descendants, byte-identical offline dry-run, exact Registry-only cascade, repeat no-match, and foreign-socket preservation. Before every Registry byte-identity baseline, the smoke requires ten unchanged samples with no exact-socket hook shell or controller writer while preserving retry-exhausted startup evidence; a deterministic delayed pre-exec shell proves the observer cannot return during the generated hook's sleep, and the same boundary covers the final kill-server/no-server transition.
75
77
  - `make test` / `make test-e2e`: Pane and Agent live-binding deletion extends the same fail-closed boundary to `delete pane|agent`. One exact `-L projmux` Pane inventory joins each Registry Pane uid through its owning Agent/Window/Project graph to the exact `@projmux_pane_uid`, `@projmux_window_uid`, optional session Project uid, and Registry-authoritative session projection; missing, duplicate, foreign-Window/Project, stale-session, and foreign-caller-socket rows cause zero writes. Agent deletion derives its runtime targets only from its Registry-owned managed Panes—never process content—and removes the Agent, managed Pane resources, and exact `%N` objects while preserving sibling Panes, Windows, and owner graph; deleting the current managed Pane alone preserves the Agent as `Offline` with a cleared `paneRef`. Dry-run, confirmation, and result output list every exact Pane plus implicit last-Pane Window and last-Window session cascades. Execution revalidates both resource and live signatures under the Registry lock, kills external targets before commit, and reports already-killed exact Pane/window/session/Pane-uid tuples with retained Registry UIDs on partial tmux or store failure. A caller-containing plan writes the complete deleted-Pane tombstone set under the Registry lock before commit; a partial tombstone or store failure restores every reachable marker while the Registry identities still exist, and any incomplete rollback keeps those Registry owners authoritative. After commit and flush, exact-socket background kills are queued only from revalidated tombstones, so any unqueued survivor remains explicitly tombstoned and retryable. Both anchored import and repair matching refuse that marker, preventing a failed delayed kill from re-importing a deleted Pane as an orphan. The isolated smoke strips inherited `TMUX`/`TMUX_PANE`, validates both socket paths below its dedicated root, covers sibling, last-Pane, managed-Pane-to-Offline, Agent cascade, anti-reimport, and second-socket preservation, and cleans up only the validated exact sockets.
76
78
  - `make test-e2e`: Implicit active target selector Phase 3 runs installed-shape apply and lifecycle convergence against real tmux servers under a dedicated `TMUX_TMPDIR`, after stripping inherited `TMUX`/`TMUX_PANE`. It verifies the real `#{socket_path}` stays below the smoke root; deleted managed Window/Pane uid options return to their original registry identities after exact-socket apply; a newly created Window and a split Pane carry bindings before the creating tmux command returns and before an implicit read; repeat apply/lifecycle leaves registry bytes and bindings unchanged; a second socket remains untouched; and cleanup kills only the validated exact sockets.
77
79
  - `make test`: Implicit active target selector Phase 2 scopes selector-omitted `get windows|panes|agents` inside tmux to the Project derived from the active Window uid mirror and registry owner chain through the selector engine's single `windowScope` seam. Tables pin inside/outside/missing-binding behavior, explicit `--project` parity, registry-wide `--all-projects`, read-route rejection of bare `--all`, unchanged global `get projects`, and unchanged notification/snapshot/destructive boundaries; the generated CLI reference documents the 0.x feature-minor default narrowing and its same-release escape hatch.
@@ -88,8 +90,10 @@
88
90
  - `make test` / `make test-e2e`: Registry–tmux Phase 6 Agent interaction and launch-readiness coverage pins exact active/explicit Agent topic and semantic-status resolution, Registry-first commit plus exact live projection and public reconcile retry, Offline topic resume mirroring, lifecycle-separated closed interaction kinds with restart/stale/Offline current-`unknown` invalidation, and Agent-owned Pane plus shell-manual-attention Window badge priority without durable glyph/color/aggregate state. Provider hooks coalesce session-ref and semantic observations into one Registry transaction when both occur and never synthesize an Agent for a shell Pane. Initial-prompt create distinguishes `Running` resource creation from bounded provider activation, returns an exact Agent/Pane retry-and-cleanup diagnostic when unconfirmed, and proves prompt non-persistence plus zero pane capture. Provider-neutral `--cwd`/repeatable `--add-dir` validation rejects nonexistent, traversal/symlink escape, unauthorized sibling, duplicate, and provider-unsupported paths before runtime mutation; preserves the Window's Project owner; stores effective Agent/Pane workspace; and forwards only caller-explicit additional roots through Codex/Claude argv. The isolated real-tmux slice uses exact primary/foreign sockets to cover immediate topic/status projection, Offline topic-to-resume mirroring, committed projection failure plus public reconcile retry/no-op, raw title/topic non-identity, foreign-socket containment, and explicit cross-Project workspace with unchanged owner Project. Compatibility `ai topic|status` keeps its stdout/stderr/exit surface while forwarding managed Pane changes through Agent authority, and notify queue acknowledgement/TTL/severity plus shell manual attention remain unchanged.
89
91
  - `make test`: Declarative contract stabilization Phase 16 closes Agent launch outcomes with `TestAgentLaunchOutcomeTableIsClosedAndPrintable`, `TestDelayedProviderActivationReturnsValidatedPaneHandle`, `TestActivationAcknowledgementWinsTheTimeoutRace`, `TestLateActivationRefinesOnlyTheSameBindingGeneration`, `TestRoadmapWorkerLaunchUsesExactOwnerPane`, and `TestWindowOnlyCreateNeverGuessesAroundADeadPrimaryAnchor`. The four printable rows are pre-runtime failure, created+acknowledged, created+unconfirmed, and delayed acknowledgement. Startup readiness and acknowledgement have independent five-second bounds: exact Codex/Claude `SessionStart` evidence keeps activation `pending` and opens the acknowledgement window, while only exact `UserPromptSubmit` evidence acknowledges, so three seconds of startup plus a 2.1-second acknowledgement succeeds with rc 0 and one `%N` line without increasing the acknowledgement deadline. Never-start stops at five seconds and ready-but-never-ack remains bounded by ten seconds overall. Activation transitions are monotonic (`pending -> unconfirmed -> acknowledged`, with direct `pending -> acknowledged`), timeout cannot downgrade acknowledged, and startup or acknowledgement hooks must carry the exact Agent-owned Pane uid and supervisor-issued generation while the Registry still records the same `%N` runtime handle. Exact Project/Window/Pane selectors anchor roadmap-style worker creation on the named current owner Pane, invalid/cross-Window/foreign Pane selectors write nothing, and Window-only creation keeps refusing a dead primary instead of guessing an alternate live Pane. Unconfirmed creation remains nonzero with no stdout while preserving the Agent/Pane and reporting exact retry/delete remediation; prompt/provider free-form text remains absent from Registry.
90
92
  - `make docs`: regenerates `docs/cli.md` from the `internal/cli` command manifest through `go run ./internal/tools/gendocs`. The render goes to stdout and is captured through a temp file, so a failed generator cannot truncate the checked-in page. Run it whenever a route, summary, usage synopsis, output mode, or field projection changes; `make test` fails until the checked-in page matches.
93
+ - `make test`: the release workflow contract requires the root release-please package to keep `draft: true` paired with `force-tag-creation: true`, so the tag exists before the action computes its next release PR; keeps the release-please PAT wired so that tag push can dispatch workflows; keeps `release.yml` listening for pushed `v*` tags; and rejects the retired post-action GitHub ref creation workaround.
91
94
  - `make test`: CLI information architecture v2 Phase 8 generated CLI reference: `docs/cli.md` is rendered from the command tree in `internal/cli/catalog.go` -- the same manifest the binary renders `projmux help` from -- and the hand-maintained prose it used to hold moved to `docs/cli-guide.md`, which the generated page, `README.md`, and `README-ko.md` all link. The drift gate compares the checked-in page byte for byte against a fresh render and names `make docs` in its failure, so the CI `Unit Tests` job catches a stale page; a determinism test renders twice and rejects an embedded repository or temp path, which is what makes regeneration on an unchanged tree a guaranteed no-op. Coverage is a two-way diff rather than a count: every public route and sub-route in the tree has a section and every section in the document is a real node, with a non-vacuity guard that both a canonical and a shortcut route exist to cover. The internal boundary is held on the published page as well as in the primary listing -- no hidden route gets a section, and no `Canonical spelling:` line may point at an executable path absent from the manifest. The generator reads the command tree and never `canonical.go`; canonical-only summaries are therefore checked as an explicit divergent set rather than leaking into command help. Advertisement-honesty checks keep registry reads aligned with the outputs their handlers implement and keep restore limited to its shipped dry-run preview contract.
92
95
  - `make test`: Agent provider session binding Phase 0 persists a validated provider-discriminated `sessionRef` for Claude, Codex, and Antigravity without coupling lifecycle phase or Pane ownership to conversation identity. Metadata/store tests cover structural refusal, deep clones, pre-field byte-compatible reads, exact provider folding, and durable observation timestamps. `internal/app` drives canonical `internal agent-hook ingest` payloads for all providers against an in-memory Registry and proves correct provider-member population, option preservation, zero-write negative cases, conversation retention after Pane release, and stable `describe agent`, `get agents`, and `agent resume` behavior.
96
+ - `make test` / `make test-integration` / `make test-e2e`: Picker-created Agent session continuity Phase 0 records the resume picker's already-normalized provider and exact conversation in the new Agent's existing durable `status.sessionRef` inside the canonical create transaction, before success and without waiting for a provider hook. Hook-0 success, same-conversation and cross-provider hook no-write behavior, Claude/Codex/Antigravity provider shape, ordinary fresh nil-ref behavior, exact-resume-only Continue (`PlanAgentResume` once and `PlanAgentLaunch` zero), visible resume-preparation refusal, and activation/commit rollback with no Agent, Pane, ref, or runtime orphan are pinned. The picker still stores no transcript, cache, turn, or new schema field; provider hooks retain their existing last-observation authority, and neither picker selection nor Continue silently falls back to a fresh conversation.
93
97
  - `make test`: Implicit active target selector Phase 0 active target for the read and rename verbs: inside tmux, a completely empty selector on `get pane`, `describe project|window|pane|agent`, `rename project|window|pane`, and `rebind project` resolves the active tmux target instead of the whole registry, through one injectable seam in the registry route layer that every one of those routes shares. `internal/app` proves the seam rather than the outcome alone: inside-tmux detection is a table over `$TMUX`/`$TMUX_PANE` that costs **zero** tmux calls whenever the process is not a client — a bare `display-message` from outside a client still answers for the most-recently-used session, so "does tmux answer" is not the test — and inside a client the recorded argv is exactly `display-message -p -t %46 -F #{@projmux_pane_uid}` / `#{@projmux_window_uid}`, read lazily so a Pane or Agent route never queries the window option and a Window or Project route never queries the pane option. Ancestors come from registry `ownerRef`, not from more tmux options: the Project is the owner of the active Window and the Agent is the owner of the active Pane, and the session-scoped `@projmux_project_uid` is never consulted because it is measurably empty on live sessions. The refusal contract is pinned as exact strings for all eight shapes (pane/window with no mirror, pane/window mirroring a uid the registry does not hold, a shell Pane with no owning Agent, a window with no registered owning Project), each asserted to be a usage error carrying neither the `want exactly one` wording nor a candidate listing, and at route level to leave 0 bytes on stdout, 0 transactions, 0 writes, and a byte-identical registry snapshot. Acceptance is measured on call counts as well as output: every explicit form — a positional ref, a `uid:` ref, a `--project`/`--window` scope, a `--selector` label, and an unmatched ref — is never handed an implicit *target*, which is proven by the outcome rather than by the count alone, since the seam resolves a resource none of those invocations address; the recorded consultation count is 0 wherever an explicit `--project` fixes the scope and 1 where the Phase 10 namespace derives it, and outside tmux all seven adopting invocations keep their byte-identical `resolve <kind>: the current selector matched N ..., want exactly one` summary and exit 2. The maintained cardinality adoption record gains a nine-route table that runs the same argv three ways against the same declared cell — mapped target satisfies it, outside tmux still violates it, unmapped target refuses with the fallback's own message — so the fallback cannot be mistaken for a relaxed exact-one. `get pane --current` is proven not to be a duplicate and is not deprecated: `-o cwd` stays byte-identical with exactly one live `#{pane_current_path}` query and zero fallback consultations, and both of its usage errors keep their exact text. `internal/core/selector` records why the fallback has no value-token spelling: `current`, `active`, and four sibling candidates all pass `ValidateName` and parse as bare `metadata.name` occurrences, so a sentinel would shadow a real resource, while `.` and `@` remain rejected as names and are therefore the documented escape hatch for a future explicit spelling. `delete` is unchanged by construction — the seam is nil on every route that does not opt in — and the rendering the provider-session-ref change added to the same file is re-pinned: `resourceSummary` keeps its `kind`/`registry` parameters and the Agent `session=<provider>:<id>` suffix, which no other kind grows.
94
98
  - `make test`: Implicit active target selector Phase 1 destructive verb containment: an omitted selector on `delete window|pane|agent` no longer means the whole registry. The measured baseline is that it did, everywhere -- `delete pane --dry-run` with nothing else on the argv planned all 5 fixture Panes, and a 1..N cell is satisfied by that, so no cardinality rule was ever going to catch it. The route now sets the shared active-target seam plus a `wholeSetFlag`, and `resolveQueryFlags.resolve` refuses the empty selector outright whenever the fallback resolved nothing, before `selector.Enforce` is reached. Acceptance is measured per kind on the plan rather than on the exit code, because the pre-change whole-registry fan-out also exited 0 under `--dry-run`: inside tmux the plan header is exactly `would delete 1 pane and 0 descendant resources` / `1 window and 4` / `1 agent and 1`, the one target line is asserted verbatim, and every uid outside the plan is asserted absent from the output. Outside tmux all three kinds refuse with `resolve <kind>: no selector was given and no active tmux target resolved, so nothing was selected; ...` at exit 2 with 0 bytes on stdout, 0 transactions, 0 writes, and a byte-identical registry snapshot -- including under `--yes`, which answers the confirmation and has never been allowed to answer which resources, and including for `delete window` and `delete agent` whose baselines were 33-line and 11-line whole-registry plans. The refusal is asserted to be neither the 1..N cardinality error nor the seam's unmapped-target refusal, so all three failures stay distinguishable; an unmapped active target keeps the seam's own message and is asserted never to advise `--all`. The whole-registry fan-out survives only under an explicit `--all`, which is a flag and not a value token because `ValidateName` leaves `all`, `current`, and `active` legal resource names that a sentinel would shadow; it is proven byte-identical in and out of tmux with the active-target seam consulted **zero** times, and it is refused next to any selector. Every string the route prints says "in the registry" rather than a bare "all", pinning which scope this `--all` is while projmux has only one. The confirmation carve-out for an exact-one leaf Pane is narrowed by exactly one condition -- the operator has to have named the target -- so the implicit and `--all` deletes always prompt while `delete pane log --project alpha --window main` still does not. Regression is measured as identity, not resemblance: eight explicit argv shapes (positional ref, `uid:` ref, multi-ref fan-out, `--project`/`--window` scope, `--selector` label, an unmatched ref) each run twice against the same fixture, once with the active target pointing at a different resource and once outside tmux, and both runs must produce identical stdout and identical error text with **zero** seam consultations, with `resolve pane: --pane zzz-nonexistent matched no panes, want at least one` pinned verbatim. The declared `<delete, kind>` cells stay 1..N deliberately: cardinality answers how many targets a resolved selector may address, and the containment changes which resources an empty selector resolves in the first place.
95
99
  - `make test`: Agent provider session binding Phase 1 resume materialization: `agent resume <ref>` stops at a stub no longer and rebinds the existing Agent, building the provider's *resume* argv from `status.sessionRef`, splitting a detached managed Pane on the target Window's `spec.defaultShellPaneRef` through the create routes' own transaction, ledger, rollback, and materializer, and attaching it to the Agent that was already there -- the success test asserts the Agent count is unchanged, the `metadata.uid` and `metadata.name` are the ones that were already stored, `status.phase` is `Running`, `status.paneRef` resolves to a Pane the Agent owns with `role: agent`, the stored conversation pointer is unchanged, exactly one registry transaction commits, the Settings enabled-agents gate ran exactly once, and the one `split-window` argv actually issued carries the stored conversation id, with no client-moving command anywhere. The no-fallback contract is measured as a count rather than as an error string: a twelve-row refusal table -- an Agent with no `sessionRef` at all, a conversation the provider's resume builder rejects, a missing provider binary, a provider disabled in Settings, a `Running` Agent, an Agent that still owns a Pane, a ref contradicting `spec.provider`, a `MissingRoot` Project, a Window with no `spec.defaultShellPaneRef`, an ambiguous reference, and a no-match -- each requires **zero** new Agents, **zero** registry transactions and writes, **zero** `split-window` calls, zero tmux calls of any kind, zero managed-pane bindings, and a byte-identical registry and tmux snapshot; a separate rolled-back-runtime test injects a failing split and requires zero commits, the Agent still `Offline` with no Pane, and exactly one launch attempt which was the resume rather than a fresh start; and a reflection guard requires the resume launch seam not to satisfy the create launch seam, not to expose `PlanAgentLaunch`, and to take the conversation id as a required input, so no fresh-start argv is reachable from the route at all. The four Phase 0 handoff items are adjudicated with tests: the tie-break is that **the conversation is never a selector** -- three Agents sharing one conversation are run across three registry permutations and the referenced Agent is always the one rebound, the siblings never change phase or pointer, and the uid-ordered stderr disclosure and the launched argv are byte-identical every time; `observedAt` is **not** a gate, proven by two refs identical except for an `observedAt` a decade apart producing identical streams and identical argv, and by the stored value surviving the rebind unrewritten; Codex's turn id stays unstored and unimplemented, pinned by a per-provider resume-argv table requiring exactly three elements with no turn slot (`claude --resume <id>`, `codex resume <id>`, `agy --conversation <uuid>`) plus four unusable-id refusals; and an Agent with **no ref at all** fails as a non-usage state error naming `create agent` rather than performing it. A permanent-exclusion guard runs the plan twice with a Claude transcript path pointing at a nonexistent file and at nothing, requiring identical results, so the transcript is proven to be a stored path and never a read. `agent resume` remains a read-only refusal on every ineligible path, the `Running` refusal stays byte-identical with and without a stored ref because the phase gate runs before the ref is read, and the wiring test pins that the route shares the create command's runtime instance rather than a second materializer.
@@ -170,6 +174,7 @@
170
174
  - `make test` also covers statusbar notify width fallbacks that preserve normal INFO/WARN/CRIT badges at readable widths, use dotless clipped text/count at very narrow widths, and keep hard truncation closed with `#[default]`.
171
175
  - `make test` also covers Usage popup MODEL/WINDOW budget projection for percent-only, absolute-count, and unsupported rows; untruncated Antigravity labels; bounded rune-safe future-provider ellipsis; ANSI visible-width alignment; popup-only inactive named-quota filtering; and retained inactive quota metadata in text/JSON/cache surfaces.
172
176
  - `make test` also covers the Globalization Phase 4 Settings and popup guidance migration: `en-US`/`ko-KR` Settings root title/chips/rows/footer rendering, native picker search/empty/footer rendering without overflow, Settings row padding by terminal cell width, shell welcome guide catalog rendering, and long Korean ANSI/tmux-styled guide truncation safety while preserving key/command/path literals.
177
+ - `make test` also covers safe shell Exit guidance in `TestShellWelcomeExitGuidanceFallbackAndLocales`: fallback, `en-US`, and `ko-KR` recommend the literal `projmux quit`, predict its default interactive action picker without promoting `--yes`, and remain lossless and terminal-cell-width-safe when wrapped and rendered.
173
178
  - `make test` also covers the Globalization Phase 5 locale settings surface: `PROJMUX_LOCALE` > global `[ui].locale` > `LC_ALL` > `LC_MESSAGES` > `LANG` > `en-US` priority, Settings > Appearance > Language / Locale row/detail visibility, `auto` current-locale/source display, unsupported locale fallback warnings, global config writes, and preserved command/config/env/path literals.
174
179
  - `make test` also covers the Globalization Phase 6 governance guard: Go string-literal audit classification for hardcoded Korean candidates, English user-facing candidates, and ignored literal/data/debug examples; no unapproved runtime Korean literals outside catalog/formatter/test fixtures; `en-US` coverage for every embedded default catalog key; and required `ko-KR` coverage for migrated notify, Settings, picker, welcome, update, and help surfaces.
175
180
  - `make test` also covers `projmux agent integrate tmux-bell` dry-run/install/remove tmux command planning, managed `alert-bell` hook append/idempotence/removal, preservation of unmanaged bell hooks, and canonical `projmux internal agent-hook ingest bell --pane` queue push/metadata/dedupe behavior for non-AI-managed panes.
@@ -228,7 +233,9 @@
228
233
  - `make test` / `make test-integration` / `make test-e2e`: Registry-first control plane Phase 7 splits Project discovery from pin authority. Unit coverage pins the typed pin envelope (`projmux-pins v2`, stored kind, corrupt/newer-version refusal, order-preserving dedup), the legacy resolution table (exactly one Registry root becomes that Project uid, zero matches stay a candidate, two or more refuse the whole migration and name the repair), the `candidates.MatchKeyFor` Linux/Windows compatibility table asserted from either host, a managed pin surviving rebind/rename/MissingRoot/no-root as the same sidebar tier, resolve-or-candidate typing of every `<dir>` argument with `uid:<uid>` as the explicit escape, write-free repeats and reads, the `create project --root` exact-path and idempotence contract, the sidebar bootstrap running after the trust gate and only for the path being opened, and a two-platform Settings golden showing Additional discovery roots, Pinned Projects, and Candidate Pins as three collections. Integration adds the real-file legs: a generic mutation route reconciling against a live server registers zero Projects for the adjacent scan-root children, an unregistered `--project <name>` refuses with the exact `--root` to pass, `pin project migrate --dry-run` writes nothing, an ambiguous legacy pin leaves both the pin file and the Registry byte-identical until a rebind repairs it, and four concurrent migrations converge on one valid typed envelope. E2E drives the user-visible flow on an exact socket with a real attached client: `switch open` on one candidate registers that one path, its siblings stay unregistered, reopening is Registry-write-free, and a rebind keeps the managed pin on the same uid with the candidate pin untouched.
229
234
  - `make test`: the Claude provider workspace/payload argv boundary is verified against the provider's own option grammar rather than substring containment, because a payload absorbed by a variadic option is still *contained* in the argv -- which is how the shipped `claude --add-dir <root> <payload>` regression passed Phase 6 review and then started sessions with no task. `internal/app/agent_launch_argv_test.go` replays each provider's documented arity over the produced argv (one-value option consumes exactly one operand; variadic option consumes operands until an option-looking token or the terminator; the terminator makes the rest operands) across Claude and Codex x zero/one/multiple additional roots x empty/one-token/many-token payloads, and requires the recovered roots, working root and prompt to equal what the launch was given, with zero undeclared options. A negative control replays the pre-fix concatenation and requires the model to *lose* the prompt, so the grammar replay cannot pass vacuously. Exact-argv tables pin `--add-dir <a> <b> -- <payload>` for Claude at every root count, `-C <cwd> --add-dir <a> --add-dir <b> <payload>` for Codex, a bare argv for an empty payload on both, and the refusal for a stored additional root on a provider projmux gives none to. `create agent` and `agent resume` are then run through the real seams and compared on the exact exec tail, which also pins that resume adds no terminator because the provider's `--resume` option is what stops the variadic root option; the former substring assertion in `agent_workspace_test.go` is now an exact-argv assertion. The one Codex payload shape that stays unsupported -- a prompt beginning with `-`, unchanged from the shipped build -- is pinned as a deliberate limit rather than left to be rediscovered. Measured against installed Claude Code 2.1.234 and Codex: `claude --add-dir DIR hello` reports "Input must be provided either through stdin or as a prompt argument" (the prompt became a directory) while `claude --add-dir A B -- hello` reaches execution with the prompt intact, and `claude --add-dir A B --resume <id>` parses both roots and the conversation id. The real-provider installed activation smoke -- installed `create agent --provider claude` with `--add-dir` plus a short payload on an isolated exact socket producing stdout `%N`, provider-hook activation acknowledgement and a transcript, with sibling and default sockets unread and exact cleanup -- runs post-merge and is recorded in the track's Archive evidence rather than in CI, because CI has no authenticated provider CLI.
230
235
 
231
- - `make test` / `make test-integration` / `make test-e2e`: Registry-first control plane Phase 8 termination evidence transport binds a managed process's real exit to the Pane it ran in. `internal/core/metadata` adds the per-materialization `status.activation` generation and the optional `status.lastTermination` receipt on Pane and Agent, and its tests pin the four-value classification vocabulary (exit 0 is `normal` and never `intentional`; non-zero and signal are `abnormal`), the generation guard across replaced generations, deleted Panes, a shell Pane claimed by an Agent and an Agent that no longer binds the Pane, the byte-identical duplicate no-op, intent recordable only by `source: control-action` and sticky against the signal a deliberate delete causes, the operation-id-scoped withdrawal that refuses to clear another operation's receipt, the generation-guarded runtime handle, and a registry stripped of both additive blocks that decodes, validates, re-encodes byte-identically, and rejects a receipt for a Pane that never carried a generation. `internal/app` adds the hidden `internal supervise` route: the argv builder terminates its own flags with `--` so an operator payload spelling `--pane-uid` cannot be re-read as one, a shell Pane with no command of its own is supervised over tmux's documented `default-command`/login-`default-shell` rule read from the same exact server, and every failure to construct a supervisor -- an unresolvable binary, an unreadable tmux default, a Pane with no generation -- returns the caller's original command so the pane still starts with no receipt rather than not starting. The route's tests pin the recorded wait status for clean exit, non-zero exit, and signal with the matching `128+signum` exit status, a registry that cannot be written costing the evidence and never the pane's own status, a stale receipt reported and dropped, a usage refusal that starts no child, and a launch failure reported as a launch failure rather than a termination. `runSupervisedChild` is exercised against real `sh` children for all three wait statuses and for argv/cwd/environment parity. Create, create-window, create-agent, `agent resume`, and the topology materializer each issue their own generation before the launch argv is built, and a route-level table proves every launched pane quotes the generation the Registry stored for it, that a fan-out never repeats one, and that a receipt from the process a resume replaced changes nothing. `delete window|pane|agent` gains `--socket`/`--socket-path` and now resolves exactly one server from those flags or the inherited absolute `$TMUX`, refusing outside tmux instead of falling back to `-L projmux` -- which supersedes the `-L projmux` inventory the Window and Pane/Agent deletion entries above describe -- and it commits its intentional receipt in its own transaction before the first live mutation, withdrawing it again on every refusal that leaves the processes running. The integration smoke records real shell exit 0, exit 7, and a signal death on an isolated exact socket and reads the receipts back out of an isolated Registry; the e2e smoke drives the same lifecycle against a real tmux server and proves an isolated `delete agent`/`delete pane` dry-run and apply touch only the socket they were given, leaving a sibling socket and the default socket unchanged.
236
+ - `make test` / `make test-integration` / `make test-e2e`: Registry-first control plane Phase 8 termination evidence transport binds a managed process's real exit to the Pane it ran in. `internal/core/metadata` adds the per-materialization `status.activation` generation and the optional `status.lastTermination` receipt on Pane and Agent, and its tests pin the closed classification vocabulary (exit 0 is `normal` and never `intentional`; non-zero and signal are `abnormal`), the generation guard across replaced generations, deleted Panes, a shell Pane claimed by an Agent and an Agent that no longer binds the Pane, the byte-identical duplicate no-op, intent recordable only by `source: control-action` and sticky against the signal a deliberate delete causes, the operation-id-scoped withdrawal that refuses to clear another operation's receipt, the generation-guarded runtime handle, and a registry stripped of both additive blocks that decodes, validates, re-encodes byte-identically, and rejects a receipt for a Pane that never carried a generation. `internal/app` adds the hidden `internal supervise` route: the argv builder terminates its own flags with `--` so an operator payload spelling `--pane-uid` cannot be re-read as one, a shell Pane with no command of its own is supervised over tmux's documented `default-command`/login-`default-shell` rule read from the same exact server, and every failure to construct a supervisor -- an unresolvable binary, an unreadable tmux default, a Pane with no generation -- returns the caller's original command so the pane still starts with no receipt rather than not starting. The route's tests pin the recorded wait status for clean exit, non-zero exit, and signal with the matching `128+signum` exit status, a registry that cannot be written costing the evidence and never the pane's own status, a stale receipt reported and dropped, a usage refusal that starts no child, and a launch failure reported as a launch failure rather than a termination. `runSupervisedChild` is exercised against real `sh` children for all three wait statuses and for argv/cwd/environment parity. Create, create-window, create-agent, `agent resume`, and the topology materializer each issue their own generation before the launch argv is built, and a route-level table proves every launched pane quotes the generation the Registry stored for it, that a fan-out never repeats one, and that a receipt from the process a resume replaced changes nothing. `delete window|pane|agent` gains `--socket`/`--socket-path` and now resolves exactly one server from those flags or the inherited absolute `$TMUX`, refusing outside tmux instead of falling back to `-L projmux` -- which supersedes the `-L projmux` inventory the Window and Pane/Agent deletion entries above describe -- and it commits its intentional receipt in its own transaction before the first live mutation, withdrawing it again on every refusal that leaves the processes running. The integration smoke records real shell exit 0, exit 7, and a signal death on an isolated exact socket and reads the receipts back out of an isolated Registry; the e2e smoke drives the same lifecycle against a real tmux server and proves an isolated `delete agent`/`delete pane` dry-run and apply touch only the socket they were given, leaving a sibling socket and the default socket unchanged.
237
+ - `make test` / `make test-integration` / `make test-e2e`: Agent clean-exit and Continue replay policy Phase 0 records Project-stop provenance without changing replay. `TestProjectStopInterruptionPrewriteSelectsOnlyExactRunningAgentsAtomically` requires every and only then-Running Agent-owned Pane in the exact Project to receive matching generation/operation `interrupted/control-action` evidence in one Registry commit. `TestProjectStopFailureCompensatesOnlyWhenExactSessionIsProvedLive` pins same-operation compensation after a proved-live failure and retention after an apply error whose exact Session is proved absent; the existing external-HUP supervision rows remain `killed/supervisor`, and `TestInterruptedControlActionIsCurrentGenerationStickyAgainstLateSupervisorHUP` prevents a delayed HUP receipt from overwriting Project-stop authority. `TestCanonicalV2MigratesToV3WithoutInformationLoss` and `TestCanonicalV2ToV3PublishesOneLosslessBackupReportAndRepeatsNoop` pin the schema 2→3 lossless transform, exact versioned backup/report, one-time write, and newer-schema downgrade refusal. Describe renders classification/source/generation as separate fields and `get` includes all three in its termination cell. Continue planning, explicit resume, Open fresh, snapshot projection, provider argv, and public hooks remain unchanged in this producer-only Phase.
238
+ - `make test` / `make test-integration` / `make test-e2e`: Agent clean-exit and Continue replay policy Phase 1 makes current-generation exact `control-action/interrupted` evidence the sole automatic Agent authority of retained-Project Continue. `TestTopologyAgentContinueEligibilityMatrix` closes source × classification × pre-projection-Running/projected-Offline phase × sessionRef, including nil evidence, stale generation, illegal pairings, and the supervisor normal/abnormal/killed, delete intentional, and reconcile unknown refusals. `TestRegistryTopologyContinueLaunchesInterruptedBOnceAndRetainsCleanA` and `TestClosedProjectStartupContinuesOnlyInterruptedAgents` require clean A launch 0 with UID/sessionRef/evidence retained, interrupted B exact-conversation launch 1, and repeated Continue duplicate 0 with no Registry write or split. A recorded conversation never degrades to fresh when its exact resume argv cannot be built. `TestExplicitSnapshotRestoreRetainsAgentReplayAuthority`, the session-state request assertion, and the isolated L10 topology/L11 startup smokes keep explicit snapshot restore and explicit `agent resume` authoritative. L10 requires raw Window/server HUP launch 0 while exact shell/Window UIDs converge, then uses only its test fixture utility to supply current-generation interrupted authority for the Agent-only anchor replay; L11 proves external HUP launch 0, Open fresh one canonical shell/zero Agent, exact `TMUX`/`TMUX_PANE` removal, unique contained sockets, and exact queried cleanup. Phase 0 producer/schema/vocabulary/migration, clean-Agent deletion, provider input/transcript/conversation stores, public hooks, ControlSession/quit/delete semantics, and Fresh identity remain unchanged.
232
239
 
233
240
  - `make test` / `make test-integration`: the delete result names the socket the invocation resolved. Phase 8 made `delete window|pane|agent` resolve one exact server from `--socket`/`--socket-path` or the inherited `$TMUX`, but left the result line printing a constant `socket=-L/projmux`, so every delete against any other server reported the wrong target -- and that line is exactly what an operator audits after a destructive command. `explicitTmuxTarget.label()` renders the flag and value the route actually used, a table pins all three resolutions (explicit name, explicit absolute path, inherited client) and fails if the default app socket appears in a result that did not name it, and the isolated integration smoke asserts the reported socket is the run-unique one -- which is the whole assertion, because a fallback would print `-L/projmux` instead, and a separate "must not contain" check would collide with the isolated socket's own `projmux` prefix.
234
241
 
@@ -371,8 +378,10 @@
371
378
  - `make test`: Codex endpoint broker runtime Phase 2 gives the dark `internal/integrations/agents/codexbroker` package an OS-level runtime: a per-state-domain singleton host, an owner-private discovery contract, and an authenticated content-free local IPC protocol, plus the minimal hidden `internal codex-broker serve|probe` entrypoint in `internal/app` that hosts one and reaches one. `TestConcurrentClientsShareOneRuntimeProcessAndOneUpstreamConnection` starts four separate client processes, holds them at one barrier, and releases them together against one empty state domain: the ledger the runtime shares with the parent must show exactly one published runtime, exactly one upstream connection, and one snapshot per client, with every client reporting the same runtime identity, because the socket bind is the singleton mutex and the exclusive startup lock keeps a losing starter from reclaiming the winner's socket. `TestDiscoveryCredentialAndPermissionFailuresBindNothingAndWriteNothing` runs the adversarial matrix - a wrong credential, an absent credential, a foreign endpoint key, a malformed first frame, a record readable beyond its owner, a record replaced by a symlink, a record naming another endpoint, no record at all, and a relative or empty state domain - and requires zero bindings, zero upstream requests, and zero upstream snapshots after all of them; an unshared protocol window gets a runtime of its own because that refusal also starts a drain. `TestStaleRuntimeArtifactsAreReclaimedOnlyByExactOwnerProof` is the ownership fixture: a live runtime's socket is reused rather than replaced with a typed `host-live`, a socket left behind by a killed runtime is removed, a regular file or a symlink at the socket path is refused as `discovery-untrusted` and left byte-identical, and the last row varies the record's pid across live, absent, and foreign values to require an identical decision every time, because a reusable pid may not be ownership authority. `TestRuntimeCrashRevokesTheOldEpochBeforeANewRuntimeRestoresAuthority` kills a real runtime process out from under a bound client: the binding revokes with `host-unavailable` before anything can present its fence, its stream closes, `ControlAuthority` and `Submit` both refuse with zero added upstream writes, and `Ensure` then proves the abandoned socket stale, starts a replacement with a different runtime identity, and restores authority only through a fresh snapshot, where a foreign epoch still refuses and writes nothing. `TestRestoredAuthorityOpensOnlyBehindTheSnapshotBarrier` holds the runtime's snapshot open and requires a bound-but-unopened client to refuse `control-not-open` with zero endpoint calls until the snapshot crosses the socket. `TestLastBindingRemovalIdlesTheRuntimeOutAndLeavesNoArtifact` keeps a held binding alive well past the idle bound, then requires the runtime to stop after the last binding is removed, remove exactly the socket and record it published, and leave no goroutine behind. `TestIncompatibleClientDrainsTheRuntimeWithoutSeveringLiveBindings` is the rolling binary replacement matrix: overlapping version windows negotiate down and reuse the running runtime in both directions, while a disjoint window is refused by name with `drain-required` without replacing the runtime or its socket, the live binding keeps delivering and keeps control authority through the drain, a new bind is refused so the drain stays finite, and only after the last binding drains does a replacement start under exact owner proof with a new runtime identity. `TestRuntimeStateAndDiagnosticsRetainNoProviderContent` drives a prompt-bearing mutation and a payload-bearing event across the socket and then searches every file the runtime persisted in its state domain for them, and pins `HostStats` as a closed set of tokens and counters. `TestRuntimeProtocolNegotiationAndFrameBoundsAreClosed` closes the negotiation table and requires an oversized frame to be refused on write with zero bytes emitted and on read. `TestInternalCodexBrokerServePublishesAndIdlesOutWithoutTouchingAnyDaemon` runs the real entrypoint against an isolated state domain: it publishes an owner-only record, reports readiness, and idles out without reaching any Codex daemon, because the broker opens its upstream connection only once a binding exists. `TestInternalCodexBrokerRouteStaysHiddenPlumbingWithNoPublicSurface` is the scope audit - no public route gains a broker spelling, the runtime stays inside the hidden `internal` namespace and out of the canonical command projection, every malformed invocation is a usage error, and `probe --no-start` refuses with `host-unavailable` while creating nothing outside the broker artifact directory. `TestCodexBrokerStateDomainFollowsTheProjmuxStatePaths` pins the singleton to the installation's state directory rather than to a working directory or a pane.
372
379
  - `make test`: Codex native lifecycle and control cutover Phase 3 makes the endpoint broker the product's endpoint producer. `TestBrokerBoundAgentsShareOneUpstreamConnectionWithIndependentBindingEpochs` binds a prompted-create Agent and a stored-resume Agent through the real runtime host and requires one upstream connection between them, one shared connection epoch, two different binding epochs, and interleaved events that reach only the thread that named them. `TestBrokerThreadlessAndForeignEventsReachNoExactAgentBinding` runs the ambiguous fixtures - no thread id, a blank one, a foreign thread, and a foreign thread sharing the bound Agent's working directory - and requires the exact binding to receive none of them while a following attributed event proves the stream stayed live. `TestBrokerEpochRotationFencesTheOldEpochOutWithZeroEndpointWrites` replaces the connection under a live binding: the retired epoch's stream closes, its steer and its approval answer are both refused, the replacement connection records zero steer requests and zero answers from it, the binding epoch survives the reconnect unchanged, and the replacement epoch starts a turn normally. `TestBrokerRuntimeLossEndsTheEpochAndRefusesEveryLateMutation` takes the runtime away from a bound client and requires the epoch to end before any caller can present its fence, every late mutation and answer to be refused with zero endpoint writes, and a reopen against an unpublished runtime to refuse rather than start one. `TestBrokerDisconnectEndsTheEpochBeforeAnyReplacementBarrier` covers the promptness half of the disconnect contract: a binding survives an outage so the broker can keep reconnecting, so the binding's out-of-band suspension - not the ordered stream, which has nothing to say until the next barrier - retires the live epoch immediately, refuses its late mutations with zero endpoint writes, and keeps the stale barrier from being re-served while the endpoint is gone. `TestBrokerEpochBacklogOverflowEndsOnlyThatEpochStream` overruns one Agent's bounded stream and requires that Agent alone to be cut to a resync while its sibling on the same shared connection keeps receiving its own events. `TestBrokerApprovalLeaseAnswersTheExactRawRequestExactlyOnce` carries an inbound server request through the broker to the app layer with its byte-faithful raw id and its normalized rendering intact, decodes it with the existing envelope decoder, answers it once, and requires the repeat to be refused with exactly one entry in the endpoint's answer ledger. `TestBrokerEpochReadsTheExactLifecycleSnapshotThroughItsOwnFence` pins the control epoch's state input to the same fence its mutations use. `TestNativeLifecycleProducerIsExactlyOnePerActivationGeneration` and `TestBrokerProducerKeepsRecoveringPastTheLegacyAttemptCeiling` are the single-producer and persistent-reconnect audits: the producer decision is total and never yields both, and a producer that owns its own reconnect keeps recovering well past the legacy six-attempt budget without ever publishing the terminal `reconnect-exhausted` fallback, while that budget stays byte-identical for the producer that still needs it. `TestBrokerCutoverPreservesTheHookFallbackVocabulary` is the compatibility audit for the authority sources, the hook-suppression rule, and the pre-mutation convergence to `provider-hook`. On the endpoint side, `TestNativeCreateAndResumeAttachToTheExactCurrentUnmanagedEndpoint` runs the real create and resume paths against a fake Codex whose every argv is recorded and requires an exact-current endpoint the official manager does not own to accept one `thread/start`, one `turn/start`, and one `thread/resume` with a daemon start/stop/restart/kill/bootstrap/remote-control/login/config argv count of zero, while `TestNativeCreateAndResumeKeepSafeFallbackOnEveryRefusedAttachRow` keeps a skewed and an ownership-unknown endpoint refusing before the connection opens, as a safe fallback carrying its typed reason and with zero thread or turn requests. `TestEndpointAttachAuthorityIsTheOnlyWidenedProductRow` walks the availability x ownership x version table behind the product's own source and availability preconditions and requires exactly one row to differ from the retired native-action gate: the ready, exact-current, unmanaged one. `TestInstalledIsolatedBrokerNativeBindingSmoke` is opt-in through `PROJMUX_CODEX_CUTOVER_SMOKE_ROOT` with a matching contained `CODEX_HOME`, an isolated state domain, and inherited `TMUX`/`TMUX_PANE` stripped, and it is the only check that runs the cutover against a real Codex app-server: a direct `codex app-server --listen unix://` under that contained `CODEX_HOME`, which upstream reports as running with no daemon backend. It requires that endpoint to be unmanaged, exact-current, attach-allowed, and to carry no daemon lifecycle authority; runs the product's own prompted create there so the thread's rollout materializes; binds that exact thread through the product session over a runtime it publishes itself; starts one turn through the resulting broker epoch and requires the epoch's own fenced snapshot to show that exact turn in progress before steering it through the same wire; interrupts it; and then requires the runtime to leave no artifact behind. Every `codex` argv the whole path ran is recorded by a PATH shim the test installs, and the run fails unless all of them are `app-server proxy` or `app-server daemon version`, which is the zero-daemon-lifecycle-mutation half of the acceptance against a real endpoint rather than a fake one.
373
380
  - `make test`: Codex interactive-only mode and native-required prompted create makes an unproven native authority a typed refusal instead of a silent plain-CLI Agent. `TestPromptedNativeCodexCreateIssuesOneTurnAndNeverRepeatsThePromptInPaneArgv` and `TestStartDefaultThreadEmptyAndPromptedRequestCounts` pin one `thread/start` plus one `turn/start` per payload with the prompt absent from Pane argv and from the Registry. `TestUnavailableNativeCreateRefusesInsteadOfSilentlyCreatingAPlainAgent` and `TestUnavailableNativePickerResumeRefusesInsteadOfRebindingOntoTheRolloutLane` close the pre-mutation refusal matrix for the two Agent-creating routes at zero Registry and zero tmux mutation, `TestIndeterminateNativeCreateRefusesASecondLaneAndWritesZero` keeps the post-mutation row refusing without offering any second lane, and `TestUnavailableNativeResumeKeepsTheStoredConversationOnTheProviderResumeLane` holds the boundary at `agent resume`, which creates no Agent and keeps its safe fallback to one provider resume of the stored conversation. `TestInteractiveOnlyIsTheOnlyPlainCodexLaneAndBothSpellingsAreEquivalent` pins flag acceptance, manifest and rendered help parity, and byte-equal output across `create agent --provider codex --interactive-only` and `create codex --interactive-only`, plus the exit-2 non-Codex-provider refusal at zero transactions. `TestStartDefaultThreadDeliversAdditionalRootsOrFailsClosed` is the roots capability matrix: the exact cleaned list on a negotiated connection, a fail-closed typed refusal when the endpoint cannot carry it, and nothing on the wire for an empty list. `TestDefaultNativeCodexFanOutRefusesWithZeroMutationsAndInteractiveOnlyKeepsCardinality` refuses a prompted multi-Window native create with zero threads, Panes, Registry writes, and tmux objects while keeping the `--interactive-only` and empty-prompt fan-out cardinality. `TestEmptyPromptCodexCreateIsByteForByteUnchangedByTheNativeRequiredGate` and `TestClaudeAndAntigravityLifecycleAndHookContractAreUnchangedByTheNativeGate` guard the deferred empty-prompt boundary and the unchanged Claude/Antigravity hook activation and fan-out contract, and `TestCodexNativeLaunchOutcomeTableIsClosed` keeps the closed outcome table describing exactly these rows.
381
+ - `make test`: Codex install capability guidance Phase 0 makes prompted native create refusal the primary actionable surface while Doctor and Settings consume the same typed `internal/app` authority. `TestCodexInstallCapabilityGuidanceMatrixStatesOnlyObservedFacts` closes `managed-ready`, `external-cli-only`, `cli-missing`, and `unknown`: external-only states exactly that the ordinary CLI exists and the managed standalone payload was not observed, while managed-ready and unknown never imply payload absence or reinstall, and no row infers npm/curl/brew provenance. `TestCodexInstallCapabilityGuidanceHasThreeConsumerParity` requires native create, Doctor, and Settings to render the same capability observation and official Codex CLI reference; the create refusal also names `--interactive-only`. `TestCodexInstallCapabilityConsumersCarryNoSurfaceLocalCopyOrURL` keeps all URL and capability copy in the one authority. Doctor and Settings remain read-only, and support JSON, install/update/daemon mutation, shell welcome, dismiss state, i18n keys, interactive-only semantics, and other providers remain unchanged.
374
382
  - `make test`: Codex per-Agent native observer retirement makes the endpoint broker the only native lifecycle, control, and approval producer, and projects its content-free telemetry onto the diagnostics surfaces. `TestNativeLifecycleProducerIsExactlyOnePerActivationGeneration` is the source audit that replaced the old producer selection: `internal/app` opens no app-server proxy of its own anywhere, and the native lifecycle observer is built with exactly one connection opener, so there is no second branch a dual-write could take. `TestRetiredCodexAppServerWatchRouteIsRefused` proves the retired producer cannot be revived by argv, hook, or script: the `codex-appserver-watch` ingest route is gone and the surviving route names the broker binding it consumes. `TestBrokerProducerKeepsRecoveringPastTheLegacyAttemptCeiling` and `TestRetiredObserverRecoveryBackoffIsCappedAndNeverExhausts` replace the fixed six-attempt exhaustion budget with the whole reconnect contract: recovery is bounded only by the capped backoff and by the exact binding, the terminal `reconnect-exhausted` fallback is never published again, the reconnect gap writes zero bytes upstream, and the only exits are a replaced binding and a cancelled context. `TestCodexNativeObserversSharingOneEndpointRecoverIndependently` keeps that containment per activation: one activation reconnecting onto its own replacement projects nothing onto its sibling and cannot terminate it. `TestRuntimeTelemetryCountsOneConnectionForConcurrentBindings` is the number the retirement is measured by - three bindings, one upstream connection, one live client session, and a telemetry frame that reaches no endpoint - and `TestRuntimeTelemetryNamesEachBindingScopedFault` keeps queue eviction, a refused reconnect snapshot, and a fenced-out epoch separable instead of collapsed into one revocation count, with a voluntary unbind never counted as a fault. `TestBrokerDiagnosticsReadCreatesNothing` and `TestBrokerDiagnosticsReportTheRestingAbsentRuntime` fix the read-only contract of that projection: asking about the broker publishes no discovery artifact, reclaims nothing, starts no runtime, and reports a machine with no live native Agent as the resting `absent` state under its closed refusal rather than as a fault. `TestBrokerDiagnosticsProjectOneConnectionAndTypedFaults` and `TestCodexBrokerDiagnosticIsContentFree` pin the rendered value: one connection per effective endpoint regardless of binding count, a down connection distinguished from one that never existed, and a closed field set carrying no provider content and no location, with an unclassified failure never leaking its message. `TestDeclaredPlainCodexLaneNamesOnlyTheByDesignLanes`, `TestDeclaredLaneVocabularyIsClosedAgainstUnknownPaneValues`, and `TestDeclaredLaneReadsBackFromTheExactPaneOption` give the two by-design plain-CLI lanes - an empty-prompt default create and an explicit `--interactive-only` - a typed declaration that survives the round trip through the exact pane option, while a forged or unknown value is refused so a declaration cannot hide a real fallback. `TestManagedCodexAuthorityCensusSeparatesDeclaredFromUnexplainedFallback` is the acceptance surface: the declared lanes are counted apart from the unexplained native fallback that the native-authority contract requires to be zero, sibling providers never enter the census, and the classes partition the managed Codex Agents exactly. `TestPlainCodexCreatesWriteTheirDeclarationOntoTheExactPane` closes the write half through the real create route and the real pane binder, so a create route that stopped passing the declaration - or a binder that stopped writing it - would fail rather than silently return every by-design plain Agent to the unexplained count.
375
- - `make test-e2e-shards`: the Linux e2e suite is scheduled one shard per runner. `scripts/test-e2e-docker.sh` accepts `PROJMUX_E2E_LINUX_SHARD=<shard>` and `PROJMUX_E2E_SUITE=codex-lifecycle|npm-staging` as mutually exclusive single-suite selectors that reuse the existing `E2E_SCENARIO` replay plumbing and derive their exact terminal inventory from the same `test/e2e/linux-shards.tsv` row the replay router reads; setting none leaves the default four-shard parallel run plus both suites unchanged. `test/e2e/shard-contract.sh` pins the CI topology to that manifest: `e2e-linux` matrixes exactly the manifest shards and `e2e-suite` the codex/npm pair, both `fail-fast: false` on their own `ubuntu-latest` runner with their own `timeout-minutes` and four uniquely named evidence artifacts, and both are `needs` and `--required` children of the aggregate `Test` gate so no shard result can be hidden. It also pins the thin `E2E Tests` job that keeps reporting the status-check name the branch ruleset requires: it is `if: always()` and evaluates both matrix results through the same `required-gate.py`, because a required context that is never reported stays pending forever instead of failing, so a skipped or missing job there would deadlock merges rather than turn a check red.
383
+ - `make test` / `make test-e2e`: Codex native reconnect stability keeps the durable exact-thread binding as reconnect authority after a transient endpoint loss. `TestCodexNativeTwoAgentDisconnectRecoversSameAgentControlAndStableProjection` fences stale start, steer, interrupt, and approval writes out of both the retired and replacement epochs; holds one target at a single invalidating/unavailable projection with zero intermediate provider-hook or hook-badge writes; restores same-Agent control only after the replacement snapshot and control endpoint agree; and proves a healthy sibling's authority, semantic projection, and control remain live. `TestInstalledIsolatedRealTmuxTwoAgentReconnectSmoke` reruns the maintained lifecycle fixture with the installed binary, stripped inherited `TMUX`/`TMUX_PANE`, disposable Registry/XDG roots, a unique real-tmux socket, immutable binary hash proof, and exact contained cleanup. `test/e2e/codex-lifecycle.sh` repeatedly polls the reconnect gap and requires byte-identical authority/status/badge tuples before accepting the replacement semantic snapshot once.
384
+ - `make test-e2e-shards`: the Linux e2e suite is scheduled one shard per runner. `scripts/test-e2e-docker.sh` accepts `PROJMUX_E2E_LINUX_SHARD=<shard>` and `PROJMUX_E2E_SUITE=codex-lifecycle|npm-staging` as mutually exclusive single-suite selectors that reuse the existing `E2E_SCENARIO` replay plumbing and derive their exact terminal inventory from the same `test/e2e/linux-shards.tsv` row the replay router reads; setting none leaves the default four-shard parallel run plus both suites unchanged. `test/e2e/shard-contract.sh` pins both workflow topologies to that manifest: `e2e-linux` matrixes exactly the manifest shards and `e2e-suite` the codex/npm pair, both `fail-fast: false` on their own bounded `ubuntu-latest` runner with uniquely named evidence artifacts. In CI they are `needs` and `--required` children of the aggregate `Test` gate, while the thin `E2E Tests` compatibility job keeps reporting the status-check name the branch ruleset requires with `if: always()` and the same `required-gate.py` reduction. In the tag-triggered Release workflow, the same children reduce through an exact `Release E2E Tests` aggregate and `Build Release` depends only on that gate, so every shard must succeed before archive construction without coupling the release workflow to the branch ruleset's required context name.
376
385
 
377
386
  ## When To Update This List
378
387
 
@@ -448,6 +457,31 @@
448
457
  `test/e2e/reliability-contract.sh` F08/F09 pin both halves: a deliberately slow
449
458
  fixture times out with its own description and state dump and then passes once
450
459
  the scale buys it time, and the timeout instant itself moves with the scale.
460
+ CI sets that scale to `2` for both required E2E child families. The Phase 5
461
+ sample covered 57 runs: the slowest job family (`fixture-3`) had p50 167s,
462
+ p95 177s, and max 201s. Because the scale applies to scenario waits rather
463
+ than whole jobs, the deciding tail is the widest scenario distribution: L12
464
+ had p50 7.185s, p95 8.081s, and max 10.188s (`max/p50=1.42`). Rounding that up
465
+ to 2 gives headroom while retaining the named fail-closed timeout; the former
466
+ 3-5x suggestion had no runner measurement and is not the configured policy.
467
+ - `make test-e2e-residual-policy` pins the Phase 5 residual decision. The exact
468
+ required inventory remains `L01`-`L19`/`C01`/`N01` (21 scenarios), and
469
+ `test/e2e/quarantine.tsv` is empty because the 57-run, 1,128-group evidence
470
+ corpus found no scenario with three observed flakes. The companion
471
+ `test/e2e/residual-observations.tsv` records each scenario's actual terminal
472
+ attempt denominator (51-54 after whole-run self-regression exclusion), and
473
+ the contract requires its complete inventory and threshold-derived eligible
474
+ set to equal the quarantine ledger. A row is invalid without
475
+ a stable scenario ID, owner, ISO-8601 deadline, and at least three observations;
476
+ this closed zero-set contract rejects a non-empty row until execution/evidence
477
+ and non-gating wiring exist. Therefore the quarantine execution Negative is
478
+ N/A: its antecedent does not exist. Instead, the contract proves that both E2E
479
+ child families still feed the exact `E2E Tests` compatibility context and the
480
+ project-wide `Test` gate. Go `Unit Tests` remain an exact required child and
481
+ outside automatic classification/quarantine: they have no per-test attempt
482
+ artifact or `class` surface, so assigning an observed-flake count would invent
483
+ evidence. A future unit quarantine first needs attempt-preserving per-test
484
+ evidence and a separate contract.
451
485
  - `test/e2e/evidence-contract.sh` (`make test-e2e-contract`) and
452
486
  `test/e2e/reliability-contract.sh` (`make test-e2e-reliability`) keep the persisted
453
487
  `projmux.e2e-attempt/v1` evidence and success result hash stable while adding
@@ -526,7 +560,7 @@
526
560
  converge on one Window with nine unique mirrored Panes and no lock or staged
527
561
  residue; it is a regression guard, not the evidence for the lock change.
528
562
  - `make test` / `make test-integration`: attention omitted-target convergence
529
- - `make test` / `make test-e2e`: Closed-Project startup mode selection is one adjudication shared by both entry points. `switch sidebar-open` re-execs across a process boundary, and before this change the picker-off sidebar always emitted `continue` and the re-exec trusted that token verbatim, so an unregistered root on a fresh install failed in `ContinueProject` with `continue project unavailable: no usable snapshot`. `defaultProjectStartupMode` now owns the picker-off decision -- the Settings/runtime sentinels, the `$HOME` guard, and the single `ProjectRegistered` read -- and all three closed-Project entry points call it: `openProjectTarget`, the sidebar emit point `openProjectTargetPathFromSidebar` that builds the `--mode` token, and `runSidebarOpen`, which re-adjudicates a `continue` that crossed the re-exec boundary. `TestProjectStartupModeSelectionIsOneDecisionAcrossEntryPoints` drives the (registered root) x (picker on/off) x (explicit picker choice) table through the in-process open, the sidebar emit point, and the sidebar continuation, requiring the emitted `--mode` token and the mode the continuation acts on to agree with the in-process choice. `TestSidebarOpenPromotesUnregisteredRootToFreshWhenPickerIsOff` is the regression guard, `TestSidebarOpenKeepsRegisteredRootOnContinue` keeps a registered root and its retained topology on `continue`, `TestSidebarOpenHonorsExplicitPickerChoice` proves an explicit picker-on choice is never promoted, `TestSidebarOpenNeverDemotesAnArrivingFreshMode` keeps the re-adjudication one-directional, `TestSidebarOpenSurfacesRegistrationReadFailure` refuses to answer an unreadable Registry with a mode, and `TestSidebarOpenContinueOnUnregisteredRootKeepsTheSnapshotRefusal` pins the no-usable-snapshot message for the continue that is still reachable by explicit choice. The L11 closed-startup e2e keeps its direct `--mode fresh` / `--mode continue` execution paths and adds step 9, `Closed Project startup mode selection`, which covers the arrival half of the decision: the picker-off sidebar now emits `fresh`, so the step deliberately forwards the legacy neutral `continue` token an older client can still send, against an unregistered root in an empty Registry on its own isolated socket/HOME/XDG/TMUX_TMPDIR, and requires the continuation to re-decide it so the Project is minted and its session opened rather than the snapshot refusal. The emitted half is covered by the unit tests through `sidebarEmittedStartupMode`.
563
+ - `make test` / `make test-e2e`: Closed-Project startup mode selection is one adjudication shared by both entry points. `sidebarStartupPickerState` is the single read-only preference authority: no file resolves to `on/default`, saved `on` and `off` keep their byte meanings, and runtime plus Settings consume the same result. `TestSidebarStartupDefaultPolicyIsSharedReadOnlyAndLocaleStable` pins the en-US/ko-KR row/detail golden, action IDs, runtime/Settings parity, missing-file zero-create, and saved bytes/mtime preservation. `TestProjectStartupModeSelectionIsOneDecisionAcrossEntryPoints` drives no-file/on/off × registered/unregistered × explicit choice through the in-process open, sidebar emitter, and re-exec continuation, requiring one matching lifecycle entry. Saved `off` still uses `defaultProjectStartupMode`; `TestSidebarOpenPromotesUnregisteredRootToFreshWhenPickerIsOff` and `TestSidebarOpenKeepsRegisteredRootOnContinue` preserve that automatic split. No-file and saved `on` preserve explicit choice, including `TestSidebarOpenContinueOnUnregisteredRootKeepsTheSnapshotRefusal`; live-session bypass, `switch sidebar-open --mode` tokens, picker row order, trust ordering, and Continue/Open fresh engines remain unchanged. L11 step 7 removes the preference, observes both native rows, cancels back to Projects with zero config creation, then selects Continue. Step 9 writes exact `off`, forwards the legacy neutral `continue` against an unregistered root, requires Fresh adjudication and a new Project, and proves saved bytes/mtime unchanged on an isolated socket/HOME/XDG/TMUX_TMPDIR.
530
564
  is enforced by `TestAttentionMutationOmittedTargetMatchesExplicitPaneLedger`,
531
565
  `TestAttentionMutationOmittedTargetRefusesWithoutExactInvocationPane`, and
532
566
  `TestAttentionMutationHelpUsesOptionalPaneCatalogUsage`; generated/reference/
@@ -656,8 +656,10 @@ Registry file and schema:
656
656
  bounded retry and stale-lock breaking, matching the notify queue and
657
657
  recent-windows stores. Explicit Registry repair uses its own recovery lock;
658
658
  see the recovery boundary below.
659
- - The envelope carries `schemaVersion: 2`. Version 1 is the first Registry
660
- envelope projmux wrote and is the only older version this build migrates.
659
+ - The envelope carries `schemaVersion: 3`. Version 1 is the first Registry
660
+ envelope projmux wrote; this build migrates v1 through v2 and then performs
661
+ the lossless v2 → v3 envelope advance that admits Project-stop
662
+ `interrupted/control-action` evidence.
661
663
  - Everything else fails closed: the file is refused as unreadable and **no
662
664
  write happens at all** — no rewrite, no backup, no staged temp file. This
663
665
  covers a **newer** schemaVersion (which would destroy state a newer build
@@ -671,20 +673,20 @@ Registry file and schema:
671
673
  registry yet" case **only before the first successful write**; see the durable
672
674
  envelope below. Only a file with actual content and no usable `schemaVersion`
673
675
  is refused as unknown.
674
- - A normal locked `Load` of v1 runs the production 1 → 2 migration, validates
675
- the repaired graph, writes the versioned backup, and publishes the v2 bytes
676
- through the existing temp-file atomic replace. A failed repair leaves the v1
677
- source bytes unchanged. Every successful first migrator (`Load`, `Update`,
678
- `UpdateConvergent`, or explicit `Migrate`) also atomically publishes a 0600
679
- `<exact-backup>.migration-report.json` beside the versioned backup before the
680
- Registry replace. That durable evidence records the exact absolute backup
681
- path, SHA-256 of its byte-identical v1 contents, version pair, repair/loss
676
+ - A normal locked `Load` of v1 or v2 runs the production migration chain,
677
+ validates the repaired graph, writes the versioned backup, and publishes the
678
+ v3 bytes through the existing temp-file atomic replace. A failed migration
679
+ leaves the source bytes unchanged. Every successful first migrator (`Load`,
680
+ `Update`, `UpdateConvergent`, or explicit `Migrate`) also atomically publishes
681
+ a 0600 `<exact-backup>.migration-report.json` beside the versioned backup
682
+ before the Registry replace. That durable evidence records the exact absolute backup
683
+ path, SHA-256 of its byte-identical source contents, version pair, repair/loss
682
684
  counts, and every repair detail. It is outside rolling recovery retention.
683
685
  A failed migration removes any staged/published report before returning while
684
686
  leaving the source bytes unchanged. `LoadWithMigrationResult` and `Migrate`
685
687
  additionally return both exact paths from the same locked transaction. A
686
- second pass sees v2 and writes neither Registry, backup, nor report bytes. An
687
- existing invalid v2 document is validated and refused byte-identically even
688
+ second pass sees v3 and writes neither Registry, backup, nor report bytes. An
689
+ existing invalid v3 document is validated and refused byte-identically even
688
690
  when explicit `Migrate` has no version step to run.
689
691
  Explicit read-only inspection migrates only its returned in-memory view and
690
692
  never publishes it.
package/docs/cli-guide.md CHANGED
@@ -571,9 +571,13 @@ client only after it converges; a refusal, a failed preflight, or a rolled-back
571
571
  partial leaves the client where it was and reports the exact stage. The
572
572
  activation is pinned to the session the open targets, so a Project whose
573
573
  Registry projects a different session name is refused instead of populating a
574
- session the open never reaches. The closed-Project startup screen has exactly
575
- two neutral actions. `Continue project` materializes current Registry desired
576
- state with the same Project UID. A retained graph keeps descendant UIDs; a
574
+ session the open never reaches. With no saved `sidebar-startup-picker`
575
+ preference, the closed-Project startup screen has exactly two neutral actions;
576
+ the missing-file default is read-only and does not create a config file. Saved
577
+ `on` retains that explicit choice, while saved `off` skips the screen and keeps
578
+ the registered-Continue/unregistered-Fresh automatic decision. `Continue
579
+ project` materializes current Registry desired state with the same Project UID.
580
+ A retained graph keeps descendant UIDs; a
577
581
  zero-Window Project atomically receives a new canonical Window/shell UID chain.
578
582
  A deleted Project may use only the exact usable snapshot compatibility path;
579
583
  an unavailable Continue is an explicit zero-write refusal with no Fresh
@@ -1919,10 +1923,11 @@ human configuration work should prefer `config render` and `config apply`.
1919
1923
  generated config. The generated app config uses absolute `$SHELL` as the
1920
1924
  tmux default shell when set, otherwise `/bin/sh`. `shell` starts or attaches
1921
1925
  the app session directly after resolving the target app session name and
1922
- startup directory. Alt-1 sidebar project open defaults to `Continue project`,
1923
- which materializes the Project's Registry Windows, shell Panes, and Agents
1924
- before the client moves. When the startup picker is enabled it contains exactly
1925
- `Continue project` and `Open fresh`; Esc returns to Projects. `Continue
1926
+ startup directory. With no saved startup preference, Alt-1 sidebar project
1927
+ open defaults to a two-action picker containing exactly `Continue project`
1928
+ and `Open fresh`; Esc returns to Projects without writing config. Saved `on`
1929
+ keeps that picker, while saved `off` skips it and preserves the existing
1930
+ automatic registered-Continue/unregistered-Fresh decision. `Continue
1926
1931
  project` restores a deleted Project only from its usable exact snapshot and
1927
1932
  otherwise refuses with zero Registry writes. `Open fresh` is a neutral,
1928
1933
  confirmation-free one-step action that atomically replaces the Project with
@@ -58,7 +58,11 @@ native binding.
58
58
  Now: the create refuses before the split, before the hook probe, and before the
59
59
  Registry commit. Zero threads, zero Panes, zero Registry writes, zero tmux
60
60
  objects. The refusal carries the typed reason from the endpoint (for example
61
- `daemon-not-running`) and names `--interactive-only`. Exit code 1.
61
+ `daemon-not-running`), names `--interactive-only`, reports only the observed
62
+ install-capability facts, and links to the
63
+ [official Codex CLI capability guidance](https://learn.chatgpt.com/docs/codex/cli).
64
+ Exit code 1. Doctor and Settings render that same typed guidance authority;
65
+ they do not maintain separate installer wording.
62
66
 
63
67
  Fix it by making the app-server endpoint available — start with
64
68
  `projmux doctor --section integrations --verbose` — or ask for the plain lane on
@@ -156,7 +160,7 @@ projmux create agent --provider codex --interactive-only -- "interactive task"
156
160
  ## Verifying this yourself
157
161
 
158
162
  ```sh
159
- go test ./internal/app/ -run 'TestInteractiveOnlyIsTheOnlyPlainCodexLaneAndBothSpellingsAreEquivalent|TestDefaultNativeCodexFanOutRefusesWithZeroMutationsAndInteractiveOnlyKeepsCardinality|TestEmptyPromptCodexCreateIsByteForByteUnchangedByTheNativeRequiredGate|TestClaudeAndAntigravityLifecycleAndHookContractAreUnchangedByTheNativeGate|TestUnavailableNative|TestCodexNativeLaunchOutcomeTableIsClosed'
163
+ go test ./internal/app/ -run 'TestCodexInstallCapability|TestInteractiveOnlyIsTheOnlyPlainCodexLaneAndBothSpellingsAreEquivalent|TestDefaultNativeCodexFanOutRefusesWithZeroMutationsAndInteractiveOnlyKeepsCardinality|TestEmptyPromptCodexCreateIsByteForByteUnchangedByTheNativeRequiredGate|TestClaudeAndAntigravityLifecycleAndHookContractAreUnchangedByTheNativeGate|TestUnavailableNative|TestCodexNativeLaunchOutcomeTableIsClosed'
160
164
  go test ./internal/integrations/agents/codexappserver/ -run TestStartDefaultThread
161
165
  ```
162
166
 
@@ -173,3 +177,4 @@ go test ./internal/integrations/agents/codexappserver/ -run TestStartDefaultThre
173
177
  | One payload sends exactly one `turn/start` and never repeats the prompt in Pane argv | `TestPromptedNativeCodexCreateIssuesOneTurnAndNeverRepeatsThePromptInPaneArgv` |
174
178
  | The post-mutation row still refuses a second lane | `TestIndeterminateNativeCreateRefusesASecondLaneAndWritesZero` |
175
179
  | The outcome table describes exactly these rows and no others | `TestCodexNativeLaunchOutcomeTableIsClosed` |
180
+ | Managed-ready/external-only/CLI-missing/unknown guidance states only observed facts and all three consumers render the same authority | `TestCodexInstallCapabilityGuidanceMatrixStatesOnlyObservedFacts`, `TestCodexInstallCapabilityGuidanceHasThreeConsumerParity`, `TestCodexInstallCapabilityConsumersCarryNoSurfaceLocalCopyOrURL` |
@@ -195,9 +195,9 @@ marker-only edit with a digest-named pre-v2 backup. [docs/keybindings.md](keybin
195
195
  table, the upgrade ordering and the downgrade procedure.
196
196
 
197
197
  This marker is a separate version domain from the CLI resource registry's
198
- `apiVersion: projmux.io/v1alpha1` / camelCase `schemaVersion: 2` envelope. The
198
+ `apiVersion: projmux.io/v1alpha1` / camelCase `schemaVersion: 3` envelope. The
199
199
  two have separate markers, separate backups and separate rollbacks; neither one
200
- failing affects the other. A successful Registry v1 → v2 migration keeps its
200
+ failing affects the other. A successful Registry v1/v2v3 migration keeps its
201
201
  private repair/loss evidence at `<exact-versioned-backup>.migration-report.json`,
202
202
  including that backup's absolute path and SHA-256; failed or repeated passes
203
203
  publish no report.
@@ -871,12 +871,15 @@ override with `inherit`, `on`, and `off`; `inherit` follows the global value,
871
871
  while `on` and `off` take precedence. Auto-save only updates the latest
872
872
  snapshot. Named snapshots are manual and are never updated by auto-save.
873
873
 
874
- Project open from the Alt-1 sidebar defaults to `Continue project`, which
875
- materializes the closed Project's current Registry desired state before moving
876
- the client. The optional `Settings > Session State > Sidebar startup picker`
877
- toggle enables a native `Start project` step with exactly `Continue project` and
878
- `Open fresh`. `Open fresh` confirms exact `Window n / Pane n / Agent n` counts
879
- and conversation-pointer loss, then atomically replaces the old Project graph
874
+ With no saved preference, Project open from the Alt-1 sidebar shows a native
875
+ `Start project` step with exactly `Continue project` and `Open fresh`.
876
+ Settings > Projects > Project Sidebar > Closed Project startup reports this as
877
+ `Continue project / Open fresh - default`. A saved `on` keeps the same explicit
878
+ choice and reports `Continue project / Open fresh - on - saved`. A saved `off`
879
+ reports `Continue project - off - saved` and skips the picker: a registered root
880
+ continues, while an unregistered root follows the existing Fresh adjudication.
881
+ Resolving or cancelling the missing-file default never creates the preference
882
+ file or changes saved bytes or mtime. `Open fresh` atomically replaces the old Project graph
880
883
  with a new Project UID and a new canonical Window/shell UID pair. Exactly one
881
884
  same-root Project claimant remains. Snapshot bytes, the root directory,
882
885
  Git/worktree data, and the trust decision remain unchanged. Esc returns to
@@ -912,6 +915,8 @@ The saved global toggles live under
912
915
  auto-save overrides live under
913
916
  `${XDG_CONFIG_HOME:-$HOME/.config}/projmux/sessionstate-projects/<session>/autosave`.
914
917
  The environment variables above override the global files.
918
+ `sidebar-startup-picker` accepts the existing `on` and `off` bytes; absence is a
919
+ read-only effective `on - default`, not a migration or an implicit write.
915
920
 
916
921
  Manual snapshot actions are available from the CLI:
917
922
 
@@ -344,8 +344,11 @@ Literal preservation:
344
344
  - Key names such as `Enter`, `Esc`, `Ctrl-b d`, and `s` remain literal inside
345
345
  localized guide sentences.
346
346
  - Commands, env/config names, and paths such as `projmux shell`,
347
- `tmux -L projmux kill-server`, `PROJMUX_PROJDIR`, and
347
+ `projmux quit`, `PROJMUX_PROJDIR`, and
348
348
  `~/.config/projmux/projdir` remain literal payload text.
349
+ - Shell Exit guidance presents literal `projmux quit` as the normal path and
350
+ briefly predicts its default interactive action picker; it does not promote
351
+ `--yes` as the normal path.
349
352
  - Project names, paths, provider payloads, enum values, and hook/action IDs are
350
353
  not translated.
351
354
 
@@ -98,10 +98,12 @@ Release binaries, including the `darwin && cgo` native key adapter, then
98
98
  publishes each staged package with `npm publish --access public`.
99
99
 
100
100
  The GitHub release itself stays a draft until that npm job succeeds. release-please
101
- creates the release with `draft` set, `release.yml` uploads archives to the drafted
102
- release, and only the final `publish-release` job flips it visible. So by the time a
103
- user can see release `vX.Y.Z`, npm `dist-tags.latest` already resolves to `X.Y.Z`;
104
- a failed npm publish keeps the release hidden and the workflow red.
101
+ creates the release with `draft` and `force-tag-creation` set, so its release pass
102
+ creates the tag before the same action computes the next release PR. The tag starts
103
+ `release.yml`, which uploads archives to the drafted release, and only the final
104
+ `publish-release` job flips it visible. So by the time a user can see release
105
+ `vX.Y.Z`, npm `dist-tags.latest` already resolves to `X.Y.Z`; a failed npm publish
106
+ keeps the release hidden and the workflow red.
105
107
  The npm publish job uses GitHub Actions OIDC (`id-token: write`) instead of a
106
108
  long-lived `NPM_TOKEN` secret. PR CI runs `make npm-pack` so package staging and
107
109
  dry-run packing fail before release.
@@ -68,7 +68,14 @@ step, never a silent no-op.
68
68
  Recent Windows links are unchanged, and `projmux runtime diagnostics` and
69
69
  `get runtime` never read it. An unrecognized saved value applies the default
70
70
  without writing and shows an invalid source.
71
- `Closed Project startup` optionally shows exactly two actions.
71
+ `Closed Project startup` shows exactly two actions when no preference file
72
+ exists and reports `Continue project / Open fresh - default` (`이어서 열기 /
73
+ 새로 열기 - 기본값` in ko-KR). Saved `on` keeps those choices and reports
74
+ `Continue project / Open fresh - on - saved`. Saved `off` reports
75
+ `Continue project - off - saved`, skips the picker, and retains the
76
+ registered-Continue/unregistered-Fresh automatic
77
+ adjudication. Reading the row or opening/cancelling the picker never writes
78
+ the default or changes saved preference bytes/mtime.
72
79
  `Continue project` materializes the Project's current Registry desired state
73
80
  and then moves the client. `Open fresh` confirms exact counts,
74
81
  atomically replaces the old Project graph with a new Project UID and a new
package/docs/testing.md CHANGED
@@ -47,7 +47,10 @@ and humans run the same entrypoints.
47
47
  them required children of the aggregate `Test` gate. It also pins the thin
48
48
  `E2E Tests` job, which exists because the branch ruleset requires a status
49
49
  check under that exact name; a required context that is never reported stays
50
- pending rather than failing, so dropping that job would deadlock merges.
50
+ pending rather than failing, so dropping that job would deadlock merges. The
51
+ tag-triggered Release workflow uses the same four Linux shards and two suite
52
+ selectors on separate runners, then reduces them through the fail-closed
53
+ `Release E2E Tests` aggregate before `Build Release` starts.
51
54
  - `make test-e2e-coverage` validates
52
55
  `test/e2e/ags-oedr-manifest.json`: executable scenario markers and shard
53
56
  assignments must match all 21 rows with orphan count zero. A matrix may move
@@ -111,9 +111,9 @@ The `Codex app-server` result keeps four readiness axes separate:
111
111
  remain separate supporting fields. A ready endpoint therefore does not hide an
112
112
  unmanaged process or version skew.
113
113
 
114
- `external-cli-only` means the ordinary Codex CLI executable is present, but
115
- the canonical managed payload needed by `codex app-server daemon start` was not
116
- observed. It does not mean the ordinary CLI is unsupported and does not prove
114
+ `external-cli-only` states only two observed facts: the ordinary Codex CLI
115
+ exists, and the managed standalone payload was not observed. It does not mean
116
+ the ordinary CLI is unsupported, identify how that CLI was installed, or prove
117
117
  who owns a running process.
118
118
 
119
119
  An explicit native action refuses a ready unmanaged or version-skewed endpoint.
@@ -131,12 +131,14 @@ reason Doctor reports. `--interactive-only` creates that plain Agent on purpose,
131
131
  without native turn control. See
132
132
  [Codex Native-Required Create Migration](codex-native-required-migration.md).
133
133
 
134
- If native app-server features are needed, review the
135
- [official Codex CLI installation options](https://learn.chatgpt.com/docs/codex/cli)
136
- and install or repair the managed standalone payload. Then rerun Doctor. Do not
137
- copy binaries, create symlinks in the Codex home, or edit the control socket as
138
- a diagnostic workaround. Doctor, Settings, and support-report collection never
139
- start the daemon or modify the installation.
134
+ Native create refusal is the primary actionable surface: it names
135
+ `--interactive-only`, reports the same observed install-capability guidance as
136
+ Doctor and Settings, and links to the
137
+ [official Codex CLI capability guidance](https://learn.chatgpt.com/docs/codex/cli).
138
+ After changing the Codex installation through an operator-chosen supported
139
+ method, rerun Doctor. Do not copy binaries, create symlinks in the Codex home,
140
+ or edit the control socket as a diagnostic workaround. Doctor, Settings, and
141
+ support-report collection never start the daemon or modify the installation.
140
142
 
141
143
  ## Incomplete npm install
142
144
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "projmux",
3
- "version": "0.14.0",
3
+ "version": "0.14.2",
4
4
  "description": "tmux project session manager",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/crevissepartners/projmux#readme",
@@ -28,9 +28,9 @@
28
28
  "package:npm:pack": "scripts/package-npm.sh --pack"
29
29
  },
30
30
  "optionalDependencies": {
31
- "@projmux/linux-x64": "0.14.0",
32
- "@projmux/linux-arm64": "0.14.0",
33
- "@projmux/darwin-x64": "0.14.0",
34
- "@projmux/darwin-arm64": "0.14.0"
31
+ "@projmux/linux-x64": "0.14.2",
32
+ "@projmux/linux-arm64": "0.14.2",
33
+ "@projmux/darwin-x64": "0.14.2",
34
+ "@projmux/darwin-arm64": "0.14.2"
35
35
  }
36
36
  }