Setup
Prerequisites: Python 3.11+, uv, Node.js 18+, pnpm.LLM_API_KEY is only required to run the backend locally or to preview auto-translated locale output on commit. Contributors without a key can still commit EN changes — a GitHub Actions workflow translates on master after PR merge. See i18n below.Type safety
mypy strict mode is enforced. The entire codebase passes with zero errors, and the pre-commit hook runs mypy on every staged.py file with full import chain checking.
Rules:
- Type hints required on all public functions.
- No
# type: ignoreexcept forimport-untyped(third-party libs without stubs). - Use
from __future__ import annotationsfor forward references. - Fix the actual types — never suppress errors to make CI pass.
Testing
Every new module must have a corresponding test file. Every feature commit must include tests.| Convention | Pattern |
|---|---|
| File | tests/test_{module}.py |
| Class | Test{Feature} |
| Method | test_{behavior_under_test} |
- All existing tests must pass before committing:
uv run pytest tests/ -x -q. - Tests must not depend on external services — mock databases, MCP servers, HTTP endpoints, and LLM calls using
unittest.mock/AsyncMock. - Run tests with coverage:
uv run pytest --cov=fim_one.
Code style
- Async-first. Use
async deffor I/O-bound operations. Wrap synchronous calls withasyncio.to_thread()rather than blocking the event loop. - Ruff for linting.
uv run ruff check src/— line length is 100 characters. - Minimal
__init__.pyexports. Only re-export the public API; keep internal symbols private. - Package managers.
uvfor Python,pnpmfor frontend. Never usepipornpm.
Git conventions
Atomic commits. One logical change per commit. Split unrelated changes even if they were developed together. Commit message format:type: description
| Type | When to use |
|---|---|
feat | New user-facing feature |
fix | Bug fix |
refactor | Code restructuring with no behavior change |
docs | Documentation only |
test | Adding or updating tests |
chore | Build, CI, dependency updates |
--no-verify. The pre-commit pipeline exists to catch errors before they reach the repository.
Pre-commit hook pipeline
The hook runs automatically on every commit. It only processes staged files relevant to each step — if you only changed Python files, the i18n step is skipped, and vice versa.| Order | Step | Trigger | What it does |
|---|---|---|---|
| 0 | Locale-edit guard | Any staged file under messages/{locale}/, docs/{locale}/, or README.{locale}.md | Aborts the commit — no override. Fix translations via scripts/translation-glossary.md instead. |
| 1 | OpenAPI spec regen | src/fim_one/web/**/*.py changed | Re-exports docs/openapi.json from FastAPI routes |
| 2 | i18n translation | messages/en/*.json, docs/*.mdx, or README.md changed (needs local LLM_API_KEY, else CI handles it after merge) | Translates new/changed keys into all target locales |
| 3 | mypy type checking | Any src/fim_one/**/*.py changed | Runs mypy with full import chain on staged files |
| 4 | MDX validation | Any .mdx file staged | Validates JSX/MDX syntax before it reaches Mintlify |
i18n
FIM One supports 6 locales (EN, ZH, JA, KO, DE, FR). Translations are fully automated. Only edit English source files:frontend/messages/en/{namespace}.json— UI stringsdocs/*.mdx(root level, not locale subdirs) — documentationREADME.md— project readme
messages/zh/, docs/zh/, README.zh.md, etc.) are auto-generated and the pre-commit hook unconditionally refuses commits that manually edit them. To fix a mistranslation, edit scripts/translation-glossary.md — the prompt-injected single source of truth for every translation rule — then regenerate affected files with uv run scripts/translate.py --files <en-sources> --force. Every glossary fix applies to all five locales permanently.
Two paths to generating locale files:
- Local pre-commit hook — runs if
LLM_API_KEYis set in.env. Translation happens before push, so you can preview the output. - CI fallback — if the local hook had no key and skipped,
.github/workflows/i18n-sync.ymltranslates onmasterafter merge and auto-commits the result.
messages/en/{ns}.json — other locale files are generated on the next commit or on CI after merge.
Force full retranslation: uv run scripts/translate.py --all (requires local LLM_API_KEY).
Database migrations
Dev uses SQLite, production uses PostgreSQL. One set of Alembic migration files must work on both. Key rules:- Every new ORM model or column must have a migration — never rely on
metadata.create_all(). - All migrations must be idempotent — use helpers from
fim_one.migrations.helpers(table_exists,table_has_column,index_exists). - Boolean defaults:
server_default=sa.text("FALSE")/sa.text("TRUE"). Never"0"/"1"— PostgreSQL rejects integer literals for Boolean columns. - SQLite cannot
ALTER COLUMN— useop.batch_alter_table()for column constraint changes. - After writing a migration, immediately run
uv run alembic upgrade headto apply it.