gabriel / musehub public
Open #34
filed by gabriel human · 37 days ago

Issues List: Codebase Intelligence Mission Control

0 Anchors
Blast radius
Churn 30d
0 Proposals

Issues List: Codebase Intelligence Mission Control

What a Muse Issue Actually Is

Before redesigning the list, we have to be clear about what we're listing.

A GitHub issue is a prose message in a ticketing system. A human types a description of a problem, other humans reply, and someone eventually closes it with a comment.

A Muse issue is a named coordination point in the codebase's evolution. It carries:

  • symbol_anchors — specific named symbols (file.py::Symbol) not line numbers
  • commit_anchors — VCS commit IDs that directly address this issue
  • agent_id + model_id — cryptographic agent provenance when filed by an AI
  • Live code intelligence via musehub_symbol_indexer: blast radius, churn frequency, dead-code flags, co-change clusters
  • Three proposal resolution strategies: commit graph ancestry, branch reachability, symbol overlap
  • A release context: which Muse release a fix actually landed in
  • A typed event timeline: opened | closed | labeled | symbol_anchored | proposal_linked | proposal_merged

The list page must communicate ALL of this in compressed, scannable form. A row that shows only title + author + date is a GitHub clone. A row that shows title + symbol heat + blast exposure + proposal coverage + agent provenance is a Muse issue.


The List as Codebase Health Dashboard

The issues list is not a to-do list. It is a real-time health dashboard of the codebase's known problems, organised by code intelligence signals rather than creation date.

╔══════════════════════════════════════════════════════════════════════════════════╗
║  ISSUES MISSION CONTROL  ·  gabriel/muse                                        ║
╠══════════════════════════════════════════════════════════════════════════════════╣
║                                                                                  ║
║  ┌── HEALTH PULSE ────────────────────────────────────────────────────────────┐  ║
║  │  ●OPEN 14   ●CLOSED 47   ⚠ 3 HOTSPOT   🔴 2 BLAST-CRITICAL   🤖 5 AGENT  │  ║
║  │  ──────────────────────────────────────────────────────────────────────── │  ║
║  │  SYMBOL HEAT (open issues):                                               │  ║
║  │  compute_snapshot_id ████████████  8 issues   churn: 12×   blast: 47     │  ║
║  │  write_commit        ██████░░░░░░  4 issues   churn: 8×    blast: 31     │  ║
║  │  _replay_branch      ████░░░░░░░░  2 issues   churn: 3×    blast: 12     │  ║
║  └────────────────────────────────────────────────────────────────────────────┘  ║
║                                                                                  ║
║  ┌── FILTERS ─────────────────────────────────────────────────────────────────┐  ║
║  │  STATE: [open ▾]  LABEL: [▾]  SYMBOL: [▾]  AUTHOR: [▾]  SORT: [▾]       │  ║
║  └────────────────────────────────────────────────────────────────────────────┘  ║
╠══════════════════════════════════════════════════════════════════════════════════╣
║                                                                                  ║
║  ┌── ROW: HOTSPOT ISSUE ──────────────────────────────────────────────────────┐  ║
║  │  🔴 #42  [bug] [performance]           ◈ compute_snapshot_id              │  ║
║  │  Memory leak in snapshot diffing on large repos                            │  ║
║  │                                                                            │  ║
║  │  blast: 47   churn: 12×   ✗ no tests   ✗ no proposal   @agentception-w03 │  ║
║  │  ─ [AGENT: claude-opus-4-6]  ·  2 days ago  ·  3 anchors  ·  0 comments  │  ║
║  └────────────────────────────────────────────────────────────────────────────┘  ║
║                                                                                  ║
║  ┌── ROW: PROPOSAL-COVERED ───────────────────────────────────────────────────┐  ║
║  │  🟡 #38  [enhancement]                 ◈ write_commit  ◈ CommitRecord     │  ║
║  │  Deterministic commit IDs break on timezone-naïve datetimes               │  ║
║  │                                                                            │  ║
║  │  blast: 31   churn: 8×   ✓ tests   ✅ proposal #7 (open)   @gabriel       │  ║
║  │  ─ [human]  ·  5 days ago  ·  2 anchors  ·  3 comments                   │  ║
║  └────────────────────────────────────────────────────────────────────────────┘  ║
║                                                                                  ║
║  ┌── ROW: UNANCHORED (needs triage) ──────────────────────────────────────────┐  ║
║  │  ⚪ #35  [question]                     ─ no symbol anchors               │  ║
║  │  Muse push fails silently when remote is unreachable                       │  ║
║  │                                                                            │  ║
║  │  blast: —   churn: —   ?  needs anchoring   no proposal   @aaronrene      │  ║
║  │  ─ [human]  ·  1 week ago  ·  0 anchors  ·  1 comment                    │  ║
║  └────────────────────────────────────────────────────────────────────────────┘  ║
║                                                                                  ║
║  ┌── ROW: RELEASED (already fixed) ───────────────────────────────────────────┐  ║
║  │  🟢 #29  [bug]                          ◈ _CatFile.read                   │  ║
║  │  cat-file process leaks stdin handle after KeyboardInterrupt               │  ║
║  │                                                                            │  ║
║  │  blast: 12   ✓ tests   ✅ merged proposal #4   📦 v0.9.2   @gabriel       │  ║
║  │  ─ [human]  ·  2 weeks ago  ·  1 anchor  ·  2 comments  ·  CLOSED        │  ║
║  └────────────────────────────────────────────────────────────────────────────┘  ║
╚══════════════════════════════════════════════════════════════════════════════════╝

ASCII Layout Art

Layout A — Symbol Heat Header (always visible, no filter required)

╔══════════════════════════════════════════════════════════════════════════════╗
║  CODEBASE SYMBOL HEAT  (14 open issues across 7 symbols)                    ║
║                                                                              ║
║  compute_snapshot_id  ██████████████████████░░  8 issues  churn 12×  B:47 ║
║  write_commit         ████████████░░░░░░░░░░░░  4 issues  churn  8×  B:31 ║
║  _replay_branch       ████████░░░░░░░░░░░░░░░░  2 issues  churn  3×  B:12 ║
║  canonical_message    ████░░░░░░░░░░░░░░░░░░░░  1 issue   churn  5×  B:18 ║
║  (no anchor)          ██░░░░░░░░░░░░░░░░░░░░░░  3 issues  —          —    ║
║                                                                              ║
║  B = blast radius (direct + cross-repo callers)                             ║
╚══════════════════════════════════════════════════════════════════════════════╝

Layout B — Full Issue Row (annotated)

┌─────────────────────────────────────────────────────────────────────────────┐
│  [STATE] #NUM  [labels…]              [PRIMARY ANCHOR SYMBOL]              │
│  Issue title here — truncated at ~80 chars if needed…                      │
│                                                                             │
│  blast: N    churn: N×    [✓/✗ tests]   [✅/✗ proposal]   [📦 release]    │
│  ─ [human|AGENT: model]  ·  relative_time  ·  N anchors  ·  N comments    │
└─────────────────────────────────────────────────────────────────────────────┘

State indicators:

  • 🔴 Open + high blast/churn (hotspot) — critical attention needed
  • 🟡 Open + proposal exists — being worked
  • ⚪ Open + no anchors — needs triage
  • 🟢 Open + merged proposal OR release — may already be fixed
  • ● Closed — resolved

Layout C — Compact Mobile Row

╔═══════════════════════════════════════════════╗
║  ●14 OPEN  ⚠3 HOT  🤖5 AGENT  ●47 CLOSED   ║
╠═══════════════════════════════════════════════╣
║  🔴 #42 [bug] Memory leak in snapshot…       ║
║       ◈ compute_snapshot_id  B:47  churn:12× ║
║       ✗proposal  @agentception-w03  2d  🤖   ║
╠═══════════════════════════════════════════════╣
║  🟡 #38 [enhancement] Deterministic…         ║
║       ◈ write_commit  B:31  ✅ PR#7  @gabriel ║
╠═══════════════════════════════════════════════╣
║  ⚪ #35 [question] Muse push fails…           ║
║       ─ no anchors  needs triage  @aaronrene  ║
╚═══════════════════════════════════════════════╝

Layout D — Sidebar Intelligence Panels

╔═══════════════════════════════════════╗
║  🔥 HOTSPOT SYMBOLS                   ║
║  ──────────────────────────────────── ║
║  compute_snapshot_id  8 issues  12×   ║
║  write_commit         4 issues   8×   ║
║  _replay_branch       2 issues   3×   ║
╠═══════════════════════════════════════╣
║  💥 BLAST RISK                        ║
║  ──────────────────────────────────── ║
║  #42 compute_snapshot_id  blast: 47   ║
║  #38 write_commit         blast: 31   ║
║  #29 canonical_message    blast: 18   ║
╠═══════════════════════════════════════╣
║  🤖 AGENT ACTIVITY (last 7 days)      ║
║  ──────────────────────────────────── ║
║  agentception-w03  5 issues           ║
║  stori-bot         2 issues           ║
╠═══════════════════════════════════════╣
║  📦 RELEASED (may be fixed)           ║
║  ──────────────────────────────────── ║
║  #29 v0.9.2  · #31 v0.9.1            ║
╚═══════════════════════════════════════╝

Layout E — Issue Row Inline Expansion (HTMX)

┌─────────────────────────────────────────────────────────────────────────────┐
│  🔴 #42  [bug] [performance]           ◈ compute_snapshot_id  ▾ expanded  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  SYMBOL INTEL  (compute_snapshot_id)                                       │
│  blast direct: 31  blast cross: 16   churn: 12 changes/30d                │
│  co-changes: write_snapshot (8×), write_commit (6×)                        │
│                                                                             │
│  PROPOSAL COVERAGE: none — no open or merged proposals touch this symbol   │
│                                                                             │
│  RELEASE CONTEXT: not yet in any release                                   │
│                                                                             │
│  LINKED COMMITS: none                                                      │
│                                                                             │
│  AGENT PROVENANCE: agentception-w03  ·  claude-opus-4-6  ·  filed 2d ago  │
│                                                                             │
│  [ view full issue ]  [ open in CLI: muse hub issue read 42 ]             │
└─────────────────────────────────────────────────────────────────────────────┘

The Enriched List Row Data Model

Each row needs more than IssueResponse currently exposes. New IssueListEntry:

class IssueListEntry(TypedDict):
    """Enriched row for the issues list view.

    Extends IssueResponse with computed code intelligence fields.
    All computed fields are produced by enrich_issue_list_entry() —
    a single function that is the source of truth for what the list
    shows per row, batched across the full page to avoid N+1 queries.
    """
    # ── Core (from IssueResponse / MusehubIssue) ──────────────────────────
    issue_id: str
    number: int
    title: str
    state: str                        # "open" | "closed"
    labels: list[str]
    author: str
    author_identity_type: str         # "human" | "agent" | "org"
    agent_id: str                     # "" if human
    model_id: str                     # "" if human
    assignee: str | None
    assignee_identity_type: str       # "human" | "agent" | "org"
    created_at: str                   # ISO-8601
    updated_at: str
    comment_count: int
    symbol_anchors: list[str]         # ["file.py::Symbol", …]
    commit_anchors: list[str]         # ["abc123…", …]

    # ── Symbol intelligence (from musehub_symbol_indexer) ─────────────────
    primary_anchor: str | None        # first/highest-blast symbol address
    primary_anchor_short: str | None  # short name for display (after "::")
    max_blast_radius: int             # max blast (direct + cross) across all anchors
    max_churn_count: int              # max change_count across all anchors
    anchor_has_test_gap: bool         # any anchor has zero test proxies
    anchor_is_dead_code: bool         # any anchor flagged as dead code
    anchor_co_changes: list[str]      # top 3 co-changing symbol short names

    # ── Proposal coverage ─────────────────────────────────────────────────
    linked_proposal_count: int        # total linked proposals (any state)
    open_proposal_count: int          # open proposals covering this issue
    merged_proposal_count: int        # merged proposals (fix landed)
    latest_proposal_number: int | None
    latest_proposal_state: str | None # "open" | "merged" | "closed"

    # ── Release context ───────────────────────────────────────────────────
    released_in: list[str]            # semver tags where fix landed (from commit anchors)
    latest_release: str | None        # most recent release tag

    # ── Row health classification ─────────────────────────────────────────
    health_class: str
    # "hotspot"      — open, blast > threshold OR churn > threshold
    # "covered"      — open, has linked open proposal
    # "released"     — open, linked merged proposal OR in a release
    # "needs_triage" — open, zero symbol anchors
    # "stale"        — open, no activity in > 30 days
    # "closed"       — state == "closed"

enrich_issue_list_entry() — The Core Service Function

async def enrich_issue_list_entry(
    issue: IssueResponse,
    db: AsyncSession,
    *,
    intel_snapshot: SymbolIntelMap,
    proposal_index: dict[str, list[_ProposalMatch]],
    identity_index: dict[str, str],
    release_index: dict[str, list[str]],
) -> IssueListEntry:
    """Compute all display-facing fields for a single issue list row.

    All four index arguments are pre-fetched once per page request and
    shared across all rows — this function does zero additional DB queries.

    intel_snapshot:  output of load_intel_snapshot() — symbol → blast/churn
    proposal_index:  issue_id → list[_ProposalMatch] — pre-resolved proposals
    identity_index:  handle → identity_type — "human"|"agent"|"org"
    release_index:   commit_id → list[semver_tag] — commits in which releases

    health_class is derived in order:
      1. "closed"       if state == "closed"
      2. "hotspot"      if max_blast_radius > BLAST_HOTSPOT_THRESHOLD (default 20)
                        OR max_churn_count > CHURN_HOTSPOT_THRESHOLD (default 10)
      3. "released"     if any commit_anchor is in release_index
                        OR merged_proposal_count > 0
      4. "covered"      if open_proposal_count > 0
      5. "needs_triage" if len(symbol_anchors) == 0
      6. "stale"        if updated_at < now - 30 days
      7. "open"         (default)

    Performance contract: O(anchors) dict lookups only. Typical latency
    for a 25-row page: <10ms (pure in-memory after pre-fetch).

    Args:
        issue:           IssueResponse for this row.
        db:              Async session (not used — all data comes from indexes).
        intel_snapshot:  Pre-loaded symbol intelligence map.
        proposal_index:  Pre-resolved proposal coverage per issue_id.
        identity_index:  Pre-loaded handle → identity_type mapping.
        release_index:   Pre-loaded commit → release tag mapping.

    Returns:
        IssueListEntry with all fields populated.

    Raises:
        ValueError: If health_class cannot be determined (should never occur).
    """

New Filter Dimensions

class IssueListFilters(BaseModel):
    """Extended query parameters for the issues list page.

    All existing filters (state, label, assignee, author, sort) are preserved.
    New filters target Muse-specific code intelligence signals.
    """
    state: Literal["open", "closed"] = "open"
    label: str | None = None
    assignee: str | None = None
    author: str | None = None
    author_type: Literal["human", "agent", "org", "all"] = "all"
    symbol: str | None = None          # filter by anchor symbol address (substring match)
    health_class: str | None = None    # hotspot | covered | needs_triage | stale | released
    has_proposal: bool | None = None   # True = has linked proposal, False = no proposal
    blast_min: int | None = None       # minimum blast radius on any anchor
    sort: Literal[
        "newest", "oldest", "most-commented",
        "blast_desc",    # highest blast radius first
        "churn_desc",    # highest churn count first
        "triage_first",  # needs_triage issues first
        "stale_first",   # least recently updated first
    ] = "newest"
    cursor: str | None = None
    limit: int = Field(default=25, ge=1, le=100)

HTMX Architecture

Three layers, same as the proposals list:

Layer 1: SSR full page
  GET /{owner}/{repo}/issues?state=open
  → renders health pulse bar, symbol heat header, sidebar panels, row fragment

Layer 2: HTMX tab/filter swap
  hx-get="/{owner}/{repo}/issues/rows"
  hx-target="#issue-rows"
  hx-push-url="true"
  → bare fragment; replaces only the row list, not the chrome

Layer 3: HTMX row inline expansion
  hx-get="/{owner}/{repo}/issues/{number}/summary"
  hx-target="#issue-row-{number}-detail"
  hx-swap="innerHTML"
  → symbol intel, proposal coverage, release context, agent provenance

Fragment endpoints

GET /{owner}/{repo}/issues/rows       ← #issue-rows fragment
GET /{owner}/{repo}/issues/heat       ← #symbol-heat fragment (symbol heat bar only)
GET /{owner}/{repo}/issues/{n}/summary ← inline row expansion

New / Extended API Endpoints

GET /api/repos/{repo_id}/issues
    NEW params: author_type, symbol, health_class, has_proposal, blast_min,
                sort (blast_desc | churn_desc | triage_first | stale_first)

GET /api/repos/{repo_id}/issues/heat
    Returns: per-symbol aggregation across all open issues
    {
      "symbols": [
        {"address": "file.py::Sym", "short": "Sym", "issue_count": 8,
         "churn_count": 12, "blast_direct": 31, "blast_cross": 16}
      ],
      "unanchored_count": 3,
      "total_open": 14
    }

GET /api/repos/{repo_id}/issues/triage
    Returns: issues with zero symbol anchors (health_class="needs_triage")
    Purpose: dedicated triage queue for humans and agents

Symbol Anchors & Blast Radius

Existing symbols (current list page)

Symbol File Notes
issue_list_page musehub/api/routes/musehub/ui_issues.py Main SSR route — extend with new filters
list_issues musehub/services/musehub_issues.py DB query — add symbol, health_class, blast_min, sort params
find_proposals_by_commit_graph musehub/services/musehub_issues.py Proposal resolution signal 1
find_proposals_by_branch_reachability musehub/services/musehub_issues.py Proposal resolution signal 2
find_proposals_by_symbol_overlap musehub/services/musehub_issues.py Proposal resolution signal 3
get_issue_release_context musehub/services/musehub_issues.py Release context for detail page
load_intel_snapshot musehub/services/musehub_symbol_indexer.py Repo-wide code intelligence
lookup_symbol_intel musehub/services/musehub_symbol_indexer.py Per-address intel lookup
get_timeline musehub/services/musehub_issues.py Event + comment timeline
MusehubIssue musehub/db/musehub_models.py ORM model
MusehubIssueEvent musehub/db/musehub_models.py Event timeline ORM

New symbols to create

Symbol File Purpose
IssueListEntry musehub/models/musehub.py Enriched row TypedDict
IssueListFilters musehub/models/musehub.py Extended query params
SymbolHeatEntry musehub/models/musehub.py Per-symbol aggregation for heat bar
SymbolHeatResponse musehub/models/musehub.py /issues/heat response
enrich_issue_list_entry musehub/services/musehub_issues.py Per-row enrichment (zero DB)
enrich_issue_list_batch musehub/services/musehub_issues.py Parallel batch enrichment
prefetch_issue_list_indexes musehub/services/musehub_issues.py Pre-loads all 4 index maps
get_symbol_heat musehub/services/musehub_issues.py Symbol heat aggregation
classify_issue_health musehub/services/musehub_issues.py health_class derivation
issue_rows_fragment musehub/api/routes/musehub/ui_issues.py HTMX row fragment route
issue_row_summary musehub/api/routes/musehub/ui_issues.py Inline row expansion
issue_heat_fragment musehub/api/routes/musehub/ui_issues.py Symbol heat HTMX swap
issue_triage_page musehub/api/routes/musehub/ui_issues.py Dedicated triage queue page
initIssueList src/ts/pages/issue-list.ts Extended TS controller

Blast radius

musehub/models/musehub.py
  IssueListEntry, IssueListFilters, SymbolHeatEntry, SymbolHeatResponse
  ↓
musehub/services/musehub_issues.py
  enrich_issue_list_entry, enrich_issue_list_batch, prefetch_issue_list_indexes,
  get_symbol_heat, classify_issue_health, list_issues (extended)
  ↓
musehub/api/routes/musehub/ui_issues.py
  issue_list_page (extended), issue_rows_fragment (new),
  issue_row_summary (new), issue_heat_fragment (new), issue_triage_page (new)
musehub/api/routes/musehub/issues.py
  list_issues JSON endpoint (new filter params: symbol, health_class, blast_min, sort)
  + GET /issues/heat, GET /issues/triage
  ↓
musehub/templates/musehub/pages/issue_list.html          (extend)
musehub/templates/musehub/fragments/issue_rows.html      (extend)
musehub/templates/musehub/fragments/issue_row_detail.html  (new)
musehub/templates/musehub/fragments/symbol_heat.html     (new)
musehub/templates/musehub/pages/issue_triage.html        (new)
src/ts/pages/issue-list.ts                               (extend)

8-Tier Test Plan

Tier 1 — Shape / Schema

  • IssueListEntry TypedDict has all keys including health_class, max_blast_radius, primary_anchor, linked_proposal_count, released_in
  • IssueListFilters has correct defaults: state="open", sort="newest", limit=25, author_type="all"
  • GET /api/repos/{id}/issues/heat returns symbols list and unanchored_count
  • enrich_issue_list_entry is importable from musehub.services.musehub_issues
  • classify_issue_health returns one of the 7 defined strings
  • IssueListEntry.author_identity_type is one of "human", "agent", "org"
  • SymbolHeatEntry has address, short, issue_count, churn_count, blast_direct, blast_cross

Tier 2 — Round-Trip / Integration

  • Create 3 issues: one anchored to high-blast symbol, one with proposal, one with no anchors → list shows correct health_class for each
  • sort=blast_desc → issue with max_blast_radius=47 appears before issue with max_blast_radius=12
  • sort=triage_first → unanchored issue appears first regardless of creation date
  • health_class=hotspot filter → only issues with blast > threshold returned
  • health_class=needs_triage filter → only unanchored issues returned
  • symbol=compute_snapshot_id filter → only issues anchoring that symbol returned
  • has_proposal=True filter → only issues with at least one linked proposal returned
  • author_type=agent filter → only issues with agent_id != "" returned
  • Create issue → assign proposal → linked_proposal_count increments in list row
  • Merge proposal → merged_proposal_count increments, health_class may become released

Tier 3 — Edge Cases

  • Repo with zero issues → list returns [], health pulse shows 0 OPEN
  • Issue with 50 symbol anchors → primary_anchor = highest-blast one, no crash
  • Issue with commit anchor matching no release → released_in = []
  • Issue with commit anchor matching 3 releases → released_in has all 3
  • Symbol from intel_snapshot missing for an anchor → graceful fallback, max_blast_radius=0
  • blast_min=0 → all issues returned (not filtered out)
  • blast_min=9999 → empty list, not error
  • Issue where agent_id="" but author matches an agent handle in DB → correctly classified as agent
  • sort=stale_first with all issues updated today → stable sort by created_at

Tier 4 — Stress

  • 1,000 open issues → list_issues with limit=25 returns in under 50ms
  • prefetch_issue_list_indexes for 1,000 issues → under 100ms (batch queries)
  • enrich_issue_list_batch for 25 issues → under 20ms (pure in-memory after pre-fetch)
  • get_symbol_heat with 500 open issues across 50 symbols → under 30ms
  • 100 concurrent requests to /issues/rows → no deadlock, all under 200ms
  • sort=blast_desc on 1,000 issues → in-memory sort under 5ms

Tier 5 — Data Integrity

  • primary_anchor is always the anchor with the highest max_blast_radius across all anchors
  • max_blast_radius = max(blast_direct + blast_cross) across all symbol_anchors
  • classify_issue_health returns "hotspot" when max_blast_radius > BLAST_HOTSPOT_THRESHOLD
  • linked_proposal_count = len(proposal_index[issue_id]) exactly
  • released_in = union of release_index[commit_id] for all commit_anchors
  • agent_filed_count on stats bar = len([i for i in all_open if i.agent_id])
  • Symbol heat issue_count for address X = len([i for i in open_issues if X in i.symbol_anchors])
  • prefetch_issue_list_indexesidentity_index covers every author and assignee handle in the page

Tier 6 — Performance / Benchmarks

  • enrich_issue_list_entry for single issue → under 0.5ms (pure dict lookups)
  • Full issue_list_page SSR for 25 rows → under 150ms end-to-end
  • issue_rows_fragment → under 60ms (rows only, no chrome)
  • issue_row_summary inline expansion → under 15ms
  • issue_heat_fragment → under 20ms (single aggregation + sort)
  • All list_issues queries verified to use ix_musehub_issues_repo_state index

Tier 7 — Security

  • symbol filter param with path traversal (../../etc/passwd) → rejected or matched against allowlist, never passed to SQL raw
  • health_class filter with unmapped value → 422 from Pydantic, not 500
  • blast_min=-1 → 422 (ge=0 validation)
  • author filter with control chars / null bytes → sanitized via _validate_assignee pattern before any query
  • limit=10000 → clamped to 100, not a full-table scan
  • Private repo issues → anonymous GET returns 403, not issue data
  • Agent-filed issue with forged agent_id (not matching any registered agent) → shown with author_identity_type="agent" (no crash, no info leak)
  • cursor with tampered ISO-8601 string → graceful fallback to start, no 500

Tier 8 — Docstrings / API Contract

  • enrich_issue_list_entry has full Args, Returns, Raises, and performance contract (zero DB queries)
  • classify_issue_health documents each health_class value and the priority order
  • prefetch_issue_list_indexes documents all four index structures and their key types
  • get_symbol_heat documents the aggregation formula and sort order
  • IssueListEntry has field-level descriptions for every non-obvious field
  • IssueListFilters documents sort=blast_desc, sort=triage_first, sort=stale_first
  • issue_row_summary documents HTMX swap target and what data it exposes
  • BLAST_HOTSPOT_THRESHOLD and CHURN_HOTSPOT_THRESHOLD are module-level constants with comments explaining the calibration

7-Phase Implementation Plan

Phase 1 — Data Models & Enrichment Core

Branch: task/issue-list-models

  1. Define IssueListEntry TypedDict in musehub/models/musehub.py
  2. Define IssueListFilters Pydantic model with all new filter params
  3. Define SymbolHeatEntry, SymbolHeatResponse
  4. Write prefetch_issue_list_indexes(db, repo_id, issues) — batches all four pre-fetch queries (symbol intel, proposal coverage, identity types, release context)
  5. Write classify_issue_health(entry_partial) -> str — pure function, no DB
  6. Write enrich_issue_list_entry(issue, *, intel_snapshot, proposal_index, identity_index, release_index) -> IssueListEntry — zero additional DB queries
  7. Write enrich_issue_list_batch(issues, db) -> list[IssueListEntry] — calls prefetch_issue_list_indexes then enrich_issue_list_entry per row
  8. Tier 1 + 5 tests

Symbols: IssueListEntry, IssueListFilters, enrich_issue_list_entry, enrich_issue_list_batch, prefetch_issue_list_indexes, classify_issue_health


Phase 2 — Symbol Heat & Extended Sort

Branch: task/issue-list-heat

  1. get_symbol_heat(db, repo_id, state) -> SymbolHeatResponse — single aggregation query; joins musehub_issues.symbol_anchors with per_symbol_intel_json
  2. Extend list_issues(...) with symbol, health_class, has_proposal, blast_min filter params and new sort values
  3. GET /api/repos/{id}/issues/heat endpoint
  4. GET /api/repos/{id}/issues/triage endpoint
  5. Tier 2 + 6 (round-trip + performance) tests

Symbols: get_symbol_heat


Phase 3 — UI Route Extensions

Branch: task/issue-list-routes

  1. Extend issue_list_page SSR route: call enrich_issue_list_batch, pass IssueListEntry list + SymbolHeatResponse to template context
  2. Add issue_rows_fragment route — bare #issue-rows swap, no chrome
  3. Add issue_row_summary route — single-issue inline expansion with full symbol intel panel
  4. Add issue_heat_fragment route — #symbol-heat HTMX swap target
  5. Add issue_triage_page route — dedicated triage queue (zero-anchor open issues)
  6. Tier 3 + 7 tests (edge cases + security)

Symbols: issue_rows_fragment, issue_row_summary, issue_heat_fragment, issue_triage_page


Phase 4 — Template Rewrite

Branch: task/issue-list-templates

  1. Extend musehub/templates/musehub/pages/issue_list.html:
    • Health pulse bar (health_class counts + agent count)
    • Symbol heat header (top 5 symbols by issue_count)
    • Extended filter bar: author_type, health_class, symbol, has_proposal dropdowns
    • Sidebar: hotspot panel, blast risk panel, agent activity, released-may-be-fixed
  2. Extend musehub/templates/musehub/fragments/issue_rows.html:
    • Health class state indicator (colour dot)
    • Primary anchor badge with blast + churn inline
    • Proposal coverage indicator (✅ / ✗)
    • Agent badge with model name
    • Release badge (📦 v0.9.2)
  3. Create musehub/templates/musehub/fragments/issue_row_detail.html:
    • Symbol intel panel, proposal coverage list, release context, agent provenance, CLI command hint
  4. Create musehub/templates/musehub/fragments/symbol_heat.html
  5. Create musehub/templates/musehub/pages/issue_triage.html

Phase 5 — TypeScript Controller

Branch: task/issue-list-ts

Extend src/ts/pages/issue-list.ts:

  1. Handle HTMX after-swap on #issue-rows — re-sync URL params, restart stagger animations
  2. Handle #symbol-heat swap — animate bar width transitions
  3. Inline row expansion: toggle aria-expanded, animate detail panel height
  4. Health class colour system: CSS custom properties for each class colour
  5. Persist filter selections in URL params with history.replaceState

Phase 6 — MCP Context Extension

Branch: task/issue-list-mcp

  1. Extend ActiveIssueContext in musehub/models/musehub_context.py with list-relevant fields: health_class, max_blast_radius, max_churn_count, linked_proposal_count, released_in
  2. Add list_issues_context(repo_id, filters) MCP tool — returns IssueListEntry list for agent consumption
  3. Agents can now scan the issue queue as machine-readable data and act (anchor, triage, file proposals) on specific issues

Phase 7 — Index Optimisation

Branch: task/issue-list-indexes

  1. Add composite index (repo_id, state, updated_at) for sort=stale_first
  2. Add GIN index on symbol_anchors JSON column for symbol= filter (PostgreSQL)
  3. Verify all list queries hit correct indexes via EXPLAIN ANALYZE harness in tests
  4. Add BLAST_HOTSPOT_THRESHOLD = 20 and CHURN_HOTSPOT_THRESHOLD = 10 as tunable constants in musehub/config.py

What Makes This Uniquely Muse

GitHub's issue list: title, label badge, author avatar, date, comment count. Zero code intelligence.

This list:

  1. Symbol heat at the page level — before you open a single issue, you can see which symbols are generating the most friction across the entire open issue queue. compute_snapshot_id has 8 open issues? That's a systemic problem with that function, not 8 isolated bugs.

  2. Blast radius per row — every issue anchored to a symbol with blast > 20 is visually flagged. Picking which issue to fix first is now informed by actual architectural risk, not gut feeling.

  3. needs_triage as a first-class state — unanchored issues are explicitly surfaced. An unanchored issue is not just missing metadata — it's an issue the codebase can't reason about. The triage queue makes that visible and actionable.

  4. Agent provenance is structural — when an agent files an issue, the agent_id and model_id are in the database, cryptographically tied to the commit that triggered the issue. The list shows @agentception-w03 [claude-opus-4-6] with the same confidence as @gabriel [human].

  5. Proposal coverage per row — if someone is already working on a fix (open proposal that touches this symbol), the list shows it. No duplicate effort.

  6. Release context at a glance — the list can show whether an issue's commit anchors have already landed in a release. This is the difference between "open but already fixed" and "open and definitely broken."


Assignee

@aaronrene — this is the companion to issues #2 (proposals list) and the issue detail reimagination doc already in docs/roadmaps/issues-reimagined.md. The detail page is strong; the list page is where a developer or agent spends most of their time deciding what to work on next. That decision surface deserves the full intelligence stack.

Priority: High — the list page is the entry point to the issues system. Every other issue feature is invisible until the list makes it legible.

Activity
gabriel opened this issue 37 days ago
No activity yet. Use the CLI to comment.