# agentmux Developer Guide

Canonical reference for AI coding agents and contributors working in this repository.

---

## Project Overview

**agentmux** is a Python CLI tool that manages AI coding agent configurations (agents, commands,
skills, rules, instructions, MCP configs) across multiple tools (Cursor, Gemini CLI, etc.) from a
single canonical format (Markdown + YAML frontmatter). It is written in Python 3.10+, uses Pydantic
v2 for data models, Click for the CLI, and Rich for terminal output.

Package layout follows the `src/` convention:

```
src/agentmux/
  adapters/   — Tool-specific adapters (Cursor, Gemini, …)
  cli/        — Click CLI commands
  core/       — Data models (component.py), config parser (setup.py), sync engine (sync.py)
  utils/      — Frontmatter parser, symlink/file helpers
tests/        — pytest test suite mirroring src layout
```

---

## Environment Setup

This project uses a `.venv` virtual environment. Activate it before running any commands:

```bash
source .venv/bin/activate
```

If it doesn't exist, create and populate it:

```bash
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
```

---

## Commands

### Run all tests
```bash
pytest --cov=agentmux --cov-report=term-missing
```

### Run a single test file
```bash
pytest tests/test_sync.py
```

### Run a single test by name
```bash
pytest tests/test_sync.py::test_sync_cursor_creates_agent
pytest -k "test_deep_merge_nested"
```

### Lint (check only)
```bash
ruff check src/
```

### Format (auto-fix)
```bash
ruff format src/
```

### Lint + format together
```bash
ruff check src/ && ruff format src/
```

### Type checking
```bash
mypy src/
```

### Run full quality check (do this before committing)
```bash
ruff check src/ && ruff format --check src/ && mypy src/ && pytest --cov=agentmux
```

---

## Code Style

### Formatting
- Line length: **100 characters** (configured in `pyproject.toml`; E501 is ignored so ruff won't
  error on it, but keep lines within 100 for readability).
- Indentation: 4 spaces, no tabs.
- Formatter: `ruff format` (Black-compatible).

### Imports
- **Absolute imports only** — never relative (`from agentmux.core.component import X`, not
  `from .component import X`).
- **Named imports** — no wildcard imports (`from module import *` is forbidden).
- Declare `__all__` in every module that has a public API.
- Use `TYPE_CHECKING` guards for imports that are only needed for type annotations to avoid
  circular imports at runtime.
- Lazy imports inside function bodies are acceptable in CLI commands to defer heavy loading.
- Annotate untyped third-party packages with `# type: ignore[import-untyped]` (specific error
  code, not a blanket `# type: ignore`).

### Naming Conventions

| Entity | Convention | Examples |
|---|---|---|
| Classes | `PascalCase` | `CursorAdapter`, `SyncAction`, `MCPConfig` |
| Functions / methods | `snake_case` | `from_file()`, `sync_setup()`, `create_symlink()` |
| Variables | `snake_case` | `raw_meta`, `mcp_path`, `sync_action` |
| Module-private constants | `_SCREAMING_SNAKE` | `_FRONTMATTER_RE`, `_MAX_BACKUPS` |
| Public Enum values | `SCREAMING_SNAKE` | `ComponentType.AGENT`, `InstallStrategy.SYMLINK` |
| Private helpers | Leading `_` | `_deep_merge()`, `_deploy()`, `_cleanup_backups()` |
| Type aliases | `PascalCase` | `Component = Agent \| Command \| ...` |

Variable names must be **descriptive** — `raw_response_payload` not `data`; `user_index` not `x`.

### Typing
- Python 3.10+ union syntax: `Path | None`, `str | int`, not `Optional[str]` or `Union[str, int]`.
- All public functions must have explicit **parameter and return type annotations**.
- Use concrete generics throughout: `dict[str, Any]`, `list[str]`, `tuple[ToolAdapter, ...]`.
- Mypy runs in **strict mode** — `disallow_untyped_defs` and all other strict flags are active.
  New code must pass `mypy src/` cleanly with zero new errors.
- Use `@dataclass` for internal data-transfer objects (no validation needed).
- Use **Pydantic v2** models for all user-facing data with validation requirements.
- Use `Literal["value"]` for constrained string fields instead of plain `str`.
- Enums should inherit from `(str, Enum)` to make values directly JSON-serializable.
- `Any` is acceptable in `dict[str, Any]` for unstructured YAML/JSON data, but document why.

### Error Handling
- **Fail loudly with context.** Wrap exceptions with human-readable messages before re-raising:
  ```python
  raise ValueError(f"Failed to load agent '{path}': {exc}") from exc
  ```
- **Always use `from exc` chaining** to preserve the original cause.
- **Guard clauses over deep nesting.** Validate inputs at the top of a function and return/raise
  early rather than wrapping the happy path in `if/else` blocks.
- Use `click.UsageError` for user-facing config errors in CLI commands (produces clean output).
- Use `sys.exit(1)` in CLI on unrecoverable errors — do not let raw exceptions propagate to the
  user.
- Broad `except Exception` is acceptable only when the intent is to record a structured error
  result (e.g., `SyncAction("error", ...)`) rather than silently swallow failures.
- Pydantic validation errors caught during `load_components()` must be re-raised as `ValueError`
  with context.

### Code Organisation
- **Single Responsibility per module** — if a module does two things, split it.
- Prefer **dispatch tables** (`dict[ComponentType, Callable]`) over long `if/elif` chains.
- Keep adapter implementations fully self-contained — no shared mutable state between adapters.
- `AdapterRegistry` uses class-level state; provide a `reset()` method for test teardown.

---

## Testing

### Framework and tools
- **pytest** with built-in `tmp_path` fixture for filesystem isolation.
- `click.testing.CliRunner` for CLI command tests.
- `monkeypatch` for patching (no external mocking libraries — `unittest.mock` is acceptable but
  prefer `monkeypatch.setattr`).
- `conftest.py` autouse fixture `reset_adapter_registry` clears the registry after every test.

### Test organisation
- Mirror the source layout: `tests/test_adapters/test_cursor.py` ↔
  `src/agentmux/adapters/cursor.py`.
- TOML fixture strings are **module-level constants** (e.g. `TOML_CURSOR_ONLY`), not inline.

### Naming
```
test_<unit>_<what_it_verifies>
# e.g.
test_agent_from_file_with_frontmatter
test_deep_merge_nested
test_sync_cursor_creates_agent
```
Add a docstring on tests where the intent is not obvious from the name.

### Assertions
- Direct equality: `assert agent.name == "My Agent"`
- Content checks: `assert "name: Helper" in content`
- Error paths: `pytest.raises(ValueError, match="pattern")` — always supply `match`.
- Boolean checks: `assert x` / `assert not x` — never `assertEqual`.

### Before adding a test
Check that coverage for the intended behaviour does not already exist. Search with:
```bash
rg "test_<keyword>" tests/
```

### After any code change
Always run the full test suite to confirm nothing regressed:
```bash
pytest
```

---

## Architecture Decisions

- **Adapter pattern** is the extension point. Each tool adapter implements the `ToolAdapter` ABC.
  New tool support = new file in `src/agentmux/adapters/`, registered via `AdapterRegistry`.
- **Canonical format** is Markdown + YAML frontmatter. Adapters are responsible for all
  format translation; the core never writes tool-specific syntax.
- **`agentmux.toml`** is the user-facing config. `Setup.from_toml()` is the single parse entry
  point; never read TOML elsewhere.
- The sync engine returns structured `SyncAction` objects — it does not print or `sys.exit`.
  Only the CLI layer surfaces results to the user.
