Engineering-operations analytics for small software teams. Self-hosted. Reads your vendors, answers about your delivery, cost and risks.
A company's engineering reality is spread across eight systems and nobody's head holds all of it. Who is stuck, which project burns money faster than it ships, whether the person who went quiet is on holiday or drowning — every answer exists, in a different tab, in a different format, a week late. OpMetrics collects those records nightly and answers the questions at four levels: company, project, team, person.
The rule it is built on: a number you cannot drill to its source record is not an answer. Every figure carries what it was computed from, and every figure that is a guess says so out loud.
Delivery
- Ticket flow per status category (todo / in progress / review / QA / done), cycle time over tickets that actually moved, QA returns and reopens.
- Review: time to first comment and first approval, merged-without-review as its own signal rather than a silent hole in the average.
- DORA at project and team level only — never per person — with every proxied event labelled as proxied.
- Sprints and velocity: committed against completed, from the tracker's own board data.
Cost
- Full spend for a scope and period: salaries, AI subscriptions and usage, tooling seats. Marked approximate whenever salary data is partial, because a floor presented as a total is a lie.
- Billable load against contracted capacity, and bench — paid capacity with no billable work against it, priced at salary.
- Budgets are alert-only. The platform never throttles anything.
Signals
- Detectors over vendor evidence: activity collapse, bench, missing hours, worklog-versus-git divergence, review bottlenecks, off-hours patterns, build-success decay and more.
- Every signal states its evidence, its period and what it does not claim. Sensitive ones sit behind a governance tier, and a dismissed signal is remembered instead of re-raised.
- They are indications for a human conversation, never verdicts about a person.
People and access
- Identities are resolved across vendors into one person, with a review queue for what could not be matched automatically — a git email is not a human.
- Roles are stored in the database and editable in the UI; permissions say what a caller may do, reach says about whom. A lead sees their teams, a PM their projects, an admin the company. Salaries are stripped server-side outside company-wide reach.
Two doors for AI agents
- An MCP server: the employee connects OpMetrics in their own Claude or ChatGPT and asks in plain words. OAuth, four read tools, one for reporting a question the tools could not answer, and three context resources teaching the vocabulary and the signal model. Refusals are sentences ("this is an answer about your access, not about the data"), and every ask is audited.
- An in-product chat: the organisation supplies one provider key, and every signed-in user gets the same tools — executed under their own access zone, so the admin's key never widens anyone's reach.
- API — NestJS 11, Prisma 7, PostgreSQL, Valkey/Redis with BullMQ, Better Auth (Google, Microsoft, Yandex, plus email and password for local work).
- Web — React 19, Vite, Tailwind v4, TanStack Query, react-i18next (Russian and English). Charts are Recharts, wrapped in design-system facades so no screen imports the library directly.
- Landing — Astro static site, and a Cloudflare Worker for its lead form.
- Shared — types, constants and pure calculators used by both sides.
- pnpm workspaces, TypeScript everywhere, oxlint and oxfmt.
apps/api NestJS backend: ingest, rollups, analytics, signals, RBAC, MCP, chat
apps/web React SPA
apps/landing Astro marketing site
apps/collector optional out-of-process collector worker
apps/lead-worker Cloudflare Worker behind the landing's lead form
packages/shared types, constants, calculators shared by everything
packages/fetcher vendor connectors: git, trackers, AI, HR, docs, observability
How a vendor record becomes a number on a screen — collection, rollups, the signal pass, and where access is enforced — is in docs/architecture.md.
Git hosting (GitHub, Bitbucket Cloud and Server, GitLab), trackers (Jira, Yandex Tracker, Kaiten), time systems (Tempo, Clockwork), directories (LDAP, Google Workspace, Microsoft Entra, Yandex 360), HR (PeopleForce, BambooHR, HiBob, Personio, Deel, Hurma), docs (Confluence, Notion), observability (Sentry, New Relic), communication (Slack), and AI usage (OpenAI, Anthropic incl. Enterprise, Cursor, Copilot).
Anything not on that list can push into the ingestion API with a scoped key — see docs/ingestion-api.md.
Requires Node 22+, pnpm and Docker.
pnpm install
cp .env.example .env # defaults match the compose file
docker compose up -d # postgres + valkey (+ openldap for tests)
pnpm --filter @opmetrics/shared build
pnpm --filter @opmetrics/api exec prisma migrate deploy
pnpm dev:together # api :3000, web :3001, sync workerpnpm dev does the same but splits the processes into separate terminal
windows, which is wired for Windows only; everywhere else use dev:together.
Getting in the first time. Email-and-password sign-in exists for local work
and is off unless DEV_PASSWORD_AUTH=1 (.env.example ships it on, and the
server ignores it when NODE_ENV=production). For anything shared, configure
one of the identity providers instead — the login page shows only the methods
that are actually configured.
Whoever registers first is not an administrator: every account starts as
employee, which is 403 everywhere until someone grants it a real role. Promote
the first one directly in the database, then do the rest in the UI:
UPDATE "user" SET role = 'admin' WHERE email = 'you@example.com';From there: add a connection in Settings → Integrations, bind its repositories or projects, and run a sync.
There is no demo seed for the product. This is a product about your data:
until you connect a vendor and let a sync finish, the screens are honestly
empty. Point it at a real repository and tracker, and the first sync backfills a
year. (pnpm --filter @opmetrics/api db:seed exists, but it fills a throwaway
test database for the e2e suite and refuses to run without SEED_ALLOW=1.)
Tests: pnpm test runs the unit suites (about 1900 tests), pnpm test:integration
runs the API integration suite against a real Postgres and a mock vendor, and
pnpm test:e2e drives the UI with Playwright. Both of the latter use a separate
opmetrics_test database, which the compose file does not create for you:
docker compose exec postgres createdb -U postgres opmetrics_test- Credentials for every connection are encrypted with AES-256-GCM under
CREDS_ENCRYPTION_KEYand never leave the server. Change that key and the stored credentials become unreadable — there is no recovery path. - Facts are never deleted. A record the vendor drops is buried (soft-deleted) and stops counting, so history stays auditable.
- The sync is incremental with per-scope watermarks, chunked backfill and per-connection rate limits. A rebuild of a period is a separate, admin-only act that demands an explicit date range, because it re-reads vendor APIs and can bury data.
- Timezones: facts are stored in UTC, reporting periods follow the organisation's configured timezone.
- You are collecting data about people. Whatever your jurisdiction requires of you — a lawful basis, notice, retention limits, works-council consent — is yours to satisfy. The platform gives you the tools (RBAC, audit log, retention setting, governance tiers on sensitive signals); it cannot give you the right.
This is a snapshot of a working product, published as is.
- It is not actively maintained. Issues and pull requests are not promised an answer.
- It is tested but not proven: about 1900 unit tests, an integration suite against mocked vendors and a Playwright pass over the UI. Expect bugs, and do not run it in production unless you are prepared to own them.
- Deployment configs (Docker Compose flavours, VPS workflows, Terraform for a
per-client AWS stack) ship with placeholder hostnames —
example.com,your-org— and are meant to be adapted, not run as they are. - The UI ships Russian and English.
Functional Source License 1.1, Apache 2.0 future license — free for every purpose except competing with it. The licence names the free ones outright:
- running it for yourself, including inside a company, on company data, for as long as you like — this is a self-hosted product and using it is the point;
- non-commercial education and research;
- professional services you provide to somebody else's licensed installation.
What it does not allow is turning this code into a commercial product or service that substitutes for OpMetrics or offers substantially the same thing. For that, and for anything else outside the licence, there is a commercial licence — write to frost.by@gmail.com.
Every version turns into Apache 2.0 two years after its release, and that grant is irrevocable. The restriction is a delay, not a taking: wait it out and the code is yours under one of the most permissive licences there is.
This makes OpMetrics fair source, not open source: the code is public, but a licence that names a forbidden purpose does not meet the Open Source Definition, and calling it open source would be inaccurate.
By submitting a pull request you agree that your contribution may be licensed by the copyright holder under this licence and commercially. You keep authorship and stay credited in the commit history.
Copyright (C) 2026 Vladislav Romanovsky.
