Development
Everything you need to hack on FestIn safely.
Setup
Commands
# full test suite — ALWAYS double timeout (some tests hang without it)
timeout 130 uv run pytest --timeout=30 -q # → 305 passed
# single file
timeout 60 uv run pytest tests/test_service.py --timeout=30 -q
# lint
uv run ruff check festin/
# complexity gate (CI enforces ≤ 10 per function)
uv run python -c "from radon.complexity import cc_visit; from pathlib import Path; \
print([(b.name, b.complexity) for f in ['database','router','scheduler'] \
for b in cc_visit(Path(f'festin/service/{f}.py').read_text()) if b.complexity > 10])"
# docs preview
uv run mkdocs serve
Rules of the repo
- Tests always with double timeout. Some tests hang indefinitely otherwise — this bit twice.
- Never
git pushwithout explicit instruction from the owner. -
JS after surgery:
node --checkonly catches syntax. Verify every called function is defined (this caused two real bugs:startSession,refreshAll):node -e "const s=require('fs').readFileSync('festin/service/static/js/app.js','utf8'); const SKIP=new Set(['function','if','for','while','catch','return','typeof','new','fetch','Error','clearTimeout','setTimeout','Date','encodeURIComponent','URLSearchParams','String','parseInt','clearInterval','setInterval','atob','Promise','JSON','document','window','localStorage','console','RegExp','Set','Math']); const called=[...s.matchAll(/(?<![.\w])(\w+)\s*\(/g)].map(m=>m[1]).filter(f=>!SKIP.has(f)); const missing=[...new Set(called)].filter(f=>!new RegExp('function\\\\s+'+f+'\\\\b').test(s)); console.log(missing.length? 'MISSING: '+missing.join(', ') : 'all defined');" -
Frozen contracts — breaking them is a regression:
GET /queues/schedulereturns the keyscheduled(notschedules)POST /auth/registeris never exempt in the JWT middleware (middleware identifies, handler authorizes)servemodule CLI uses--db(not--state)- findings/buckets are persisted as rows, not just counters
-
VERSION POLICY — never bump the minor without the owner's explicit order. Patch bumps only (0.4.0 → 0.4.1) autonomously when a publish is needed. Minor decisions (0.4.0 → 0.5.0) belong to Daniel.
-
Static assets are cache-busted with
?v=Ninindex.html— bump the version on every JS/CSS change, or browsers serve stale copies. -
HTML edits: validate tag balance after touching
index.html(a lost tag once made the whole SPA inert): -
UI verification is done with a headless browser against the real server (
localhost:8420) withlocalStorage.clear()first — residual state lies.
Testing conventions
- pytest-asyncio in
automode; async fixtures allowed. - Tests for the service live in
tests/test_service.py(core) andtests/test_service_projects.py(projects/persistence/users/stats). - Complexity gate: every function ≤ 10 (radon) —
tests/test_complexity.pyfails CI otherwise. Refactor, don't fight it. - New constants-only module? Add it to
constant_onlyintests/test_complexity.py.
Project layout
festin/ scanner core
festin/service/ dashboard (aiohttp)
festin/service/static SPA (vanilla JS)
tests/ 305 tests
docs/ MkDocs Material (this site)
Documentation
Screenshots for the docs are taken from the real dashboard (headless browser, PNG); keep them in docs/img/.
Commit convention
type(scope): summary — e.g. fix(spa): restore content wrapper, feat(service): multi-project schema.