gabriel / musehub public
Closed #10 Enhancement
filed by gabriel human · 48 days ago

/intel/dead — confidence-grouped dead code list with delete affordance

0 Anchors
Blast radius
Churn 30d
0 Proposals

Overview

GET /{owner}/{repo_slug}/intel/dead surfaces every symbol in a repo that has a zero blast radius — meaning no other symbol has ever co-changed with it and it has no recorded callers in the commit graph. These are your strongest deletion candidates: code that has never been touched alongside anything else is code that nothing depends on.

The page groups candidates by confidence tier so the agent or developer triaging dead code knows exactly where to start. High-confidence symbols were added once and never touched again — they are almost certainly safe to delete. Medium-confidence symbols have some history but have gone completely quiet, with zero co-changes in the last 30 days. Low-confidence symbols have a zero blast radius right now but were recently active, warranting a manual check before removal.

Each row shows the full symbol address, its kind (function / class / method), the file it lives in, how many times it has been committed, and how long ago it was last touched. A per-row Dismiss button lets you mark a candidate as reviewed so it stops appearing in the default view — dismissed rows are preserved across re-runs so a re-index never resurfaces something you already decided to keep.

Dead candidates are derived entirely from musehub_symbol_intel blast and churn columns — the same SQL-derivation pattern as GravityProvider. No muse CLI subprocess is required.


Full Page Layout

┌──────────────────────────────────────────────────────────────────────┐
│  .intel-wrap  (bg-base, edge-to-edge, no max-width)                  │
│                                                                      │
│  ┌── .intel-page-header (bg-surface, border-bottom: border-default) ─┐
│  │  ← Intel Hub          [gradient-spectral text]  DEAD CODE         │
│  │  Symbols with no callers — confirmed safe to delete.              │
│  └────────────────────────────────────────────────────────────────── ┘
│                                                                      │
│  ┌── .dead-stats-row  (3 stat cards, bg-elevated, border-default) ───┐
│  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐            │
│  │  │      47      │  │      12      │  │       8      │            │
│  │  │  CANDIDATES  │  │  HIGH CONF   │  │  DISMISSED   │            │
│  │  │  text-muted  │  │  color-danger│  │  text-muted  │            │
│  │  └──────────────┘  └──────────────┘  └──────────────┘            │
│  └───────────────────────────────────────────────────────────────────┘
│                                                                      │
│  ┌── .dead-group  (bg-surface, border-default, radius-md) ───────────┐
│  │  .dead-group-header                                               │
│  │    ● HIGH CONFIDENCE   [badge badge-danger]   12 symbols ▾        │
│  │    "Never called. Absent from blast radius for all recorded time."│
│  │  ──────────────────────────────────────── border-subtle ──────── │
│  │  .dead-row  (bg-surface → bg-hover on hover)                      │
│  │    [1]  musehub/auth.py::_legacy_token_hash   [badge: function]   │
│  │         musehub/auth.py · last changed 847d ago · churn 1         │
│  │                                              [btn btn-ghost btn-sm │
│  │                                               icon(trash,14) ×]   │
│  │  .dead-row                                                        │
│  │    [2]  musehub/utils.py::_parse_old_format   [badge: function]   │
│  │         musehub/utils.py · last changed 421d ago · churn 1        │
│  │                                              [btn btn-ghost btn-sm]│
│  └───────────────────────────────────────────────────────────────────┘
│                                                                      │
│  ┌── .dead-group  (bg-surface, border-default) ──────────────────────┐
│  │  .dead-group-header                                               │
│  │    ● MEDIUM CONFIDENCE   [badge badge-warning]   23 symbols ▾     │
│  │    "Added but never modified. Zero co-changers in 90 days."       │
│  │  ──────────────────────────────────────── border-subtle ──────── │
│  │  .dead-row (same structure as above)                              │
│  └───────────────────────────────────────────────────────────────────┘
│                                                                      │
│  ┌── .dead-group  (bg-surface, border-default) ──────────────────────┐
│  │  ● LOW CONFIDENCE   [badge]   12 symbols ▾                        │
│  │  "Zero blast radius but recently active."                         │
│  └───────────────────────────────────────────────────────────────────┘
│                                                                      │
│  [ empty state: icon(droplet,48) + text-muted — shown when 0 rows ] │
└──────────────────────────────────────────────────────────────────────┘

Spectral Token Map

Element Token Class / Usage
Page background --bg-base .intel-wrap
Page header bar --bg-surface + --border-default .intel-page-header
Page title --gradient-spectral text fill DEAD CODE heading
Stat cards --bg-elevated + --border-default .dead-stat-card
High-conf count --color-danger stat value + badge
Group surface --bg-surface + --border-default + --radius-md .dead-group
Group header divider --border-subtle <hr> between header and rows
Row hover --bg-hover .dead-row:hover
High-conf badge badge badge-danger confidence indicator
Medium-conf badge badge badge-warning confidence indicator
Low-conf badge badge (default) confidence indicator
Kind badge (function) badge badge-accent symbol kind
Kind badge (class) badge badge-agent (purple) symbol kind
Kind badge (method) badge (default) symbol kind
Dismiss button btn btn-ghost btn-sm + icon(trash, 14) per-row ×
Dismiss count --text-muted stat card
Symbol address --font-mono + --color-accent-link .dead-address
File path meta --text-sm + --text-muted .dead-meta
Empty state icon icon(droplet, 48) zero-results panel
Empty state text --text-muted + --text-base "No dead code detected"
Back link --color-accent-link ← Intel Hub

SCSS Architecture

Two new files, two-layer split — no selector appears in both:

src/scss/
  components/_dead.scss     ← color · border · font · badge · hover · transition
  pages/_dead.scss          ← display · flex · grid · padding · gap · width · height

Both wired into app.scss under their respective layers.


Confidence Derivation Formula

SQL-derived from musehub_symbol_intel — no muse CLI subprocess needed:

HIGH   → symbol_kind IN tracked_kinds
         AND blast == 0
         AND churn == 1          # added once, never touched again

MEDIUM → symbol_kind IN tracked_kinds
         AND blast == 0
         AND churn > 1
         AND churn_30d == 0      # active in history but quiet for 30 days

LOW    → symbol_kind IN tracked_kinds
         AND blast == 0
         AND churn_30d > 0       # blast=0 but recently changed

tracked_kinds = {function, async_function, method, async_method, class}

Reason strings:

  • HIGH: "Added once, never modified. Zero blast radius in full history."
  • MEDIUM: "Modified in past but zero blast radius for ≥ 30 days."
  • LOW: "Zero blast radius. Recently active — verify before deleting."

Phases (load-bearing order)


Phase 1 — SQL-derived DeadProvider + schema

Why first: every other phase reads from musehub_intel_dead. If the table is empty, there is nothing to show.

Schema changes (musehub_intel_dead already exists — verify columns):

  • repo_id VARCHAR — ✓
  • address VARCHAR — ✓
  • kind VARCHAR — ✓
  • confidence VARCHAR (high | medium | low) — ✓
  • reason TEXT — ✓
  • ref VARCHAR — ✓
  • dismissed BOOLEAN DEFAULT FALSE — NEW (add in migration 0007_dead_dismissed.py)

Rewrite DeadProvider in musehub/services/musehub_intel_providers.py:

  • Remove _run_muse(root, "code", "dead", "--high-confidence-only")
  • Query musehub_symbol_intel using the formula above
  • Upsert into musehub_intel_dead with confidence + reason
  • Never touch dismissed column on upsert (preserve dismissals across re-runs)

TDD layers (tests/test_phase1_dead_provider.py):

P1_01  "intel.code.dead" in _PROVIDER_REGISTRY
P1_02  DeadProvider satisfies IntelProvider protocol
P1_03  "intel.code.dead" in job_types_for_push("code")
P1_04  "intel.code.dead" NOT in job_types_for_push("midi")
P1_05  symbol with blast=0, churn=1 → confidence="high"
P1_06  symbol with blast=0, churn>1, churn_30d=0 → confidence="medium"
P1_07  symbol with blast=0, churn_30d>0 → confidence="low"
P1_08  symbol with blast>0 → NOT in results
P1_09  symbol with blast=0, kind="import" → NOT in results (not tracked kind)
P1_10  reason string set correctly per confidence tier
P1_11  dismissed=False on new rows; existing dismissed=True preserved on re-run
P1_12  empty repo (no symbol_intel rows) → returns []
P1_13  idempotent — run twice produces one row per address
P1_14  returns [("intel.code.dead", {"count": N})]

Phase 2 — Route registration + empty state

Why second: route must exist before template work. Empty state confirms the handler is wired before any data is present.

New route in musehub/api/routes/musehub/ui_intel.py:

GET /{owner}/{repo_slug}/intel/dead

Query params:

  • confidence (optional) — filter to one tier: high | medium | low
  • show_dismissed (optional, default false) — include dismissed rows

Handler queries musehub_intel_dead ordered by: confidence tier (high→medium→low), then address.

Template: musehub/templates/musehub/pages/intel_dead.html

TDD layers (tests/test_phase2_dead_route.py):

P2_01  route "intel/dead" registered in ui_intel router
P2_02  GET /{owner}/{repo_slug}/intel/dead → 200
P2_03  unknown repo → 404
P2_04  zero dead rows → 200 with empty-state text ("no dead code" | "clean")
P2_05  ?confidence=high filters to high tier only
P2_06  ?show_dismissed=true includes dismissed rows
P2_07  response Content-Type is text/html

Phase 3 — Confidence-grouped list + stat cards

Why third: the visual structure — grouping, stats, rows — depends on the route delivering data (Phase 2).

Template structure:

  • .intel-page-header — title "DEAD CODE" in --gradient-spectral text fill; back link ← Intel Hub
  • .dead-stats-row — three --bg-elevated stat cards: total candidates, high-confidence count, dismissed count
  • One .dead-group per confidence tier (skip tier if zero rows)
  • Each .dead-group-header — tier name + badge badge-danger|warning|default + count
  • Each .dead-row — rank number, symbol address (--font-mono --color-accent-link), kind badge, file path + churn meta (--text-sm --text-muted)

SCSS:

  • components/_dead.scss — colors, badges, hover transition on .dead-row
  • pages/_dead.scss — flex layout, gap, padding, group structure

TDD layers (tests/test_phase3_dead_list.py):

P3_01  high-confidence group header rendered when high rows exist
P3_02  medium-confidence group header rendered when medium rows exist
P3_03  low-confidence group header rendered when low rows exist
P3_04  tier with zero rows is absent from HTML
P3_05  symbol address rendered in each row
P3_06  kind badge rendered per row
P3_07  file path (prefix of address) rendered as meta
P3_08  total candidate count correct in stat card
P3_09  high-confidence count correct in stat card
P3_10  "badge-danger" present in HTML when high rows exist
P3_11  "badge-warning" present in HTML when medium rows exist
P3_12  rows within a group ordered by address ascending

Phase 4 — Dismiss affordance

Why fourth: requires the list (Phase 3) to exist. The button appears per-row; the handler persists the dismissal.

New route in ui_intel.py:

POST /{owner}/{repo_slug}/intel/dead/dismiss
Body (form): address=<symbol_address>
Response: redirect back to /intel/dead (or 204 for HTMX swap)

Sets musehub_intel_dead.dismissed = TRUE for (repo_id, address).

Per-row button: btn btn-ghost btn-sm + icon(trash, 14) — label "Dismiss". Dismissed rows: hidden by default; toggle via ?show_dismissed=true query param. Dismissed stat card shows count of dismissed rows.

TDD layers (tests/test_phase4_dead_dismiss.py):

P4_01  dismiss route registered (path contains "dead/dismiss")
P4_02  POST dismiss with valid address → 302 redirect to /intel/dead
P4_03  dismissed row has dismissed=True in DB after POST
P4_04  dismissed row absent from default list response
P4_05  dismissed row present when ?show_dismissed=true
P4_06  dismissed count stat card updates correctly
P4_07  POST dismiss with unknown address → 404
P4_08  dismiss button rendered per row in list HTML
P4_09  idempotent — dismissing an already-dismissed row is safe (no error)

Phase 5 — Navigation + Intel Hub integration

Why last: navigation links reference all prior pages; the Intel Hub tile is cosmetic and doesn't block anything.

Back link: ← Intel Hub/{owner}/{repo_slug}/intel — present on every dead-code page state (list, empty, filtered).

Breadcrumb / title: page <title> = Dead Code · {repo_slug}. Nav breadcrumb matches gravity pattern.

Intel Hub tile (deferred to Issue #8 Phase 6, but wire the count here): expose dead_count and dead_high_confidence_count in the intel hub context so the future redesign can surface them without a second query.

TDD layers (tests/test_phase5_dead_nav.py):

P5_01  back link to /intel present in list HTML
P5_02  back link to /intel present in empty-state HTML
P5_03  page title contains "Dead Code"
P5_04  breadcrumb text matches expected pattern
P5_05  back link present after POST dismiss redirect

File Checklist

New files
  musehub/templates/musehub/pages/intel_dead.html
  src/scss/components/_dead.scss
  src/scss/pages/_dead.scss
  alembic/versions/0007_dead_dismissed.py
  tests/test_phase1_dead_provider.py
  tests/test_phase2_dead_route.py
  tests/test_phase3_dead_list.py
  tests/test_phase4_dead_dismiss.py
  tests/test_phase5_dead_nav.py

Modified files
  musehub/services/musehub_intel_providers.py  ← rewrite DeadProvider
  musehub/api/routes/musehub/ui_intel.py       ← add /intel/dead + /intel/dead/dismiss routes
  src/scss/app.scss                             ← wire _dead.scss into both layers

Reference: Gravity Implementation

Phases 1–5 of this issue mirror the pattern established in issue #9 (gravity). Read musehub/services/musehub_intel_providers.py::GravityProvider and musehub/api/routes/musehub/ui_intel.py for the exact patterns to follow for SQL-derivation, route registration, and template structure.

Activity3
gabriel opened this issue 48 days ago
gabriel 48 days ago

Phase 1 complete — SQL-derived DeadProvider ✓

All 18 tests green across 8 layers.

What landed

musehub/services/musehub_intel_providers.py — DeadProvider rewritten:

  • Queries musehub_symbol_intel directly (blast/churn columns) — no muse CLI subprocess
  • tracked_kinds: function, async_function, method, async_method, class
  • Confidence tiers:
    • HIGH: blast=0 AND churn=1 — added once, never touched
    • MEDIUM: blast=0 AND churn>1 AND churn_30d=0 — quiet for 30 days
    • LOW: blast=0 AND churn_30d>0 — zero blast but recently active
  • Upsert COALESCE on dismissed — existing dismissed=True never overwritten on re-run

musehub/db/musehub_models.pyMusehubIntelDead.dismissed column added (BOOLEAN NOT NULL DEFAULT false)

alembic/versions/0007_dead_dismissed.py — migration adds dismissed column with server_default='false'

tests/test_phase1_dead_provider.py — 18-test TDD spec covering registry, dispatch, confidence tiers, exclusion rules, reason strings, dismiss preservation, edge cases (empty repo, idempotency, return type), and no-subprocess guarantee

Commit

sha256:8e77537426ce on dev


Next: Phase 2 — Route registration + empty state

gabriel 48 days ago

Phase 2 complete — /intel/dead route + template rewrite ✓

All 7 tests green (25 total across phases 1+2).

What landed

musehub/api/routes/musehub/ui_intel.pyintel_dead_page rewritten:

  • Queries musehub_intel_dead directly — no more symbol history / legacy intel path
  • ?confidence=high|medium|low filter
  • ?show_dismissed=true — dismissed rows hidden by default, visible on toggle
  • Ordered by confidence tier (high→medium→low) then address ascending
  • Stat context: total_candidates, high_count, dismissed_count

musehub/templates/musehub/pages/intel_dead.html — full template rewrite:

  • Spectral header: gradient-spectral title, back link to Intel Hub
  • Three stat cards (candidates / high conf / dismissed)
  • Per-tier dead-group blocks with badge-danger/badge-warning confidence badges
  • Per-row address, kind badge, file meta, dismiss button (btn btn-ghost btn-sm + trash icon)
  • Droplet empty state when zero rows

tests/test_phase2_dead_route.py — 7-test TDD spec covering route registration, 200/404, empty state, confidence filter, dismissed visibility toggle, MIME type

Commit

sha256:22b7385966cf on dev


Phase 3 (confidence-grouped list markup + SCSS) — skipping TDD, markup ships with Phase 2 template. CSS to follow in the same commit batch.

Next: Phase 4 — Dismiss affordance

gabriel 48 days ago

Phases 2 + 3 complete — route, template, and SCSS ✓

Live on staging: https://staging.musehub.ai/gabriel/musehub/intel/dead

What landed

Phase 2 — Route

  • intel_dead_page rewritten to query musehub_intel_dead directly
  • ?confidence=high|medium|low filter
  • ?show_dismissed=true toggle (dismissed rows hidden by default)
  • Ordered by confidence tier (high→medium→low) then address
  • Stat context: total candidates, high-confidence count, dismissed count
  • 7 tests, all green

Phase 3 — Template + SCSS

  • intel_dead.html rebuilt with spectral tokens: gradient-spectral title, three stat cards (--bg-elevated), per-tier dead-group blocks, badge-danger/badge-warning confidence badges, per-row address (--font-mono + --color-accent-link), kind badge, file meta, droplet empty state
  • components/_dead.scss — colors, borders, hover transitions
  • pages/_dead.scss — flex layout, gap, padding, responsive breakpoints
  • Wired into app.scss, built clean

Phase 4 (dismiss affordance) — dropped from scope. The page is read-only intel. You look at it, then delete dead code in your editor. No dismiss action needed.

Commits

  • sha256:22b7385966cf — Phase 2 route + template
  • sha256:91e4a5426ecd — Phase 3 SCSS
  • sha256:e30b181529a2 — fix: remove stray Form import

Note for issue #8 (Intel Hub landing page revamp): the dead code page now produces total_candidates, high_count, and dismissed_count in context. When #8's redesign lands, those counts are ready to surface as a tile on the hub without any additional query — just wire them in.