projmux 0.15.2 → 0.16.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.
@@ -348,14 +348,19 @@ a recorded verdict moves. Neither it nor L20 proves:
348
348
  - acceptance of unknown frames or fields, which the closed vocabulary rejects
349
349
  by design.
350
350
 
351
- The L20 fixture emits none of the corpus's dropped side frames. Recorded real
352
- shapes the validator rejects today:
351
+ L20 replays every corpus item whose verdict is `drop` through the production
352
+ reply-only validator. Its Claude fixture reads the corpus at run time, emits each
353
+ dropped side frame with its key set unchanged after init and before its startup
354
+ result, and the scenario requires the emitted names to equal the corpus drop
355
+ set. Those frames are key-shape placeholders, not model output. L20 does not
356
+ emit the recorded real shapes the validator rejects today:
353
357
 
354
358
  - `result-success-25-keys`: the result allowlist lacks `origin`;
355
359
  - `system-init-allowlist-diff`: the init allowlist lacks `memory_paths` and
356
360
  `terminal_slash_commands`.
357
361
 
358
- Shapes without preserved evidence are gaps, not corpus items: `command_lifecycle`
362
+ Shapes without preserved evidence are gaps, not corpus items, so neither the
363
+ corpus nor L20 exercises them: `command_lifecycle`
359
364
  keys and values; a non-null assistant `context_management` value; `system`
360
365
  `thinking_tokens` frames; hook and user `tool_result` frames; the exact real init
361
366
  key set; nested values of every frame; `rate_limit_event` envelope keys beyond
package/docs/hooks.md CHANGED
@@ -117,10 +117,8 @@ special fields—`[env]` preserves any valid user-supplied key verbatim and no
117
117
  Global hooks under `$XDG_CONFIG_HOME` are prompt-free.
118
118
 
119
119
  Project-local executable automation is gated by trust-on-first-use. This
120
- includes `.projmux/config.toml` before projmux runs hooks or applies
121
- startup/session environment settings, and a selected
122
- `.projmux/layouts/*.toml` named snapshot before projmux replays any declared
123
- `command`.
120
+ covers `.projmux/config.toml` before projmux runs hooks or applies
121
+ startup/session environment settings.
124
122
  Approving "always"
125
123
  records the file content hash in:
126
124
 
@@ -131,19 +129,13 @@ ${XDG_STATE_HOME:-$HOME/.local/state}/projmux/trusted-projects.json
131
129
  The trust key is the absolute repository path and each artifact path is stored
132
130
  relative to that repository. Each project entry has a
133
131
  `trusted_at` timestamp and a `files` map of relative paths to SHA-256 hashes.
134
- When file content changes or the layout path is replaced, projmux asks again
135
- and shows the old and new SHA-256 hashes. Layout symlinks, including symlinked
136
- `.projmux` or `layouts` path components, are rejected. The selected layout is
137
- read once; those exact bytes are both hashed and parsed, and the resulting
138
- in-memory snapshot is what restore uses. In non-interactive contexts such as
139
- tmux run-shell or CI, untrusted or changed project-local executable files fail
140
- closed with a warning.
132
+ When file content changes, projmux asks again and shows the old and new
133
+ SHA-256 hashes. In non-interactive contexts such as tmux run-shell or CI,
134
+ untrusted or changed project-local executable files fail closed with a warning.
141
135
 
142
136
  Set `PROJMUX_PROJECT_HOOKS=off` to disable project-local hook discovery
143
137
  entirely. Project-local hooks can also be disabled from `projmux settings`
144
- under Labs. The global hook still runs either way. This setting does not trust
145
- or bypass executable named-snapshot commands. A layout without a startup
146
- `command` does not require executable-artifact approval.
138
+ under Labs. The global hook still runs either way.
147
139
 
148
140
  ## Startup Commands
149
141
 
@@ -531,6 +523,16 @@ default install catalog is based on Claude Code 2.1.140 and represents the
531
523
  | `CwdChanged` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
532
524
  | `FileChanged` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
533
525
 
526
+ Current Claude Code (2.1.277) sends no hook when the operator denies a
527
+ permission dialog, so neither `PermissionDenied` nor `Stop` closes it and the
528
+ Agent keeps its `approval_required` observation. The held coordination message
529
+ release is therefore a second transcript tail reader besides `Stop`: while a
530
+ held message waits on that Agent, it reads the tail (at most 256 KiB) of the
531
+ `transcript_path` recorded in the Agent's own Registry binding, and looks only
532
+ at each line's type, subtype, and timestamp and whether an assistant line has a
533
+ `tool_use` item. Nothing it reads is stored, logged, or forwarded. See
534
+ [held messages](claude-coordination-endpoints.md#held-while-the-target-awaits-its-operator).
535
+
534
536
  Hook-generated queue rows use the same compact body catalog: agent label,
535
537
  event category, then the best available summary (Codex assistant text, Claude
536
538
  tool/action summary, transcript summary, error, or teammate labels). Structured
@@ -695,6 +697,53 @@ Codex and are managed from `Settings > Notifications > Agent event behavior`.
695
697
  They only affect ingest delivery; `projmux agent integrate claude` still uses the
696
698
  catalog `install` field for installed hook events.
697
699
 
700
+ ### Answering AskUserQuestion From The Command Line
701
+
702
+ `projmux agent integrate claude` also installs one `PreToolUse` entry with
703
+ `"matcher": "AskUserQuestion"` that runs
704
+ `projmux internal claude-question-hook` (marker
705
+ `projmux-managed:claude-question:v1`, `"timeout": 315`). Unlike the ingest
706
+ command its stdout is not discarded, because that is where an answer is handed
707
+ to Claude Code. Re-running the integration keeps exactly one such entry,
708
+ `--remove` deletes it, and `config apply` never adds or changes it.
709
+
710
+ The hook does nothing unless the Claude Agent that asks was opted in:
711
+
712
+ ```sh
713
+ projmux agent question enable <agent-ref>
714
+ projmux agent question disable <agent-ref>
715
+ ```
716
+
717
+ For every other Agent, and for a subagent's question, the hook prints nothing
718
+ and exits at once, so Claude Code shows its usual question prompt.
719
+
720
+ For an opted-in Agent the hook records the question and holds the tool call
721
+ open for up to 300 seconds. While it waits, Claude Code shows the hook's status
722
+ message instead of the question prompt; pressing Esc cancels the wait and
723
+ declines the question. Answer it from any shell:
724
+
725
+ ```sh
726
+ projmux agent question list <agent-ref> [-o json]
727
+ projmux agent question answer <agent-ref> <question-id> --option 1=<label> --index 2=<n> --text 3=<free text>
728
+ ```
729
+
730
+ Questions are numbered from 1 in the order Claude asked them, and so are the
731
+ options of each question. `--option <n>=<label>` picks an option by its exact
732
+ label, `--index <n>=<k>` by its number, and `--text <n>=<text>` answers with
733
+ free text; a label that is not one of the options is refused rather than taken
734
+ as free text. A multi-select question takes several `--option`/`--index`
735
+ occurrences, which are joined with `", "` in option order; a single-select
736
+ question takes exactly one. Every question needs an answer. A refused answer
737
+ changes nothing and names one reason token: `question-not-found`,
738
+ `question-not-pending` (already answered), `question-expired`,
739
+ `question-closed`, `question-invalid-answer`, `question-channel-off`, or
740
+ `question-provider-unsupported`.
741
+
742
+ If nobody answers within 300 seconds, the question expires and Claude Code
743
+ shows its own prompt as usual. `agent question disable` also hands every
744
+ question the Agent is still holding back to that prompt immediately. Records
745
+ live in `<state dir>/agent-questions/` and settled ones are kept for a day.
746
+
698
747
  ## Antigravity Hook Ingest
699
748
 
700
749
  `projmux internal agent-hook ingest antigravity-hook --event <event> < payload.json` accepts
@@ -702,19 +751,24 @@ Antigravity CLI `agy` hook payloads. Antigravity v1.1.12 stdin does
702
751
  not include the event name, so the command's `--event` value is authoritative.
703
752
  Payload event aliases remain a compatibility fallback when `--event` is
704
753
  omitted. `projmux agent integrate antigravity [--dry-run|--remove]` owns exactly
705
- the named `projmux` entry in `~/.gemini/config/hooks.json` and separately owns
706
- only `statusLine` in `~/.gemini/antigravity-cli/settings.json`. Other named hooks,
707
- their fields, and unknown JSON values remain untouched. The generated commands
754
+ the named `projmux` entry in `~/.gemini/config/hooks.json`. Other named hooks,
755
+ their fields, and unknown JSON values remain untouched. projmux does not
756
+ install, read, or judge the Antigravity `statusLine` in
757
+ `~/.gemini/antigravity-cli/settings.json`, and never creates that file. The generated commands
708
758
  use the stable absolute projmux executable because Antigravity runs handlers
709
759
  with the config directory as cwd. The command refuses unmanaged name/command
710
760
  conflicts, malformed JSON, symlink paths, and permission failures with an
711
761
  actionable diagnostic. Doctor/Settings distinguish installed, missing,
712
762
  conflicting, and stale managed entries; stale covers executable, event/schema,
713
763
  or stdout-fallback drift and is refreshed by the install command.
714
- The statusline object uses the official v1.1.12 command shape with
715
- `enabled=true` and `stack_with_default=true`. Its direct explicit `Statusline`
716
- ingest command emits empty stdout, preserving the built-in line. Existing
717
- custom statusline commands are conflicts and are never wrapped or chained.
764
+
765
+ Older releases also installed a `statusLine` bridge. `projmux config apply`
766
+ (and so `make install`) removes only a `statusLine` whose `command` carries the
767
+ `projmux-managed:antigravity-statusline:v1` marker, byte-preserving the rest of
768
+ the file; `agent integrate antigravity --remove` removes it too. Any other
769
+ `statusLine` value is left untouched and never produces a conflict. Until that
770
+ removal runs, the leftover bridge's `--event Statusline` call exits 0 with
771
+ empty stdout and changes nothing.
718
772
 
719
773
  The default Antigravity catalog records the five official v1.1.12 events and
720
774
  installs four non-permission events:
@@ -726,18 +780,16 @@ installs four non-permission events:
726
780
  | `PostInvocation` | marks the matched pane hook-active and writes a quiet bookkeeping diagnostic; no notify queue entry is pushed |
727
781
  | `PostToolUse` | marks the matched pane hook-active, retains tool error metadata in quiet diagnostics, and pushes no notify queue entry |
728
782
  | `Stop` | pushes an info completion unless an explicit error signal requires a critical error row |
729
- | `Statusline` with `tool_confirmation_pending=true` | pushes/replaces a deduped critical approval-required row outside the hook catalog |
730
- | `Statusline` with `agent_state=thinking|working|tool_use` | moves the matched pane to thinking/busy without notifying, unless a terminal completion/approval state must be preserved from a late refresh; a new `PreInvocation` resets the next generation to busy |
731
- | `Statusline` with `agent_state=idle` or `tool_confirmation_pending=false` | quiet update; does not clear completion/approval attention and creates no notification |
732
783
  | unknown events | mark the matched pane hook-active and write quiet ingest diagnostics only |
733
784
 
734
- Antigravity notify rows use `agent=antigravity` metadata. The v1.1.12 parser
785
+ Antigravity has no official approval-required or mid-turn busy event, so
786
+ projmux shows neither for Antigravity. Antigravity notify rows use
787
+ `agent=antigravity` metadata. The v1.1.12 parser
735
788
  retains common camelCase `conversationId`, `workspacePaths`, `transcriptPath`,
736
789
  `artifactDirectoryPath`, and `modelName`; invocation `invocationNum` and
737
790
  `initialNumSteps`; post-tool `toolCall`, `stepIdx`, and `error`; and Stop
738
791
  `executionNum`, `terminationReason`, `error`, and `fullyIdle`. Existing aliases
739
- such as `conversation_id`, `cwd`, `workspace.path`, `agent_state`, and nested
740
- `statusline.tool_confirmation_pending` remain accepted. The first non-empty
792
+ such as `conversation_id`, `cwd`, and `workspace.path` remain accepted. The first non-empty
741
793
  `workspacePaths` value is only a cwd fallback candidate; empty/absent arrays do
742
794
  not become the process cwd, and inherited `$TMUX_PANE` still wins attribution.
743
795
  `NO_TOOL_CALL`, `MODEL_STOP`, and known normal reasons are info completions.
@@ -756,9 +808,9 @@ The named entry in `hooks.json` remains the install source of truth. Use
756
808
  `agy -p '/hooks' --output-format json` only as a read-only runtime diagnosis of
757
809
  loaded sources/events; its result is never used to generate or rewrite config.
758
810
  Antigravity ingest uses `conversationId` as pane thread metadata for matching
759
- and as session-state resume metadata. Session restore uses
811
+ and as Agent resume metadata. Resume uses
760
812
  `agy --conversation <uuid>` only when that id is present and UUID-shaped;
761
- otherwise preview and doctor render `resume unavailable`. Official snake_case
813
+ otherwise the resume is refused rather than starting a fresh Agent. Official snake_case
762
814
  `cwd`, `conversation_id`, `transcript_path`, `agent_state`,
763
815
  `tool_confirmation_pending`, and structured `context_window.used_percentage`
764
816
  plus token fields are parsed directly. The structured percentage is persisted
@@ -824,13 +876,12 @@ empty.
824
876
  | `PROJMUX_SESSION_KIND` | `persistent` or `ephemeral` | `persistent` or `ephemeral` | empty | empty |
825
877
  | `PROJMUX_VERSION` | projmux version | projmux version | projmux version | projmux version |
826
878
  | `PROJMUX_SOCKET` | app socket metadata (`projmux`) | app socket metadata (`projmux`) | app socket metadata (`projmux`) | queue-entry socket when known; otherwise omitted |
827
- | `PROJMUX_PANE` | omitted: no pane exists yet | exact id returned by standard persistent/ephemeral `tmux new-session`, such as `%7`; omitted for snapshot replay | omitted | target pane when known; otherwise omitted |
879
+ | `PROJMUX_PANE` | omitted: no pane exists yet | exact id returned by standard persistent/ephemeral `tmux new-session`, such as `%7` | omitted | target pane when known; otherwise omitted |
828
880
 
829
881
  `pre-create` intentionally has no `PROJMUX_PANE`: it runs before
830
882
  `tmux new-session` creates the first pane. Standard persistent and ephemeral
831
883
  `post-create` paths run after creation and therefore receive that exact pane
832
- id. Snapshot replay can restore multiple panes and does not expose a single
833
- returned pane at its lifecycle boundary, so it omits `PROJMUX_PANE`.
884
+ id.
834
885
  `PROJMUX_SOCKET` is routing metadata for hook commands; it does not imply that
835
886
  the tmux client itself adds `-L` to its commands. The `post-attach` and
836
887
  `send-noti` cells describe their existing contexts; this contract adds no pane
@@ -194,8 +194,9 @@ Optional direct keys can be added for actions such as:
194
194
  | `AIResumePickerToggle` | AI resume session picker; default `Alt-4`; pressing again closes the picker popup |
195
195
  | `ai-split-right` | Open a new direct AI split to the right |
196
196
  | `ai-split-down` | Open a new direct AI split below |
197
- | `new-window` | New tmux window in the current pane directory |
198
- | `rename-window` | Rename the current tmux window |
197
+ | `new-window` | New tmux window in the current pane directory, opened with the saved launch default |
198
+ | `rename-window` | Rename the current Window in the Registry; see [Rename keys](#rename-keys) |
199
+ | `rename-pane-label` | Rename the current Pane in the Registry; see [Rename keys](#rename-keys) |
199
200
 
200
201
  `AISplitPickerToggle` is the `Alt-7` popup picker toggle. It opens or closes
201
202
  the picker UI where the user chooses the AI split mode. It is separate from
@@ -205,6 +206,70 @@ The direct AI split actions create a new managed AI pane each time they run.
205
206
  Existing AI panes are left in place; the requested direction controls where the
206
207
  new pane is created.
207
208
 
209
+ A UI split focuses the new Pane. This covers the saved-default split key, the
210
+ provider and shell direct keys, a selection in the `Alt-7` picker or the resume
211
+ picker (including its `new` row), and the Pane menu's Horizontal Split /
212
+ Vertical Split. Once the create commits, the new Pane becomes the Window's
213
+ active Pane, but only when the client that pressed the key or clicked the menu
214
+ is still attached and still showing that Window. If that client has detached
215
+ or moved to another Window, focus is left alone and the new Pane stays. The
216
+ client is never moved to the Window. If the focus step itself fails, the Pane
217
+ is kept and that client sees one line:
218
+ `Created Pane, but projmux could not focus it: <reason>`. The public
219
+ `projmux create pane` and `create agent` commands never change focus.
220
+
221
+ A selection in the `Alt-7` picker or the resume picker closes its popup at
222
+ once. The picker hands the selection to a detached job on the same tmux server
223
+ and exits, and that job runs the same create the picker used to run while the
224
+ popup stayed up as an empty frame. The new Pane therefore appears a moment
225
+ after the popup closes. A successful split still shows nothing else; a refused
226
+ create, a failed focus, or a split start notice reaches the client that pressed
227
+ the key as one line. Every picker row travels this way: a selection is a plain
228
+ value -- a provider, and for a resume the conversation it names. The provider
229
+ picker offers one row per enabled provider plus the shell row; it has no
230
+ per-launch model or effort row.
231
+
232
+ ### The new Window's first Pane
233
+
234
+ `window.create` (v0 id `new-window`) and the Window menu's New At End decide the
235
+ new Window's first Pane before the Window exists, following the saved launch
236
+ default -- the same setting the saved-default split key reads (Settings > AI
237
+ Settings, stored in `$XDG_CONFIG_HOME/projmux/tmux-ai-split-mode`). The order is:
238
+
239
+ 1. Ask. A picker mode opens its picker on the Pane the key was pressed in; a
240
+ provider mode and `shell` are already the answer and open nothing.
241
+ 2. Create the Window with one shell Pane.
242
+ 3. Fill it: an Agent answer is created in that Window first, and the shell is
243
+ then removed through the canonical Pane delete.
244
+ 4. Move the pressing client onto the finished Window.
245
+
246
+ The client therefore never sees a shell Pane that is about to be replaced.
247
+
248
+ | Saved default | The new Window ends up with |
249
+ | --- | --- |
250
+ | `shell` | the shell Pane the create made; nothing else runs |
251
+ | `claude`, `codex`, `antigravity` | exactly that Agent Pane, with no picker |
252
+ | `selective` (also the unset default) | whatever the `Alt-7` picker chose: that Agent Pane, or the shell Pane for the shell row; its `resume` row opens the resume session list in the same popup |
253
+ | `resume` | whatever the resume picker chose, on the same terms |
254
+
255
+ Cancelling the picker creates nothing: no Window, and the client stays where it
256
+ was. If the picker cannot be opened, nothing is created either. An Agent answer
257
+ is committed with its Window in one transaction, so a provider that Settings has
258
+ since disabled or an Agent that could not be created leaves no Window behind.
259
+ Both report one line on the pressing client instead of the usual
260
+ `Created Window`:
261
+ `projmux Create Window failed; no Window was created: <reason>`. The line is
262
+ fitted to that client's width (80 cells when the width cannot be read). The
263
+ outcome is never cut; a reason too long to fit keeps its front, which names the
264
+ step that failed, and its end, where tmux's own cause is, and loses its middle
265
+ to one `…`. A create whose pressing client could not be moved still fills the
266
+ Window with what was chosen and shows the existing `Created Window, but
267
+ projmux could not move this client to it` line.
268
+
269
+ The typed `projmux create window` and `projmux create agent --create-window`
270
+ are unchanged and never read the saved default: a typed command's outcome
271
+ stays visible in its own argv.
272
+
208
273
  Pane switching is catalogued as transport-dependent and the generated app tmux
209
274
  config binds `M-Left`, `M-Right`, `M-Up`, and `M-Down` to `select-pane`
210
275
  movement. Previous/next window remain transport-dependent and the generated app
@@ -295,7 +360,47 @@ write; the projmux process never issues `kill-pane` or `kill-window`.
295
360
 
296
361
  A mouse menu acts on what was clicked, not on what is focused: Kill, Rename, and
297
362
  New At End in the status-line Window menu act on the clicked Window, and the
298
- Rename prompt starts with that Window's name.
363
+ Rename prompt starts with that Window's name. New At End opens the new Window's
364
+ first Pane with the saved launch default; see
365
+ [The new Window's first Pane](#the-new-windows-first-pane).
366
+
367
+ ## Rename keys
368
+
369
+ `rename-window` (`window.rename`) and the Window menu Rename item run
370
+ `internal tmux window-rename`; `rename-pane-label` (`pane.rename`) runs
371
+ `internal tmux pane-rename`. Neither has a default key. Both are Registry
372
+ renames through the same owner as `projmux rename window` and
373
+ `projmux rename pane`: the Pane the key was pressed in, or the menu target,
374
+ resolves to its exact Registry Window or Pane, its `metadata.name` changes, and
375
+ its mirror converges with it. A Window rename writes `@projmux_window_name`
376
+ and, after the Registry commit, the tmux `window_name`; a Pane rename writes
377
+ `@projmux_pane_label`. A Continue that rebuilds the session from the Registry
378
+ therefore keeps the new name, including for the first Window of a session.
379
+
380
+ The prompt response is used exactly as typed:
381
+
382
+ - An empty or whitespace-only response changes nothing, not even the label,
383
+ and the client is told nothing was changed.
384
+ - A response that is not a valid name, such as one with a space, leading or
385
+ trailing whitespace, or a character like `'`, `"`, `$`, `;`, `%`, or `#`, is
386
+ refused with the reason and the usable spelling projmux would accept, for
387
+ example `a-b` for `a b`. Nothing is written.
388
+ - A name already used by another Window of the same Project or ControlSession,
389
+ or by another Pane of the same root, is refused. No suffix is added.
390
+ - A Pane with no Registry identity, such as one on a server projmux does not
391
+ manage, is refused with no write.
392
+ - A name a launcher chose, such as an Agent Pane's, renames like any other.
393
+
394
+ Every result is one line on the client that pressed the key. The response
395
+ reaches projmux on stdin inside a quoted here-document, so no shell parses it.
396
+ tmux still format-expands the `run-shell` command before any shell runs, as it
397
+ does at its own `:` prompt: a `#{...}` sequence in a response is substituted and
398
+ a `#(...)` sequence is run by tmux as a format job. Names cannot contain `#`, so
399
+ a response that still holds one is refused.
400
+
401
+ `internal tmux rename-pane <pane> <label>` is a separate label-only helper with
402
+ no generated binding. It writes `@projmux_pane_label` without touching the
403
+ Registry, so the next Continue restores the Registry name.
299
404
 
300
405
  The standalone `~/.tmux.conf` snippet keeps tmux's stock `prefix <`, `prefix >`,
301
406
  `MouseDown3Status`, `M-MouseDown3Status`, and `M-MouseDown3Pane` menus; it
@@ -32,12 +32,12 @@ unknown-command contract.
32
32
  | `notify push`, `notify list`, `notify ack`, `notify reconcile` | `create notification`, `get notifications`, `notification ack`, `notification reconcile` |
33
33
  | direct `pin list|add|remove|toggle|clear` | `pin project ...` |
34
34
  | `prune ephemeral` | `runtime prune` |
35
- | `prune session-state ...` | `prune snapshot ...` or `delete snapshot ...` |
36
- | `session-state status|save|delete|restore|preview|popup` | `get snapshots`, `create snapshot`, `delete snapshot`, `restore snapshot` |
35
+ | `prune session-state ...` | None. Project snapshots were removed; `registry.json` is the only saved Project state |
36
+ | `session-state status|save|delete|restore|preview|popup` | None. The interim `create|get|delete|restore|prune snapshot` replacements were removed with Project snapshots; Continue project and Clear layout and open start closed Projects from the Registry |
37
37
  | direct `tag list|toggle|clear`, `tag project ...` | `runtime tag ...` |
38
38
 
39
39
  The surviving mixed-root commands are exactly `attach project`, `focus
40
- project|window|pane`, `pin project`, and `prune project|snapshot`. The Shortcut
40
+ project|window|pane`, `pin project`, and `prune agent|project`. The Shortcut
41
41
  routes `doctor`, `quit`, `resources`, `settings`, `shell`, `switch`, and
42
42
  `welcome` remain. Singular/plural resource-kind aliases remain.
43
43
 
@@ -7,11 +7,11 @@ CLI, config action, output format, permission, or retention contract.
7
7
 
8
8
  | Surface | Producer / reader files | Current consumer and data boundary | Candidate and rationale | Separate follow-up |
9
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 internal 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. |
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 internal status usage` operator stderr; documented by `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` | none; the autosave route that read it is now a no-op | Ignored; `docs/configuration.md` lists it as ignored | **Removed.** Autosave no longer exists, so there is no quiet error left to surface. The variable was dropped in the breaking change that removed autosave. | No. |
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 `docs/cli.md`, `docs/configuration.md`, and `docs/operational-diagnostics.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
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` | Canonical reader `projmux diagnostics agent-hook`; 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 | **Keep behind canonical diagnostics.** Phase 5 common events cover anomalous classification but intentionally omit normal state/notify/quiet/dedupe detail and all identity. | Re-evaluate only if the detailed local diagnostic requirement changes. |
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` | Canonical reader `projmux diagnostics agent-hook`; count-only support reader `internal/app/diagnostics_report.go`; docs consumers `docs/cli.md`, `docs/hooks.md`, `docs/configuration.md`, and `docs/operational-diagnostics.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 | **Keep behind canonical diagnostics.** Phase 5 common events cover anomalous classification but intentionally omit normal state/notify/quiet/dedupe detail and all identity. | Re-evaluate only if the detailed local diagnostic requirement changes. |
15
15
 
16
16
  The separate host resource status path is not a legacy debug surface:
17
17
  `internal/app/status.go` invokes `internal/systemstatus.Sampler` for
@@ -17,7 +17,7 @@ picker process. `internal/ui/picker` owns the interaction contract and
17
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
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
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` |
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 `internal/ui/picker/backend_test.go` and `internal/app/native_picker_test.go` |
21
21
 
22
22
  ## Rendering And Popup Chrome
23
23
 
@@ -97,7 +97,5 @@ saved-selector access, propagation, or external picker launch path.
97
97
 
98
98
  ## Maintenance
99
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.
100
+ Update this document whenever picker behavior changes coverage level, gains a
101
+ new product flow, or changes input/render/action semantics.
@@ -73,7 +73,7 @@ routing/debug context such as `agent`, `thread_id`, `turn_id`, `cwd`,
73
73
  `teammate_name`. Antigravity hook rows carry `agent=antigravity`,
74
74
  `conversation_id`, `termination_reason`, `fully_idle`,
75
75
  `tool_confirmation_pending`, `agent_state`, and `context_window` when present.
76
- The same `conversation_id` can seed session-state restore via
76
+ The same `conversation_id` can seed an Agent resume via
77
77
  `agy --conversation <uuid>` when it is UUID-shaped. Antigravity
78
78
  account quota remains outside notify attention semantics: `context_window` is a
79
79
  separate conversation-local gauge and is never treated as quota data.
@@ -41,7 +41,7 @@ Project lifecycle operator diagnostics also keep plans mutually exclusive:
41
41
  classes. Startup and unregister failures print the closed action, failing
42
42
  stage, old Project UID, and new Project UID (or `-` when absent). These opaque
43
43
  UIDs and stage labels are bounded control data; root paths, pane content,
44
- history, prompts, transcripts, and snapshot contents are never identity or
44
+ history, prompts, and transcripts are never identity or
45
45
  intent authority.
46
46
 
47
47
  Automatic Window teardown decisions use `component=topology` and
@@ -58,29 +58,52 @@ optional opaque `window_uid` (`win-…`) and `pane_uid` (`pane-…`) Registry UI
58
58
  tmux `%N`/`@N`/`$N` handles, socket paths, session names, cwd, argv, and free
59
59
  text are never recorded, and every other event family rejects these fields.
60
60
 
61
- Session State mutations use one outcome-only `session-state.outcome` record
62
- per selected attempt. The closed operations are `session-state.save`,
63
- `session-state.autosave`, `session-state.restore`, and `session-state.delete`;
64
- an error uses only the matching `.failed` code, `kind=runtime`, and an empty
65
- message. Optional sources are limited to `manual`, `settings-latest`,
66
- `settings-named`, `autosave`, `startup-latest`, `startup-named`, and `prune`.
67
- Successful save and actual startup restore outcomes contain only exact
68
- non-negative `window_count`, `pane_count`, `shell_recipe_count`,
69
- `agent_recipe_count`, and `startup_recipe_count` aggregates. Successful delete
70
- contains only `item_count`; errors contain no counts. Snapshot paths/content,
71
- project paths, pane cwd/commands, snapshot names, and agent or conversation
72
- identifiers are never projected.
73
-
74
- Direct and popup save, Settings latest/named save, direct and Settings delete,
75
- deduplicated prune delete, and actual latest/named project-startup replay own
76
- these outcomes. Preview, dry-run, and nested store/replay calls do not.
77
- Autosave success and disabled, not-due, or fresh no-ops always write zero
78
- records; a real autosave failure writes exactly one error even when `--quiet`
79
- preserves its historical successful exit. Session State logical ownership
80
- suppresses a generic top-level outcome even when journal append fails. An
81
- actual restore may also produce its runtime lifecycle pair with the same run
82
- ID; the lifecycle pair and Session State terminal outcome describe different
83
- contracts and are not duplicates.
61
+ Create transactions use `component=create` and `event=create.outcome`: one
62
+ record per create transaction, written under the process `run_id`. It is
63
+ measurement only and changes nothing about the create itself. The kind of the
64
+ create is carried in the closed `operation` field: `window` (`create window`
65
+ and the UI new Window, including one whose answer is an Agent), `pane`
66
+ (`create pane`, the split UI's shell Pane, and the pane-menu split), `agent`
67
+ (`create agent` and the split UI's Agent Pane, including a resume-picker
68
+ pick, which creates a new Agent), or `resume` (`agent resume`, including the
69
+ resume `agent persona` runs). A successful transaction is `info`/`success`;
70
+ a failed one, including one that was rolled back, is `error`/`error` with
71
+ `kind=runtime`, so the support report's existing error-only projection
72
+ carries it. The record adds only two timings:
73
+
74
+ - `duration_ms` runs from entering the create transaction to its return. It
75
+ includes the runtime route bind, the wait for the Registry lock, the time
76
+ the lock is held, and on failure the rollback, and in every case the
77
+ create-operation lease clear. It excludes everything outside the
78
+ transaction: process start and exit, argument parsing, scope and selector
79
+ resolution, the Settings enabled-agents gate, the time a picker or prompt
80
+ waits for the operator, the result line printed after the commit, and
81
+ Agent activation observed after the transaction returns.
82
+ - `lock_held_ms` runs from entering the Registry mutation to the Registry
83
+ update returning. It includes the create's guards, reconciliation, Registry
84
+ and tmux mutations, the store's own validate and write, and the unlock. It
85
+ excludes the wait for the lock and the Registry read before the mutation
86
+ starts. It is absent when the transaction failed before it entered the
87
+ mutation, and `0 <= lock_held_ms <= duration_ms` always holds.
88
+
89
+ The record is appended after the transaction returns, so never while the
90
+ Registry lock is held, and a journal failure never changes the create's
91
+ result, exit status, stdout, or stderr. It does not replace the invocation's
92
+ `command.outcome`, and it does not count guard reads or time individual
93
+ guards. The generated Window rename also runs through the same transaction
94
+ and is not recorded, because it is not a create. Every other event family
95
+ rejects `lock_held_ms` and the `create` component.
96
+
97
+ projmux no longer emits `session-state.outcome` records. Project snapshots
98
+ were removed, and the retained `internal tmux autosave-session-state` route is
99
+ a no-op that writes nothing. Records written by older versions keep their
100
+ closed vocabulary: the `session-state.outcome` event, the
101
+ `session-state.save`, `session-state.autosave`, `session-state.restore`, and
102
+ `session-state.delete` operations with their `.failed` codes, the sources
103
+ `manual`, `settings-latest`, `settings-named`, `autosave`, `startup-latest`,
104
+ `startup-named`, and `prune`, and the aggregate `window_count`, `pane_count`,
105
+ `shell_recipe_count`, `agent_recipe_count`, `startup_recipe_count`, and
106
+ `item_count` fields still validate and parse when the journal is read.
84
107
 
85
108
  Notify and focus transitions use the same process `run_id` and add only closed
86
109
  `transition`, `disposition`, `provider`, `category`, and `route` enums. Notify
@@ -119,8 +142,9 @@ is the generic `ai`; ingest providers are `codex`, `claude`, `antigravity`, or
119
142
  for example, `tmux-bell` can only emit `bell`, while watcher events can only use
120
143
  the generic `ai` provider and `watcher` kind. Provider event names are projected
121
144
  into semantic kinds such as `prompt`, `permission`, `stop`, `notification`,
122
- `tool`, `session`, `compact`, `subagent`, `teammate`, `statusline`, `invocation`,
123
- `lifecycle`, `bell`, `payload`, or `unknown`. A raw or future event name can
145
+ `tool`, `session`, `compact`, `subagent`, `teammate`, `invocation`,
146
+ `lifecycle`, `bell`, `payload`, or `unknown`. `statusline` is still accepted
147
+ when reading older records but is no longer written. A raw or future event name can
124
148
  therefore be diagnosed as `unknown` but can never extend the journal schema.
125
149
 
126
150
  One watcher process emits at most one `started` transition, one terminal
@@ -288,22 +312,20 @@ before the next append, and the reader skips malformed or truncated records.
288
312
  Classification is intentionally conservative for mutation-capable interactive
289
313
  commands: opening session/project/settings/popup flows is treated as changing
290
314
  even when a user cancels. Explicit read variants (`internal status`, `list`,
291
- `get`, read-only restore preview, config rendering, plain welcome, and the diagnostics viewer) remain
315
+ `get`, config rendering, plain welcome, and the diagnostics viewer) remain
292
316
  read-only. The successful automatic hook/poll paths `internal agent-hook ingest`, `attention
293
317
  arm`, `attention clear`, `attention window`, `internal tmux autosave-session-state`, and
294
318
  `window record` are also read-only so high-frequency operation does not append
295
319
  to the journal; an error from any of them still records exactly one safe error
296
320
  outcome. Explicit user mutations such as `attention toggle` retain their
297
321
  state-changing success record. Direct top-level help and explicit preview-only intents (`update
298
- apply --dry-run`, AI integration dry-runs, and snapshot projection restore with
299
- `--dry-run`) are also read-only. Approved snapshot projection restore is a
300
- state-changing operation. Doctor is a stricter
322
+ apply --dry-run` and AI integration dry-runs) are also read-only. Doctor is a stricter
301
323
  boundary: successes and errors never append to this journal, so diagnostics do
302
324
  not make its filesystem contract self-defeating. Support report success and
303
325
  errors likewise never append; its strict reader shares the viewer's tolerant
304
326
  decoder but never creates/locks/chmods/repairs/truncates the source journal.
305
327
  Multi-mode commands such as AI status/topic,
306
- terminal apply, snapshot delete, update check, and welcome popup inspect only
328
+ terminal apply, update check, and welcome popup inspect only
307
329
  allowlisted mode/flag names; boolean `=false` values retain mutation-capable
308
330
  classification, and no flag values are ever recorded. Help-looking tokens
309
331
  after the direct command position stay conservatively mutation-capable because