|
| 1 | +# Contributing |
| 2 | + |
| 3 | +Thanks for helping improve the IDC REST API + MCP server. This document is the **process**: how we |
| 4 | +branch, commit, changelog, and version. |
| 5 | + |
| 6 | +The **code** is documented elsewhere, and this file does not repeat it: |
| 7 | + |
| 8 | +| You want to | Read | |
| 9 | +|---|---| |
| 10 | +| Set up, run, and test the project | [dev/developer_guide.md](dev/developer_guide.md) — *Setup*, *Run*, *Test* | |
| 11 | +| Know what CI will run against your PR | [dev/developer_guide.md](dev/developer_guide.md) — *Continuous integration* | |
| 12 | +| Understand the invariants reviewers check | [dev/developer_guide.md](dev/developer_guide.md) — *Conventions* | |
| 13 | +| Add a capability (model → service → REST → MCP → parity test) | [dev/developer_guide.md](dev/developer_guide.md) — *Walkthrough* | |
| 14 | +| Understand *why* it's built this way | [dev/architecture.md](dev/architecture.md) | |
| 15 | +| Cut a release / deploy | [dev/deployment.md](dev/deployment.md) — *Cutting a release* | |
| 16 | + |
| 17 | +In short: `uv pip install -e ".[dev]"`, then make sure `ruff`, `bandit`, and `pytest` pass before |
| 18 | +you push — the developer guide has the exact commands CI uses. |
| 19 | + |
| 20 | +Documentation is split by audience (user guide vs. the agent-facing `idc://guide` resource vs. |
| 21 | +the always-on MCP `INSTRUCTIONS` vs. `dev/`). Keep each in its lane — the conventions, and which |
| 22 | +file to touch when, are in [CLAUDE.md](CLAUDE.md#documentation-conventions). |
| 23 | + |
| 24 | +## Branches |
| 25 | + |
| 26 | +Branch off `main`, named `<type>/<short-slug>` using the same type vocabulary as commits: |
| 27 | + |
| 28 | +``` |
| 29 | +feat/cohort-size-estimate fix/mcp-trailing-slash |
| 30 | +docs/api-endpoint-examples ci/multi-tier-deploy |
| 31 | +``` |
| 32 | + |
| 33 | +Pull requests are merged with a merge commit, so the individual commits on your branch land in |
| 34 | +`main`'s history. Make them ones you'd want to read later. |
| 35 | + |
| 36 | +## Commits |
| 37 | + |
| 38 | +We follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/): |
| 39 | + |
| 40 | +``` |
| 41 | +<type>(<optional scope>): <imperative, lowercase summary> |
| 42 | +``` |
| 43 | + |
| 44 | +**Types:** `feat`, `fix`, `docs`, `test`, `refactor`, `perf`, `style`, `build`, `ci`, `chore`. |
| 45 | +**Scopes** in use: `rest`, `mcp`, `api`, `deploy`, `deps`. |
| 46 | + |
| 47 | +``` |
| 48 | +feat(rest): redirect the bare domain to the interactive docs |
| 49 | +fix(mcp): serve /mcp and /mcp/ directly instead of redirecting |
| 50 | +docs(api): add OpenAPI summaries/descriptions to every REST route |
| 51 | +``` |
| 52 | + |
| 53 | +This is a convention, not a CI gate — nothing will fail your build if you deviate. It exists so |
| 54 | +history stays skimmable and so the changelog is easy to assemble at release time. Dependabot's |
| 55 | +commits don't always conform; that's fine. |
| 56 | + |
| 57 | +Mark a breaking change to the REST or MCP contract with a `!` (`feat(rest)!: …`) and a |
| 58 | +`BREAKING CHANGE:` footer. See [Versioning](#versioning) — such a change needs a new URL prefix, |
| 59 | +so it is a much bigger conversation than a commit message. |
| 60 | + |
| 61 | +## Changelog |
| 62 | + |
| 63 | +[CHANGELOG.md](CHANGELOG.md) is **hand-curated**, in [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) |
| 64 | +format. It is not generated from commits: it describes what changed for *callers of the API*, |
| 65 | +which is a different thing from what changed in the tree. |
| 66 | + |
| 67 | +**If your PR changes user-visible behavior, add an entry to `## [Unreleased]` in the same PR.** |
| 68 | +User-visible means an endpoint, an MCP tool or its description, a response shape, a configuration |
| 69 | +variable, or a default. Use the standard groupings — `Added`, `Changed`, `Deprecated`, `Removed`, |
| 70 | +`Fixed`, `Security` — and write for someone consuming the API, not someone reading the diff: |
| 71 | + |
| 72 | +```markdown |
| 73 | +### Fixed |
| 74 | +- MCP: `/mcp` and `/mcp/` are both served directly; neither redirects. |
| 75 | +``` |
| 76 | + |
| 77 | +Refactors, test changes, CI, formatting, and dependency bumps do **not** get an entry. The git |
| 78 | +history already records them. |
| 79 | + |
| 80 | +> While `3.0.0b1` is still unreleased, fixes to code that has never shipped are folded into that |
| 81 | +> release's `Added` section rather than listed under `Fixed` — there is no released behavior to |
| 82 | +> have fixed. Once the beta ships, use the groupings normally. |
| 83 | +
|
| 84 | +## Versioning |
| 85 | + |
| 86 | +[Semantic Versioning](https://semver.org/spec/v2.0.0.html), with one house rule: |
| 87 | + |
| 88 | +**MAJOR is pinned to the served URL prefix.** `/v3` ↔ `3.y.z`, always. |
| 89 | + |
| 90 | +| Change | Version | URL | |
| 91 | +|---|---|---| |
| 92 | +| Add an endpoint, MCP tool, or optional field | MINOR — `3.1.0` | `/v3` | |
| 93 | +| Fix a bug without changing the contract | PATCH — `3.0.1` | `/v3` | |
| 94 | +| Break the REST or MCP contract | MAJOR — `4.0.0` | new prefix `/v4` | |
| 95 | + |
| 96 | +So a breaking change is never a silent break under `/v3`: it is a new prefix served alongside the |
| 97 | +old one. This keeps `api_version` predictive of the URL, and matches the clean break v3 already |
| 98 | +made from v1/v2. |
| 99 | + |
| 100 | +Pre-releases use [PEP 440](https://peps.python.org/pep-0440/) spelling so the Python package |
| 101 | +version and the git tag agree: `3.0.0b1` → tag `v3.0.0b1`; `3.0.0rc1` → tag `v3.0.0rc1`. |
| 102 | + |
| 103 | +**The version lives in exactly one place: `version` in [pyproject.toml](pyproject.toml).** |
| 104 | +Everything else derives from it — `idc_api.__version__` and `core/version.py:package_version()` |
| 105 | +both read the installed distribution metadata. Never hardcode it a second time. |
| 106 | + |
| 107 | +## Releasing |
| 108 | + |
| 109 | +Cutting a release is a **maintainer** task that deploys to production. The runbook — including the |
| 110 | +v3 beta plan — lives with the deploy machinery it depends on: |
| 111 | +[dev/deployment.md § Cutting a release](dev/deployment.md#cutting-a-release). |
| 112 | + |
| 113 | +Two things every contributor should know: |
| 114 | + |
| 115 | +> [!IMPORTANT] |
| 116 | +> **Pushing a `v*` tag deploys to production.** [promote.yml](.github/workflows/promote.yml) |
| 117 | +> triggers on `push: tags: ["v*"]`, and that glob matches pre-release tags too — `v3.0.0b1` goes |
| 118 | +> to prod exactly like `v3.0.0`. Never create a `v*` tag for bookkeeping, and be careful with |
| 119 | +> `git push --tags`, which can fire a deploy from a stale local tag. |
| 120 | +
|
| 121 | +And you cannot tag a release without bumping `pyproject.toml` first: `promote.yml` asserts that |
| 122 | +the tag equals `"v"` + the packaged version, and fails before the reviewer gate if they disagree. |
| 123 | + |
| 124 | +## Reporting security issues |
| 125 | + |
| 126 | +Please don't open a public issue for a vulnerability — use GitHub's private reporting flow, as |
| 127 | +described in [SECURITY.md](SECURITY.md). |
0 commit comments