projmux 0.12.1 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/agent-workflow.md +191 -23
- package/docs/ai-agent-shortcuts.md +22 -3
- package/docs/architecture.md +1334 -80
- package/docs/cli-guide.md +549 -55
- package/docs/cli.md +274 -102
- package/docs/configuration.md +197 -33
- package/docs/hooks.md +17 -3
- package/docs/keybindings.md +2 -2
- package/docs/legacy-cli-retirement.md +37 -8
- package/docs/operational-diagnostics.md +58 -2
- package/docs/resource-attribution.md +11 -4
- package/docs/session-restore.md +73 -150
- package/docs/settings-ia.md +48 -17
- package/docs/statusbar.md +10 -2
- package/docs/testing.md +80 -4
- package/docs/upgrading.md +243 -5
- package/docs/usage-tracking.md +56 -10
- package/package.json +5 -5
package/docs/agent-workflow.md
CHANGED
|
@@ -26,22 +26,46 @@
|
|
|
26
26
|
9. Prepare review notes with parity status, commands run, and remaining risks.
|
|
27
27
|
|
|
28
28
|
## Maintained Test List
|
|
29
|
+
|
|
30
|
+
- `make test` / `make test-integration` / `make test-e2e`: Window anchor and primary-shell separation Phase 0 finalizes the unpublished Registry v2 Window shape without consuming a new schema number. `TestFinalV2AnchorAndDefaultShellValidationMatrix`, `TestSchemaVersion2WindowShapeNormalizationTable`, and the deterministic random-graph properties cover required same-Window shell or managed-Agent `anchorPaneRef`, optional direct-shell `defaultShellPaneRef`, mixed legacy/final refusal, v1 and intermediate-v2 deterministic normalization, final-writer legacy-field zero, and second-pass idempotence. `TestIntermediateV2NormalizationPublishesExactEvidenceAndSecondPassIsZeroByte`, `TestIntermediateV2NormalizationFailureHonorsEvidencePublicationBoundary`, and `TestPrivateEvidencePublicationIsExclusive` pin the exclusive exact mode-0600 backup, checksum/repair report, pre-publication cleanup, post-publication evidence preservation, pre-replace source-byte preservation, and final-v2 zero-byte pass. Existing create, materialize, resume, focus, and real-tmux outcomes stay at Phase-0 parity through the pure compatibility shell resolver; the E2E stale-anchor fixture now corrupts `anchorPaneRef` and keeps its isolated zero-write refusal.
|
|
31
|
+
- `make test` / `make test-integration` / `make test-e2e`: Window anchor and primary-shell separation Phase 2 closes the role/live consumer table. `TestAnchorAwareCreatePaneAndAgentUseExactLiveShellAnchorDetached`, `TestAnchorAwareCreatePaneAndAgentUseExactLiveAgentAnchorDetached`, `TestAnchorAwareCreatePaneAdoptsNewShellAsEmptyDefaultWithoutReplacingShellAnchor`, the paired resume tests, and the explicit-Pane/popup precedence tests pin detached exact-Window outcomes and create-time default-shell adoption. `TestAnchorAwareCreateRefusesDeadAndCrossWindowAnchorsWithZeroWrites` and `TestRegistryTopologyMaterializationRefusesDeadAnchorWithoutAlternateLivePaneInference` prohibit alternate-live-Pane inference. `TestRegistryTopologyMaterializationAgentAnchorLazyDefaultShellAndRepeatNoop` plus the installed isolated real-tmux Agent-only Window fixture pin the plan-visible lazy shell, preserved Agent anchor/Pane uid, exact default owner/ref graph, smoke-root socket containment, and repeat no-op. `TestFocusKindOnAnOfflineTargetExitsTwoAndMaterializesNothing`, `TestFocusKindMovesTheClientToAnAlreadyLiveTarget`, and `TestControlOwnedRegistryKeepsMaterializeGreen` cover strict read/navigation-only focus and the Project/ControlSession root boundary.
|
|
32
|
+
- `make test` / `make test-integration` / `make test-e2e`: Window anchor and primary-shell separation Phase 3 keeps canonical Pane/Agent delete and clean exit independent from explicit Window deletion. Registry-order sibling choice, optional-empty default shell, Offline Agent retention, and same Window UID/name remain pinned for non-last Pane deletion. Project runtime stop Phase 1 supersedes the old last-Pane replacement boundary: an exact clean last-Pane receipt waits for its matching `window-unlinked`, then deletes only the Window subtree with zero replacement allocation. Explicit `delete window --yes` uses the same root-preserving shape: a non-last primary reanchors to its existing sibling and a last Window leaves the Project uid/root/reservation with empty `primaryWindowRef`; only explicit `delete project` unregisters the root. Unknown, killed, unpaired, foreign, stale, missing-server, and empty observations retain zero Window-delete authority.
|
|
33
|
+
- `make test` / `make test-integration` / `make test-e2e`: Window anchor and primary-shell separation Phase 4 closes projection and startup over final-v2 desired state. `TestSnapshotProjectionFinalV2RefsAndOpenFreshGolden`, `FuzzSnapshotProjectionIsScopedValidAndIdempotent`, and `FuzzOpenFreshKeepsOnlyCanonicalProjectShell` pin metadata-bearing and legacy snapshot projection, Agent-anchor/default-shell policy, target-subtree replacement, source-snapshot preservation, and fixed points. `TestProjectFreshStartPruneScope` preserves an existing canonical Project/Window/shell UID chain and exact transaction-level Registry bytes across repeated Open fresh, while `TestProjectFreshStartAgentAnchorCreatesExactMinimumShellAndRepeatsNoop` covers its one permitted shell allocation; `TestRestoredOfflineAgentAnchorContinueConvergesLazyShellAgentAndRepeatNoop` covers offline restore through lazy default-shell/Agent materialization. `TestAuthorshipPromotionPreservesSiblingProjectSocketAndOtherHostDesiredState` pins exact-socket positive convergence while sibling-socket and other-host-only desired refs/status plus Registry order remain byte-identical. The isolated smoke covers snapshot/Open fresh closed repeats with the exact UID chain, lifecycle Window survival, create/resume/materialize/focus, failure/repeat paths, and exact-socket sibling isolation.
|
|
34
|
+
- `make test` / `make test-integration` / `make test-e2e`: Codex native control plane Phase 3 projects a closed, content-free app-server lifecycle into Agent interaction, badges, attention, and notifications. Unit and fuzz event sequences pin exact thread/turn/item/request approval identity, waiting-plus-unresolved gating, resolution and leave-waiting clears, successful completion once, failed/interrupted non-completion, and old-epoch/foreign-identity writes at zero. Fake proxy tests pin observation-only server requests with no approval response, snapshot convergence, invalidation-before-fallback ordering, reconnect replacement, and all Codex hooks writing zero through pending/healthy/invalidating authority. Settings and delivery tests pin the independent `approval_required`/`response_complete` Notify/State only/Quiet matrix across app-server and hook-fallback epochs, explicit raw fallback-only precedence, exact non-Notify queue clearing, raw hook-byte preservation and no inference, mixed multi-Pane source/reason/epoch diagnostics, Quiet aggregate-badge suppression, and content-free `describe agent` rows. Isolated tmux integration and e2e cover exact Registry/Pane/generation/thread binding, native/fallback/reconnect behavior, queue/desktop outcomes, sibling containment, and a privacy/scope inventory that excludes prompt/control, progress/diff, catalog/session-state, provider configuration, schema, and release changes.
|
|
35
|
+
- `make test` / `make test-integration` / `make test-e2e`: Codex native control plane Phase 7 admits only bounded scalar progress from the exact current app-server turn. The adapter drops plan explanation/step text, diff bytes and paths after a 256 KiB header-only scan, and every item body while mapping only the installed-schema discriminator into the ten-value activity set. Unit and fake-clock properties pin 99 plan steps, 999 changed-file headers, 32 opaque dedupe ids, deterministic dropped/unknown/overflow counters, identical/out-of-order write zero, and at most four non-terminal Registry writes per Agent per second. Adapter-to-reducer-to-Registry/read/UI tests pin exact activation turn attribution, KO/EN 80/100/120-cell rendering, read-time-only Window counts, and immediate zero-history clear on completed/interrupted/failed turn, generation replacement, disconnect/invalidation, Offline, and Failed; unavailable native progress leaves the existing interaction UI unchanged. Privacy inventories reject prompt, reasoning, step, command/tool/file/path/name/output, diff body/hash, model, and effort fields across the reducer input, durable Agent status, describe/Inspector projection, tmux options, and diagnostics.
|
|
36
|
+
- `make test`: Codex native control plane Phase 0 keeps the app-server wire in `internal/integrations/agents/codexappserver` and pins the headerless JSON-RPC JSONL frame bound, monotonic request IDs, out-of-order response routing, initialize/initialized lifecycle, typed notification delivery, context-bounded request and initialized-notification writes, local cancellation with ignored late responses, and connection replacement with no goroutine leak. A fake stdio proxy matrix closes healthy, missing/disconnected, unsupported `-32601`, malformed, and timeout outcomes into exactly one of available/unsupported/unavailable/timeout/protocol-error, while Doctor, support-report redaction, and the Settings no-op state row expose only source/reason/version/endpoint-kind/connection-state. Negative source and argv audits keep daemon mutation, login, config writes, remote transport, all existing Agent/hook/resume/usage consumers, giant provider interfaces, and Settings source controls at zero.
|
|
37
|
+
- `make test`: Codex native control plane Phase 1 pins the closed trigger × probe × start × readiness lifecycle table: Doctor, Settings, and support reports repeat with zero daemon mutation; only a native user action whose official local socket is missing or refuses a connection may share one in-process `codex app-server daemon start`; and already-running, executable-missing, nonzero, timeout, socket-unavailable, unsupported, protocol, and other endpoint failures retain exact content-free reasons and the existing fallback. Fake CLI/proxy fixtures cover exact argv, cold and warm initialize, bounded readiness, concurrent callers, per-caller cancellation, late-arrival handoff, zero waiter leaks, and discarded process output. The opt-in installed-Codex smoke strips inherited `TMUX`/`TMUX_PANE`, contains the Codex home and control socket below one validated temporary root, proves idempotent start-to-proxy-initialize readiness, and records the exact cleanup target without adding product shutdown behavior.
|
|
38
|
+
- `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.
|
|
39
|
+
- `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.
|
|
40
|
+
- `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.
|
|
29
41
|
- `make fmt`: repository formatting for Go, shell snippets, and generated docs where applicable.
|
|
30
42
|
- `make fix`: safe automatic fixes such as `go fix` and repository-approved cleanup steps.
|
|
31
43
|
- `make npm-pack`: macOS-hosted local npm binary package staging and `npm pack --dry-run` for the root package plus platform packages, including native-key-adapter checks for both Darwin architectures; release staging reuses the release matrix archives.
|
|
32
44
|
- `make security`: pinned govulncheck, gosec, staticcheck, gitleaks history/current-tree, actionlint, and shellcheck gates. Reviewed gosec/staticcheck findings use checked-in fingerprint/count baselines so only new findings fail.
|
|
33
|
-
- `make test` / `make test-integration` / `make test-e2e`: Agent Usage HUD provider/window visibility coverage pins the `UsageSupported()` provider order and explicit Claude/Codex `5h`+Weekly versus Antigravity Weekly-only capability matrix; missing/invalid on
|
|
45
|
+
- `make test` / `make test-integration` / `make test-e2e`: Agent Usage HUD provider/window visibility coverage pins the `UsageSupported()` provider order and explicit Claude/Codex `5h`+Weekly versus Antigravity Weekly-only capability matrix; missing/invalid defaults of off for Codex `5h` and on for every other leaf, with explicit saved `on` overriding that compact default; atomic 0600 leaf persistence; the full overall/provider/window saved-effective parent matrix and restoration; en-US/ko-KR Settings View navigation; ambient-only filtering after collect/load/cache/backoff; weekly-only official-window width shedding; zero hidden label/bar/separator/staleness residue and all-provider-off empty text; lossless explicit table/JSON and CachedState popup; unchanged cache/backoff/provider-enablement byte/hash/inode/mtime sentinels; and Settings save to generated/live exact source-file apply.
|
|
34
46
|
- `make test` / `make test-integration`: Settings IA Phase 4 row-0 HUD visibility coverage pins default/saved/invalid effective-source projection; independent Notifications HUD and Agent Usage HUD persistence; byte-identical default app/standalone output; exact four-combination range/alignment/full-width behavior; all-off one-line (`status on`) convergence with the structural Window row, quiet autosave, and no stale row residue; Settings-generated config source reload; unchanged notify queue and usage snapshot state; and production exact-socket live tmux apply across all four combinations under the integration smoke's guarded socket cleanup.
|
|
35
47
|
- `make test` / `make test-integration`: Settings IA Phase 5 row-1 segment controls cover Project, Working directory, Git, Resources, Clock, and Settings launcher independently: five visibility files reuse the Phase 4 missing/invalid→on default, saved/effective/source, atomic 0600 persistence, generated-config no-write, and live exact `source-file` apply contracts; Resources has no duplicate visibility file and uses `live-resources` alone to remove its range/job and guard the sampler/cache mutation path; cwd/Git detail Views own separate Visible and Icon controls so icon off preserves their text; app/standalone exact mixed output removes hidden ranges and owned spacing with retired Kube ranges/jobs absent; and Settings launcher off retains the CLI/default-keybinding Settings entry.
|
|
36
48
|
- `make test` / `make test-integration` / `make test-e2e`: Settings IA Phase 6 retires the hidden Project recipe and Effective merge handlers/catalog/i18n plus every Kube-specific product projection. Named regressions pin the Project tree to Automation and Snapshots, reject public and internal Kube status routes, prove app/standalone generated configs contain no Kube range/job/binding, remove Kube from popup/sidebar switch previews and hook/session environments, preserve project hooks/trust plus `[startup]` and arbitrary generic `[env]` keys, and make a legacy `[kube]` update fail with the exact documented diagnostic while preserving original bytes and mtime. The integration smoke checks the same generated/live tmux absence on an isolated exact socket.
|
|
37
49
|
- `make test`: CLI information architecture v2 Phase 0 Cobra command catalog and help boundary: the `internal/cli` manifest holds every public route plus the 2 hidden internal helpers with exactly one canonical/shortcut/compatibility/internal disposition each (34 public, 9/7/13/5 tally, the ninth canonical being the `get` node the selector Phase added) and zero orphan routes in either direction against the canonical route manifest, which pins `rebind project`, `prune project`, `agent usage`, and the Pane-read `cwd` field projection alongside the closed shared `-o uid|name|ref|metadata|json|pane-id|none` catalog that deliberately excludes `cwd`; a golden fixture freezes the byte-identical top-level listing now rendered from the manifest and a command-tree golden freezes the Cobra surface (36 route nodes, hidden flags, `DisableFlagParsing` on every bridge, no injected `help`/`completion`/`__complete` command); a raw-argv table proves each bridge forwards `--`, positional arguments, unknown flags, and payload tails untouched, that handler-returned `flag.ErrHelp` and exit coders survive the Cobra boundary, and that unknown commands keep `unknown command: <token>` on stderr with exit 1 rather than a usage error; and the help no-side-effect negative tests prove every root, nested, and hidden internal help invocation exits 0 with stdout-only manifest help across every `flag`-equivalent spelling — both dash prefixes of `help`/`h`, bare and with any `=value`, matched on the flag name and pinned against the real `flag` package, with a guard that no leaf parser defines a competing `help`/`h` flag, reaches no handler, writes nothing into an isolated home, records zero operational-error journal rows through the same policy the process entrypoint uses and zero phantom state-changing success rows at any route depth (the diagnostics direct-help classifier walks positional sub-route words but stops at the first flag, so `upgrade --ref --help` and a literal `pin add help` path stay conservatively mutation-capable), skips the legacy hook filesystem migration, and — with a bounded timeout regression guard — no longer blocks on the `popup-wait-key` tty read, while a help flag after `--` stays payload and a bare nested `help` word still reaches its handler.
|
|
38
|
-
- `make test`: CLI information architecture v2 Phase 1 resource metadata foundation: `internal/core/metadata` unit and table tests cover the closed `Project`/`Window`/`Pane`/`Agent` kind set under `apiVersion: projmux.io/v1alpha1`, the one-time name-base seed priorities for every kind (Agent topics and raw pane titles are excluded everywhere), name-key validation, the persisted `nameReservations` suffix allocator proving the lowest free suffix is scan-order independent and that an explicit collision fails with `ErrNameConflict` and zero reservations written, offline Project bootstrap topology with a resolvable `spec.
|
|
50
|
+
- `make test`: CLI information architecture v2 Phase 1 resource metadata foundation: `internal/core/metadata` unit and table tests cover the closed `Project`/`Window`/`Pane`/`Agent` kind set under `apiVersion: projmux.io/v1alpha1`, the one-time name-base seed priorities for every kind (Agent topics and raw pane titles are excluded everywhere), name-key validation, the persisted `nameReservations` suffix allocator proving the lowest free suffix is scan-order independent and that an explicit collision fails with `ErrNameConflict` and zero reservations written, offline Project bootstrap topology with a resolvable `spec.defaultShellPaneRef`, uid immutability across runtime creation, teardown, and rebind, zero heuristic uid merges (same-basename roots stay distinct; only an exact saved root reuses its uid), rebind and root-missing/root-returning `MissingRoot` condition lifecycle with preserved first-observed timestamps and name reservations, reverse-order operation rollback restoring a byte-identical registry, the closed Agent phase set with its exit classification and resumable Offline/Failed transitions, `schemaVersion` classification that fails closed on any newer envelope and on an unversioned document (production registers no migration step, since v1 is the first shipped envelope), a `testdata/registry-v0-source.json` → `testdata/registry-v1-migrated.golden` migration pair driven by a test-injected older step with reservation backfill that never renumbers existing names, full registry structural validation, and Session State round-tripping through additive `omitempty` `metadata` blocks at the unchanged snapshot `version: 1` including deterministic positional reconciliation of pre-metadata snapshots. `internal/integrations/metadata` covers the locked `<state>/projmux/metadata/registry.json` store: a newer `schemaVersion`, malformed JSON, and a content-bearing document with no `schemaVersion` are all refused byte-for-byte with no write, no temp file, no backup, and no quarantine on `Load`/`Update`/`Migrate`, while an absent, empty, or whitespace-only file still yields a fresh empty registry; migration atomicity is proven against a test-registered older step by injected failures after the backup, after the staged temp write, and just before the atomic replace, each leaving the original bytes and no leaked temp; a successful migration keeps a verifiable pre-migration backup and is idempotent; a failing operation writes nothing; a bootstrap registry golden freezes the camelCase file format and 0600 mode; concurrent registrations serialize through the lock with unique allocated names; and an end-to-end test round-trips an offline snapshot through both the registry store and the session-state store, reconciling every Window and Pane back by uid. The tmux mirror tests pin the exact session/window/pane option writes, the new window-scoped options, `automatic-rename off` on managed windows, `rename pane` touching only `@projmux_pane_label` and never `pane_title`, uid ↔ target resolution in both directions, and write-free legacy observation. `internal/app` and `cmd/projmux` prove the metadata usage-error seam: explicit name and rebind-root collisions reach exit code 2 through the existing `UsageError` path with zero mutations, while a newer registry schema stays a runtime error at exit code 1.
|
|
51
|
+
- `make test` / `make test-integration`: Registry-first control plane Phase 0 durable recovery envelope: `internal/integrations/metadata` proves the initialized boundary in both directions -- before the first commit every read answers the empty registry and creates nothing under the state dir, the first commit publishes `registry.json` plus the `registry.initialized` marker at 0600 below a 0700 directory, and after that a missing, empty, or whitespace-only registry is `ErrRegistryStateLost` on `Load`, `LoadReadOnly`, `LoadSnapshot`, `Update`, and `UpdateConvergent` instead of a fresh identity domain; a registry written before the marker existed still reads and gains the marker on its next write; each same-version semantic write copies the prior verified bytes into bounded `recovery/` copies whose chronological order survives retention, skips the copy for structurally invalid prior bytes, and a migration keeps its versioned `.bak` without spending a recovery slot; a convergent no-op leaves registry bytes, mtime, inode, marker, and copies untouched; an injected failure at the staged fsync, the staged write, staged validation, the recovery copy, the marker, the pre-replace directory sync, or the atomic replace leaves the prior registry byte-identical with the copy rolled back, no staged temp file, no lock, and a retry that still converges, while a failed first commit leaves neither a registry nor a marker so the next read is still first use; missing-after-initialization, malformed, too-new, and unreadable stay four distinguishable causes that create no registry and no uid; and the integration smoke repeats init -> updates -> no-op -> loss -> diagnostic -> marker-removal remedy against the installed binary under an isolated `XDG_STATE_HOME` below the smoke root, asserting the bounded five retained copies hold the bytes their writes replaced.
|
|
52
|
+
- `make test` / `make test-integration` / `make test-e2e`: Registry-first control plane Phase 1 registry recovery boundary: `projmux reconcile registry` plans with zero writes and restores only an explicitly named verified source. `internal/integrations/metadata` proves the plan classifies the current registry and every bounded candidate -- `valid`, `first-use`, `missing`, `empty`, `malformed`, `schema-too-new`, `invalid`, `unreadable` -- with a `sha256:` digest of the exact bytes and the resource/reservation counts, newest first over both the write-copy and replaced-copy families, while inspecting a first-use state dir creates nothing under it and inspecting a lost one leaves every file's mode, size, and mtime unchanged; malformed, empty, too-new, dangling-`ownerRef`, and duplicate-uid sources are refused as `ErrRecoverySourceRejected` with the live registry and the whole state dir byte-identical, including when the only candidate is a too-new envelope; files this store did not write are never offered as candidates; a valid restore publishes the verified bytes verbatim so uids, owner relations, and name reservations round-trip through `LoadReadOnly`, a known older envelope restores unrewritten and still migrates in memory only, a restore into a marker-less state dir establishes the boundary so a later loss reads as state loss, and a repeat restore is a byte no-op that takes no preserved copy and leaves the lock removed; the bytes a restore replaces are preserved -- malformed originals included -- at 0600 as their own bounded family that is offered back as a candidate and never consumes the write-copy history; a stale `--expect-source-checksum` or `--expect-current-checksum`, a source rewritten under the lock, and a registry replaced under the lock all refuse as `ErrRecoveryRaced` with nothing published, no preserved copy, and no staged temp file, an ambiguous or unknown selector is never ranked or guessed, and an injected staged fsync or directory sync failure leaves the registry and marker byte-identical. `internal/app` proves the command boundary: a dry-run is byte-identical output over two runs with the whole state dir unchanged and zero tmux calls, a sourceless run only ever plans and prints the exact guarded next command, every refusal still emits the report and exits non-zero with the current registry untouched, usage refusals happen before the store is opened, and the human projection carries the same state, candidates, reasons, and next command as the JSON. The mirror diagnostic is evidence only: it appears exactly when recovery is needed and no verified copy exists, resolves containment from stable tmux ids, counts uid-less panes as unattributed rather than recoverable, counts provider-carrying panes as proof of Agents whose uids are not mirrored, always states the six unrecoverable scopes, makes only read-only calls on the one exact socket, and reports a missing transport or a dead server as a reason without probing another server or generating a registry. The integration smoke repeats state loss -> preview -> guarded restore -> resource reads -> repeat no-op -> corrupt/too-new/ambiguous/raced refusal against the installed binary under an isolated `XDG_STATE_HOME` below the smoke root.
|
|
53
|
+
- `make test` / `make test-integration` / `make test-e2e`: Runtime topology exit cascade Phase 1 exact Offline Pane/Agent canonical deletion: unit/runtime tables require an explicit exact `uid:` plus durable `MissingRuntime=True/RuntimeUnbound` Pane evidence or an unbound `Offline` Agent whose retained Panes all carry that evidence, and require a positive exact socket identity plus non-empty inventory proving zero mirrors; implicit/name/scope/`--all`, no-server, empty/error/permission inventory, live-without-evidence, duplicate, and foreign ownership cases refuse with zero writes. Route tests pin byte-consistent dry-run/apply Registry-only plans and the exact `would be killed`/`was killed` diagnostics, locked zero-to-live, live-to-zero, generation, owner, duplicate, and foreign revalidation, repeat no-match idempotence, zero raw tmux kills, and preservation of the owning Window/root, sibling Panes, and a resumed live Agent bound to a newer Pane generation. The isolated exact-socket e2e repeats Pane and Agent dry-run/apply/repeat on app-owned and standalone hosts, keeps sibling Registry/tmux hashes and a foreign socket unchanged, and verifies that no canonical `kill-pane` targets the already absent runtime while missing-server authority is refused.
|
|
54
|
+
- `make test`: Registry-first control plane Phase 2 resolved resource graph: `internal/core/resourcegraph` is the one join between the Registry's desired topology and one exact tmux server, and it ships with its contract frozen rather than with a consumer. The attribution matrix table covers managed sessions/windows/Agent-owned panes, an app-owned control session, the same control marker refused on a standalone host, an unknown role value, an ephemeral scratch session, ephemeral outranking a control marker on one session, unmarked objects resolving to unattributed inside a managed enclosure or on an app-owned server and to foreign otherwise, unknown Project/Window/Pane uids resolving to recoverable, duplicate claims, a window whose session mirrors another Project, a pane whose window mirrors another Window, Pane and Agent uids mirrored onto the wrong kind of option, and absent containment evidence still binding on an exact uid. Status derivation is pinned for all four kinds including `missing-root` outranking a live session, an Agent deriving its managed Pane's status while its phase stays the Registry's, and a paneless Agent reading offline rather than unknown. The owner-chain invariant crosses every level of runtime containment and then asserts that no surviving binding disagrees with the Registry chain, so a cross-Project match is a test failure rather than a review question. Partial failure is a per-scope table: a failed sessions, windows, or panes query downgrades only its own rows to `unknown` with a stated reason while every row count is preserved. Determinism is proven by sixteen shuffled inventories marshalling byte-identically plus a fixed ascending tmux-id order, `Resolve` is proven not to mutate either argument, and a `go/parser` import audit is what keeps the package unable to write anything -- no filesystem, process, or tmux dependency can be added to it without that test failing. The transport table covers explicit `--socket`, an absolute `--socket-path` including cleaning and the relative-path refusal, the mutually exclusive flags, explicit flags outranking an inherited `$TMUX`, an inherited absolute socket path, a discarded relative inherited value, and no transport as a legal state; `@projmux_app` is trusted only at its exact value. `internal/integrations/metadata` covers the observation adapter: a fixed four-call budget that does not grow with a 200-session server and does not repeat across five `Observe` calls, every call pinned to the exact socket with an explicit mutating-verb denylist, the four transport modes (app-owned `-L`, standalone `-L`, explicit `-S`, and no transport with zero tmux calls), two sockets never reading each other, one failed query degrading exactly one scope while the full budget still runs, an absent server costing one call and reading as nothing-live rather than unknown, identical managed rows resolved from one tmux output under both host modes, and the routing layer refusing an absent transport, a non-tmux executable, and a missing runner instead of falling back to the default server. An opt-in real-tmux smoke (`PROJMUX_RESOURCEGRAPH_TMUX_SMOKE=1`, skipped by default so a unit run never spawns a server) builds its own isolated `TMUX_TMPDIR` and two unique sockets, strips the inherited `TMUX`/`TMUX_PANE` from every invocation, and proves the format strings and the containment join against real tmux: the managed Project/Window/Pane bind to the exact `$N`/`@N`/`%N` the server reported, Home resolves to control, a scratch session to ephemeral, a plain session to unattributed, a sibling socket carrying the same uids is never read and is unchanged afterwards, and cleanup kills only the exact `#{socket_path}` it has confirmed lives inside its own temporary root.
|
|
39
55
|
- `make test`: CLI information architecture v2 Phase 2 selector and read-only resolution: `internal/core/selector` unit and table tests cover the pure selector grammar and the read-only resolution engine over a structurally validated registry fixture that carries duplicate `displayName`s, an offline Project, a `MissingRoot` Project, and Window/Pane names repeated across owner scopes. An aggregate negative table proves the excluded forms resolve **0 occurrences** — an implicit comma split of two real names, a duplicate `displayName`, a real `spec.root`/`spec.cwd` path, a tmux `%N`/`@N`/`$N` handle, and a bare uid without the `uid:` prefix — while `displayName` is still reported as ambiguity context and the `uid:` form does resolve. A recorded per-stage trace pins the fixed name/uid-union -> AND label-filter -> uid-dedupe order with a 3/2/1 shape no other ordering can produce; repeated singular `--window`/`--pane` occurrences union in argv order and collapse by uid; the Pane universe is owner-scoped across Projects, Windows, and Agent-managed Panes; Window/Pane status is inherited from the owning Project as live/offline/`missing-root` with `MissingRoot` outranking a stale live session; and the resolver never mutates the registry it is handed. The `<verb, kind>` cardinality matrix pins the contract cells (`get windows` 0..N, `rename window` exact-one, repeated `create --window` 1..N) and a violation returns a usage error whose ambiguity listing is bounded to 5 candidate rows carrying name/`displayName`/owner context plus an omitted count. `internal/cli` proves `cwd` is accepted by exactly one canonical route: `ResolveOutputToken` grants it only on `get pane` and rejects it on every other read kind and on every named mutation route, the shared `-o` catalog still excludes it, and the new `get` node owns only the `pane` kind while the `current` compatibility route is left untouched. `internal/app` covers the route: `get pane --current -o cwd` emits a single trailing-newline path scalar from one tmux `display-message` query; `-o cwd` without `--current` and `--current` with any other projection or with selectors are usage errors with 0 bytes on stdout and 0 tmux calls; ambiguity, no-match, an emptied label filter, and an unresolvable `--project` scope are exact-one violations with 0 bytes on stdout; the output table pins default/uid/name/ref/metadata/json/none for a resolved read; the read loads the registry through a store path that creates no directory and no lock file when no registry exists; and `get` is excluded from the pre-dispatch legacy-hook filesystem migration. `internal/integrations/metadata` pins the zero-side-effect `LoadReadOnly` read, and `cmd/projmux` proves a bounded cardinality failure reaches exit code 2 with the listing on stderr and 0 bytes on stdout.
|
|
40
56
|
- `make test`: Implicit active target selector Phase 3 managed runtime binding convergence: a normal live-reload `config apply` invokes the existing registry matcher/reconciler/mirror only after successful `source-file` and routes every inventory and mirror call through the exact supplied `-L` socket; `--no-reload` and config/keymap preflight failures make zero live calls. Generated app config installs synchronous `after-new-window` and `after-split-window` boundaries that pass the expanded absolute `#{socket_path}` to hidden plumbing and use exact `-S` routing with no default socket or inherited `$TMUX` fallback. Unit/integration tables cover blank bindable Window/Pane repair to the original registry uids, ambiguous and foreign refusal, identity/owner/name-reservation preservation, two-socket isolation, and a repeat pass with zero `set-option`/`rename-window` calls and zero registry byte write. `get pane`, `describe pane`, and active-target resolution open zero write transactions and issue only the required option read. The CLI catalog, public option/environment surface, registry schema, Project scope, and heuristic/name/cwd matching remain unchanged.
|
|
41
|
-
- `make test` / `make test-integration` / `make test-e2e`:
|
|
57
|
+
- `make test` / `make test-integration` / `make test-e2e`: Registry-first control plane Phase 3 command-scoped controller kernel makes `internal/core/controller` the one authorization and ordering seam that `projmux reconcile resources` runs through: observe one exact server, resolve a `resourcegraph.Graph`, plan, commit the Registry, guard tmux, execute, reobserve. The policy is a closed intent x attribution table and is tested as one -- every cell is covered exactly once with a stated reason, `start`/`import`/`delete` are refused for all seven classes, repair is allowed only on `managed` and `unattributed`, `recoverable`/`foreign`/`conflict` are refused, `control`/`ephemeral` are observe-only, and an invented class fails closed. A verb gate refuses `new-session`, `new-window`, `split-window`, `kill-session`, `kill-pane`, and an empty argv even on a managed handle while still passing `set-option` and `rename-window`, so "convergence never created or killed a runtime object" is structural. Authorization resolves an operator-spelled `session:index` target to its stable `@N` before anything runs, refuses a target the observation never saw, attaches the uid guard plus the containment guard (`session_id` for a window, `window_id` for a pane) only to writes that will actually run, and walks containment two levels to recognize a managed enclosure. Plan order is proven determinism-under-shuffle for keys and marshalled bytes plus session-before-window-before-pane containment order, which is load bearing rather than cosmetic. App-level coverage pins acceptance directly: dry-run and execute project the identical key sequence, execute reports `reobserved: converged`, and the repeat is a no-op with zero Registry writes, zero tmux mutations, and byte-identical Registry and tmux state; a machine carrying an offline Registry Project, an app-owned `role=control` Home session, and an unattributed scratch session survives an execute with zero lifecycle verbs, unchanged session/window/pane counts, untouched control and scratch options, the offline Project neither started nor pruned nor duplicated, and the start/import/delete refusals stated in the report; a uid claimed by somebody else at the plan-to-execute boundary aborts at `tmux prevalidation` with zero writes, the durable Registry commit already recorded, and the exact retry; a `#{socket_path}` that changes under the run aborts before the first write; and an injected Registry commit failure reaches tmux zero times while keeping the stage list. The pre-existing partial-tmux-failure suite now converges through the kernel unchanged. The in-memory tmux fake gained `show-options -gv` for the `@projmux_app` marker and a targetless `display-message -p '#{socket_path}'`, which is the socket guard. The observation adapter gained one required fix the kernel depends on: tmux answers a read of an unset `@projmux_app` with a non-zero `invalid option`, so treating that as a failed query left host mode permanently `unknown` on every server projmux did not start and downgraded each unmarked object there from `unattributed` to `foreign`; an unset marker now reads as `standalone` with zero degraded scopes while a genuinely unreadable option still reads as `unknown`. Because that distinction only appears against real tmux, the policy carries one explicit grant: `reconcile resources` cannot run without an operator-named exact server, and that selection -- and only that selection -- makes an unmarked `foreign` object repairable; the grant is proven to change exactly the two foreign repair cells and to buy no import, start, or delete. Explicit topology materialization keeps its own engine and its own plan-time guard and is deliberately not routed through the kernel, because it plans against objects it is about to create.
|
|
58
|
+
- `make test` / `make test-integration` / `make test-e2e`: Registry-first control plane Phase 4 runtime diagnostics escape hatch adds `projmux get runtime sessions|windows|panes [--socket <name> | --socket-path <absolute>] [-o json|none]` and the `projmux runtime diagnostics` picker over a new pure package, `internal/core/runtimediag`, which projects the runtime half of a resolved `resourcegraph.Graph` and re-derives no attribution of its own. The pure tests pin that every observed object is emitted with its class, its reason, its stable tmux id and its fully qualified coordinate -- managed no-ops included -- that a `recoverable` object reports its mirrored uid and is still handed no resource identity, that a duplicate claim reaches both claimants as a conflict naming both handles, that a coordinate degrades from the session name to the `$N` id and disappears entirely when the enclosing session cannot be resolved, that a no-transport graph projects every scope unavailable with a never-null empty item list while a readable empty server projects neither, that the three report kinds are byte-identical across eight rebuilds and hold only their own object kind, and that the class tally names only the classes present in the closed declaration order. The route tests run the production observation adapter against a fake server carrying a managed Project session, the Home control session, a scratch session, an unmarked window and pane, and a window mirroring an unknown uid: they assert the full row set per kind, the human table golden and its host/transport header, the stable JSON envelope and its byte-identical repeat, the app-owned/standalone/no-transport/inherited-`$TMUX` matrix, and a call audit proving the read issues exactly `show-options`, `list-sessions`, `list-windows`, and `list-panes` -- four per observation, zero write verbs, and zero calls on a second socket that exists in the same fixture. The picker tests assert the rendered rows and tally, that Focus forwards `--target <session>:@N.%N --socket <#{socket_path}>` to the existing focus route, that Attach reaches `attach project uid:<uid>` only for a bound session and only outside tmux and otherwise renders the stated refusal, that Inspect reaches the shipped Resource Inspector with no arguments, that the whole action vocabulary of every row is those three plus Back and inert rows, and that ko-KR localizes the chrome while leaving every tmux id, uid, class, and name untranslated. The Linux integration smoke runs the read against three real isolated servers -- an app-owned host, a standalone host mirroring the same Registry uids, and a sibling that must never be read -- and proves the classes, the identical managed identity under both hosts, the control marker refused on the guest, the no-transport projection, byte-identical repeats, and unchanged Registry/tmux/sibling state. The Linux e2e opens `projmux runtime diagnostics` in a real `display-popup` on a real attached client, walks the list to its last row so every live session, window, and pane id is observed on screen, opens one action menu and asserts Focus/Attach-refusal/Resource Inspector with no destructive entry, and then proves the Registry bytes, the exact server, the sibling socket, and the client focus are all unchanged.
|
|
59
|
+
- `make test` / `make test-integration` / `make test-e2e`: Registry-first control plane Phase 5 makes the primary navigation surfaces Registry-first over a new pure package, `internal/core/registryview`, which projects a resolved `resourcegraph.Graph` plus the caller's filesystem discovery into three sections: the Registry Projects with their Windows, shell Panes, Agents and Agent-owned Panes; the discovered directories no Project claims; and one Runtime link. The pure tests pin the Project -> Window -> Pane/Agent hierarchy and its depths and parents in Registry slice order, that an Agent-owned Pane is emitted once and only under its Agent, that a no-transport graph yields the same row ids in the same order with status downgraded to unknown and the offline start/resume action still offered, that closing a runtime object changes only status and moves no row, that identity/order/eligibility are identical on an app-owned and a standalone host while the runtime tally moves the same object from unattributed to foreign, that no control or ephemeral runtime object can become a row, that a session literally named `home` with no role marker stays unattributed, that a discovered directory already claimed by a Project root is not duplicated, that a missing-root row offers rebind and delete only, that the Runtime link is emitted even on an empty Registry with no transport, that `Descendants` returns exactly one Project subtree, that an Agent phase is reported verbatim, and that `Build` mutates neither argument. The app tests run the production observation adapter against the same fake app-owned/standalone/sibling servers the Phase 4 escape hatch uses: three navigation refreshes issue exactly `show-options`, `list-sessions`, `list-windows` and `list-panes` -- four per observation, zero write verbs, zero reconcile or materialize, zero calls on the sibling socket -- while a no-transport read issues zero tmux calls and still lists the Registry rows, and both host modes produce byte-identical rows. The Projects picker tests pin the section order (Registry Projects, unregistered candidates, Runtime link), the identical row values with and without a server, the withdrawal of the Home control session, a scratch session and a hand-opened window from the managed list, the uid-carrying selection of a missing-root Project that used to fail the whole picker on directory validation, the Runtime link forwarding to `runtime diagnostics`, the dedicated key opening `Projects > Resources`, the declined Project lookup for an unregistered path, and Registry-sourced window tabs on an offline Project. The recent-session and recent-window tests pin id-based attribution (`$N` and `@N`, never a name join), the withheld class tally and its Runtime link, the managed resource name carried beside the exact tmux handle the action adapter targets, the always-kept current window, and full degradation to the observed list when no graph is available. The hierarchy action tests prove every action forwards to a shipped route: `focus --target <coordinate> --socket <exact path>` for a live row, `attach project uid:<uid>` for an offline Project and for the owning Project of a row beneath one, `agent resume uid:<uid>` for an Agent, and `runtime diagnostics` for the link -- with rebind and delete listed as eligible with their exact command rather than executed. `internal/integrations/tmux` now reads `#{session_id}` with the recent-session rows so that pairing is exact. The integration smoke asserts the non-interactive `switch preview` projection of the view model on a real app-owned server, on a real standalone server, and outside tmux: three Registry rows with live/unknown status, byte-identical row identity and order across the transition and across the two hosts, the per-host Runtime tally, no Registry created by a no-transport read, and a Registry plus both servers plus the sibling socket unchanged. The e2e smoke drives a real 80x24 popup through a real attached client: the Projects sidebar shows the Runtime link with its control/ephemeral/recoverable tally, the filter finds the managed Project row, the dedicated key opens the read-only hierarchy with the host header and live rows and without any runtime-only object, and after the managed session is killed the same Project is still a row whose hierarchy reports offline with the start action -- with the Registry byte-identical, the sibling socket unchanged, and the attached client never moved.
|
|
60
|
+
- `make test` / `make test-e2e`: Registry-first control plane Phase 5 corrective, Project sidebar presentation order: Phase 5 inverted the row *source* correctly and then ordered the result by nothing but the pin flag, which put `$HOME` behind every managed Project and left liveness out of the order entirely. Membership stays the Registry's and order becomes the sidebar's: `internal/app/switch_registry_rows.go` partitions the managed Projects into three presentation tiers -- pinned, then live, then closed -- with Registry order preserved inside each tier as a stable tie-break, and `internal/app/switch.go` lifts the discovered `$HOME` row out of the unregistered section to lead the whole list as chrome. Nothing is synthesized and nothing is stored: a tier is a projection of one exact host's live overlay, `internal/core/registryview` still emits Registry order, and no Registry schema, order field, or desired topology is involved. Because a tier can move a row between refreshes, the sidebar refresh no longer lets the cursor ride a position or a path: it resolves the pre-refresh selection to its Project uid and that uid back to whatever row the Project renders as now, so a Project that loses its root and starts carrying `uid:<uid>` keeps the cursor too. The unit tests pin the exact mixed-fixture order (Home, the two pinned Projects whether live or closed, the two live Projects, the two closed Projects, the unregistered candidate, the Runtime link) over a Registry whose slice order disagrees with every tier, the isolated pinned-offline-beats-unpinned-live rule, the no-transport collapse into one closed tier in Registry order, byte-identical rows and order across app-owned and standalone hosts with the same live overlay, Home leading the list while resolving to no Registry Project and claiming no uid anchor, the uid anchor surviving both a tier change that provably moves the row's index and a root that goes missing under it, the absent anchor for Home/Settings/the Runtime link/an unregistered path, and a negative audit that three refreshes over the anchor's extra bounded read issue no write verb, never touch the sibling socket, and leave the exact server byte-identical. The e2e smoke extends the real-tmux Projects sidebar with a second managed Project so the live and closed tiers are both populated, reads the rows out of a real pane rather than a popup so their order is observable, and proves the order flips exactly as the live overlay changes across a real open -> close -> refresh while the Registry stays byte-identical.
|
|
61
|
+
- `make test` / `make test-integration` / `make test-e2e`: public resource reconciliation adds `projmux reconcile resources [--dry-run] [--materialize-project <name|uid:uid>] [--socket <name> | --socket-path <absolute>] [-o json]` to the CLI manifest/help/reference. One planner runs the production Registry reconciler, mutator, matcher, and tmux Mirror over a cloned Registry and a zero-write tmux overlay; normalized allocation display values keep stable item keys/order without making opaque UIDs a matching key. Tests pin missing/stale/foreign/orphan Registry and tmux items, human/JSON target plus changed/no-op/failed counts, exact `-L`, absolute/inherited `-S`, no-flag outside-tmux pre-mutation usage refusal, two-socket isolation, dry-run byte stability, repeat no-op, and whole-session containment for unknown/duplicate/wrong-owner bindings, including a rootless UID-less session whose name is claimed by multiple Registry Projects. Execute scopes observation to Registry graphs safely attributable to the selected socket, replans under the Registry lock, commits Registry authority before non-transactional mirror writes, guards every planned live write through its current Project/Window/Pane UID binding before the first mutation, and reports completed stages, each current remaining drift item in human and JSON output, and the exact retry. Injected Registry commit failure publishes zero planned UID and changes neither Registry nor tmux; injected post-commit tmux failure leaves one authoritative identity graph that the exact retry converges without duplicates. A second socket and its pre-existing Registry graph remain byte/model identical. `doctor`, `get`, and `describe` remain read-only, the route skips unrelated legacy/config migration, and no config apply, default-socket fallback, state-loss recovery, heuristic merge, rename/rebind, or Agent semantic-status work is introduced.
|
|
62
|
+
- `make test` / `make test-integration` / `make test-e2e`: explicit Registry desired-topology materialization adds the exact-one `--materialize-project <name|uid:uid>` opt-in to `reconcile resources` without changing the default plan or strict reads. A separate pure Project graph plan and ledger executor cover deterministic zero-write human/JSON preview, offline `-L` full and live `-L`/`-S` partial creation in Registry insertion order, fresh session/Window primary binding, primary reconstruction from an exact-bound shell anchor, additional Window/shell Pane creation, exact uid/name/owner/CWD mirrors, stable retries that preserve selector and socket, second-run no-op with no ensure/lease/write, raw-kill recreation versus canonical-delete exclusion, and two-socket isolation. Negative tables reject duplicate selectors, a present-but-blank selector that must not degrade into the broad default reconcile (zero Registry read, transaction, write, tmux call, and output), invalid root/CWD, zero Windows, non-shell or indirect primary refs, every pre-existing uid-less Pane, unknown/duplicate/wrong-owner claims, name collisions, races, and injected failures before mutation or with ownership-checked reverse rollback and explicit residual drift. Pre-mutation owner-set proofs reject a Window relinked to a foreign session, a Pane join-paned out of its planned Window before planning, and the same move injected at the plan-to-execute boundary. A server-wide uid preflight runs before the selected Project session is created, so an offline Project whose desired Window or Pane uid is already claimed by a foreign session is refused with zero sessions created, zero pre/post-create hook runs, and zero runtime mutation; the inventory is refreshed after the session exists to cover its new tuple and any race since. a `new-window` or `split-window` that tmux mutated before reporting a synchronous hook failure has its exact attributed handle claimed and ledgered so rollback removes it, leaving byte-identical runtime and Registry. Call logs prove stored commands, Offline/Failed Agents, Agent-owned Panes, snapshots, notifications, ephemeral sessions, and historic geometry are never replayed. Initial Pane cwd is separate from the unchanged Project hook/root cwd. The isolated real-tmux smoke uses inherited-environment removal plus a unique `TMUX_TMPDIR` and two exact `-L` sockets, and observes zero-write preview, live partial Window/Pane repair back to the original uids, a zero-write repeat no-op, an empty start command on every recreated Pane, canonical delete that is not replayed, offline full topology rebuild under the original Project/Window/Pane uids, no Agent left Running, a byte-identical second socket, and cleanup of only the sockets whose queried `#{socket_path}` is under the smoke root. Offline arbitrary `-S` session creation is an explicit safety refusal because the public name-only `PROJMUX_SOCKET` cannot encode an exact socket path; live partial `-S` remains supported, and the refusal can be lifted only with a future versioned hook socket-path contract.
|
|
63
|
+
- `make test` / `make test-integration` / `make test-e2e`: Declarative contract stabilization Phase 15 projects one explicit snapshot into one exact closed Project Registry subtree. `FuzzSnapshotProjectionIsScopedValidAndIdempotent` and metadata/legacy/collision tables pin pure planning, target Project identity, unrelated Project and ControlSession preservation, owner/kind collision refusal, and a zero-diff repeat; commit tests require pre-commit zero writes, byte-identical source snapshots, ordinary materialization only, committed desired state plus an item notice after runtime refusal, and exact `-L projmux switch-client -c` as the final handoff. Closed-Project startup exposes exactly `Continue project` and `Open fresh`, with Esc navigation and picker-failure Continue fallback. `FuzzOpenFreshKeepsOnlyCanonicalProjectShell` is the production Fresh planner: the canonical Project Window and one Window-owned shell Pane UID/recipe survive when available, an exact minimum shell is created when absent, and every other target descendant, reservation, and conversation pointer is removed. Invalid anchors refuse with zero writes, repeat planning is a Registry zero-diff, and snapshot bytes are unchanged. Negative audits keep direct snapshot replay, global Registry replacement, snapshot deletion, retired startup labels, and hidden runtime fallback out of the startup path.
|
|
64
|
+
- `make test` / `make test-integration` / `make test-e2e`: Declarative contract stabilization Phase 10 closes managed runtime mutation behind printable typed plans. Named coverage keeps the verb inventory and product-surface table closed, proves deterministic bytes and successful execute → exact effect ledger → empty replan, rejects unknown observation with zero Registry deletion/runtime kill, audits every non-test app and tmux/mux/metadata/replay source (including typed helpers, argv callbacks, and variable argv), and bijects generated catalog, Pane-menu, popup, hook, and native provider/resume picker producers to planned or exact semantic-exemption rows. The executor validates printable Socket/PhysicalSocket/server-generation authority before pre-effect reobservation can declare a repeat empty, then validates every pending semantic guard before the first write. Focused controller tests cover Project/ControlSession Window create/rename, Project/sidebar and Session-picker stop, first-use Project and attach Home bootstrap, typed automatic layout operands, batch Pane/Window delete, app-owned path/pid/app/logical guards, exact inherited standalone `TMUX=path,pid,index` plus producer-anchored `$N`/`@N`/`%N` authority, and explicit controller `--socket-path` authority with PID/blank markers plus action UID/session/window guards and no inferred Pane. Partial-marker/forged/recycled-server refusal, fresh bootstrap generation binding, full-tuple owned reverse rollback, and self-target commit/flush ordering are pinned. Generated popup/menu actions carry their verified Pane anchor into the child without synthesizing `TMUX_PANE`; Settings persists config but refuses live source/retirement on standalone authority. The isolated tmux suites remove inherited `TMUX`/`TMUX_PANE` where independence is required, use a unique socket root, assert physical `-S` writes, and clean only exact observed sockets.
|
|
42
65
|
- `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.
|
|
43
66
|
- `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.
|
|
44
|
-
- `make test` / `make test-e2e`:
|
|
67
|
+
- `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.
|
|
68
|
+
- `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.
|
|
45
69
|
- `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.
|
|
46
70
|
- `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.
|
|
47
71
|
- `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.
|
|
@@ -49,19 +73,21 @@
|
|
|
49
73
|
- `make test`: CLI information architecture v2 Phase 3 public verb-to-kind and runtime-domain parity: `internal/cli` grows six canonical top-level nodes (`delete`, `describe`, `rebind`, `rename`, `restore`, `runtime`) alongside the existing `get`, taking the manifest to 40 public routes plus the 2 hidden helpers with a 15/7/13/5 disposition tally and zero orphans in either direction; a written-out inventory table proves all 33 originally inventoried routes survive with an unchanged disposition and hidden flag, the added kind tokens do not shadow an existing subcommand under `pin`/`tag`/`prune`/`attach`, and both goldens are regenerated together. `internal/core/selector` adds the Project and Agent resolution pipelines through the same three-stage engine — a Project list read over the whole registry, an Agent set scoped by `--project`/`--window` with Window-scoped name uniqueness — and keeps `displayName`, `spec.root`, and tmux handles structurally unmatchable; Agent references render without a `--agent` flag spelling because the grammar has none. `internal/app` covers the routes: the plural reads are 0..N with an empty result as a success and a `<Kind>List`/`<Kind>MetadataList` envelope for the structured modes, `describe` is exact-one per kind with a key/value description block, and a maintained adoption table asserts every route resolves against the declared `<verb, kind>` cardinality cell rather than a per-route rule. `focus project|window|pane` resolves an already-live exact-one target from read-only `list-sessions`/`list-windows`/`list-panes` and exits 2 on an offline Project, Window, or Pane, on a prefix-match fallback, and on an ambiguous live match, with zero session/window/pane-creating tmux verbs in the call log, while the legacy `focus --target` spelling keeps its prefix-match and index fallbacks. `attach project` forwards to the Project open path only from outside tmux and returns a usage error naming `projmux focus project <ref>` from inside a client, reaching the open path zero times on every refusal. `rename` changes only `metadata.name` and its scope reservation, and a collision, an ambiguous target, or a missing `--name` commits zero writes. `rebind project` rewrites only `spec.root`, measured by walking a real temporary directory tree before and after, refuses a non-existent, relative, or already-bound root with zero writes, and leaves two Projects that come to share a basename as two uids. `prune project` requires `--missing` and `--older-than`, lists candidates bounded to 5 rows plus an omitted count without opening a write transaction, excludes recovered roots, live runtimes, and freshly observed missing roots, deletes only under `--yes`, and short-circuits an empty registry. `delete` preflights the full uid set and the whole descendant plan, prints it under `--dry-run` with zero transactions, requires confirmation for any cascade or multi-target run while exempting an exact-one leaf Pane, exits 2 without `--yes` in non-interactive mode, aborts when the cascade plan changes between preflight and execution, and leaves a validating registry in which a Window cascade preserves its Project, an Agent cascade preserves its Window and sibling Panes, and deleting an Agent-owned current Pane leaves the Agent alive as Offline. The runtime domain and the remaining canonical spellings are parity aliases proven by an argv-forwarding table that pins the exact tail handed to `sessions`, `attach auto`, `kill tagged`, `tag`, `runtime prune`, `notify list|ack`, and `session-state status|delete|restore`, relays their streams and errors untouched, and reaches no other handler. `get` and `describe` stay off the pre-dispatch legacy-hook filesystem migration while the mutation routes keep the historical behavior.
|
|
50
74
|
- `make test`: CLI information architecture v2 Phase 4 Agent namespace and lifecycle parity: `internal/cli` grows two canonical top-level nodes (`agent`, `create`), taking the manifest to 42 public routes plus the 2 hidden helpers with a 17/7/13/5 disposition tally and zero orphans in either direction; the written-out inventory table still proves all 33 originally inventoried routes survive unchanged, `ai`, `usage`, and `status usage` included, and both goldens are regenerated together. The Agent provider enum is derived from the provider catalog rather than hand-written, so an aggregate table proves `selective`, `resume`, and `shell` are members of neither the enum nor the create-shortcut set while `selective`/`resume` stay a named closed picker-adapter concept; the `create <provider>` shortcuts are opt-in through an explicit catalog flag, collide with no resource kind or shared verb, and render in their own `Provider shortcuts` help group instead of among the resource kinds. `internal/app` covers the routes: a written-out argv table proves `create agent --provider`, `create codex|claude|antigravity`, and `create pane` normalize onto the exact `ai split` argv the current spelling parses — `--placement` for the bare positional, `-o pane-id` for `--print-pane-id`, and the `--` payload forwarded untouched — while a missing `--provider`, a picker spelling, `shell`, an unknown provider, a shortcut that respells its own provider, a `left` placement, and the legacy `--force-agent`/`--agent`/`--print-pane-id` flags are all usage errors that reach the split handler zero times, and the projections needing the resource-backed create composition fail as runtime errors so exit 2 keeps meaning invalid input. `agent status|topic|integrate` forward raw argv to the handler that owns them and `agent usage` forwards to the top-level usage handler instance with no prefix at all, which is measured as byte-identical stdout/stderr/exit against `usage` for six flag spellings. `agent resume` resolves exactly one existing Agent through the shared selector engine and the newly declared `<resume, Agent>` matrix cell, accepts only Offline and Failed, refuses a Running Agent with a usage error that names `focus pane` rather than performing it, and on every path forwards to no other handler and opens zero registry transactions. The Agent lifecycle table pins the `Pending -> Running -> Offline/Failed` transition set and the exit-classification mapping, and both a normal managed-Pane exit and an explicit `delete pane` through the public route leave the Agent alive as Offline with its uid, name, and owner intact and its `paneRef` cleared.
|
|
51
75
|
- `make test`: CLI information architecture v2 Phase 5 internal plumbing isolation: `internal/cli` adds the hidden `internal` namespace, keeps plumbing out of public listings, and proves zero catalog/canonical/handler orphans. Generated standalone/app config and popup/hook payloads are exhaustively extracted so every emitted command uses an executable `internal` route. Raw tails, unknown flags, tmux formats, direct help, diagnostics classification, and bounded TTY helpers remain covered through their canonical internal spellings; newly generated artifacts contain no pre-namespace plumbing route.
|
|
52
|
-
- `make test`: CLI information architecture v2 Phase 6 detached Project runtime materialization and Window/Pane create: `internal/cli` makes `create window` a real executable node and gives both resource-backed create nodes the full shared `-o uid|name|ref|metadata|json|pane-id|none` catalog, with the public route count, disposition tally, and both goldens unchanged because no top-level route is added; a help-honesty test resolves every advertised `-o` token against the canonical route, runs each one end to end, and rejects any usage synopsis naming a flag the parser does not define. `internal/core/selector` adds the `<create, Project>` exact-one cell beside the existing `<create, Window>` 1..N and `<create, Pane>` exact-one cells, and the maintained adoption record gains all three. `internal/app` covers the routes against an in-memory tmux server that models sessions, windows, panes, scoped options, and the escaped format separator: a Project whose `status.session` is absent or not live is materialized in the background and its brand-new session's initial window and pane are adopted onto the Project's first stored Window and its `
|
|
53
|
-
- `make test-e2e`: CLI information architecture v2 Phase 6 detached create against a real tmux server on a dedicated exact socket under the smoke root, with inherited `TMUX`/`TMUX_PANE` stripped from every call, the real `#{socket_path}` proven to be inside that root before cleanup, and only that socket killed: a pre-create hook refusal creates neither the registry nor a session and writes nothing to stdout; a post-create hook failure still commits and prints its result with the warning on stderr, and the registry lands at 0600; the legacy naming migration gives an `automatic-rename on` Window the stable name `window`, retains its observed `window_name` as the separately described displayName instead of deriving identity from its user Pane label, turns automatic-rename off, and mirrors the Window and Pane uids onto the live objects; `--placement right` and `--placement down` produce the two split axes measured from real `pane_left`/`pane_top` geometry, run from inside a real pane whose completion signal is an exit-code marker file rather than pane contents or `send-keys`, and leave the session's active window and pane unchanged with neither new pane active; `--create-window` converges on exactly one Window and still returns a `pane/` ref; a stale `
|
|
76
|
+
- `make test`: CLI information architecture v2 Phase 6 detached Project runtime materialization and Window/Pane create: `internal/cli` makes `create window` a real executable node and gives both resource-backed create nodes the full shared `-o uid|name|ref|metadata|json|pane-id|none` catalog, with the public route count, disposition tally, and both goldens unchanged because no top-level route is added; a help-honesty test resolves every advertised `-o` token against the canonical route, runs each one end to end, and rejects any usage synopsis naming a flag the parser does not define. `internal/core/selector` adds the `<create, Project>` exact-one cell beside the existing `<create, Window>` 1..N and `<create, Pane>` exact-one cells, and the maintained adoption record gains all three. `internal/app` covers the routes against an in-memory tmux server that models sessions, windows, panes, scoped options, and the escaped format separator: a Project whose `status.session` is absent or not live is materialized in the background and its brand-new session's initial window and pane are adopted onto the Project's first stored Window and its `defaultShellPaneRef` instead of being left as an orphan; the result kind follows the route rather than the side effects, so `create window` emits a `WindowList` even though it also creates a Pane and `create pane --create-window` emits a `PaneList` even though it also creates a Window; a missing `--window` name is a plain no-match with zero mutations and only `--create-window` promotes it to an atomic Window-plus-initial-Pane ensure inside the same single transaction, while an already-existing name is reused rather than duplicated; every fan-out target splits its own anchor exactly once, an explicit `--pane` resolves exact-one inside each target Window, and a missing or stale `spec.defaultShellPaneRef` is exit 2 with 0 stdout, 0 registry writes, and no `split-window` at all even when a live pane is available to fall back to; a pre-create hook refusal leaves zero registry mutations and zero runtime objects while a post-create hook warning is a logged success; no path issues `switch-client`, `select-window`, `select-pane`, or `attach-session`, reads any focus format, or omits `-d` from a window or pane creation; `--placement right|down` maps onto the closed `-h`/`-v` split axis; the runtime ledger rolls back in reverse order and only removes objects still carrying the uid this operation mirrored onto them, leaving a bystander session and a re-bound window untouched; the negative table proves `displayName`, bare uids, commas, `--create-window` without an exact name or with `--selector`, `left` placement, the Pane-read `cwd` projection, and a `MissingRoot` Project are all usage errors with zero mutations; and `create pane` without `--project` still forwards the byte-identical legacy shell-split argv, with the discriminator pinned across every flag spelling and proven not to fire on payload text. The registry's first real production use is exercised against the actual locked store on a real filesystem: a create from a completely empty state creates `registry.json` at 0600 below a 0700 directory with no leaked lock, registers every discovered workdir with a bootstrap Window and a resolvable `defaultShellPaneRef`, and validates; a second create extends it without renumbering or re-identifying a Project; six concurrent `--create-window` runs on one name serialize through the `O_CREATE|O_EXCL` lock and converge on exactly one Window uid with one Pane per racer and one live tmux window; and a legacy-state fixture proves the first real import allocates `window`/`window-1` independently of runtime attributes, projects the observed `window_name`s into displayName, exposes the separation through `describe window`, turns `automatic-rename` off, mirrors the allocated uids back onto exactly the tmux objects it imported, and does not create new identities on a second pass. `internal/integrations/metadata` pins the tmux format separator contract that first use exposed: formats carry the escaped `\037` spelling because tmux 3.5a renders a raw `0x1F` as its octal escape while tmux 3.6 passes it through, and row parsing folds the escaped spelling back before splitting.
|
|
77
|
+
- `make test-e2e`: CLI information architecture v2 Phase 6 detached create against a real tmux server on a dedicated exact socket under the smoke root, with inherited `TMUX`/`TMUX_PANE` stripped from every call, the real `#{socket_path}` proven to be inside that root before cleanup, and only that socket killed: a pre-create hook refusal creates neither the registry nor a session and writes nothing to stdout; a post-create hook failure still commits and prints its result with the warning on stderr, and the registry lands at 0600; the legacy naming migration gives an `automatic-rename on` Window the stable name `window`, retains its observed `window_name` as the separately described displayName instead of deriving identity from its user Pane label, turns automatic-rename off, and mirrors the Window and Pane uids onto the live objects; `--placement right` and `--placement down` produce the two split axes measured from real `pane_left`/`pane_top` geometry, run from inside a real pane whose completion signal is an exit-code marker file rather than pane contents or `send-keys`, and leave the session's active window and pane unchanged with neither new pane active; `--create-window` converges on exactly one Window and still returns a `pane/` ref; a stale `anchorPaneRef` is exit 2 with 0 stdout, a byte-identical registry, and no new pane; and an injected `split-window` failure rolls the operation back to the exact window count and registry bytes it started from.
|
|
54
78
|
- `make test`: CLI information architecture v2 Phase 7 Agent create composition: `create agent` and the `codex`/`claude`/`antigravity` shortcuts gain a resource-backed detached route behind the same `--project` dispatch discriminator `create pane` uses, so the `--project`-less invocation stays the byte-identical `ai split` bridge while the canonical route allocates a Window-owned Agent plus its Agent-owned `<agent-name>-pane` managed Pane, reuses the Phase 6 anchor resolution, `--create-window` ensure, operation ledger and rollback, and enforces a new `{create, Agent} = 1..N` fan-out cell that four tests falsify when it is weakened; a matrix covers provider-required refusals on every canonical spelling, shortcut-to-canonical registry-snapshot equivalence, non-reuse against a Window that already owns a running Agent of the same provider (`codex` → `codex-1` → `codex-2`, first Agent's `status.paneRef` untouched), Window-scoped explicit `--name` (legal across two Windows, exit 2 with zero mutations inside one), payload-after-`--` reaching the provider launch but never the Agent or Pane name, all seven shared `-o` modes end to end with `AgentList`/`AgentMetadataList` envelopes for a single result and `-o pane-id` resolving to the managed Pane rather than the anchor, multi-window fan-out hitting each Window's own anchor exactly once, four failure stages each leaving the registry byte-identical with no `Failed` Agent behind, six racers converging on one ensured Window uid under the real on-disk lock with `-race -count=10`, the Settings enabled-agents gate applying to the canonical route as a plain error without promoting `--force-agent`, and a help-honesty audit that runs every advertised `-o` token and every advertised `--flag` of the Agent node and all three shortcuts; the identity projections of the compatibility bridge become exit 2 naming `--project` as the fix instead of exit 1 claiming the composition is unwired.
|
|
55
79
|
- `make test-e2e`: CLI information architecture v2 Phase 7 Agent create against a real tmux server on the same dedicated exact socket under the smoke root, with inherited `TMUX`/`TMUX_PANE` stripped from every call and only the verified `#{socket_path}` killed: a stub provider binary proves the launch actually ran in the created pane with the project root as its cwd, the managed pane carries both the Projmux Pane uid mirror and the `@projmux_ai_managed`/`@projmux_ai_agent` options, the active window and pane are unchanged and the new pane is not active, the `codex` shortcut allocates `codex-1` instead of reusing the first Agent, a `--` payload reaches the provider argv while the name stays `codex-2`, a missing `--provider` and an explicit name collision are each exit 2 with zero stdout and an unchanged registry md5, and the `--project`-less bridge still refuses its identity projections with a message naming `--project`.
|
|
56
80
|
- `make test` / `make test-e2e`: canonical create even-axis parity shares the legacy AI split geometry parser, row/column peer selector, and deterministic remainder distributor across `create pane`, `create agent`, and every provider shortcut; route tests pin split → Window-local geometry read → axis-only resize ordering for explicit/default anchors, right/down placement, fan-out, ensured Windows, one-pane no-op, malformed/unreadable topology, unrelated rows/columns, and silent post-split observation/resize failures with zero false rollback. The shared geometry format carries the materializer's escaped `\037` row separator rather than a raw tab, and a dedicated test pins both that the format holds no raw tab and that a `_`-sanitized row parses as no geometry: tmux 3.5a rewrites a raw tab in list output to `_` while 3.6 emits it verbatim, so the tab spelling parsed every row as a single field and made equalization a silent no-op on 3.5a — the version the e2e image runs. The isolated real-tmux create smoke repeats horizontal and vertical canonical creates to four panes, measures a maximum one-cell size difference, preserves raw pane IDs, Registry mirrors, focus, and an unrelated Window's exact geometry, and retains exact socket-contained cleanup.
|
|
81
|
+
- `make test` / `make test-e2e`: resource scope short-option parity registers `-p` for Project and `-w` for Window on the shared selector, standalone Pane read, resource-backed create, and canonical focus seams, while `-A` remains exclusive to `get windows|panes|agents`. Deterministic matrices compare long-only, short-only, and mixed argv for selector results, cardinality/error bytes, positional interleave, repeated Window order, stdout/stderr/exit, Registry mutation, and tmux call plans; explicit negatives keep Pane without a shortcut, reject `-A` elsewhere, and leave `agent usage --window` without `-w`. Help-honesty and generated-reference checks require every advertised alias to parse and preserve the root-help and command-tree goldens byte-for-byte. The isolated real-tmux smoke exercises short-form create agent/pane, read, focus, and all-Project escape on its exact guarded socket, with the first `--` preserving all following scope-looking payload bytes.
|
|
57
82
|
- `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.
|
|
83
|
+
- `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.
|
|
58
84
|
- `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.
|
|
59
85
|
- `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.
|
|
60
86
|
- `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.
|
|
61
|
-
- `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 —
|
|
87
|
+
- `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.
|
|
62
88
|
- `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.
|
|
63
|
-
- `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.
|
|
64
|
-
- `make test`: Agent liveness transition Phase 0 a dead managed Pane releases its Agent: when the tmux pane behind an Agent's managed Pane is gone the Agent moves to `Offline` and drops `status.paneRef`, so `agent resume` reaches its normal path without an explicit `projmux delete pane` first. The subject is an inventory diff rather than an event handler and the tests state it as one: `after-kill-pane` fires with an empty `#{hook_pane}` so no hook can name the pane that died, and `#{pane_dead_status}`/`#{pane_dead_signal}` are empty at hook time for a clean exit, a non-zero exit, and a SIGKILL alike -- they populate only under `remain-on-exit on`, which suppresses `pane-exited` entirely and changes pane lifecycle -- so
|
|
89
|
+
- `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.
|
|
90
|
+
- `make test`: Agent liveness transition Phase 0 a dead managed Pane releases its Agent: when the tmux pane behind an Agent's managed Pane is gone the Agent moves to `Offline` and drops `status.paneRef`, so `agent resume` reaches its normal path without an explicit `projmux delete pane` first. The subject is an inventory diff rather than an event handler and the tests state it as one: `after-kill-pane` fires with an empty `#{hook_pane}` so no hook can name the pane that died, and `#{pane_dead_status}`/`#{pane_dead_signal}` are empty at hook time for a clean exit, a non-zero exit, and a SIGKILL alike -- they populate only under `remain-on-exit on`, which suppresses `pane-exited` entirely and changes pane lifecycle -- so a hook cannot classify a death from tmux at all -- which is why the classification now comes from the durable receipt the supervisor or the control action wrote instead, and only a death with no receipt at all is treated as unproven; the Phase 9 entry below supersedes this paragraph's `AgentExitNormal`-for-everything rule and its `deadAgentPaneReason` spelling. `internal/integrations/metadata` gains `Mirror.LivePaneUIDs`, the mirrored-uid half of the diff, sharing its one `list-panes -a -F '#{@projmux_pane_uid}\037#{pane_id}'` query with `PaneTargetForUID` so the two can never disagree about which Panes still have a transport binding. `internal/app` covers the sweep: the core case asserts `Offline`, a cleared `paneRef`, a deleted managed Pane, the recorded liveness reason, and a `status.sessionRef` preserved pointer-identical, because the conversation pointer is exactly what makes a released Agent resumable; a five-row phase table cross-checks the selection against `CanTransitionAgent` itself rather than against a hardcoded list, so a phase outside the closed table is skipped with its Agent, `paneRef`, and managed Pane all untouched instead of forced to `Offline`, and the table is never widened; and an Agent holding no `paneRef` is never handed a fresh reason or transition timestamp. Cost is measured in transactions rather than in an unchanged registry, because both tmux pane-exit hooks fire on every pane exit in every session: a sweep that finds every mirrored uid live queries tmux exactly once and opens **zero** write transactions, an unreadable inventory releases nothing, reports no error, and opens zero transactions -- fail closed, since an unreadable inventory is indistinguishable from an empty one and treating it as empty would offline every managed Agent on a machine whose tmux server simply is not up -- and a real death opens exactly one. Both triggers are proven end to end: the reconciler path releases a dangling `paneRef` through a real mutation route while leaving a mirrored Agent `Running` on its own Pane, the ordering is read off the recorded tmux argv so the inventory query must follow the last `@projmux_pane_uid` write of the same pass (a freshly imported Pane must never look dead, which is also why there is no one-shot migration -- the first pass resolves every pre-existing dangling ref and the second finds nothing left to do), and the hidden `internal tmux release-dead-agent-panes` route dispatches, releases, opens one transaction, prints nothing, and refuses arguments because it is a sweep and not a per-pane handler -- it is now the widest-legal-event entrypoint into the shared reconciliation below rather than a second implementation. The generated `pane-exited` and `after-kill-pane` hooks are pinned as one shared backgrounded body that runs `rebalance-panes` first under its own `|| true` and then the sweep under its own, so pane layout never depends on whether the registry sweep succeeded and the sweep still runs when there was no layout to rebalance; the theme config byte-identity goldens move with that shared addition. The pre-existing explicit `delete pane` -> `Offline` transition is re-pinned unchanged and still recorded as `deleted`, so the two paths stay distinguishable in `describe agent`. A pane-exit sweep that waits behind a newer create now refreshes the exact mirrored Pane UID inside the Registry transaction before releasing anything, preserving the newer Agent as `Running` with its Agent-owned Pane; a failed locked refresh aborts with zero writes while a true death still commits one release.
|
|
65
91
|
- `make test`: Registry runtime observation Phase 0 observed status for Window and Pane: `Window`/`Pane` status is no longer inherited from the owning Project's stored `status.session.live` bool -- a bool written `true` at legacy import, refreshed only by the reconciler, and never seen by the read verbs at all, so a registry object bound to nothing live reported `live` forever. `internal/core/selector` gains `ObservedStatus(missingRoot, bound)` as the single derivation rule every kind goes through, pinned by a four-row truth table plus a resolver table that reads one unchanged registry against three different machines (the alpha project running, one pane of a live window closed, and nothing running at all) and requires the reported status to follow the observation every time; the negative guard flips `status.session.live` both ways against one fixed observation and demands byte-identical Window and Pane statuses, which is the falsifiable form of "zero remaining stored-value paths", and it is repeated at route level over real `get windows`/`get panes` output. `MissingRoot` precedence is re-pinned on all three kinds at once -- Project, plus a Window and a Pane the observation says are demonstrably live -- so the preservation contract still outranks a stale runtime. `internal/integrations/metadata` gains `Mirror.LiveWindowUIDs`, the Window half of the registry-versus-machine diff, sharing its one `list-windows -a` query with `WindowTargetForUID` exactly as `LivePaneUIDs` shares `list-panes -a` with `PaneTargetForUID`; both inventories are proven to drop unclaimed tmux objects, to issue exactly one read command each, to write nothing, and to propagate a query failure rather than flatten it into an empty inventory. `internal/core/metadata` gains `RuntimeObservation`, a `MissingRuntime` condition with reason `RuntimeUnbound` on `Window.status` and `Pane.status`, and `Mutator.ObserveRuntimeBindings`, covered for set/clear symmetry, a preserved `firstObservedAt` across repeat observations, and the preservation contract itself: no resource deleted, no uid or name changed, no name reservation released; `Window.status` is declared `omitzero` and a serialization test proves a condition-free Window still round-trips byte-identically to a pre-observation registry, which is what keeps the addition inside `schemaVersion: 1`. `internal/app` covers the observation seam and the reconciler: the snapshot is taken once per process invocation and memoized (five resolutions, one `list-panes -a` and one `list-windows -a`), a failed query fails to *offline* per inventory half and never back to a stored value, and a nil lookup is the empty observation. The reconciler integration test fires **no hook at all** -- panes and windows simply stop existing on the fake server -- and the next pass records `MissingRuntime` on exactly the vanished uids, keeps every resource queryable, preserves `firstObservedAt` on a repeat pass, and clears the condition when the runtime returns on the same uids; an unreadable inventory records nothing, matching the dead-pane sweep's fail-closed tolerance. The acceptance table drives the real `intmetadata.Mirror` over a fake tmux server through the real `get` route: the reported `live` count is asserted as an inequality against the actual tmux pane count while the server is torn down one pane at a time, closing one pane offlines exactly that Pane on the next query with no hook and no write while its siblings stay live and it stays selectable, and a server that is up but mirrors no `@projmux` uid at all -- the tmux-restart shape, and the exact machine state that produced the defect -- reports zero live Windows and zero live Panes while still listing every one of them.
|
|
66
92
|
- `make test`: Registry runtime observation Phase 1 binding reapply and adoption: the `@projmux_window_uid`/`@projmux_pane_uid` options were written once, at legacy-session import time, and never again -- a tmux server restart or an option reset left every live window and pane carrying no uid at all, which is what made `projmux delete pane` with no selector fail with *the active tmux pane carries no `@projmux_pane_uid`* in almost every pane on the measured machine, and the import guard `!result.ProjectReused || len(reg.WindowsOf(projectUID)) == 0` made that permanent by *avoiding* duplicate Windows instead of repairing the drift. `internal/core/metadata` gains `BindingMatcher`, the single structural matching rule: Project scope first, then ordinal alignment (the session's tmux windows in `window_index` order against that Project's registry Windows in creation order, panes the same way inside a matched Window), returning exactly one of rebind/adopt/unmatched/refused. Two table tests, one per kind, pin every decision the rule may make against a two-Project fixture that deliberately carries the Window name `one` and the Pane name `zsh` in both Projects -- so a name-based match would cross and be caught -- and every refusal has its own row: an unresolved session scope, a uid the registry has never heard of (never adopted, reported as its own `AdoptionForeign` outcome so the anchored import path may mint rather than reuse -- projmux itself produces unknown uids, because a reconcile rolled back by a pre-create hook refusal has already written them onto non-transactional tmux options), a uid another Project owns, a Pane of a sibling Window, a uid two live objects both carry, a candidate already bound to a different live tmux object (skipped, never stolen), and a candidate already claimed earlier in the same pass; `paneUIDsInWindowOrder` is pinned to registry insertion order against a Window that mixes shell and managed Panes, because borrowing `snapshotPanesOf`'s shell-then-managed grouping would shear the alignment. `ImportLegacySession` takes a pass-scoped matcher and always walks `legacy.Windows`; `ImportedWindow`/`ImportedPane` gain an `Origin` of created/adopted/rebound, and adopted objects are reported to the adapter for mirroring but never recorded in the transaction ledger, proven by an empty `Created` on a pure-adoption re-import and by a single-entry ledger when an adopted Window still imports one extra Pane it has no candidate for. Import-path negatives cover zero duplicate Windows across two passes with every uid wiped, zero cross-project adoption for two same-shaped sessions, and and a foreign uid that mints a fresh Window instead of re-identifying the unbound one sitting right next to it, while a uid another Project owns produces nothing at all. `internal/integrations/metadata` extends `ObserveLegacySessionTargets` to read the uid each live object already carries (5-field window rows, 9-field pane rows), pinned for a bound window and a blank one in the same session. `internal/app` wires two binding steps against one pre-pass live-uid observation taken through the Phase 0 read seam: the anchored import path, and a new `reapplyUnresolvedBindings` for sessions with no `@projmux_project_path` -- the shape every session on the measured machine had -- which resolves through the Project<->session-name edge `refreshSessionProjections` maintains and creates nothing. The reconciler suite runs the measured fixture directly: one live blank window, a Project reachable only through `status.session.name`, two unbound registry Windows, and a stored `status.session.live` of `false` that nothing is allowed to consult; a **single** pass writes `win-first`/`pan-first` onto the live objects together with `automatic-rename off` and the name mirror, creates and re-identifies nothing, and leaves `MissingRuntime` on exactly the Window and Pane that really are unbound. A second pass leaves both the registry and the whole fake tmux server byte-identical. The refusals are covered at route level too: a session no Project claims and a session two Projects both claim each write nothing, two Projects with two sessions each reach only their own objects, a foreign uid is left exactly as found with none of its panes bound, a Window already bound to another live tmux window is not stolen (the blank window takes the next candidate instead), and a binding write that fails is skipped rather than failing the create that triggered the reconcile.
|
|
67
93
|
- `make test`: Registry runtime observation Phase 2 stable Window identity and runtime display projection: the `LegacyWindowNameSeed` table supplies tmux `window_name`, both automatic-rename modes, Pane label, provider, known shell, topic, and raw title and requires every row to return only `FallbackWindowNameBase`; an isolated two-Window import allocates `window`/`window-1`, records the observed `window_name`s as duplicate-allowed `metadata.displayName`, and proves `describe window` renders stable `Name` and runtime `DisplayName` as separate rows. A re-import after an explicit `rename window` keeps every existing uid, `metadata.name`, ownerRef, and name reservation while updating only displayName, and the unanchored binding-reapply fixture repeats that negative guard at the reconciler seam. `internal/integrations/metadata` pins the split transport: `@projmux_window_name` continues to mirror stable metadata.name while tmux `window_name` is driven by displayName, with an empty legacy displayName falling back safely to metadata.name. The explicit resource rename test proves `projmux rename window ... --name` does not change displayName; Pane `pane`/`pane-1` import naming and provider-base Agent naming/linkage/status remain covered by their existing Phase 3/4 tests.
|
|
@@ -70,6 +96,7 @@
|
|
|
70
96
|
- `make test`: Columnar default for list reads Phase 0 plus display-first resource projection: the default human plural reads (`get projects|windows|panes|agents`) are space-aligned tables whose first two columns are always `DISPLAY NAME`, `NAME`, followed by the existing per-kind columns in their prior relative order (`STATUS`, owner chain, optional AGENT/SESSION, `AGE`). Four whole-table goldens pin Project aliases, the already-projected tmux Window display/tab beside its distinct stable name, and current Pane/Agent fallback; the fallback uses metadata.name only when `strings.TrimSpace(metadata.displayName)` is empty and preserves a non-empty stored displayName byte-for-byte. Adversarial Pane `status.displayTitle` and Agent topic values, raw-title wording, tmux handles, duplicate display aliases, and selector negatives prove that secondary display data is never promoted and that only metadata.name or explicit `uid:` remains selectable. Each golden also asserts zero box/rule characters, tabs, and trailing whitespace. Widths stay terminal-display-cell based through `projmuxpicker.VisibleLen`; Hangul, long values, empty interior/trailing cells, and the direct rune-count falsification remain covered. The table remains gated on `OutputModeDefault` plus plural `list`: singular `get`, `describe`, and rename/create summaries remain byte-frozen, as do scalar `-o uid|name|ref|none` and whole List-envelope `-o metadata|json` projections. Zero rows still succeed with zero stdout/stderr bytes, and the injected-clock AGE boundary/determinism suite remains unchanged.
|
|
71
97
|
- `make test`: Resource timestamps surfacing Phase 0 timestamps in read surfaces: the registry has always stored `metadata.createdAt` on every resource, `Condition.firstObservedAt`/`lastTransitionAt` on every observed condition, and `Agent.status.lastTransitionAt` on every Agent, and `-o json` has always emitted them -- but no human-facing surface printed a single one of them, so `describe pane` carried zero time rows and the columnar `get` carried no `AGE`. Both now render, and nothing new is collected: no tmux query, no schema field, and no second relative-time implementation. The plural reads gain an `AGE` column, last on all four kinds because that is the one column whose width moves with the clock and last is the only position where growing it cannot walk an owner-chain column sideways; it is rendered through `i18n.FormatDuration`'s compact form, the same single implementation `notify list` already renders its own AGE through. `internal/app` pins the arithmetic as a boundary table asserted from both sides of every unit rollover (`59s`/`1m`, `59m`/`1h`, `23h`/`1d`, up to `900d`), plus the three cases that render an empty cell rather than a number -- a uid that has left the registry, a resource stored before `createdAt` was stamped whose zero Time would otherwise date it to year 1, and a caller that passed no clock -- and a whole-table golden spanning `9s`/`47m`/`6h`/`11d` in one read, which is also the alignment case because the widest cell sets the column. Determinism is stated as a two-sided claim rather than a single assertion, since only the pair is falsifiable: the same injected clock twice must be byte-identical (which a renderer ignoring the clock would also satisfy) **and** a clock advanced three days must produce a different table (which a renderer reading `time.Now` would fail), so only a renderer reading the passed-in clock and nothing else survives both. The clock reaches exactly one projection: `writeResourceProjection` takes it as an explicit parameter and the singular call sites (`get pane`, `describe`, `rename`, and the create fan-out, whose default projection is the `<kind>/<name> created` line and never the table) pass the zero Time, which `resourceAgeCell` renders as no age at all rather than as "created exactly now". `describe` gains absolute UTC instants instead: a `CreatedAt` row beside the identity rows on all four kinds, `PhaseSince` under an Agent's phase, and the second half of a condition's stored pair (`lastTransitionAt` beside the `firstObservedAt` that was already there), each omitted when the registry file predates the field so a pre-`createdAt` registry keeps its exact current block. The machine-consumer contract is frozen the whole way: `-o uid|name|ref|none` byte for byte, and `-o metadata|json` as whole documents rather than by substring -- they are rendered from the same `ObjectMeta` the AGE cell reads, so a new key anywhere in either reddens. The Columnar track's `describe pane` byte-golden moves by exactly one row and keeps its own purpose intact: what it guards is that the columnar table is reachable only with `list` true, and a header line or a padded column in a singular route still reddens it.
|
|
72
98
|
- `make test`: Settings IA re-cut Phase 0 full navigation cutover: the visible Settings tree is declared once in `internal/app/settings_nav.go` and frozen by `internal/app/testdata/settings-nav-tree.golden`, so an IA change is a golden diff rather than a shape spread across the section loops. Structural tests prove the catalog is a tree (unique ids, resolvable parents, a scope root at the top of every path), that every control hangs off exactly one owning `[View]` and that only a View owns children (row-affordance exclusivity), and that each rendered picker value maps onto exactly one navigation node whose owning loop the closed reachability table actually handles (control-owner cardinality); the drift guard runs in both directions, so a section loop cannot grow a row the golden does not show. The removed-path negative guard proves `Labs`, the separate `Theme` root, `Project recipe` and `Effective merge view` are neither rendered, nor catalogued, nor routable through `runSection` — the retired handlers, action metadata, and i18n entries are absent rather than hidden redirects — and the legacy-copy guard rejects the retired vocabulary (`AI Settings`, `Project Picker`, `Project Root`, `Workdirs`, `Enabled agents`, `Default split mode`, `Resume picker`, `Session State`, `Kill Session`, `New window`, `Hook quiet policy`, `Delivery sources`, `Labs`, `Usage HUD`, `Path icon`, `Notify icon`, …) across navigation labels, rendered rows and keymap display labels, in both locales. Display renaming is proven display-only: the keymap action IDs (`new-window`, `Sidebar:KillSession`, `ai-split-right`, …), the `sessionstate:`/`project-hooks:`/`live-resources:`/`statusbar-decoration:`/`workdir:`/`projdir:`/`switch:`/`keymap:`/`theme:` action spellings and their owner contracts are all unchanged behind the renamed rows. Keybinding navigation is category-based: an exhaustiveness test requires every catalog action to carry exactly one explicit category (never an ID-prefix inference), every surface action to sit in the sidebar/picker category with a display label for its surface, one display label per action, and each action to render exactly once across the rendered categories, while the category rows carry their members' action IDs, labels, chords and state so search still crosses the category boundary. The action detail projects the product semantics the target IA asks for — target kind, result kind, placement and anchor — and pins that every interactive right/down split declares the explicit current-Pane anchor and a right/down placement, that `Create <Provider> Agent` is always-new, and that resume names the existing `Offline`/`Failed` Agent it rebinds. Navigation is proven side-effect free (opening the root, every section and every container writes no config and runs no tmux command), every `[State]` row carries the catalogued passive value so Enter can never fall through, and the promoted rows keep their old state at the new route (Resources reads the same `live-resources` file it read from Labs, the project automation policy the same `project-hooks` file, Theme the same global `[theme]` block through `Appearance > Theme`). The pinned Project item view keeps Project identity separable (display name, unique name, uid, bound root, condition, missing-since, runtime) and offers same-uid remediation through the canonical `rebind project` route instead of re-pinning, with the unregistered-path case rendering a disabled rebind row carrying its reason. The Agent Usage HUD ownership guard keeps `agent usage` canonical: no Settings row spells a `Usage` resource or a `get usage` read, and `docs/settings-ia.md` names the owner.
|
|
99
|
+
- `make test` also covers Settings directional hierarchy parity: every catalogued navigation/actionable/passive/Back kind has a Right/Left outcome; Right opens only forward-navigation rows and never executes mutation, passive, or Back rows; child and grandchild Left return exactly one View while root Left stays in place; Esc and the Settings popup-toggle close action remain available at root and every visited depth; en-US/ko-KR frames advertise the arrows; and typed-query, color-grid, scope-chip/chord, and key-recorder input retain their existing native semantics. A native root → child → grandchild → back → root interaction and a config-tree mutation-negative audit pin the shared policy without changing global picker arrows.
|
|
73
100
|
- `make test`: Settings IA re-cut Phase 1 Automation and Notification ownership: the two containers are proven to be different product concepts by their labels alone. Automation names only Projmux-owned user scripts and the policy that gates them (`Projmux session lifecycle`, `After notification queued`, `Project automation policy`) and never a Provider or an integration; Notifications names only delivery and projection and never a lifecycle script or a `[hooks.*]` key. The Provider inventory is pinned to the Claude/Codex/Antigravity enum: the tmux bell producer is filtered out of Provider Integrations by producer identity and rendered flat at its own `tmux event source` View, whose wiring row says in words that the bell is not an Agent Provider. `Agent event behavior` renders the same Provider enum, so a Provider can never be wireable in one destination and missing from the other, and a provider x event matrix walks every event to its four-way `default`/`notify`/`state`/`quiet` choice and asserts each choice is addressed to the provider and event that rendered it. The two Provider destinations are proven disjoint: install/remove/conflict/config-path/`Check integration` rows exist only in the wiring detail and the `ai-hook-set:` choices only in the behavior view, with neither surface rendering the other's action prefix. `Check integration` (and `Check` for the bell) is an observable read-only action rather than a placeholder: it re-reads the shipped doctor diagnostic, reports status/config path/conflict as Settings feedback, shells out to nothing, leaves the config tree byte-identical, and errors on an unknown id. The `[hooks.send-noti]` + `PROJMUX_NOTIFY_HOOK` parity test sets both at once and proves they are a supported combination rather than an either/or: each keeps its own resolved value, the desktop row disclaims the fan-out and the Automation row disclaims the sender, neither row renders the other's value, and the three session lifecycle events stay empty. An automation event x scope matrix walks `pre-create`/`post-create`/`post-attach`/`send-noti` in both scopes, keeps the technical `[hooks.<event>]` spelling in the state row, requires the project scope to name its missing-context reason instead of falling through to a global value, and pins `send-noti` as a sibling of the lifecycle list rather than a fourth member of it. The attention guard keeps live Pane badges out of every Notifications surface while the badge style keeps its Appearance home, and the retired `Hook quiet policy` vocabulary is gone from the shared UI text catalog and from the rendered rows in both locales while staying on the negative-guard list.
|
|
74
101
|
- `make test`: interaction-first Keybindings details keep action state, current single keys/sequences, and add/manage/remove/reset/test actions ahead of passive explanation. `TestSettingsKeybindingActionDetailMatrix` verifies semantic/handler/`internal/cli` manifest parity directly against the internal models while separately proving every rendered selectable row is handled; `TestSettingsKeybindingActionDetailPrioritizesStateBindingsAndActions` freezes the default/custom/unbound row order and mutation reachability; `TestSettingsKeybindingKeyDetailRowsAreAllHandled` keeps replace/remove/test routes and requires an unavailable test row to carry both reason and next step; and `TestSettingsKeybindingRetiredContainerCopyIsGone` renders Action/Key/Sequence surfaces in en-US and ko-KR and rejects visible Target/Result/Placement/Anchor/Handler, canonical storage/delivery, cancellation teaching, and Advanced/Details/Delivery/About-style containers. Delivery outcome, one-reader, unavailable/cancel/no-write, capture/typed normalization, feedback-stage recovery, protected-action, reserved-key, sequence storage/runtime, and distinct create/resume parity tests remain in the maintained suite.
|
|
75
102
|
- `make test-e2e`: Settings IA re-cut Phase 0 native navigation smoke against the real attached tmux client: a deep search from the Settings root walks `Appearance > Status Bar > Git > Icon` — proving search lands on the owning View rather than executing a control, and that reaching the icon chooser writes no decoration file — then returns to the root through the Back rows, and the keybinding recorder flow now reaches its action through the `Launch & popups` category, searching for the action's own name at the root to prove search crosses the category boundary.
|
|
@@ -87,7 +114,8 @@
|
|
|
87
114
|
- `make test` also covers the usage segment's element-priority degradation: `usageShedOrder` in `internal/app/usagecmd/usage.go` is the single definition of the drop order (cosmetic age text, then stale age text with the `~`/`~~` marker kept, then a provider's secondary window bar, then bars segment-wide, then long labels), per-provider rules run tail-first over the canonical provider order, and `docs/statusbar.md`'s `Usage element drop order` section is read by the test rather than duplicated so the two cannot drift; a width-sweep table pins the exact element set every fixture renders at 40/60/80/100/120/160/200 for three shapes — the installed shape, plus two shapes where more than one provider is eligible for the per-provider rules, which is the only way to tell element-priority degradation apart from the whole-segment `first tier that fits` ladder it replaced (at 120 cells the dual-window shape now renders 118 cells keeping Claude's second window and shedding only Codex's, where whole-segment selection drew 92); the ordering guarantees are asserted as width relations derived from the renderer rather than at hand-picked widths, so an age indicator is always shed at a strictly wider row than any official window bar and the `~`/`~~` marker outlives every official window bar; every reachable shed state is walked directly to prove no step removes a provider, its official window (5h, otherwise weekly), or its staleness marker, and that each step strictly narrows the segment; and a wide-width parity golden generated by the pre-change tree (`main` 6dfed76) plus the unchanged `status-usage-budgets.golden`, `status-usage-fresh-widths.golden` and the three staleness goldens prove a row wide enough to shed nothing is byte-identical to what whole-segment selection produced.
|
|
88
115
|
- `make test` also covers Settings > AI Settings > Enabled agents Phase 0 config behavior: missing config enables Claude, Codex, and Antigravity, unknown saved provider names are ignored without disabling known providers, Settings toggles persist Claude/Codex/Antigravity only, shell/selective are absent from enabled-agent rows, and a saved default split mode warns when its provider is disabled.
|
|
89
116
|
- `make test` also covers the Settings information-density/feedback contract: compact About producers and removed static-guide negative guards, canonical `setup` / `setup terminal` / read-only `doctor` destinations, single-source no-project guidance with project-context action preservation, Appearance/Theme/About ownership copy, and popup-visible passive mutation success/failure/validation feedback with replacement/clear and rendered-entry reachability guards. Dedicated Welcome, key-capture/probe/diagnostic, and read-only viewer outputs remain outside generic mutation feedback.
|
|
90
|
-
- `make test` also covers native Alt-1 sidebar `Sidebar:KillSession` mutable refresh: the action kills through the focused-session safety path, keeps the native picker session open, refreshes rows and preview with `picker.Action.Mutate`/`DeferredUpdate`,
|
|
117
|
+
- `make test` also covers native Alt-1 sidebar `Sidebar:KillSession` mutable refresh: the action kills through the focused-session safety path, keeps the native picker session open, refreshes rows and preview with `picker.Action.Mutate`/`DeferredUpdate`, preserves the previous-live-session guard, and invalidates the private origin Pane only when Ctrl-X stopped that exact origin session so a later Continue rebinds to the still-live client's Pane. Linux L11 drives that popup-receipt cross-transition on a dedicated pseudo-TTY (`Open fresh → Ctrl-X → Continue project`) and requires the same fresh Project/Window/Pane UID graph plus final client handoff.
|
|
118
|
+
- `make test` also covers native Alt-1 sidebar project-open failure propagation: trust denial remains a normal picker outcome, while topology/materialization failures reopen the sidebar with the prior query, selection, and bounded error status before returning the operational error to the command journal and caller; the detached continuation preserves the popup launcher's exact private Pane anchor for runtime-route authorization, then its `tmux run-shell` wrapper absorbs only the already-recorded exit status so tmux cannot replace the picker feedback with the raw quoted continuation command.
|
|
91
119
|
- `make test` also covers Notify sidebar pane-grouped read-model rows and interactions: collapsed pane inbox group construction, fixed three-line group cards with stable project/provider, topic/context, aggregate metadata, and latest-preview slots including topic-with-project cases, child-count badges that match expandable child rows without rendering `+0`, childless group headers without strong fold affordance, selected/expanded marker-only row shape changes, duplicate label/preview reduction, worst-severity aggregation, inactive/gone group display and child-count rendering, pane-less fallback grouping, compact newest-first child event rows with warn/critical severity preservation, Right/Left fold mutation plus childless Right no-op refresh, folded and expanded live group Enter focus+ack with mixed-severity critical rows, inactive-but-routable group Enter focus+ack, gone group Enter cleanup without focus including critical rows, target-gone race cleanup, transient focus-failure no-ack refresh, child Enter focus/ack preservation, explicit group acknowledgement including mixed-severity critical rows, and grouped deferred refresh output with expanded-state pruning.
|
|
92
120
|
- `make test` also covers the installed-surface coherence contract: localized Session State summaries and Project Picker Back/open guidance; single-label Resource search plus disabled empty shared-bucket feedback; en-US/ko-KR Sessions row/preview labels with the concise open/close footer visible in actual 80x24 projmux and 112x30 daylight native frames; Notify empty read-only close-only chrome; and INFO/WARN/CRIT Clear All confirmation with Esc/Cancel no-write and explicit confirm feedback.
|
|
93
121
|
- `make test` also covers the AI provider metadata registry for Claude/Codex/Antigravity launch providers: registry-derived enabled-agent defaults and ordering, Settings enabled-agent rows, AI picker visibility for disabled providers, ambient usage model scope derivation for currently registered adapters, hook diagnostic provider metadata, Antigravity session-state support metadata, and explicit diagnostics for disabled providers.
|
|
@@ -105,24 +133,23 @@
|
|
|
105
133
|
- Darwin native-key focused coverage includes portable physical chord parsing for Alt/Option, Control, Control-Option, Shift, letters/digits/navigation/function keys, `darwin && cgo` synthetic event-to-canonical-chord mapping for Option, Control, shifted letters, function keys, and arrows with unmatched/plain event pass-through, shared `keymap.toml` custom-action loading (including `M-a`), focused tmux client discovery, canonical `send-keys -K -c` dispatch through the existing root key table, native Settings Add key capture winning over simultaneous terminal text/error results after ignoring the activation Enter while the non-Darwin path preserves immediate terminal capture, default-on shell startup, `PROJMUX_NATIVE_KEYS=0`/`false` and Settings `[ui].native_keys = false` broker opt-out gates, broker direct no-op behavior, one-time pre-permission consent guidance, broker-only process replacement after an Accessibility wait so fresh macOS permission state is observed without restarting tmux, preservation of the direct no-server shutdown boundary while permission is pending, and an explicit Linux/Windows/no-CGO shell startup no-op boundary.
|
|
106
134
|
- `make test` covers the default picker key ownership across catalog, generated tmux config, terminal adapters, setup probes, and native popup close hints: `Alt-4` opens AI Resume as a guaranteed launch default, while `Alt-7` opens the AI split picker as its editable default.
|
|
107
135
|
- `make test` also covers canonical `projmux setup terminal` terminal detection, preview no-write, apply/backup, Ghostty candidate conflicts, Windows Terminal merge, config overrides, and symlink refusal/allow; removed top-level command dispatch/help negatives, canonical rejection and help absence of the removed legacy-only `--dry-run` flag, and setup remediation hints are asserted separately.
|
|
108
|
-
- `make test` also covers the direct split pane-id output contract: `--print-pane-id` parsing, explicit Claude/Codex/Antigravity/shell output as exactly one `%N` line, unchanged empty stdout without the flag, prompt argv-tail preservation, pre-launch rejection for saved-default/selective/resume picker paths, actionable missing/invalid tmux result errors, and the tmux `split-window -P -F "#{pane_id}"` contract.
|
|
109
136
|
- `make test` also covers welcome revisit policy: legacy welcome state remains readable without suppressing shell entry, Enter continues without storing release skip, `s` stores an update skip for the current latest tag when an update is available, source installs show disabled Upgrade guidance, stale update cache refresh is best-effort, Settings > About > Welcome opens a Settings-native viewer without pending state, and shell prompt display does not schedule a redundant attach popup.
|
|
110
137
|
- `make test` also covers transport-dependent app tmux defaults for pane/window navigation (`M-Left`/`M-Right`/`M-Up`/`M-Down` and `M-S-Left`/`M-S-Right`) while keeping visible default chords, allowing additive safe plain aliases that do not store or remove the transport defaults, and no UserKey/CSI-u generated fallback.
|
|
111
|
-
- `make test` also covers direct `projmux create agent --provider <claude|codex|antigravity>` and `projmux create pane` launches, enabled-agent gating for disabled
|
|
138
|
+
- `make test` also covers direct `projmux create agent --provider <claude|codex|antigravity>` and `projmux create pane` launches, enabled-agent gating for disabled saved defaults and disabled picker selections, selective picker filtering with all-disabled shell fallback guidance, resume picker routing/New delegation/zero-session fallback, config-default preservation, extra args appended to resolved agent executables, managed pane metadata, and selective picker delegation. The legacy `ai split` route and its `--agent`/`--force-agent`/`--print-pane-id` flags are retired; the split-UI half of that coverage now lives in the Phase 11 entry below.
|
|
112
139
|
- `make test` also covers managed Antigravity hooks integration in an isolated home: named-entry install/dry-run/remove/idempotency, byte-preserving unmanaged JSON edits, explicit four-event absolute commands with no `PreToolUse` permission policy, valid non-blocking stdout fallbacks, conflict/malformed/symlink/permission diagnostics, provider registry/doctor/Settings install-remove-dry-run guidance, PreInvocation thinking/busy state, quiet PostInvocation/PostToolUse bookkeeping with tool-error diagnostics, and Stop decision/notify behavior.
|
|
113
140
|
- `make test` also covers Settings rendered-entry reachability across root/section/detail builders with a closed owner-loop table, handlerless-value rejection, passive info/disabled Enter no-op behavior, `send-noti` visibility in Hooks, no retired Project recipe or Effective merge route/catalog/i18n residue, AI/Labs view-first detail rows, Appearance Path/Git/Notify icon direct off/symbol/emoji preview selection with no Change page, Notifications root/Desktop notifications/Delivery sources relocation, removal of standalone queue/hook-override rows, `PROJMUX_NOTIFY_HOOK` state merged into Delivery sources, localized Korean Desktop notifications root/detail chrome without visible English residue, Delivery sources command-row clipboard copy, and Labs limited to Live system resources plus overview-first Project Hooks while legacy picker env/file values remain native-compatible.
|
|
114
141
|
- `make test` also covers the macOS/Linux/WSL live resource status Lab: default-off and atomic saved-value persistence, direct Labs on/off mutation plus live tmux option update, generated app/standalone conditional placement between git and clock for both Lab modes, independent CPU 70/90 and memory 75/90 warning/critical boundary classification, mixed-severity per-metric tmux style runs without visible severity words, secondary-style unavailable values without severity leakage, fixed four-column percent slots and invariant metric/segment visible widths across nil/0/9/15/69/70/74/75/89/90/99/100 under default/custom themes, light/dark semantic-role rendering, Linux `/proc/stat` first/delta/reset/malformed/overflow and stale-reference handling, Linux `/proc/meminfo` `MemAvailable` semantics, macOS native Mach CPU tick and free/inactive memory sampling, 0–100 clamping, atomic private sample-cache writes, and compact unavailable rendering.
|
|
115
142
|
- `make test` also covers AI desktop notification dedupe precedence (env override > Settings saved value > default), Desktop notifications mode persistence through `desktop-notify-mode`, saved-config precedence over live tmux options, `projmux config apply` regeneration of `@projmux_desktop_notify_mode`, configured dedupe-window collapse/send behavior, Settings > Notifications AI dedupe preset/custom rows, explicit notify focus consume rules for selected critical rows and older same-pane non-critical AI cleanup, preservation of critical/permission/stop-failure/external/git/k8s rows during bulk cleanup, `projmux internal focus --uri` compatibility-input queue consume, and attention clear paths that do not ack the queue.
|
|
116
143
|
- `make test` also covers the two-state desktop notification contract: `NormalizeDesktopNotifyMode` accepting only `off`/`notify` while reading retired `raise`/`auto-raise`/`autoraise` literals as `notify`, per-source legacy-alias resolution that keeps env > saved file > tmux option precedence instead of falling through, a platform-neutral `notify` default including WSL + Windows Terminal, saved/tmux-applied values restricted to `off`/`notify`, Settings > Notifications > Desktop notifications offering exactly `Off` and `Notify` with no `Raise` row/description/search keyword in English and Korean, WSL Toasts carrying no `launch`/`activationType="protocol"` payload and writing no `projmux://` protocol-handler registration, and `projmux focus` never dispatching a host-window focus adapter in any mode.
|
|
117
144
|
- `make test` also covers AI hook runtime action precedence over catalog defaults, runtime quiet for known Codex notify events, runtime notify for known Claude quiet events, generic in-app-only notify rows for known Codex hook events without specialized handlers, suppression of desktop notification and `send-noti` dispatch on that generic path, separation of runtime hook action from catalog install events, Settings > Notifications hook quiet policy display/write behavior without external install/remove execution, hook desktop notification payload parity with the in-app queue text across the shared OS notification payload, normal/transient OS urgency and expiration for critical AI queue rows, and dormant title/capture fallback gating once a pane is hook-active.
|
|
118
|
-
- `make test` also covers Settings > Session State as global settings-only UI with default-off auto-save
|
|
145
|
+
- `make test` also covers Settings > Session State as global settings-only UI with default-off auto-save and no window/pane tree, Settings > Project > Session State project-derived identity plus project auto-save override/effective source rows and distinct save labels, closed-project save disabled reasons, named-snapshot portable path conversion, project/global auto-save precedence, Projects > Sessions > State read-only snapshot overview, and the optional closed-Project startup screen with exactly `Continue project` / `Open fresh`, Esc navigation, existing-session bypass, startup-before-trust ordering, detached client-scoped continuation, trust approve, and trust deny/cancel zero-session behavior.
|
|
119
146
|
- `make test` also covers `projmux shell` usage without startup selector flags, project-context default target lookup from `PROJMUX_CWD` or nearest project marker, explicit `--session` separation from project-derived defaults, and direct empty attach behavior without shell startup picker/replay.
|
|
120
147
|
- `make test` also covers pane identity separation and projection: canonical `rename-pane-label` prompts with `pane label:` initialized only from the current label, Enter confirms, empty Enter clears, and native Esc cancels; unchanged Ctrl-Shift-M default and Settings expose only `rename-pane-label`, while stale `[bindings.rename-pane-topic]` keymaps fail with an exact `[bindings.rename-pane-label]` replacement. AI topic set/clear keep existing topic/manual ownership, no raw-title action is exposed, and pane border/window tab/popup/sidebar/recent-window resolve user label → agent AI topic → known shell → raw title.
|
|
121
148
|
- `make test` also covers session snapshot pane identity capture, old/new decode, and replay field-matrix contracts: `Pane.Label`, raw title, recipe topic/manual ownership, startup fields, and agent/resume metadata stay separate; old snapshots leave label and ownership absent without equality inference; tmux creation-returned pane ids are the replay targets; and agent direct-start never copies a saved topic into the raw title.
|
|
122
149
|
- `make test` also covers Antigravity v1.1.12 current-storage resume discovery from sanitized synthetic `last_conversations.json` and workspace-bearing `conversation_metadata.json` fixtures: exact/depth workspace filtering, URI/path list variants, required metadata summaries, safe-title/short-UUID and blank-turn degradation, normalized UUID plus exact regular `<uuid>.db` existence/mtime validation, missing DB/sidecar/traversal/file-symlink rejection without DB reads, malformed/missing/unknown/workspace-less clean degradation, the separate live hook/session-state lane plus disk-picker last-conversation > metadata > legacy source dedupe without cross-provider behavior changes, high live/medium cache/low history preview and doctor confidence, and picker selection wiring to `agy --conversation <uuid>`.
|
|
123
150
|
- `make test-e2e` also runs a disposable exact tmux server save → destroy → production restore flow for shell/startup/agent panes, changes `pane-base-index` before restore to reject index-derived targets, verifies raw tmux metadata plus visible label resolution, and replays an old snapshot with cleared label/manual ownership.
|
|
124
151
|
- `make test` also covers manual Session State Save snapshot and restore dry-run preview actions, title-first non-agent pane preview labels with legacy snapshot fallback, agent pane health-first bounded rows that keep status/confidence/source ahead of the full resume id while retaining id/title in the unbounded model, missing-snapshot messaging, and the absence of the removed statusbar State shortcut.
|
|
125
|
-
- `make test` also covers legacy named-snapshot storage compatibility for `.projmux/layouts/*.toml`, supported interpolation validation, malformed-file list warnings, portable conversion helpers, dry-run conversion to the session-state
|
|
152
|
+
- `make test` also covers legacy named-snapshot storage compatibility for `.projmux/layouts/*.toml`, supported interpolation validation, malformed-file list warnings, portable conversion helpers, dry-run conversion to the read-only session-state preview with source labels, and missing-current-session failure. Startup does not expose these artifacts as action choices; explicit snapshot projection is the only restore authority.
|
|
126
153
|
- `make test` also covers the project-local executable artifact trust boundary for named snapshots: one-read exact-byte parse/hash reuse, SHA-256 allow-once/allow-always decisions, changed-byte and rename/replacement reauthorization, final-file and parent-component symlink rejection, config-absent and `PROJMUX_PROJECT_HOOKS=off` independence, non-interactive fail-closed behavior, commandless-layout compatibility, terminal-control escaping for layout names/descriptions/window names/agent topics/command previews, and deny/error paths that stop before restore, session/pane creation, or startup `send-keys`.
|
|
127
154
|
- `make test` also covers `projmux agent integrate codex` default hooks install/idempotence/dry-run/removal, compatibility legacy Codex notify mode install/idempotence, dry-run preview, unmanaged `notify = ...` conflict refusal, managed-block removal, catalog-driven Codex hook event installation with local override merging, nested inline Codex hook schema, `[features]` merge/preservation, preservation of unrelated Codex/Claude config and unmanaged hook entries, Settings Codex `/hooks` review notice and tested-version rows, and remove-all behavior for projmux-managed Codex blocks.
|
|
128
155
|
- `make test` also covers canonical `projmux internal agent-hook ingest codex-hook` event parsing, PermissionRequest critical queue rows with tool/action metadata, Stop info queue rows, UserPromptSubmit busy/no-queue behavior, pane-level semantic badge kind writes that keep permission, response complete, and in-progress states distinct, quiet/no-notify diagnostics for non-firing Codex hook events including override-added catalog events, cataloged hook notification body formatting, hook-active pane marking, topic preservation without hook candidate writes, and mutable resume metadata writes for `@projmux_ai_resume_id`, source, and updated-at.
|
|
@@ -142,7 +169,7 @@
|
|
|
142
169
|
- `make test` also covers `projmux diagnostics agent-hook` tail/path rendering and bounded JSONL log trimming for ingest diagnostics.
|
|
143
170
|
- `make test` also covers the Phase 0 operational diagnostics foundation: closed safe-event schema, control/length/home sanitization, lossy stable top-level error messages with dynamic pane/title/body/transcript/config-value exclusion, exhaustive allowlisted top-level/multi-mode command classification including direct-help and explicit dry-run success exclusion with boolean `=false` safeguards, exactly-once state-changing/error outcomes with explicit zero-success/error-one coverage for the six automatic `internal agent-hook ingest`, attention arm/clear/window, session-state autosave, and recent-window record paths plus an explicit-mutation success regression, private permission repair, platform replace-existing bounded complete-record retention, corrupt-tail recovery, OS-owned inter-process lock identity/crash recovery and explicit 200 ms budget, concurrent short-process rotation, common text/JSON/tail/filter reader fixtures, and unchanged top-level runtime/usage/exitCoder output and exit semantics.
|
|
144
171
|
- `make test` also covers Operational diagnostics Phase 2 lifecycle adoption: the maintained explicit-mutation inventory (attach/current/kill/sessions/switch plus session-popup open, window recent, runtime prune, focus session switching, shell open-app, snapshot replay creation, popup-toggle cancel restore, and config apply); session create/attach/switch/kill and `config apply` start/outcome success/error tables; composite create-then-attach and prune-kill-then-attach failure-stage attribution while preserving one outer pair; shared run IDs and no duplicate top-level outcome; best-effort store/write failure injection; closed operation/code pairing; apply typed no-server versus generic runner failure classification; focus success sealing at the actual switch-client boundary; unchanged read-only/hot-path zero-success recording; typed no-write runtime health projection with latest-state and bounded failure-code semantics; Doctor runtime/log typed consumer reuse; and support-report raw socket/path/session/window/pane/name negative fixtures. `make test-integration` additionally runs real create/attach/switch/kill success/failure routing against a run-unique `-L` socket, validates exact lifecycle pairs and apply classifications without raw routing, and guards cleanup with the queried actual socket path beneath the run-unique smoke root.
|
|
145
|
-
- `make test` also covers Operational diagnostics Phase 3 Session State adoption: the closed outcome/operation/code/source/six-count schema; direct and popup manual save/delete, Settings latest/named save and current/project delete, deduplicated prune delete, and
|
|
172
|
+
- `make test` also covers Operational diagnostics Phase 3 Session State adoption: the closed outcome/operation/code/source/six-count schema; direct and popup manual save/delete, Settings latest/named save and current/project delete, deduplicated prune delete, and manual snapshot projection ownership; exact snapshot and item aggregates; error count omission; Settings feedback error capture; append-failure best-effort ownership; autosave session/config/source/interval/debounce/store/save/set-option failure ownership including quiet error-one and project on/off precedence over a broken global config, plus repeated autosave success/no-op zero-volume behavior; same-run coexistence with the runtime lifecycle pair; and seeded path/command/session metadata negative fixtures. `make test-integration` exercises manual save, autosave success/error, save → desired-state mutation → exact closed-Project restore projection → ordinary materialization, and aggregate delete against the guarded run-unique tmux socket and isolated XDG root, including exact snapshot cleanup and raw metadata negative checks.
|
|
146
173
|
- `make test` also covers Operational diagnostics Phase 4 notify/focus adoption: the closed transition/disposition/provider/category/route schema and cross-family rejection; explicit notify enqueue ownership with queued/deduplicated/failed outcomes; secondary attention and nested prune-reconcile enqueue ownership isolation with no-change zero volume; external-hook, Linux, WSL Toast/fallback, disabled, unavailable, desktop-dedupe, and visible-pane delivery outcomes; one shared process recorder with identical-tuple hot-path coalescing; append-failure best-effort semantics; focus direct/queue/toast request classification with unresolved, notify-only, fallback, and focused outcomes; same-run coexistence with the existing `session.switch` lifecycle pair without a generic duplicate; unchanged `PROJMUX_FOCUS_DEBUG` output; and seeded summary/body/tag/group/title/path/UUID plus support-report negative fixtures.
|
|
147
174
|
- `make test` also covers Operational diagnostics Phase 5 AI watcher/ingest adoption: the closed provider/event-kind/result/failure schema, provider-specific kind catalogs, result/failure tuple compatibility, and cross-family rejection; watcher start plus pane-gone/hook-active terminal ownership, actual launch-failure classification, per-run tuple coalescing, and no per-poll records; Codex/Claude/Antigravity malformed, read, oversized, unmatched, unsupported-event, and route-error projection plus tmux-bell valid, blank, and pane-not-found coverage; zero common AI events for normal state/notify/known-quiet/deduped ingress while Phase 4 notify retains its transition; legacy `ai-ingest.log` producer, `diagnostics agent-hook`, bounded retention, and support count-consumer parity; append-failure best-effort ownership with no generic duplicate; and seeded prompt/tool/path/UUID/conversation/session/title negative fixtures for operations and support output.
|
|
148
175
|
- `make test` also covers Operational diagnostics Phase 6 resource adoption and legacy review: exact closed resource source/result/failure tuples and cross-family rejection; production app graph wiring through Resource Inspector lifecycle → tmux inventory/project discovery/procfs attribution; unavailable, partial, stale, collection error, and actual context-deadline scan-budget fixtures; normal sample/refresh zero volume, persistent-transition coalescing, silent recovery plus anomaly re-entry, and append-failure best-effort UI/CLI preservation; separate host-only status sampler zero ownership; operations text/JSON and error-only support projection with partial/stale omission; seeded CPU/MEM/PID/process/path/title/command/UUID/session/error text negative fixtures; common journal retention/rotation regression; and the file/surface legacy diagnostic inventory without breaking removal.
|
|
@@ -156,8 +183,8 @@
|
|
|
156
183
|
- `make test` also covers stale state cleanup policy: successful prune, runtime stop, runtime sessions picker, and switch/sidebar session kills trigger best-effort preview-row cleanup; notify refresh publishing sweeps dead-PID sockets while preserving live-PID sockets; snapshot prune listing distinguishes dead sessions from old live persistent sessions without deleting either, explicit named deletion removes only selected snapshots, malformed snapshots remain preserved with a warning, and doctor documents the manual-only retention policy.
|
|
157
184
|
- `make test` also covers Recent Windows Phase 2/3/4 keybinding, popup, and runtime recorder integration: `RecentWindows:Open` owns the guaranteed `M-3` launch default, generated tmux config uses client-scoped `tmux popup-toggle recent-windows`, the popup body runs `projmux window recent`, generated tmux config installs background `window record` hooks for window and client-session changes plus first-use population without `session-window-changed`, `window record` snapshots current tmux socket/session/window/active-pane metadata into the socket-scoped store with escaped field parsing, repeated same-window record fires dedupe instead of growing entries, the queue remains bounded by `recentwindows.DefaultLimit`, Settings > Keybindings shows Recent Windows with the `M-3` default, setup probe text names Recent Windows, `SessionPopupToggle` no longer owns the guaranteed `M-3` default, and escaped tmux field separators are parsed for current/list window metadata.
|
|
158
185
|
- `make test` also covers the Linux resource-attribution Phase 0 contract: tmux-only typed socket/session/window/pane PID/TTY/current-path/project-anchor inventory without unsupported placeholder values; explicit project-anchor priority plus read-only current-path fallback through canonical pinned/root-child discovery, longest-root matching, outside/home exclusion, and linked explicit/fallback conflict handling; one-pass procfs stat/meminfo collection; PID+starttime reuse defense and SID ownership; first/invalid/logical-CPU-change unknown CPU; host-share/core-equivalent CPU and RSS/host-ratio memory; unique pane-to-window-to-project conservation; linked-anchor `Shared / ambiguous`, missing-anchor `Unassigned`, setsid escape, fast-exit/race/permission partial states; and explicit host remainder/overage without silent clamping. The opt-in `PROJMUX_RESOURCE_PROJECT_FALLBACK_SMOKE=1 go test -run TestResourceProjectFallbackTransientSmoke -v ./internal/integrations/tmux` test provides an isolated read-only fallback fixture, while `PROJMUX_RESOURCE_TMUX_SOCKET=projmux PROJMUX_RESOURCE_EXPECT_PROJECT_ROOT=/path/to/project go test -run TestResourceAttributionRealTmuxReadOnlySmoke -v ./internal/integrations/tmux` provides sanitized live-socket project-bucket/count evidence; benchmark commands and current measurements are maintained in [resource-attribution.md](resource-attribution.md).
|
|
159
|
-
- `make test` also covers the Resource Inspector Phase 1 vertical slice: immediate non-actionable warming chrome; Project → Window → Pane → read-only detail hierarchy with project/window `PANES n` and pane `PROCESSES n` projections;
|
|
160
|
-
- `make test` also covers the Resource Inspector readable-layout Phase 0 contract: non-duplicating scope breadcrumbs; separate host, attributed, Other, diagnostic, list/detail, and contextual-action bands; primary identity separated from secondary context; vertically comparable CPU/Memory/count clusters; deterministic 80x24 and wide themed frame geometry without border bleed; en-US/ko-KR copy and
|
|
186
|
+
- `make test` also covers the Resource Inspector Phase 1 vertical slice: immediate non-actionable warming chrome; Project → Window → Pane → read-only detail hierarchy with project/window `PANES n` and pane `PROCESSES n` projections; concrete-project and `No project match` drill-down plus the non-actionable Other summary; defensive Shared attribution omitted from project rows while its CPU/RSS/pane aggregate remains in Attributed totals and a bounded non-drillable Sample diagnostic; shared label → active-agent topic → interactive shell → raw-title pane identity precedence with reused `Process <PaneCommand>`/PID-SID/Pane %N/TTY secondary and detail naming; resource-only Right/Enter forward, Left back with root no-op, all-depth Esc close, and complete Alt-Left action/hint removal; statusbar-parity CPU 70/90 and memory 75/90 exact semantic boundaries with severity-word-free values, unavailable `--`, lifecycle text, and resolved default/custom theme roles; Name-default Tab-computed CPU/Memory/Name ordering that remains stable across automatic refresh; stable-id search metadata plus refresh selection/query/breadcrumb/focus preservation and vanished-row nearest-neighbor behavior; fresh/stale/partial/unavailable/empty/gone/overage transitions with atomic refresh-progress/footer updates; an exact fixed five-row divider/Host/Attributed/Coverage/Sample bottom summary dock that remains independent of scroll/search and separate from the action footer, with bounded diagnostics on Sample only, minimum navigable list height, and no divider/border bleed; changed-row synchronized differential frames at 80x24 and wide en-US/ko-KR layouts; truthful RSS-sum rendering; immediate automatic pre-collection not-fresh progress with the last complete sample retained, gated non-overlapping two-second and Ctrl-R sampling, cancellation, and prior-sample disposal; direct CLI and client-scoped popup mode; no-default `Resources:Open` catalog/Settings/config/custom-alias close behavior; statusbar `resources` range/click byte-equivalence, Labs visibility independence, other-client isolation, and existing `projmux internal status resources`/window-click fallback regression.
|
|
187
|
+
- `make test` also covers the Resource Inspector readable-layout Phase 0 contract: non-duplicating scope breadcrumbs; separate host, attributed, Other, diagnostic, list/detail, and contextual-action bands; primary identity separated from secondary context; vertically comparable CPU/Memory/count clusters; deterministic 80x24 and wide themed frame geometry without border bleed; en-US/ko-KR copy and concrete/Unassigned stable-root parity with no Shared root row; read-only pane detail without search, pointer, Enter, or sort affordances; explicit non-actionable Other/empty/vanished surfaces; populated/empty deferred action reconciliation without hidden sort; and resource-scoped directional hierarchy flow while collector, attribution, cadence, and client-scoped popup contracts remain unchanged.
|
|
161
188
|
- Current focused unit coverage also includes strict notify SOT behavior
|
|
162
189
|
(TTL does not remove rows, focus success and target-gone clicks ack,
|
|
163
190
|
reconcile reports inactive `queue-stale` rows), `get notifications --live` queue/live explanations, notify sidebar
|
|
@@ -177,24 +204,165 @@
|
|
|
177
204
|
- Picker focused unit coverage includes native picker item/action mapping, direct native routing with no backend selector, stale selector env/file non-observation without lookup/delete/rewrite/propagation, native title-focused filtering, numeric selection, shared close actions including raw native custom actions, fuzzy-score reference fixtures, AI picker title chrome and stable search-key ordering, Settings title chrome and root section order, Settings Labs without picker source or hidden keybindings rows, compact multi-line metadata gutters with one-column-indented metadata, proportional native scrollbar thumb rendering, native row width clamps that preserve ANSI resets and Korean/wide-cell frame geometry with matched scrollbar/no-scrollbar row budgets including Settings > Appearance AI badge preview rows and parent long emoji preview rows, native app-background and semantic selected/muted/accent/warning/critical role coverage for fallback/effective themes across content padding, empty no-footer rows, footer rows, scrollbars, split-preview gaps, reset-to-padding continuation, and Korean Settings > Appearance AI badge style chips/prompt/emoji rows, multiline partial next/previous item row rendering with rendered-row scrollbar units, fixed split-preview and sidebar list viewports with scrollbar tracks, native Up/Down and Ctrl-N/Ctrl-P navigation wrap with empty-list safety and PageUp/PageDown/Home/End clamp regression coverage, native mouse down/follow-drag/release behavior, deferred native row updates that preserve filter/selection state and can repeat from event triggers, preview tab/control normalization before width clipping, optional native frame titlebars without same-line rule fill and with title/chip gap inheritance guards, titled Alt-1 sidebar chrome, statusbar-preserving sidebar popup height, compact project sidebar popup sizing, cheap Alt-1 first-paint rows with deferred git/window/attention/preview enrichment, fixed three-line Alt-1 sidebar row geometry across cheap/enriched paints, bulk switch existing-session classification with per-candidate fallback, and notify sidebar title/popup sizing.
|
|
178
205
|
- Picker/setup lifecycle coverage includes deferred native key-reader stop and repeated reopen cleanup, plus per-probe controlling-TTY cleanup and timeout-then-next-key reads without stale goroutines stealing input.
|
|
179
206
|
- Doctor focused unit coverage includes tmux required/stale behavior on every host, Windows `stty` skip behavior, and tmux bell fallback diagnostics.
|
|
180
|
-
- Shell focused unit coverage includes
|
|
207
|
+
- Shell focused unit coverage includes canonical typed Project/ControlSession bootstrap followed by the non-creating foreground route `tmux -L projmux -f <generated config> attach-session -t =<session> [-c <cwd>]`; disappearance after preflight fails closed instead of letting `new-session -A` provision outside the plan. Generated tmux config callbacks, native key-broker platform policy, runtime overrides, and nested-socket guards remain covered.
|
|
181
208
|
- `make test` also covers npm retire/staging path canonicalization for the resolved projmux executable so generated tmux config and hooks never embed a `node_modules/.projmux-<hash>` directory, plus error-suppressed attention and rebalance-panes hooks.
|
|
182
209
|
- `make test` also covers the public config spelling: `projmux config edit [--get|--set <mode>]`, `config render standalone|app`, and `config apply` forward raw argv to their manual leaf handlers; flags, unknown tokens, streams, handler errors, and side effects stay on those handlers. `make install` and every update path emit `projmux config apply`, while generated plumbing uses only the hidden `internal` namespace.
|
|
183
|
-
- `make test` also covers Legacy CLI retirement Phase 2/3: the
|
|
184
|
-
- `make test`
|
|
185
|
-
- `make test` also
|
|
210
|
+
- `make test` also covers Legacy CLI retirement Phase 2/3: rejected compatibility argv below the surviving mixed roots returns exact replacement guidance with stdout/handler/pre-dispatch mutation zero; `attach project`, `focus project|window|pane`, `pin project`, and `prune project|snapshot` remain exact raw-argv forwards. The fully removed human roots (`current`, `kill`, `notify`, `sessions`, `session-state`, `tag`, `upgrade`, `usage`), removed pre-namespace internal tokens, and the final `ai` root have no catalog or top-level handler and use root unknown-command exit 1 for every argv. The exact `tmux apply` updater handoff still normalizes to `config apply` before routing, while every other `tmux` argv remains unknown. Canonical `internal agent-hook ingest` forwards all provider event shapes to the unchanged handler, while stale unmanaged legacy-hook fixtures remain byte-preserved and require the documented manual canonical-prefix remediation. The tracked-repository literal audit permits the retired producer spelling only in test fixtures and explicit retirement/upgrade history, ignores untracked local/build artifacts without opening them, and a normal `go build ./cmd/projmux` regression requires the production binary itself to contain zero contiguous retired producer bytes. Maintained-doc scans, catalog/canonical/handler two-way audits, root/command-tree/reference goldens, the exact removed route set, preserved Shortcut/kind aliases, and generated producer scans close the topology and drift contracts. The release-boundary ledger additionally pins the post-retirement `-p`/`-w`/`-A` resource scopes, Registry topology materialization, Registry-only Offline Window deletion, and their generated CLI reference evidence so compatibility cleanup cannot silently roll back later canonical behavior.
|
|
211
|
+
- `make test` also covers current-directory keybinding shell safety: the generated `run-shell` command passes `#{q:pane_current_path}` to `switch open`, and adversarial cwd fixtures containing spaces, single and double quotes, command substitutions, backticks, and shell metacharacters remain one literal argv without executing injected commands.
|
|
212
|
+
- `make test` / `make test-integration` also cover post-retirement generated UI routing: Agent/Pane default-target bindings use hidden `internal agent-pane launch-default`, launch/resume popups re-enter through `internal agent-pane picker`, and direct Codex/Claude/shell bindings use the hidden `launch-provider` / `launch-shell` canonical-intent bridges; the notification sidebar uses `get notifications`, and the existing-session popup uses `runtime sessions`. Regression assertions reject retired `ai`, `notify`, and `sessions` roots in these generated commands, raw-argv forwarding tests preserve the canonical intent and picker handlers behind the hidden bridge, and the built-binary smoke drives a saved shell default through that bridge with `%7` as its exact origin: the missing Pane mirror returns the origin-loss usage refusal with exit 2 and zero stdout, logs only the read-only Pane probe, and performs zero raw tmux mutations.
|
|
213
|
+
- `make test` also covers the canonical manifest honesty audit: every canonical spelling resolves to a full executable path in the command tree, no canonical summary carries forward-looking vocabulary or any adjudicated-and-retired string, the remaining target-state summary (`delete pane`) is pinned verbatim with a named owning track and must stay absent from every command-tree node, and the separate shortcut gaps are enumerated as a two-way diff so a future change cannot drop a mapping unadjudicated; config and workflow routes are also asserted to have no cardinality-matrix row because they resolve no resource set.
|
|
186
214
|
- `make test` also covers singular and plural kind spellings on the four resource verbs: `Route.Aliases` in `internal/cli/catalog.go` is the single declaration, `cli.CanonicalChildToken` normalizes an alias to its canonical token before `get`/`describe`/`delete`/`rename` dispatch, and the unknown-kind refusals are rendered from `cli.ChildSpellings` so an accepted spelling can never be missing from the list they print. `TestKindSpellingAliasesAreByteIdenticalToTheCanonicalSpelling` runs all 17 canonical/alias pairs -- including the two forwarded parity kinds per verb, whose forwarded argv is compared as well -- and requires identical stdout, identical stderr, and an identical error, with the canonical run required to succeed so an all-failing pair cannot satisfy byte-equality vacuously; `TestEveryManifestKindHasAParityCase` derives the matrix from the manifest so a kind added later cannot escape coverage, and `TestKindSpellingAliasesNeverReachAnotherRoute` proves each singular `get` alias answers its own inventory and is distinguishable from every sibling kind. On the manifest side `TestCanonicalKindSpellingsSurviveTheAliasContract` pins the canonical kind token of every resource-verb child so an alias can never replace a shipped spelling, `TestNoChildAliasShadowsACanonicalSpelling` walks the whole tree for alias/name collisions and duplicate aliases, and `TestResourceVerbsAcceptBothFormsOfEveryKind` asserts zero missing singular/plural combinations with `get pane`/`get panes` as the one enumerated divergent pair. That exception is the product decision the track recorded and is held by `TestGetPaneKeepsItsOwnRouteUnderBothSpellings` and `TestGetPaneSpellingIsUnchanged`: `get pane` stays the exact-one Pane read that owns `--current -o cwd`, `get panes` stays the 0..N inventory, and neither aliases onto the other. `rename` no longer special-cases the `agent` token by hand -- the manifest has no Agent child, so `agent` and `agents` both reach the ordinary refusal.
|
|
187
215
|
- `make test-integration` / `make test-e2e` / `make test-install-smoke`: suite containers still run with `--network none`, so `scripts/test-docker-run.sh` prefetches the checked-in module graph once per `go.sum` into a host-side Go module cache outside the repository and mounts it into the isolated run; `test/lib/smoke.sh` honors that inherited `GOMODCACHE` instead of forcing an empty run-local one.
|
|
188
|
-
- `make test-
|
|
216
|
+
- `make test` / `make test-e2e`: resource-first create and materialization removes the `--project` dispatch discriminator, so `create window|pane|agent|<provider>` share one parser and one product model. A two-way argv table parses every flag group -- `--name`, `--label`, `-o`, the `--` payload, `--window`/`-w`, `--pane`, `--selector`, `--create-window`, `--placement`, `--cwd`, `--add-dir` -- with and without an explicit `--project` on all six spellings and requires the two parses to be identical except for the scope occurrence, which is what the user reproduction `create codex -w hi --create-window` failed on; a structural test additionally requires `create` to carry no raw-argv split seam at all, so the removed model cannot return by one edit. Scope resolution has two branches: an explicit `--project` wins inside and outside tmux and never observes the active target, while an omitted one derives the Project from the mirrored `@projmux_window_uid` and its registry owner chain, and derives the Window **and** anchor Pane only when no `--project`, `--window`, `--pane`, or `--selector` was given at all -- which is what keeps the generated `create codex|claude|pane --placement right|down` bindings a split of the Window the operator is in rather than a fan-out over the Project. Refusals are measured, not asserted: outside tmux, a Home/control pane with no mirrored Window uid, an unattributed pane, a foreign uid the Registry does not hold, and a Window whose owning Project is gone each exit 2 naming `--project` with zero stdout, zero registry transactions, a byte-identical registry snapshot, and zero tmux calls, on all four create spellings; an argv-only refusal table additionally requires every invalid-input row to be decided with zero active-target observations, so a typo is never reported as a missing scope. Behavior tests run against the in-memory tmux server: the user reproduction creates Window/Agent/managed Pane in one transaction, materializes them in the derived Project's own session, mirrors the allocated uids onto the exact tmux objects, and prints the managed Pane's live handle for `-o pane-id`; an explicit `--project beta` from inside an alpha pane lands in beta with the active-target seam untouched; an explicit `--window` suppresses the implicit anchor; and a failing `split-window` under an implicit scope rolls back to a byte-identical registry with no leftover tmux window. The generated keybinding producer now renders every `run-shell` projmux binding as `TMUX_PANE=#{pane_id} <bin> ...`, because tmux exports `$TMUX` to a `run-shell` child but never `$TMUX_PANE`; a catalog-wide test pins that rendering for every run-projmux action and runs each `create ...` binding body through the route to prove the argv the generated config ships is the argv the implicit-scope route accepts. No create path issues `switch-client`, `select-window`, `select-pane`, or `attach-session`.
|
|
217
|
+
- `make test-integration`: Docker-backed Linux integration smoke with real `tmux`, `git`, and `stty`; covers Doctor schema 2 missing/corrupt/permission degradation, runtime socket/config current/stale findings against a guarded run-unique `-L` socket, source byte/mode/mtime/inventory no-repair evidence, explicit support-report no-request/preview/private archive and Doctor-schema inspection, missing/corrupt/permission-denied source manifest reasons with no source-mode repair, preview-writer failure before output creation, collision byte/mode preservation and temp cleanup, tmux config render/install/apply including stale live `C-t` pane-label retirement, current `M-p` preservation, current `C-t` reassignment, repeated apply/keymap byte preservation, and unrelated live/current binding preservation, default-off/on live resource config plus two-sample procfs status rendering, notify queue CRUD against isolated HOME/XDG paths, private operational-log persistence/mode repair/readback/hot-path exclusion/best-effort write failure, and the built-CLI saved-default split bridge reaching canonical intent scope: with `%7` carrying no Pane mirror it exits 2 with the exact origin-loss reason, writes nothing to stdout, issues only the read-only exact-Pane probe, and performs zero raw tmux mutations, where the retired bridge would have logged one `split-window`. The generated config is also audited for the CLI internal-namespace relocation: it must contain the `internal status`/`internal statusbar`/`internal tmux` spellings and no `'<bin>' <route>` invocation of any of the 7 removed pre-namespace plumbing tokens; invoking any removed token directly is separately pinned to the root unknown-command contract. Root containers run the permission case through `runuser`+`nobody`; environments without that adapter take an explicit asserted skip instead of treating root chmod access as denial.
|
|
189
218
|
- `make test-install-smoke`: Docker-backed source install smoke; covers `make install`, atomic binary replacement, `config apply` against a live `projmux` socket, post-install notify queue initialization, the installed `~/.config/projmux/tmux.conf` carrying the relocated `internal status`/`internal statusbar` spellings and no pre-namespace `status`/`statusbar`/`tmux` invocation of the installed binary, and installed-binary temporary HOME/XDG support-report preview → explicit private archive → manifest/Doctor schema inspection with exact smoke-root cleanup.
|
|
190
|
-
- `make test-e2e`: Docker-backed real-tmux workflow smoke for session/pane setup, a
|
|
219
|
+
- `make test-e2e`: Docker-backed real-tmux workflow smoke for session/pane setup, a canonical `create pane -o pane-id` whose returned `%N` handle is verified against the isolated live server, an attached-client `rename-pane-label` command-prompt flow covering current-label initial value, Enter confirm, native Esc cancel, empty clear, and AI topic/manual/raw-title non-mutation, app config sourcing, reply-state notify reconciliation, focus notify fallback, canonical `internal status notify` rendering with contextual project/state/agent badges, and an isolated attached-client `display-popup` Settings recorder flow against a temporary HOME/keymap: one Add binding recorder saves a single C-r after one confirm, Esc cancels another draft with zero mutation, the same still-open reader records visible `C-o,o`, pops/re-adds its last stroke with Backspace, writes canonical `C-o o`, applies the live root-table trie, renders comma-form detail, and removes the sequence without stale tables; stale retired picker env/file values remain inert, unchanged, and silent. The reused FIFO/attached-client Notify Clear All flow has command markers and queue JSON proving cancel no-write, INFO/WARN/CRIT confirm clear, and pane focus restoration. Host-only terminal, WSL, macOS, and GUI notification behavior remains outside Docker; see [docs/testing.md](testing.md).
|
|
220
|
+
- `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.
|
|
221
|
+
- `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.
|
|
222
|
+
|
|
223
|
+
- `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.
|
|
224
|
+
|
|
225
|
+
- `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.
|
|
226
|
+
|
|
227
|
+
- `make test` / `make test-integration` / `make test-e2e`: Registry-first control plane Phase 9 exit reconciliation consumes a generation-safe termination receipt and one exact runtime absence in a single lifecycle transition. `internal/core/metadata` adds `ProjectTermination`, the one transition body, plus the `TerminationSourceReconcile` provenance, the `AgentExitUnknown` exit, and a distinct `status.reason` clause per classification. Its tests pin the whole matrix from the evidence side -- an explicit control action is `intentional`/`Offline`, a supervised exit 0 is `normal`/`Offline`, exit 42 and a signal are `abnormal`/`Failed`, and an absence with no receipt is `unknown`/`Offline` -- with the released Agent asserted on every row for a cleared `paneRef`, a preserved `status.sessionRef`, a mirrored receipt that survives the deleted Pane resource, and a registry that still validates. Idempotence is asserted as two independent properties: the absence-with-no-receipt case *records* an `unknown` receipt so a second pass finds it already stored, and running the projection again leaves the registry byte-identical for an agent Pane with a receipt, an agent Pane without one, and a shell Pane. The guards get their own cases: an event quoting a replaced generation leaves the current binding and the whole registry untouched; an Agent-owned Pane the Agent no longer binds after a resume receives the evidence and nothing else, with the resumed binding still `Running`; a shell Pane's runtime loss keeps the resource, adds `MissingRuntime`, and adds the evidence; a Pane with no activation generation at all -- every Project session's adopted first pane -- converges on `unknown` and stops being re-projected; recorded intent still wins over the signal a deliberate delete causes; and a nine-row pairing table plus a `Validate` leg prove `unknown` is writable only by `source: reconcile` and that the reconciler may write nothing else, so a hand-edited registry cannot bypass it. `internal/app` owns the dirty-event seam: a narrowed event reconciles exactly the Pane it names and leaves another dead Pane's evidence unwritten, a stale-generation event opens **zero** transactions, a repeat pass opens zero and reports why, and both trigger paths -- the reconciler's observation step and the one-shot -- are asserted to classify the same exit 42 into the same phase, reason, and preserved exit code, because they share one transition body. Exact-host containment is proven over the routed two-server fake for both `-L` and `-S`: the sibling mirrors the dead Pane's uid on a live pane of its own and reuses the same `%N` handle space, and the assertion is that the sibling receives zero calls while the event host's absence still decides the outcome. A forbidden-verb audit over the recorded argv proves the pass issues no `split-window`, `respawn-*`, `kill-*`, `send-keys`, `set-option`, or focus-moving call, so an observation can never become an activation; the core-layer half asserts no replacement Pane is materialized for an Offline Agent. Fail-closed is pinned for both the preflight and the in-lock observation, each leaving the registry byte-identical and returning no error. The read projection is asserted with zero writes throughout: `describe agent|pane` renders classification, source, observed instant, exit code or signal, Pane ref and generation as their own rows and renders none at all for a live resource, `get pane|agent` renders a `TERMINATION` column of `<classification>/<source> [exit=|signal=] <age>` that stays blank for a live row (the four whole-table goldens and the display-cell width suite move with the added column), the Registry-first navigation row carries a *copy* of the receipt and renders it in its own column, a navigation refresh is re-pinned as single-socket and mutation-free, and a pending intent receipt on a still-live Pane survives `get` in three output modes plus `describe` with the Agent left `Running`. The integration smoke reconciles real shell and provider exits on isolated exact sockets and reads the phases and receipts back out of an isolated Registry: exit 0, exit 42 and a signal for the three provider stubs, an external `kill-pane`, a `SIGKILL`ed supervisor (the one case that is deterministically `unknown`, from `source: reconcile`), a preserved shell Pane carrying `MissingRuntime` plus its evidence, a write-free repeat after every case, and a restart leg on both an app-owned and a standalone host whose assertions are deliberately narrow -- an unreadable server may not attract *reconciler* evidence even though the same pass honestly reprojects session liveness, `kill-server` SIGHUPs the panes so a surviving supervisor's real `abnormal signal=HUP` receipt is accepted alongside `unknown`, and the restarted server must end with zero mirrored pane uids because an observation is not an activation authority. A sibling server carrying the same mirrored Pane uid on a live pane of its own is byte-identical afterwards and changed no outcome. The e2e smoke proves the user-visible property the integration legs cannot: with the generated config installed on an exact socket, a provider exiting 42 reaches `Failed` and one exiting 0 reaches `Offline` **with no projmux command run to make it happen** -- convergence is polled through `describe`, which reconciles nothing -- and it repeats the read-projection, restart/no-autostart, and sibling-containment legs against a real server.
|
|
228
|
+
- `make test` / `make test-e2e`: Registry-first control plane Phase 10 scopes an explicit singular reference to the active Project. A `metadata.name` is unique inside its owner scope and never across the Registry, so `projmux describe window zsh` typed inside a managed Project compared one word against every Project at once; the measured baseline on the installed binary was `--window zsh matched 9 windows, want exactly one`. `describe window|pane|agent <ref>` and `rename window|pane <ref>` now fill `selector.Query.DefaultRoot` with the active Window's Project owner, which is the same `windowScope` choice point the plural `get windows|panes|agents` default uses -- the two differ in which invocations opt in, and Phase 13 later widens only the plural default to both managed root kinds. `internal/app` pins the applied matrix in both directions: each of the five adopting `<verb, kind>` pairs resolves a name that also exists in another Project to the active Project's resource with exactly **one** observation, while `describe|rename project`, `rename agent`, `delete`, `rebind`, and `agent resume` stay whole-registry with **zero** observations, and an explicit `-p beta` resolves beta with **zero** observations. The narrowing is proven not to be a target rule: two same-named Panes inside the one Project stay the bounded `matched 2 panes, want exactly one` ambiguity with no beta candidate in the listing rather than being broken by the active Window, a `--selector` label is scoped the same way, and a `uid:` reference belonging to another Project is `matched no windows` rather than a cross-Project hit. Outside tmux every one of those argv shapes keeps its byte-identical whole-registry result and ambiguity. Inside tmux an underivable namespace is its own refusal -- `resolve project scope: a resource reference was given inside tmux and ...; the active Project namespace is undecidable, ...` -- pinned verbatim for a missing `@projmux_window_uid`, a mirrored Window uid the Registry does not hold, and a Window with no owning Project, each asserted to be a usage error with 0 bytes on stdout, 0 writes, and a byte-identical registry snapshot on the mutation half; it is deliberately not the active-target refusal, whose "no selector was given" opening would be false here. E2E runs the same three shapes on both host modes against real tmux: the standalone inherited-socket client builds Window `ns-one` and Pane `shared` in alpha *and* beta plus a second alpha `shared` under `ns-two`, then proves the reproduction resolves one Window, the intra-Project ambiguity survives, `-w`/`-p` disambiguate, a foreign uid is a no-match, `rename window` moves only the active Project's Window, and unsetting the live `@projmux_window_uid` refuses instead of widening; the app-owned `-L projmux` host repeats the reference/explicit-`--project`/foreign-Window trio read-only against its own fixture. Singular `uid:` namespace semantics, `delete`, and the empty-selector active target remain unchanged.
|
|
229
|
+
- `make test` / `make test-integration` / `make test-e2e`: Registry-first control plane Phase 11 lifecycle trigger convergence and legacy seam retirement. **One entrypoint.** `internal tmux reconcile-bindings` and `internal tmux release-dead-agent-panes` are retired and replaced by one hidden route, `internal tmux converge --socket-path <path> --session <id> --reason <config-apply|runtime-created|runtime-exited>`, which `config apply` and all four generated lifecycle hooks reach; the reason set is one closed enum the route parser and the hook renderer both read, so a spelling a config can emit is one the binary accepts, and a malformed target, a missing reason, an unknown reason, and a positional argument each refuse with the controller reached zero times. A trigger carries no pane id and no generation on purpose -- `after-kill-pane` fires with an empty `#{hook_pane}` -- and the trigger-inventory test reads both generated configs plus the live `show-hooks -g` output to prove every hook reaches the entrypoint, that the creation hooks stay synchronous while the exit hooks stay backgrounded, and that neither retired route survives anywhere. **One pass.** A pass is one locked reconciliation followed by an exit-half reobservation, in that order: the projection has to run after the same pass's binding writes or it files an unknown termination against every Pane the pass was about to bind, which a stage-order test states directly. `config apply` still converges only after a successful `source-file`, on the exact `-L` target it reloaded, with no hook session because an apply is not caused by a create; `--no-reload` and every preflight failure remain strict no-touch paths. **At most one worker.** The dirty-event log is one file per event under `<state>/projmux/controller/<key>.events/`, so marking needs no lock and no counter can be lost, and the worker lease is an advisory whole-file `flock` the kernel releases on process death -- an abandoned lock file is acquirable immediately, which is what makes crash recovery need no staleness rule. Coalescing is measured rather than described: eight producers arriving during one pass cost two convergence bodies and one worker, every loser exits zero with its event still unacknowledged, the loop repeats until a pass writes nothing and nothing is pending, and the repeat bound stops a producer that marks faster than a pass completes without claiming convergence. The e2e burst additionally proves no `acquire lock: exhausted` appears anywhere, which is the failure mode the pre-Phase fleet of per-event workers produced, and that a `get` read starts no worker and leaves no event or lease behind. Two spellings of one server get two keys and therefore two leases, and a sibling socket is never blocked by another's. **Split UI convergence.** `internal agent-pane launch-default` and the `Alt-7`/resume pickers produce a canonical create intent -- provider, placement, and for a resume the provider-normalized conversation id -- rendered as the exact argv an operator would type, so the projection is asserted as a pure function rather than through whatever the create did next; the resume branch has no public spelling and reaches the shared Agent body with one extra field, which keeps `create` free of a public `--resume`. The saved mode is the only hidden state the UI reads, a mode naming no launch opens its popup, and a saved or selected provider Settings has disabled refuses before the intent exists with zero mutations. The legacy `ai split` route, its `--agent`/`--force-agent`/`--print-pane-id` flags, the layout/context/title-watcher helpers only it used, and `mux.SplitWindow` itself are all deleted, and a source audit over `internal/app` proves the materializer is the only place a `split-window` argv exists -- tmux's own pane-context-menu entries excepted, which are menu items rather than a projmux-issued split.
|
|
230
|
+
- `make test` / `make test-integration` / `make test-e2e`: Popup-origin split anchor. A `display-popup -E` job inherits `$TMUX` and no `$TMUX_PANE`, so after the split UI moved onto the canonical create intent every terminal action of the two split pickers -- provider row, `shell` row, resume row, and the resume picker's `new` row -- reached `create` with nothing to derive a scope from and refused with the `--project` usage error (exit 2). **The origin pane travels on the intent.** `agentPaneIntent.anchorPaneID` carries the pane the keypress came from, `createCommand.withAnchorPane` makes it that invocation's active target through `anchoredActiveTargetLookup`, and Project/Window/anchor resolve through the same identity mirror a pane-hosted invocation reads. Named regressions fix both halves in one table: an anchored intent with no `$TMUX_PANE` creates exactly one Pane in the origin pane's Window, while an anchor-free intent, a whitespace anchor, an anchor that leaked outside any client, and an anchor carrying no managed identity all keep the exact refusal and exit 2 with zero Registry writes and zero tmux calls. A typed `create pane|window|agent` inside the same popup runtime -- origin pane present in the environment -- keeps its refusal byte-for-byte, the pane-hosted `launch-default` producer states no anchor and probes nothing, and a source audit pins `TMUX_SPLIT_TARGET_PANE` to one reader in the split UI so it cannot become an implicit scope for the read, rename, or delete verbs.
|
|
231
|
+
|
|
232
|
+
- `make test` / `make test-integration` / `make test-e2e`: Registry-first control plane Phase 0 control-session resource slice. The app-owned Home session becomes a Registry **owner** of Windows and Panes without becoming a Project. `internal/core/metadata` unit and table tests pin the fifth kind `ControlSession` in the closed `Kinds()` set and its declaration order, the `ctl-` uid prefix through `NewUID`/`UIDKind` with no prefix collision against the other four kinds, and the widened `requireOwner` **allowed set** -- a Window may be owned by a Project or a ControlSession, and the refusal now names both (`ownerRef kind "Pane" is not "Project" or "ControlSession"`) while every single-kind refusal (`Pane` under an `Agent`, an `Agent` under a `Project`, a `Project` carrying an `ownerRef`, a missing `ownerRef`, an `ownerRef` that does not exist) keeps its wording byte-for-byte. A reflect test pins that `ControlSessionSpec` carries **no path field at all**, which is the compile-time reason `$HOME` can never reach managed roots, trust, rebind, or cwd defaults. `BindControlSession` is proved convergent in the same table: a first pass mints the ControlSession plus one Window and Pane and creates zero Projects and zero Agents; a second pass over live objects carrying the allocated uids rebinds with a byte-identical registry; an unmirrored live window **adopts** the existing unbound Window instead of duplicating it (the mirror-write-failed repair); a live uid this Registry does not know mints a fresh resource rather than reusing the foreign one; a uid that belongs to a Project's scope is refused outright and left untouched; a mid-transaction failure rolls back every resource and reservation. A pre-Phase-0 registry document decodes, classifies as `SchemaCurrent`, validates, and re-encodes without the `controlSessions` key -- **`schemaVersion` stays 1 and `productionMigrations` stays empty**. `internal/app` covers the writer: the `projmux shell` entry provisions the app session with `has-session` followed by a bare `new-session -d`, and only then converges. It is deliberately **not** `new-session -A -d`: measured on tmux, `-A` against an existing session turns the create into an attach that `-d` does not suppress (`open terminal failed: not a terminal` outside a terminal, a seized client inside one), so the already-live backfill this preflight exists for would never run -- a table test therefore rejects any `-A` anywhere in the preflight argv, and the probe/create argv pair is asserted in order. A five-row table pins every answer the probe reads as absent, including the socket-level `error connecting to <path> (No such file or directory)` a real run measured on a socket whose file does not exist yet, while an unclassified probe failure such as `permission denied` stays fail-closed: nothing is created and the pass does not run. A live session is probed and left exactly as it was found, a `duplicate session` race is read as the postcondition already holding, the foreground attach argv stays byte-identical to the pre-marker contract, a Project-default target gets **no** preflight and **no** marker, and a failed provision or convergence prints one warning and still attaches. The converger writes `@projmux_session_role=control` once, on one exact `-t <session>` target with no `-g` and no pattern, routes every tmux call through the invocation's explicit `-L <socket>`, and mirrors `@projmux_window_uid` / `@projmux_window_name` / `@projmux_pane_uid` / `@projmux_pane_label` onto the exact live objects it bound; a repeat pass writes zero registry bytes and re-mirrors no already-bound uid. Fail-closed rows pin that a server without `@projmux_app=1`, a hand-set app-marker value, and a session carrying `@projmux_ephemeral=1` each skip with zero transactions and zero option writes. The parity audit compares `get projects` against the same registry with and without a control session (identical output, no `home` and no `ctl-` uid anywhere), lists the control-owned Window and Pane through `get windows -A` / `get panes -A`, resolves `describe pane` with no selector inside the control session, and pins the **next-phase-encroachment guard**: `create`/split inside Home still refuses with the `--project` usage error and exit 2, with both the pre-Phase-0 detail clause (`carries no @projmux_window_uid`) and the post-Phase-0 one (`resolves to window "window", which has no owning Project in the registry`) asserted verbatim so the documented wording change cannot drift.
|
|
233
|
+
- `make test`: Control-owner reconcile commit projection. `projmux reconcile resources` (execute) aborted at the Registry commit with `validate registry: Window name reservation "window" refers to unknown uid "win-..."` on any machine whose Home session had become a `ControlSession` owner, so `--dry-run` planned cleanly while every execute rolled back and repaired nothing -- including the `@projmux_project_uid` / `@projmux_project_name` session mirrors another recovery path reads. **The defect was the projection, not the file.** `mergeScopedResourceRegistry` rebuilt Windows/Panes/Agents from `retained` (the graph of every *other* Project) plus `changed` (the graph of the reconciled Projects), and a Window a ControlSession owns is in neither, so it was dropped while `out.NameReservations = scopedAfter.NameReservations` put its reservation back. `retained` now comes from `resourceRegistryWithoutProjectGraphs`, a removal rather than an inclusion, so a resource the removal set does not reach is carried through untouched; the two are exact complements over a well-formed registry, which is why a Project-only registry projects byte-for-byte as before. Two further invariants are pinned rather than described: `resourceRegistryProjectGraph` claims the reservations of the ControlSession roots `Clone()` brings along, so the scope projection can no longer hold a root `Validate` rejects from the other direction (`ControlSession %q has no name reservation in scope`); and the commit projection is filtered through `retainReservedResourceNames`, which drops any reservation naming no resource it carried -- a table row over a scope whose Project stopped resolving between the two halves shows that failure was reachable for a Project-only registry too, before a ControlSession existed. The table runs {with control-owned resources, without} x {`resourceRegistryProjectGraph`, `scopeResourceRegistry`, `mergeScopedResourceRegistry`, a vanished scope}, asserting the committed projection passes `Validate` outright while the two allocator scopes -- which deliberately carry out-of-scope reservations as allocator input -- may never carry a resource the reservation table does not name. A round-trip test proves the ControlSession, its Window, and its Pane come through a no-op pass with the same uid, name, owner, and reservation, and that a second pass is a fixed point. The public route is covered end to end: with a live Home control session carrying the control role and mirrored Window/Pane uids, execute exits 0, commits a `Validate`-clean registry, writes both session option mirrors, leaves the control graph byte-identical, agrees with `--dry-run` on refused drift, and writes zero registry transactions on a repeat pass. The refuse vocabulary is a three-row table: a control-owned Window sitting in the session its own ControlSession is bound to is converged and is **not listed** at all, the same Window observed anywhere else is refused as `live Window uid is outside the exact ControlSession owner scope`, and a Project-owned Window outside its Project keeps `live Window uid is outside the exact Project owner scope` byte-for-byte.
|
|
234
|
+
- `make test`: Root kind projection sweep. C-5 says a Registry has two root kinds, `Project` and `ControlSession`, and that every projection knows both; `#705` paid that cost for exactly one traversal by inspection, and nothing said how many others there were. `internal/app` now carries the sweep as an executable table: every non-test function that ranges over a root slice is classified `both-kinds`, `paired` (a kind-scoped lookup whose ControlSession half is a named sibling), `project-only` (Project semantics -- a root path, a Project session claim, a Project pin -- where a ControlSession is structurally absent rather than forgotten), or `gap`, and the site set is **re-derived from the source with `go/ast` on every run**, so a traversal added, removed, moved, or renamed fails the test until it is classified. Each verdict is machine-checked against the same scan: a `both-kinds` site must range over `.ControlSessions` in the same function, a `paired` site must name a sibling that does, and a `project-only` or `gap` site must not. Every function that visits `.ControlSessions` must appear in the table as a site or as a named pair, so no root traversal can hide from the sweep in either direction. A second test asserts zero `gap` rows inside the Phase 4 boundary and logs the ones outside it with their exact path and owner; the whole table renders as a fixed-width printable report. The one gap inside the boundary is closed: `registryResourceRecords`, the flat record projection behind `registryUIDSet` and `registryReconcileItems`, read Project as the only root, so a ControlSession uid answered "not a resource projmux owns" to the foreign-drift check and an appearing or changed control root produced no plan item at all. It now carries both kinds, pinned behaviourally: the uid set contains the control root, an appearing ControlSession produces exactly one `missing`/`create` item targeting `controlsession/<name>`, and an unchanged Registry still produces none. The four routes C-5 Guarantee has to survive are green on one control-owned fixture seeded through the real `BindControlSession` -- `reconcile resources` execute, `prune project --missing --older-than --yes`, the pin authority (selection, rows, discovery paths, legacy resolution), and `--materialize-project` dry-run plus execute -- each asserting the Registry still validates and the ControlSession, its Window, its Pane, and all three name reservations come through untouched. The pin route additionally pins C-5's Non-Guarantee as behaviour: the control root never reaches the resolver as a `ProjectRef`, never reads as a pinned Project, and a legacy path pin naming the control session's own Pane cwd stays a candidate instead of being typed onto anything.
|
|
235
|
+
- `make test`: Declarative contract stabilization Phase 7 divergence taxonomy. `TestDivergenceTaxonomyTableIsClosedAndPrintable` pins the closed printable D1--D6 label set; `TestDivergenceClassifierCoversEveryRegistryInventoryCombination` and the named property test `FuzzDivergenceClassifierLabelsEveryRegistryInventoryItem` prove every item produced for arbitrary Registry/runtime Inventory pairs has exactly one valid label and a non-empty separate reason; `TestIncident20260820ClassifiesExactlyTwoOrphanMirrors` fixes the incident fixture at exactly two D3 orphan mirrors; and `TestResourceReconcilePlanItemsHaveExactlyOneDivergenceAndReason` covers every plan/refusal emitter. `TestControlSessionRootPreservesRuntimeAndDescendantAttribution`, `TestControlOwnedHomeGraphSurvivesSidebarRuntimeAndSessionAttribution`, `TestRootKindProjectionSitesAreExhaustivelyClassified`, and `TestRootKindProjectionSweepLeavesNoGapInsideThePhase4Boundary` close the resolver/index, node, Registry view, runtime diagnostics, offline-row, and session-attribution root-kind gaps while preserving the control-owned Home graph. Doctor and dry-run goldens pin all six counts, and the support-report redaction test permits only aggregate counts -- no reasons, paths, or identifiers.
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
- `make test` / `make test-integration`: First-open Project identity convergence. Opening an unregistered directory mints an exact Registry Project UID and sends session creation, initial Window/Pane adoption, Project identity, lease, and startup finalization through the canonical materializer transaction on one explicit app route before moving the client. There is no reduced `EnsureSession`/later-mirror fallback: a missing UID, tmux runner, or canonical transaction seam refuses with zero runtime writes, and a failed stage rolls back only exact handles owned by that operation. An already-registered Project still converges through the Registry topology engine; a Home row carries no Project UID and therefore cannot authorize managed Project creation. The two-action startup screen uses the same transaction for `Continue project` and requires canonical identity before `Open fresh`. End-to-end coverage pins unregistered directory → one Project → one fully attributed session → subsequent managed Window/Agent creation, while `preflightSessionOwnership` continues to refuse blank, foreign, and duplicate-root claims with zero Registry/tmux mutation. The real-tmux smoke uses an isolated unique logical socket and `TMUX_TMPDIR`, removes inherited `TMUX`/`TMUX_PANE`, verifies app/logical markers and exact `#{socket_path}`, and cleans up only that observed socket.
|
|
239
|
+
|
|
240
|
+
- `make test`: Declarative contract stabilization Phase 2 adds the Registry materialization invariant audit to `projmux doctor` as the `registry` section, present in the default report and in `--section registry`, in both text and `--json`. The audit is the consumer predicate rather than a second opinion about it: every Registry Project is planned through the shipped `planRegistryTopology` with no observed sessions, and `refusalScope` supplies the fatal/skipped split, so `TestDoctorRegistryInvariantAuditReusesTheMaterializePredicate` fails if the reported counts ever diverge from the planner's own refusals over the same Registry. After schema v2, `TestDoctorRegistryInvariantAuditReportsTheFieldFailureStructure` proves deleting a Window's last shell installs a canonical replacement and therefore emits no skipped-Window finding; the remaining gone-root Project is still reported as `registry.materialize.fatal.project`. `TestDoctorRegistryInvariantAuditStatesTheZeroCase` requires a clean Registry to report `registry.materialize.clean` and the number of Projects planned without `--verbose`, because a silent clean audit is indistinguishable from a section that never ran, and `TestDoctorRegistryInvariantAuditGoldens` pins the zero and N section output in text and JSON. `TestDoctorRegistryInvariantAuditWritesNothing` runs the production zero-write snapshot read under an isolated `HOME`/`XDG_STATE_HOME`/`XDG_CONFIG_HOME` on a machine that never created a Project and requires an unchanged filesystem tree and no state directory; `TestDoctorRegistryInvariantAuditNeverReachesATmuxServer` pins that the runner handed to the planner refuses every call, and `TestDoctorRegistryInvariantAuditDegradesWhenTheRegistryCannotBeRead` keeps an unreadable Registry a `registry.materialize.unavailable` finding rather than a failed diagnostic. `TestSupportReportCarriesRegistryInvariantCountsWithoutReasonsOrPaths` requires the archive's `doctor.json` to carry the same codes, kinds, and counts and neither the planner's reason wording nor the fixture's absolute paths, and `TestDoctorRegistryInvariantCodesStayInsideThePublishedInventory` keeps every emitted code and remediation inside `doctorFindingCodeInventory`/`doctorFindingRemediationInventory` so nothing reaches a support archive as an opaque hash. The audit performs no repair, schema migration, or display-layer diagnosis, and adds no second diagnostic command.
|
|
241
|
+
- `make test` / `make test-integration` / `make test-e2e`: Declarative contract stabilization Phase 3 raises the Registry envelope to schema v2 and ships the atomic v1 → v2 invariant repair. `TestRegistryV2GenerationMigrationGoldens` labels the 0.10/0.11/0.12 source generations and pins raw SHA-256 bytes for every source/output pair while comparing generated output directly to the raw golden bytes and asserting repair and information-loss counts, identity/name/owner/name-reservation preservation, Agent conversation-pointer preservation, graph validity, and a no-op second pass. `TestV1MigrationCreatesCanonicalShellChainForAZeroWindowProject` covers additive opaque Window/Pane creation, and `TestValidV2CanonicalAnchorRoundTripsWithZeroDiff` fixes a valid anchor at zero diff. `TestEveryFirstMigratorLeavesDurableV012RepairAndLossEvidenceOnce` drives normal `Load`, `Update`, and explicit `Migrate` as the first writer and requires one private report adjacent to the exact versioned backup, with that backup's absolute path/SHA-256 and exact repair/loss detail; returned paths match the durable pair where the API returns evidence, and the next migration pass writes zero Registry, backup, or report bytes. Failure-injection rows require a report published before Registry staging to be removed on every failed ordering. `TestMigrateRejectsAnInvalidCurrentRegistryWithZeroWrite` prevents a current-version no-op from bypassing validation, and future/unversioned envelopes remain byte-identical refusals with no evidence report. `TestV012MigrationLeavesNoMaterializeRefusals` runs the shipped materialize predicate over the repaired 0.12 sample and requires zero fatal and zero skipped items plus the exact Project → Window → direct shell Pane chain. Current-schema validation globally enforces that chain, and last-shell deletion installs a replacement bare shell. Phase 15 consumes this canonical anchor as the only identity retained by `Open fresh`; schema-v2 validation remains the pre-commit refusal boundary for an invalid anchor.
|
|
242
|
+
- `make test` / `make test-e2e`: Declarative contract stabilization Phase 5 moves the generated `MouseDown3Pane` Horizontal/Vertical Split and Kill entries onto managed resource intents. The generated-config byte golden fixes the exact `internal tmux pane-menu --client #{client_tty} <action> #{pane_id}` actions, and a negative audit requires zero `split-window`, `kill-pane`, `respawn-pane`, and `Respawn` literals in that menu. Unit tables prove both split directions emit one `agentPaneIntent` with the exact clicked `%N` in `anchorPaneID`, so `createFromIntent` and the popup-origin `anchoredActiveTargetLookup` remain the only scope and mutation definitions; Kill resolves through the same anchor and calls canonical `delete pane`, preserves its full output, and displays its summary on the originating client. Injected create and delete refusals are displayed on that client and trigger no fallback mutation. Respawn is removed from the generated menu and its internal action is rejected; no managed respawn/replace intent is introduced. The isolated real-tmux leg strips inherited `TMUX`/`TMUX_PANE`, uses a run-unique `TMUX_TMPDIR` and socket, audits the installed `MouseDown3Pane` binding and selects Split/Kill actions through a real `display-menu` on an attached client, verifies split uid/Registry visibility and canonical Kill removal/result, and validates then records the exact socket cleanup target under its smoke root.
|
|
243
|
+
- `make test` / `make test-integration` / `make test-e2e`: Declarative contract stabilization Phase 6 makes termination evidence survive the exit boundary without taking the Registry lock. The supervisor writes one bounded JSON row, framed by leading and trailing delimiters in a single `O_APPEND` call to `termination-receipts.jsonl`; the leading delimiter terminates any prior process's partial tail. Controller convergence reads the append snapshot outside the Registry transaction, absorbs generation-guarded receipts before absence projection, tolerates malformed/partial rows without disabling later convergence, and relies on duplicate/stale receipts as no-ops rather than truncating or garbage-collecting the journal. `TestSupervisorReceiptPrewriteDoesNotWaitForRegistryLock` proves the prewrite never enters a deliberately blocked Registry update, `TestMalformedTerminationJournalRowCannotDisableConvergence` pins recovery from a truly unterminated tail, and `TestInvalidTerminationJournalRowCannotDisableLaterValidReceipt` proves a well-formed but contract-invalid row cannot poison later valid evidence. The generated hooks use three distinct reasons: `pane-exited` carries exact `#{hook_pane}` and reobserves only the matching stored activation handle; `pane-killed` carries no invented pane and retains whole-host reobservation; `window-unlinked` carries exact `#{hook_window}` as explicit event evidence while retaining whole-host fresh reobservation. Phase 6 had no persisted runtime/liveness mapping; Phase 3 now persists only the exact last-positive Window owner handles required for causal teardown. `TestLifecycleHookRoutesCarryExactPaneAndWindowEvidence` and `TestWindowUnlinkedRecordsKillEvidenceWithoutPersistedRuntimeState` pin the Phase 6 boundaries and kill-window evidence recording. `TestPaneKilledCoalescedBehindExactPaneExitWidensEveryLaterPass` pins that a narrow pane-exited lease holder widens after any coalesced event and stays whole-host through its final no-op verification, so a kill or different-pane event cannot be acknowledged under the first Pane's selector. `TestPaneExitedAbsorbsLateSupervisorReceiptAfterUnknownProjection` covers the fast-exit ordering where runtime-created first records unknown: exact pane-exited still treats later journal absorption as work, refines the released same-generation Agent's evidence to normal or killed, and repeats with zero transactions; `TestLateSupervisorRefinementNeverFollowsAResumedBinding` keeps a new non-empty Pane binding strictly outside that exception. The closed classification vocabulary adds `killed`: only a supervisor's SIGHUP wait status may claim it, it means external kill rather than intent, and a same-generation `control-action/intentional` receipt remains sticky when the resulting HUP arrives. `TestHookWaitStatusReceiptDecisionTableIsClosed` covers pane exit, pane kill, standalone window kill, last-pane clean exit, paired control receipt, and evidence-free absence; `TestTerminationPairingRejectsUnentitledClaims` proves supervisor cannot claim intent, control action cannot claim observed outcomes, and reconcile can claim only unknown. `FuzzTerminationEventSequencePreservesCanonicalDeleteAndHookRows` fixes the authority boundary over arbitrary event order: canonical delete removes the Pane row and every repeat is byte-identical, while hook-driven observation changes evidence/status, clears the Agent binding, preserves Pane/Agent rows, and a repeated reconciliation opens zero transactions.
|
|
244
|
+
|
|
245
|
+
- `make test`: Phase 6 kill/reap race enforcement keeps external-kill evidence from settling as unknown merely because tmux's 50ms hook beat the supervisor append. `TestPaneKilledWaitsPastHookDelayForSupervisorReceipt` delays the SIGHUP receipt past 50ms and requires killed evidence; `TestPaneKilledWaitRefinesRacingUnknownWithDelayedSupervisorReceipt` starts from a racing reconcile/unknown and requires the same killed refinement. `TestPaneKilledReceiptWaitIsBoundedBeforeUnknownProjection` proves a genuinely receipt-free kill waits only for the fixed bound and then remains honest unknown. Phase 2 extends the same lock-free bounded wait to the exact `pane-exited` activation only: `TestExactPaneExitedReceiptWaitIsBoundedBeforeUnknownProjection` proves a receipt-free timeout still retains the resource with honest unknown evidence, while integration/e2e prove a racing same-generation normal supervisor row enables deletion. The wait reads only the exact-host inventory, Registry snapshot, and lock-free journal before convergence; it takes no Registry write lock, invents no intent, keeps exact pane exit narrowed to its `%N`, and leaves kill/window paths whole-host. At the process boundary, `TestRunSupervisedChildRetainsRelayedHUPEvidenceWhenChildExits129` proves a provider that catches a successfully relayed HUP and exits 129 still records external-kill evidence, while `TestRunSupervisedChildDoesNotInferHUPFromExit129` proves the same numeric exit without an observed relay remains abnormal.
|
|
246
|
+
- `make test` / `make test-integration` / `make test-e2e`: Runtime topology exit cascade Phase 2 promotes only one exact positive event to Registry delete authority. Generated `pane-exited` carries tmux's `%N` Pane and `@N` Window; preflight and locked revalidation both resolve that Window UID and the current Pane activation/owner chain on the same exact socket. A same-generation `normal/supervisor` row must already exist in the lock-free termination journal before `PlanPaneAgentCascadeDelete` removes the managed Pane while retaining its Agent Offline with `sessionRef` intact and `paneRef` empty. `TestPaneAgentCascadeDeletePlanReleasesPaneAndRetainsAgentWindow`, `TestExactCleanShellAndProviderExitCascadeRowsAfterJournalEvidence`, and `TestExactPaneExitReceiptPermutationsConvergeToOneIdempotentPlan` pin clean shell/provider parity, journal survival, duplicate/permutation idempotence, and Window/sibling preservation. `TestExactPaneExitNegativeAuthorityRowsProduceDeletePlanZero`, `TestWindowUnlinkAloneAndAdversarialObservationsDeleteNothing`, and `TestLatePaneExitNeverFollowsAResumedGeneration` pin delete-plan zero for abnormal, killed, unknown, empty/unavailable/foreign observation and stale/resumed generations. Integration drives clean shell, Claude, and Codex outcomes against an isolated exact socket and reads the pre-delete journal after the rows disappear; e2e closes one of several Panes and proves the sibling runtime, Agent, Window, Registry graph, and sibling socket remain. This path never consumes `window-unlinked`, deletes a Window/Project, changes root/pin/reopen state, or parses `/exit`, pane content, prompt, history, or transcript.
|
|
247
|
+
|
|
248
|
+
- `make test` / `make test-integration` / `make test-e2e`: Declarative contract stabilization Phase 8 recovery authority. `TestRecoveryLadderL0ThroughL10IsClosedLossOrderedAndPrintable` and `TestRecoveryAuthorityClassificationTriggerTableIsClosedAndPrintable` pin the printable closed L0--L10 ladder and every D1--D6 x hook-converge/explicit-reconcile/Project-open cell; `TestPropertyEveryClassificationTriggerAutomaticAllowanceIsExact`, `TestAutomaticRecoveryProducerInventoryIsClosedAndEveryPathIsAuthorized`, and `TestAutomaticRecoveryCallsitesCannotBypassTheProductionAuthorityGate` keep automatic producers inside the exact non-ordinal table. `TestIncident20260820ExactD3AutomaticallyRunsL8WhileL7RequiresApproval`, `TestPublicReconcileD3L8PreservesAuthoritativeRegistryAndAllowsFollowUpWrite`, `TestIncident20260820DryRunDisclosesExactApprovedL7CommandAndLoss`, `TestApprovedL7ImportsExactD3OnlyAndLeavesBlankD2Unattributed`, and `TestApprovedL7StillRefusesD4Contamination` pin exact D3 classification, automatic L8 mirror/routing-index discard, authoritative Project graph and Agent session-ref preservation through the public planner/merge boundary, a usable follow-up Registry write, and the separate `--import-orphan-mirrors --yes` L7 approval path with `identity-provenance` kind and exact object count. `TestLockedAutomaticRecoveryReclassifiesConcurrentRegistryClaimBeforeL8`, `TestAutomaticRecoveryNeverDeletesConversationSnapshotOrRegistryRow`, and the incident's managed Agent session-ref sentinel prove all production triggers reclassify under the Registry lock and automatic recovery preserves Registry rows, conversation pointers, snapshots (which are unreachable from the L8 execution seam), provider, and topic. Default hook, explicit reconcile, and Project open never import D2, including anchored legacy state; materialization is authorized as Project-open automatic L4/L5 or explicit approved L4/L5 without changing snapshot restore or Open-fresh semantics.
|
|
249
|
+
- `make test` / `make test-e2e`: Declarative contract stabilization Phase 9 degraded Registry mode. `TestDegradedModeEntersOnlyForAnIllegalRegistry` closes the state table: only `valid` and legitimate `first-use` permit ordinary mutation, while initialized missing/empty, malformed, unsupported/newer schema, graph-invalid, and unreadable Registries enter degraded mode. `TestOrdinaryUpdateCannotReplaceAnInvalidRegistry` pins the lock-free ordinary-write gate, preserves the original typed validation cause, and requires the refusal to end in exact `projmux reconcile registry --dry-run` guidance instead of exposing a raw graph error. `TestRepairDoesNotReuseOrdinaryWriteLockOrValidation` holds `registry.json.lock`, injects failure into the ordinary staged-validation hook, and proves explicitly sourced restore proceeds through its distinct `registry.json.repair.lock` plus source/staged/current verification. `TestIllegalRegistryFixtureKeepsReadDiagnosisAndRepairAvailable` drives the checked-in dangling-reservation fixture through production paths: `get projects` remains readable, recovery dry-run diagnoses and prints a checksum-guarded exact restore command, `create project` is byte-preserving and refused before the ordinary lock, explicit repair succeeds, and the same create succeeds afterwards. The existing isolated create e2e now pins a persisted stale `anchorPaneRef` as degraded exit 1 with the exact repair-plan command, zero Registry/runtime mutations, and zero stdout; its inherited `TMUX`/`TMUX_PANE` removal, run-unique socket, smoke-root socket-path validation, and exact cleanup target remain unchanged. The repair command never automatically selects or reconstructs a Registry source and does not widen the Phase 8 authority ladder or loss disclosure.
|
|
250
|
+
|
|
251
|
+
- `make test` / `make test-integration` / `make test-e2e`: Declarative contract stabilization Phase 11 control-root identity convergence. `PlanControlTargetConvergence` is the pure closed plan for one exact app socket/session declaration or an already-recorded ControlSession identity; its input enumerates Registry-root, session-role, Window uid, and Pane uid presence without cwd, display-name, command, or `ProjectByRoot` evidence. `FuzzControlTargetConvergenceIsClosedAndIdempotent` seeds all 16 partial-state combinations and requires every input to converge or record one exact refusal, with every allowed first plan producing an empty second plan. App tests execute the installed-Home repair shapes (root only missing, mirrors only missing, and root/role/mirrors all missing), prove the second pass issues zero Registry and tmux writes, and pin zero-write reasons for foreign claimants, duplicate claimants, and Project uid conflicts. Canonical `projmux shell` and successful `config apply` declare the exact target; generated lifecycle triggers continue only exact ControlSession identities already in the Registry, so an already-live Home repairs after install/apply without a one-shot migration or a raw tmux fallback. The smoke contract unsets inherited `TMUX`/`TMUX_PANE`, uses one run-unique `-L` socket under a dedicated `TMUX_TMPDIR`, records the exact socket path, and verifies that cleanup targets remain inside that smoke root.
|
|
252
|
+
|
|
253
|
+
- `make test` / `make test-integration` / `make test-e2e`: Declarative contract stabilization Phase 12 closes canonical UI create over pane-menu, saved-default, provider-picker, resume-picker, direct-provider, and direct-shell producers for both Project and ControlSession roots. `TestCanonicalCreateProducerRootOutcomeTable` executes every producer x root x created cell and rejects inventory gaps, while `FuzzCanonicalUIIntentStaysManaged` requires every generated success to add exactly one mirrored managed Pane below the exact origin Window (and an Agent owner where applicable). Scope is proved only by the Phase 11 Pane uid -> Window uid -> Project-or-ControlSession uid chain plus the exact ControlSession declaration/app/root-role mirrors; cwd remains launch workspace and never root identity. `TestCanonicalCreateConflictAndLostAnchorHaveZeroWritesAndExactReason`, `TestCanonicalProducerNegativeAuditHasZeroHiddenStderr`, `TestPopupCanonicalFailureIsProjectedToExactOriginatingClient`, and the pane-menu refusal fixtures pin zero Registry/tmux writes, no exit-code-hidden reason, and exact client-scoped projection including route stderr. `TestHomePopupOriginSplitResumeDefaultAndShellUseCanonicalCreate` and `TestHomePaneMenuSplitUsesCanonicalCreate` cover the Home popup provider, resume, saved default, shell, and pane-menu paths, while `TestOnlyTheMaterializerRunsSplitWindow` keeps raw producer mutation/fallback at zero.
|
|
254
|
+
|
|
255
|
+
- `make test` / `make test-e2e`: Declarative contract stabilization Phase 13 makes selector-omitted `get windows|panes|agents` derive one exact managed-root default from the active Window owner chain. `TestWindowScopeChoosesExplicitProjectThenDefaultRootThenGlobal` pins the resolver's Project-or-ControlSession root branch and Pane/Agent propagation; `TestPluralReadContextSelectorMatrix` asserts exact sets for all three kinds over Project/ControlSession/foreign/outside contexts crossed with omitted, explicit Project, both whole-set spellings, and applicable `uid:` selectors. Explicit `--project`, `--all-projects`/`-A`, and any opaque `uid:` occurrence bypass active observation with zero calls and keep Registry-global authority, including cross-root and foreign contexts; names remain root-scoped and a foreign context still refuses rather than widening. The real-tmux e2e extends the isolated Home ControlSession fixture with one sibling Project carrying its own Agent and one foreign Pane, runs the same exact-set matrix through the built binary, strips inherited `TMUX`/`TMUX_PANE` before supplying each exact context, validates the socket path is below the smoke root, and records the exact server/root cleanup target.
|
|
256
|
+
|
|
257
|
+
- `make test` / `make test-integration` / `make test-e2e`: Declarative contract stabilization Phase 14 closes generic operation parity over Project and ControlSession roots. `TestControlRootOperationTableIsClosedAndPrintable` is the maintained verb × root-kind table for rename/delete/focus/attention/reconcile/termination projection and permits only the intentional Project-only `rename project` and `focus project` cells. Generic `rename window|pane|agent` now derives the active Project-or-ControlSession namespace without adding a public ControlSession selector or an outside-tmux fallback. Window/Pane/Agent delete live preflight carries `(root kind, root uid, exact session)` through the signed target, applies Project's optional uid mirror separately from ControlSession's no-Project-mirror invariant, and reports final-session impact with the actual root kind. Focus Window/Pane and attention remain exact-runtime/root-neutral; reconcile retains its exact ControlSession diagnostic vocabulary; termination projection retains the ControlSession → Window → Agent/Pane owner chain while releasing only lifecycle bindings.
|
|
258
|
+
|
|
259
|
+
- `make test` / `make test-e2e`: Declarative contract stabilization Phase 10 fresh ControlSession bootstrap regression. `TestControlBootstrapNoServerBindsFirstCreatedPhysicalRoute` now starts from a genuinely absent logical route marker, accepts that absence only in the `write-route-marker` action's pre-effect phase, requires the exact marker after execution, and parses both raw and escaped tmux row separators before comparing the exact `$N/@N/%N` containment. `TestPreMarkerRouteGuardAllowsOnlyTheMissingLogicalEffect` keeps the exception closed: physical socket, server PID, app ownership, printable route authority, and any existing marker stay exact, a foreign marker is refused, and the ordinary post-write guard still rejects absence. Selector-free shell discovery treats exact HOME as an upward-walk boundary, so HOME remains a ControlSession even when `$HOME/.git` exists; nested `.git` and `.projmux` markers below HOME still resolve as Projects, and general hook marker semantics remain unchanged. The isolated real-tmux e2e runs that exact command from such a HOME against a run-unique absent socket, allows only the final non-terminal attach failure, then requires the surviving `home` ControlSession, route marker, Window/Pane mirrors, zero Projects, smoke-root socket identity, and exact cleanup.
|
|
260
|
+
- `make test` / `make test-integration` / `make test-e2e`: post-deployment Corrective A contains the Home dual-root incident without reopening a phase. `TestHomeDualRootClaimantPrecedenceAndPhysicalAllocation` and the order-independence property pin exact `ControlSession.spec.session` precedence over Project session-name fallback, D4 refusal when a Project uid/root contaminates that exact control-owned session, and a stable tmux-valid `home--<full Project uid>` physical projection for the same-name Project. `TestHomeDualRootIncidentDoesNotBlockUnrelatedCreateAgent` runs the public create-agent pre-reconcile over the exact preserved ControlSession/closed-Project fixture and requires the unrelated Agent to bind while both root uids, owner chains, and control-owned live mirrors survive with no cross-kind write. Explicit Project materialization creates only the deterministic suffixed session and repeats with zero Registry writes; malformed unrelated session observation becomes one reason-bearing D6 item and does not stop the remaining reconcile pass. Constructor and injected-planner tests keep every refusal/diagnostic map writable before observation.
|
|
261
|
+
|
|
262
|
+
- `make test` / `make test-integration` / `make test-e2e`: Corrective A install-acceptance follow-up contains a generic runtime Agent marker on the exact Registry canonical default shell without advancing anchor/primary-shell semantics. `TestLinkAgentPaneRefusesCanonicalShellMarkerWithZeroWrites` pins the pure direct Window-owned `role=shell` plus `defaultShellPaneRef` guard before Agent allocation, while `TestLinkAgentPanePreservesAnchorOnlyPromotionBehavior` keeps anchor-only promotion unchanged. `TestAutomaticReconcilePreservesCanonicalShellWithGenericAgentMarker` and `TestResourceReconcileRefusesCanonicalShellAgentMarkerAndContinuesUnrelatedDrift` require zero Agent mint/reservation move/Pane reparent, unchanged owner/role/default-shell ref and Registry bytes, a deterministic reason-bearing D2 public refusal, unrelated mirror convergence, and stable repeat behavior. The isolated real-tmux create fixture adds the same marker to its exact canonical shell, requires `config apply` success plus Registry SHA and `$N`/`@N`/`%N`/uid/marker preservation, and verifies byte-identical public dry-runs and zero-write refused execute.
|
|
263
|
+
|
|
264
|
+
- `make test` / `make test-integration` / `make test-e2e`: post-deployment Corrective B closes selector-free ProjectDefault cold start without reopening a phase. `TestShellTmuxExecRunnerReturnsClosedTypedFailures` and `TestClientSessionExistsClassifiesTypedFailures` pin typed genuine absent-socket, generic exit, permission, not-found, and runner failures while preserving removal of inherited `TMUX` and `TMUX_PANE`; only genuine no-server or missing-session evidence is classified as absence. `TestShellTypedPrepareFailuresReachCLIAndClosedJournal` requires a cause-bearing CLI diagnostic plus a closed content-free journal code instead of raw stderr authority or exit-code suppression. The isolated real-tmux shell fixture starts with clean Home, Registry, `TMUX_TMPDIR`, and a run-unique absent socket, succeeds on the first ProjectDefault PTY attach/detach without Home bootstrap, succeeds again on repeat attach, validates the queried physical socket and cleanup target below the smoke root, and removes that exact socket and root.
|
|
265
|
+
|
|
266
|
+
- `make test`: Codex native control plane Phase 8 capability and review coverage pins distinct localized default/advanced provider rows; default Codex launch with zero capability-session/readiness calls and zero `--model`/`model_reasoning_effort` overrides; advanced-only hidden/duplicate model removal, supported-effort and closed-modality normalization, boolean-only personality projection, catalog-localized semantic picker labels with provider model/effort/modality data unchanged, paginated `model/list`, connection/version epoch cache invalidation, stale picker refusal, live-session cleanup on pre-plan create refusal, the live picker-to-pre-create session refresh rejecting a removed option, and dynamic `model/list` -> exact launch argv. Advanced discovery failure returns its exact unavailable reason with zero create intents while a separate default launch remains usable. Review tests require one exact Running Agent -> owned Pane -> activation generation -> stored/live thread binding before `review/start`, use one bounded action context, reject incomplete or unknown initial turns, map only a valid initial response to provider-control-plane interaction state, and commit zero lifecycle writes for unavailable, timed-out, mismatched, or replaced bindings. The installed smoke is opt-in through `PROJMUX_CODEX_CAPABILITY_INSTALLED_SMOKE=1`; it closes the discovery context before refreshing the retained live connection. CI does not require a local authenticated Codex daemon or create a disposable provider thread.
|
|
267
|
+
|
|
268
|
+
- `make test`: Codex native control plane Phase 5 makes `account/rateLimits/read` the authoritative Codex Usage source and consumes `account/rateLimits/updated` through one single-lease, demand-bounded read-only watcher. Bucket/malformed/sparse-event goldens pin percent, reset, label, nullable identity, unknown cadence, and row-level isolation. Manager tests require an accepted native event batch to atomically replace only Codex rows, preserve other-provider/backoff state, advance the Codex throttle timestamp, and trigger zero second collector calls in the same HUD invocation. Watcher tests pin single ownership, demand-expiry heartbeat cleanup, failure backoff, sparse-update recovery after malformed events, and a private atomic native-only sidecar that never replaces last-known-good bytes with rollout/fallback provenance. The watcher launch boundary rejects `.test` and `.test.exe` executables before starting a child; the status regression runs repeated calls from the actual Go test binary with zero child starts and all usage state confined to its isolated root. Adapter/CLI/HUD/diagnostics tests keep unavailable and unsupported accounts on the existing rollout lane, all-malformed native batches on last-known-good with a closed stale reason, source/value parity across table/JSON/HUD, and an exact request inventory containing only `account/rateLimits/read` with zero login/logout/config/token mutation.
|
|
269
|
+
|
|
270
|
+
- `make test`: Codex native control plane Phase 9 makes the compact Usage identity native-first: label goldens map healthy authoritative app-server rows to exact `Codex`, fresh rollout rows and unknown non-stale provenance to `Codex [fallback]`, and retained last-known-good rows to `Codex [stale]`; en-US/ko-KR narrow statusbar fixtures pin spacing and width. Same-snapshot HUD/table/JSON tests and operations-journal tests preserve exact percent/reset values plus typed source/fallback/stale reasons, while Claude/Antigravity labels, projection order, and Usage semantics remain unchanged.
|
|
271
|
+
|
|
272
|
+
- `make test` / `make test-e2e`: Alt-1 Runtime diagnostics visibility Phase 0 makes the Projects sidebar's Runtime link contextual without touching the diagnostics contract behind it. `internal/config` owns the saved `runtime-diagnostics-visibility` choice (`when-needed` default, `always`), and its unit coverage pins the read-time default with no file written, the round trip, an unrecognized saved value that resolves to the default without rewriting the operator's bytes, the closed normalization table, and a rejected save. `internal/app` owns the pure reducer and the settings adapter: the visibility table covers preference x class x observability with no gap -- empty, control-only, ephemeral-only and control+ephemeral hosts withhold the row; each of `Unattributed`, `Foreign`, `Recoverable`, `Conflict` and a mixed tally restore it; no transport and any unavailable scope restore it; and `Always` keeps it in every one of those states. A second table proves a visible row keeps its exact shipped label, tally and sentinel. The rendered-surface tests drive the production observation adapter over a fake host that mirrors precisely what the Registry desires -- the fixture asserts a zero tally and a complete observation before asserting the withheld row -- then over a uid-conflict host and a no-transport host that restore it, with `Always` placing it last before Settings on the healthy host. A taxonomy guard records that an un-mirrored control or scratch session restores the row through its `Unattributed` descendants rather than through its own class, so a class-taxonomy change surfaces there instead of silently changing Alt-1. The settings tests pin the three source annotations including the invalid fail-safe, the save/reload round trip applying on the next invocation, the rejected action value, the ko-KR/en-US parity of the row, both choices, the state row and its source, and the control living inside the exact `Project Sidebar` view with no Advanced or Troubleshooting container. The negative parity audit compares `get runtime sessions|windows|panes` output, the exact server and the Registry across both modes, and compares the whole Registry navigation view -- its single complete Runtime row, its complete class tally, every row's actions and reason -- plus the Sessions surface's own Runtime link, across both modes. The e2e smoke adds a real 80x24 sidebar over a host whose every observed session, window and pane is managed: the default withholds the row (proven by the list's own filter answering `no matches`), `Always` restores it with its empty-state label and its nested Runtime entry and Esc return, and the direct `get runtime` output is byte-identical across both modes while the Registry, the sibling socket and the attached client are unchanged.
|
|
273
|
+
|
|
274
|
+
- Real-tmux Phase 3 hook-format evidence is an exact causal pair. `pane-exited`
|
|
275
|
+
exposes `%N` through `#{hook_pane}`, but its current-context session/window
|
|
276
|
+
formats may already name a surviving client Window; projmux therefore pairs
|
|
277
|
+
`%N` with the owner Window's `$N/@N` binding saved by the latest live
|
|
278
|
+
observation. `window-unlinked` supplies that dead Window directly through
|
|
279
|
+
`#{hook_session}` and `#{hook_window}`. Generated routes never substitute
|
|
280
|
+
current `#{session_id}`/`#{window_id}` for teardown authority.
|
|
281
|
+
|
|
282
|
+
- `make test` / `make test-integration` / `make test-e2e`: Project runtime stop and fresh identity separation Phase 1 pairs only an exact clean last-Pane `pane-exited` receipt with its matching `window-unlinked` using socket, `$N` session, `@N` Window, `%N` Pane, Registry owner chain, activation generation, and the current-or-exact-released Agent binding. `TestLastPaneExitAndMatchingWindowUnlinkDeleteWindowAndRetainProject`, `TestLastPaneCausalPairDeletesWindowAndPreservesZeroWindowProject`, `TestControlSessionLastWindowCausalPairDeletesWindowAndRetainsRootIdentity`, and `TestWindowCascadeRefusesReceiptAfterAgentResumedOnDifferentPane` pin target-only Window/Pane/owned-Agent deletion, sibling-primary reanchor, ControlSession retention, zero-Window Project validation, later-clock receipt byte-idempotence, and stale-resume zero-write. The canonical Window clean-exit corrective coverage adds `TestCanonicalWindowCreateCommitsExactOwnerPairAndInitialActivationForManagedRoots`, `TestCanonicalWindowOwnerPairBindingFailureRollsBackRegistryAndRuntime`, `TestCanonicalWindowCreateFeedsExactLastPaneCausalCleanupForManagedRoots`, and `TestCanonicalWindowCreateLifecycleAuthorityMatrixIsClosed`: Project and ControlSession intent producers atomically commit exact `$N/@N/%N`, an injected owner-pair binding failure leaves zero Registry/runtime claimant, the producer result feeds the shipped causal consumer, and missing/abnormal/killed/unknown/stale/foreign/mismatched evidence retains Window-delete zero. The controller tests cover unlink-before-pane-exit carry/retry; negative tables keep abnormal/killed/unknown/no-server/foreign/stale/unpaired evidence at Window-delete zero. `TestWindowUnlinkedWithoutExactPendingEvidenceWritesNothing` fixes historical absence-only ghosts at zero writes; the only recovery is exact canonical `delete window`, whose last-Window path retains the Project. The real-tmux integration closes a one-Window Project and the e2e closes a target in a two-Window Project followed by its final Window; the canonical `internal tmux window-create` e2e leg also proves its committed owner pair and initial Pane activation before a normal target-only clean exit while a separately contained sibling-socket sentinel remains byte-identical. These require exact descendant rows zero, Project uid/root preservation, same-server Project and sibling-socket containment, server/session absence, and a byte-identical repeat. Project stop, Continue materialization, Fresh/startup identity behavior, public hook routing, UI output, and provider-content parsing remain outside this phase.
|
|
283
|
+
|
|
284
|
+
- `make test` / `make test-integration` / `make test-e2e`: Project runtime stop and fresh identity separation Phase 2 closes the retained-window/zero-window/deleted × Stop/Continue/Fresh/explicit-delete lifecycle table. `TestProjectLifecycleStateTableHasTwelveClosedExclusiveCells` pins one exclusive operation and write set per cell, while `TestProjectLifecycleAppClassifiesAllThreeDesiredStatesWithoutRuntimeEvidence` pins the app classifier without runtime-absence authority. `TestManagedRuntimeStopUsesOnePrintedPhysicalObservationAndRegistryAuthority` keeps the retained desired graph unchanged while stopping only the exact physical runtime, and `TestManagedRuntimeStopAuthorityRejectsZeroWindowNoWriteCell` refuses the zero-Window no-runtime cell. `TestZeroWindowContinuePreservesProjectAndAllocatesCanonicalWindowShellUIDs` preserves the Project uid while allocating one new canonical Window/shell pair. `TestPlanProjectFreshReplacementCreatesOneNewClaimantAndPreservesPreimage`, `TestPlanProjectFreshReplacementSupportsZeroWindowAndFailureRetainsOldGraph`, and `FuzzPlanProjectFreshReplacementIsScopedValidAndAlwaysMintsIdentity` require every Fresh from retained or zero-Window state to atomically replace the Project/Window/Pane uid chain, leave exactly one same-root claimant, preserve unrelated graphs, and retain the old preimage on failure. `TestDeleteProjectIsTheOnlyRegistryUnregisterAndPreservesExternalAssets`, `TestDeleteZeroWindowProjectConsumesExplicitUnregisterCell`, and the post-commit write/flush failure tests keep explicit unregister separate from runtime, root, Git/worktree, and snapshot ownership while exposing action, stage, and old/new uids. The isolated real-tmux startup matrix drives managed Ctrl-X stop→Continue with exact Project/Window/shell identities and resumed Agent conversation, zero-Window Continue with new child identities, and Fresh from both retained and zero-Window states with a new canonical chain each time; it removes inherited tmux and private routing authority, uses unique contained sockets, preserves snapshot bytes and a sibling socket, and cleans only queried exact socket paths.
|
|
285
|
+
|
|
286
|
+
- `make test`: Codex native control plane Phase 6 pins exact Agent turn control
|
|
287
|
+
and approvals. Adapter fake-server tests freeze `turn/start` with only
|
|
288
|
+
`threadId` plus one byte-faithful text input, `turn/steer` with the current
|
|
289
|
+
`expectedTurnId`, `turn/interrupt` with the exact turn id, raw numeric and
|
|
290
|
+
string JSON-RPC request-id preservation, optional approval ids, and the
|
|
291
|
+
command/file/network one-shot and permission turn-only response shapes.
|
|
292
|
+
App property and adversarial tables cross action, request kind, available
|
|
293
|
+
decision, binding generation, connection epoch, resolution, and concurrent
|
|
294
|
+
response attempts; stale, lost, ambiguous, resolved, reconnected, or repeated
|
|
295
|
+
identities require zero writes. Private bounded local-socket tests cover
|
|
296
|
+
ownership, modes, collision refusal, deterministic overlong-path refusal,
|
|
297
|
+
epoch teardown, and frame limits. Picker tests require bounded terminal-safe
|
|
298
|
+
request targets and effects on every actionable decision row, with detail and
|
|
299
|
+
focus-only rows writing nothing. Notify tests require responder-vs-focus-only
|
|
300
|
+
availability and resolved queue cleanup, while privacy audits keep prompt,
|
|
301
|
+
command, path, permission, and request content out of Registry, tmux, notify,
|
|
302
|
+
diagnostics, support, and Archive sinks.
|
|
303
|
+
|
|
304
|
+
- `make test` / `make test-integration` / `make test-e2e`: Window anchor and primary-shell role separation Phase 1 makes launch-authorship promotion one atomic Registry/runtime transaction. `TestResolveAgentPaneAuthorityClosedTable` closes explicit launch-authorship, hook-only, no-marker, and ambiguous authority; `TestMarkAIHookPaneSeparatesTransientShellObservationFromOwnedAgentStatus` keeps exact Agent-owned hook status/sessionRef separate from unbound shell transients; `TestPublicAuthorshipPromotionMultiAllocationOrderParity` and `TestPublicAuthorshipPromotionAttachesExistingAgentWithoutAllocation` pin symbolic UID order plus distinct mint/attach allocation; `TestPublicResourceReconcilePromotesCanonicalLaunchAuthorshipAtomicallyAndRepeatsEmpty` covers Pane owner/role, Agent paneRef, anchor/default transitions, guarded exact runtime options, dry-run/execute structure, and repeat zero-write; `TestControllerRuntimeMutationAcceptsExactSiblingUIDEffectBetweenPlanAndGuard` permits only a same-plan exact sibling UID that converged between observation and guard while retaining foreign-UID zero-write refusal; `TestPublicLaunchProviderConflictFreezesExactTargetAndConvergesUnrelatedTarget` requires target conflict zero-write without suppressing unrelated convergence; and `TestPublicAuthorshipPromotionPrecommitAndGuardFaultMatrixLeavesNoMixedState`, `TestAuthorshipPromotionRuntimeRollbackIsIndependentOfActionOrder`, and `TestAuthorshipPromotionPreservesSiblingProjectSocketAndOtherHostDesiredState` cover fault/action-order rollback with no mixed state and preservation of sibling Project/socket, other-host-only desired status, and D5 state.
|
|
305
|
+
|
|
306
|
+
- `make test` / `make test-e2e`: interactive tmux `run-shell` producers converge on one output channel. tmux paints a foreground `run-shell` job's stdout, its stderr, and its `'<command>' returned <n>` line as a view-mode screen over the pane the key was pressed in, which is how a successful pane-menu split and a successful Window create/rename came to hide the Codex process the operator was reading behind `[0/1452]`. `internal/app/run_shell_output_ledger.go` declares the closed set: every producer -- catalog binding, context-menu item, status-bar binding, generated hook, startup command, runtime command, and the one allowlisted control sentinel -- carries exactly one of `intentional-ui`, `stdout-stderr-zero`, `redirect`, and `exact-client-message`, and `forbidden-overlay` exists only so the test has a name for what it refuses. Two sweeps keep the set closed: the generated standalone and app configs are rendered with every catalog action bound (Window create and rename ship with no default chord, so a stock-config sweep would never see them), each `run-shell` occurrence must resolve to exactly one row, and each row's declared background/redirect/exit-guard must match the bytes; a source sweep then requires every non-comment `run-shell` line in `internal/app` to be a registered site, so a new producer cannot be added without classifying it. Enforcement is structural rather than per-route: `App.Run` dispatches the ledgered interactive argv through a guard that buffers stdout and stderr, converges a failure to one bounded `display-message` on the exact client, and exits zero -- with the single exception of an undeliverable message, where the original error is returned rather than claiming success. Coverage pins the guarded/unguarded matrix (public `switch open`, `create`, `rename`, `describe`, popup payload routes, the picker, the supervisor, and every help spelling stay unguarded and byte-identical), a loud success reaching tmux as zero bytes and zero messages, the bounded one-line failure copy, and the confirmed producers' new copy: `Created Pane`, `Created Window`, `Renamed Window: <name>`, each delivered to the exact client with the canonical projection consumed in-process and left to the public CLI. The direct split bridge stops reporting a refusal twice (client message plus non-zero exit), the status-bar keyboard table now carries `#{client_tty}` so a second attached client never receives another operator's result, and the foreground runtime-created hook is exit-guarded so a refused convergence cannot paint the pane that just appeared. The generated-config theme goldens were rebaselined once for those two config changes.
|
|
191
307
|
|
|
192
308
|
## When To Update This List
|
|
309
|
+
|
|
193
310
|
- A feature moves between unit, integration, and e2e coverage levels.
|
|
194
311
|
- A new subsystem introduces a new validation target or removes one.
|
|
195
312
|
- Behavior changes require new parity assertions or a different e2e scenario.
|
|
196
313
|
- A target stops being authoritative and must be replaced.
|
|
197
314
|
|
|
315
|
+
- `make test` / `make test-integration` / `make test-e2e`: Project runtime stop and fresh identity separation Phase 0 makes clean Agent exit a fixed point over supervisor receipt, exact same-socket `pane_dead=1`, Registry projection, and controller-lock ordering. `TestExactDeadPaneReceiptWaitUsesLiveMinusDeadObservation` proves a retained dead Pane is not mistaken for a running Pane during the bounded lock-free receipt wait. `TestPaneAgentCascadeDeletePlanReleasesPaneAndRetainsAgentWindow` and `TestReleasedSameGenerationDeadPaneRetryConvergesAndPreservesSessionRef` pin both orderings: a current binding and an already released Offline Agent with matching same-generation Pane/Agent supervisor evidence both remove only the Pane, preserve Agent/sessionRef/Window, and never follow a resumed non-empty new binding. `TestExactDeadPaneUIDDisambiguatesAReusedRuntimeHandle` keeps a historical Pane with the same recycled tmux `%N` activation handle and requires the exact current `pane_dead=1` mirrored uid to select and delete only the new dead Pane while the historical Pane/Agent remain byte-identical. `TestExactPaneExitReceiptPermutationsConvergeToOneIdempotentPlan`, `TestSimultaneousCleanAgentExitsConvergeWithoutDeadOrMirroredResiduals`, and `TestCoalescedCleanAgentExitEventsConvergeBothExactDeadPanes` require receipt duplicates, event order, two near-simultaneous exits, and one-worker coalescing to reach byte-identical repeat results with sibling state unchanged. Exact dead runtime cleanup precedes the final Registry graph commit; `TestDeadPaneCleanupFailureExposesTypedRetryAndNextPassConverges` requires the machine-checkable `exact-dead-pane-cleanup-retry` reason and next-pass convergence without replacement authority. `TestControllerRetriesTypedDeadPaneCleanupReasonAndConvergesNextPass` requires that reason to remain visible while the same controller worker retries the exact event before widening. `TestATransientConvergenceErrorReplaysTheDrainedEventAndConverges` and `TestAConvergenceErrorReachesTheProducer` require a drained event to be restored across ordinary convergence failures, retried by the same worker up to the bound, and retained durably with the terminal error if the failure persists; whole-host coalescing preserves the retry generation instead of resetting it. Abnormal, killed, unknown, foreign, empty, unavailable, stale, and resumed observations retain zero clean-delete authority. Phase 1 now owns the last-Pane Window/resource boundary; Phase 0's non-last Pane fixed point and public hook routing remain unchanged.
|
|
316
|
+
|
|
317
|
+
- `make test` / `make test-integration` / `make test-e2e`: Create Agent early-exit authority keeps the provider behind the creator's exact Registry commit without weakening rollback identity. The supervised activation child enters the existing cross-process Registry lock and admits only the committed Agent-owned Pane whose generation, operation, `%N` runtime, and Running `paneRef` all match; the read-only callback writes zero Registry bytes. The creator carries one private cleaned absolute Registry path through the supervisor, so admission reads and termination-receipt writes stay on the creator's state root even when the tmux server inherited another XDG environment; this path is not exported to the provider or public hooks. On POSIX, a private CLOEXEC failure channel distinguishes admission/provider-exec failure (no termination receipt) from the same numeric provider exit (exact supervisor receipt), while HUP during admission kills the supervised gate before provider side effects and remains exact killed evidence. `TestSuperviseWaitsForTheCommittedActivationBeforeStartingTheChild`, `TestSuperviseRefusesTheChildWhenCreateAbortsBeforeActivationCommit`, `TestSuperviseActivationAdmissionIsExactAndZeroWrite`, `TestActivationAuthorityRejectsNonExactRegistryPathsBeforeProviderStart`, `TestAgentAdmissionAndReceiptUseCreatorRegistryAuthorityNotAmbientXDG`, `TestActivationExecFailureHandshakeIsTyped`, `TestSuperviseActivationHandshakeDistinguishesGateFailureFromProviderExit`, and `TestActivationExecCloseOnExecDoesNotWaitForProviderDescendants` pin commit/abort, blank/malformed/foreign identity refusal, split XDG authority, sibling byte preservation, exit 1/42 disambiguation, HUP side-effect zero, and descendant fd containment. The real-tmux integration and L17 e2e run an immediate exit-42 provider with the generated `pane-exited` hook live and require no blank-UID rollback, exact generation/operation evidence, sibling preservation, and fixed-point repeat. `PMX_TEST_EARLY_EXIT_REPEATS=30 test/integration/create-agent-early-exit-repeat.sh` is the only repeat interface: it builds one immutable attempt binary, then gives every sample a fresh HOME/XDG/Registry/root and unique app/sibling sockets before exact-owned cleanup. Shell Pane supervision and the public `PROJMUX_*` hook contract are unchanged.
|
|
318
|
+
|
|
319
|
+
- `make test` / `make test-integration` / `make test-e2e`: Native Codex exact-selector mutation route authority carries the activation gate's exact raw `%N` through the existing private `__PROJMUX_RUNTIME_ANCHOR_PANE` only; the public `PROJMUX_*` environment, activation generation, sessionRef, thread/turn, and lifecycle meanings stay unchanged. `TestNativeActivationEnvironmentAddsOnlyThePrivateExactRouteAnchor`, `TestExactThreeUIDCreateAgentRouteIgnoresUnrelatedAmbientPane`, `TestNativePrivateActivationAnchorNegativeAuthorityMatrixIsFirstWriteZero`, and `TestStandaloneRouteRequiresBlankClassAndExactInheritedPaneReceipt` pin exact anchor production, UID owner-chain independence from an unrelated client, stale/malformed/wrong-server/foreign first-write refusal, and unchanged standalone receipt strength. `TestOutsideTmuxExactThreeUIDCreateAgentUsesOnlyQuietAppRoute`, `TestOutsideTmuxExactThreeUIDCreateRoutesProductionAIPresentationWrites`, `TestOutsideTmuxProductionAIPresentationFailureIsVisibleAndRollsBack`, `TestAgentResumeRoutesProductionAIPresentationWritesThroughExactRuntime`, and `TestProductionAIPaneBindersUseOnlySuppliedExactRuntimeRoute` require an outside-tmux exact create to use only explicit `-L`/`-S` calls—including exact `select-pane -T ... -t %N` title presentation through the real AI binder—while returning one trimmed `%N` on stdout and no stderr; the provider shell contains no tmux mutation, and real permission and route-authority failures remain non-zero and transactional. The real-tmux early-exit integration asserts provider/private-anchor equality plus owner-title parity, and L06 audits the exact command's tmux argv while preserving two attached clients' sibling Window hash and the sibling socket hash.
|
|
320
|
+
|
|
321
|
+
## E2E Required-Gate Evidence
|
|
322
|
+
|
|
323
|
+
- `make test-e2e-coverage` is the maintained AGS-OEDR inventory gate. It
|
|
324
|
+
requires exact `L01`-`L19`/`C01`/`N01` executable marker parity, four Linux
|
|
325
|
+
shard assignments, valid referenced lower-layer test symbols/selectors, and
|
|
326
|
+
orphan count zero. A moved matrix is invalid unless positive, negative, and
|
|
327
|
+
fixed-point evidence are all non-empty and executable while E2E retains a
|
|
328
|
+
real-boundary sentinel. `make test-e2e` always runs the manifest/orphan half;
|
|
329
|
+
the referenced lower test is also exercised by the required unit-test job.
|
|
330
|
+
- L19's former 60-cell plural-read matrix now runs in
|
|
331
|
+
`TestPluralReadContextSelectorMatrix`: each Project/ControlSession/foreign/
|
|
332
|
+
outside x omitted/Project/global-short/global-long/UID cell runs twice with
|
|
333
|
+
identical output/error/observation count and zero Registry transaction,
|
|
334
|
+
write, or model change. The L19 real-tmux fixture retains five sentinels and
|
|
335
|
+
owns exact popup origin, queried socket containment, and owned-root cleanup.
|
|
336
|
+
- The same manifest absorbs merged-main evidence without expanding the stable
|
|
337
|
+
inventory. L17 closes simultaneous/coalesced provider exits through exact
|
|
338
|
+
generation dead/mirrored cleanup, resumable Offline Agent refs, sibling
|
|
339
|
+
preservation, and byte-identical repeat. L18 closes the generated foreground
|
|
340
|
+
`run-shell` matrix through exact-client bounded delivery, success silence,
|
|
341
|
+
no view-mode overlay, and origin PID/focus/Registry identity preservation.
|
|
342
|
+
Merged lifecycle commit `3322b5f7` adds no stable ID: L17 separately owns the
|
|
343
|
+
causal clean last-Pane/Window-unlinked Project-stop path, zero-Window root,
|
|
344
|
+
stale-resume refusal, sibling reanchor/containment, and fixed point; L19 owns
|
|
345
|
+
the ControlSession last-Window descendant cleanup, root retention, zero
|
|
346
|
+
replacement allocation, sibling containment, and fixed point. Their exact
|
|
347
|
+
lower test sets share the existing real last-Pane integration marker but use
|
|
348
|
+
distinct E2E markers before each scenario's pass. Every referenced guarantee,
|
|
349
|
+
unit symbol/selector, and integration/E2E marker is fail-closed by the
|
|
350
|
+
coverage audit.
|
|
351
|
+
- Merged lifecycle commit `dcffa5da` stays on L11: its closed row links managed
|
|
352
|
+
runtime stop, retained/zero-Window Continue, always-new Fresh, exact client
|
|
353
|
+
handoff, and sibling containment to lower tests plus the existing lifecycle
|
|
354
|
+
integration completion and L11 pass boundary. Merged authority commit
|
|
355
|
+
`de52d15d` stays on L17: its row links creator-root activation admission,
|
|
356
|
+
exact receipt/exec disambiguation, immediate exit-42, the fresh-root focused
|
|
357
|
+
repeat harness, and the read-only owner/queue + product-terminal handoff.
|
|
358
|
+
Stable inventory remains exact `L01`-`L19`/`C01`/`N01`; source commit,
|
|
359
|
+
guarantee, lower selector, supporting marker, and marker-before-pass drift
|
|
360
|
+
all fail closed.
|
|
361
|
+
- `make test-e2e` uses one attempt-local binary across isolated four-way Linux,
|
|
362
|
+
Codex, and npm consumers. Local Security is the exact three-way
|
|
363
|
+
`make security` topology; CI's required branch-protection context is the
|
|
364
|
+
fail-closed aggregate named `Test`.
|
|
365
|
+
|
|
198
366
|
## Review Checklist
|
|
199
367
|
- The branch stays within its stated scope.
|
|
200
368
|
- The change preserves boundaries between portable `projmux` behavior and local machine policy.
|