# Contributing FabledCurator is developed by a single maintainer for their own use, and published because it may be useful to others. That shapes what contribution looks like here. **Issues are welcome** — bug reports, and questions about running it, are genuinely useful and often the fastest way to find out that something is broken outside the one environment it was built in. **Open an issue before writing a pull request.** Not as a formality: the project has opinions that are not obvious from the code, and it is unpleasant for everyone when a finished patch turns out to conflict with one. A short issue first costs you nothing and may save you an evening. **Contributions are licensed under the AGPL-3.0**, like the rest of the project. By submitting one you agree it ships under that licence. There is no CLA and no copyright assignment. ## Running it for development ```bash docker compose up -d # UI on http://localhost:8080 ``` The dev override (`docker-compose.override.yml`) is auto-merged and builds the app images locally from source, so this needs no `.env` and no registry access. Postgres and Redis ports are exposed on the host. ## What CI checks Every push runs these, and they are the definition of done for a change: ```bash ruff check backend/ tests/ alembic/ agent/ scripts/ # lint (and import order) pytest tests/ -m "not integration" # backend unit tests pytest tests/ -m integration # needs pgvector + redis cd frontend && npm run test:unit && npm run build # frontend ``` The integration lane builds its schema by running the real migrations (`alembic upgrade head`), never from ORM metadata — so a migration that does not apply cleanly fails CI rather than being discovered later. Note for the linter: ruff's isort runs with `order-by-type`, which sorts ALL-CAPS names ahead of CamelCase. `from sqlalchemy import JSON, DateTime, ...` is correct; putting `JSON` alphabetically between `Integer` and `String` is not. This catches people out. ## Database changes The ORM models and the migration chain must agree. This is enforced, and it is enforced because they silently diverged for a long time and nobody noticed until they were compared: the models were missing indexes, defaults and uniqueness guarantees that only ever existed inside a migration, which made `alembic revision --autogenerate` actively unsafe to run. So: if you change a model, write the migration; if you write a migration, change the model to match. Both, in the same commit. Adding a value to a CHECK-constrained column means swapping the constraint in the same change — the constraint is not documentation, and a new value without it fails at insert time. ## Branch model `dev` is where work happens. `main` is production and is only reached by a merge from `dev`, never pushed to directly. If you are sending a pull request, target `dev`. ## Style Match the surrounding code. The one convention worth stating explicitly is that comments here explain *why*, especially where a choice looks wrong at a glance — a comment recording which migration a constraint came from, or why a default is a `text()` rather than a string, is the kind that has repeatedly turned out to be worth its space.