# Contributing Thank you for helping build LookSee. This guide covers how to set up a development environment, the checks a change has to pass, and how commits and pull requests are shaped. Everyone participating agrees to the [Code of Conduct](CODE_OF_CONDUCT.md). ## Ways to contribute - **Report a bug** with the bug report issue form. Include the commit or release, the deployment shape, and the logs of the affected service. - **Propose a feature** with the feature request form. Describe the problem before the solution; small, well-scoped proposals land faster. - **Report a vulnerability** privately as described in [SECURITY.md](SECURITY.md). - **Improve documentation** in the [looksee-docs](https://github.com/okoflow/looksee-docs) repository; the README files in this repository describe the code, not the product. - **Send a pull request** for an open issue. Comment on the issue first for anything larger than a fix, so the approach is agreed before the work. ## Repository layout | Path | What lives there | | --- | --- | | `apps/api` | FastAPI control plane: workflows, cameras, events, actions, alerts | | `apps/inference` | ONNX detection service: decoding, models, tracking | | `apps/studio` | Next.js interface: workflow editor and live monitor | | `packages/shared` | Pydantic wire contracts between the API and inference | | `ee/api` | Enterprise node types and integrations under `ee/LICENSE` | | `models` | Model bundles: `manifest.json` plus an ignored `model.onnx` | | `docker`, `compose.yaml` | MediaMTX configuration and the deployment stack | Each Python package and Studio has a README that explains its internal layout and the rules for extending it. ## Development setup Prerequisites: [uv](https://docs.astral.sh/uv/), Python 3.12, Node.js 24, [pnpm](https://pnpm.io/) 11, Docker with Compose 2.24 or later. ```bash git clone https://github.com/okoflow/looksee.git cd looksee cp .env.example .env uv sync --all-packages --extra cpu pnpm -C apps/studio install --frozen-lockfile ``` Run the infrastructure in containers and the services natively. Set private `POSTGRES_PASSWORD`, `MTX_MEDIA_PASSWORD`, and `STORAGE_PASSWORD` values in `.env`. For native services, set `DATABASE_URL` to the local PostgreSQL address and `S3_ENDPOINT_URL=http://localhost:9000`, `S3_ACCESS_KEY_ID=looksee`, and `S3_SECRET_ACCESS_KEY` to the storage password. `compose.native.yaml` connects MediaMTX to the host API and shares the host playback cache. The API must be reachable before media playback starts. ```bash mkdir -p data/media-cache docker compose -f compose.yaml -f compose.native.yaml up -d --build postgres redis mediamtx storage-init uv run --package looksee-api alembic -c apps/api/alembic.ini upgrade head uv run --package looksee-api fastapi dev apps/api/src/api/main.py uv run --package looksee-inference faststream run inference.main:app pnpm -C apps/studio dev ``` Studio serves `http://localhost:3000`, the API `http://localhost:8000` with OpenAPI at `/docs`. `docker compose up -d --build` runs the whole stack from images instead. Model bundles are not committed. Place a directory under `models/` with a `manifest.json` and a `model.onnx`; the API discovers it on the next request. ## Checks Continuous integration runs the same commands on every pull request. Run them before you push. ```bash uv run ruff check . uv run ruff format --check . uv run pytest uv run --package looksee-api alembic -c apps/api/alembic.ini check pnpm -C apps/studio lint pnpm -C apps/studio lint:fsd pnpm -C apps/studio typecheck pnpm -C apps/studio build docker compose --env-file .env.example config --quiet ``` `alembic check` needs a database at head: run `upgrade head` first. `ruff format .` and `pnpm lint:fix` apply the formatting fixes. ## Code guidelines ### Python - Dependencies point inward: `entrypoints` call `application` use cases, which use `domain` models and application-owned ports. Keep FastAPI, SQLAlchemy, and transport details out of `domain`. - Prefer short functions. Separate the logical phases inside a function (guards, preparation, action, return) with one blank line, and name intermediate values instead of writing dense expressions. - No comments that restate the code and no re-export boilerplate. Document why, not what. - Ruff enforces the rule set in `pyproject.toml`, including security (`S`), async (`ASYNC`), and timezone (`DTZ`) checks. Do not add `noqa` without a reason next to it. - Keep applied migrations immutable. Add a forward migration for ORM changes, test it on a fresh database and an existing database, and confirm with `alembic check`. The delivery outbox starts the incremental history after `0001`. - `shared` contracts reject unknown fields and have no legacy aliases. When a payload changes, change the API and the inference service in the same pull request. ### Studio - Studio follows Feature-Sliced Design: `app`, `_pages`, `widgets`, `features`, `entities`, `shared`. Import across slices only through a slice's `index.ts`; `pnpm lint:fsd` checks the boundaries. - Biome, configured through Ultracite, formats and lints the code. Add components from shadcn into `src/shared/ui`. - Runtime configuration comes from `RUNTIME_*` variables validated on the server; never read `process.env` in client code. ### Tests - Tests live next to each package: `packages/shared/tests`, `apps/api/tests`, `apps/inference/tests`, `ee/api/tests`, and run with `uv run pytest`. - Unit tests do not need PostgreSQL, Valkey, MediaMTX, or the network. Use the in-memory adapters and small fakes for ports. - Integration tests use `TEST_DATABASE_URL` and `TEST_REDIS_URL`; without those variables they are skipped. Each PostgreSQL test creates and removes its own schema; Valkey tests use unique keys. CI supplies both services and runs them. - Name tests after the behaviour they prove, one module per source module. ## Commits Commits follow [Conventional Commits 1.0.0](https://www.conventionalcommits.org/en/v1.0.0/): `type(scope): description`. - Types: `feat`, `fix`, `docs`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`. Only `feat` and `fix` move the version. - The scope is an area such as `api`, `inference`, `studio`, `shared`, `ee`, never a file name. - The description is imperative and lowercase, under 72 characters, without a trailing period. It says what the change does, not what task it closes. - A breaking change carries `!` before the colon and a `BREAKING CHANGE:` footer that states what an operator must do. - One commit is one logical change that builds and passes on its own. Add a body only for what the diff cannot show: the problem, the reason for the approach, a constraint. Footers such as `Fixes #12` go last. - Commits credit people. Do not add generator or tooling trailers. ## Pull requests - One pull request is one standalone change. Split anything above roughly 400 changed lines unless the parts only make sense together; stack dependent branches on each other rather than on `main`. - The title is the merge commit subject and follows the commit format. The body says what changed and why in a few sentences and names the risky spot. Add `Verified:` with the checks you ran and `Fixes #N` when it closes an issue. - Attach a screenshot or recording for visible Studio changes. - CI has to pass. Reviews focus on correctness, layering, and whether the change is the smallest one that solves the problem. ## Licensing of contributions Code outside `ee/` is licensed under Apache-2.0 and code inside `ee/` under the [LookSee Enterprise license](ee/LICENSE). By submitting a contribution you agree that it is licensed under the license of the directory it lands in.