Skip to main content
This page covers development conventions and code quality standards. For the full contributing guide — including the Pioneer Program, field testing, PR process, and community links — see CONTRIBUTING.md on GitHub.

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.
Verify everything works:

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: ignore except for import-untyped (third-party libs without stubs).
  • Use from __future__ import annotations for 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. Rules:
  • 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 def for I/O-bound operations. Wrap synchronous calls with asyncio.to_thread() rather than blocking the event loop.
  • Ruff for linting. uv run ruff check src/ — line length is 100 characters.
  • Minimal __init__.py exports. Only re-export the public API; keep internal symbols private.
  • Package managers. uv for Python, pnpm for frontend. Never use pip or npm.

Git conventions

Atomic commits. One logical change per commit. Split unrelated changes even if they were developed together. Commit message format: type: description Never skip hooks with --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. If any step fails, the commit is aborted. Fix the reported errors and retry.

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 strings
  • docs/*.mdx (root level, not locale subdirs) — documentation
  • README.md — project readme
Other locale files (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:
  1. Local pre-commit hook — runs if LLM_API_KEY is set in .env. Translation happens before push, so you can preview the output.
  2. CI fallback — if the local hook had no key and skipped, .github/workflows/i18n-sync.yml translates on master after merge and auto-commits the result.
Contributors without an API key can commit EN changes freely — the CI path ensures locales land correctly. Adding a new i18n namespace: Create 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 — use op.batch_alter_table() for column constraint changes.
  • After writing a migration, immediately run uv run alembic upgrade head to apply it.

Quick reference