1
Development and CI
Latte edited this page 2026-06-29 13:55:51 +00:00

Development & CI

How to develop, test, and ship familiar.

Commands

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), 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:

  • 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 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.