CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / integration (push) Successful in 1m1s
CI & Build / Python tests (push) Successful in 2m2s
CI & Build / Build & push image (push) Successful in 28s
The forge repo is now bvandeusen/FabledGrimoire. The plugin marketplace default, the install commands in the docs and plugin README, the CI requirements title and the example repo URLs in comments and tests follow it. docs/development.md now names the image the build actually publishes, fabledgrimoire. Two test names still said "scribes_own"; they now say "grimoires_own". What still says scribe does so on purpose: - the backup reader's accepted archive names and prefix; - the label every stored secret is sealed under; - the notes on keeping an old install's database names; - the old migrations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
88 lines
4.2 KiB
Markdown
88 lines
4.2 KiB
Markdown
# 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`, `:<sha>` |
|
|
| `main` | `:latest`, `:<sha>` |
|
|
| a `v*` tag | `:latest`, `:<version>`, `:<sha>` |
|
|
|
|
`:<sha>` never moves, so it is the rollback point. Images publish to `git.fabledsword.com/bvandeusen/fabledgrimoire`, 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.
|