projmux 0.16.0 → 0.16.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.
package/docs/testing.md CHANGED
@@ -5,25 +5,52 @@ and humans run the same entrypoints.
5
5
 
6
6
  ## Targets
7
7
 
8
- - `make test` runs the fast Go unit suite. These tests avoid tmux, TTY, GUI,
9
- and host shell dependencies.
8
+ - `make test` runs the fast Go unit suite. Apart from the real-tmux tests
9
+ below, these tests avoid tmux, TTY, GUI, and host shell dependencies.
10
10
  - `make vet` runs `go vet ./...`. CI runs it in the Unit Tests job after
11
11
  `make test`.
12
+ - The real-tmux Go tests in `internal/app` find tmux through one helper,
13
+ `requireRealTmux`. Without tmux they skip; `PROJMUX_REAL_TMUX_STRICT=1`
14
+ turns a missing tmux into a test failure instead, so a run that is meant to
15
+ exercise real tmux cannot pass by skipping. A new real-tmux test must call
16
+ the helper rather than its own `exec.LookPath("tmux")` skip, and the source
17
+ audit `TestRealTmuxSkipIsOwnedByOneHelper` fails on any other skip.
18
+ The CI Unit Tests job installs tmux and runs
19
+ `PROJMUX_REAL_TMUX_STRICT=1 make test` with a TMPDIR of at least 100 bytes,
20
+ so a missing tmux there fails the job instead of skipping those tests.
21
+ The same switch turns on the isolated real-tmux smokes in
22
+ `internal/integrations/tmux` and `internal/integrations/metadata`, which
23
+ otherwise wait for their own opt-in variable.
24
+ - A Go test that opens a unix socket must put it under a short root (the
25
+ `/tmp` + `os.MkdirTemp` idiom, e.g. codexbroker `newRuntimeDomain`,
26
+ `internal/app` `shortTempDomain`) rather than `t.TempDir()`, because the
27
+ socket path has a platform bound (Linux 108B, macOS 104B) and the long CI
28
+ TMPDIR fails such a test. Real-tmux tests put their `TMUX_TMPDIR` or
29
+ `tmux -S` root under `os.MkdirTemp("/tmp", …)` the same way, and the
30
+ liveguard private root, which holds a guarded package's `TMUX_TMPDIR` and
31
+ `XDG_STATE_HOME`, is made under `/tmp` too. Its owner holds a lock file in
32
+ that root for its whole life, so a later guarded owner reclaims the roots of
33
+ killed runs, whose lock is free, and leaves roots without that lock file
34
+ alone.
12
35
  - Picker unit coverage includes the backend-neutral item/action contract,
13
36
  native title-focused filtering, numeric selection, and shared close actions.
14
37
  - `make test-integration` builds `test/docker/Dockerfile` and runs
15
38
  `test/integration/linux-smoke.sh` in Docker. It validates Linux dependency
16
39
  discovery, tmux config generation/install, app config reload against a real
17
- `tmux` server, and notify queue CRUD.
18
- - `make test-install-smoke` builds the same Docker image and runs
19
- `test/install/smoke.sh`. It validates `make install`, atomic binary
20
- replacement into an isolated install dir, pre-publication marker convergence,
21
- concurrent legacy/candidate/installed shell and attach consumers, exact
22
- server-generation/session preservation, `tmux apply`, and post-install notify
23
- reconcile initialization with a fresh HOME/XDG state tree.
40
+ `tmux` server, and notify queue CRUD. It also runs `test/install/smoke.sh`,
41
+ so the required `Integration Tests` job covers the install smoke.
42
+ - `make test-install-smoke` builds the same Docker image and runs only
43
+ `test/install/smoke.sh`, as a local entrypoint. It validates `make install`,
44
+ atomic binary replacement into an isolated install dir, pre-publication
45
+ marker convergence, concurrent legacy/candidate/installed shell and attach
46
+ consumers, exact server-generation/session preservation, `tmux apply` into
47
+ the resolved `$XDG_CONFIG_HOME/projmux/tmux.conf`, and post-install notify
48
+ reconcile initialization with a fresh HOME/XDG state tree. It ends with a
49
+ `>> install smoke passed` line. `make verify` does not repeat it, because
50
+ `make test-integration` already runs it.
24
51
  - `make test-e2e` prepares one attempt-local immutable product binary, then
25
52
  runs four isolated Linux real-tmux fixtures plus the Codex lifecycle and npm
26
- staging fixtures. The required inventory is `L01`-`L19`, `C01`, and `N01`;
53
+ staging fixtures. The required inventory is `L01`-`L20`, `C01`, and `N01`;
27
54
  every fixture has its own HOME/XDG/tmux/socket/evidence roots and every
28
55
  consumer records the same binary SHA. `E2E_SCENARIO=<ID>` selects one exact
29
56
  stable scenario for replay.
@@ -61,7 +88,7 @@ and humans run the same entrypoints.
61
88
  the full product matrix. The shard target also pins the CI job list to the
62
89
  manifest. A deterministic fake-runner start/release barrier proves local
63
90
  default overlap is one and explicit parallel overlap is four while both use
64
- one immutable binary, the exact 21-scenario inventory, and the same result
91
+ one immutable binary, the exact 22-scenario inventory, and the same result
65
92
  hash. The target also pins one non-fail-fast CI job per shard and per suite,
66
93
  each with its own runner, timeout, and uniquely named evidence artifacts, all
67
94
  of them required children of the aggregate `Test` gate. It also pins the thin
@@ -70,21 +97,34 @@ and humans run the same entrypoints.
70
97
  pending rather than failing, so dropping that job would deadlock merges. The
71
98
  tag-triggered Release workflow uses the same four Linux shards and two suite
72
99
  selectors on separate runners, then reduces them through the fail-closed
73
- `Release E2E Tests` aggregate before `Build Release` starts.
100
+ `Release E2E Tests` aggregate before `Build Release` starts. `make test`
101
+ runs `make test-e2e-shards`, so the required Unit Tests job fails on shard
102
+ contract drift. The live dialogue canary's own contract is
103
+ `test/agent_dialogue_canary_test.py`, not this target.
104
+ - `make test` also runs the evidence contract (`make e2e-evidence-contract`),
105
+ `make test-e2e-reliability`, and `make test-e2e-residual-policy`, so the
106
+ required Unit Tests job fails when any of them does. `make test-e2e-contract`
107
+ is a local entry point for the admission and evidence halves, both of which
108
+ `make test` already runs. The reliability contract removes the artifact
109
+ directory it creates on every exit, and keeps one the caller passes in
110
+ `PROJMUX_E2E_ARTIFACTS`.
74
111
  - `make test-e2e-coverage` validates
75
112
  `test/e2e/ags-oedr-manifest.json`: executable scenario markers and shard
76
- assignments must match all 21 rows with orphan count zero. A matrix may move
113
+ assignments must match all 22 rows with orphan count zero. A matrix may move
77
114
  out of real-tmux E2E only when its checked-in entry names executable lower
78
115
  positive, negative, and fixed-point evidence and retains a real-boundary
79
116
  sentinel. The manifest/orphan half is a prerequisite of `make test-e2e`,
80
117
  while the referenced lower test runs in both this coverage target and the
81
- required unit-test job.
118
+ required unit-test job, so `make test-e2e-coverage` is a local entry point
119
+ whose two halves are already enforced.
82
120
  - `make security` runs the exact three Security groups in parallel locally:
83
121
  Go vulnerability/security, Go static quality, and repository policy.
84
122
  `make security-serial` is the parity control and `make security-contract`
85
- checks scanner/rule/baseline identity, PR-range/full-history secret scans,
123
+ checks scanner/rule/baseline identity, PR-range and HEAD-history secret scans,
86
124
  cache miss-to-hit convergence, privacy-safe artifacts, and the fail-closed
87
125
  aggregate. CI exposes their stable aggregate as `Test`.
126
+ After adding or removing a Go package, refresh the package-set pin with
127
+ `make security-pin-refresh` (rule: `AGENTS.md` `## Security`).
88
128
  - `make deadcode` runs the focused baseline-contract fixture, then runs
89
129
  `go tool deadcode` (pinned via the go.mod tool directive) over the module.
90
130
  `.deadcode-allowlist.txt` is exact to current findings: duplicate and stale
@@ -156,7 +196,7 @@ remains inside L17. Its row links creator-Registry admission, journal-path and
156
196
  CLOEXEC handshake tests to the immediate-exit integration/E2E markers, and
157
197
  also closes the focused fresh-root repeat harness plus the read-only
158
198
  owner/queue observation and product-terminal controller marker. These rows add
159
- no scenario ID: the audit still requires exactly 21 stable scenarios and fails
199
+ no scenario ID: the audit still requires exactly 22 stable scenarios and fails
160
200
  closed if either source commit, guarantee set, lower selector, supporting
161
201
  marker, or marker-before-pass edge drifts.
162
202
 
@@ -168,6 +208,15 @@ value for an explicitly observed cancellation; the shell harness does not
168
208
  invent cancellation evidence from a signal whose semantic cause it cannot
169
209
  attribute.
170
210
 
211
+ `smoke_contract_pass` records `pass` only when the contract shell's own stdout
212
+ and stderr between `smoke_contract_begin` and that call carry no Python
213
+ traceback, even one whose child status was consumed. A traceback ends the
214
+ scenario through the ordinary fail path at the pass line and logs
215
+ `E2E_CONTRACT id=<ID> guard=python-traceback stream=<stream> exception=<last
216
+ exception line>`; there is no exemption list. Output redirected to a file never
217
+ reaches the guard. `make e2e-traceback-guard-contract` pins this, including a
218
+ traceback written while the job log lags behind the pass call.
219
+
171
220
  ## Host-Only Checks
172
221
 
173
222
  The Docker suites do not replace checks that depend on a real host terminal,
@@ -298,8 +347,9 @@ Observe:
298
347
  ```markdown
299
348
  Host-only smoke validation:
300
349
 
301
- - Docker-covered checks: `make test-integration`, `make test-install-smoke`,
302
- and `make test-e2e` cover portable Linux tmux/config/notify behavior only.
350
+ - Docker-covered checks: `make test-integration` (which includes the install
351
+ smoke) and `make test-e2e` cover portable Linux tmux/config/notify behavior
352
+ only.
303
353
  - Terminal key delivery: not run / run on <terminal>; `projmux setup --timeout
304
354
  10s` showed <result>; `Alt-1..5` app popup smoke <passed/failed/not run>.
305
355
  - WSL toast: not run / run on <Windows + WSL distro>; detached-focus smoke
@@ -9,11 +9,11 @@ semantic role map is `internal/theme/resolve.go`.
9
9
  ## Scope
10
10
 
11
11
  The fallback palette is a semantic token layer. The effective theme is a global
12
- user theme resolved from `~/.config/projmux/config.toml`, followed by the
13
- built-in fallback values from `internal/theme/palette.go`. The resolver derives
14
- a semantic role map (`RenderRoles` for tmux chrome, `ANSIRoles` for native ANSI
15
- surfaces) from the effective theme, and renderers consume those roles instead of
16
- bare palette literals.
12
+ user theme resolved from `${XDG_CONFIG_HOME:-$HOME/.config}/projmux/config.toml`,
13
+ followed by the built-in fallback values from `internal/theme/palette.go`. The
14
+ resolver derives a semantic role map (`RenderRoles` for tmux chrome, `ANSIRoles`
15
+ for native ANSI surfaces) from the effective theme, and renderers consume those
16
+ roles instead of bare palette literals.
17
17
 
18
18
  - Native picker truecolor SGR tokens.
19
19
  - Native sidebar and chip-strip 256-color SGR tokens.
@@ -124,7 +124,7 @@ Rules:
124
124
 
125
125
  The effective theme resolves theme fields from:
126
126
 
127
- 1. Global `~/.config/projmux/config.toml`
127
+ 1. Global `${XDG_CONFIG_HOME:-$HOME/.config}/projmux/config.toml`
128
128
  2. Built-in fallback preset `projmux`
129
129
 
130
130
  Rules:
@@ -10,6 +10,7 @@ Primary production sources:
10
10
 
11
11
  - `internal/integrations/mux/`
12
12
  - `internal/integrations/tmux/`
13
+ - `internal/integrations/tmuxexec/`
13
14
  - `internal/app/`
14
15
  - generated config from `projmux config render standalone`,
15
16
  `projmux config render app`, and `projmux shell`
@@ -44,6 +45,18 @@ The boundary is intentionally pragmatic:
44
45
  - avoid spreading raw tmux invocation through app packages;
45
46
  - avoid introducing an abstraction that only renames a single local call.
46
47
 
48
+ Every process projmux starts that may be a tmux command client is started
49
+ through `internal/integrations/tmuxexec`. It adds tmux's `-u` client flag, so
50
+ format output keeps its `\x1f` and `\t` separators and UTF-8 values when the
51
+ caller has no UTF-8 locale (no `LANG`/`LC_*`, or `LC_ALL=C`); without it tmux
52
+ rewrites those bytes to `_`. Clients that take over a terminal
53
+ (`attach-session`, a `new-session` that is not detached) are left as they are.
54
+ `TestTmuxSpawnSitesAreClosed` checks every package whose source holds the
55
+ literal `"tmux"` or that imports a tmux integration package: each process spawn
56
+ there must use that package or be on its closed list of spawns that never run
57
+ tmux. A spawn of the literal `"tmux"` fails in any package. A package that runs
58
+ tmux with neither the literal nor such an import is not checked.
59
+
47
60
  ## Semantic Command Surface
48
61
 
49
62
  ### Metadata And Inventory
package/docs/upgrading.md CHANGED
@@ -61,9 +61,9 @@ With nothing left to choose, the setting that turned the screen off is gone.
61
61
 
62
62
  - Settings > Projects > Project Sidebar > Closed Project startup is removed.
63
63
  Project Sidebar keeps its Runtime diagnostics choice.
64
- - `~/.config/projmux/sidebar-startup-picker` is no longer read. The first
65
- `projmux config apply` after upgrading (which `make install` and
66
- `projmux update apply` also run) removes it and prints one
64
+ - `${XDG_CONFIG_HOME:-$HOME/.config}/projmux/sidebar-startup-picker` is no
65
+ longer read. The first `projmux config apply` after upgrading (which
66
+ `make install` and `projmux update apply` also run) removes it and prints one
67
67
  `reclaimed retired closed-Project startup setting: removed 1 file` line.
68
68
  Only a regular file with that name is removed; a failure is reported and
69
69
  never fails the apply, and later applies print nothing.
@@ -105,9 +105,9 @@ projmux no longer installs, reads, or judges the Antigravity `statusLine` in
105
105
  prints the `usage unsupported` note; `agent capabilities` reports Antigravity
106
106
  usage as unsupported. Working state still follows the official
107
107
  `PreInvocation`/`Stop` hooks.
108
- - Leftover `~/.config/projmux/statusbar-visibility-*antigravity*` files,
109
- `<state>/usage/antigravity-*.json` sidecars, and cached Antigravity rows in
110
- `snapshots.json` are ignored, not deleted. Doctor JSON no longer has
108
+ - Leftover `${XDG_CONFIG_HOME:-$HOME/.config}/projmux/statusbar-visibility-*antigravity*`
109
+ files, `<state>/usage/antigravity-*.json` sidecars, and cached Antigravity
110
+ rows in `snapshots.json` are ignored, not deleted. Doctor JSON no longer has
111
111
  `statusline_config_path`, and the diagnostic is named `Antigravity hooks`.
112
112
 
113
113
  ### Project snapshots removed
@@ -419,8 +419,9 @@ projmux get projects
419
419
 
420
420
  ### Pins are typed, and migrate on request
421
421
 
422
- **Breaking.** `~/.config/projmux/pins` held one absolute path per line, which
423
- could not say whether the path was a Project. It is now a typed envelope:
422
+ **Breaking.** `${XDG_CONFIG_HOME:-$HOME/.config}/projmux/pins` held one absolute
423
+ path per line, which could not say whether the path was a Project. It is now a
424
+ typed envelope:
424
425
 
425
426
  ```text
426
427
  projmux-pins v2
@@ -586,12 +587,12 @@ The deprecated top-level `projmux init` command and its legacy-only
586
587
 
587
588
  ### Keymap schema migration
588
589
 
589
- `~/.config/projmux/keymap.toml` is now versioned by a root `schema_version`
590
- marker. A file without one is v0 and uses the action ids projmux has always
591
- written; `schema_version = 1` uses canonical dotted ids such as
592
- `window.create` and `project-sidebar.runtime.stop`; `schema_version = 2` keeps
593
- those ids and adds two-to-four-stroke `sequences`. All versions read — the v0
594
- spelling of every action stays a permanent read alias.
590
+ `${XDG_CONFIG_HOME:-$HOME/.config}/projmux/keymap.toml` is now versioned by a
591
+ root `schema_version` marker. A file without one is v0 and uses the action ids
592
+ projmux has always written; `schema_version = 1` uses canonical dotted ids such
593
+ as `window.create` and `project-sidebar.runtime.stop`; `schema_version = 2`
594
+ keeps those ids and adds two-to-four-stroke `sequences`. All versions read — the
595
+ v0 spelling of every action stays a permanent read alias.
595
596
 
596
597
  The migration needs no command of its own. Every installer path ends by running
597
598
  the newly installed binary's `projmux config apply`, which migrates first and only
@@ -616,7 +617,7 @@ fail before the keymap, generated config, or live server changes.
616
617
  **Downgrading to a projmux that predates the schema:** restore the backup first.
617
618
 
618
619
  ```sh
619
- cp ~/.config/projmux/keymap.toml.pre-v1-<digest>.bak ~/.config/projmux/keymap.toml
620
+ cp "${XDG_CONFIG_HOME:-$HOME/.config}/projmux/keymap.toml.pre-v1-<digest>.bak" "${XDG_CONFIG_HOME:-$HOME/.config}/projmux/keymap.toml"
620
621
  ```
621
622
 
622
623
  An older binary reads `schema_version` as an unsupported root key and refuses
@@ -627,7 +628,7 @@ full ordering.
627
628
  ### Pane rename keymap action ID removed
628
629
 
629
630
  The deprecated `rename-pane-topic` keybinding action ID has been removed. If
630
- `~/.config/projmux/keymap.toml` still contains
631
+ `${XDG_CONFIG_HOME:-$HOME/.config}/projmux/keymap.toml` still contains
631
632
  `[bindings.rename-pane-topic]`, rename that table to
632
633
  `[bindings.rename-pane-label]` before running Settings or
633
634
  `projmux config apply`. Projmux now rejects the stale table with that exact
@@ -641,7 +642,8 @@ independent fallback.
641
642
  ### Theme is now global-only
642
643
 
643
644
  Theme is a global user preference. The effective theme resolves from the global
644
- `[theme]` in `~/.config/projmux/config.toml` plus a built-in fallback preset.
645
+ `[theme]` in `${XDG_CONFIG_HOME:-$HOME/.config}/projmux/config.toml` plus a
646
+ built-in fallback preset.
645
647
 
646
648
  If you previously set a `[theme]` section in a project's `.projmux/config.toml`,
647
649
  it is now **deprecated and ignored** — it no longer overrides the global theme
@@ -650,11 +652,11 @@ The project `[theme]` keys are left in the file untouched (no warning, no
650
652
  removal); they simply have no effect.
651
653
 
652
654
  To restore your previous look, copy the values into the global
653
- `~/.config/projmux/config.toml` `[theme]` section, or edit them through
654
- Settings > Theme. Settings no longer exposes a Project theme editor, and the
655
- separate Effective theme view has been merged into the Global theme view: each
656
- token row now shows its resolved value inline, with unset tokens shown as their
657
- dimmed `(fallback)` value.
655
+ `${XDG_CONFIG_HOME:-$HOME/.config}/projmux/config.toml` `[theme]` section, or
656
+ edit them through Settings > Theme. Settings no longer exposes a Project theme
657
+ editor, and the separate Effective theme view has been merged into the Global
658
+ theme view: each token row now shows its resolved value inline, with unset
659
+ tokens shown as their dimmed `(fallback)` value.
658
660
 
659
661
  ### Foreground split
660
662
 
@@ -780,7 +782,7 @@ apply refuses and names the path it found — the active executable is left
780
782
  untouched. The scratch directory is removed on success and on every failure.
781
783
 
782
784
  The command reads `PROJMUX_PROJDIR` from the calling shell and memoizes the
783
- primary path to `~/.config/projmux/projdir`, so the new binary keeps the same
785
+ primary path to `$HOME/.config/projmux/projdir`, so the new binary keeps the same
784
786
  project root context as the one it replaces.
785
787
 
786
788
  To switch the saved project root during the upgrade:
@@ -147,6 +147,17 @@ JSON document keyed by adapter, recording:
147
147
  - per-adapter `last_collect` timestamp (drives the throttle)
148
148
  - per-adapter `Backoff{Until, Consecutive}` (drives the cooldown)
149
149
 
150
+ A persistent `.snapshots.json.lock` next to the file serializes every
151
+ read-modify-write across the processes on one machine. The lock is never held
152
+ during an adapter call. Instead, a throttled refresh records `last_collect`
153
+ for the adapters it is about to walk before it calls them, so concurrent
154
+ refreshes collect each adapter once per throttle window. A refresh that cannot
155
+ take the lock within one second walks nothing and fails with a lock-timeout
156
+ error, which the operations journal records as `failure=state-lock-timeout`
157
+ (see [Collection failures](#collection-failures)). A healthy wait takes milliseconds,
158
+ so repeated rows point at a stuck lock holder. The lock does not coordinate
159
+ machines that share a synced directory.
160
+
150
161
  Adapter failures merge over the prior slice rather than replacing it,
151
162
  so a 429 keeps the last known good rows visible. A *partial* collect — some
152
163
  rows dropped, some kept — still counts as a successful refresh for the models
@@ -334,7 +345,11 @@ When a collection fails, the failure is visible in three places:
334
345
  Codex failure. Status codes, paths, upstream bodies, and
335
346
  credentials never reach the row. Codex rollout fallback records
336
347
  `source=rollout` plus its closed fallback reason; retained data records
337
- `source=last-known-good` plus its closed stale reason. A healthy native
348
+ `source=last-known-good` plus its closed stale reason. A refresh that timed
349
+ out on the snapshot state lock records one `provider=projmux`
350
+ `failure=state-lock-timeout` row at `level=error`, without a source: no
351
+ adapter ran, so no provider owns it, and adapter failures that came with
352
+ the timeout keep their own rows. A healthy native
338
353
  collection writes no row at all. Identical `(provider, source, failure)`
339
354
  tuples are recorded at most once per
340
355
  process run — the same suppression the notify/focus recorder uses — so a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "projmux",
3
- "version": "0.16.0",
3
+ "version": "0.16.1",
4
4
  "description": "tmux project session manager",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/crevissepartners/projmux#readme",
@@ -28,9 +28,9 @@
28
28
  "package:npm:pack": "scripts/package-npm.sh --pack"
29
29
  },
30
30
  "optionalDependencies": {
31
- "@projmux/linux-x64": "0.16.0",
32
- "@projmux/linux-arm64": "0.16.0",
33
- "@projmux/darwin-x64": "0.16.0",
34
- "@projmux/darwin-arm64": "0.16.0"
31
+ "@projmux/linux-x64": "0.16.1",
32
+ "@projmux/linux-arm64": "0.16.1",
33
+ "@projmux/darwin-x64": "0.16.1",
34
+ "@projmux/darwin-arm64": "0.16.1"
35
35
  }
36
36
  }