From 3106654b09e905996f41e7f3c6c725930cac322d Mon Sep 17 00:00:00 2001 From: Latte <1+latte@noreply.hiddenden.cafe> Date: Mon, 29 Jun 2026 13:55:51 +0000 Subject: [PATCH] Add Development and CI wiki page --- Development-and-CI.md | 65 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 Development-and-CI.md diff --git a/Development-and-CI.md b/Development-and-CI.md new file mode 100644 index 0000000..e6becb3 --- /dev/null +++ b/Development-and-CI.md @@ -0,0 +1,65 @@ +# Development & CI + +How to develop, test, and ship familiar. + +## Commands + +```bash +uv sync # install deps (incl. dev group) +uv run pytest # full suite; FAILS under 80% coverage +uv run pytest tests/test_market.py::test_get_market_price # a single test +uv run ruff format . # format +uv run ruff check . # lint +uv run familiar # run the MCP server over stdio +``` + +Run `ruff format` and `ruff check` before committing — **CI enforces both**. + +There is no separate build step; `uv build` produces the sdist + wheel (CI does this on merge). + +## Project layout + +- `src/familiar/` — the package. One module per tool group (see **[Architecture](Architecture)**), plus the shared `esi`, `zkill`, `sde`, `auth`, `charutil`, `config`, `writes`, and `server` modules. +- `tests/` — one test module per source module, driven by `tests/conftest.py`. +- `pyproject.toml` — deps, scripts, and tool config (ruff line length 100, pytest coverage gate). + +## Testing conventions + +Tests use [`tests/conftest.py`](https://git.hiddenden.cafe/Hiddenden/familiar/src/branch/main/tests/conftest.py): + +- The **`mock_esi`** fixture installs a mock ESI client driven by a request handler; **`route(req, {substring: body})`** dispatches by path substring. +- Async tools are driven with `asyncio.run(...)` — there is **no async-test plugin**. +- Authenticated-tool tests **monkeypatch** `auth.list_characters` / `auth.get_auth` / `auth.resolve_character` rather than touching the real token store. +- Write tests set `FAMILIAR_DATA_DIR` to a tmp dir and toggle `FAMILIAR_ENABLE_WRITES`. +- **`make_jwt(claims)`** builds fake access tokens. +- The ESI/zKill clients and the SDE connection are swapped for mocks via `esi.set_client` / `zkill.set_client` / `sde.set_connection`. + +Coverage is gated at **80%** (`--cov-fail-under=80` in `pyproject.toml` addopts), so the suite fails if coverage drops below that. + +## Git workflow + +- Work happens on **`dev`**. Feature branches → PR → **fast-forward** merge into `dev` → delete the branch. +- Cut a release by bumping the version in `pyproject.toml` / `src/familiar/__init__.py` and opening a **`dev → main`** fast-forward PR (which publishes the clean version). +- **`main` and `dev` are protected**: no direct pushes, **signed commits required** (the user's git auto-signs), merge via PR only. + +## CI / publish pipeline + +[`.gitea/workflows/ci.yml`](https://git.hiddenden.cafe/Hiddenden/familiar/src/branch/main/.gitea/workflows/ci.yml) runs on every PR and push to `main`/`dev`: + +1. **lint** — `ruff check .` and `ruff format --check .`. +2. **test** — `uv run pytest` (the 80% coverage gate). +3. **publish** — only on a real **push/merge** into `main` or `dev` (never on PRs), and only after lint + test pass. It builds the sdist + wheel with `uv build` and uploads to the **Gitea PyPI registry** with twine. + +Version rules in the publish job: + +- **`main`** publishes the clean version from `pyproject.toml`. +- **`dev`** publishes a unique pre-release `X.Y.Z.devN` (where `N` is the run number) so merges never collide. + +Notes: + +- Auth uses the **`REGISTRY_TOKEN`** secret (a Gitea token with package read/write scope) as the twine password. +- Gitea's PyPI registry does **not** support twine's `--skip-existing`; the workflow runs a plain upload and tolerates an "already exists"/409 conflict in the shell (which only bites a `main` re-run of an already-published release). + +## A note on attribution + +Commits, merges, and PRs in this repo are authored plainly by the maintainer — no AI co-author trailers or "generated with" footers. Keep messages clean.