<!-- OpenSmartRoute: osr-contributing. Source https://github.com/isathish/OpenSmartRoute/blob/main/.claude/skills/osr-contributing/SKILL.md; HTML https://opensmartroute.ai/docs/skills/osr-contributing -->
---
name: osr-contributing
description: Work inside the OpenSmartRoute repository itself. Add a new strategy, signal, adapter or module to the codebase under src/opensmartroute; update the frozen public API snapshot in tests/public_api.json; keep the version literal and pyproject in sync; pass the ruff, mypy and pytest quality gates and the docs gate (docs/REFERENCE.md regenerated, every docs/*.md linked from README); write or update docs/*.md and .claude/skills SKILL.md packages so they render on the documentation site; follow the zero-dependency, settings and branding single-source-of-truth rules and the design patterns; write tests and CHANGELOG entries; prepare a release. Use when contributing code or documentation to the repo, fixing a failing gate, or reviewing a pull request against project conventions.
license: Apache-2.0
compatibility: OpenSmartRoute repository, Python >= 3.10
metadata:
  author: opensmartroute
  osr-domains: "coding"
  osr-tags: "opensmartroute contributing repo conventions"
  osr-quality-prior: "0.9"
  osr-primary: "false"
---

# Contributing to OpenSmartRoute

## Gates (run all four before finishing any change)

```
python -m ruff format src tests
python -m ruff check src tests          # rules E,F,I,UP,B,S; line length 120
python -m mypy src                      # mypy_path=src, packages=opensmartroute; must be clean
python -m pytest tests -q               # pythonpath=src
python scripts/api_reference.py         # regenerate docs/REFERENCE.md after any public-name change
```

`tests/test_docs.py` fails when `docs/REFERENCE.md` is stale or an exported name has no summary;
`python scripts/api_reference.py report` lists the undocumented names. It also requires every
`docs/*.md` to be linked from `README.md` and every `.claude/skills/<name>/` to contain a `SKILL.md`.
`tests/test_docs_claims.py` (SDK) and `platform/api/tests/test_platform_docs_claims.py` (platform) fail when a
doc names an `osr` command / flag, an `OSR_*` variable, an HTTP endpoint or a `/platform/dashboard/<page>` that
does not exist, when a user-facing doc is missing from the site catalogue, or when a CHANGELOG section
lacks a date, body or compare link - fix the doc or the code, never the test.
Ad-hoc scripts: `$env:PYTHONPATH="src"; python -X utf8 ...` (the package is not installed in the venv).

## Hard rules

- **Zero runtime dependencies in the core.** Anything optional (`yaml`, `fastapi`, `httpx`,
  `sentence_transformers`, `opentelemetry`, `cryptography`) is imported lazily inside the function
  that needs it and mapped to an extra in `pyproject.toml` (`yaml`, `server`, `embeddings`, `otel`,
  `crypto`, `all`).
- **Public API is frozen.** `tests/public_api.json` snapshots `opensmartroute.__all__`. Adding a
  name = add it to the JSON (sorted; uppercase first, then `__version__`, then lowercase). Removing
  or renaming = major version bump.
- **Version literal.** `__version__ = "x.y.z"` stays a literal in `src/opensmartroute/__init__.py`
  and must equal `version = "x.y.z"` in `pyproject.toml` (`scripts/release.py` rewrites both).
  Read it elsewhere through `branding.version()`.
- **No hardcoded tunables.** Thresholds, weights, scales go in `settings.py` dataclasses and are
  read through `resolve(settings).<group>.<field>`; document new fields (they become
  `OSR_<GROUP>_<FIELD>` automatically).
- **No brand literals.** Use `branding.PACKAGE / CLI / env_key / error_code / metadata_key /
  logger / user_agent`. `osr-*` metadata keys come from `metadata_key(field)`.
- **Typed errors only.** Raise subclasses of `OpenSmartRouteError` (`ConfigurationError`,
  `ValidationError`, `NoRouteError`, `TargetUnavailableError`, `ExecutionError`, `SecurityError`,
  `StateStoreError`) with `details={...}`; never bare `Exception`.
- **Every exported name is documented.** Classes and functions carry a one-line docstring; module-level
  constants and type aliases carry a same-line `# comment` (or a comment on the line above). The
  first line becomes its row in `docs/REFERENCE.md`.
- Strategies must degrade to neutral scores on internal failure, not raise during `route()`.

## Where things go

```
core/        types + TargetRegistry            strategies/  Strategy subclasses (name = snake_case noun)
signals/     SignalExtractor subclasses        policy/      PolicyRule callables + Policy chain
math/        pure numerics, no routing types   learning/    learners that persist state
realtime/    breakers, budgets, health         enterprise/  builder, middleware, telemetry, stores, ops
security/    guards, limits, provenance        adapters/    external systems; lazy optional imports
eval/        datasets, metrics, frontiers      sdk.py       ComponentRegistry + decorators
observability.py  Tracer, Span, Event, sinks   (emit with current_tracer().event(...); names in EVENT_NAMES)
settings.py  tunables                          branding.py  names (leaf module, imports nothing internal)
```

## Adding a strategy

1. Subclass `Strategy` in `strategies/<name>.py`; set `name`; implement
   `score(request, signals, candidates) -> dict[str, StrategyScore]`; optional `update(outcome)`,
   `state()/load_state()` for persistence.
2. Take `settings: Settings | None = None`; add a `WeightSettings.<name>` default.
3. Export from `strategies/__init__.py` (`__all__`) and, if public, `opensmartroute/__init__.py` +
   `tests/public_api.json`. Add to `default_strategies()` only if it needs no external service.
4. Tests: deterministic (`seed=0`), one behavioural test + one persistence round-trip if stateful.
5. Document in `docs/ARCHITECTURE.md` (strategies table) and `README.md` "Strategies"; CHANGELOG
   under `[Unreleased]`; run `python scripts/api_reference.py` so `docs/REFERENCE.md` picks it up.

## Adding an adapter

Return `RouteTarget`s or handlers; never import the optional client at module import time; validate
external input (`ConfigurationError` with `details["path"]`), pass descriptions through
`security.description_risk` when they come from a third party.

## Documentation

- Layout: `README.md` short overview; `docs/GUIDE.md` walkthroughs (numbered H2s); `docs/SDK.md` decorator
  SDK and settings; `docs/ARCHITECTURE.md` pipeline and strategies table; `docs/MATH.md` numbered sections
  with module references; `docs/RESEARCH.md` idea-to-module map; `docs/ROADMAP.md` rows name the module;
  `docs/REFERENCE.md` is generated - never edit by hand and never `> docs/REFERENCE.md` (the script writes it).
- The Next.js documentation site (`platform/web`, `/docs/<slug>`) is end-user documentation for the
  hosted platform, the Python SDK and the pip package. It publishes `docs/*.md` (`docs/PLATFORM.md` is
  the platform user guide), the root `README`/`CHANGELOG`/`CONTRIBUTING`/`SECURITY`, `deploy/README.md`,
  `spec/ocm/README.md`, every `.claude/skills/*/SKILL.md` and a REST API reference generated from
  `platform/api/openapi.json`. Internal documents (`docs/PLATFORM_PLAN.md`, `docs/GO_TO_MARKET.md`,
  `docs/BRAND.md`, `docs/sales/*.md`, `platform/README.md`) stay in the repository and are linked from
  `README.md` only. A new user-facing `docs/*.md` needs an entry in
  `platform/web/src/lib/docs/catalogue.ts` and a link in `README.md`; skills are discovered
  automatically. Details: the `osr-platform` skill and `platform/README.md`.
- Markdown that renders well on the site: one H1, H2/H3 headings (they become the table of contents and
  the search index), GitHub-flavoured tables, fenced code with a language, ```` ```mermaid ```` for
  diagrams, relative links to other `.md` files (rewritten to site routes) and to source files
  (rewritten to GitHub). Do not rename headings that other files link to without updating the anchors.
- Skill packages: `.claude/skills/<name>/SKILL.md` with frontmatter `name` (= directory), `description`
  (<= 1024 chars, several request-like sentences - they are the similarity examples), `license`,
  `compatibility`, `metadata.osr-domains` (ontology names only, e.g. `coding general`),
  `osr-tags`, `osr-quality-prior` (0.8-0.95), `osr-primary: "false"`. Omit `osr-actions`. Add a
  representative query to `tests/test_skills.py` that must select the new skill for the plan slot;
  `osr skills .claude/skills` validates the folder.

## Tests

- `tests/test_router.py`, `test_math.py`, `test_security.py`, `test_enterprise.py`,
  `test_adapters.py`, `test_execution.py`, `test_sdk.py`, `test_integrations.py`, `test_public_api.py`.
- Use `tmp_path` for state, `monkeypatch` for env, `ComponentRegistry()` instances rather than the
  global `components`; restore `configure(before)` in a `finally` when touching settings.

## Release

Edit `CHANGELOG.md` `[Unreleased]` as you go (Added / Changed / Fixed). `scripts/release.py` (run by the
`Prepare release` workflow) bumps the version literal in both files and dates the section; the
`release.yml` workflow publishes. Do not hand-edit released sections. `scripts/release.py readiness`
computes the v1.0 readiness table (ROADMAP) from the repo - release rows block `check` for 1.x, adoption
rows (`docs/SECURITY_REVIEW.md` review log, `examples/leaderboard/results/README.md` *Listings* rows with
`accepted`, `ADOPTERS.md` *Production users* rows) are tracked only. Never fake a row - the table is the
honest state of 1.0. Version mirrors outside `release.py prepare`: platform/api/{pyproject.toml (version and
`opensmartroute[...]>=`), osr_platform/__init__.py}, platform/web/package{,-lock}.json (top two entries),
deploy/helm/opensmartroute/Chart.yaml (version + appVersion), azure.yaml `template@`, deploy/Dockerfile and
deploy/README.md image tags, README `OSR_VERSION=`, docs/sales/DEMO_SCRIPT.md, docs/SECURITY_REVIEW.md
subject; then regenerate `platform/api/openapi.json` and `docs/REFERENCE.md`.
