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.mdis the single source of truth for architecture, conventions, and code standards. When this file andCLAUDE.mddisagree,CLAUDE.mdwins — 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.mdinstead.
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.example → backend/.env and frontend/.env.local.example → frontend/.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.shinstallsrequirements.txt, which is enough to run the backend but not to test or lint it. Installrequirements-dev.txtbefore 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:
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,
rufffor both lint and format (88 columns). All endpoints areasync. Request/response validation goes through Pydantic v2. - Frontend — TypeScript
strict: true, neverany(useunknown), 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:
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
.envfiles..env.examplecarries 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, andtask_timeout_secondsexist 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:
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:
- Your contribution is licensed to the project under the same Sustainable Use License.
- 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).
- 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.