Files
OmniRoute/CONTRIBUTING.md
Diego Rodrigues de Sa e Souza 5ac4637816 docs: reorganize docs/ + accuracy audit + drop Node 20 (rebased onto current release tip) (#5262)
* docs: move superpowers/research artifacts to isolated _tasks repo + docs tree cleanup

- Move docs/superpowers/{plans,specs} and docs/research/* into the gitignored,
  separately-versioned _tasks/ repo; untrack the two tracked research design docs.
- Add CLAUDE.md "Planning & Research Artifacts" section overriding the superpowers
  default save paths (docs/... -> _tasks/...); align REPOSITORY_MAP and
  DOCUMENTATION_OVERHAUL_PLAN with the new convention.
- Drop 4 now-obsolete /api/discovery/* entries from check-docs-symbols allowlist
  (stale-enforcement) and refresh code/spec path comments to _tasks/...
- Sweeps in concurrent docs-tree restructuring (root-level provider/guide docs,
  compression spec cleanup, .mcp.json.example removal).

* docs: reorganize docs/ tree + fix stale facts across ~26 docs

Phase A — reorganization:
- Move 7 orphan root docs into subfolders (providers/ created; TIERS+USAGE_QUOTA→guides/;
  plugins+PLUGIN_SDK→frameworks/); delete 8 obsolete/redundant docs (SUBMIT_PR superseded
  by CONTRIBUTING; DOCUMENTATION_OVERHAUL_PLAN; INCIDENT_RESPONSE/PERF_BUDGETS/THREAT_MODEL;
  3 ops snapshots). Rebuild README index (was missing ~40 files) + per-folder meta.json nav.
- Clean 14 dangling doc-path references in bin/ ops scripts, scripts/, workflow, tests;
  fix the dockerignore-docs-coverage required-docs path (PROVIDERS→providers/CLAUDE_WEB).

Phase B — content accuracy (verified against code, not the audit summary):
- Functional: ENVIRONMENT flag defaults (INPUT_SANITIZER/MCP_ENFORCE_SCOPES=true,
  COMPRESS_DESCRIPTIONS=false, dynamic heap); MCP-SERVER notion tool names (omniroute_*→
  notion_*) + counts 87→94; coverage gate 75/70→60/60/60/60 (RELEASE_CHECKLIST, COVERAGE_PLAN,
  ERROR_SANITIZATION, CONTRIBUTING); pre-push hook description; regenerate PROVIDER_REFERENCE (237).
- Count drift: providers 237, executors 70, migrations 106, db modules 94, oauth 19,
  strategies 17, MCP 94, flags 38, TS 6.0, open-sse ~900/services 294 across architecture/
  frameworks/ops docs; AUTO-COMBO 9→12 factors w/ correct DEFAULT_WEIGHTS; REASONING +2
  patterns; STEALTH UA defaults; AGENT_PROTOCOLS +cursor-cloud/list-capabilities;
  LANGUAGE_PACKS +id pack.
- Kept Node 20 (runtime guard accepts 20.20.2+; only engines is stricter) and MCP scopes=13
  (mcpScopes.ts) — both were correct in the docs; corrected only the attribution.

* docs: finish content refresh — compression engines, CLI_TOKEN merge, metadata sweep

- Compression: document the additional built-in engines (CCR, headroom, ionizer,
  session-dedup) in COMPRESSION_ENGINES; clarify LLMLingua-2 is the ultra-mode SLM
  backend + cross-ref the extra engines in EXTENDING_COMPRESSION; add the id
  (Indonesian) language pack to LANGUAGE_PACKS.
- AUTO-COMBO: replace the orphan 'How tiers fit' weight table (stale weights) with a
  pointer to the canonical 12-factor DEFAULT_WEIGHTS table.
- Security: merge CLI_TOKEN_AUTH.md (legacy 32-char SHA-256 format) into CLI_TOKEN.md
  as a 'Legacy format — still accepted' section (server accepts both HMAC + legacy),
  delete CLI_TOKEN_AUTH.md, drop it from the index + security nav.
- Metadata: bump stale frontmatter (version/lastUpdated) to 3.8.40/2026-06-28 across the
  doc set audited this pass, and normalize the in-body 'Last updated' header lines to match.

* fix(runtime): drop Node 20 from supported range + align all docs/diagrams/counts

- Node minimum is now 22 (aligned with package.json engines). SUPPORTED_NODE_RANGE in
  src/shared/utils/nodeRuntimeSupport.ts (and the bin/ mirror) drops the 20.x line →
  '>=22.22.2 <23 || >=24.0.0 <27'; getNodeRuntimeSupport now rejects Node 20 as
  unsupported-major. Test updated (TDD): node-runtime-support.test.ts asserts Node 20
  rejected. Docs aligned (TROUBLESHOOTING ×2, TERMUX, RELEASE_CHECKLIST, CODEBASE,
  CLI-TOOLS, README, llm.txt + 42 i18n llm.txt mirrors, skills/cli-serve).
- Diagrams regenerated: mcp-tools-87 -> mcp-tools-94 (34 base + pool 6 = 94) and
  auto-combo-9factor -> auto-combo-12factor (correct DEFAULT_WEIGHTS); SVGs re-rendered
  via mermaid-cli; doc refs + diagrams/README updated; fixed a pre-existing broken
  resilience-3layers image path.
- CLAUDE.md + AGENTS.md aligned to real counts (237 providers, 94 MCP tools / 34 base,
  106 migrations, 94 db modules, 12-factor auto-combo, 17 strategies); README provider
  count 231 -> 237; executor count corrected to 68 (provider executors, excl base/index)
  and OAuth to 18 across architecture docs. check:docs-all now passes (0 strict drift,
  0 broken links); removed dead .mcp.json.example doc link.

* fix(services): update installer Node hint to >=22.22.2 (aligned with dropped Node 20)

* docs: realign counts to current release tip after rebase

The release tip advanced while this work was in flight (Gemini CLI provider/executor
removed by #5246, plus other PRs). Re-counted against the current code and updated:
providers 237->236, executors 68->67, OAuth modules 18->17, open-sse services 294->298;
regenerated PROVIDER_REFERENCE.md (236). check:docs-all passes (0 strict drift).

* docs(changelog) + i18n: record Node 20 drop + fix nodeIncompatibleHint

- CHANGELOG: add [3.8.40] entries for the Node 20.x removal (runtime) and the docs
  reorganization/accuracy audit.
- i18n: nodeIncompatibleHint across all 42 locales no longer lists Node 20.x as
  supported (ASCII + CJK full-width variants), aligned with the dropped Node 20.

* fix(docs): repair CI breakages from the doc moves

- test: cli-plugin-system asserted docs/dev/plugins.md exists; the file moved to
  docs/frameworks/PLUGINS.md — point the test at the new path (Unit fast-path 2/2 fix).
- frontmatter: PLUGINS.md and PLUGIN_SDK.md moved into the fumadocs-indexed
  docs/frameworks/ which requires a 'title' frontmatter; the missing frontmatter
  failed the Next.js MDX build (dast-smoke 'invalid frontmatter'). Added frontmatter
  to both, plus the providers/ docs (consistency; that folder is not indexed).
2026-06-28 22:07:18 -03:00

16 KiB

Contributing to OmniRoute

Thank you for your interest in contributing! This guide covers everything you need to get started.


Development Setup

Prerequisites

  • Node.js >=22.22.3 <23, or >=24.0.0 <27 (recommended: 24 LTS)
  • npm 10+
  • Git

Clone & Install

git clone https://github.com/diegosouzapw/OmniRoute.git
cd OmniRoute
npm install

Environment Variables

# Create your .env from the template
cp .env.example .env

# Generate required secrets
echo "JWT_SECRET=$(openssl rand -base64 48)" >> .env
echo "API_KEY_SECRET=$(openssl rand -hex 32)" >> .env

Key variables for development:

Variable Development Default Description
PORT 20128 Server port
NEXT_PUBLIC_BASE_URL http://localhost:20128 Base URL for frontend
JWT_SECRET (generate above) JWT signing secret
INITIAL_PASSWORD CHANGEME First login password
APP_LOG_LEVEL info Log verbosity level

Dashboard Settings

The dashboard provides UI toggles for features that can also be configured via environment variables:

Setting Location Toggle Description
Settings → Advanced Debug Mode Enable debug request logs (UI)
Settings → General Sidebar Visibility Show/hide sidebar sections

These settings are stored in the database and persist across restarts, overriding env var defaults when set.

Running Locally

# Development mode (hot reload)
npm run dev

# Production build
npm run build    # next build → .build/next/ then assembleStandalone → dist/
npm run start

# Release build (clean rebuild + HEAD sentinel — required for deploy)
npm run build:release   # rm -rf .build dist && build + writes dist/BUILD_SHA

# Common port configuration
PORT=20128 NEXT_PUBLIC_BASE_URL=http://localhost:20128 npm run dev

Build Output Layout

Directory Contents Tracked
src/ Application source (TypeScript / TSX) Yes
.build/ Intermediates — next build output (gitignored, distDir = .build/next) No
dist/ Shippable bundle — assembled by assembleStandalone (gitignored) No

The build pipeline is a single pass:

npm run build
  └─ next build → .build/next/standalone  (Next.js output)
  └─ assembleStandalone()                 (copies standalone + static + public + native assets)
       └─ output: dist/                   (server.js, .next/static/, public/, node_modules/)

npm run build:release additionally cleans both directories first and writes dist/BUILD_SHA (= git rev-parse --short HEAD) as a deploy integrity sentinel.

VPS deploy note: the remote image directory /usr/lib/node_modules/omniroute/app/ is unchanged. The deploy skills rsync the contents of dist/ into it. Only the in-repo build output path moved (app/dist/).

Default URLs:

  • Dashboard: http://localhost:20128/dashboard
  • API: http://localhost:20128/v1

Git Workflow

⚠️ NEVER commit directly to main. Always use feature branches.

git checkout -b feat/your-feature-name
# ... make changes ...
git commit -m "feat: describe your change"
git push -u origin feat/your-feature-name
# Open a Pull Request on GitHub

Branch Naming

Prefix Purpose
feat/ New features
fix/ Bug fixes
refactor/ Code restructuring
docs/ Documentation changes
test/ Test additions/fixes
chore/ Tooling, CI, dependencies

Commit Messages

Follow Conventional Commits:

feat: add circuit breaker for provider calls
fix: resolve JWT secret validation edge case
docs: update SECURITY.md with PII protection
test: add observability unit tests
refactor(db): consolidate rate limit tables

Scopes (v3.8): db, sse, oauth, dashboard, api, cli, docker, ci, mcp, a2a, memory, skills, cloud-agent, guardrails, compression, auto-combo, resilience, providers, executors, translator, domain, authz.


Running Tests

# All tests (unit + vitest + ecosystem + e2e)
npm run test:all

# Single test file (Node.js native test runner — most tests use this)
node --import tsx/esm --test tests/unit/your-file.test.ts

# Vitest (MCP server, autoCombo, cache)
npm run test:vitest

# E2E tests (requires Playwright)
npm run test:e2e

# Protocol clients E2E (MCP transports, A2A)
npm run test:protocols:e2e

# Ecosystem compatibility tests
npm run test:ecosystem

# Coverage gate: 60% statements/lines/functions/branches
npm run test:coverage
npm run coverage:report

# Lint + format check
npm run lint
npm run check

# Gated real-upstream combo smoke (requires VPS access + real provider credits)
# Hits REAL providers — costs a little. NEVER runs in CI. Skips cleanly without the gate.
# Needs: ssh root@192.168.0.15 access (sources a read-only DB snapshot from the VPS).
RUN_COMBO_LIVE=1 npm run test:combo:live

# Phase-3 VPS live smoke — plain Node ESM scripts, hit the live .15 server directly.
# Requires: ssh root@192.168.0.15 access (combos created/torn down via SSH sqlite).
# Hits REAL providers (small cost). Creates/deletes only __live_test__* combos. NEVER runs in CI.
# REQUIRE_API_KEY=false on .15 so no API key needed, but honors COMBO_LIVE_BASE_URL / COMBO_LIVE_API_KEY if set.
npm run test:combo:live:vps              # 7 HTTP scenarios (priority/round-robin/weighted/cost/fusion/auto + health)
npm run test:combo:live:vps:failover     # adds a real cross-provider failover scenario (8 total)

Coverage notes:

  • npm run test:coverage measures source coverage for the main unit test suite, excludes tests/**, and includes open-sse/**
  • Pull requests must keep the coverage gate at 60%+ statements/lines/functions/branches
  • If a PR changes production code in src/, open-sse/, electron/, or bin/, it must add or update automated tests in the same PR
  • npm run coverage:report prints the detailed file-by-file report from the latest coverage run
  • npm run test:coverage:legacy preserves the older metric for historical comparison
  • See docs/ops/COVERAGE_PLAN.md for the phased coverage improvement roadmap

Pull Request Requirements

Before opening or merging a PR:

  • Run npm run test:unit
  • Run npm run test:coverage
  • Ensure the coverage gate stays at 60%+ statements/lines/functions/branches
  • Include the changed or added test files in the PR description when production code changed
  • Check the SonarQube result on the PR when the project secrets are configured in CI

Current test status: 122 unit test files covering:

  • Provider translators and format conversion
  • Rate limiting, circuit breaker, and resilience
  • Semantic cache, idempotency, progress tracking
  • Database operations and schema (21 DB modules)
  • OAuth flows and authentication
  • API endpoint validation (Zod v4)
  • MCP server tools and scope enforcement
  • Memory and Skills systems

Code Style

  • ESLint — Run npm run lint before committing
  • Prettier — Auto-formatted via lint-staged on commit (2 spaces, semicolons, double quotes, 100 char width, es5 trailing commas)
  • TypeScript — All src/ code uses .ts/.tsx; open-sse/ uses .ts/.js; document with TSDoc (@param, @returns, @throws)
  • No eval() — ESLint enforces no-eval, no-implied-eval, no-new-func
  • Zod validation — Use Zod v4 schemas for all API input validation
  • Naming: Files = camelCase/kebab-case, components = PascalCase, constants = UPPER_SNAKE

Project Structure

src/                        # TypeScript (.ts / .tsx)
├── app/                    # Next.js 16 App Router
│   ├── (dashboard)/        # Dashboard pages (23 sections)
│   ├── api/                # API routes (51 directories)
│   └── login/              # Auth pages (.tsx)
├── domain/                 # Policy engine (policyEngine, comboResolver, costRules, etc.)
├── lib/                    # Core business logic (.ts)
│   ├── a2a/                # Agent-to-Agent v0.3 protocol server
│   ├── acp/                # Agent Communication Protocol registry
│   ├── compliance/         # Compliance policy engine
│   ├── db/                 # SQLite database layer (21 modules + 16 migrations)
│   ├── memory/             # Persistent conversational memory
│   ├── oauth/              # OAuth providers, services, and utilities
│   ├── skills/             # Extensible skill framework
│   ├── usage/              # Usage tracking and cost calculation
│   └── localDb.ts          # Re-export layer only — never add logic here
├── middleware/              # Request middleware (promptInjectionGuard)
├── mitm/                   # MITM proxy (cert, DNS, target routing)
├── shared/
│   ├── components/         # React components (.tsx)
│   ├── constants/          # Provider definitions (177), MCP scopes, 14 routing strategies
│   ├── utils/              # Circuit breaker, sanitizer, auth helpers
│   └── validation/         # Zod v4 schemas
└── sse/                    # SSE proxy pipeline

open-sse/                   # @omniroute/open-sse workspace
├── executors/              # 14 provider-specific request executors
├── handlers/               # 11 request handlers (chat, responses, embeddings, images, etc.)
├── mcp-server/             # MCP server (25 tools, 3 transports, 10 scopes)
├── services/               # 36+ services (combo, autoCombo, rateLimitManager, etc.)
├── translator/             # Format translators (OpenAI ↔ Claude ↔ Gemini ↔ Responses ↔ Ollama)
├── transformer/            # Responses API transformer
└── utils/                  # 22 utility modules (stream, TLS, proxy, logging)

electron/                   # Electron desktop app (cross-platform)

tests/
├── unit/                   # Node.js test runner (1,574 test files)
├── integration/            # Integration tests
├── e2e/                    # Playwright tests
├── security/               # Security tests
├── translator/             # Translator-specific tests
└── load/                   # Load tests

docs/
├── adr/                     # Architecture Decision Records
├── architecture/            # System architecture & resilience
├── comparison/              # OmniRoute vs alternatives
├── compression/             # Compression guides & rules
├── dev/                     # Development guides
├── diagrams/                # Architecture diagrams
├── frameworks/              # MCP, A2A, OpenCode, Memory, Skills
├── guides/                  # User guide, Docker, setup, troubleshooting
├── i18n/                    # Internationalized README translations
├── marketing/               # Marketing materials
├── ops/                     # Deployment, proxy, coverage, releases
├── providers/               # Provider-specific docs
├── reference/               # API reference, env vars, CLI tools, free tiers
├── releases/                # Release notes
├── routing/                 # Auto-combo engine, reasoning replay
├── screenshots/             # Dashboard screenshots
├── security/                # Guardrails, compliance, stealth, tokens
└── specs/                   # Design specs

Adding a New Provider

Step 1: Register Provider Constants

Add to src/shared/constants/providers.ts — Zod-validated at module load.

Step 2: Add Executor (if custom logic needed)

Create executor in open-sse/executors/your-provider.ts extending the base executor.

Step 3: Add Translator (if non-OpenAI format)

Create request/response translators in open-sse/translator/.

Step 4: Add OAuth Config (if OAuth-based)

Add OAuth credentials in src/lib/oauth/constants/oauth.ts and service in src/lib/oauth/services/.

If the upstream provider distributes a public OAuth client_id/secret or Firebase Web API key inside its public CLI / browser bundle, do not embed it as a string literal. Use resolvePublicCred() from open-sse/utils/publicCreds.ts and add a masked byte entry to EMBEDDED_DEFAULTS. The full mandatory workflow is documented in docs/security/PUBLIC_CREDS.md.

Inside handlers/executors, error messages reaching the client must go through buildErrorBody() / sanitizeErrorMessage() from open-sse/utils/error.ts — never put raw err.stack or err.message in a Response body. See docs/security/ERROR_SANITIZATION.md.

Step 5: Register Models

Add model definitions in open-sse/config/providerRegistry.ts.

Step 6: Add Tests

Write unit tests in tests/unit/ covering at minimum:

  • Provider registration
  • Request/response translation
  • Error handling

Pull Request Checklist

  • Tests pass (npm test)
  • Linting passes (npm run lint)
  • Build succeeds (npm run build)
  • TypeScript types added for new public functions and interfaces
  • No hardcoded secrets or fallback values
  • Public upstream credentials embedded via resolvePublicCred() (see docs/security/PUBLIC_CREDS.md), never as literals
  • Error responses route through buildErrorBody() / sanitizeErrorMessage() — no raw stack traces in response bodies (see docs/security/ERROR_SANITIZATION.md)
  • Shell commands (exec / spawn) pass runtime values via env, not via string interpolation
  • All inputs validated with Zod schemas
  • CHANGELOG updated (if user-facing change)
  • Documentation updated (if applicable)
  • No new CodeQL / Secret-Scanning alerts opened, or each one dismissed with technical justification referencing the relevant docs/security/ doc
  • Routes that spawn child processes (/api/mcp/, /api/cli-tools/runtime/) classified as isLocalOnlyPath() in src/server/authz/routeGuard.ts — see Hard Rule #15
  • No Co-Authored-By trailers in commit messages — commits must appear solely under the repository owner's Git identity (Hard Rule #16)

Releasing

Releases are managed via the /generate-release workflow. When a new GitHub Release is created, the package is automatically published to npm via GitHub Actions.

For VPS deploys, use npm run build:release (not npm run build) — it performs a clean rebuild, assembles the bundle into dist/, and writes the dist/BUILD_SHA sentinel. Then use the /deploy-vps-*-cc skills which rsync dist/ to the remote app/ directory.


Getting Help