{"owner":"tmux-python","repo":"tmuxp","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["AGENTS.md",".windsurfrules"],"skills":{"AGENTS.md":"# AGENTS.md\n\nThis file provides guidance to AI agents (e.g., Claude Code, Cursor, and other LLM-powered tools) when working with code in this repository.\n\n## Project Overview\n\ntmuxp is a session manager for tmux that allows users to save and load tmux sessions through YAML/JSON configuration files. It's powered by libtmux and provides a declarative way to manage tmux sessions.\n\n## Development Commands\n\n### Testing\n- `just test` or `uv run py.test` - Run all tests\n- `uv run py.test tests/path/to/test.py::TestClass::test_method` - Run a single test\n- `uv run ptw .` - Continuous test runner with pytest-watcher\n- `uv run ptw . --now --doctest-modules` - Watch tests including doctests\n- `just start` or `just watch-test` - Watch and run tests on file changes\n\n### Code Quality\n- `just ruff` or `uv run ruff check .` - Run linter\n- `uv run ruff check . --fix --show-fixes` - Fix linting issues automatically\n- `just ruff-format` or `uv run ruff format .` - Format code\n- `just mypy` or `uv run mypy` - Run type checking (strict mode enabled)\n- `just watch-ruff` - Watch and lint on changes\n- `just watch-mypy` - Watch and type check on changes\n\n### Documentation\n- `just build-docs` - Build documentation\n- `just serve-docs` - Serve docs locally at http://localhost:8013\n- `just dev-docs` - Watch and serve docs with auto-reload\n- `just start-docs` - Alternative to dev_docs\n\n### CLI Commands\n- `tmuxp load <config>` - Load a tmux session from config\n- `tmuxp load -d <config>` - Load session in detached state\n- `tmuxp freeze <session-name>` - Export running session to config\n- `tmuxp convert <file>` - Convert between YAML and JSON\n- `tmuxp shell` - Interactive Python shell with tmux context\n- `tmuxp debug-info` - Collect system info for debugging\n\n## Architecture\n\n### Core Components\n\n1. **CLI Module** (`src/tmuxp/cli/`): Entry points for all tmuxp commands\n   - `load.py`: Load tmux sessions from config files\n   - `freeze.py`: Export live sessions to config files\n   - `convert.py`: Convert between YAML/JSON formats\n   - `shell.py`: Interactive Python shell with tmux context\n\n2. **Workspace Module** (`src/tmuxp/workspace/`): Core session management\n   - `builder.py`: Builds tmux sessions from configuration\n   - `loader.py`: Loads and validates config files\n   - `finders.py`: Locates workspace config files\n   - `freezer.py`: Exports running sessions to config\n\n3. **Plugin System** (`src/tmuxp/plugin.py`): Extensibility framework\n   - Plugins extend `TmuxpPlugin` base class\n   - Hooks: `before_workspace_builder`, `on_window_create`, `after_window_finished`, `before_script`, `reattach`\n   - Version constraint checking for compatibility\n\n### Configuration Flow\n\n1. Load YAML/JSON config via `ConfigReader` (handles includes, environment variables)\n2. Expand inline shorthand syntax\n3. Trickle down default values (session → window → pane)\n4. Validate configuration structure\n5. Build tmux session via `WorkspaceBuilder`\n\n### Key Patterns\n\n- **Type Safety**: All code uses type hints with mypy strict mode\n- **Error Handling**: Custom exception hierarchy based on `TmuxpException`\n- **Testing**: Pytest with fixtures for tmux server/session/window/pane isolation\n- **Future Imports**: All files use `from __future__ import annotations`\n\n## Configuration Format\n\n```yaml\nsession_name: my-session\nstart_directory: ~/project\nwindows:\n  - window_name: editor\n    layout: main-vertical\n    panes:\n      - shell_command:\n          - vim\n      - shell_command:\n          - git status\n```\n\n## Environment Variables\n\n- `TMUXP_CONFIGDIR`: Custom directory for workspace configs\n- `TMUX_CONF`: Path to tmux configuration file\n- `TMUXP_DEFAULT_COLUMNS/ROWS`: Default session dimensions\n\n## Testing Guidelines\n\n- **Use functional tests only**: Write tests as standalone functions, not classes. Avoid `class TestFoo:` groupings - use descriptive function names and file organization instead.\n- Use pytest fixtures from `tests/fixtures/` for tmux objects\n- Test plugins using mock packages in `tests/fixtures/pluginsystem/`\n- Use `retry_until` utilities for async tmux operations\n- Run single tests with: `uv run py.test tests/file.py::test_function_name`\n- **Use libtmux fixtures**: Prefer `server`, `session`, `window`, `pane` fixtures over manual setup\n- **Avoid mocks when fixtures exist**: Use real tmux fixtures instead of `MagicMock`\n- **Use `tmp_path`** fixture instead of Python's `tempfile`\n- **Use `monkeypatch`** fixture instead of `unittest.mock`\n\n## Code Style\n\n- Follow NumPy-style docstrings (pydocstyle convention)\n- Use ruff for formatting and linting\n- Maintain strict mypy type checking\n- Keep imports organized with future annotations at top\n- **Prefer namespace imports for stdlib**: Use `import enum` and `enum.Enum` instead of `from enum import Enum`; third-party packages may use `from X import Y`\n- **Type imports**: Use `import typing as t` and access via namespace (e.g., `t.Optional`)\n- **Development workflow**: Format → Test → Commit → Lint/Type Check → Test → Final Commit\n\n**Classes with fields** — `NamedTuple`, dataclasses — document every field in\nan `Attributes` section:\n\n```python\nclass SearchToken(t.NamedTuple):\n    \"\"\"Parsed search token with target fields and raw pattern.\n\n    Attributes\n    ----------\n    fields : tuple[str, ...]\n        Canonical field names to search (e.g., ('name', 'session_name')).\n    pattern : str\n        Raw search pattern before regex compilation.\n    \"\"\"\n```\n\nAutodoc renders every field whether or not you describe it, so an\nundocumented `NamedTuple` field ships to the API docs as \"Alias for field\nnumber 0\" and a dataclass field ships bare. Document all of them — a class\nwith three fields and two documented still ships a stub for the third.\n\n## Git Commit Standards\n\nFormat commit messages as:\n```\nScope(type[detail]): concise description\n\nwhy: Explanation of necessity or impact.\n\nwhat:\n- Specific technical changes made\n- Focused on a single topic\n```\n\nKeep the subject ≤50 chars (excluding any trailing `(#NN)` PR ref); wrap\nbody lines at ≤72 chars. Separate the `why:` and `what:` blocks with a\nblank line.\n\nCommon commit types:\n- **feat**: New features or enhancements\n- **fix**: Bug fixes\n- **refactor**: Code restructuring without functional change\n- **docs**: Documentation updates\n- **chore**: Maintenance (dependencies, tooling, config)\n- **test**: Test-related updates\n- **style**: Code style and formatting\n- **py(deps)**: Dependencies\n- **py(deps[dev])**: Dev Dependencies\n- **ai(rules[AGENTS])**: AI rule updates\n- **ai(claude[rules])**: Claude Code rules (CLAUDE.md)\n- **ai(claude[command])**: Claude Code command changes\n\nExample:\n```\nPane(feat[send_keys]): Add support for literal flag\n\nwhy: Enable sending literal characters without tmux interpretation\n\nwhat:\n- Add literal parameter to send_keys method\n- Update send_keys to pass -l flag when literal=True\n- Add tests for literal key sending\n```\n#### Release commits\n\nNever create tags. Never push tags. The user handles tagging and tag\npushes (tags trigger the CI publish workflow).\n\nRelease commit subjects are plain and short: `Tag v<version>`. Put\nthe detailed why/what in the commit body. Don't use the\n`Scope(type[detail]):` format for releases — don't bury the lede.\n\nFor multi-line commits, use heredoc to preserve formatting:\n```bash\ngit commit -m \"$(cat <<'EOF'\nfeat(Component[method]) add feature description\n\nwhy: Explanation of the change.\n\nwhat:\n- First change\n- Second change\nEOF\n)\"\n```\n\n## Logging Standards\n\nThese rules guide future logging changes; existing code may not yet conform.\n\n### Logger setup\n\n- Use `logging.getLogger(__name__)` in every module\n- Add `NullHandler` in library `__init__.py` files\n- Never configure handlers, levels, or formatters in library code — that's the application's job\n\n### Structured context via `extra`\n\nPass structured data on every log call where useful for filtering, searching, or test assertions.\n\n**Core keys** (stable, scalar, safe at any log level):\n\n| Key | Type | Context |\n|-----|------|---------|\n| `tmux_cmd` | `str` | tmux command line |\n| `tmux_subcommand` | `str` | tmux subcommand (e.g. `new-session`) |\n| `tmux_target` | `str` | tmux target specifier (e.g. `mysession:1.2`) |\n| `tmux_exit_code` | `int` | tmux process exit code |\n| `tmux_session` | `str` | session name |\n| `tmux_window` | `str` | window name or index |\n| `tmux_pane` | `str` | pane identifier |\n| `tmux_config_path` | `str` | workspace config file path |\n| `tmux_layout` | `str` | window layout string |\n\n**Heavy/optional keys** (DEBUG only, potentially large):\n\n| Key | Type | Context |\n|-----|------|---------|\n| `tmux_stdout` | `list[str]` | tmux stdout lines (truncate or cap; `%(tmux_stdout)s` produces repr) |\n| `tmux_stderr` | `list[str]` | tmux stderr lines (same caveats) |\n\nTreat established keys as compatibility-sensitive — downstream users may build dashboards and alerts on them. Change deliberately.\n\n### Key naming rules\n\n- `snake_case`, not dotted; `tmux_` prefix\n- Prefer stable scalars; avoid ad-hoc objects\n- Heavy keys (`tmux_stdout`, `tmux_stderr`) are DEBUG-only; consider companion `tmux_stdout_len` fields or hard truncation (e.g. `stdout[:100]`)\n\n### Lazy formatting\n\n`logger.debug(\"msg %s\", val)` not f-strings. Two rationales:\n- Deferred string interpolation: skipped entirely when level is filtered\n- Aggregator message template grouping: `\"Running %s\"` is one signature grouped ×10,000; f-strings make each line unique\n\nWhen computing `val` itself is expensive, guard with `if logger.isEnabledFor(logging.DEBUG)`.\n\n### stacklevel for wrappers\n\nIncrement for each wrapper layer so `%(filename)s:%(lineno)d` and OTel `code.filepath` point to the real caller. Verify whenever call depth changes.\n\n### LoggerAdapter for persistent context\n\nFor objects with stable identity (Session, Window, Pane), use `LoggerAdapter` to avoid repeating the same `extra` on every call. Lead with the portable pattern (override `process()` to merge); `merge_extra=True` simplifies this on Python 3.13+.\n\n### Log levels\n\n| Level | Use for | Examples |\n|-------|---------|----------|\n| `DEBUG` | Internal mechanics, tmux I/O, config expansion | tmux command + stdout, trickle-down steps |\n| `INFO` | Session lifecycle, user-visible operations | Session created, window added, workspace loaded |\n| `WARNING` | Recoverable issues, deprecation, user-actionable config | Deprecated key, missing optional program |\n| `ERROR` | Failures that stop an operation | tmux command failed, config validation error |\n\nConfig discovery noise belongs in `DEBUG`; only surprising/user-actionable config issues → `WARNING`.\n\n### Message style\n\n- Lowercase, past tense for events: `\"session created\"`, `\"tmux command failed\"`\n- No trailing punctuation\n- Keep messages short; put details in `extra`, not the message string\n\n### Exception logging\n\n- Use `logger.exception()` only inside `except` blocks when you are **not** re-raising\n- Use `logger.error(..., exc_info=True)` when you need the traceback outside an `except` block\n- Avoid `logger.exception()` followed by `raise` — this duplicates the traceback. Either add context via `extra` that would otherwise be lost, or let the exception propagate\n\n### Testing logs\n\nAssert on `caplog.records` attributes, not string matching on `caplog.text`:\n- Scope capture: `caplog.at_level(logging.DEBUG, logger=\"libtmux.common\")`\n- Filter records rather than index by position: `[r for r in caplog.records if hasattr(r, \"tmux_cmd\")]`\n- Assert on schema: `record.tmux_exit_code == 0` not `\"exit code 0\" in caplog.text`\n- `caplog.record_tuples` cannot access extra fields — always use `caplog.records`\n\n### Output channels\n\nTwo output channels serve different audiences:\n\n1. **Diagnostics** (`logger.*()` with `extra`): System events for log files, `caplog`, and aggregators. Never styled.\n2. **User-facing output**: What the human sees. Styled via `Colors` class.\n   - Commands with output modes (`--json`/`--ndjson`): prefer `OutputFormatter.emit_text()` from `tmuxp.cli._output` — silenced in non-human modes.\n   - Human-only commands: use `tmuxp_echo()` from `tmuxp.log` (re-exported via `tmuxp.cli.utils`) for user-facing messages.\n   - **Undefined contracts:** Machine-output behavior for error and empty-result paths (e.g., `search` with no matches) is not yet defined. These paths currently emit styled text through `formatter.emit_text()`, which is a no-op in machine modes.\n\nRaw `print()` is forbidden in command/business logic. The `print()` call lives only inside the presenter layer (`_output.py`) or `tmuxp_echo`.\n\n### Avoid\n\n- f-strings/`.format()` in log calls\n- Unguarded logging in hot loops (guard with `isEnabledFor()`)\n- Catch-log-reraise without adding new context\n- `print()` for debugging or internal diagnostics — use `logger.debug()` with structured `extra` instead\n- Logging secret env var values (log key names only)\n- Non-scalar ad-hoc objects in `extra`\n- Requiring custom `extra` fields in format strings without safe defaults (missing keys raise `KeyError`)\n\n## Doctests\n\n**All functions and methods MUST have working doctests.** Doctests serve as both documentation and tests.\n\n**CRITICAL RULES:**\n- Doctests MUST actually execute - never comment out function calls or similar\n- Doctests MUST NOT be converted to `.. code-block::` as a workaround (code-blocks don't run)\n- If you cannot create a working doctest, **STOP and ask for help**\n\n**Available tools for doctests:**\n- `doctest_namespace` fixtures: `server`, `session`, `window`, `pane`, `tmp_path`, `test_utils`\n- Ellipsis for variable output: `# doctest: +ELLIPSIS`\n- Update `conftest.py` to add new fixtures to `doctest_namespace`\n\n**`# doctest: +SKIP` is NOT permitted** - it's just another workaround that doesn't test anything. Use the fixtures properly - tmux is required to run tests anyway.\n\n**Using fixtures in doctests:**\n```python\n>>> from tmuxp.workspace.builder import WorkspaceBuilder\n>>> config = {'session_name': 'test', 'windows': [{'window_name': 'main'}]}\n>>> builder = WorkspaceBuilder(session_config=config, server=server)  # doctest: +ELLIPSIS\n>>> builder.build()\n>>> builder.session.name\n'test'\n```\n\n**When output varies, use ellipsis:**\n```python\n>>> session.session_id  # doctest: +ELLIPSIS\n'$...'\n>>> window.window_id  # doctest: +ELLIPSIS\n'@...'\n```\n\n**Additional guidelines:**\n1. **Use narrative descriptions** for test sections rather than inline comments\n2. **Move complex examples** to dedicated test files at `tests/examples/<path>/test_<example>.py`\n3. **Keep doctests simple and focused** on demonstrating usage\n4. **Add blank lines between test sections** for improved readability\n\n**Doctest exceptions** (patterns where doctests are not required):\n\n1. **Sphinx/docutils `visit_*`/`depart_*` methods** - tested via integration tests; 0 examples across docutils (851 methods), Sphinx (800+), and CPython's `ast.NodeVisitor`\n2. **Sphinx `setup()` functions** - entry points not testable in isolation\n3. **Complex recursive traversal functions** - extract helper predicates instead\n\n**Best practice for node processing**: Extract testable helper functions (like `_is_usage_block()`) and doctest those. Keep complex visitor logic in integration tests.\n\n## Documentation Standards\n\n### Code Blocks\n\nCode blocks are paste-and-run units: pasting one block runs exactly one\nintended action. Doctests and other executed examples are exempt — the test\nsuite runs them, nobody pastes them.\n\n- **One command per block.** Multiple steps may share a block only when\n  explicitly chained with `&&`, `;`, or `\\` continuations — the chain is\n  then one logical command.\n- **Explanations go in prose above the block**, never as `#` comments inside it.\n- **Command menus are per-command blocks with prose lead-ins**, not tables.\n- **Shell commands use the `console` tag with a `$ ` prefix.** This separates\n  interactive commands from scripts and enables prompt-aware copy.\n- **Split long commands with `\\`** — one flag or flag+value pair per indented\n  continuation line, positional arguments last.\n\nGood:\n\nShow the last ten commits as a graph:\n\n```console\n$ git log \\\n    --max-count=10 \\\n    --graph \\\n    --oneline\n```\n\nBad:\n\n```console\n# Show the last ten commits as a graph\n$ git log --max-count=10 --graph --oneline\n```\n\n### Changelog Conventions\n\nThese rules apply when authoring entries in `CHANGES`, which is rendered as the Sphinx changelog page. Modeled on Django's release-notes shape — deliverables get titles and prose, not bullets.\n\n**Release entry boilerplate.** Every release header is `## tmuxp X.Y.Z (YYYY-MM-DD)`. The file opens with a `## tmuxp X.Y.Z (Yet to be released)` placeholder block fenced by `<!-- KEEP THIS PLACEHOLDER ... -->` and `<!-- END PLACEHOLDER ... -->` HTML comments — new release entries land immediately below the END marker, never above it.\n\n**Open with a multi-sentence lead paragraph.** Plain prose, no italic. Open with the version as sentence subject (*\"tmuxp X.Y.Z ships …\"*) so the lead is self-contained when excerpted. Two to four sentences telling the reader what shipped and who cares — user-visible takeaways, not internal mechanism. Cross-reference detail docs with `{ref}` to keep the lead compact.\n\n**Each deliverable is a section, not a bullet.** Inside `### What's new`, every distinct deliverable gets a `#### Deliverable title (#NN)` heading naming it in user vocabulary, followed by 1-3 prose paragraphs explaining what shipped. Don't wrap a paragraph in `- ` — bullets are for enumerable lists, not paragraph containers. Cross-link detail docs (`See {ref}\\`foo\\` for details.`) so prose stays focused.\n\n**The deliverable test.** Before writing an entry, ask: \"What's the deliverable, in user vocabulary?\" If you can't answer in one sentence, the entry isn't ready. Mechanism (helper internals, byte counters, schema-validation locations) belongs in PR descriptions and code comments, not the changelog.\n\n**Fixed subheadings**, in this order when present: `### Breaking changes`, `### Dependencies`, `### What's new`, `### Fixes`, `### Documentation`, `### Development`. Dev tooling (helper scripts, internal automation) lives under `### Development`. For breaking changes, show the migration path with concrete inline code (e.g. a `# Before` / `# After` fenced code block). Dependency floor bumps use the form ``Minimum `pkg>=X.Y.Z` (was `>=X.Y.W`)``.\n\n**PR refs `(#NN)`** sit in each deliverable's `####` heading.\n\n**When bullets are appropriate.** Catch-all sections (`### Fixes`, occasionally `### Documentation`) with 3+ genuinely small items use bullets — one line each, never paragraphs. If a bullet swells past two lines, promote it to a `#### Title (#NN)` heading with prose body.\n\n**Anti-patterns.**\n\n- Fragile metrics: token ceilings, third-party version pins, percent benchmarks, exact byte counts. Describe the *capability*, not the math.\n- Internal jargon: private symbols (leading-underscore identifiers), algorithm names exposed for the first time, backend scaffolding.\n- Walls of text dressed up as bullets.\n- Buried breaking changes — they get their own subheading at the top of the entry.\n\n**Always link autodoc'd APIs.** Any class, method, function, exception, or attribute that has its own rendered page must be cited via the appropriate role (`{class}`, `{meth}`, `{func}`, `{exc}`, `{attr}`) — never with plain backticks. Doc pages without explicit ref labels use `{doc}`. Plain backticks are correct for code syntax, env vars, parameter names, and file paths that aren't doc pages — anything without an autodoc destination.\n\n**MyST roles.** Class references use `{class}` (e.g. `{class}\\`~tmuxp.workspace.builder.WorkspaceBuilder\\``), methods use `{meth}`, functions use `{func}`, exceptions use `{exc}`, attributes use `{attr}`, internal anchors use `{ref}`, doc-path links use `{doc}`.\n\n**Summarization style.** When a user asks \"what changed in the latest version?\" or similar, lead with the entry's lead paragraph (paraphrased if needed), followed by each `####` deliverable heading under `### What's new` with a one-sentence summary. Cite `(#NN)` only if the user asks for source links. Don't invent versions, dates, or numbers not present in `CHANGES`. Don't quote line numbers or file offsets — those shift as the file evolves.\n\n## Important Notes\n\n- **QA every edit**: Run formatting and tests before committing\n- **Minimum Python**: 3.10+ (per pyproject.toml)\n- **Minimum tmux**: 3.2+ (as per README)\n\n## CLI Color Semantics (Revision 1, 2026-01-04)\n\nThe CLI uses semantic colors via the `Colors` class in `src/tmuxp/_internal/colors.py`. Colors are chosen based on **hierarchy level** and **semantic meaning**, not just data type.\n\n### Design Principles\n\n1. **Structural hierarchy**: Headers > Items > Details\n2. **Semantic meaning**: What IS this element?\n3. **Visual weight**: What should draw the eye first?\n4. **Depth separation**: Parent elements should visually contain children\n\nInspired by patterns from **jq** (object keys vs values), **ripgrep** (path/line/match distinction), and **mise/just** (semantic method names).\n\n### Hierarchy-Based Colors\n\n| Level | Element Type | Method | Color | Examples |\n|-------|--------------|--------|-------|----------|\n| **L0** | Section headers | `heading()` | Bright cyan + bold | \"Local workspaces:\", \"Global workspaces:\" |\n| **L1** | Primary content | `highlight()` | Magenta + bold | Workspace names (braintree, .tmuxp) |\n| **L2** | Supplementary info | `info()` | Cyan | Paths (~/.tmuxp, ~/project/.tmuxp.yaml) |\n| **L3** | Metadata/labels | `muted()` | Blue | Source labels (Legacy:, XDG default:) |\n\n### Status-Based Colors (Override hierarchy when applicable)\n\n| Status | Method | Color | Examples |\n|--------|--------|-------|----------|\n| Success/Active | `success()` | Green | \"active\", \"18 workspaces\" |\n| Warning | `warning()` | Yellow | Deprecation notices |\n| Error | `error()` | Red | Error messages |\n\n### Example Output\n\n```\nLocal workspaces:                              ← heading() bright_cyan+bold\n  .tmuxp  ~/work/python/tmuxp/.tmuxp.yaml      ← highlight() + info()\n\nGlobal workspaces (~/.tmuxp):                  ← heading() + info()\n  braintree                                    ← highlight()\n  cihai                                        ← highlight()\n\nGlobal workspace directories:                  ← heading()\n  Legacy: ~/.tmuxp (18 workspaces, active)     ← muted() + info() + success()\n  XDG default: ~/.config/tmuxp (not found)     ← muted() + info() + muted()\n```\n\n### Available Methods\n\n```python\ncolors = Colors()\ncolors.heading(\"Section:\")  # Cyan + bold (section headers)\ncolors.highlight(\"item\")  # Magenta + bold (primary content)\ncolors.info(\"/path/to/file\")  # Cyan (paths, supplementary info)\ncolors.muted(\"label:\")  # Blue (metadata, labels)\ncolors.success(\"ok\")  # Green (success states)\ncolors.warning(\"caution\")  # Yellow (warnings)\ncolors.error(\"failed\")  # Red (errors)\n```\n\n### Key Rules\n\n**Never use the same color for adjacent hierarchy levels.** If headers and items are both blue, they blend together. Each level must be visually distinct.\n\n**Avoid dim/faint styling.** The ANSI dim attribute (`\\x1b[2m`) is too dark to read on black terminal backgrounds. This includes both standard and bright color variants with dim.\n\n**Bold may not render distinctly.** Some terminal/font combinations don't differentiate bold from normal weight. Don't rely on bold alone for visual distinction - pair it with color differences.\n\n## AI Slop Prevention\n\nTreat AI slop as **review-hostile noise**, not as proof that text or\ncode is wrong. The goal is to maximize information density by removing\nartifacts that make the repository harder to trust or navigate.\n\n### The Anti-Slop Rubric\n\nBefore committing, audit all AI-assisted changes for these noise\npatterns:\n\n- **AI Signatures:** Remove \"Generated by\", footers, conversational\n  filler (\"Certainly!\", \"Here is...\"), unexplained emojis (🤖, ✨), and\n  AI-tool metadata.\n- **Brittle References:** Avoid hard-coded line numbers, fragile\n  file/test counts, dated \"as of\" claims, bare SHAs, and local\n  absolute paths unless they are strict evidentiary artifacts (e.g.,\n  benchmark logs).\n- **Diff Narration:** Do not restate what moved, was renamed, or was\n  removed in artifacts the downstream reader holds: code, docstrings,\n  README, CHANGES, PR descriptions, or release notes. The diff and\n  commit message already carry this history.\n- **Branch-Internal Narrative:** Do not mention intermediate branch\n  states, abandoned approaches, or \"no longer\" behavior unless users\n  of a published release actually experienced the old state (**The\n  Published-Release Test**).\n- **Low-Value Scaffolding:** Remove ownerless TODOs (`TODO: revisit`),\n  unused future-proofing, debug artifacts, and defensive wrappers that\n  do not protect a currently reachable failure mode.\n- **Prose Inflation:** Replace generic AI \"tells\" like *comprehensive,\n  robust, seamless, production-ready, leverage, delve, tapestry,* and\n  *best practices* with concrete descriptions of behavior,\n  constraints, or trade-offs.\n- **Coded Labels:** Write rules, options, and findings as plain\n  imperatives. Don't tag them with codes like `[R1]`, `A1`, or\n  `Option B` in artifacts a human reads — the reader shouldn't have to\n  decode an index. Internal agent bookkeeping may use ids; shipped text\n  may not.\n\n### Durable Source Links\n\nLink to a pinned revision, never to trunk. A pinned permalink is not a\nbrittle reference; an unlinked SHA dropped into prose is. `blob/master/…`\nlinks rot silently — the file moves, lines shift, and the anchor lands\non unrelated code while still resolving.\n\n- Prefer a release tag (`blob/v1.4.0/…`). Most durable, and it tells\n  the reader which released version the claim held for.\n- Otherwise use a 7-char commit ref (`blob/9a29b1a/…`) reachable from\n  trunk. Use when there is no tag or the claim is about unreleased\n  code. Never a PR-head SHA — it can be rebased or garbage-collected.\n- Reserve `blob/master/…` for living documents meant to always show the\n  latest state, such as a contributing guide.\n- Line anchors (`#L120-L145`) are only safe on a pinned ref.\n\n### Preservation & Context\n\n**When unsure, leave the text in place and ask.** Subjective cleanup\nmust never be a reason to remove load-bearing rationale.\n\n- **Preserve the \"Why\":** You MUST NOT delete comments that document\n  invariants, protocol constraints, platform quirks, security\n  boundaries, and upstream workarounds.\n- **Evidence is Immune:** Preserve exact counts, dates, and SHAs when\n  they serve as evidence in benchmark results, release notes, stack\n  traces, or lockfiles.\n- **Behavior Over Inventory:** A useful description explains what\n  changed for the *system or user*; it does not provide an inventory\n  of files or functions the diff already shows.\n\n### The Published-Release Test\n\nLong-running branches accumulate tactical decisions — renames,\nrefactors, attempts-then-reverts. When deciding what counts as\nbranch-internal, use trunk or the parent branch as the baseline — not\nintermediate states inside the current branch. Ask:\n\n> Did users of the most recently published release ever experience\n> this old name, old behavior, or bug?\n\nIf the answer is **no**, it is branch-internal narrative. Move it to\nthe commit message and describe only the final state in the artifact.\n\n**Keep in shipped artifacts:**\n- Deprecations and migration guides for symbols that actually shipped.\n- `### Fixes` entries for bugs that affected users of a published\n  release.\n- Comments explaining *why the current code looks this way*\n  (invariants, platform quirks) that make sense to a reader who never\n  saw the previous version.\n\n### Cleanup in Hindsight\n\nWhen applying these rules retroactively from inside a feature branch,\nfirst establish scope by diffing against the parent branch (or trunk)\nto identify which commits this branch actually introduced. Then:\n\n- **In-branch commits:** Prompt the user with two options: `fixup!`\n  commits with `git rebase --autosquash` to address each causal commit\n  at its source, or a single cleanup commit at branch tip.\n- **Trunk/Parent commits:** Default to leaving them alone. Act only on\n  explicit user instruction. If the user opts in, fold the cleanup\n  into a single commit at branch tip; do not rewrite shared history.\n- **Scope guard:** If cleaning prior slop would touch a colleague's\n  work or expand the branch beyond its stated goal, stay in lane:\n  protect the current goal and leave prior slop alone.\n\n### Change Discipline\n\n- Make the smallest coherent change that solves the verified problem;\n  keep unrelated cleanup out of it.\n- Reuse an existing file, component, helper, API, or test before adding\n  a new one. Modify in place when the change fits the file's\n  responsibility.\n- Keep new APIs private until a caller outside the module needs them.\n- Add a file only for a durable boundary — a distinct responsibility,\n  independent reuse, or splitting an oversized high-touch module — not\n  for a single-use helper or a one-line re-export.\n\n### Keep Instructions Lean\n\nTreat this file like code and prune it.\n\n- Delete a line whose removal would not cause a mistake.\n- Move multi-step procedures into skills, path-specific rules into\n  nested AGENTS.md files, and hard limits into hooks or CI.\n- Keep only non-obvious, broadly applicable defaults here. Anything a\n  reader can infer from the code, a manifest, or a linter does not\n  belong.\n",".windsurfrules":"# libtmux Python Project Rules\n\n<project_stack>\n- uv - Python package management and virtual environments\n- ruff - Fast Python linter and formatter\n- py.test - Testing framework\n  - pytest-watcher - Continuous test runner\n- mypy - Static type checking\n- doctest - Testing code examples in documentation\n</project_stack>\n\n<coding_style>\n- Use a consistent coding style throughout the project\n- Format code with ruff before committing\n- Run linting and type checking before finalizing changes\n- Verify tests pass after each significant change\n</coding_style>\n\n<python_docstrings>\n- Use reStructuredText format for all docstrings in src/**/*.py files\n- Keep the main description on the first line after the opening `\"\"\"`\n- Use NumPy docstyle for parameter and return value documentation\n- Format docstrings as follows:\n  ```python\n  \"\"\"Short description of the function or class.\n\n  Detailed description using reStructuredText format.\n\n  Parameters\n  ----------\n  param1 : type\n      Description of param1\n  param2 : type\n      Description of param2\n\n  Returns\n  -------\n  type\n      Description of return value\n  \"\"\"\n  ```\n</python_docstrings>\n\n<python_doctests>\n- Use narrative descriptions for test sections rather than inline comments\n- Format doctests as follows:\n  ```python\n  \"\"\"\n  Examples\n  --------\n  Create an instance:\n\n  >>> obj = ExampleClass()\n  \n  Verify a property:\n  \n  >>> obj.property\n  'expected value'\n  \"\"\"\n  ```\n- Add blank lines between test sections for improved readability\n- Keep doctests simple and focused on demonstrating usage\n- Move complex examples to dedicated test files at tests/examples/<path_to_module>/test_<example>.py\n- Utilize pytest fixtures via doctest_namespace for complex scenarios\n</python_doctests>\n\n<testing_practices>\n- Run tests with `uv run py.test` before committing changes\n- Use pytest-watcher for continuous testing: `uv run ptw . --now --doctest-modules`\n- Fix any test failures before proceeding with additional changes\n</testing_practices>\n\n<git_workflow>\n- Make atomic commits with conventional commit messages\n- Start with an initial commit of functional changes\n- Follow with separate commits for formatting, linting, and type checking fixes\n</git_workflow>\n\n<git_commit_standards>\n- Use the following commit message format:\n  ```\n  Component/File(commit-type[Subcomponent/method]): Concise description\n\n  why: Explanation of necessity or impact.\n  what:\n  - Specific technical changes made\n  - Focused on a single topic\n\n  refs: #issue-number, breaking changes, or relevant links\n  ```\n\n- Common commit types:\n  - **feat**: New features or enhancements\n  - **fix**: Bug fixes\n  - **refactor**: Code restructuring without functional change\n  - **docs**: Documentation updates\n  - **chore**: Maintenance (dependencies, tooling, config)\n  - **test**: Test-related updates\n  - **style**: Code style and formatting\n\n- Prefix Python package changes with:\n  - `py(deps):` for standard packages\n  - `py(deps[dev]):` for development packages\n  - `py(deps[extra]):` for extras/sub-packages\n\n- General guidelines:\n  - Subject line: Maximum 50 characters\n  - Body lines: Maximum 72 characters\n  - Use imperative mood (e.g., \"Add\", \"Fix\", not \"Added\", \"Fixed\")\n  - Limit to one topic per commit\n  - Separate subject from body with a blank line\n  - Mark breaking changes clearly: `BREAKING:`\n</git_commit_standards>\n\n<pytest_testing_guidelines>\n- Use fixtures from conftest.py instead of monkeypatch and MagicMock when available\n- For libtmux tests, use these provided fixtures for fast, efficient tmux resource management:\n  - `server`: Creates a temporary tmux server with isolated socket\n  - `session`: Creates a temporary tmux session in the server\n  - `window`: Creates a temporary tmux window in the session\n  - `pane`: Creates a temporary tmux pane in the pane\n  - `TestServer`: Factory for creating multiple independent servers with unique socket names\n- Example usage with server fixture:\n  ```python\n  def test_something_with_server(server):\n      # server is already running with proper configuration\n      my_session = server.new_session(\"test-session\")\n      assert server.is_alive()\n  ```\n- Example usage with session fixture:\n  ```python\n  def test_something_with_session(session):\n      # session is already created and configured\n      new_window = session.new_window(\"test-window\")\n      assert new_window in session.windows\n  ```\n- Customize session parameters by overriding the session_params fixture:\n  ```python\n  @pytest.fixture\n  def session_params():\n      return {\n          'x': 800,\n          'y': 600,\n          'window_name': 'custom-window'\n      }\n  ```\n- Benefits of using libtmux fixtures:\n  - No need to manually set up and tear down tmux infrastructure\n  - Tests run in isolated tmux environments\n  - Faster test execution\n  - Reliable test environment with predictable configuration\n- Document in test docstrings why standard fixtures weren't used for exceptional cases\n- Use tmp_path (pathlib.Path) fixture over Python's tempfile\n- Use monkeypatch fixture over unittest.mock\n</pytest_testing_guidelines>\n\n<import_guidelines>\n- Prefer namespace imports over importing specific symbols\n- Import modules and access attributes through the namespace:\n  - Use `import enum` and access `enum.Enum` instead of `from enum import Enum`\n  - This applies to standard library modules like pathlib, os, and similar cases\n- For typing, use `import typing as t` and access via the namespace:\n  - Access typing elements as `t.NamedTuple`, `t.TypedDict`, etc.\n  - Note primitive types like unions can be done via `|` pipes\n  - Primitive types like list and dict can be done via `list` and `dict` directly\n- Benefits of namespace imports:\n  - Improves code readability by making the source of symbols clear\n  - Reduces potential naming conflicts\n  - Makes import statements more maintainable\n</import_guidelines>\n"},"files":{"AGENTS.md":"# AGENTS.md\n\nThis file provides guidance to AI agents (e.g., Claude Code, Cursor, and other LLM-powered tools) when working with code in this repository.\n\n## Project Overview\n\ntmuxp is a session manager for tmux that allows users to save and load tmux sessions through YAML/JSON configuration files. It's powered by libtmux and provides a declarative way to manage tmux sessions.\n\n## Development Commands\n\n### Testing\n- `just test` or `uv run py.test` - Run all tests\n- `uv run py.test tests/path/to/test.py::TestClass::test_method` - Run a single test\n- `uv run ptw .` - Continuous test runner with pytest-watcher\n- `uv run ptw . --now --doctest-modules` - Watch tests including doctests\n- `just start` or `just watch-test` - Watch and run tests on file changes\n\n### Code Quality\n- `just ruff` or `uv run ruff check .` - Run linter\n- `uv run ruff check . --fix --show-fixes` - Fix linting issues automatically\n- `just ruff-format` or `uv run ruff format .` - Format code\n- `just mypy` or `uv run mypy` - Run type checking (strict mode enabled)\n- `just watch-ruff` - Watch and lint on changes\n- `just watch-mypy` - Watch and type check on changes\n\n### Documentation\n- `just build-docs` - Build documentation\n- `just serve-docs` - Serve docs locally at http://localhost:8013\n- `just dev-docs` - Watch and serve docs with auto-reload\n- `just start-docs` - Alternative to dev_docs\n\n### CLI Commands\n- `tmuxp load <config>` - Load a tmux session from config\n- `tmuxp load -d <config>` - Load session in detached state\n- `tmuxp freeze <session-name>` - Export running session to config\n- `tmuxp convert <file>` - Convert between YAML and JSON\n- `tmuxp shell` - Interactive Python shell with tmux context\n- `tmuxp debug-info` - Collect system info for debugging\n\n## Architecture\n\n### Core Components\n\n1. **CLI Module** (`src/tmuxp/cli/`): Entry points for all tmuxp commands\n   - `load.py`: Load tmux sessions from config files\n   - `freeze.py`: Export live sessions to config files\n   - `convert.py`: Convert between YAML/JSON formats\n   - `shell.py`: Interactive Python shell with tmux context\n\n2. **Workspace Module** (`src/tmuxp/workspace/`): Core session management\n   - `builder.py`: Builds tmux sessions from configuration\n   - `loader.py`: Loads and validates config files\n   - `finders.py`: Locates workspace config files\n   - `freezer.py`: Exports running sessions to config\n\n3. **Plugin System** (`src/tmuxp/plugin.py`): Extensibility framework\n   - Plugins extend `TmuxpPlugin` base class\n   - Hooks: `before_workspace_builder`, `on_window_create`, `after_window_finished`, `before_script`, `reattach`\n   - Version constraint checking for compatibility\n\n### Configuration Flow\n\n1. Load YAML/JSON config via `ConfigReader` (handles includes, environment variables)\n2. Expand inline shorthand syntax\n3. Trickle down default values (session → window → pane)\n4. Validate configuration structure\n5. Build tmux session via `WorkspaceBuilder`\n\n### Key Patterns\n\n- **Type Safety**: All code uses type hints with mypy strict mode\n- **Error Handling**: Custom exception hierarchy based on `TmuxpException`\n- **Testing**: Pytest with fixtures for tmux server/session/window/pane isolation\n- **Future Imports**: All files use `from __future__ import annotations`\n\n## Configuration Format\n\n```yaml\nsession_name: my-session\nstart_directory: ~/project\nwindows:\n  - window_name: editor\n    layout: main-vertical\n    panes:\n      - shell_command:\n          - vim\n      - shell_command:\n          - git status\n```\n\n## Environment Variables\n\n- `TMUXP_CONFIGDIR`: Custom directory for workspace configs\n- `TMUX_CONF`: Path to tmux configuration file\n- `TMUXP_DEFAULT_COLUMNS/ROWS`: Default session dimensions\n\n## Testing Guidelines\n\n- **Use functional tests only**: Write tests as standalone functions, not classes. Avoid `class TestFoo:` groupings - use descriptive function names and file organization instead.\n- Use pytest fixtures from `tests/fixtures/` for tmux objects\n- Test plugins using mock packages in `tests/fixtures/pluginsystem/`\n- Use `retry_until` utilities for async tmux operations\n- Run single tests with: `uv run py.test tests/file.py::test_function_name`\n- **Use libtmux fixtures**: Prefer `server`, `session`, `window`, `pane` fixtures over manual setup\n- **Avoid mocks when fixtures exist**: Use real tmux fixtures instead of `MagicMock`\n- **Use `tmp_path`** fixture instead of Python's `tempfile`\n- **Use `monkeypatch`** fixture instead of `unittest.mock`\n\n## Code Style\n\n- Follow NumPy-style docstrings (pydocstyle convention)\n- Use ruff for formatting and linting\n- Maintain strict mypy type checking\n- Keep imports organized with future annotations at top\n- **Prefer namespace imports for stdlib**: Use `import enum` and `enum.Enum` instead of `from enum import Enum`; third-party packages may use `from X import Y`\n- **Type imports**: Use `import typing as t` and access via namespace (e.g., `t.Optional`)\n- **Development workflow**: Format → Test → Commit → Lint/Type Check → Test → Final Commit\n\n**Classes with fields** — `NamedTuple`, dataclasses — document every field in\nan `Attributes` section:\n\n```python\nclass SearchToken(t.NamedTuple):\n    \"\"\"Parsed search token with target fields and raw pattern.\n\n    Attributes\n    ----------\n    fields : tuple[str, ...]\n        Canonical field names to search (e.g., ('name', 'session_name')).\n    pattern : str\n        Raw search pattern before regex compilation.\n    \"\"\"\n```\n\nAutodoc renders every field whether or not you describe it, so an\nundocumented `NamedTuple` field ships to the API docs as \"Alias for field\nnumber 0\" and a dataclass field ships bare. Document all of them — a class\nwith three fields and two documented still ships a stub for the third.\n\n## Git Commit Standards\n\nFormat commit messages as:\n```\nScope(type[detail]): concise description\n\nwhy: Explanation of necessity or impact.\n\nwhat:\n- Specific technical changes made\n- Focused on a single topic\n```\n\nKeep the subject ≤50 chars (excluding any trailing `(#NN)` PR ref); wrap\nbody lines at ≤72 chars. Separate the `why:` and `what:` blocks with a\nblank line.\n\nCommon commit types:\n- **feat**: New features or enhancements\n- **fix**: Bug fixes\n- **refactor**: Code restructuring without functional change\n- **docs**: Documentation updates\n- **chore**: Maintenance (dependencies, tooling, config)\n- **test**: Test-related updates\n- **style**: Code style and formatting\n- **py(deps)**: Dependencies\n- **py(deps[dev])**: Dev Dependencies\n- **ai(rules[AGENTS])**: AI rule updates\n- **ai(claude[rules])**: Claude Code rules (CLAUDE.md)\n- **ai(claude[command])**: Claude Code command changes\n\nExample:\n```\nPane(feat[send_keys]): Add support for literal flag\n\nwhy: Enable sending literal characters without tmux interpretation\n\nwhat:\n- Add literal parameter to send_keys method\n- Update send_keys to pass -l flag when literal=True\n- Add tests for literal key sending\n```\n#### Release commits\n\nNever create tags. Never push tags. The user handles tagging and tag\npushes (tags trigger the CI publish workflow).\n\nRelease commit subjects are plain and short: `Tag v<version>`. Put\nthe detailed why/what in the commit body. Don't use the\n`Scope(type[detail]):` format for releases — don't bury the lede.\n\nFor multi-line commits, use heredoc to preserve formatting:\n```bash\ngit commit -m \"$(cat <<'EOF'\nfeat(Component[method]) add feature description\n\nwhy: Explanation of the change.\n\nwhat:\n- First change\n- Second change\nEOF\n)\"\n```\n\n## Logging Standards\n\nThese rules guide future logging changes; existing code may not yet conform.\n\n### Logger setup\n\n- Use `logging.getLogger(__name__)` in every module\n- Add `NullHandler` in library `__init__.py` files\n- Never configure handlers, levels, or formatters in library code — that's the application's job\n\n### Structured context via `extra`\n\nPass structured data on every log call where useful for filtering, searching, or test assertions.\n\n**Core keys** (stable, scalar, safe at any log level):\n\n| Key | Type | Context |\n|-----|------|---------|\n| `tmux_cmd` | `str` | tmux command line |\n| `tmux_subcommand` | `str` | tmux subcommand (e.g. `new-session`) |\n| `tmux_target` | `str` | tmux target specifier (e.g. `mysession:1.2`) |\n| `tmux_exit_code` | `int` | tmux process exit code |\n| `tmux_session` | `str` | session name |\n| `tmux_window` | `str` | window name or index |\n| `tmux_pane` | `str` | pane identifier |\n| `tmux_config_path` | `str` | workspace config file path |\n| `tmux_layout` | `str` | window layout string |\n\n**Heavy/optional keys** (DEBUG only, potentially large):\n\n| Key | Type | Context |\n|-----|------|---------|\n| `tmux_stdout` | `list[str]` | tmux stdout lines (truncate or cap; `%(tmux_stdout)s` produces repr) |\n| `tmux_stderr` | `list[str]` | tmux stderr lines (same caveats) |\n\nTreat established keys as compatibility-sensitive — downstream users may build dashboards and alerts on them. Change deliberately.\n\n### Key naming rules\n\n- `snake_case`, not dotted; `tmux_` prefix\n- Prefer stable scalars; avoid ad-hoc objects\n- Heavy keys (`tmux_stdout`, `tmux_stderr`) are DEBUG-only; consider companion `tmux_stdout_len` fields or hard truncation (e.g. `stdout[:100]`)\n\n### Lazy formatting\n\n`logger.debug(\"msg %s\", val)` not f-strings. Two rationales:\n- Deferred string interpolation: skipped entirely when level is filtered\n- Aggregator message template grouping: `\"Running %s\"` is one signature grouped ×10,000; f-strings make each line unique\n\nWhen computing `val` itself is expensive, guard with `if logger.isEnabledFor(logging.DEBUG)`.\n\n### stacklevel for wrappers\n\nIncrement for each wrapper layer so `%(filename)s:%(lineno)d` and OTel `code.filepath` point to the real caller. Verify whenever call depth changes.\n\n### LoggerAdapter for persistent context\n\nFor objects with stable identity (Session, Window, Pane), use `LoggerAdapter` to avoid repeating the same `extra` on every call. Lead with the portable pattern (override `process()` to merge); `merge_extra=True` simplifies this on Python 3.13+.\n\n### Log levels\n\n| Level | Use for | Examples |\n|-------|---------|----------|\n| `DEBUG` | Internal mechanics, tmux I/O, config expansion | tmux command + stdout, trickle-down steps |\n| `INFO` | Session lifecycle, user-visible operations | Session created, window added, workspace loaded |\n| `WARNING` | Recoverable issues, deprecation, user-actionable config | Deprecated key, missing optional program |\n| `ERROR` | Failures that stop an operation | tmux command failed, config validation error |\n\nConfig discovery noise belongs in `DEBUG`; only surprising/user-actionable config issues → `WARNING`.\n\n### Message style\n\n- Lowercase, past tense for events: `\"session created\"`, `\"tmux command failed\"`\n- No trailing punctuation\n- Keep messages short; put details in `extra`, not the message string\n\n### Exception logging\n\n- Use `logger.exception()` only inside `except` blocks when you are **not** re-raising\n- Use `logger.error(..., exc_info=True)` when you need the traceback outside an `except` block\n- Avoid `logger.exception()` followed by `raise` — this duplicates the traceback. Either add context via `extra` that would otherwise be lost, or let the exception propagate\n\n### Testing logs\n\nAssert on `caplog.records` attributes, not string matching on `caplog.text`:\n- Scope capture: `caplog.at_level(logging.DEBUG, logger=\"libtmux.common\")`\n- Filter records rather than index by position: `[r for r in caplog.records if hasattr(r, \"tmux_cmd\")]`\n- Assert on schema: `record.tmux_exit_code == 0` not `\"exit code 0\" in caplog.text`\n- `caplog.record_tuples` cannot access extra fields — always use `caplog.records`\n\n### Output channels\n\nTwo output channels serve different audiences:\n\n1. **Diagnostics** (`logger.*()` with `extra`): System events for log files, `caplog`, and aggregators. Never styled.\n2. **User-facing output**: What the human sees. Styled via `Colors` class.\n   - Commands with output modes (`--json`/`--ndjson`): prefer `OutputFormatter.emit_text()` from `tmuxp.cli._output` — silenced in non-human modes.\n   - Human-only commands: use `tmuxp_echo()` from `tmuxp.log` (re-exported via `tmuxp.cli.utils`) for user-facing messages.\n   - **Undefined contracts:** Machine-output behavior for error and empty-result paths (e.g., `search` with no matches) is not yet defined. These paths currently emit styled text through `formatter.emit_text()`, which is a no-op in machine modes.\n\nRaw `print()` is forbidden in command/business logic. The `print()` call lives only inside the presenter layer (`_output.py`) or `tmuxp_echo`.\n\n### Avoid\n\n- f-strings/`.format()` in log calls\n- Unguarded logging in hot loops (guard with `isEnabledFor()`)\n- Catch-log-reraise without adding new context\n- `print()` for debugging or internal diagnostics — use `logger.debug()` with structured `extra` instead\n- Logging secret env var values (log key names only)\n- Non-scalar ad-hoc objects in `extra`\n- Requiring custom `extra` fields in format strings without safe defaults (missing keys raise `KeyError`)\n\n## Doctests\n\n**All functions and methods MUST have working doctests.** Doctests serve as both documentation and tests.\n\n**CRITICAL RULES:**\n- Doctests MUST actually execute - never comment out function calls or similar\n- Doctests MUST NOT be converted to `.. code-block::` as a workaround (code-blocks don't run)\n- If you cannot create a working doctest, **STOP and ask for help**\n\n**Available tools for doctests:**\n- `doctest_namespace` fixtures: `server`, `session`, `window`, `pane`, `tmp_path`, `test_utils`\n- Ellipsis for variable output: `# doctest: +ELLIPSIS`\n- Update `conftest.py` to add new fixtures to `doctest_namespace`\n\n**`# doctest: +SKIP` is NOT permitted** - it's just another workaround that doesn't test anything. Use the fixtures properly - tmux is required to run tests anyway.\n\n**Using fixtures in doctests:**\n```python\n>>> from tmuxp.workspace.builder import WorkspaceBuilder\n>>> config = {'session_name': 'test', 'windows': [{'window_name': 'main'}]}\n>>> builder = WorkspaceBuilder(session_config=config, server=server)  # doctest: +ELLIPSIS\n>>> builder.build()\n>>> builder.session.name\n'test'\n```\n\n**When output varies, use ellipsis:**\n```python\n>>> session.session_id  # doctest: +ELLIPSIS\n'$...'\n>>> window.window_id  # doctest: +ELLIPSIS\n'@...'\n```\n\n**Additional guidelines:**\n1. **Use narrative descriptions** for test sections rather than inline comments\n2. **Move complex examples** to dedicated test files at `tests/examples/<path>/test_<example>.py`\n3. **Keep doctests simple and focused** on demonstrating usage\n4. **Add blank lines between test sections** for improved readability\n\n**Doctest exceptions** (patterns where doctests are not required):\n\n1. **Sphinx/docutils `visit_*`/`depart_*` methods** - tested via integration tests; 0 examples across docutils (851 methods), Sphinx (800+), and CPython's `ast.NodeVisitor`\n2. **Sphinx `setup()` functions** - entry points not testable in isolation\n3. **Complex recursive traversal functions** - extract helper predicates instead\n\n**Best practice for node processing**: Extract testable helper functions (like `_is_usage_block()`) and doctest those. Keep complex visitor logic in integration tests.\n\n## Documentation Standards\n\n### Code Blocks\n\nCode blocks are paste-and-run units: pasting one block runs exactly one\nintended action. Doctests and other executed examples are exempt — the test\nsuite runs them, nobody pastes them.\n\n- **One command per block.** Multiple steps may share a block only when\n  explicitly chained with `&&`, `;`, or `\\` continuations — the chain is\n  then one logical command.\n- **Explanations go in prose above the block**, never as `#` comments inside it.\n- **Command menus are per-command blocks with prose lead-ins**, not tables.\n- **Shell commands use the `console` tag with a `$ ` prefix.** This separates\n  interactive commands from scripts and enables prompt-aware copy.\n- **Split long commands with `\\`** — one flag or flag+value pair per indented\n  continuation line, positional arguments last.\n\nGood:\n\nShow the last ten commits as a graph:\n\n```console\n$ git log \\\n    --max-count=10 \\\n    --graph \\\n    --oneline\n```\n\nBad:\n\n```console\n# Show the last ten commits as a graph\n$ git log --max-count=10 --graph --oneline\n```\n\n### Changelog Conventions\n\nThese rules apply when authoring entries in `CHANGES`, which is rendered as the Sphinx changelog page. Modeled on Django's release-notes shape — deliverables get titles and prose, not bullets.\n\n**Release entry boilerplate.** Every release header is `## tmuxp X.Y.Z (YYYY-MM-DD)`. The file opens with a `## tmuxp X.Y.Z (Yet to be released)` placeholder block fenced by `<!-- KEEP THIS PLACEHOLDER ... -->` and `<!-- END PLACEHOLDER ... -->` HTML comments — new release entries land immediately below the END marker, never above it.\n\n**Open with a multi-sentence lead paragraph.** Plain prose, no italic. Open with the version as sentence subject (*\"tmuxp X.Y.Z ships …\"*) so the lead is self-contained when excerpted. Two to four sentences telling the reader what shipped and who cares — user-visible takeaways, not internal mechanism. Cross-reference detail docs with `{ref}` to keep the lead compact.\n\n**Each deliverable is a section, not a bullet.** Inside `### What's new`, every distinct deliverable gets a `#### Deliverable title (#NN)` heading naming it in user vocabulary, followed by 1-3 prose paragraphs explaining what shipped. Don't wrap a paragraph in `- ` — bullets are for enumerable lists, not paragraph containers. Cross-link detail docs (`See {ref}\\`foo\\` for details.`) so prose stays focused.\n\n**The deliverable test.** Before writing an entry, ask: \"What's the deliverable, in user vocabulary?\" If you can't answer in one sentence, the entry isn't ready. Mechanism (helper internals, byte counters, schema-validation locations) belongs in PR descriptions and code comments, not the changelog.\n\n**Fixed subheadings**, in this order when present: `### Breaking changes`, `### Dependencies`, `### What's new`, `### Fixes`, `### Documentation`, `### Development`. Dev tooling (helper scripts, internal automation) lives under `### Development`. For breaking changes, show the migration path with concrete inline code (e.g. a `# Before` / `# After` fenced code block). Dependency floor bumps use the form ``Minimum `pkg>=X.Y.Z` (was `>=X.Y.W`)``.\n\n**PR refs `(#NN)`** sit in each deliverable's `####` heading.\n\n**When bullets are appropriate.** Catch-all sections (`### Fixes`, occasionally `### Documentation`) with 3+ genuinely small items use bullets — one line each, never paragraphs. If a bullet swells past two lines, promote it to a `#### Title (#NN)` heading with prose body.\n\n**Anti-patterns.**\n\n- Fragile metrics: token ceilings, third-party version pins, percent benchmarks, exact byte counts. Describe the *capability*, not the math.\n- Internal jargon: private symbols (leading-underscore identifiers), algorithm names exposed for the first time, backend scaffolding.\n- Walls of text dressed up as bullets.\n- Buried breaking changes — they get their own subheading at the top of the entry.\n\n**Always link autodoc'd APIs.** Any class, method, function, exception, or attribute that has its own rendered page must be cited via the appropriate role (`{class}`, `{meth}`, `{func}`, `{exc}`, `{attr}`) — never with plain backticks. Doc pages without explicit ref labels use `{doc}`. Plain backticks are correct for code syntax, env vars, parameter names, and file paths that aren't doc pages — anything without an autodoc destination.\n\n**MyST roles.** Class references use `{class}` (e.g. `{class}\\`~tmuxp.workspace.builder.WorkspaceBuilder\\``), methods use `{meth}`, functions use `{func}`, exceptions use `{exc}`, attributes use `{attr}`, internal anchors use `{ref}`, doc-path links use `{doc}`.\n\n**Summarization style.** When a user asks \"what changed in the latest version?\" or similar, lead with the entry's lead paragraph (paraphrased if needed), followed by each `####` deliverable heading under `### What's new` with a one-sentence summary. Cite `(#NN)` only if the user asks for source links. Don't invent versions, dates, or numbers not present in `CHANGES`. Don't quote line numbers or file offsets — those shift as the file evolves.\n\n## Important Notes\n\n- **QA every edit**: Run formatting and tests before committing\n- **Minimum Python**: 3.10+ (per pyproject.toml)\n- **Minimum tmux**: 3.2+ (as per README)\n\n## CLI Color Semantics (Revision 1, 2026-01-04)\n\nThe CLI uses semantic colors via the `Colors` class in `src/tmuxp/_internal/colors.py`. Colors are chosen based on **hierarchy level** and **semantic meaning**, not just data type.\n\n### Design Principles\n\n1. **Structural hierarchy**: Headers > Items > Details\n2. **Semantic meaning**: What IS this element?\n3. **Visual weight**: What should draw the eye first?\n4. **Depth separation**: Parent elements should visually contain children\n\nInspired by patterns from **jq** (object keys vs values), **ripgrep** (path/line/match distinction), and **mise/just** (semantic method names).\n\n### Hierarchy-Based Colors\n\n| Level | Element Type | Method | Color | Examples |\n|-------|--------------|--------|-------|----------|\n| **L0** | Section headers | `heading()` | Bright cyan + bold | \"Local workspaces:\", \"Global workspaces:\" |\n| **L1** | Primary content | `highlight()` | Magenta + bold | Workspace names (braintree, .tmuxp) |\n| **L2** | Supplementary info | `info()` | Cyan | Paths (~/.tmuxp, ~/project/.tmuxp.yaml) |\n| **L3** | Metadata/labels | `muted()` | Blue | Source labels (Legacy:, XDG default:) |\n\n### Status-Based Colors (Override hierarchy when applicable)\n\n| Status | Method | Color | Examples |\n|--------|--------|-------|----------|\n| Success/Active | `success()` | Green | \"active\", \"18 workspaces\" |\n| Warning | `warning()` | Yellow | Deprecation notices |\n| Error | `error()` | Red | Error messages |\n\n### Example Output\n\n```\nLocal workspaces:                              ← heading() bright_cyan+bold\n  .tmuxp  ~/work/python/tmuxp/.tmuxp.yaml      ← highlight() + info()\n\nGlobal workspaces (~/.tmuxp):                  ← heading() + info()\n  braintree                                    ← highlight()\n  cihai                                        ← highlight()\n\nGlobal workspace directories:                  ← heading()\n  Legacy: ~/.tmuxp (18 workspaces, active)     ← muted() + info() + success()\n  XDG default: ~/.config/tmuxp (not found)     ← muted() + info() + muted()\n```\n\n### Available Methods\n\n```python\ncolors = Colors()\ncolors.heading(\"Section:\")  # Cyan + bold (section headers)\ncolors.highlight(\"item\")  # Magenta + bold (primary content)\ncolors.info(\"/path/to/file\")  # Cyan (paths, supplementary info)\ncolors.muted(\"label:\")  # Blue (metadata, labels)\ncolors.success(\"ok\")  # Green (success states)\ncolors.warning(\"caution\")  # Yellow (warnings)\ncolors.error(\"failed\")  # Red (errors)\n```\n\n### Key Rules\n\n**Never use the same color for adjacent hierarchy levels.** If headers and items are both blue, they blend together. Each level must be visually distinct.\n\n**Avoid dim/faint styling.** The ANSI dim attribute (`\\x1b[2m`) is too dark to read on black terminal backgrounds. This includes both standard and bright color variants with dim.\n\n**Bold may not render distinctly.** Some terminal/font combinations don't differentiate bold from normal weight. Don't rely on bold alone for visual distinction - pair it with color differences.\n\n## AI Slop Prevention\n\nTreat AI slop as **review-hostile noise**, not as proof that text or\ncode is wrong. The goal is to maximize information density by removing\nartifacts that make the repository harder to trust or navigate.\n\n### The Anti-Slop Rubric\n\nBefore committing, audit all AI-assisted changes for these noise\npatterns:\n\n- **AI Signatures:** Remove \"Generated by\", footers, conversational\n  filler (\"Certainly!\", \"Here is...\"), unexplained emojis (🤖, ✨), and\n  AI-tool metadata.\n- **Brittle References:** Avoid hard-coded line numbers, fragile\n  file/test counts, dated \"as of\" claims, bare SHAs, and local\n  absolute paths unless they are strict evidentiary artifacts (e.g.,\n  benchmark logs).\n- **Diff Narration:** Do not restate what moved, was renamed, or was\n  removed in artifacts the downstream reader holds: code, docstrings,\n  README, CHANGES, PR descriptions, or release notes. The diff and\n  commit message already carry this history.\n- **Branch-Internal Narrative:** Do not mention intermediate branch\n  states, abandoned approaches, or \"no longer\" behavior unless users\n  of a published release actually experienced the old state (**The\n  Published-Release Test**).\n- **Low-Value Scaffolding:** Remove ownerless TODOs (`TODO: revisit`),\n  unused future-proofing, debug artifacts, and defensive wrappers that\n  do not protect a currently reachable failure mode.\n- **Prose Inflation:** Replace generic AI \"tells\" like *comprehensive,\n  robust, seamless, production-ready, leverage, delve, tapestry,* and\n  *best practices* with concrete descriptions of behavior,\n  constraints, or trade-offs.\n- **Coded Labels:** Write rules, options, and findings as plain\n  imperatives. Don't tag them with codes like `[R1]`, `A1`, or\n  `Option B` in artifacts a human reads — the reader shouldn't have to\n  decode an index. Internal agent bookkeeping may use ids; shipped text\n  may not.\n\n### Durable Source Links\n\nLink to a pinned revision, never to trunk. A pinned permalink is not a\nbrittle reference; an unlinked SHA dropped into prose is. `blob/master/…`\nlinks rot silently — the file moves, lines shift, and the anchor lands\non unrelated code while still resolving.\n\n- Prefer a release tag (`blob/v1.4.0/…`). Most durable, and it tells\n  the reader which released version the claim held for.\n- Otherwise use a 7-char commit ref (`blob/9a29b1a/…`) reachable from\n  trunk. Use when there is no tag or the claim is about unreleased\n  code. Never a PR-head SHA — it can be rebased or garbage-collected.\n- Reserve `blob/master/…` for living documents meant to always show the\n  latest state, such as a contributing guide.\n- Line anchors (`#L120-L145`) are only safe on a pinned ref.\n\n### Preservation & Context\n\n**When unsure, leave the text in place and ask.** Subjective cleanup\nmust never be a reason to remove load-bearing rationale.\n\n- **Preserve the \"Why\":** You MUST NOT delete comments that document\n  invariants, protocol constraints, platform quirks, security\n  boundaries, and upstream workarounds.\n- **Evidence is Immune:** Preserve exact counts, dates, and SHAs when\n  they serve as evidence in benchmark results, release notes, stack\n  traces, or lockfiles.\n- **Behavior Over Inventory:** A useful description explains what\n  changed for the *system or user*; it does not provide an inventory\n  of files or functions the diff already shows.\n\n### The Published-Release Test\n\nLong-running branches accumulate tactical decisions — renames,\nrefactors, attempts-then-reverts. When deciding what counts as\nbranch-internal, use trunk or the parent branch as the baseline — not\nintermediate states inside the current branch. Ask:\n\n> Did users of the most recently published release ever experience\n> this old name, old behavior, or bug?\n\nIf the answer is **no**, it is branch-internal narrative. Move it to\nthe commit message and describe only the final state in the artifact.\n\n**Keep in shipped artifacts:**\n- Deprecations and migration guides for symbols that actually shipped.\n- `### Fixes` entries for bugs that affected users of a published\n  release.\n- Comments explaining *why the current code looks this way*\n  (invariants, platform quirks) that make sense to a reader who never\n  saw the previous version.\n\n### Cleanup in Hindsight\n\nWhen applying these rules retroactively from inside a feature branch,\nfirst establish scope by diffing against the parent branch (or trunk)\nto identify which commits this branch actually introduced. Then:\n\n- **In-branch commits:** Prompt the user with two options: `fixup!`\n  commits with `git rebase --autosquash` to address each causal commit\n  at its source, or a single cleanup commit at branch tip.\n- **Trunk/Parent commits:** Default to leaving them alone. Act only on\n  explicit user instruction. If the user opts in, fold the cleanup\n  into a single commit at branch tip; do not rewrite shared history.\n- **Scope guard:** If cleaning prior slop would touch a colleague's\n  work or expand the branch beyond its stated goal, stay in lane:\n  protect the current goal and leave prior slop alone.\n\n### Change Discipline\n\n- Make the smallest coherent change that solves the verified problem;\n  keep unrelated cleanup out of it.\n- Reuse an existing file, component, helper, API, or test before adding\n  a new one. Modify in place when the change fits the file's\n  responsibility.\n- Keep new APIs private until a caller outside the module needs them.\n- Add a file only for a durable boundary — a distinct responsibility,\n  independent reuse, or splitting an oversized high-touch module — not\n  for a single-use helper or a one-line re-export.\n\n### Keep Instructions Lean\n\nTreat this file like code and prune it.\n\n- Delete a line whose removal would not cause a mistake.\n- Move multi-step procedures into skills, path-specific rules into\n  nested AGENTS.md files, and hard limits into hooks or CI.\n- Keep only non-obvious, broadly applicable defaults here. Anything a\n  reader can infer from the code, a manifest, or a linter does not\n  belong.\n",".windsurfrules":"# libtmux Python Project Rules\n\n<project_stack>\n- uv - Python package management and virtual environments\n- ruff - Fast Python linter and formatter\n- py.test - Testing framework\n  - pytest-watcher - Continuous test runner\n- mypy - Static type checking\n- doctest - Testing code examples in documentation\n</project_stack>\n\n<coding_style>\n- Use a consistent coding style throughout the project\n- Format code with ruff before committing\n- Run linting and type checking before finalizing changes\n- Verify tests pass after each significant change\n</coding_style>\n\n<python_docstrings>\n- Use reStructuredText format for all docstrings in src/**/*.py files\n- Keep the main description on the first line after the opening `\"\"\"`\n- Use NumPy docstyle for parameter and return value documentation\n- Format docstrings as follows:\n  ```python\n  \"\"\"Short description of the function or class.\n\n  Detailed description using reStructuredText format.\n\n  Parameters\n  ----------\n  param1 : type\n      Description of param1\n  param2 : type\n      Description of param2\n\n  Returns\n  -------\n  type\n      Description of return value\n  \"\"\"\n  ```\n</python_docstrings>\n\n<python_doctests>\n- Use narrative descriptions for test sections rather than inline comments\n- Format doctests as follows:\n  ```python\n  \"\"\"\n  Examples\n  --------\n  Create an instance:\n\n  >>> obj = ExampleClass()\n  \n  Verify a property:\n  \n  >>> obj.property\n  'expected value'\n  \"\"\"\n  ```\n- Add blank lines between test sections for improved readability\n- Keep doctests simple and focused on demonstrating usage\n- Move complex examples to dedicated test files at tests/examples/<path_to_module>/test_<example>.py\n- Utilize pytest fixtures via doctest_namespace for complex scenarios\n</python_doctests>\n\n<testing_practices>\n- Run tests with `uv run py.test` before committing changes\n- Use pytest-watcher for continuous testing: `uv run ptw . --now --doctest-modules`\n- Fix any test failures before proceeding with additional changes\n</testing_practices>\n\n<git_workflow>\n- Make atomic commits with conventional commit messages\n- Start with an initial commit of functional changes\n- Follow with separate commits for formatting, linting, and type checking fixes\n</git_workflow>\n\n<git_commit_standards>\n- Use the following commit message format:\n  ```\n  Component/File(commit-type[Subcomponent/method]): Concise description\n\n  why: Explanation of necessity or impact.\n  what:\n  - Specific technical changes made\n  - Focused on a single topic\n\n  refs: #issue-number, breaking changes, or relevant links\n  ```\n\n- Common commit types:\n  - **feat**: New features or enhancements\n  - **fix**: Bug fixes\n  - **refactor**: Code restructuring without functional change\n  - **docs**: Documentation updates\n  - **chore**: Maintenance (dependencies, tooling, config)\n  - **test**: Test-related updates\n  - **style**: Code style and formatting\n\n- Prefix Python package changes with:\n  - `py(deps):` for standard packages\n  - `py(deps[dev]):` for development packages\n  - `py(deps[extra]):` for extras/sub-packages\n\n- General guidelines:\n  - Subject line: Maximum 50 characters\n  - Body lines: Maximum 72 characters\n  - Use imperative mood (e.g., \"Add\", \"Fix\", not \"Added\", \"Fixed\")\n  - Limit to one topic per commit\n  - Separate subject from body with a blank line\n  - Mark breaking changes clearly: `BREAKING:`\n</git_commit_standards>\n\n<pytest_testing_guidelines>\n- Use fixtures from conftest.py instead of monkeypatch and MagicMock when available\n- For libtmux tests, use these provided fixtures for fast, efficient tmux resource management:\n  - `server`: Creates a temporary tmux server with isolated socket\n  - `session`: Creates a temporary tmux session in the server\n  - `window`: Creates a temporary tmux window in the session\n  - `pane`: Creates a temporary tmux pane in the pane\n  - `TestServer`: Factory for creating multiple independent servers with unique socket names\n- Example usage with server fixture:\n  ```python\n  def test_something_with_server(server):\n      # server is already running with proper configuration\n      my_session = server.new_session(\"test-session\")\n      assert server.is_alive()\n  ```\n- Example usage with session fixture:\n  ```python\n  def test_something_with_session(session):\n      # session is already created and configured\n      new_window = session.new_window(\"test-window\")\n      assert new_window in session.windows\n  ```\n- Customize session parameters by overriding the session_params fixture:\n  ```python\n  @pytest.fixture\n  def session_params():\n      return {\n          'x': 800,\n          'y': 600,\n          'window_name': 'custom-window'\n      }\n  ```\n- Benefits of using libtmux fixtures:\n  - No need to manually set up and tear down tmux infrastructure\n  - Tests run in isolated tmux environments\n  - Faster test execution\n  - Reliable test environment with predictable configuration\n- Document in test docstrings why standard fixtures weren't used for exceptional cases\n- Use tmp_path (pathlib.Path) fixture over Python's tempfile\n- Use monkeypatch fixture over unittest.mock\n</pytest_testing_guidelines>\n\n<import_guidelines>\n- Prefer namespace imports over importing specific symbols\n- Import modules and access attributes through the namespace:\n  - Use `import enum` and access `enum.Enum` instead of `from enum import Enum`\n  - This applies to standard library modules like pathlib, os, and similar cases\n- For typing, use `import typing as t` and access via the namespace:\n  - Access typing elements as `t.NamedTuple`, `t.TypedDict`, etc.\n  - Note primitive types like unions can be done via `|` pipes\n  - Primitive types like list and dict can be done via `list` and `dict` directly\n- Benefits of namespace imports:\n  - Improves code readability by making the source of symbols clear\n  - Reduces potential naming conflicts\n  - Makes import statements more maintainable\n</import_guidelines>\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","title":"AGENTS.md","content":"# AGENTS.md\n\nThis file provides guidance to AI agents (e.g., Claude Code, Cursor, and other LLM-powered tools) when working with code in this repository.\n\n## Project Overview\n\ntmuxp is a session manager for tmux that allows users to save and load tmux sessions through YAML/JSON configuration files. It's powered by libtmux and provides a declarative way to manage tmux sessions.\n\n## Development Commands\n\n### Testing\n- `just test` or `uv run py.test` - Run all tests\n- `uv run py.test tests/path/to/test.py::TestClass::test_method` - Run a single test\n- `uv run ptw .` - Continuous test runner with pytest-watcher\n- `uv run ptw . --now --doctest-modules` - Watch tests including doctests\n- `just start` or `just watch-test` - Watch and run tests on file changes\n\n### Code Quality\n- `just ruff` or `uv run ruff check .` - Run linter\n- `uv run ruff check . --fix --show-fixes` - Fix linting issues automatically\n- `just ruff-format` or `uv run ruff format .` - Format code\n- `just mypy` or `uv run mypy` - Run type checking (strict mode enabled)\n- `just watch-ruff` - Watch and lint on changes\n- `just watch-mypy` - Watch and type check on changes\n\n### Documentation\n- `just build-docs` - Build documentation\n- `just serve-docs` - Serve docs locally at http://localhost:8013\n- `just dev-docs` - Watch and serve docs with auto-reload\n- `just start-docs` - Alternative to dev_docs\n\n### CLI Commands\n- `tmuxp load <config>` - Load a tmux session from config\n- `tmuxp load -d <config>` - Load session in detached state\n- `tmuxp freeze <session-name>` - Export running session to config\n- `tmuxp convert <file>` - Convert between YAML and JSON\n- `tmuxp shell` - Interactive Python shell with tmux context\n- `tmuxp debug-info` - Collect system info for debugging\n\n## Architecture\n\n### Core Components\n\n1. **CLI Module** (`src/tmuxp/cli/`): Entry points for all tmuxp commands\n   - `load.py`: Load tmux sessions from config files\n   - `freeze.py`: Export live sessions to config files\n   - `convert.py`: Convert between YAML/JSON formats\n   - `shell.py`: Interactive Python shell with tmux context\n\n2. **Workspace Module** (`src/tmuxp/workspace/`): Core session management\n   - `builder.py`: Builds tmux sessions from configuration\n   - `loader.py`: Loads and validates config files\n   - `finders.py`: Locates workspace config files\n   - `freezer.py`: Exports running sessions to config\n\n3. **Plugin System** (`src/tmuxp/plugin.py`): Extensibility framework\n   - Plugins extend `TmuxpPlugin` base class\n   - Hooks: `before_workspace_builder`, `on_window_create`, `after_window_finished`, `before_script`, `reattach`\n   - Version constraint checking for compatibility\n\n### Configuration Flow\n\n1. Load YAML/JSON config via `ConfigReader` (handles includes, environment variables)\n2. Expand inline shorthand syntax\n3. Trickle down default values (session → window → pane)\n4. Validate configuration structure\n5. Build tmux session via `WorkspaceBuilder`\n\n### Key Patterns\n\n- **Type Safety**: All code uses type hints with mypy strict mode\n- **Error Handling**: Custom exception hierarchy based on `TmuxpException`\n- **Testing**: Pytest with fixtures for tmux server/session/window/pane isolation\n- **Future Imports**: All files use `from __future__ import annotations`\n\n## Configuration Format\n\n```yaml\nsession_name: my-session\nstart_directory: ~/project\nwindows:\n  - window_name: editor\n    layout: main-vertical\n    panes:\n      - shell_command:\n          - vim\n      - shell_command:\n          - git status\n```\n\n## Environment Variables\n\n- `TMUXP_CONFIGDIR`: Custom directory for workspace configs\n- `TMUX_CONF`: Path to tmux configuration file\n- `TMUXP_DEFAULT_COLUMNS/ROWS`: Default session dimensions\n\n## Testing Guidelines\n\n- **Use functional tests only**: Write tests as standalone functions, not classes. Avoid `class TestFoo:` groupings - use descriptive function names and file organization instead.\n- Use pytest fixtures from `tests/fixtures/` for tmux objects\n- Test plugins using mock packages in `tests/fixtures/pluginsystem/`\n- Use `retry_until` utilities for async tmux operations\n- Run single tests with: `uv run py.test tests/file.py::test_function_name`\n- **Use libtmux fixtures**: Prefer `server`, `session`, `window`, `pane` fixtures over manual setup\n- **Avoid mocks when fixtures exist**: Use real tmux fixtures instead of `MagicMock`\n- **Use `tmp_path`** fixture instead of Python's `tempfile`\n- **Use `monkeypatch`** fixture instead of `unittest.mock`\n\n## Code Style\n\n- Follow NumPy-style docstrings (pydocstyle convention)\n- Use ruff for formatting and linting\n- Maintain strict mypy type checking\n- Keep imports organized with future annotations at top\n- **Prefer namespace imports for stdlib**: Use `import enum` and `enum.Enum` instead of `from enum import Enum`; third-party packages may use `from X import Y`\n- **Type imports**: Use `import typing as t` and access via namespace (e.g., `t.Optional`)\n- **Development workflow**: Format → Test → Commit → Lint/Type Check → Test → Final Commit\n\n**Classes with fields** — `NamedTuple`, dataclasses — document every field in\nan `Attributes` section:\n\n```python\nclass SearchToken(t.NamedTuple):\n    \"\"\"Parsed search token with target fields and raw pattern.\n\n    Attributes\n    ----------\n    fields : tuple[str, ...]\n        Canonical field names to search (e.g., ('name', 'session_name')).\n    pattern : str\n        Raw search pattern before regex compilation.\n    \"\"\"\n```\n\nAutodoc renders every field whether or not you describe it, so an\nundocumented `NamedTuple` field ships to the API docs as \"Alias for field\nnumber 0\" and a dataclass field ships bare. Document all of them — a class\nwith three fields and two documented still ships a stub for the third.\n\n## Git Commit Standards\n\nFormat commit messages as:\n```\nScope(type[detail]): concise description\n\nwhy: Explanation of necessity or impact.\n\nwhat:\n- Specific technical changes made\n- Focused on a single topic\n```\n\nKeep the subject ≤50 chars (excluding any trailing `(#NN)` PR ref); wrap\nbody lines at ≤72 chars. Separate the `why:` and `what:` blocks with a\nblank line.\n\nCommon commit types:\n- **feat**: New features or enhancements\n- **fix**: Bug fixes\n- **refactor**: Code restructuring without functional change\n- **docs**: Documentation updates\n- **chore**: Maintenance (dependencies, tooling, config)\n- **test**: Test-related updates\n- **style**: Code style and formatting\n- **py(deps)**: Dependencies\n- **py(deps[dev])**: Dev Dependencies\n- **ai(rules[AGENTS])**: AI rule updates\n- **ai(claude[rules])**: Claude Code rules (CLAUDE.md)\n- **ai(claude[command])**: Claude Code command changes\n\nExample:\n```\nPane(feat[send_keys]): Add support for literal flag\n\nwhy: Enable sending literal characters without tmux interpretation\n\nwhat:\n- Add literal parameter to send_keys method\n- Update send_keys to pass -l flag when literal=True\n- Add tests for literal key sending\n```\n#### Release commits\n\nNever create tags. Never push tags. The user handles tagging and tag\npushes (tags trigger the CI publish workflow).\n\nRelease commit subjects are plain and short: `Tag v<version>`. Put\nthe detailed why/what in the commit body. Don't use the\n`Scope(type[detail]):` format for releases — don't bury the lede.\n\nFor multi-line commits, use heredoc to preserve formatting:\n```bash\ngit commit -m \"$(cat <<'EOF'\nfeat(Component[method]) add feature description\n\nwhy: Explanation of the change.\n\nwhat:\n- First change\n- Second change\nEOF\n)\"\n```\n\n## Logging Standards\n\nThese rules guide future logging changes; existing code may not yet conform.\n\n### Logger setup\n\n- Use `logging.getLogger(__name__)` in every module\n- Add `NullHandler` in library `__init__.py` files\n- Never configure handlers, levels, or formatters in library code — that's the application's job\n\n### Structured context via `extra`\n\nPass structured data on every log call where useful for filtering, searching, or test assertions.\n\n**Core keys** (stable, scalar, safe at any log level):\n\n| Key | Type | Context |\n|-----|------|---------|\n| `tmux_cmd` | `str` | tmux command line |\n| `tmux_subcommand` | `str` | tmux subcommand (e.g. `new-session`) |\n| `tmux_target` | `str` | tmux target specifier (e.g. `mysession:1.2`) |\n| `tmux_exit_code` | `int` | tmux process exit code |\n| `tmux_session` | `str` | session name |\n| `tmux_window` | `str` | window name or index |\n| `tmux_pane` | `str` | pane identifier |\n| `tmux_config_path` | `str` | workspace config file path |\n| `tmux_layout` | `str` | window layout string |\n\n**Heavy/optional keys** (DEBUG only, potentially large):\n\n| Key | Type | Context |\n|-----|------|---------|\n| `tmux_stdout` | `list[str]` | tmux stdout lines (truncate or cap; `%(tmux_stdout)s` produces repr) |\n| `tmux_stderr` | `list[str]` | tmux stderr lines (same caveats) |\n\nTreat established keys as compatibility-sensitive — downstream users may build dashboards and alerts on them. Change deliberately.\n\n### Key naming rules\n\n- `snake_case`, not dotted; `tmux_` prefix\n- Prefer stable scalars; avoid ad-hoc objects\n- Heavy keys (`tmux_stdout`, `tmux_stderr`) are DEBUG-only; consider companion `tmux_stdout_len` fields or hard truncation (e.g. `stdout[:100]`)\n\n### Lazy formatting\n\n`logger.debug(\"msg %s\", val)` not f-strings. Two rationales:\n- Deferred string interpolation: skipped entirely when level is filtered\n- Aggregator message template grouping: `\"Running %s\"` is one signature grouped ×10,000; f-strings make each line unique\n\nWhen computing `val` itself is expensive, guard with `if logger.isEnabledFor(logging.DEBUG)`.\n\n### stacklevel for wrappers\n\nIncrement for each wrapper layer so `%(filename)s:%(lineno)d` and OTel `code.filepath` point to the real caller. Verify whenever call depth changes.\n\n### LoggerAdapter for persistent context\n\nFor objects with stable identity (Session, Window, Pane), use `LoggerAdapter` to avoid repeating the same `extra` on every call. Lead with the portable pattern (override `process()` to merge); `merge_extra=True` simplifies this on Python 3.13+.\n\n### Log levels\n\n| Level | Use for | Examples |\n|-------|---------|----------|\n| `DEBUG` | Internal mechanics, tmux I/O, config expansion | tmux command + stdout, trickle-down steps |\n| `INFO` | Session lifecycle, user-visible operations | Session created, window added, workspace loaded |\n| `WARNING` | Recoverable issues, deprecation, user-actionable config | Deprecated key, missing optional program |\n| `ERROR` | Failures that stop an operation | tmux command failed, config validation error |\n\nConfig discovery noise belongs in `DEBUG`; only surprising/user-actionable config issues → `WARNING`.\n\n### Message style\n\n- Lowercase, past tense for events: `\"session created\"`, `\"tmux command failed\"`\n- No trailing punctuation\n- Keep messages short; put details in `extra`, not the message string\n\n### Exception logging\n\n- Use `logger.exception()` only inside `except` blocks when you are **not** re-raising\n- Use `logger.error(..., exc_info=True)` when you need the traceback outside an `except` block\n- Avoid `logger.exception()` followed by `raise` — this duplicates the traceback. Either add context via `extra` that would otherwise be lost, or let the exception propagate\n\n### Testing logs\n\nAssert on `caplog.records` attributes, not string matching on `caplog.text`:\n- Scope capture: `caplog.at_level(logging.DEBUG, logger=\"libtmux.common\")`\n- Filter records rather than index by position: `[r for r in caplog.records if hasattr(r, \"tmux_cmd\")]`\n- Assert on schema: `record.tmux_exit_code == 0` not `\"exit code 0\" in caplog.text`\n- `caplog.record_tuples` cannot access extra fields — always use `caplog.records`\n\n### Output channels\n\nTwo output channels serve different audiences:\n\n1. **Diagnostics** (`logger.*()` with `extra`): System events for log files, `caplog`, and aggregators. Never styled.\n2. **User-facing output**: What the human sees. Styled via `Colors` class.\n   - Commands with output modes (`--json`/`--ndjson`): prefer `OutputFormatter.emit_text()` from `tmuxp.cli._output` — silenced in non-human modes.\n   - Human-only commands: use `tmuxp_echo()` from `tmuxp.log` (re-exported via `tmuxp.cli.utils`) for user-facing messages.\n   - **Undefined contracts:** Machine-output behavior for error and empty-result paths (e.g., `search` with no matches) is not yet defined. These paths currently emit styled text through `formatter.emit_text()`, which is a no-op in machine modes.\n\nRaw `print()` is forbidden in command/business logic. The `print()` call lives only inside the presenter layer (`_output.py`) or `tmuxp_echo`.\n\n### Avoid\n\n- f-strings/`.format()` in log calls\n- Unguarded logging in hot loops (guard with `isEnabledFor()`)\n- Catch-log-reraise without adding new context\n- `print()` for debugging or internal diagnostics — use `logger.debug()` with structured `extra` instead\n- Logging secret env var values (log key names only)\n- Non-scalar ad-hoc objects in `extra`\n- Requiring custom `extra` fields in format strings without safe defaults (missing keys raise `KeyError`)\n\n## Doctests\n\n**All functions and methods MUST have working doctests.** Doctests serve as both documentation and tests.\n\n**CRITICAL RULES:**\n- Doctests MUST actually execute - never comment out function calls or similar\n- Doctests MUST NOT be converted to `.. code-block::` as a workaround (code-blocks don't run)\n- If you cannot create a working doctest, **STOP and ask for help**\n\n**Available tools for doctests:**\n- `doctest_namespace` fixtures: `server`, `session`, `window`, `pane`, `tmp_path`, `test_utils`\n- Ellipsis for variable output: `# doctest: +ELLIPSIS`\n- Update `conftest.py` to add new fixtures to `doctest_namespace`\n\n**`# doctest: +SKIP` is NOT permitted** - it's just another workaround that doesn't test anything. Use the fixtures properly - tmux is required to run tests anyway.\n\n**Using fixtures in doctests:**\n```python\n>>> from tmuxp.workspace.builder import WorkspaceBuilder\n>>> config = {'session_name': 'test', 'windows': [{'window_name': 'main'}]}\n>>> builder = WorkspaceBuilder(session_config=config, server=server)  # doctest: +ELLIPSIS\n>>> builder.build()\n>>> builder.session.name\n'test'\n```\n\n**When output varies, use ellipsis:**\n```python\n>>> session.session_id  # doctest: +ELLIPSIS\n'$...'\n>>> window.window_id  # doctest: +ELLIPSIS\n'@...'\n```\n\n**Additional guidelines:**\n1. **Use narrative descriptions** for test sections rather than inline comments\n2. **Move complex examples** to dedicated test files at `tests/examples/<path>/test_<example>.py`\n3. **Keep doctests simple and focused** on demonstrating usage\n4. **Add blank lines between test sections** for improved readability\n\n**Doctest exceptions** (patterns where doctests are not required):\n\n1. **Sphinx/docutils `visit_*`/`depart_*` methods** - tested via integration tests; 0 examples across docutils (851 methods), Sphinx (800+), and CPython's `ast.NodeVisitor`\n2. **Sphinx `setup()` functions** - entry points not testable in isolation\n3. **Complex recursive traversal functions** - extract helper predicates instead\n\n**Best practice for node processing**: Extract testable helper functions (like `_is_usage_block()`) and doctest those. Keep complex visitor logic in integration tests.\n\n## Documentation Standards\n\n### Code Blocks\n\nCode blocks are paste-and-run units: pasting one block runs exactly one\nintended action. Doctests and other executed examples are exempt — the test\nsuite runs them, nobody pastes them.\n\n- **One command per block.** Multiple steps may share a block only when\n  explicitly chained with `&&`, `;`, or `\\` continuations — the chain is\n  then one logical command.\n- **Explanations go in prose above the block**, never as `#` comments inside it.\n- **Command menus are per-command blocks with prose lead-ins**, not tables.\n- **Shell commands use the `console` tag with a `$ ` prefix.** This separates\n  interactive commands from scripts and enables prompt-aware copy.\n- **Split long commands with `\\`** — one flag or flag+value pair per indented\n  continuation line, positional arguments last.\n\nGood:\n\nShow the last ten commits as a graph:\n\n```console\n$ git log \\\n    --max-count=10 \\\n    --graph \\\n    --oneline\n```\n\nBad:\n\n```console\n# Show the last ten commits as a graph\n$ git log --max-count=10 --graph --oneline\n```\n\n### Changelog Conventions\n\nThese rules apply when authoring entries in `CHANGES`, which is rendered as the Sphinx changelog page. Modeled on Django's release-notes shape — deliverables get titles and prose, not bullets.\n\n**Release entry boilerplate.** Every release header is `## tmuxp X.Y.Z (YYYY-MM-DD)`. The file opens with a `## tmuxp X.Y.Z (Yet to be released)` placeholder block fenced by `<!-- KEEP THIS PLACEHOLDER ... -->` and `<!-- END PLACEHOLDER ... -->` HTML comments — new release entries land immediately below the END marker, never above it.\n\n**Open with a multi-sentence lead paragraph.** Plain prose, no italic. Open with the version as sentence subject (*\"tmuxp X.Y.Z ships …\"*) so the lead is self-contained when excerpted. Two to four sentences telling the reader what shipped and who cares — user-visible takeaways, not internal mechanism. Cross-reference detail docs with `{ref}` to keep the lead compact.\n\n**Each deliverable is a section, not a bullet.** Inside `### What's new`, every distinct deliverable gets a `#### Deliverable title (#NN)` heading naming it in user vocabulary, followed by 1-3 prose paragraphs explaining what shipped. Don't wrap a paragraph in `- ` — bullets are for enumerable lists, not paragraph containers. Cross-link detail docs (`See {ref}\\`foo\\` for details.`) so prose stays focused.\n\n**The deliverable test.** Before writing an entry, ask: \"What's the deliverable, in user vocabulary?\" If you can't answer in one sentence, the entry isn't ready. Mechanism (helper internals, byte counters, schema-validation locations) belongs in PR descriptions and code comments, not the changelog.\n\n**Fixed subheadings**, in this order when present: `### Breaking changes`, `### Dependencies`, `### What's new`, `### Fixes`, `### Documentation`, `### Development`. Dev tooling (helper scripts, internal automation) lives under `### Development`. For breaking changes, show the migration path with concrete inline code (e.g. a `# Before` / `# After` fenced code block). Dependency floor bumps use the form ``Minimum `pkg>=X.Y.Z` (was `>=X.Y.W`)``.\n\n**PR refs `(#NN)`** sit in each deliverable's `####` heading.\n\n**When bullets are appropriate.** Catch-all sections (`### Fixes`, occasionally `### Documentation`) with 3+ genuinely small items use bullets — one line each, never paragraphs. If a bullet swells past two lines, promote it to a `#### Title (#NN)` heading with prose body.\n\n**Anti-patterns.**\n\n- Fragile metrics: token ceilings, third-party version pins, percent benchmarks, exact byte counts. Describe the *capability*, not the math.\n- Internal jargon: private symbols (leading-underscore identifiers), algorithm names exposed for the first time, backend scaffolding.\n- Walls of text dressed up as bullets.\n- Buried breaking changes — they get their own subheading at the top of the entry.\n\n**Always link autodoc'd APIs.** Any class, method, function, exception, or attribute that has its own rendered page must be cited via the appropriate role (`{class}`, `{meth}`, `{func}`, `{exc}`, `{attr}`) — never with plain backticks. Doc pages without explicit ref labels use `{doc}`. Plain backticks are correct for code syntax, env vars, parameter names, and file paths that aren't doc pages — anything without an autodoc destination.\n\n**MyST roles.** Class references use `{class}` (e.g. `{class}\\`~tmuxp.workspace.builder.WorkspaceBuilder\\``), methods use `{meth}`, functions use `{func}`, exceptions use `{exc}`, attributes use `{attr}`, internal anchors use `{ref}`, doc-path links use `{doc}`.\n\n**Summarization style.** When a user asks \"what changed in the latest version?\" or similar, lead with the entry's lead paragraph (paraphrased if needed), followed by each `####` deliverable heading under `### What's new` with a one-sentence summary. Cite `(#NN)` only if the user asks for source links. Don't invent versions, dates, or numbers not present in `CHANGES`. Don't quote line numbers or file offsets — those shift as the file evolves.\n\n## Important Notes\n\n- **QA every edit**: Run formatting and tests before committing\n- **Minimum Python**: 3.10+ (per pyproject.toml)\n- **Minimum tmux**: 3.2+ (as per README)\n\n## CLI Color Semantics (Revision 1, 2026-01-04)\n\nThe CLI uses semantic colors via the `Colors` class in `src/tmuxp/_internal/colors.py`. Colors are chosen based on **hierarchy level** and **semantic meaning**, not just data type.\n\n### Design Principles\n\n1. **Structural hierarchy**: Headers > Items > Details\n2. **Semantic meaning**: What IS this element?\n3. **Visual weight**: What should draw the eye first?\n4. **Depth separation**: Parent elements should visually contain children\n\nInspired by patterns from **jq** (object keys vs values), **ripgrep** (path/line/match distinction), and **mise/just** (semantic method names).\n\n### Hierarchy-Based Colors\n\n| Level | Element Type | Method | Color | Examples |\n|-------|--------------|--------|-------|----------|\n| **L0** | Section headers | `heading()` | Bright cyan + bold | \"Local workspaces:\", \"Global workspaces:\" |\n| **L1** | Primary content | `highlight()` | Magenta + bold | Workspace names (braintree, .tmuxp) |\n| **L2** | Supplementary info | `info()` | Cyan | Paths (~/.tmuxp, ~/project/.tmuxp.yaml) |\n| **L3** | Metadata/labels | `muted()` | Blue | Source labels (Legacy:, XDG default:) |\n\n### Status-Based Colors (Override hierarchy when applicable)\n\n| Status | Method | Color | Examples |\n|--------|--------|-------|----------|\n| Success/Active | `success()` | Green | \"active\", \"18 workspaces\" |\n| Warning | `warning()` | Yellow | Deprecation notices |\n| Error | `error()` | Red | Error messages |\n\n### Example Output\n\n```\nLocal workspaces:                              ← heading() bright_cyan+bold\n  .tmuxp  ~/work/python/tmuxp/.tmuxp.yaml      ← highlight() + info()\n\nGlobal workspaces (~/.tmuxp):                  ← heading() + info()\n  braintree                                    ← highlight()\n  cihai                                        ← highlight()\n\nGlobal workspace directories:                  ← heading()\n  Legacy: ~/.tmuxp (18 workspaces, active)     ← muted() + info() + success()\n  XDG default: ~/.config/tmuxp (not found)     ← muted() + info() + muted()\n```\n\n### Available Methods\n\n```python\ncolors = Colors()\ncolors.heading(\"Section:\")  # Cyan + bold (section headers)\ncolors.highlight(\"item\")  # Magenta + bold (primary content)\ncolors.info(\"/path/to/file\")  # Cyan (paths, supplementary info)\ncolors.muted(\"label:\")  # Blue (metadata, labels)\ncolors.success(\"ok\")  # Green (success states)\ncolors.warning(\"caution\")  # Yellow (warnings)\ncolors.error(\"failed\")  # Red (errors)\n```\n\n### Key Rules\n\n**Never use the same color for adjacent hierarchy levels.** If headers and items are both blue, they blend together. Each level must be visually distinct.\n\n**Avoid dim/faint styling.** The ANSI dim attribute (`\\x1b[2m`) is too dark to read on black terminal backgrounds. This includes both standard and bright color variants with dim.\n\n**Bold may not render distinctly.** Some terminal/font combinations don't differentiate bold from normal weight. Don't rely on bold alone for visual distinction - pair it with color differences.\n\n## AI Slop Prevention\n\nTreat AI slop as **review-hostile noise**, not as proof that text or\ncode is wrong. The goal is to maximize information density by removing\nartifacts that make the repository harder to trust or navigate.\n\n### The Anti-Slop Rubric\n\nBefore committing, audit all AI-assisted changes for these noise\npatterns:\n\n- **AI Signatures:** Remove \"Generated by\", footers, conversational\n  filler (\"Certainly!\", \"Here is...\"), unexplained emojis (🤖, ✨), and\n  AI-tool metadata.\n- **Brittle References:** Avoid hard-coded line numbers, fragile\n  file/test counts, dated \"as of\" claims, bare SHAs, and local\n  absolute paths unless they are strict evidentiary artifacts (e.g.,\n  benchmark logs).\n- **Diff Narration:** Do not restate what moved, was renamed, or was\n  removed in artifacts the downstream reader holds: code, docstrings,\n  README, CHANGES, PR descriptions, or release notes. The diff and\n  commit message already carry this history.\n- **Branch-Internal Narrative:** Do not mention intermediate branch\n  states, abandoned approaches, or \"no longer\" behavior unless users\n  of a published release actually experienced the old state (**The\n  Published-Release Test**).\n- **Low-Value Scaffolding:** Remove ownerless TODOs (`TODO: revisit`),\n  unused future-proofing, debug artifacts, and defensive wrappers that\n  do not protect a currently reachable failure mode.\n- **Prose Inflation:** Replace generic AI \"tells\" like *comprehensive,\n  robust, seamless, production-ready, leverage, delve, tapestry,* and\n  *best practices* with concrete descriptions of behavior,\n  constraints, or trade-offs.\n- **Coded Labels:** Write rules, options, and findings as plain\n  imperatives. Don't tag them with codes like `[R1]`, `A1`, or\n  `Option B` in artifacts a human reads — the reader shouldn't have to\n  decode an index. Internal agent bookkeeping may use ids; shipped text\n  may not.\n\n### Durable Source Links\n\nLink to a pinned revision, never to trunk. A pinned permalink is not a\nbrittle reference; an unlinked SHA dropped into prose is. `blob/master/…`\nlinks rot silently — the file moves, lines shift, and the anchor lands\non unrelated code while still resolving.\n\n- Prefer a release tag (`blob/v1.4.0/…`). Most durable, and it tells\n  the reader which released version the claim held for.\n- Otherwise use a 7-char commit ref (`blob/9a29b1a/…`) reachable from\n  trunk. Use when there is no tag or the claim is about unreleased\n  code. Never a PR-head SHA — it can be rebased or garbage-collected.\n- Reserve `blob/master/…` for living documents meant to always show the\n  latest state, such as a contributing guide.\n- Line anchors (`#L120-L145`) are only safe on a pinned ref.\n\n### Preservation & Context\n\n**When unsure, leave the text in place and ask.** Subjective cleanup\nmust never be a reason to remove load-bearing rationale.\n\n- **Preserve the \"Why\":** You MUST NOT delete comments that document\n  invariants, protocol constraints, platform quirks, security\n  boundaries, and upstream workarounds.\n- **Evidence is Immune:** Preserve exact counts, dates, and SHAs when\n  they serve as evidence in benchmark results, release notes, stack\n  traces, or lockfiles.\n- **Behavior Over Inventory:** A useful description explains what\n  changed for the *system or user*; it does not provide an inventory\n  of files or functions the diff already shows.\n\n### The Published-Release Test\n\nLong-running branches accumulate tactical decisions — renames,\nrefactors, attempts-then-reverts. When deciding what counts as\nbranch-internal, use trunk or the parent branch as the baseline — not\nintermediate states inside the current branch. Ask:\n\n> Did users of the most recently published release ever experience\n> this old name, old behavior, or bug?\n\nIf the answer is **no**, it is branch-internal narrative. Move it to\nthe commit message and describe only the final state in the artifact.\n\n**Keep in shipped artifacts:**\n- Deprecations and migration guides for symbols that actually shipped.\n- `### Fixes` entries for bugs that affected users of a published\n  release.\n- Comments explaining *why the current code looks this way*\n  (invariants, platform quirks) that make sense to a reader who never\n  saw the previous version.\n\n### Cleanup in Hindsight\n\nWhen applying these rules retroactively from inside a feature branch,\nfirst establish scope by diffing against the parent branch (or trunk)\nto identify which commits this branch actually introduced. Then:\n\n- **In-branch commits:** Prompt the user with two options: `fixup!`\n  commits with `git rebase --autosquash` to address each causal commit\n  at its source, or a single cleanup commit at branch tip.\n- **Trunk/Parent commits:** Default to leaving them alone. Act only on\n  explicit user instruction. If the user opts in, fold the cleanup\n  into a single commit at branch tip; do not rewrite shared history.\n- **Scope guard:** If cleaning prior slop would touch a colleague's\n  work or expand the branch beyond its stated goal, stay in lane:\n  protect the current goal and leave prior slop alone.\n\n### Change Discipline\n\n- Make the smallest coherent change that solves the verified problem;\n  keep unrelated cleanup out of it.\n- Reuse an existing file, component, helper, API, or test before adding\n  a new one. Modify in place when the change fits the file's\n  responsibility.\n- Keep new APIs private until a caller outside the module needs them.\n- Add a file only for a durable boundary — a distinct responsibility,\n  independent reuse, or splitting an oversized high-touch module — not\n  for a single-use helper or a one-line re-export.\n\n### Keep Instructions Lean\n\nTreat this file like code and prune it.\n\n- Delete a line whose removal would not cause a mistake.\n- Move multi-step procedures into skills, path-specific rules into\n  nested AGENTS.md files, and hard limits into hooks or CI.\n- Keep only non-obvious, broadly applicable defaults here. Anything a\n  reader can infer from the code, a manifest, or a linter does not\n  belong.\n","category":"root","tokens":7362},{"name":".windsurfrules","path":".windsurfrules","title":".windsurfrules","content":"# libtmux Python Project Rules\n\n<project_stack>\n- uv - Python package management and virtual environments\n- ruff - Fast Python linter and formatter\n- py.test - Testing framework\n  - pytest-watcher - Continuous test runner\n- mypy - Static type checking\n- doctest - Testing code examples in documentation\n</project_stack>\n\n<coding_style>\n- Use a consistent coding style throughout the project\n- Format code with ruff before committing\n- Run linting and type checking before finalizing changes\n- Verify tests pass after each significant change\n</coding_style>\n\n<python_docstrings>\n- Use reStructuredText format for all docstrings in src/**/*.py files\n- Keep the main description on the first line after the opening `\"\"\"`\n- Use NumPy docstyle for parameter and return value documentation\n- Format docstrings as follows:\n  ```python\n  \"\"\"Short description of the function or class.\n\n  Detailed description using reStructuredText format.\n\n  Parameters\n  ----------\n  param1 : type\n      Description of param1\n  param2 : type\n      Description of param2\n\n  Returns\n  -------\n  type\n      Description of return value\n  \"\"\"\n  ```\n</python_docstrings>\n\n<python_doctests>\n- Use narrative descriptions for test sections rather than inline comments\n- Format doctests as follows:\n  ```python\n  \"\"\"\n  Examples\n  --------\n  Create an instance:\n\n  >>> obj = ExampleClass()\n  \n  Verify a property:\n  \n  >>> obj.property\n  'expected value'\n  \"\"\"\n  ```\n- Add blank lines between test sections for improved readability\n- Keep doctests simple and focused on demonstrating usage\n- Move complex examples to dedicated test files at tests/examples/<path_to_module>/test_<example>.py\n- Utilize pytest fixtures via doctest_namespace for complex scenarios\n</python_doctests>\n\n<testing_practices>\n- Run tests with `uv run py.test` before committing changes\n- Use pytest-watcher for continuous testing: `uv run ptw . --now --doctest-modules`\n- Fix any test failures before proceeding with additional changes\n</testing_practices>\n\n<git_workflow>\n- Make atomic commits with conventional commit messages\n- Start with an initial commit of functional changes\n- Follow with separate commits for formatting, linting, and type checking fixes\n</git_workflow>\n\n<git_commit_standards>\n- Use the following commit message format:\n  ```\n  Component/File(commit-type[Subcomponent/method]): Concise description\n\n  why: Explanation of necessity or impact.\n  what:\n  - Specific technical changes made\n  - Focused on a single topic\n\n  refs: #issue-number, breaking changes, or relevant links\n  ```\n\n- Common commit types:\n  - **feat**: New features or enhancements\n  - **fix**: Bug fixes\n  - **refactor**: Code restructuring without functional change\n  - **docs**: Documentation updates\n  - **chore**: Maintenance (dependencies, tooling, config)\n  - **test**: Test-related updates\n  - **style**: Code style and formatting\n\n- Prefix Python package changes with:\n  - `py(deps):` for standard packages\n  - `py(deps[dev]):` for development packages\n  - `py(deps[extra]):` for extras/sub-packages\n\n- General guidelines:\n  - Subject line: Maximum 50 characters\n  - Body lines: Maximum 72 characters\n  - Use imperative mood (e.g., \"Add\", \"Fix\", not \"Added\", \"Fixed\")\n  - Limit to one topic per commit\n  - Separate subject from body with a blank line\n  - Mark breaking changes clearly: `BREAKING:`\n</git_commit_standards>\n\n<pytest_testing_guidelines>\n- Use fixtures from conftest.py instead of monkeypatch and MagicMock when available\n- For libtmux tests, use these provided fixtures for fast, efficient tmux resource management:\n  - `server`: Creates a temporary tmux server with isolated socket\n  - `session`: Creates a temporary tmux session in the server\n  - `window`: Creates a temporary tmux window in the session\n  - `pane`: Creates a temporary tmux pane in the pane\n  - `TestServer`: Factory for creating multiple independent servers with unique socket names\n- Example usage with server fixture:\n  ```python\n  def test_something_with_server(server):\n      # server is already running with proper configuration\n      my_session = server.new_session(\"test-session\")\n      assert server.is_alive()\n  ```\n- Example usage with session fixture:\n  ```python\n  def test_something_with_session(session):\n      # session is already created and configured\n      new_window = session.new_window(\"test-window\")\n      assert new_window in session.windows\n  ```\n- Customize session parameters by overriding the session_params fixture:\n  ```python\n  @pytest.fixture\n  def session_params():\n      return {\n          'x': 800,\n          'y': 600,\n          'window_name': 'custom-window'\n      }\n  ```\n- Benefits of using libtmux fixtures:\n  - No need to manually set up and tear down tmux infrastructure\n  - Tests run in isolated tmux environments\n  - Faster test execution\n  - Reliable test environment with predictable configuration\n- Document in test docstrings why standard fixtures weren't used for exceptional cases\n- Use tmp_path (pathlib.Path) fixture over Python's tempfile\n- Use monkeypatch fixture over unittest.mock\n</pytest_testing_guidelines>\n\n<import_guidelines>\n- Prefer namespace imports over importing specific symbols\n- Import modules and access attributes through the namespace:\n  - Use `import enum` and access `enum.Enum` instead of `from enum import Enum`\n  - This applies to standard library modules like pathlib, os, and similar cases\n- For typing, use `import typing as t` and access via the namespace:\n  - Access typing elements as `t.NamedTuple`, `t.TypedDict`, etc.\n  - Note primitive types like unions can be done via `|` pipes\n  - Primitive types like list and dict can be done via `list` and `dict` directly\n- Benefits of namespace imports:\n  - Improves code readability by making the source of symbols clear\n  - Reduces potential naming conflicts\n  - Makes import statements more maintainable\n</import_guidelines>\n","category":"root","tokens":1470}]}