# Development ## Running it ```bash docker compose up --build # app + PostgreSQL at http://localhost:5000 docker compose up --build app # rebuild after a change docker compose down -v # stop and wipe the database ``` The image builds the frontend, so a frontend change needs a rebuild too. For quicker frontend work, run Vite against the running app; it proxies `/api` to port 5000: ```bash cd frontend && npm ci && npm run dev # http://localhost:5173 ``` ## Checks CI runs these on every push; locally they need `uv sync --extra dev` and `cd frontend && npm ci`. ```bash make lint # ruff check src/ make typecheck # vue-tsc --noEmit make test # pytest tests/ make check # all three ``` Unit tests run without a database. Tests marked `integration` need a real PostgreSQL and run in CI's integration job, against a schema built by the real migrations. ## Database migrations The container runs `alembic upgrade head` on every start, after waiting for the database to accept connections. ```bash docker compose exec app alembic revision -m "short_description" ``` - Number revisions sequentially (`0123_...`). Revision ids must fit in 32 characters, the width of `alembic_version.version_num`. - Write both `upgrade()` and `downgrade()`. - Adding a value to a `CHECK`-constrained column means dropping and re-adding the constraint in the same migration. - Keep migrations to schema changes. Filling a derived table from a large one belongs in a background backfill after startup, where its cost doesn't hold up the boot. - A migration that depends on application code (a key derivation, a serialiser) copies what it needs rather than importing it, so a later edit can't change what an old migration does. ## The plugin `plugin/` ships straight from git: installs fetch it through `.claude-plugin/marketplace.json`, and there is no build step. After changing anything under `plugin/` or `.claude-plugin/`, run `make mint-plugin` before committing. Claude Code only refreshes an installed plugin when its version changes, and CI fails a plugin change that didn't bump it. The hooks must run on a bare machine: POSIX shell, awk, sed and the usual coreutils, plus `git` and `curl`. They fail open, so a slow or unreachable Grimoire never blocks Claude Code. ## CI and releases CI runs on Gitea Actions (workflow in `.forgejo/workflows/ci.yml`) in the shared `ci-python:3.14` image ([`ci-requirements.md`](../ci-requirements.md) lists what it relies on). Six jobs: TypeScript typecheck, plugin hooks, Python lint (plus design-token and dangling-style checks), unit tests, integration tests, and the image build, which waits on all of them except the plugin job. | Push | Image tags | |------|-----------| | `dev` | `:dev`, `:` | | `main` | `:latest`, `:` | | a `v*` tag | `:latest`, `:`, `:` | `:` never moves, so it is the rollback point. Images publish to `git.fabledsword.com/bvandeusen/fabledscribe`, with a registry layer cache at `:cache`. The build needs the `REGISTRY_USER` and `REGISTRY_TOKEN` (a token with `write:packages`) repository secrets. Work happens on `dev`. A release merges `dev` into `main` with a plain merge commit; a dated `vYYYY.MM.DD.HHMM` tag, matching the version the image reports, can mark it. ## Conventions ### Backend - Services open their own session: `async with async_session() as session:` (from `grimoire.models`). - Routes and MCP tools stay thin; the logic lives in `services/`, shared by both. - Access checks go through `services/access.py`, never inline ownership checks. - Every wait on something outside the process has a deadline. ### Frontend - API calls go through the typed helpers in `frontend/src/api/client.ts`. - Pinia stores for shared state, `ref()` for state local to a component; composables for reusable behaviour. - Colours, spacing and type come from the design system's tokens in `theme.css`, which is generated, so CI rejects an unknown `var()`. ### Commits ``` type(scope): what the change does Why, and anything a reader tracing this change later needs. ``` Types: `feat`, `fix`, `refactor`, `docs`, `chore`, `test`. Comments in the code describe how it works today; the history of a change belongs in its commit message.