CONTRIBUTING.md
Rama main · réplica de lectura del repositorio original.
# Contributing to Snagr
Thanks for considering it. This is a small project with strong conventions — most of them are enforced by CI, so this page is mainly about not being surprised.
For orientation, [AGENTS.md](AGENTS.md) is the working guide (commands, invariants, house rules — the same file coding agents read), and [backend/STRUCTURE.md](backend/STRUCTURE.md) and [agent/STRUCTURE.md](agent/STRUCTURE.md) map those components file by file.
## Dev setup
Each Python component (`backend/`, `agent/`, `vision/`) keeps its own virtualenv; always invoke tools through it:
```bash
cd backend && python -m venv venv && ./venv/bin/pip install -r requirements.txt
./venv/bin/pytest
```
The frontend is plain npm (`npm ci`, `npm run dev`, `npm test`); it proxies `/api` to `localhost:8000` by default, or runs standalone against a full mock API with `VITE_USE_MOCKS=true npm run dev` — see [frontend/README.md](frontend/README.md).
## The test contract
- Backend and agent tests need a reachable **PostgreSQL with the pgvector extension**; vision tests too. Point `DATABASE_URL` (or `backend/.env`) at it.
- Suites never touch your data: `conftest.py` rewrites the URL's last path segment to a throwaway database (`snagr_test`, or `snagr_test_vision` for the vision suite) before importing anything, and asserts the rewrite changed the URL. Those test databases must exist on the server, and the test role needs `CREATEDB` (the migration test builds a scratch database) and the right to create the `vector` extension.
- **The backend and agent suites share `snagr_test`** and both truncate it — run them one after the other, never at the same time.
- Frontend unit tests are vitest (`npm test`) over `src/**/*.test.ts`, for pure logic; anything visual is verified by driving the app in a browser.
- Every behavior change lands with a test; bug fixes reproduce first with a failing test. Backend tests run against the real DB — don't mock the ORM.
## Style
- Python is settled by **ruff** (`./venv/bin/ruff check --fix && ./venv/bin/ruff format`); the frontend by **oxlint** + the TypeScript build + vitest (`npm run lint && npm run build && npm test`). CI runs all of it, linting the whole repo from the root with a pinned `ruff==0.16.2` — if your venv's `ruff --version` differs, install that version into it or you'll disagree with CI.
- Everything ruff can't see is settled by precedent: open the sibling file that does the same kind of job and copy its idioms. Boring and explicit beats clever.
- Python dependency pins are Dependabot-managed — don't hand-bump versions in a feature PR. Two exceptions: `torch` in `vision/requirements.txt` is bumped by hand (Dependabot is told to ignore it), and `ruff` is deliberately unpinned in all three requirements files — its version of record is the pin in CI.
## Comments & docstrings
Write for a reader who has never seen the code and wasn't there when it was written.
- **Explain why, not what.** A comment earns its place with a domain rule, a gotcha, or an invariant the code can't show. If it restates the next line, delete it.
- **Describe the code as it is, not how it got here.** No "used to", "now", "no longer", "PR 2a", "Phase 3", "step 2". History belongs in the commit message and the PR.
- **No bare decision codes.** Don't cite `D-V5` or `decision 9`. State the rule in a sentence.
- **Docstrings are prose.** Start with a one-line summary. Add a paragraph only for the contract: what it raises, side effects, and who owns the transaction. Don't add `Args:`/`Returns:` sections that repeat the type hints.
- **Coverage:** every public Python module, class, and function outside `tests/` and `migrations/` has a docstring, and every exported frontend symbol has a JSDoc block. Tests are exempt, because their names say what they check. ruff's `D1` rules enforce the Python side (exemptions live in `ruff.toml`); oxlint has no equivalent, so frontend coverage is checked in review.
- **Placement:** put comments on their own line above the code they explain. Trailing comments are for short notes like `models.py` column annotations; keep the `# + api` markers there.
- **Keep them short.** A comment that runs past about six lines is documentation. Move it to `backend/STRUCTURE.md` or `docs/`.
- **`agent/tools.py` docstrings are prompts.** The model reads them as tool descriptions, so editing one changes the hunter's behavior. Review it like code.
## PRs
- **PR titles are conventional commits** and linted: `feat|fix|refactor|test|docs|style|chore|ci|build|perf|revert`, scope free-form (`feat(backend): ...`). PRs are squash-merged with the title as the commit message, so the title is what history keeps.
- Those titles also drive release-please: `feat` bumps the minor, `fix` the patch, and each title becomes a CHANGELOG line.
- CI is path-gated per component: jobs for parts you didn't touch report "skipped", which is normal and satisfies the required checks. CodeQL is *not* path-gated and runs on every PR, and touching `.github/workflows/**`, `ruff.toml`, or `docker-compose.yml` un-skips every job.
- CI's `migrations` job runs `alembic upgrade head` and `alembic check` against a fresh database, so a model change without its migration (or the reverse) fails there.
- The API contract is defined by the frontend (`frontend/src/api/types.ts` + `frontend/src/mocks/handlers.ts`); the backend is built to match it, not the other way around.