|
| 1 | +# AGENTS.md — supamem |
| 2 | + |
| 3 | +Operational guide for AI coding agents working in this repository. Project-agnostic dual-memory CLI for Claude Code, Cursor, and OpenCode. |
| 4 | + |
| 5 | +## Project Snapshot |
| 6 | + |
| 7 | +- **Language:** Python 3.12+ |
| 8 | +- **Build backend:** hatchling |
| 9 | +- **Package manager:** `uv` |
| 10 | +- **CLI entry:** `supamem` → `src/supamem/cli.py:main` |
| 11 | +- **Tests:** `pytest` (config in `pyproject.toml`) |
| 12 | +- **Lint/format:** `ruff` (line length 100, target py312) |
| 13 | +- **External deps:** Qdrant 1.10+ (HTTP), MCP 1.13+, fastembed, langchain-text-splitters |
| 14 | + |
| 15 | +## Architecture |
| 16 | + |
| 17 | +``` |
| 18 | +src/supamem/ |
| 19 | +├── cli.py # Typer-based CLI dispatcher |
| 20 | +├── console.py # Shared Rich console + theme (single source of branding) |
| 21 | +├── config.py # Pydantic config schema (D-38) |
| 22 | +├── config_io.py # Discovery: project → user → defaults; merge order locked |
| 23 | +├── doctor.py # Health + drift report (Plan 80.6-11) |
| 24 | +├── init.py # Greenfield bootstrap (Plan 80.6-08) |
| 25 | +├── migrate.py # Brownfield migration paths (Plan 80.6-09) |
| 26 | +├── mcp_server.py # MCP server entrypoint |
| 27 | +├── embedders/ # minilm, bm25 — pluggable via entry-points |
| 28 | +├── indexer/ # chunker (markdown_header), manifest |
| 29 | +├── retrieval/ # tuned_hybrid, dense, bm25 backends |
| 30 | +├── eval/ # Bench runner + bundled goldens (Plan 80.6-12) |
| 31 | +├── hooks/ # claude_code, cursor — per-client snapshot hooks |
| 32 | +├── install/ # claude_code, cursor, opencode — config installers |
| 33 | +├── share/ # Canonical artifact templates |
| 34 | +└── stats/ # Welford counter for usage telemetry |
| 35 | +``` |
| 36 | + |
| 37 | +## Plugin Entry Points (D-48) |
| 38 | + |
| 39 | +Three plugin groups in `pyproject.toml`. Third parties add backends without forking: |
| 40 | + |
| 41 | +- `supamem.retrieval` — `tuned_hybrid`, `dense`, `bm25` |
| 42 | +- `supamem.embedder` — `minilm`, `bm25` |
| 43 | +- `supamem.chunker` — `markdown_header` |
| 44 | + |
| 45 | +## Config Discovery (D-38) |
| 46 | + |
| 47 | +Order: `./.supamem/config.toml` (project) → `~/.config/supamem/config.toml` (user) → `share/default.toml` (shipped). Merge is shallow; project wins. |
| 48 | + |
| 49 | +## Hard Constraints |
| 50 | + |
| 51 | +- NEVER bypass failing tests — fix root cause |
| 52 | +- NEVER hardcode collection names; always read from `config.collection` |
| 53 | +- NEVER print to stdout in MCP server — JSON-RPC contract requires stdio purity (use `err_console`) |
| 54 | +- NEVER write to `~/` outside `~/.cache/supamem/` and `~/.config/supamem/` without explicit user opt-in |
| 55 | +- NEVER delete a Qdrant collection without `--force` flag confirmation |
| 56 | +- ALWAYS use `console.py` exports for terminal output (no bare `print()`) |
| 57 | +- ALWAYS run `pytest` from project root via `uv run pytest` |
| 58 | + |
| 59 | +## Workflow |
| 60 | + |
| 61 | +```bash |
| 62 | +# Setup |
| 63 | +uv sync --extra dev |
| 64 | + |
| 65 | +# Tests |
| 66 | +uv run pytest # full suite |
| 67 | +uv run pytest tests/test_X.py -v # single file |
| 68 | + |
| 69 | +# Lint / type |
| 70 | +uv run ruff check src tests |
| 71 | +uv run ruff format src tests |
| 72 | + |
| 73 | +# Build + verify |
| 74 | +uv build |
| 75 | +uvx twine check dist/* |
| 76 | + |
| 77 | +# Run locally |
| 78 | +uv run supamem --help |
| 79 | +uv run python -m supamem doctor |
| 80 | +``` |
| 81 | + |
| 82 | +## Test Discipline |
| 83 | + |
| 84 | +- Subprocess-based CLI smoke tests (`test_cli_smoke.py`) MUST pin a deterministic env: `NO_COLOR=1`, `TERM=dumb`, `COLUMNS=200` — pop `FORCE_COLOR`. Rich autodetect alone is insufficient on CI runners. |
| 85 | +- Use `pytest-asyncio` with `mode=Mode.STRICT`; mark async tests with `@pytest.mark.asyncio`. |
| 86 | +- Mock Qdrant via fixtures, not by hitting a live instance — bench/eval suites are the integration boundary. |
| 87 | + |
| 88 | +## Release Process |
| 89 | + |
| 90 | +1. Bump `version` in `pyproject.toml` and add `CHANGELOG.md` entry. |
| 91 | +2. Verify license metadata complies with PEP 639 (`license = "MIT"` SPDX, no `License ::` classifier). |
| 92 | +3. `uv build && uvx twine check dist/*` |
| 93 | +4. Create annotated tag: `git tag -a vX.Y.Z -m "..."` |
| 94 | +5. Push tag: `git push origin vX.Y.Z` — release workflow publishes to PyPI via Trusted Publisher OIDC. |
| 95 | +6. Verify on PyPI: `pip install supamem==X.Y.Z` in a clean venv. |
| 96 | + |
| 97 | +PyPI tags are immutable: never re-use a published version number. |
| 98 | + |
| 99 | +## Update-check (v0.1.1+) |
| 100 | + |
| 101 | +A daemon thread probes GitHub Releases on every CLI invocation, caches result for 24h in `platformdirs.user_cache_dir("supamem")/update_check.json`, and prints a stderr footer on the *next* invocation if a newer version is available. Suppress with `SUPAMEM_NO_UPDATE_CHECK=1`, `CI=1`, or `NO_UPDATE_NOTIFIER=1`. Never blocks; never raises. |
| 102 | + |
| 103 | +## Reference Links |
| 104 | + |
| 105 | +- README: high-level overview, install, quickstart |
| 106 | +- MIGRATION.md: version-to-version upgrade notes |
| 107 | +- CHANGELOG.md: release log |
| 108 | +- `docs/` (if present): ADRs, design docs |
| 109 | + |
| 110 | +## Decision Guides |
| 111 | + |
| 112 | +- New backend: register via entry-point group, do NOT modify `cli.py` dispatch |
| 113 | +- New CLI subcommand: add Typer command in `cli.py`, route to module under `src/supamem/` |
| 114 | +- New config field: extend `config.py` Pydantic schema + bump default in `share/default.toml` |
| 115 | +- New hook target: add module under `src/supamem/hooks/<client>.py`, register in `cli.py hook` dispatcher |
| 116 | +- Failure in network code: blanket `except Exception: pass` is correct for non-essential probes (update_check); for indexing/retrieval, surface error to user via `err_console` |
0 commit comments