Migrate to the FabledRulebook CI-Runner contract: - .forgejo/workflows/ci.yml: all four jobs (typecheck/lint/test/build) now schedule on the `python-ci` runner label and run inside container.image: git.fabledsword.com/bvandeusen/ci-python:3.14 (Python 3.14 + Node 24 + ruff + uv + Docker CLI). Dropped the inline uv install in the test job — uv is now baked into the image. - Dockerfile: production runtime bumped to python:3.14-slim so test results stay representative against what we ship. - ci-requirements.md: new file at repo root declaring image deps and per-job installs (per FabledRulebook ci-runners.md). - infra/Dockerfile.runner-base: deleted. The in-repo runner base (Ubuntu 24.04 + Python 3.12 + Node 22) is superseded by the shared ci-python image. The runner-host deployment files (runner-compose.yml + act-runner-config.yml) stay as deployment-shape documentation; source of truth is the deployed config. - docs/development.md: CI/CD + Runner sections refreshed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5.9 KiB
Development
Workflow
All development is Docker-based. Do not install Python or Node dependencies locally.
# Start the full stack (app + PostgreSQL + Ollama)
docker compose up --build
# Rebuild after backend changes (frontend changes require rebuild too)
docker compose up --build app
# Reset everything (wipes database)
docker compose down -v && docker compose up --build
# Run checks (lint, format, typecheck, tests)
make check
# Individual checks
make lint # ruff check src/
make fmt # ruff format src/
make typecheck # vue-tsc --noEmit
make test # pytest tests/
Frontend Hot Reload
The Docker setup does not include Vite's hot-reload dev server. After frontend changes, rebuild the image. For faster iteration during active frontend work, you can run Vite locally:
cd frontend
npm install
npm run dev # Vite dev server at http://localhost:5173
Point the Vite dev server at the backend by setting VITE_API_BASE_URL=http://localhost:5000 (or configure vite.config.ts proxy).
Database Migrations
Alembic migrations run automatically on container startup (alembic upgrade head in the CMD).
To create a new migration:
# Inside the running app container
docker compose exec app alembic revision -m "description_of_change"
# Edit the generated file in alembic/versions/
Migration conventions:
- Use raw SQL with
IF NOT EXISTSguards for idempotency - Use
DO $$ BEGIN CREATE TYPE … EXCEPTION WHEN duplicate_object THEN NULL; END $$for enum types - Number migrations sequentially (e.g.
0027_add_something.py) - Always provide both
upgrade()anddowngrade()
CI/CD
Pipeline
CI runs on Forgejo Actions, consuming the shared
ci-python:3.14 image
via container.image (Python 3.14 + Node 24 + ruff + uv + Docker CLI):
| Trigger | Jobs | Docker tags pushed |
|---|---|---|
Push to dev |
typecheck + lint + test → build | :dev, :<sha> |
Tag v* on main |
typecheck + lint + test → build | :latest, :<version>, :<sha> |
Push to main |
typecheck + lint + test | (no build) |
Release Process
- Work on
devbranch — CI validates on every push - When ready, open a PR from
dev→mainin Forgejo - Merge the PR
- Create a release via the Forgejo UI on
mainwith av*tag (e.g.v26.03.23.1— CalVer:YY.MM.DD.N) - The tag push triggers CI → build job pushes
:latest+:<version>Docker images - After merging to main, sync dev back:
git checkout dev && git merge main && git push origin dev
Runner
CI jobs schedule against the python-ci runner label and run inside the
shared git.fabledsword.com/bvandeusen/ci-python:3.14 image (see
ci-requirements.md for what this project relies on from the image).
The runner deployment lives outside this repo; image bumps happen in
CI-Runner via Renovate.
infra/runner-compose.yml + infra/act-runner-config.yml document the
runner-host deployment shape; the source of truth is the deployed
config on the runner host.
Docker Registry
Images pushed to: git.fabledsword.com/bvandeusen/fabledassistant
Cache tag: :cache (reduces build time ~80%)
Required secrets (repo → Settings → Secrets → Actions):
REGISTRY_USER— Forgejo usernameREGISTRY_TOKEN— Forgejo PAT withwrite:packagesscope
Migration Chain
Current migration sequence (all idempotent raw SQL):
0001 create_notes_table
0002 create_tasks_table
0003 task_note_companion (data migration)
0004 merge_tasks_into_notes
0005 add_chat_tables
0006 add_settings_table
0007 add_title_and_updated_at_indexes
0008 add_users_and_user_id
0009 add_message_status
0010 add_app_logs_table
0011 add_password_reset_tokens
0012 add_invitation_tokens
0013 add_tool_calls_to_messages
0014 add_note_embeddings
0015 add_oauth_fields
0016 add_image_cache
0017 add_projects
0018 add_push_subscriptions
0019 add_events (dead code — internal CalDAV/Radicale table; Radicale was removed)
0020 add_milestones
0021 add_task_logs
0022 add_note_versions_and_drafts
0023 add_tags_to_note_versions
0024 add_session_version
0025 add_sharing_and_notifications
0026 add_briefing_tables
0027 add_api_keys
Important: Do NOT use op.create_table() or sa.Enum() — SQLAlchemy's event system can fire CREATE TYPE even with create_type=False, causing failures on re-run. Always use raw SQL with IF NOT EXISTS / DO $$ BEGIN ... EXCEPTION WHEN duplicate_object guards.
Project Conventions
Backend
- Services:
async with async_session() as session:— import fromfabledassistant.models - No
fabledassistant.databasemodule - Blueprint per resource:
routes/notes.py,routes/tasks.py, etc. - All business logic in
services/; routes are thin wrappers - Permission checks via
services/access.py— never inline ownership checks in routes
Frontend
- API calls via
frontend/src/api/client.tstyped helpers (apiGet,apiPost,apiPatch,apiDelete) - Pinia stores for shared state; local
ref()for component-only state - Composables in
composables/for reusable behaviour (autosave, keyboard nav, tag suggestions, …) - Views are page-level components in
views/; reusable UI incomponents/
Commit Style
type(scope): short description
Longer body if needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Types: feat, fix, refactor, docs, chore, test
Scopes: feature area (e.g. chat, briefing, fable-mcp, notes)
Testing
# Run all tests
make test
# Run specific test file
docker compose exec app /opt/venv/bin/pytest tests/test_auth.py -v
Tests are in tests/. They run against a real PostgreSQL instance in CI (not mocked). Keep tests integration-style where possible — mock failures have historically masked real migration bugs.