Instrument a minimal event stream: PRs, deploys, incidents
Track events, not opinions. Use one append-only record shape: type, entity_id, repo or service, actor, created_at, optional merged_at or resolved_at, and source_ids.
Ingest code and deploy events
From GitHub, subscribe to pull_request and deployment webhooks, or fetch them through REST. Normalize merged_at to mergedAt, keep head and base refs, and store the deployment environment as a first-class field (source: GitHub REST API v3 documentation (consulted 2026-07)).
Ingest issue lifecycle events
From Jira or Linear, capture issue creation and completion timestamps. Jira exposes issue fields through its Cloud REST API; Linear exposes createdAt and completedAt through GraphQL objects (source: Jira Cloud REST API documentation (consulted 2026-07); source: Linear GraphQL API docs (consulted 2026-07)).
Link work to code
Link issues to PRs through branch-name conventions such as ENG-421-cache-timeout, or by matching an issue key in the PR title. Store the matched issue ID in source_ids, not as free text (source: Jira Cloud REST API documentation (consulted 2026-07); source: Linear GraphQL API docs (consulted 2026-07)).
Prefer webhooks when the source supports them. Use short-interval scheduled fetches only as a fallback, and make the fetcher idempotent so repeated API reads do not create duplicate events.
Keep raw events immutable. Derive lead_time, review_wait, and time_between_deploys from timestamps downstream, then band those fields later. Do not emit a composite velocity score.
Replace composite scores with bands and rolling baselines
Use DORA’s delivery-performance signals as the metric family, then define local baselines rather than a cross-team score (source: DORA Accelerate State of DevOps 2023 (Google Cloud)). A practical setup is a rolling baseline for lead time and review wait. Store the median and p90, then compare the latest weekly view against that local baseline.
Use bands instead of points. Green means the signal is within its baseline range. Yellow means drift that needs inspection. Red means sustained drift or a p90 breach. Keep these labels separate; never average them into one velocity score.
| Check | Pass condition |
|---|---|
| PR age | No open PR is older than the baseline p90 age. |
| Deploy interval | No deploy gap exceeds the baseline p90 interval. |
| Incident MTTR | Incident MTTR stays within the baseline p90. |
Plot run charts for each signal with median and p90 overlays. Mark yellow and red points as annotations, not rankings.
Use the charts to inspect within-team change over time. A team recovering from red to yellow has useful signal; a cross-team leaderboard does not.
Queries you can run today: GitHub + Jira/Linear wiring
Run these queries into the same Events table. Keep raw timestamps and compute durations outside the source systems.
Use GitHub pull request data for merged PRs and keep this field shape:
pullRequests(states: MERGED) {
nodes {
createdAt
mergedAt
author { login }
baseRefName
headRefName
}
}
Compute PR lead time as mergedAt - createdAt. Store baseRefName and headRefName so release branches and hotfix branches can be banded separately.
Use REST deployments to infer production deploys: GET /repos/{owner}/{repo}/deployments, then fetch each status with GET /repos/{owner}/{repo}/deployments/{id}/statuses (source: GitHub REST API v3 documentation (consulted 2026-07)).
Write one deploy event per production status. Keep the deployment environment, status timestamp, repository, and commit SHA.
Use JQL for recently completed work:
project=XYZ AND status CHANGED TO Done DURING (startOfDay(-30d), now())
For each issue, compute cycle time as resolutiondate - created. Keep issue key, project, issue type, assignee, created, and resolutiondate (source: Jira Cloud REST API documentation (consulted 2026-07)).
Use Linear GraphQL for completed issues:
issues(filter: { completedAt: { gt: ... } }) {
nodes {
createdAt
completedAt
branchName
}
}
Compute issue cycle time as completedAt - createdAt. Link issues to PRs through branchName, or parse the Linear issue key from the PR title (source: Linear GraphQL API docs (consulted 2026-07)).
Use GitHub search syntax to flag open PRs waiting on review (source: GitHub REST API v3 documentation (consulted 2026-07)):
is:pr is:open -draft review:required updated:<YYYY-MM-DD
Map each result into Events as pr_opened with last_updated. This keeps stale review queues visible without scoring authors or teams.
Non-scoring velocity signals to track weekly
Flow signals
Track lead time for changes as the weekly median and p90 from issue_started_at to pr_merged_at; DORA treats lead time for changes as a software delivery performance signal (source: DORA Accelerate State of DevOps 2023 (Google Cloud)).
Count production deploys per week, then derive time-between-deploys from adjacent deploy_succeeded events. DORA treats deployment frequency as an outcome signal, not a subjective speed rating (source: DORA Accelerate State of DevOps 2023 (Google Cloud)).
Queue signals
Measure review flow with two fields: time to first review, and PRs awaiting review. Band both against the baseline p90, so the action is clearing the queue, not ranking reviewers.
Track WIP aging as open PR counts in three bands: ≤p50, p50–p90, and >p90 versus the current baseline. This exposes tail risk when the median PR still looks healthy.
Resilience signal
Link incidents to the last production deploy when timestamps overlap your incident window. Report median time to restore as a trend, because DORA includes recovery from failures in software delivery performance (source: DORA Accelerate State of DevOps 2023 (Google Cloud)).
When linked incidents are sparse, use pass/fail checks instead of percentages: “incident linked to last deploy” and “restore time worse than baseline” are enough for weekly review.
Make it visible: a one‑page velocity ledger and alerts
Keep the ledger as one weekly CSV row per team or service. Use these starter columns: week_start, merges, deploys, lead_time_p50, lead_time_p90, review_age_p90, wip_over_p90, incidents, mttr_minutes, and notes.
Write the ledger from code
Run a scheduled script that reads GitHub, Jira, Linear, and incident APIs, then writes velocity-ledger.csv into a repo. Commit generated rows so every calculation is reviewable, reproducible, and diffable.
Render without hiding formulas
Render the CSV into a static dashboard or connected sheet. Keep band logic in versioned code or visible formulas, not inside a vendor-only widget.
Open issues only for sustained red
When the same signal stays red across back-to-back weekly rows, open an action-item issue with the affected service, signal name, recent rows, and owner. Do not page on single-week noise.
Publish metric definitions and band rules next to the chart. Aggregate only at team or service level; never publish person-level rows, because individual visibility turns measurement into target-chasing.
Ship a small adoption pack with velocity-ledger.csv, a webhook receiver, the ingestion script, and dashboard renderer. Put it under the MIT license so teams can fork it, edit bands, and keep their history portable.
Continue reading
Mintlify vs Document360: Best Pick for Startups
A founder‑friendly decision matrix, concrete setup playbooks, and a hybrid pattern to ship developer docs and a help center without overstaffing.
Alternatives to Intercom Fin for Small Businesses: A Shortlist
A founder-grade guide to picking an Intercom Fin alternative for small teams, with a decision matrix, setup steps, and a cost-control checklist.
Intercom Fin pricing vs other platforms: a cost model that fits
Skip vendor hype: map how Intercom Fin and competitors actually bill, plug your ticket volume into a sheet, and get a break-even answer you can act on.