projmux 0.9.0 → 0.10.1

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.
@@ -0,0 +1,23 @@
1
+ # Legacy diagnostics inventory
2
+
3
+ This is the Phase 6 file/surface inventory. `Keep`, `Deprecate candidate`, and
4
+ `Remove candidate` are review classifications, not runtime changes. Nothing in
5
+ this phase removes, renames, ignores, or changes an environment variable, log,
6
+ CLI, config action, output format, permission, or retention contract.
7
+
8
+ | Surface | Producer / reader files | Current consumer and data boundary | Candidate and rationale | Separate follow-up |
9
+ | --- | --- | --- | --- | --- |
10
+ | `PROJMUX_USAGE_DEBUG` | `internal/app/usagecmd/usage.go`; adapter contract in `internal/core/usage/registry.go` and safe token helper in `internal/core/usage/adapters/claude/claude.go` | `projmux status usage` operator stderr; documented by `AGENTS.md`, `docs/configuration.md`, and `docs/usage-tracking.md`; `internal/app/usagecmd/usage_test.go` fixes the opt-in behavior; adapter errors are otherwise swallowed on the status hot path | **Keep.** Usage adapters are not adopted by the common journal and this opt-in stderr is the only immediate adapter failure consumer. | No removal follow-up. Re-evaluate only with a separately scoped usage diagnostics adoption. |
11
+ | `PROJMUX_SESSIONSTATE_DEBUG` | read/output gate in `internal/app/tmux.go` | Operator/automation stderr for a quiet autosave error; documented in `docs/configuration.md`; tests in `internal/app/tmux_test.go` cover popup environment propagation | **Deprecate candidate.** Phase 3 already records the same quiet autosave failure as a closed common error, but scripts may consume stderr. | Yes: announce deprecation, audit scripts, and remove only in a breaking PR after a compatibility window. |
12
+ | `PROJMUX_FOCUS_DEBUG` | read/output gate in `internal/app/focus.go` | Operator stderr one-line raw target/session/window/pane/socket/client/source/kind; documented in `AGENTS.md`, `docs/cli.md`, `docs/configuration.md`, `docs/operational-diagnostics.md`, and the maintained test list in `docs/agent-workflow.md`; `internal/app/focus_test.go` fixes the byte contract | **Deprecate candidate.** Phase 4 common focus events are safer for support, but the raw routing line remains a distinct local troubleshooting consumer. | Yes: publish safe replacement guidance and remove only through a breaking deprecation roadmap. |
13
+ | `PROJMUX_NATIVE_DEBUG_LOG` | `internal/ui/picker/backend.go` and `colorgrid.go` append native picker actions/query/value/errors; `internal/app/tmux.go` inherits it into popup environments | Developer-selected file path; popup inheritance is covered in `internal/app/tmux_test.go`; no public CLI/support reader, retention bound, or privacy redaction | **Remove candidate.** The opt-in trace can contain user query/value/error text and is not bounded. Current behavior remains intact because a safe bounded replacement and migration notice are outside Phase 6. | Yes: design a bounded private picker trace or common closed events, document migration, then ship removal as breaking. |
14
+ | bounded `ai-ingest.log` | Codex producer `internal/app/ai_ingest_codex.go`; Claude producer `internal/app/ai_ingest_claude.go`; Antigravity producer `internal/app/ai_ingest_antigravity.go`; tmux-bell producer plus shared `appendAIIngestLog`, path, cap, and trim in `internal/app/ai_ingest.go`; common projection in `internal/app/ai_ingest_diagnostics.go` | Detailed CLI reader `internal/app/ai_ingest.go` (`projmux ai ingest log`); count-only support reader `internal/app/diagnostics_report.go`; docs consumers `docs/cli.md`, `docs/hooks.md`, `docs/configuration.md`, `docs/operational-diagnostics.md`, and the maintained test list in `docs/agent-workflow.md`; `internal/app/ai_ingest_test.go`, `internal/app/ai_ingest_diagnostics_test.go`, `internal/app/diagnostics_report_test.go`, and `test/integration/linux-smoke.sh` cover retention/consumer/corrupt/permission behavior | **Deprecate candidate.** Phase 5 common events cover anomalous classification, but intentionally omit normal state/notify/quiet/dedupe detail and all identity. Removing the file now would break the detailed CLI consumer and measured parity. | Yes: define whether detailed normal-state diagnostics remain a product requirement, migrate or retire `ai ingest log` and support counts, announce a compatibility window, then use a breaking PR. |
15
+
16
+ The separate host resource status path is not a legacy debug surface:
17
+ `internal/app/status.go` invokes `internal/systemstatus.Sampler` for
18
+ `projmux status resources`. It owns only host aggregate display/cache behavior,
19
+ not pane/project attribution, and emits zero common resource events. The Phase
20
+ 6 Resource Inspector owner is `internal/app/resources.go` →
21
+ `internal/app/resources_collector_linux.go` →
22
+ `internal/integrations/tmux.ListResourcePanes` plus
23
+ `internal/integrations/procfsresources.Collector`.
@@ -0,0 +1,103 @@
1
+ # Native Picker
2
+
3
+ The native picker is the product picker for every interactive selection flow.
4
+ There is no runtime backend selection, saved picker selector, or external
5
+ picker process. `internal/ui/picker` owns the interaction contract and
6
+ `internal/ui/projmuxpicker` owns its visual composition.
7
+
8
+ ## Product Contract
9
+
10
+ | Area | Behavior | Primary evidence |
11
+ | --- | --- | --- |
12
+ | Items and results | Structured items keep display labels, stable values, optional search text, multi-line metadata, and action results separate. `internal/ui/pickercompat` maps older internal option/result shapes into this contract; it is not a runtime backend. | `TestPickerOptionsFromCompatPickerMapsCandidatesWhenEntriesAreEmpty`; `TestPickerOptionsFromCompatPickerPreservesTheme` |
13
+ | Search | Lower-case queries are case-insensitive, uppercase queries are case-sensitive, hidden values remain searchable when no explicit search key exists, explicit search-key lists preserve caller order, and simple rows receive fuzzy ranking and match highlighting. | `TestFilterItemsUsesSmartCase`; `TestFilterItemsSearchesHiddenValueWhenNoSearchKey`; `TestFilterItemsPreservesSearchKeyOrder`; `TestFilterItemsRanksBetterMatchesFirst` |
14
+ | Navigation | Up/Down, Ctrl-J/Ctrl-N, and Ctrl-K/Ctrl-P wrap selection; PageUp/PageDown and Home/End clamp or jump; empty lists remain safe. Ctrl-N and Ctrl-P remain real navigation keys unless a caller claims the key as a custom action. | `TestNativeInteractiveSupportsControlNavigationKeys`; `TestNativeInteractiveWrapsPreviousNavigationKeys`; `TestNativeInteractiveWrapsNextNavigationKeys`; `TestNativeInteractiveNavigationKeysAreNoopForEmptyList`; `TestNativeInteractiveJumpNavigationRemainsClamped` |
15
+ | Editing | Left/Right, Ctrl-A/E, Delete, Backspace, Ctrl-U/W, printable input, accept-query mode, and a visible query cursor are supported. | `TestNativeInteractiveEditsTypedQueryAtCursor`; `TestNativeInteractiveSupportsQueryLineEditingKeys`; `TestNativeInteractiveCtrlUDeletesBeforeCursor`; `TestNativeRunnerAcceptsTypedQuery` |
16
+ | Actions | Enter accepts, shared close actions abort, printable/control expect keys return stable action keys, command actions can refresh previews, and mutable actions can update items without restarting the picker. | `TestNativeRunnerUsesSharedCloseActions`; `TestNativeInteractiveSupportsPrintableExpectKeys`; `TestNativeInteractiveRunsCustomActionCommandAndRefreshes`; `TestNativeInteractiveCustomActionMutatesItemsAndRefreshes` |
17
+ | Deferred state | Deferred and event-triggered updates preserve query and selection by value, can repeat, and may explicitly choose a new focus value. | `TestNativeInteractiveDeferredUpdateTriggerRefreshesRepeatedly`; switch and notify sidebar mutable-refresh app tests |
18
+ | Preview | Popup previews use a right split, sidebar previews use a bottom split, control bytes and tabs are normalized before clipping, and preview scrolling/cycling rerenders in place. | `TestNativeInteractiveRendersWidePreviewBesideList`; `TestNativeInteractiveRendersDownPreviewBelowList`; `TestRenderSplitPreviewRowsNormalizesPreviewTabsBeforeTruncating`; `TestNativeInteractiveRendersPreviewOffset` |
19
+ | Mouse | SGR mouse input focuses on primary down, follows drag, accepts on matching release, and scrolls with the wheel. | `TestNativeInteractiveSelectsOnMouseRelease`; `TestNativeInteractiveMouseDragFollowsSelection`; `TestNativeInteractiveSupportsMouseWheelSelection` |
20
+ | Lifecycle | Interactive runs use the alternate screen, synchronized/coalesced frame updates, controlling-TTY fallback, and deterministic reader cleanup. | `TestNativeInteractiveUsesAlternateScreen`; `TestNativeInteractiveWrapsRedrawsInSynchronizedUpdates`; picker/setup lifecycle tests in `docs/agent-workflow.md` |
21
+
22
+ ## Rendering And Popup Chrome
23
+
24
+ The renderer owns rounded frames, optional titlebars and chips, search/header
25
+ separators, footer placement, selected-row styling, proportional scrollbars,
26
+ multi-line gaps, preview geometry, ANSI restoration, and terminal-cell width for
27
+ CJK text, emoji, and combining marks. Frame-owned cells inherit the effective
28
+ theme's `surface` and `chrome_foreground` values, including padding after
29
+ embedded resets.
30
+
31
+ Picker popups are always borderless tmux popups (`display-popup -B`) so the
32
+ native renderer owns the visible frame. The popup body receives a per-command
33
+ style derived from the effective theme; no global popup, pane, window, shell,
34
+ or status style is mutated. Project sidebars use a `20%` width with a `40`
35
+ column minimum, notification sidebars use `24%` with a `64` column minimum,
36
+ and both reserve the two statusbar rows when client height is known.
37
+
38
+ Primary evidence includes `TestAppRunTmuxPopupToggleUsesBorderlessNativePopup`,
39
+ `TestAppRunTmuxPopupToggleUsesNativePopupBodyStyleFromGlobalTheme`,
40
+ `TestBuildPopupToggleAppliesNativeBodyStyle`,
41
+ `TestSessionizerSidebarWidthUsesCompactMinimum`,
42
+ `TestNotifySidebarWidthUsesProductContract`, and
43
+ `TestSidebarPopupHeightLeavesStatusbarRows`.
44
+
45
+ ## Fuzzy Scoring Provenance
46
+
47
+ The native fuzzy scorer intentionally follows the fzf V2 dynamic-scoring
48
+ algorithm for the maintained reference cases in `internal/ui/picker/backend_test.go`.
49
+ `TestFuzzyScoreMatchesFZFV2ReferenceScores` and
50
+ `TestFuzzyScoreRejectsFZFV2ReferenceNonMatches` are provenance fixtures; keep
51
+ their expected scores stable when changing filtering internals.
52
+
53
+ ## Retired Artifact Contract
54
+
55
+ The retired picker selector environment variable and saved selector filename
56
+ are not runtime inputs. Their presence must not cause lookup, file reads,
57
+ warnings, deletion, rewriting, propagation to child popups, or any change to
58
+ the native path. There is deliberately no alias, migration, or stale-value
59
+ cleanup behavior.
60
+
61
+ Coverage is split by boundary:
62
+
63
+ - `TestRunNativePickerOptionDoesNotObserveRetiredBackendArtifacts` guards the
64
+ direct picker path, fails if the retired env name is queried, and verifies a
65
+ stale file is neither deleted nor replaced.
66
+ - `TestAppRunSwitchUsesNativePickerWithoutBackendLookup` guards the ordinary
67
+ switch flow.
68
+ - `TestAppRunTmuxPopupToggleIgnoresRetiredBackendArtifacts` and
69
+ `TestBuildPopupTogglePropagatesNativePickerEnvironmentWithoutRetiredBackend`
70
+ guard popup chrome and child-environment construction.
71
+ - `test/e2e/linux-smoke.sh` launches the ordinary attached-client Settings
72
+ popup with stale env/file values, completes the recorder flow, verifies the
73
+ file inode/content are unchanged, and rejects visible compatibility output.
74
+
75
+ ## Removed PoC Coverage Mapping
76
+
77
+ The retired standalone dependency sandbox and focused smoke duplicated product
78
+ coverage or tested a dependency policy that no longer exists. Its valid
79
+ behavior assertions remain covered as follows:
80
+
81
+ | Removed focused assertion | Maintained coverage |
82
+ | --- | --- |
83
+ | Settings and Labs render through the native picker | `TestSettingsUsesNativePicker`; `TestSettingsHubKeepsLabsSectionWithoutRetiredPickerChoices`; ordinary attached-client Settings recorder in `test/e2e/linux-smoke.sh` |
84
+ | Search chrome, smart-case filtering, native frame ownership, and full-height rendering | `TestNativeInteractiveSeparatesSearchHeaderFromList`; `TestFilterItemsUsesSmartCase`; `TestNativeInteractiveRendersBorderFrame`; `TestNativeInteractiveUsesAvailableHeightForSimpleLists` |
85
+ | AI Settings maps a typed selection to the saved mode | `TestAISettingsPickerSetsSelectedMode`; native picker option/result mapping tests |
86
+ | Switch popup/sidebar filtering, selection, preview cycling, and initial focus | `TestAppRunSwitchDefaultsToPopupAndOpensSelectedSession`; `TestSwitchCommandSupportsSidebarUI`; preview cycle and initial-position tests in `internal/app/switch_test.go` |
87
+ | Sessions popup filtering, selection, and preview cycling | `TestAppRunSessionsDefaultsToPopupAndOpensSelectedSession`; session popup preview/cycle tests in `internal/app/session_popup_test.go` |
88
+ | Notification sidebar navigation and mutable actions | `TestNotifySidebarUsesNativePicker`; notify sidebar ack/clear/deferred-refresh app tests |
89
+ | Launch-key close behavior and Ctrl-N/Ctrl-P navigation | `TestNativeInteractiveClosesOnMatchingLaunchCloseKey`; `TestNativeInteractiveSupportsControlNavigationKeys`; empty-list and wrap tests |
90
+ | Mouse selection and alternate-screen restoration | native mouse and lifecycle tests listed above |
91
+ | Generated shell config and the Alt-1 project-sidebar popup path | `TestShellWritesAppConfigAndRunsIsolatedTmux`; `TestAppRunTmuxPopupToggleOpensStandaloneSidebar`; popup frame/body-style tests listed above |
92
+ | Stale selector values do not affect the product | retained and strengthened in the unit/app/e2e negative coverage listed in the previous section |
93
+
94
+ The dependency-absence assertion is now a source-residue property rather than
95
+ a separate container scenario: production code contains no selector, resolver,
96
+ saved-selector access, propagation, or external picker launch path.
97
+
98
+ ## Maintenance
99
+
100
+ Update this document and the maintained list in
101
+ [`docs/agent-workflow.md`](agent-workflow.md) whenever picker behavior changes
102
+ coverage level, gains a new product flow, or changes input/render/action
103
+ semantics.
@@ -2,15 +2,19 @@
2
2
 
3
3
  Projmux records a small local-only operational journal so command failures and
4
4
  state changes can be inspected after the originating process exits. It does
5
- not upload the journal, create support archives, contact an issue tracker, or
6
- provide a background telemetry service.
5
+ not upload the journal, contact an issue tracker, or provide a background
6
+ telemetry service. A support archive is created only by an explicit
7
+ `projmux diagnostics report` invocation and is never transmitted.
7
8
 
8
9
  ## Safe event contract
9
10
 
10
11
  Each JSONL record has a closed schema: `at`, `level`, `component`, `event`,
11
12
  `result`, `duration_ms`, `run_id`, `version`, `mux_backend`, and optional
12
13
  allowlisted `command`, `subcommand`, `kind`, and sanitized `message`. There is
13
- no generic metadata map.
14
+ no generic metadata map. Runtime lifecycle records add only closed
15
+ `operation` and `code` enums. The allowed operations are session create,
16
+ attach, switch, kill, and tmux apply; codes are stable failure/health
17
+ classifications and never carry routing identity or subprocess details.
14
18
 
15
19
  Command and subcommand names come from static allowlists. Unknown argv values,
16
20
  paths, flags, and arguments are dropped. Messages have control/format
@@ -25,6 +29,158 @@ pane captures/output/title/topic/content, transcripts, raw hook payloads,
25
29
  configuration secrets, or arbitrary environment values. Phase 0 also does not
26
30
  add session/window/pane or other routing identifiers.
27
31
 
32
+ One explicit state-changing command owns at most one lifecycle pair. Its
33
+ `lifecycle.start` and `lifecycle.outcome` share the process `run_id`, and a
34
+ composite create-then-attach/switch flow keeps the first real mutation as its
35
+ operation instead of recording nested outcomes. Lifecycle ownership replaces
36
+ the generic top-level `command.outcome`; it never duplicates it. Start/outcome
37
+ append failures are ignored and do not change the command result.
38
+
39
+ Session State mutations use one outcome-only `session-state.outcome` record
40
+ per selected attempt. The closed operations are `session-state.save`,
41
+ `session-state.autosave`, `session-state.restore`, and `session-state.delete`;
42
+ an error uses only the matching `.failed` code, `kind=runtime`, and an empty
43
+ message. Optional sources are limited to `manual`, `settings-latest`,
44
+ `settings-named`, `autosave`, `startup-latest`, `startup-named`, and `prune`.
45
+ Successful save and actual startup restore outcomes contain only exact
46
+ non-negative `window_count`, `pane_count`, `shell_recipe_count`,
47
+ `agent_recipe_count`, and `startup_recipe_count` aggregates. Successful delete
48
+ contains only `item_count`; errors contain no counts. Snapshot paths/content,
49
+ project paths, pane cwd/commands, snapshot names, and agent or conversation
50
+ identifiers are never projected.
51
+
52
+ Direct and popup save, Settings latest/named save, direct and Settings delete,
53
+ deduplicated prune delete, and actual latest/named project-startup replay own
54
+ these outcomes. Preview, dry-run, and nested store/replay calls do not.
55
+ Autosave success and disabled, not-due, or fresh no-ops always write zero
56
+ records; a real autosave failure writes exactly one error even when `--quiet`
57
+ preserves its historical successful exit. Session State logical ownership
58
+ suppresses a generic top-level outcome even when journal append fails. An
59
+ actual restore may also produce its runtime lifecycle pair with the same run
60
+ ID; the lifecycle pair and Session State terminal outcome describe different
61
+ contracts and are not duplicates.
62
+
63
+ Notify and focus transitions use the same process `run_id` and add only closed
64
+ `transition`, `disposition`, `provider`, `category`, and `route` enums. Notify
65
+ transitions are `enqueue` and `delivery`; focus uses `request`. Enqueue records
66
+ distinguish queued, stable-ID deduplicated, and failed outcomes. Delivery
67
+ records distinguish delivered, dedupe/visibility/setting suppression, and
68
+ failed outcomes across the external sender hook, WSL Toast and fallback, and
69
+ Linux `notify-send` routes. Focus request records distinguish focused,
70
+ notify-only, session-only, window-only, and failed outcomes. Failure codes are
71
+ closed stage classifications and messages stay empty.
72
+
73
+ Provider and category values are projected through fixed allowlists. Unknown
74
+ values become `other`; arbitrary provider payloads and notification metadata
75
+ cannot extend the event schema. Notification summary/body, tag/group, terminal
76
+ title/topic, paths, queue or routing IDs, UUIDs, and AI/conversation/session
77
+ identifiers are never recorded. The notifier sender owns one terminal delivery
78
+ outcome after fallback selection, so failed intermediate WSL adapters do not
79
+ produce duplicate records when a later route succeeds.
80
+
81
+ One process writes at most one copy of an identical safe notify/focus tuple.
82
+ This fixes repeated stable-ID queue replacement, desktop dedupe, visible-pane
83
+ suppression, and reconcile hot paths to a finite per-run volume; the journal's
84
+ existing size/retention cap remains the cross-process bound. Explicit `notify
85
+ push` and `focus` outcomes logically replace the generic top-level
86
+ `command.outcome`, including when append fails. Secondary automatic enqueue or
87
+ delivery events do not claim an unrelated outer command. A successful focus
88
+ that switches a tmux client may coexist with the shipped runtime
89
+ `session.switch` lifecycle pair under the same run ID: the pair describes the
90
+ tmux mutation, while `focus.transition` describes the request-level result.
91
+
92
+ AI watcher and hook-ingest diagnostics use the same process `run_id` and add
93
+ only closed `provider`, `ai_kind`, `ai_result`, and `failure` enums. The event
94
+ families are `ai.watcher.transition` and `ai.ingest.outcome`. Watcher provider
95
+ is the generic `ai`; ingest providers are `codex`, `claude`, `antigravity`, or
96
+ `tmux-bell`. Each provider accepts only its own closed semantic-kind catalog;
97
+ for example, `tmux-bell` can only emit `bell`, while watcher events can only use
98
+ the generic `ai` provider and `watcher` kind. Provider event names are projected
99
+ into semantic kinds such as `prompt`, `permission`, `stop`, `notification`,
100
+ `tool`, `session`, `compact`, `subagent`, `teammate`, `statusline`, `invocation`,
101
+ `lifecycle`, `bell`, `payload`, or `unknown`. A raw or future event name can
102
+ therefore be diagnosed as `unknown` but can never extend the journal schema.
103
+
104
+ One watcher process emits at most one `started` transition, one terminal
105
+ `pane-gone` or `hook-active` transition, and one copy of each distinct safe
106
+ failure tuple. The existing observable launch seam uses only
107
+ `watcher-launch-failed`; status application remains the pre-existing
108
+ best-effort operation and does not claim to expose swallowed tmux write errors.
109
+ The terminal watcher event logically replaces its generic top-level outcome,
110
+ including when append fails.
111
+ The polling loop does not record snapshots, observed titles, captures, pane
112
+ state, or a record per iteration.
113
+
114
+ Hook ingest projects only anomalies: invalid/read/oversized payloads, unmatched
115
+ or invalid targets, unsupported event classification, and terminal route
116
+ failure. Route failures are limited to the observable bell queue/store and
117
+ Antigravity explicit-response seams. Identical safe anomaly tuples are
118
+ coalesced per process. Successful state, notification, quiet, and bell-dedupe
119
+ traffic emits zero common AI events. Notify enqueue/delivery remains owned by
120
+ the Phase 4 notify recorder, so ingest does not add a second AI success outcome
121
+ or claim a secondary notify outcome. An ingest failure owns the top-level error
122
+ logically before its best-effort append, preventing a duplicate generic
123
+ `command.outcome`.
124
+
125
+ The common AI event never contains the raw hook payload or event name, prompt,
126
+ transcript, tool name/input/output, notification summary/body, pane content,
127
+ cwd/path/command/title/topic, tmux target, queue ID, provider conversation or
128
+ session identifier, UUID, or arbitrary reason/error string. `failure` is a
129
+ stage enum, not `error.Error()`.
130
+
131
+ Resource attribution diagnostics use `component=resource` and the single
132
+ `resource.sampler.outcome` family. The Resource Inspector lifecycle is the
133
+ only writer: its Linux collector owns tmux inventory, project-root discovery,
134
+ and procfs attribution, while its refresh gate owns derived staleness. The
135
+ closed `source` values are `sampler`, `tmux-inventory`, `project-discovery`,
136
+ and `refresh`; the closed `resource_result` values are `unavailable`,
137
+ `partial`, `stale`, `error`, and `scan-budget-exceeded`. `failure` is limited
138
+ to the matching safe stage enum. Impossible source/result/failure combinations
139
+ are rejected.
140
+
141
+ The lifecycle's existing overlap and trigger-drop counters remain UI-only
142
+ refresh feedback and do not create an operational event by themselves. When
143
+ the retained last-complete sample crosses the stale boundary, the refresh
144
+ owner records the single coalesced `stale` transition instead.
145
+
146
+ Normal warming/ready samples, periodic samples, successful automatic/manual
147
+ refreshes, and the separate host-only `projmux status resources` sampler emit
148
+ zero common resource events. The two-second Resource Inspector lifecycle budget
149
+ measures inventory, discovery, and procfs collection together. Tmux inventory
150
+ and procfs observe its context directly. Project discovery does not currently
151
+ accept a context, so an overrun is classified as budget-exceeded when discovery
152
+ returns rather than being preempted; making that seam cancellable is a separate
153
+ follow-up. Budget exhaustion preserves the established unavailable UI/CLI
154
+ result and adds only the safe budget tuple. A persistent identical anomaly
155
+ emits once. Recovery is silent and resets that transition, so a later re-entry
156
+ emits once again. Journal append failure does not affect snapshot selection,
157
+ popup refresh, stdout/stderr, or exit status.
158
+
159
+ No resource event contains CPU or memory values, PID/process counts, process
160
+ command/cwd/title, project/session/window/pane identifiers, socket/TTY,
161
+ attribution details, snapshot status text, arbitrary errors, paths, UUIDs, or
162
+ privacy seeds. Partial and stale outcomes are info-level and remain in the
163
+ private local journal only. Unavailable, collection/inventory/discovery error,
164
+ and scan-budget outcomes are error-level, so the explicit support report may
165
+ include their closed enums after hashing run/version correlation. The report's
166
+ existing error-only projection omits partial/stale and never exports metrics or
167
+ identity.
168
+
169
+ The diagnostics package exposes a typed `ReadRuntimeHealth` projection for
170
+ read-only Doctor consumers. It reports the fixed `tmux` backend, latest
171
+ socket/apply state, and a bounded tail/count of safe failures using only
172
+ `Store.ReadOnly`; it does not create, chmod, lock, truncate, apply, restart, or
173
+ repair anything. Doctor schema 2 consumes that seam for its `logs` findings
174
+ and adds one fixed-argv, one-second `tmux -L projmux show-options` probe for
175
+ actual socket/config health. The probe neither generates nor applies config.
176
+ Its captured output is capped at 4 KiB. Doctor reads only a pre-existing
177
+ regular generated config (at most 1 MiB) without following symlinks, and the
178
+ shared read-only journal seam rejects non-regular inputs and files above 5 MiB.
179
+ These conditions degrade to typed findings rather than blocking or repairing
180
+ the source. Windows ACL privacy is reported as unverified because `os.FileMode`
181
+ cannot prove it; a separate finding preserves the metadata-only writability
182
+ result, and Doctor does not modify ACLs.
183
+
28
184
  ## Storage and retention
29
185
 
30
186
  The path is
@@ -54,9 +210,13 @@ arm`, `attention clear`, `attention window`, `tmux autosave-session-state`, and
54
210
  to the journal; an error from any of them still records exactly one safe error
55
211
  outcome. Explicit user mutations such as `attention toggle` retain their
56
212
  state-changing success record. Direct top-level help and explicit preview-only intents (`upgrade
57
- --dry-run`, `update apply --dry-run`, `doctor --install-missing --dry-run`, AI
58
- integration dry-runs, and the currently preview-only session restore) are also
59
- read-only. Multi-mode commands such as AI status/topic, doctor install,
213
+ --dry-run`, `update apply --dry-run`, AI integration dry-runs, and the
214
+ currently preview-only session restore) are also read-only. Doctor is a stricter
215
+ boundary: successes and errors never append to this journal, so diagnostics do
216
+ not make its filesystem contract self-defeating. Support report success and
217
+ errors likewise never append; its strict reader shares the viewer's tolerant
218
+ decoder but never creates/locks/chmods/repairs/truncates the source journal.
219
+ Multi-mode commands such as AI status/topic,
60
220
  terminal apply, snapshot delete, update check, and welcome popup inspect only
61
221
  allowlisted mode/flag names; boolean `=false` values retain mutation-capable
62
222
  classification, and no flag values are ever recorded. Help-looking tokens
@@ -76,5 +236,52 @@ viewer read is excluded from success logging, so inspection does not create a
76
236
  recursion loop.
77
237
 
78
238
  The older bounded `ai-ingest.log` and subsystem-specific `PROJMUX_*_DEBUG`
79
- surfaces retain their current paths, formats, and behavior. They are not
80
- migrated by this foundation.
239
+ surfaces retain their current paths, formats, and behavior. Phase 6 deliberately
240
+ keeps the legacy producer and both existing consumers: `projmux ai ingest log`
241
+ still reads the legacy JSONL bytes, and `diagnostics report` still emits its
242
+ allowlisted source/result count summary. Legacy append failure remains
243
+ best-effort and independent of common-journal append failure.
244
+
245
+ The measured migration parity is:
246
+
247
+ | Legacy `ai-ingest.log` result | Common operational projection |
248
+ | --- | --- |
249
+ | parse error with no classified event | `payload / failed / payload-invalid` |
250
+ | bell queue/store or Antigravity response route error | allowlisted semantic kind / `failed / route-failed` |
251
+ | no matching pane | allowlisted semantic kind / `ignored / target-unmatched` |
252
+ | pane-not-found bell target | `bell / ignored / target-unmatched` |
253
+ | unknown event recorded as quiet | `unknown / ignored / unsupported-event` |
254
+ | normal `state`, `notify`, known `quiet`, or `deduped` | zero common AI events; existing state/notify owner remains authoritative |
255
+ | stdin read or payload-size rejection before the legacy append seam | common-only `payload-read` or `payload-oversized`; no legacy row existed |
256
+ | blank `ai ingest bell` CLI target rejected before the legacy append seam | common-only `bell / ignored / target-invalid`; exit semantics unchanged |
257
+
258
+ This is a dual-run migration seam, not a deprecation. Operators who need the
259
+ legacy detailed local view can keep using `ai ingest log`; support archives
260
+ remain count-only for that file. The common journal is the safe correlated
261
+ source for watcher lifecycle and anomalous ingest classification. The file is
262
+ a documented **Deprecate candidate**, not deprecated behavior in this release:
263
+ common diagnostics cover anomalies but not the legacy detailed normal-state
264
+ consumer. Any deprecation/removal requires a separate breaking roadmap with
265
+ consumer migration.
266
+
267
+ `PROJMUX_FOCUS_DEBUG` remains available with its existing one-line byte
268
+ contract. Focus diagnostics share its request classification seam, but do not
269
+ copy the debug line's raw target, session/window/pane, socket, client, source,
270
+ or kind values into the journal. It is a documented **Deprecate candidate**
271
+ because the common focus transition is the safe support path; its raw routing
272
+ byte contract remains unchanged until a separate breaking deprecation.
273
+
274
+ The complete file/surface inventory and decisions are maintained in
275
+ [legacy-diagnostics-inventory.md](legacy-diagnostics-inventory.md). These are
276
+ candidates only; Phase 6 removes, renames, ignores, or changes none of them.
277
+
278
+ ## Explicit support report
279
+
280
+ `projmux diagnostics report [--output <path>]` previews and then atomically
281
+ publishes a private local `tar.gz`; see [cli.md](cli.md#diagnostics). The
282
+ manifest records report schema version 2, `default-hash-v1` redaction, every
283
+ included entry, and stable missing/corrupt/permission omission reasons. Doctor
284
+ JSON schema version 2 and the bounded operations decoder are reused rather than
285
+ duplicated. AI ingest contributes count-only allowlisted source/result rows,
286
+ never raw legacy lines. Existing output files survive collisions and partial
287
+ temporary archives are removed.
@@ -24,7 +24,7 @@ feat(ai): add codex split picker keybinding
24
24
  fix(ai): prepend agent bin dir to PATH so node-managed CLIs find node
25
25
  docs(readme): drop Releases and Configuration sections
26
26
  chore: bump release-please manifest to 0.3.0
27
- refactor(picker): collapse duplicate fzf bootstrap code
27
+ refactor(picker): simplify native picker bootstrap code
28
28
  ```
29
29
 
30
30
  Rules:
@@ -48,6 +48,13 @@ maps, SQLite, protobuf, or remote telemetry.
48
48
  - Snapshot state is `warming`, `ready`, `partial`, or `unavailable`. Bounded
49
49
  diagnostics contain scan duration and sampled/skipped/race/permission counts,
50
50
  plus identity/delta quality counts; they contain no user payload.
51
+ - The interactive lifecycle has a two-second total budget matching its
52
+ non-overlapping cadence. Tmux inventory and procfs observe the context
53
+ directly. Project discovery is included in elapsed-budget classification but
54
+ its current interface is not cancellable, so an overrun is recognized only
55
+ when discovery returns. Budget exhaustion remains an unavailable snapshot;
56
+ it never exposes a partially mutating result. Context-aware project discovery
57
+ is a separate follow-up.
51
58
 
52
59
  ## Measurements
53
60
 
@@ -90,11 +97,18 @@ The opt-in read-only test below observes an existing socket and emits counts
90
97
  only. It neither captures pane content nor reads process command lines.
91
98
 
92
99
  ```text
93
- PROJMUX_RESOURCE_TMUX_SOCKET=projmux go test \
100
+ PROJMUX_RESOURCE_TMUX_SOCKET=projmux \
101
+ PROJMUX_RESOURCE_EXPECT_PROJECT_ROOT=/path/to/project go test \
94
102
  -run TestResourceAttributionRealTmuxReadOnlySmoke -v \
95
103
  ./internal/integrations/tmux
96
104
  ```
97
105
 
106
+ When `PROJMUX_RESOURCE_EXPECT_PROJECT_ROOT` is set, the smoke additionally
107
+ requires at least one blank explicit anchor to resolve from its pane current
108
+ path and requires the resulting project bucket to contain a pane. Output stays
109
+ bounded to the expected project path and aggregate counts; it does not emit
110
+ session names, pane content, prompts, transcripts, or process command lines.
111
+
98
112
  2026-08-12 result: `panes=8`, `pane_pid_eq_sid=8`, `missing_pids=0`,
99
113
  `attributed_processes=13`, `escaped_boundary=0`, `sampled=474`, `skipped=0`,
100
114
  `race=0`, `permission=0`, `status=ready`. A separate tmux format-only count
@@ -119,6 +133,18 @@ PROJMUX_RESOURCE_TRANSIENT_SMOKE=1 go test \
119
133
  The pane shell remained attributed while its real `setsid` child was counted
120
134
  at the escaped/Other boundary, without reading the child command line.
121
135
 
136
+ The current-path fallback itself has a separate isolated real-tmux smoke. It
137
+ starts with inherited `TMUX`/`TMUX_PANE` removed, uses a dedicated
138
+ `TMUX_TMPDIR` plus `-L` socket, verifies the actual socket path is below that
139
+ temporary root before exact cleanup, and confirms the blank tmux project
140
+ option remains blank after in-memory attribution:
141
+
142
+ ```text
143
+ PROJMUX_RESOURCE_PROJECT_FALLBACK_SMOKE=1 go test \
144
+ -run TestResourceProjectFallbackTransientSmoke -v \
145
+ ./internal/integrations/tmux
146
+ ```
147
+
122
148
  ## Phase 1 inspector
123
149
 
124
150
  The popup retains warming/partial/unavailable and overage states, renders RSS
@@ -127,6 +153,53 @@ samples when it closes. Its non-overlapping default cadence is two seconds;
127
153
  Ctrl-R shares the same scan gate. Selection and query survive refresh by stable
128
154
  row identity, while a vanished row clamps to the nearest valid neighbor.
129
155
  Display labels use label → agent topic → known interactive shell → raw title,
130
- but those values never become ownership keys. Unsupported platforms show an
131
- unavailable reason, not zero metrics. PSS, non-Linux collectors, process-list
132
- drill-down, history, and resource mutation remain outside this contract.
156
+ but those values never become ownership keys. Pane rows and detail reuse that
157
+ identity plus the tmux current command, PID/SID, pane id, and TTY; pane rows
158
+ show attributed process counts while project/window rows retain pane counts.
159
+ Right/Enter move forward, Left moves back (and is a root no-op), and Esc closes
160
+ at every depth. Unsupported platforms show an unavailable reason, not zero
161
+ metrics. PSS, non-Linux collectors, process-list drill-down, history, and
162
+ resource mutation remain outside this contract.
163
+
164
+ Host and attributed CPU/memory use the same semantic classifier as the live
165
+ statusbar: CPU is normal below 70%, warning at 70–89.9%, and critical at 90%
166
+ or above; memory is normal below 75%, warning at 75–89.9%, and critical at 90%
167
+ or above. Values retain the resolved semantic role but omit visible severity
168
+ words. Unknown is rendered as `--`, never as zero; Sample lifecycle and
169
+ freshness remain explicit text.
170
+
171
+ The first paint is a non-actionable warming surface. Completed samples report
172
+ age and fresh/stale state; partial and overage callouts stay bounded to counts
173
+ and aggregate values. Empty and gone scopes are read-only and explain what the
174
+ latest complete sample can no longer open. Automatic refresh runs every two
175
+ seconds; Ctrl-R reports in-progress state while retaining the last complete
176
+ sample. Both paths preserve scope, breadcrumb, query, selection, and the row
177
+ order last computed by Tab. The default order is Name; Tab computes CPU,
178
+ Memory, or Name once from the current sample, while later refreshes update row
179
+ values without silently moving focus. Native synchronized frame diffs repaint
180
+ only changed rows and update state/footer chrome together.
181
+
182
+ The Resource Inspector lifecycle projects only unavailable, partial, stale,
183
+ collection/inventory/discovery error, and scan-budget transitions into the
184
+ private bounded operations journal. A persistent identical transition is
185
+ coalesced until silent recovery; normal samples and refreshes emit nothing.
186
+ The statusbar's separate host-only sampler also emits nothing. Resource events
187
+ never contain the metrics, PID/process data, tmux/project identity, paths,
188
+ titles, commands, snapshot reasons, or arbitrary error strings described by
189
+ this UI model. See [operational-diagnostics.md](operational-diagnostics.md).
190
+
191
+ The live summary is a fixed five-row bottom dock below the search/list surface:
192
+ one renderer-owned theme-aware divider, then Host, Attributed, Coverage, and
193
+ Sample. It does not scroll or filter with rows. Coverage owns the non-drillable
194
+ Other or current-scope empty/gone explanation; bounded partial/overage details
195
+ stay on Sample. The action footer remains below the dock with its own chrome
196
+ boundary, so diagnostic values and key hints never share a role. The 80x24
197
+ layout retains a navigable list viewport without clipping, border bleed, or a
198
+ second dock divider.
199
+
200
+ Project rows label project paths explicitly. The two attribution buckets keep
201
+ their stable core keys but display `No project match` and `Multiple project
202
+ matches` with bounded explanations. Pane primary identity follows the shared
203
+ label → agent-only AI topic → interactive shell → raw title resolver; pane id,
204
+ process id, and TTY remain labeled secondary details and stable keys are
205
+ unchanged.
@@ -137,3 +137,21 @@ longer accepts startup selector flags for session-state restore. It always
137
137
  follows the normal empty attach path after resolving the target app session name
138
138
  and startup directory. Use `Settings > Session State > Sidebar startup picker` for
139
139
  interactive Latest snapshot / Named snapshot / Empty session selection.
140
+
141
+ ## Operational diagnostics
142
+
143
+ Selected Session State mutations leave one local, best-effort terminal outcome
144
+ in the bounded operational journal. Manual and popup save, Project Settings
145
+ latest/named save, manual/Settings/prune delete, and actual project-startup
146
+ latest/named replay are covered. Restore preview and `--dry-run` remain
147
+ read-only. Successful autosave, disabled/not-due/fresh autosave no-ops, and
148
+ nested snapshot store/replay calls do not write an outcome; an actual autosave
149
+ failure writes one safe error even under `--quiet`.
150
+
151
+ The outcome contains only a closed operation/source and aggregate window,
152
+ pane, recipe, or deleted-item counts. It never contains a snapshot or project
153
+ path, snapshot content/name, pane cwd/command, agent resume/conversation/session
154
+ ID, or arbitrary metadata. Diagnostics append failure never changes save,
155
+ restore, delete, or quiet-autosave behavior. See
156
+ [operational-diagnostics.md](operational-diagnostics.md) for the complete event
157
+ schema and retention/privacy contract.
@@ -71,11 +71,11 @@ view-first layout:
71
71
  desktop AI notification collapse window. It stores integer seconds and shows
72
72
  the effective source; `PROJMUX_TMUX_NOTIFY_DEDUPE_SECONDS` remains the top
73
73
  override. The tmux bell fallback keeps its fixed 5 second window.
74
- - `Settings > Notifications > Delivery sources` shows Codex hooks, Claude, and
75
- tmux producer diagnostics, the effective desktop sender override state, and
76
- copyable install/remove/dry-run commands. Settings copies command text only;
77
- it does not install or remove external notify wiring. The legacy Codex notify
78
- source is intentionally omitted from Settings.
74
+ - `Settings > Notifications > Delivery sources` shows Codex, Claude,
75
+ Antigravity, and tmux producer diagnostics, the effective desktop sender
76
+ override state, and copyable install/remove/dry-run commands. Settings copies
77
+ command text only; it does not install or remove external notify wiring. The
78
+ legacy Codex notify source is intentionally omitted from Settings.
79
79
  - `Settings > Notifications > Hook quiet policy` shows Codex/Claude hook
80
80
  runtime action values and writes only
81
81
  `${XDG_CONFIG_HOME:-$HOME/.config}/projmux/ai-hook-actions.json`. It does not
@@ -85,8 +85,8 @@ view-first layout:
85
85
  `${XDG_CONFIG_HOME:-$HOME/.config}/projmux/sidebar-startup-picker`.
86
86
  - `Settings > Labs` contains only Live system resources and Project Hooks.
87
87
  Keybindings live at `Settings > Keybindings`; Labs has no visible or hidden
88
- keybindings redirect. Native is the only picker backend, so Labs does not
89
- render picker source/backend information.
88
+ keybindings redirect. The native picker is the product picker, so Labs does
89
+ not render picker source information.
90
90
  - `Settings > Labs > Live system resources` is a direct global on/off toggle
91
91
  for the macOS/Linux/WSL lower-status-row `CPU N% MEM N%` segment. It defaults
92
92
  off, updates live tmux state when toggled, and renders unavailable on
@@ -97,7 +97,8 @@ view-first layout:
97
97
  - `Settings > Labs > Project Hooks` is overview-first. The Labs root opens the
98
98
  overview, and the on/off mutation rows live one level deeper.
99
99
  - `Settings > AI Settings` is view-first. The root contains `Default split
100
- mode`; the detail contains the `Claude`, `Codex`, and `Shell` choices.
100
+ mode`, `Enabled agents`, and `Resume picker`; the default-mode detail contains
101
+ the `Claude`, `Codex`, `Antigravity`, `Shell`, and `Selective` choices.
101
102
  - `Settings > Project > Project recipe` is the functional label for
102
103
  `.projmux/config.toml`. Search still matches `config.toml` as an alias.
103
104
  - `Settings > Project > Project recipe` is view-first. The root contains section
@@ -117,7 +118,7 @@ view-first layout:
117
118
  visible as warnings and fall back to `en-US`.
118
119
  - Global root descriptions keep ownership explicit: Appearance owns language,
119
120
  AI badge style, and status/notification icon decoration; Theme owns presets,
120
- color tokens, and font hints. About describes only the surface it retains.
121
+ and color tokens. About describes only the surface it retains.
121
122
  - `Settings > About` is intentionally compact: Version, Source, update
122
123
  status/actions (including Latest, Update state, Installer, and Release notes
123
124
  when available), Welcome, and Quit. It does not reproduce static key,