Skip to content

Contributing to Maestro

Thanks for taking the time to contribute. This document covers everything from getting the stack running locally to what CI will check before your pull request can merge.

Before you start

  • CLAUDE.md is the single source of truth for architecture, conventions, and code standards. When this file and CLAUDE.md disagree, CLAUDE.md wins — please open an issue so we can fix the drift.
  • Participation is governed by our Code of Conduct.
  • Found a security vulnerability? Do not open an issue. Follow SECURITY.md instead.

Local setup

Maestro runs three databases in Docker (PostgreSQL, MongoDB, Qdrant) while the backend, frontend, and Ollama run on the host.

The fast path

git clone https://github.com/Yigtwxx/Maestro.git
cd maestro

./scripts/dev.sh          # macOS / Linux
.\scripts\dev.ps1         # Windows

The script starts the Docker infra, creates the backend virtualenv, copies .env.examplebackend/.env and frontend/.env.local.examplefrontend/.env.local, runs Alembic migrations, seeds the marketplace, and launches both dev servers. Pass --skip-infra if your databases are already up, or --skip-seed to skip seeding.

Backend lands on http://localhost:8000 (API docs at /docs), frontend on http://localhost:3000.

The manual path

docker compose up -d                       # Postgres :5433, Mongo :27018, Qdrant :6333

cd backend
python -m venv .venv
source .venv/bin/activate                  # Windows: .venv\Scripts\activate
pip install -r requirements-dev.txt        # NOT requirements.txt — you need the test deps
cp ../.env.example .env
alembic upgrade head
uvicorn app.main:app --reload

cd ../frontend
npm install
cp .env.local.example .env.local
npm run dev

Note that scripts/dev.sh installs requirements.txt, which is enough to run the backend but not to test or lint it. Install requirements-dev.txt before you start writing code.

No API key required

You do not need a paid provider key to develop. Maestro ships a free local tier — an open-weights model served through Ollama:

ollama pull qwen3
ollama pull nomic-embed-text

Google Gemini also has a free tier if you prefer a hosted model (get a key, no credit card).

Verification before you open a pull request

CI runs on every push and pull request to main. Run the same commands locally first — these are copied verbatim from .github/workflows/ci.yml:

# Backend (Python 3.11)
cd backend
ruff check .
ruff format --check .
pytest

# Frontend (Node 20)
cd frontend
npm run lint
npm run type-check
npm run build

The frontend has no test runner yet. There is no npm test script — do not add one to a feature PR without discussing it in an issue first.

Dependency management

Both stacks pin dependencies for reproducible builds.

Backend uses the pip-tools convention: requirements.in / requirements-dev.in hold the human-edited version ranges; requirements.txt / requirements-dev.txt are generated lockfiles (pinned, hashed, platform-independent) — never edit them by hand. To add or change a dependency, edit the .in file and regenerate with uv:

cd backend
uv pip compile requirements.in -o requirements.txt --universal --python-version 3.11 --generate-hashes
uv pip compile requirements-dev.in -o requirements-dev.txt --universal --python-version 3.11 --generate-hashes -c requirements.txt

Commit the .in and .txt files together — CI's "Lock freshness check" fails if they drift apart. --universal keeps one lock valid on Windows, macOS, and Linux.

Frontend pins exact versions in package.json (.npmrc sets save-exact=true, so npm install <pkg> does the right thing) and package-lock.json locks the tree. Routine version bumps arrive as weekly Dependabot PRs; don't bump versions in a feature PR.

Code standards

Condensed from CLAUDE.md §5. Read that section before your first PR.

  • English only in code, identifiers, comments, and commit messages. User-facing UI strings may be localized.
  • Backend — Python 3.11+, type annotations are mandatory, ruff for both lint and format (88 columns). All endpoints are async. Request/response validation goes through Pydantic v2.
  • Frontend — TypeScript strict: true, never any (use unknown), functional components only, Zustand for state, Tailwind for styling.
  • Business logic lives in services/. Route handlers stay thin.
  • No magic numbers or strings. Constants belong in constants.py / constants.ts.
  • New LLM providers are added as a new adapter class in services/llm_service.py. Existing adapters are never modified — that is the whole point of the pattern (CLAUDE.md §11 and §15).

Tests

Backend tests use pytest with pytest-asyncio in asyncio_mode = "auto", so async tests need no @pytest.mark.asyncio decorator:

async def test_something() -> None:
    ...

Files live flat in backend/tests/ and are named test_*.py. Tests run against SQLite via aiosqlite, so they need no Docker infra. New behavior needs a test; bug fixes need a regression test that fails before the fix.

Things that will block a merge

These are hard rules, not style preferences (CLAUDE.md §9 and §15):

  • Never commit secrets. No API keys, tokens, or .env files. .env.example carries placeholders only.
  • Never log, store in plaintext, or return an API key to the frontend. Keys are AES-256-GCM encrypted at rest and decrypted only in server memory at call time.
  • Never bypass Alembic. Schema changes ship as a migration; no hand-run SQL.
  • Always bound agent loops. max_iterations, max_review_iterations, and task_timeout_seconds exist because an unbounded agent drains a user's provider quota.
  • Never let one user's data reach another. Every query in Postgres, MongoDB, and Qdrant is scoped by user id.

Commits and pull requests

Commit messages follow Conventional Commits:

<type>(<scope>): <short description>

[optional body explaining WHY, not what]

Types: feat, fix, refactor, test, docs, chore. Keep the subject under 72 characters and use the imperative mood ("add", not "added").

Branch off main, keep one logical change per pull request, and fill in the pull request template. Link the issue your PR closes.

Your pull request will not be squashed. Squash merging is disabled on this repository, so the commits you push are the commits that land on main. Write them as you want them read: one logical change each, no wip or fix typo noise. Clean the branch up with an interactive rebase before you ask for review.

Sign your commits

Signing is strongly encouraged and not yet enforced, so an unsigned pull request will still be reviewed. It is what lets a reader confirm the author of a commit is who the commit says it is — see Commit provenance. If you have never set it up, SSH signing takes a minute and reuses the key format you already know:

ssh-keygen -t ed25519 -C "you@example.com (git signing)" -f ~/.ssh/id_ed25519_signing

git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519_signing.pub
git config --global commit.gpgsign true

Then add the public key (~/.ssh/id_ed25519_signing.pub) to your GitHub account under Settings → SSH and GPG keys, choosing key type Signing key — an authentication key does not verify commits. Your commits will show as Verified.

Licensing of contributions

Maestro is distributed under the Sustainable Use License (fair-code). By intentionally submitting a contribution for inclusion, you agree that:

  1. Your contribution is licensed to the project under the same Sustainable Use License.
  2. You grant the maintainer the right to relicense your contribution as part of the project (for example, to offer it under a commercial license for the hosted service, or to adjust the project license in the future).
  3. You have the right to submit the contribution (it is your own work, or you are authorized to contribute it).

Opening a pull request constitutes acceptance of these terms — there is no separate signature step or DCO sign-off. In particular, the relicensing grant in point 2 lets the maintainer offer contributed code under a commercial license as part of the hosted Maestro service without asking again. These terms are the substance of a CLA; a formal CLA bot is intentionally not used while external contribution volume is low, and may be added later without changing what you agree to here.