Ctrl K
ComunidadGuías…

CONTRIBUTING.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Contributing to niche-finder

Thanks for considering a contribution. This project is a small, self-hosted
tool, so the bar is low and the process is simple. Taking part means following
the [code of conduct](CODE_OF_CONDUCT.md); security problems go to a private
advisory, not an issue ([SECURITY.md](SECURITY.md)).

## Ways to contribute

- **Bug reports** — open an issue with what you ran, what you expected, and
  what happened. `docker compose run --rm mcp python cli.py doctor` (or
  `make doctor`) is usually the first thing worth including — it checks the
  key, network, and database in one shot.
- **Bug fixes / small improvements** — open a PR directly.
- **New MCP tools / API endpoints / dashboard screens** — please open an
  issue first to agree on the shape before writing code, so you don't end up
  reworking it.
- **Documentation** — typos, unclear steps, outdated commands: PRs welcome,
  no need to ask first.

## Getting set up

```bash
git clone https://github.com/pandich93/youtube-niche-finder.git
cd youtube-niche-finder
cp .env.example .env          # YOUTUBE_API_KEY is optional for local dev
docker compose build
docker compose up -d postgres worker
make doctor
```

Or without Docker — see
[backend/README.md#running-without-docker](backend/README.md#running-without-docker).

## Running the tests

```bash
make up-db        # start just Postgres
make local-install
make local-test   # every backend/tests/test_*.py, no YouTube key or network needed
```

`make local-test` runs the whole `backend/tests/` directory in one pytest
process and exits non-zero if anything fails. CI runs exactly the same
command under `coverage` (see `.github/workflows/ci.yml`) plus a
`docker compose build` check on every push and pull request — both must be
green before a PR is merged.

Every file still sees the world it would see running alone
(`backend/tests/conftest.py`): Postgres-backed files get a fresh schema each
(`tests/schema_scope.py`), and the sqlite-double files (`test_alerts.py`,
`test_inspection.py`, `test_library.py`, `test_metadata_review.py`,
`test_top_tags.py`) keep `infrastructure.postgres` & co. in a private module
world (`tests/module_doubles.py`) instead of writing them into `sys.modules`
— `tests/test_module_doubles.py` fails if a file does the latter.

To run one file, from `backend/`:

```bash
./.venv/bin/python -m pytest -q tests/test_rss.py
./.venv/bin/python tests/test_rss.py     # same tests, built-in runner, no pytest needed
```

`make test` runs only `tests/test_smoke.py`, inside the Docker image
(which has no pytest).

A new `backend/tests/test_*.py` is picked up by CI and `make local-test`
automatically — no workflow edit needed. Postgres-backed tests import
`schema_scope` first (see `test_smoke.py`, `test_mcp_tools.py`); pure logic
and sqlite-double tests follow `test_metadata_domain.py` /
`test_metadata_review.py` (doubles via `ModuleDoubles`, never
`sys.modules[...] = ...`). External calls (YouTube, OpenRouter, Telegram)
must be mocked. If you give a file its own `__main__` runner, make it exit
non-zero when a test fails.

## Code layout

The backend follows a DDD-ish layering — see
[backend/README.md#structure](backend/README.md#structure) for the full
tree. In short:

- `domain/` — pure formulas and parsing, no I/O, no external dependencies.
- `infrastructure/` — adapters to Postgres, the YouTube API (plus channel
  RSS feeds), the local embeddings model, LLM providers (OpenRouter /
  Ollama) and notifications (Telegram / webhook).
- `application/` — use cases that orchestrate domain + infrastructure.
- `interfaces/` — thin entry points (MCP server, HTTP API, CLI, worker).

`server.py`, `api.py`, `cli.py`, and `worker.py` at the top of `backend/`
are shims that just import from `interfaces/` — don't add logic there.
The pre-DDD flat-module code that used to live in `backend/_legacy_flat_modules/`
has been removed; nothing imported it, so there is nothing to keep in sync
with `application/`, `domain/`, and `infrastructure/` anymore.

The frontend (`frontend/`) is plain ES modules with no build step and no
framework — keep it that way. New screens follow the existing pattern in
`screens/*.js` (one file per screen), `router.js` (hash routing), `shared.js`
(pieces several screens use) and `ui.js` (shared components/formatters).

## Style

- `make lint` runs [ruff](https://docs.astral.sh/ruff/) over `backend/`
  (config in `backend/pyproject.toml`, same pinned version as CI, where it
  must pass). There is no auto-formatter; match the style of the surrounding
  code (naming, docstrings, formatting).
- Keep entry-point shims thin — real logic belongs in `domain/`,
  `infrastructure/`, or `application/`.
- Prefer a test against the throwaway Postgres schema over a mock of our own
  code; mock only external services.
- A use case that touches someone's own data takes `*, user_id` with no
  default; the caller names the user. HTTP routes pass `_uid(request)`, MCP
  tools `_uid()`, the CLI and the worker `LOCAL_USER_ID`, tests `user_id=1`.
  `tests/test_user_id_required.py` fails on any `user_id` with a default in
  `application/`: a forgotten argument must be an error, not user 1's data.
- A new score or estimate goes into `backend/domain/score_catalog.py` in the
  same pull request: its formula, inputs, minimum sample and `source`
  (`youtube` or `niche-finder`). YouTube's derived-metrics policy asks API
  clients never to present their own metrics as YouTube data, and the "?"
  tips and the help page read their text from there.

## Documentation

[docs/README.md](docs/README.md) maps every document. When you change
behaviour, update the doc that describes it in the same PR:

- a new or changed MCP tool → its table in
  [backend/README.md#tools](backend/README.md#tools) (and the tool count in
  the README badges if it changed);
- a new or changed HTTP route → run `make api-docs` (it rewrites
  `docs/http-api.md` from the code; add a one-line summary for a new route in
  `scripts/gen_http_api_docs.py`, the script refuses to run without one) —
  CI fails when the reference is out of date;
- a new dashboard screen → [frontend/README.md#screens](frontend/README.md#screens);
- a new setting → a commented line in `.env.example` and the configuration
  table in `backend/README.md`;
- anything user-visible → an entry under `## [Unreleased]` in
  [CHANGELOG.md](CHANGELOG.md);
- a new page in `docs/` → a `!docs/<page>.md` line in `.gitignore` (it skips
  the rest of `docs/`), or git never sees it. `make doc-links` (CI runs it)
  fails when a relative link points at a file git does not track or at a
  heading that is not there.

## Submitting a PR

1. Fork the repo and create a branch off `main`.
2. Make your change, keeping it focused — unrelated cleanup makes review
   harder.
3. Run `make lint` and `make local-test`, and confirm `docker compose build`
   still works if you touched `backend/requirements.txt` or the Dockerfile.
   If you touched HTTP routes, run `make api-docs`.
4. Open a PR describing what changed and why. Link the issue it addresses,
   if any.
5. CI must pass (ruff lint, the API-reference check, all backend tests, the
   dashboard smoke test and the Docker build) before merge.

## Releasing (maintainers)

Versions follow [Semantic Versioning](https://semver.org/); the release
notes are the changelog.

1. In `CHANGELOG.md`, move the `[Unreleased]` entries under
   `## [X.Y.Z] - YYYY-MM-DD` (a short highlights paragraph on top helps) and
   update the compare links at the bottom.
2. Set the same version in `extension/manifest.json`.
3. Commit, then tag and push:

   ```bash
   git tag -a vX.Y.Z -m "niche-finder vX.Y.Z"
   git push origin main --follow-tags
   ```

4. The tag starts `.github/workflows/release.yml`: it checks that the tag,
   the manifest and the changelog agree, builds
   `niche-finder-extension-vX.Y.Z.zip` (`make extension-zip` does the same
   locally) and publishes the GitHub release with the changelog section as
   notes.

By contributing, you agree your contribution is licensed under this
project's [MIT License](LICENSE).