feat(intel): Velocity subpage — module growth rates, acceleration, dual-window comparison
Overview
Full-featured velocity intelligence subpage surfacing muse code velocity data.
Matches CLI output exactly, then supercharges it with filterable tables, acceleration
badges, dual-window bar comparisons, and a sparkline commit timeline — all in the
Spectral theme.
Closes the velocity gap on issue #8.
CLI Output Shape (ground truth)
muse -C ~/ecosystem/musehub code velocity --json
{
"mode": "velocity",
"ref": "dev",
"window_size": 20,
"commits_analysed": 593,
"truncated": false,
"filters": { "top": 20, "since": null, "predict": 0, "max_commits": 10000 },
"modules": [
{
"module": "tests/",
"current": { "added": 150, "removed": 0, "net": 150, "modified": 3, "active_commits": 4 },
"prior": { "added": 324, "removed": 21, "net": 303, "modified": 26, "active_commits": 12 },
"acceleration": -153,
"stagnant_commits": 3
}
]
}
Key semantics:
- current = last
window_sizecommits; prior = the precedingwindow_sizecommits - acceleration =
current.net − prior.net(positive = speeding up, negative = slowing) - stagnant_commits = commits that touched the module with net symbol delta = 0
DB table (musehub_intel_velocity) already exists but only stores partial prior-window
data (prior_added, prior_net). Phase 0 expands this to the full prior shape.
Web UX — ASCII Wireframes
Page: /{owner}/{repo}/intel/velocity
┌──────────────────────────────────────────────────────────────────────────────┐
│ ← intel ⚡ Velocity — gabriel/musehub │
│ Module growth rates, dual-window comparison, acceleration signal. │
├──────────────────────────────────────────────────────────────────────────────┤
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ COMMITS ANALYSED │ │ WINDOW SIZE │ │ REF │ │
│ │ 593 │ │ 20 │ │ dev (HEAD) │ │
│ └──────────────────┘ └──────────────────┘ └──────────────────┘ │
├──────────────────────────────────────────────────────────────────────────────┤
│ SORT BY [active_commits ▾] TOP [25 ▾] FILTER MODULE [______] [Apply] │
├──────────────────────────────────────────────────────────────────────────────┤
│ │
│ MODULE CURRENT WINDOW PRIOR WINDOW ACCEL │
│ ─────────────────────────────────────────────────────────────── ───────── │
│ │
│ ▌ tests/ +150 4 commits +303 12 commits -153 ▼ │
│ current ████████░░░░░░░░ prior ████████████████████████████░░ │
│ │
│ ▌ alembic/versions/ +18 3 commits +18 3 commits 0 ─ │
│ current ████░░░░░░░░░░░░ prior ████░░░░░░░░░░░░░░░░░░░░░░░░░░ │
│ │
│ ▌ musehub/api/routes/ +11 8 commits +11 5 commits 0 ─ │
│ current ██████████████░░ prior ██████████░░░░░░░░░░░░░░░░░░░░ │
│ │
│ ▌ musehub/services/ +0 5 commits +12 6 commits -12 ▼ │
│ current ░░░░░░░░░░░░░░░░ prior ████████░░░░░░░░░░░░░░░░░░░░░░ │
│ │
│ ▌ musehub/db/ +0 3 commits +0 3 commits 0 ─ │
│ stagnant ●●●●●●● (7) │
└──────────────────────────────────────────────────────────────────────────────┘
Acceleration badge legend
+N ▲ color: --color-success (accelerating — net positive vs prior)
0 ─ color: --text-muted (steady state)
-N ▼ color: --color-warning (decelerating — net negative vs prior)
Module row detail (expanded concept)
┌─────────────────────────────────────────────────────────────────┐
│ ▌ musehub/api/routes/musehub/ │ ← left accent bar:
│ │ green=accel, warn=decel
│ CURRENT WINDOW (last 20 commits) PRIOR WINDOW │
│ +11 added 0 removed +11 net +11 added 0 removed │
│ ████████████████████░░░░░░░░░░ ████████████████░░░░ │
│ 8 active commits 5 active commits │
│ │
│ ACCELERATION 0 ─ STAGNANT 2 commits │
└─────────────────────────────────────────────────────────────────┘
Dashboard sparkline (existing, already wired)
VELOCITY (12 weeks) View all →
┌──────────────────────────────────────────────────────────────┐
│ │
│ ▐▌ ▐▌ ▐▌ │
│ ▐▌ ▐▌ ▐▌ ▐▌ ▐▌ │
│ ▐▌ ▐▌ ▐▌ ▐▌ ▐▌ ▐▌ ▐▌ ▐▌ ▐▌ ▐▌ ▐▌ ▐▌ │
│ week12 ... week1 now │
└──────────────────────────────────────────────────────────────┘
(bars = commits that week; newest on right)
Spectral Theme Tokens Used
| Element | Token |
|---|---|
| Module row background | --bg-surface |
| Row hover | --bg-hover |
| Left accent — accelerating | --color-success |
| Left accent — decelerating | --color-warning |
| Left accent — steady | --border-default |
| Current-window bar fill | --color-accent |
| Prior-window bar fill | --border-strong |
| Acceleration +N badge | --color-success |
| Acceleration −N badge | --color-warning |
| Stagnant dot | --color-warning |
| Meta pill border | --border-subtle |
| Card/surface | --bg-surface / --border-default |
| Font mono | --font-mono |
Implementation Plan
Phase 0 — VelocityProvider rewrite (pure-SQL, no subprocess)
Problem: Current VelocityProvider calls _run_muse subprocess (exits 2 in
worker environment — same bug as the old CouplingProvider). DB table is empty.
Fix: Rewrite as pure-SQL BFS, mirroring CouplingProvider pattern:
- Fetch all commits + parent_ids for the repo.
- BFS-walk from HEAD, cap at
_MAX_WALK = 10_000. - Bulk-fetch
musehub_symbol_history_entriesfor the repo. - Derive module from
address.split('::')[0].split('/')[0]+ optional second segment. - Build two commit-ordered windows (current = 0..window_size-1, prior = window_size..2*window_size-1).
- Per-module: count added/removed/net/modified/active_commits for each window.
- Compute
acceleration = current.net − prior.net. - Count stagnant: commits in current window touching module with net = 0.
- DELETE stale rows; upsert fresh set.
- Also store full prior window stats (migration required — see Phase 1).
Docstring contract:
CouplingProvider → file pairs
VelocityProvider → module windows
EntangleProvider → symbol pairs
Constants:
_MAX_WALK = 10_000 # BFS depth cap
_WINDOW = 20 # commits per window (matches CLI default)
_TOP = 20 # stored leaderboard size
Phase 1 — Migration: expand prior-window columns
Add missing columns to musehub_intel_velocity so all prior-window stats are stored:
revision = "0012"
down_revision = "0011"
Adds:
prior_modified INTEGER NOT NULL DEFAULT 0
prior_active_commits INTEGER NOT NULL DEFAULT 0
window_size INTEGER NOT NULL DEFAULT 20
commits_analysed INTEGER NOT NULL DEFAULT 0
ix_intel_velocity_repo_active (repo_id, active_commits DESC)
Also update MusehubIntelVelocity model in musehub_db/musehub_models.py.
Phase 2 — SCSS (Spectral theme)
Two files following the established two-file split:
src/scss/components/_velocity.scss — visual only
.vl-row // module row: bg-surface, border-top, hover
.vl-row-accent // left 3px accent bar: success/warning/muted
.vl-module-path // font-mono, accent-link, truncated
.vl-bar-track // base track (bg-elevated, h:4px)
.vl-bar-fill // fill (--color-accent)
.vl-bar-fill--prior // prior fill (--border-strong)
.vl-accel-badge // acceleration number + arrow
.vl-accel-badge--up // color-success
.vl-accel-badge--down // color-warning
.vl-accel-badge--flat // text-muted
.vl-stagnant-dots // dot row for stagnant commits
.vl-meta-pill // stat pill: border-subtle bg
.vl-filter-bar // filter row layout
src/scss/pages/_velocity.scss — structural layout only
.vl-wrap // page container
.vl-summary-row // 3-col meta stat grid
.vl-filter-bar // form layout
.vl-module-list // flex-column gap-0
.vl-row // grid: path / current / prior / accel
.vl-bar-row // dual bar row
Wire into src/scss/app.scss.
Phase 3 — Route + Template
Route: GET /{owner}/{repo_slug}/intel/velocity
Query params:
sort:active_commits(default) |net|acceleration|stagnanttop:25|50|100|0(all)module: prefix filter string
Context built from musehub_intel_velocity query:
{
"modules": [{
"module", "added", "removed", "net", "modified", "active_commits",
"prior_added", "prior_net", "prior_modified", "prior_active_commits",
"acceleration", "stagnant_commits", "window_size", "commits_analysed",
"bar_pct": int, # current.active_commits / max_active * 100
"prior_bar_pct": int, # prior.active_commits / max_active * 100
"accel_class": str, # "up" | "down" | "flat"
"accel_label": str, # "+153" | "−153" | "0"
"net_label": str, # "+150" | "−12" | "0"
}],
"total_count", "commits_analysed", "window_size", "ref",
"sort", "top", "module_filter", "valid_tops", "valid_sorts",
}
Template: musehub/templates/musehub/pages/intel_velocity.html
- Extends base; breadcrumb: intel / velocity
intel-page-headerwith trending-up icon + descriptionintel-meta-bar: commits analysed, window size, ref- Filter form (sort + top + module prefix)
- Module list: each row shows dual-bar + acceleration badge + stagnant dots
Phase 4 — Dashboard integration
- Add "View all →" link to the existing velocity sparkline section
- Update dashboard sparkline title to link:
<a href="{base_url}/intel/velocity"\>VELOCITY</a> - No additional DB queries needed (sparkline data already in
intel.velocity.weeks)
Phase 5 — Test suite (TDD, 7 tiers, 50+ cases)
File: tests/test_velocity_provider.py
Docstring contract for every test:
Each test carries: what it verifies, why it matters (invariant or failure mode), and the expected observable — following the same pattern as test_coupling_provider.py.
Tier 1 Unit VL_01–VL_08 module derivation, acceleration formula,
window split, accel_class helper, bar_pct helper
Tier 2 Integration VL_09–VL_18 provider upserts, re-runs, window correctness,
stagnant count, prior vs current symmetry
Tier 3 E2E VL_19–VL_26 full seeded scenarios, sort params, top-N limit,
module prefix filter
Tier 4 Performance VL_27–VL_33 50-module dataset < 2s, 500-commit BFS < 3s,
re-run idempotent timing
Tier 5 Data Integrity VL_34–VL_39 acceleration = current.net − prior.net exactly,
stagnant count precise, no double-counting
across windows, bar_pct bounded [0,100]
Tier 6 Security VL_40–VL_45 injection strings in module paths, repo isolation
(two repos never share rows), unicode paths
Tier 7 Stress VL_46–VL_50 10k-commit BFS cap, 500-module repo,
empty repo returns [], single-commit repo
Total: 50 cases across 7 tiers.
Acceptance Criteria
/gabriel/musehub/intel/velocityreturns 200 with data matching CLI output- Module rows match
muse code velocity --jsonmodules list exactly - Acceleration badges colored correctly (success/warning/muted)
- Dual current/prior bars render proportionally
- Sort by active_commits, net, acceleration, stagnant all work
- Module prefix filter narrows list
- Dashboard sparkline has working "View all →" link
- All 50 tests pass
- VelocityProvider stores data without subprocess (worker-safe)
- Migration 0012 applies cleanly forward and back
Velocity feature — all 5 phases complete ✓
Deployed to staging as commit
10e6267a(image tag10e6267a-20260503083857).What shipped
VelocityProvideras pure-SQL BFS (no subprocess)prior_modified,prior_active_commits,window_size,commits_analysedcomponents/_velocity.scss+pages/_velocity.scss, wired intoapp.scss/intel/velocitylist page +/intel/velocity/detailper-module page/intel/velocitytests/test_velocity_provider.py(VL_01–VL_50, 7 tiers) — all passTest tiers
Algorithm
Two-window BFS from HEAD over
musehub_commits+musehub_symbol_history_entries:current_window= first 20 commits in BFS orderprior_window= commits 20–39