diff --git a/docs/plans/2026-06-15-001-feat-command-center-and-sdlc-gaps-plan.md b/docs/plans/2026-06-15-001-feat-command-center-and-sdlc-gaps-plan.md new file mode 100644 index 0000000000..7b8a3e364a --- /dev/null +++ b/docs/plans/2026-06-15-001-feat-command-center-and-sdlc-gaps-plan.md @@ -0,0 +1,787 @@ +--- +title: "feat: Command Center dashboard + software-delivery-loop gap-fill" +type: feat +status: active +date: 2026-06-15 +depth: deep +origin: none (solo plan; external research from a competitor product, see Sources) +--- + +# feat: Command Center dashboard + software-delivery-loop gap-fill + +## Summary + +Build a **Command Center** for the Fusion dashboard — a combined **historical analytics** +surface (tokens, tools, activity, productivity, ecosystem, per-agent/per-node breakdowns +over selectable date ranges, with CSV + OpenTelemetry export) **and a live Mission-Control +panel** (concurrent sessions, active nodes, what each agent is doing right now, SDLC funnel +throughput). Then close the gaps between Fusion and an end-to-end software-delivery system — +the **Signal → Triage → Plan → Execute → Validate → Ship → Monitor** loop — by adding the +stages Fusion does not yet cover (external signal ingestion, monitoring/incident response, +persistent knowledge layer) and the cross-cutting capabilities the loop implies (a **Fusion Model Router** that auto-selects the +cheapest-capable model per task, auto-triage of inbound issues **and PRs**, auto-resolution of +PR review comments, and surfacing external signals as dashboard metrics). + +This plan is intentionally large because the user asked for full implementation coverage of +all gaps. It is organized into three phases so it can land incrementally: **Phase A** (metrics +foundation) and **Phase B** (the Command Center itself) deliver the branch's headline feature; +**Phase C** (SDLC gap-fill) is sequenced after, and several of its units are large enough that +the plan flags them as candidates to spin into their own brainstorm before execution. + +--- + +## Problem Frame + +Fusion is the model- and surface-agnostic orchestration layer for a developer driving many +agent sessions across nodes and surfaces (see `STRATEGY.md`). Two problems: + +1. **There is no observability surface.** A developer juggling 10+ agents across machines has + no single place to answer "how much am I spending, on which models, across which nodes, + what's running right now, and what did all this work actually ship?" Fusion captures the + raw data (per-task token columns, agent runs, activity log, commit associations, PRs, CLI + sessions) but exposes only fragmentary panels (`ReliabilityView`, `AgentTokenStatsPanel`). + Fusion's own `STRATEGY.md` key metrics (concurrent sessions, active nodes, ecosystem + breadth, task completion rate, LOC shipped) are precisely what such a view should make + observable. + +2. **Fusion covers the middle of the SDLC loop but not the ends.** The end-to-end delivery + loop is self-reinforcing: *Signal → Triage → Plan → Execute → Validate → Ship → Monitor*. + Fusion is strong on Plan/Execute/Validate/Ship and has partial Triage (GitHub issue + ingestion). It has **no Signal ingestion beyond GitHub, no Monitor stage, and no persistent + knowledge layer** — the parts that make the loop close and compound. + +This plan addresses both: the Command Center (Phases A–B) and the missing stages (Phase C). + +--- + +## Requirements + +Traceability is to Fusion's `STRATEGY.md` key metrics (KM) and the external feature set the +user asked us to match (external research, no formal requirements doc). + +- **R1 — Historical analytics.** Surface token consumption (by model, provider, node, agent, + time), tool usage and autonomy ratio, activity (sessions/messages/active-nodes over time), + productivity (files, commits, PRs, LOC), and ecosystem breadth (unique models + plugins). + (KM: all five.) +- **R2 — Date-range filtering.** All analytics support a selectable range (presets + custom), + mirroring `agent-token-usage.ts`'s windowed aggregation extended to arbitrary ranges. +- **R3 — Live Mission Control.** A real-time panel: concurrent agent sessions, active nodes, + per-agent current activity, and an SDLC funnel (triage→todo→in-progress→in-review→done) + with live throughput. (KM: concurrent agent sessions, active nodes, task completion rate.) +- **R4 — Export.** CSV export of any analytics table, and OpenTelemetry (OTLP) export of the + metrics, for shipping to Datadog/Grafana/etc. +- **R5 — Analytics API.** Programmatic endpoints (activity, tokens, tools, productivity) so an + agent can pull metrics. +- **R6 — Cost.** Derive USD cost from token counts × a model pricing map (Fusion stores tokens + but not cost today). +- **R7 — Signal ingestion.** Ingest external signals beyond GitHub (error trackers / alerting: + Sentry, Datadog, PagerDuty, generic webhook) into triageable tasks. +- **R8 — Triage stage.** Auto-classify and decompose incoming signals/issues into board tasks. +- **R9 — Monitor stage.** Track deployments and production incidents, compute MTTR, and feed + Monitor signals back into the funnel (closing the loop). +- **R10 — Knowledge layer.** A persistent, incrementally-refreshed knowledge index downstream + agents can query. +- **R13 — Fusion Model Router.** Automatic per-task / per-request model selection across + providers (route routine steps to fast/cheap models, reserve stronger models for hard + reasoning), with fallback, prompt-cache awareness, and respect for existing model controls — + a direct expression of Fusion's model-agnostic thesis. +- **R14 — Auto-triage of incoming issues *and* pull requests.** Triage applies to inbound PRs + (external contributions, dependabot, etc.), not just issues/signals — classify, label, and + route or open a follow-up task. +- **R15 — Auto-resolution of PR review comments.** Build on Fusion's existing **Review-response + loop** so PR review threads are acted on automatically (fix + push + reply, or disagree with + reasoning) as a first-class, surfaced capability. +- **R16 — External signals in dashboard metrics.** The Command Center surfaces external signals + (errors, alerts, incidents from R7 sources) as a metric area and in Mission Control, not only + as task-creating triggers. + +--- + +## Key Technical Decisions + +### KTD1 — Mirror the built-in view pattern; no router, no plugin +Register `command-center` as a `BuiltInTaskView` (`useViewState.ts`), lazy-load `CommandCenter` +in `App.tsx`, and add the nav entry in `Header.tsx`, exactly mirroring `reliability`. The +dashboard has **no URL router** (view state is `?view=` + `localStorage`); do not introduce +one. Ship as a built-in view (optionally behind an `experimentalFeatures.commandCenter` flag +like `insights`/`memoryView`), **not** a plugin — it is core product surface. + +### KTD2 — Aggregation lives in `packages/core`; the route is a thin adapter +Put all metric math in new `packages/core/src/*-analytics.ts` modules (so engine/CLI can reuse +it), mirroring `agent-token-usage.ts`. The dashboard exposes it via an `ApiRouteRegistrar` +(`register-command-center-routes.ts`) registered in `routes.ts`, mirroring +`register-usage-routes.ts`. Do **not** import the engine into the React frontend; everything +goes over HTTP/SSE. + +### KTD3 — A queryable telemetry/events table is required (the data is not all queryable today) +Token counts live on `tasks` (queryable) but **tool calls live in per-task JSONL agent logs** +and messages/sessions are spread across `chat_room_messages`/`cli_sessions`. The Tools area +and autonomy ratio need a queryable source. Decision: introduce a `usage_events` table in +`packages/core/src/db.ts` (migration in the same file) fed from the **store-level event seams** +(task-execution `appendAgentLog`, heartbeat/run `appendRunLog`, and the CLI/chat +`chat_room_messages` writer), rather than parsing JSONL at query time. This is the single +highest-risk change — see Risks for the SCHEMA_VERSION trap. **Tool calls are NOT all funneled +through one writer:** task execution, heartbeat agents (`appendRunLog`, callback-mode — bypasses +the file store), and CLI/chat (`chat_room_messages`, not tool-granular today) are distinct paths, +so a dual-write at `agent-log-file-store.ts` alone would silently undercount. The design must +instrument all three OR explicitly scope `usage_events` to task-execution and document the +exclusion. *Alternative (open — see Open Questions):* a lazy-materialization / cache table +populated on first query per time-bucket — viable because R1/R2/R5 state no sub-second +requirement, and it avoids coupling the agent hot-path write to a SQLite transaction. + +### KTD4 — Charting: extend the house "hand-rolled CSS bars" style, do not add a chart lib (default) +The codebase has **zero** charting dependencies and a strong convention of hand-built CSS-bar +histograms (`ReliabilityView.tsx:199-207`). Default to extending that style with a small set of +reusable primitives (bar, sparkline, stacked bar, funnel) under +`packages/dashboard/app/components/command-center/charts/`. Adding a dependency (Recharts) is a +notable departure requiring a changeset + maintainer sign-off; surfaced as a call-out, not +assumed. *Rationale:* keeps bundle lean, matches existing code, avoids a lazy-loaded chart +vendor in a view that already lazy-loads. Revisit only if a chart type (e.g. multi-series time +series) proves impractical by hand. + +### KTD5 — Live data uses push + poll convergence, throttled deltas +The Mission-Control panel follows the documented live-data pattern: an SSE event triggers an +immediate refetch while a poll interval (e.g. 5s) runs as a fallback **only while work is +in-flight**; high-frequency updates are throttled server-side. Historical analytics use plain +query + SWR (no streaming machinery). (See `docs/solutions/architecture-patterns/observable-long-running-agent-turns-through-blocking-plugin-route-seam.md`.) + +### KTD6 — Cost via a versioned pricing map, never persisted as truth +Cost is derived at read time from token columns × a `model-pricing.ts` map (input/output/cache +rates per `modelProvider`+`modelId`), not stored. Unknown models surface tokens with cost +marked unavailable rather than guessing. Keeps historical rows correct when prices change and +avoids a migration to backfill cost. The map carries a `pricingAsOf` date and per-entry source +link; the UI shows "prices as of " and marks entries older than a threshold low-confidence, +so stale-but-present rates (which the unknown-model guard does not catch) are visible rather than +silently wrong. + +### KTD7 — SDLC stages map onto existing workflow columns + new trait-tagged columns +Phase C does not invent a parallel pipeline. Signal/Triage/Monitor attach to the existing +workflow-column system (`Column`, `Trait`, `Workflow Extension` in `CONCEPTS.md`): a `signal` +intake column, a `triage` trait that auto-decomposes, and a `monitor` trait that watches +deployments. This reuses the workflow runtime rather than forking lifecycle policy. + +### KTD8 — Signal ingestion reuses the GitHub ingestion seam +Sentry/Datadog/PagerDuty/webhook ingestion mirrors the existing GitHub source path +(`github-source-issue-close.ts`, `github-poll.ts`, `github-webhooks.ts`) behind a common +`SignalSource` adapter interface, so each provider is a small adapter rather than bespoke wiring. + +### KTD9 — Model Router is a selection layer over existing agent/model resolution, not a new executor +The Fusion Model Router slots into the existing **effective-agent / model-pair resolution** path +(`CONCEPTS.md` Effective agent, Workflow Setting model lanes) as a routing policy that *chooses* +the `(provider, model)` before a session starts (session routing) and may re-route per request +for routine sub-steps. It does **not** add an executor kind — it picks which existing +CLI/provider runs. It respects column-agent overrides and model controls (an org/project that +restricts a model restricts the router's ability to pick it), and reuses the U3 pricing map + +U1 telemetry to make cost/latency-aware decisions and to measure its own savings. Routing rules +are declarative (task complexity signal → model tier) with a safe fallback to the configured +default pair when the router is disabled or a pick is unavailable. This is a natural fit for the +`ecosystem breadth` strategy metric and feeds the Command Center directly. + +### KTD10 — PR-comment auto-resolution extends the existing Review-response loop, not a rebuild +Fusion already has a **Review-response loop** (entry point `packages/engine/src/pr-response-run.ts`). R15 makes it a +first-class, default-surfaced capability rather than new machinery: ensure it triggers on +PR-entity review threads, expose its activity in the Command Center / Mission Control, and gate +it consistently with the merge/auto-merge model. Do not re-implement the loop. + +--- + +## High-Level Technical Design + +### The software delivery loop: Fusion today vs. the gaps this plan fills + +```mermaid +flowchart LR + subgraph Loop["Software delivery loop (SDLC)"] + Signal["Signal\n(R7 — GAP*)"] --> Triage["Triage\n(R8 — partial)"] + Triage --> Plan["Plan\n(have: CE, missions)"] + Plan --> Execute["Execute\n(have: CLI sessions, nodes)"] + Execute --> Validate["Validate\n(have: validator, review loop)"] + Validate --> Ship["Ship\n(have: merge, PR entity)"] + Ship --> Monitor["Monitor\n(R9 — GAP)"] + Monitor -.feeds.-> Signal + end + Knowledge["Knowledge layer (R10 — GAP)"] -.enriches every stage.-> Loop + CC["Command Center (R1–R6) — observes the whole loop"] -.reads telemetry from.-> Loop + style Signal fill:#3b82f6,color:#fff + style Monitor fill:#3b82f6,color:#fff + style Knowledge fill:#3b82f6,color:#fff + style CC fill:#1e40af,color:#fff +``` +*GAP\* = GitHub-only today; other sources are the gap. Blue = net-new in this plan.* + +### Command Center data flow (Phase A → B) + +```mermaid +flowchart TD + subgraph Sources["Existing data (packages/core, SQLite + JSONL)"] + T["tasks (token cols, model, files, timing)"] + AR["agentRuns / agentHeartbeats / agentTaskSessions"] + AL["activityLog"] + CC0["task_commit_associations"] + PR["pull_requests"] + CS["cli_sessions / chat_room_messages"] + JL["per-task JSONL agent logs (tool calls)"] + end + JL -->|U1: writer also appends| UE[("usage_events (new table)")] + CS -->|U1| UE + Sources --> AGG["U2: *-analytics.ts aggregators in packages/core\n(date-range windows, group-by model/node/agent)"] + UE --> AGG + AGG --> PRICE["U3: model-pricing.ts → cost (KTD6)"] + PRICE --> API["U9: register-command-center-routes.ts (ApiRouteRegistrar)\n/api/command-center/{tokens,tools,activity,productivity,live}"] + API -->|HTTP + SWR| HV["U5: Command Center historical areas"] + API -->|SSE + poll (KTD5)| LV["U6b: Mission-Control live panel (frontend)"] + API --> CSV["U8: CSV export"] + API --> OTEL["U10: OTLP exporter"] + AGG --> FUNNEL["U7: SDLC funnel (activityLog transitions)"] + HV --> VIEW["U4: Command Center shell (lazy view, nav entry)"] + LV --> VIEW + FUNNEL --> VIEW +``` + +--- + +## Output Structure + +New files this plan introduces (repo-relative; existing files edited are listed per unit): + +``` +packages/core/src/ + usage-events.ts # U1 write/query the new events table + model-pricing.ts # U3 pricing map + cost derivation + token-analytics.ts # U2 extends agent-token-usage windows → ranges + tool-analytics.ts # U2 tool calls by category, autonomy ratio + activity-analytics.ts # U2 sessions/messages/active-nodes/stickiness + productivity-analytics.ts # U2 files/commits/PRs/LOC, language dist + command-center-live.ts # U6a live snapshot: sessions, nodes, funnel + otel-metrics.ts # U10 OTLP metric mapping (pure mapping; wiring is in dashboard) + model-router.ts # U17 routing policy + rule evaluation +packages/dashboard/src/routes/ + register-command-center-routes.ts # U9 analytics + live + export endpoints + register-signal-routes.ts # U11 inbound signal webhooks +packages/dashboard/src/ + command-center-csv.ts # U8 CSV serialization + signal-source.ts # U11 SignalSource adapter interface + registry (mirrors github-* — dashboard) + signal-sources/{sentry,datadog,pagerduty,webhook}.ts # U11 adapters + knowledge-index.ts # U14 knowledge store + refresh (mirrors insights-routes — dashboard) + monitor-routes.ts # U13 deployment/incident tracking +packages/dashboard/app/components/command-center/ + CommandCenter.tsx # U4 shell + sub-view tabs + CommandCenter.css # U4 + charts/{Bar,StackedBar,Sparkline,Funnel}.tsx + .css # U4 chart primitives + areas/{TokensArea,ToolsArea,ActivityArea,ProductivityArea,EcosystemArea,SignalsArea}.tsx # U5 + MissionControlPanel.tsx # U6b live ops + SdlcFunnel.tsx # U7 funnel/throughput + DateRangePicker.tsx # U5/B shared range control +``` + +> **Package note (feasibility).** New modules that *mirror an existing precedent* must live in +> the same package as that precedent. The GitHub ingestion path, `reliability-metrics.ts`, +> `subtask-breakdown.ts`, `runtime-provider-probes.ts`, and `pr-conflict-resolver.ts` all live in +> `packages/dashboard/src`, **not** `packages/core` — so `signal-source.ts` (mirrors `github-*`), +> `knowledge-index.ts` (mirrors `insights-routes.ts`), the OTel wiring, and `monitor-routes.ts` +> belong in dashboard. Pure, reusable aggregation (`*-analytics.ts`, `model-pricing.ts`, +> `command-center-live.ts`, the OTLP *mapping*, `model-router.ts`) stays in `packages/core` per +> KTD2. If a core module needs GitHub-ingestion code, expose it through a core-level seam rather +> than importing dashboard into core. + +--- + +## Implementation Units + +### Phase A — Metrics foundation + +#### U1. Queryable usage-events telemetry table +**Goal:** Create a normalized, queryable source for tool calls, messages, and session +lifecycle so the Tools/Activity areas and OTel export do not have to parse JSONL at query time. +**Requirements:** R1, R3, R5 (substrate). +**Dependencies:** none. +**Files:** +- `packages/core/src/db.ts` — add the `usage_events` table, a new `applyMigration(N, ...)` block, and bump `SCHEMA_VERSION` to N (currently 117). `applyMigration`, `SCHEMA_VERSION`, `MIGRATION_ONLY_TABLE_SCHEMAS`, and `SCHEMA_COMPAT_FINGERPRINT` all live in `db.ts`, **not** `db-migrate.ts` (which is the legacy-data import path) — see Risks. +- `packages/core/src/usage-events.ts` (new: append + range query helpers) +- a dedicated `emitUsageEvent(...)` capture call invoked from the layer where `model`/`provider`/`nodeId`/`category` are already in scope — the **executor / session-run layer** — **not** by overloading `store.appendAgentLog` / `store.appendRunLog`, whose signatures and the `AgentLogEntry` they persist carry none of those fields (widening them is a high-fanout ~20+ call-site change across `engine/src/merger.ts`, `executor.ts`, etc.). `agent-log-file-store.ts` is likewise unusable (pure-FS, no DB handle). The field-carrying mechanism (dedicated call vs signature-widening vs hot-path lookup) is recorded as an Open Question. +- `packages/core/src/__tests__/usage-events.test.ts`, and the `db.ts` migration test (extend) +**Approach:** Columns: `id`, `ts`, `kind` (`tool_call|tool_result|tool_error|user_message|session_start|session_stop`), `taskId`, `agentId`, `nodeId`, `model`, `provider`, `toolName`, `category`, `meta` (JSON). **v1 scope:** task-execution + run-log events (which can carry model/provider/node from the session context). The chat path (`ChatStore`/`chat_room_messages`, which has no model/provider at its write site) contributes **message counts only** — chat-origin rows are model/provider-null by design, documented in U2. **`nodeId`** is sourced from the run/session context (`agentRuns`/`cli_sessions`), not the `tasks` row (which has no `nodeId`); events with no node context record `nodeId` null. **Mapping:** the agent-log `type` value `tool` maps to `kind: tool_call` (there is no `tool_call` in `AgentLogType`, which is `text|tool|thinking|tool_result|tool_error`); `user_message`/`session_start`/`session_stop` originate from `cli_sessions`/`chat_room_messages`. **`meta` safety:** capped at a fixed byte size (~4 KB, rejected at write); carries only non-sensitive descriptors (error code, category, duration) — **never** tool arguments/content or credential-class fields — with a documented retention/age-out policy. Reads come from SQLite. Index `(ts)`, `(taskId)`, `(agentId)`. +**Patterns to follow:** `packages/core/src/agent-token-usage.ts` (range scans), the `applyMigration` shape in `db.ts`, and the schema-version learning doc. +**Test scenarios:** +- Happy: a `tool`-type agent-log entry inserts one `usage_events` row with `kind: tool_call` and correct `category`. +- Completeness: a heartbeat-run (`appendRunLog`) tool call and a chat tool call either appear in `usage_events` or are asserted intentionally absent per the documented scope (guards the multi-path undercount). +- Edge: a chat-session event with no `taskId` records with `taskId` null and `agentId` set. +- Migration: seed a DB **at the previous schema version**, run migrate, assert the table exists and `SCHEMA_VERSION` equals the highest migration target (fresh-DB tests cannot catch the early-return bug). +- Error: malformed event is skipped without throwing and without aborting the underlying write. +- Edge: a `meta` payload exceeding the byte cap is rejected at write; tool-argument content never lands in `meta`. +- Integration: a real task execution that calls 3 tools yields 3 `tool_call` rows queryable by range, with `model`/`provider`/`nodeId` populated from the session context. + +#### U2. Core analytics aggregators (date-range windows) +**Goal:** Pure, reusable aggregation over tasks + `usage_events` producing the six measurement +areas for an arbitrary date range, grouped by model/provider/node/agent. +**Requirements:** R1, R2. +**Dependencies:** U1. +**Files:** +- `packages/core/src/token-analytics.ts`, `tool-analytics.ts`, `activity-analytics.ts`, + `productivity-analytics.ts` (new) +- `packages/core/src/__tests__/{token,tool,activity,productivity}-analytics.test.ts` +**Approach:** Each exports `aggregate({from, to, groupBy})`. Tokens: sum `tasks.tokenUsage*` +columns filtered by `tokenUsageLastUsedAt` in range. Tools: count `usage_events` by +`category`; **autonomy ratio = tool_call count / human-intervention events** — NOT raw user +messages, which trend to zero for autonomous task execution. The denominator's three components +have distinct, named sources (they are not one queryable thing): **approvals** from +`approval_request_audit_events` (filter to `created`/`approved`); **user-authored steers** from +the `SteeringComment[]` JSON on the task row, filtered to `author === "user"` (agent-authored +steers excluded — note this re-introduces a per-task JSON read, so mirror steers into +`usage_events` if range-querying proves costly); **waiting-on-input** is a task *status*, not a +counted event — drop it unless a concrete answer event is defined. A fully-autonomous session +(zero interventions) reports tool-calls-per-session instead of ∞. Activity: distinct +active nodes/agents per day, sessions from `cli_sessions`, messages from `usage_events`, +**stickiness = DAU/MAU**. Productivity: `tasks.modifiedFiles` count + language distribution, +`task_commit_associations` count, `pull_requests` count; LOC from commit diff stats if +available else flagged unavailable. Generalize `agent-token-usage.ts`'s 24h/7d/all-time windows +to `(from,to)`. +**Patterns to follow:** `packages/core/src/agent-token-usage.ts`. +**Test scenarios:** +- Happy: a known fixture of 5 tasks across 2 models returns correct per-model token totals. +- Edge: empty range returns zeroed structures, not nulls; a range boundary task (exactly at `from`) is included per documented inclusivity. +- Edge: a fully-autonomous session (zero human-intervention events) reports tool-calls-per-session, not ∞ or a divide-by-zero; validated against both an autonomous and an interactive fixture. +- Edge: the intervention denominator counts a user-authored steer and an approval but NOT an agent-authored steer. +- Productivity: LOC unavailable when commit diff stats are missing is reported as `null` + `unavailable: true`, not `0`. + +#### U3. Model pricing → cost derivation +**Goal:** Derive USD cost from token counts without persisting cost. +**Requirements:** R6. +**Dependencies:** U2. +**Files:** `packages/core/src/model-pricing.ts` (new), `packages/core/src/__tests__/model-pricing.test.ts`; consumed by `token-analytics.ts`. +**Approach:** A map keyed by `provider:model` → `{inputPer1M, outputPer1M, cacheReadPer1M, cacheWritePer1M, source}` plus a top-level `pricingAsOf` date. `costFor(usage, model)` returns `{usd, unavailable, stale}`. Unknown model → `unavailable: true`, never a guessed price. +**Patterns to follow:** plain data module; colocate with token-analytics. +**Test scenarios:** +- Happy: known model + token counts yields expected USD to cent precision. +- Edge: unknown model returns `unavailable: true` with `usd: null`. +- Edge: cache tokens priced at cache rate, not input rate. +- Edge: the map carries a `pricingAsOf` date and entries older than the threshold return `stale: true`. + +### Phase B — Command Center dashboard + +#### U4. Command Center shell, nav registration, and chart primitives +**Goal:** Register the `command-center` view end-to-end and build the reusable CSS-bar chart +primitives the areas render with. +**Requirements:** R1 (shell), KTD1, KTD4. +**Dependencies:** none (can start parallel to A; renders real data once A lands). +**Files:** +- `packages/dashboard/app/hooks/useViewState.ts` (add `command-center` to union + array) +- `packages/dashboard/app/App.tsx` (lazy import + prefetch + render branch, mirror `reliability` at App.tsx:1818-1826) +- `packages/dashboard/app/components/Header.tsx` (nav button mirroring reliability at :1223-1234; add `command-center` to active-check at :1095; optional `experimentalFeatures.commandCenter` gate) +- `packages/dashboard/app/components/MobileNavBar.tsx` (mobile parity) +- `packages/dashboard/app/components/command-center/CommandCenter.tsx` + `.css`, `charts/{Bar,StackedBar,Sparkline,Funnel}.tsx` + `.css`, `DateRangePicker.tsx` +- i18n strings under the `app` namespace +- `packages/dashboard/app/components/command-center/__tests__/charts.test.tsx`, `CommandCenter.test.tsx` +**Approach:** Shell renders sub-view tabs (Overview / Tokens / Tools / Activity / Productivity / Ecosystem / Mission Control). Chart primitives are hand-rolled CSS-bar components. Use `--duration-*` tokens (never `--transition-*`) for any loader/pulse animation. +**Overview tab content:** one headline stat card per area (total tokens + cost, autonomy ratio, active nodes, tasks done, unique models, open signals) plus a compact live Mission-Control strip; the date-range picker applies to the cards but not the live strip. A single "no usage data yet" empty state when nothing exists. +**Tab a11y:** sub-tabs use the ARIA tabs pattern (`role=tablist/tab/tabpanel`, arrow-key roving tabindex, Enter/Space activates, Tab moves into the active panel); the DateRangePicker returns focus to its trigger on dismiss. +**Patterns to follow:** `ReliabilityView.tsx` (skeleton, loading/error/empty), `AgentsView.tsx` (sub-view toggles), the reliability nav button. +**Execution note:** Build the chart primitives test-first — they are pure and the CSS-token trap is invisible without a real-browser assertion. +**Test scenarios:** +- Happy: selecting the Command Center nav entry renders the shell with the Overview tab active; `?view=command-center` deep-links to it. +- Edge: empty data renders the documented empty state per area, not a crash. +- CSS (real browser): `getComputedStyle(barEl).animationName !== "none"` for any animated loader (guards the IACVT token trap); extend `animation-duration-tokens.css.test.ts` for new CSS. +- Edge: chart bar with a zero value renders a 0-width bar with accessible label, not NaN width. +- A11y: a keyboard user can arrow between tabs, activate with Enter/Space, and Tab into the panel without losing focus; the date-range picker returns focus to its trigger on dismiss. + +#### U5. Historical analytics areas + date-range filtering +**Goal:** Render the measurement areas from the Phase A aggregators with a shared date-range +control, **including an External Signals area** (errors/alerts/incidents from R7 sources). +**Requirements:** R1, R2, R6, R16. +**Dependencies:** U2, U3, U4, U9; the Signals area depends on U11 data (degrades to empty until U11 lands). +**Files:** `packages/dashboard/app/components/command-center/areas/{TokensArea,ToolsArea,ActivityArea,ProductivityArea,EcosystemArea,SignalsArea}.tsx`, `DateRangePicker.tsx`; tests alongside. +**Approach:** Each area fetches its endpoint via the `api()` helper with the selected range, +renders stat cards + tables + CSS-bar charts. **Productivity framing (A5):** present LOC and +tool-count as *volume* proxies alongside outcome counters (tasks reaching done, PRs merged, +incidents resolved); do not frame high LOC/tool counts as inherently positive. +**Ecosystem area:** unique-active-model count + per-model session count as a bar chart, plugin +activation count, and a sparkline of distinct models/day; empty state when no third-party models +or plugins have been used. Reuses the tokens endpoint grouped by model where possible. +**External Signals area (R16):** signal volume by source/severity over the range, open vs +resolved, and MTTR (from U13) — wired so external signals are visible as dashboard metrics, not +only as task triggers. Until U11/U13 land, the area renders its empty state. +**SWR trap:** key any selection/drill-down reset effect on a derived value (e.g. +`rows.map(r => r.id).join(" ")`), never the array identity, or it resets every revalidation. +**Patterns to follow:** `AgentTokenStatsPanel.tsx` (token tables/totals), `ReliabilityView.tsx`. +**Test scenarios:** +- Happy: Tokens area shows per-model totals + cost; changing the range refetches and re-renders. +- Tools: autonomy ratio displayed; tool categories shown as a sorted bar chart. +- Edge (SWR): a revalidation that returns content-identical rows with new identity does **not** reset the user's column sort / selected row (regression: seed cache, defer fetch, interact, resolve with `JSON.parse(JSON.stringify(original))`, assert state survives). +- Edge: custom range with `from > to` is rejected client-side with a message. +- Productivity: unavailable LOC shows "—" with a tooltip, not `0`. +- Signals (R16): with U11 fixture data, the External Signals area shows volume by source/severity and open-vs-resolved; with no signal data it renders the empty state, not an error. + +#### U6. Live Mission-Control panel +**Goal:** Real-time view of concurrent sessions, active nodes, per-agent current activity, and +the live SDLC funnel. +**Requirements:** R3. +**Dependencies:** U4, and U9 for the endpoint. **To break the U6↔U9 cycle, U6 splits in two:** U6a = the core `command-center-live.ts` snapshot composer (no deps); U6b = the `MissionControlPanel` frontend (deps U4, U9). U9's live branch depends on U6a, not the U6 frontend. +**Files:** `packages/dashboard/app/components/command-center/MissionControlPanel.tsx`, `packages/core/src/command-center-live.ts` (live snapshot, U6a), live branch in `register-command-center-routes.ts`; tests alongside. +**Approach:** `command-center-live.ts` composes a snapshot from `agentHeartbeats`/`agentRuns`/`cli_sessions`/`tasks` (current column counts). Frontend follows **push + poll convergence (KTD5)**: subscribe to the existing SSE bus, refetch on event, poll every ~5s **only while any session is in-flight**, stop polling when idle. Server throttles emits (~500ms) and sends deltas, not full snapshots, for high-churn fields. +**Patterns to follow:** `app/sse-bus.ts`, the observable-long-running-agent-turns learning doc, `AgentsOverviewBar.tsx`. +**Test scenarios:** +- Happy: a newly-started session appears in the live panel within one poll/SSE cycle; ending it removes it. +- Edge: with zero active sessions, polling is not running (assert no interval scheduled when idle). +- Integration: SSE event triggers an immediate refetch (push) even between poll ticks. +- Edge: a node going stale (no heartbeat past threshold) is shown as inactive, not dropped silently. + +#### U7. SDLC funnel + throughput visualization +**Goal:** A funnel/Sankey-style visualization of tasks across columns with throughput (e.g. +tasks/day reaching done) and completion rate, both live and over a range. +**Requirements:** R1, R3 (KM: task completion rate). +**Dependencies:** U2, U4. +**Files:** `packages/dashboard/app/components/command-center/SdlcFunnel.tsx` + `.css`; aggregation in `activity-analytics.ts`; tests alongside. +**Approach:** Map the workflow columns (`triage→todo→in-progress→in-review→done`) to funnel +stages using `activityLog` transitions; show counts per stage and conversion between stages. +Reuse the `Funnel` chart primitive from U4. +**Patterns to follow:** the hand-rolled bar style; `activityLog` event types in `types.ts`. +**Test scenarios:** +- Happy: a fixture of tasks distributed across columns renders correct per-stage counts. +- Edge: workflow-defined custom columns (not the default enum) are mapped by trait, not by hardcoded names. +- Edge: completion rate over a range divides done-in-range by entered-in-range, documented and tested for the zero-denominator case. + +#### U8. CSV export +**Goal:** Export any analytics table as CSV. +**Requirements:** R4. +**Dependencies:** U2. +**Files:** `packages/dashboard/src/command-center-csv.ts` (new), export branch in `register-command-center-routes.ts`; export buttons in the area components; tests alongside. +**Approach:** A route variant sets `Content-Type: text/csv` + `Content-Disposition: attachment`. Server-side serialization of the same aggregator output. **Honors `getScopedStore(req)` before aggregation, exactly like U9's JSON endpoints — no cross-project leak via the export path.** No precedent exists — net-new. +**Test scenarios:** +- Happy: token endpoint with `?format=csv` returns well-formed CSV with a header row and the attachment header. +- Edge: values containing commas/quotes/newlines are RFC-4180 quoted. +- Edge: empty result returns header-only CSV, not a 204. +- Security: a project-A request cannot retrieve project-B data via CSV export (mirrors the U9 scoping test). + +#### U9. Analytics API endpoints +**Goal:** Programmatic endpoints backing the view and usable by agents. +**Requirements:** R5. +**Dependencies:** U2, U3, U6a (the `command-center-live.ts` snapshot composer — not the U6 frontend, which breaks the cycle). +**Files:** `packages/dashboard/src/routes/register-command-center-routes.ts` (new), registered in `packages/dashboard/src/routes.ts` near the other registrars (~:1991); tests in `packages/dashboard/src/__tests__/`. +**Approach:** `GET /api/command-center/{tokens,tools,activity,productivity}` (range + group-by params), +`GET /api/command-center/live` (snapshot), all thin adapters over Phase A aggregators. **Verify the +Vite proxy:** confirm `vite.config.ts`'s negative-lookahead `/api` proxy routes these to the +backend while leaving app source modules on Vite — `curl` both a real endpoint and a `?import` +source path. **Auth:** all routes inherit the dashboard's standard session/auth middleware via the +`ApiRouteRegistrar` (same as `register-usage-routes.ts`); machine/agent callers use the existing +credential model — **no analytics endpoint, including `/live`, is unauthenticated**, and every +endpoint (JSON, `/live`, and the CSV variant) applies `getScopedStore(req)` before aggregation. +**Patterns to follow:** `register-usage-routes.ts` (registrar shape), `ApiRoutesContext` in `routes/types.ts`. +**Test scenarios:** +- Happy: each endpoint returns the aggregator output with correct shape for a fixture DB. +- Edge: missing/invalid range params default to a documented window (e.g. last 7d), not a 500. +- Security: an unauthenticated request to each endpoint (including `/live`) returns 401. +- Security: project scoping — `getScopedStore(req)` is honored on the JSON and `/live` endpoints so cross-project data does not leak. +- Integration (proxy): real endpoint proxies to backend; a same-prefix `.ts?import` source path stays on Vite. + +#### U10. OpenTelemetry (OTLP) metrics export +**Goal:** Export the metrics over OTLP so teams can ship to Datadog/Grafana/etc. +**Requirements:** R4. +**Dependencies:** U2, U3. +**Files:** `packages/core/src/otel-metrics.ts` (new), wiring in the dashboard server (opt-in via config/env), changeset; tests alongside. Adds an OTel SDK dependency (changeset + sign-off). +**Approach:** Map aggregator outputs to OTLP metric instruments (counters/gauges) on a periodic +export, endpoint + headers from config. Disabled by default. **The endpoint is validated on write +(https-only in production; warn loudly on http); auth headers (Datadog/Grafana tokens) are stored +via the same secret-storage strategy as other credentials and are never logged or included in +diagnostic output.** +**Test scenarios:** +- Happy: with an OTLP collector stub, token/cost/activity metrics are exported with expected + metric names + attributes (model, node, provider). +- Edge: disabled by default — no exporter starts without explicit config. +- Security: an `http://` endpoint emits a warning; auth header values are redacted from any log output. +- Error: collector unreachable logs and backs off; it never crashes the server or blocks requests. + +### Phase C — Software-delivery-loop gap-fill + +> **Scope note:** Phase C closes the Signal/Triage/Monitor/Knowledge gaps. +> Per the user's request these are specified as buildable units, but **U11 and U14 are +> each large enough to merit their own `ce-brainstorm` before execution** — they are flagged +> inline. Sequence Phase C after Phases A–B ship. + +#### U11. External signal ingestion (Sentry / Datadog / PagerDuty / webhook) +**Goal:** Ingest signals beyond GitHub into triageable tasks via a common adapter seam. +**Requirements:** R7, KTD8. +**Dependencies:** none (independent of the Command Center); benefits from U13. +**Files:** `packages/dashboard/src/signal-source.ts` (adapter interface + registry — mirrors the GitHub path, lives in dashboard), `packages/dashboard/src/signal-sources/{sentry,datadog,pagerduty,webhook}.ts`, `packages/dashboard/src/routes/register-signal-routes.ts` (inbound webhooks), config/settings entries; tests alongside. +**Approach:** A `SignalSource` interface (`verify(req)`, `normalize(payload) → Signal`) mirroring +the GitHub source path. The normalized `Signal` includes a **`groupingKey`** populated from the +provider's native primitive (Sentry `issue.id`, PagerDuty `incident.id`, …) for U13's storm guard; +the generic webhook requires the caller to supply one or falls back to `source + normalized-title`. +Inbound webhooks land normalized `Signal`s that create tasks in a `signal`/`triage` column. Each +provider is a thin adapter. +**Security (mandatory, not deferred to the brainstorm):** every adapter's `verify(req)` performs +HMAC signature verification against a per-provider secret stored in encrypted settings/env (never +source-controlled); a missing or invalid secret rejects with 401 — **the generic webhook is never +an unauthenticated task-creation endpoint.** Add a replay window (reject timestamps outside ±5 min) +plus delivery-id nonce dedup; treat any URLs in payloads as SSRF-untrusted. Enforce a request body +size cap (~1 MB), per-source rate limiting, and field-length caps on normalized `Signal` fields; +`meta` JSON from external sources is stored as data and never rendered as raw HTML in the dashboard. +**Patterns to follow:** `github-source-issue-close.ts`, `github-webhooks.ts`, `github-poll.ts`. +**Execution note:** Characterize the existing GitHub ingestion path first, then factor the +shared seam — do not break GitHub ingestion while generalizing it. +**Flag:** Candidate for its own brainstorm (provider auth models, dedup, rate limits differ per provider). **Defer the `SignalSource` registry/interface extraction until a second provider exists** — for the first delivery, implement one provider (generic webhook) as a standalone module mirroring `github-webhooks.ts`, then extract the shared interface once the brainstorm settles the auth/dedup/rate-limit shape and two providers coexist. +**Test scenarios:** +- Happy: a valid Sentry webhook creates one triage task with normalized title/severity/link. +- Security: an unsigned/invalid-signature webhook (including the generic webhook with no secret) is rejected with 401 and creates no task. +- Security: a replayed valid payload (timestamp outside the window or duplicate delivery-id nonce) is rejected. +- Edge: duplicate delivery (same external id) is deduped, not double-created (mirror `github-tracking-dedup.ts`). +- Edge: an oversized payload (>1 MB) is rejected; per-source rate limit caps a flood. +- Error: a malformed payload returns 4xx and creates no task. + +#### U12. Triage stage — auto-classify + decompose, for issues *and* pull requests +**Goal:** Auto-classify incoming signals/issues **and inbound pull requests** and decompose or +route them into board tasks. +**Requirements:** R8, R14, KTD7. +**Dependencies:** U11; reuses existing breakdown + GitHub PR ingestion. +**Files:** a `triage` trait/handler via the workflow-extension system, `packages/dashboard/src/subtask-breakdown.ts` reuse, PR-source wiring near `github-poll.ts`/`github-webhooks.ts`; tests alongside. +**Approach:** A triage column trait runs a classify+decompose pass (priority/area/labels), using +the existing subtask-breakdown machinery, then routes to `todo`. Express as a `Trait` with an +`onEnter` hook (see `CONCEPTS.md` Trait), not a hardcoded branch. **PRs:** inbound PRs (external +contributors, dependabot) are classified and either labeled/routed for review or used to open a +follow-up task; PR triage reuses the `pull_requests` / PR-entity model rather than minting issues. +**Patterns to follow:** `subtask-breakdown.ts`, `mission-interview.ts`, `github-webhooks.ts`, the Trait/Workflow-Extension model. +**Test scenarios:** +- Happy (issue): a signal-created task entering `triage` is classified and decomposed into N todo tasks linked back to the signal. +- Happy (PR): an inbound PR is classified (e.g. dependency-bump vs feature) and routed to review or a follow-up task, linked to its PR entity. +- Edge: a signal too small to decompose passes through as a single task, not zero. +- Edge: a PR Fusion itself opened is **not** re-triaged as inbound (no self-loop). +- Error: classifier failure parks the item in triage with a diagnostic, does not drop it. + +#### U13. Monitor stage (deployments, incidents, MTTR) — closes the loop +**Goal:** Track deployments and production incidents, compute MTTR, and feed Monitor signals +back to Signal/Triage. +**Requirements:** R9, KTD7. +**Dependencies:** U11 (signals), U2 (so MTTR surfaces in the Command Center). +**Files:** `packages/dashboard/src/monitor-routes.ts`, a `deployments`/`incidents` table (db.ts + migration), a `monitor` column trait, MTTR aggregation in `activity-analytics.ts`, Command Center surfacing; tests alongside. +**Approach:** Record deploys (from CI/Ship events) and incidents (from U11 signals). MTTR = +incident-open → incident-resolved. A `monitor` trait watches post-ship and can auto-open a +fix task on a regression signal, closing the loop back to Triage. **Storm/dedup guard (required — +production signals are bursty):** grouping requires a **`groupingKey`** that each U11 adapter's +`normalize()` populates from its provider's native primitive (Sentry `issue.id`/`event.fingerprint`, +PagerDuty `incident.id`, Datadog monitor/aggregation key) — there is no Fusion error-fingerprint +concept, and the content-hash `computeContentFingerprint` (task title/description) is wrong for +bursty alerts. The **generic webhook has no native key**: require the caller to supply one, else +fall back to `source + normalized-title` with a documented coarser cooldown. With the key: a +threshold/sustained-duration gate precedes task creation; a cooldown attaches re-firing signals to +the existing fix task (reuse `findLatestByDedupeKey`); a circuit-breaker caps auto-created tasks per +window; a Fusion-opened fix task never re-triggers (no self-loop, mirroring U12). **Deploy/incident +ingestion auth:** the CI→`monitor-routes` endpoint requires a shared secret / bearer token (stored +in encrypted settings, never unauthenticated, 401 on missing/invalid), and payload URLs are +SSRF-untrusted — mirroring U11. The MTTR aggregator lives in `activity-analytics.ts` +(`packages/core`); deployment/incident recording and `monitor-routes.ts` live in +`packages/dashboard/src` (the aggregator is the core seam the route consumes). +**Patterns to follow:** `reliability-metrics.ts` (metric aggregation + endpoint), KTD7 traits. +**Test scenarios:** +- Happy: an incident opened then resolved yields a correct MTTR in the Monitor metrics. +- Integration: a post-ship error signal auto-creates a single linked fix task in triage (loop closure). +- Storm: a 100-event burst sharing one `groupingKey` yields exactly one fix task; a flapping alert yields no new task; an already-open fix task absorbs repeat signals. +- Edge: the generic webhook with no supplied grouping key falls back deterministically (source + normalized-title), not per-event. +- Security: an unauthenticated deploy/incident POST to `monitor-routes` returns 401 and records nothing. +- Edge: an unresolved incident contributes to "open incidents," not to MTTR. +- Edge: deploy with no following incident counts toward deploy frequency / change-fail rate denominator. + +#### U14. Persistent knowledge index +**Goal:** A persistent, incrementally-refreshed knowledge layer downstream agents can query. +**Framing:** this is a *delta* over the existing `insights`/`memoryView` surfaces, which already +provide part of this — characterize what they lack before building. If the delta is small, extend +those surfaces rather than introducing a greenfield store; the new-table spec below applies only if +the brainstorm concludes a separate store is warranted. +**Requirements:** R10. +**Dependencies:** none; integrates with existing `insights`/`memoryView`. +**Files:** `packages/dashboard/src/knowledge-index.ts` (mirrors `insights-routes.ts` — lives in dashboard), a knowledge store table (`db.ts` + migration), refresh hook on task completion, a dashboard surface reusing the `memoryView`/`InsightsView` patterns; tests alongside. +**Approach:** Index repo + task/PR history into queryable knowledge pages, refreshed +incrementally on task completion (not full re-index). Expose a query API agents can call — +**under the same session/auth middleware and `getScopedStore(req)` scoping as U9** (the index +holds sensitive repo/commit/PR content, so it is an information-disclosure surface, not an open +endpoint). +**Patterns to follow:** `InsightsView.tsx` + `insights-routes.ts`, the `memoryView` experimental flag. +**Flag:** Candidate for its own brainstorm (indexing strategy, storage/embedding choice, refresh cost). +**Test scenarios:** +- Happy: completing a task adds/updates a knowledge page; a keyword query returns it. +- Edge: incremental refresh updates only affected pages, not the whole index (assert unaffected pages' timestamps unchanged). +- Integration: an agent query endpoint returns relevant pages for a known fixture. +- Security: an unauthenticated query returns 401; a project-A caller cannot retrieve project-B pages (mirrors U9 scoping). + +#### U17. Fusion Model Router +**Goal:** Automatic per-task / per-request model selection across providers, optimizing +cost/latency while preserving frontier quality on hard work. +**Requirements:** R13, KTD9. +**Dependencies:** U1 (telemetry to measure savings), U3 (pricing); independent of the view UI. +**Files:** `packages/core/src/model-router.ts` (routing policy + rule evaluation), wiring into +the effective-agent / model-pair resolution path, a router config/settings surface, a Command +Center readout of router decisions + realized savings; tests alongside. +**Approach:** Per KTD9 — a selection layer, not an executor. **Session-level routing only for this +unit:** pick the `(provider, model)` pair at session start. *Per-request mid-session re-routing is +deferred* (it needs its own design pass on streaming continuity, context-window compatibility, and +prompt-cache invalidation — see Deferred). **Routing signal (load-bearing, must be settled before +build):** no structured `complexity`/`difficulty` field exists on tasks or steps today, and prompt +size alone is a weak proxy (short-but-hard vs long-but-boilerplate). The classifier signal must be +defined and validated against real Fusion task data, and paired with a **quality guardrail** +(escalation/retry to the strong tier on cheap-tier failure) and a **quality-regression metric** — +not only the cost-savings readout — so the router cannot report savings while silently degrading +output. **The gate is exitable two ways:** the unit does not ship until the brainstorm produces a +validated signal, OR it ships a deliberately-conservative v0 that routes only an allowlist of +mechanical traits (dependabot bumps, lint-only fixes) to the cheap tier and everything else to the +default pair. It must NOT be read as "build the full classifier now" with prompt-size as the de +facto signal. **Resolution lanes:** enumerate which lanes the router governs (execution, planning, +validation, …; `model-resolution.ts` exposes a distinct resolver per lane) and test each — it must +neither leak into ungoverned lanes nor return a forbidden pair in any governed lane. Respects +column-agent overrides and org/project/user model controls (cannot pick a restricted model). Safe +fallback to the configured default pair when disabled or a pick is unavailable. Emits its decisions +(including the counterfactual model that *would* have run) to U1 so the Command Center can show +adoption and realized cost delta versus always-premium. +**Patterns to follow:** the Effective-agent / Workflow-Setting model-lane resolution and +`model-resolution.ts` lanes; `runtime-provider-probes.ts` for provider availability. +**Execution note:** Implement the resolution-seam integration test-first — routing must never +hand back a pair the model controls forbid. +**Flag:** Candidate for its own brainstorm — the routing signal and quality guardrail are +load-bearing and unproven, making this at least as design-heavy as U11/U14. It is also the most +strategy-aligned new capability after the Command Center (it directly expresses the model-agnostic +thesis and feeds `ecosystem breadth`), so it should not be deferred or rejected alongside the +competitor-parity units — elevate it on its own merits. +**Test scenarios:** +- Happy: a routine step routes to the cheap tier; a deep-reasoning task routes to the strong tier. +- Edge: a column-agent `override` binding wins over the router (router defers). +- Security/governance: a model restricted by project policy is never selected, even if it scores best. +- Edge: router disabled → resolution is byte-identical to today's default-pair behavior (no regression). +- Integration: router decisions appear in `usage_events` and the Command Center shows realized cost savings vs premium-only. + +#### U18. PR review-comment auto-resolution (surface + harden the Review-response loop) +**Goal:** Make automatic resolution of PR review comments a first-class, surfaced capability +built on the existing Review-response loop. +**Requirements:** R15, KTD10. +**Dependencies:** none (extends existing PR-entity + review-response machinery); benefits from U6b. +**Files:** wiring/config around the existing Review-response loop — the real entry point is +`packages/engine/src/pr-response-run.ts` (plus the `ce-resolve-pr-feedback` skill), **not** a +`pr-comment-resolver` module by that name — and the PR entity (`CONCEPTS.md` PR entity, +Review-response loop); Command Center / Mission-Control surfacing of in-flight resolutions; tests +alongside. Note the `packages/engine` home of the loop. +**Approach:** Per KTD10 — do not rebuild the loop. Ensure it triggers on PR-entity review +threads (human + bot), is gated consistently with the auto-merge model, and exposes its +activity (threads acted on, fixed vs disagreed) to the Command Center. Make default-on behavior +explicit and configurable. +**Patterns to follow:** the existing `ce-resolve-pr-feedback` skill seam, `pr-conflict-resolver.ts`, the Review-response loop description in `CONCEPTS.md`. +**Test scenarios:** +- Happy: a new review thread dispatches a resolver that fixes, pushes to the PR branch, and replies to the thread. +- Edge: the resolver disagrees → posts reasoning and leaves the thread open (no silent push). +- Edge: auto-resolution respects the auto-merge gate (disabled → resolves but does not merge). +- Integration: in-flight resolutions appear in Mission Control and counts roll into Command Center metrics. + +--- + +## Scope Boundaries + +**In scope:** The Command Center (combined historical analytics + live Mission Control, +including an External Signals metric area), its metrics foundation, export (CSV + OTel), the +Analytics API, and buildable units for every SDLC gap (Signal ingestion, Triage of issues +**and PRs**, Monitor, Knowledge, the **Fusion Model Router**, +and **auto-resolution of PR review comments**). + +### Deferred to Follow-Up Work +- **Per-unit brainstorms for U11 and U14** before execution — each has substantial design + surface (provider auth/dedup; indexing/embedding strategy) that this + plan scopes but does not fully resolve. +- **Per-request mid-session model re-routing (U17)** — this unit ships session-level routing only; + per-request re-routing needs its own design pass on streaming continuity, context-window + compatibility, and prompt-cache invalidation. +- **Recharts (or any chart-lib) adoption** — only if KTD4's hand-rolled approach proves + impractical for a needed chart type; would be a separate changeset + sign-off. +- **Human "Users" analytics** — Fusion's notion of a human user is thin (`assigneeUserId`); + the Users/per-person area is modeled here as **per-agent**. A per-human breakdown waits until + multi-user (the `Pluggable multi-user` track in `STRATEGY.md`) lands. + +### Out of scope +- Replacing or forking the workflow runtime — Phase C attaches to it via traits/extensions. +- A URL router for the dashboard — the `?view=` + `localStorage` model is preserved. + +--- + +## Risks & Dependencies + +- **SCHEMA_VERSION migration trap (high).** U1/U13/U14 add tables. `applyMigration`, + `SCHEMA_VERSION` (currently 117), `MIGRATION_ONLY_TABLE_SCHEMAS`, and `SCHEMA_COMPAT_FINGERPRINT` + all live in `packages/core/src/db.ts`, **not** `db-migrate.ts` (the legacy-data path). Every + `applyMigration(N)` **must** bump `SCHEMA_VERSION` to N in the same change, or the migrate loop early-returns and + the migration silently never runs on already-upgraded DBs (fresh DBs mask it). Also update + `MIGRATION_ONLY_TABLE_SCHEMAS`/`SCHEMA_COMPAT_FINGERPRINT`, add a **seed-at-previous-version** + migration test, and run the version-literal sweep across **plugin** workspaces too, not just + `packages/`. (`docs/solutions/database-issues/schema-version-constant-must-equal-highest-migration.md`.) +- **Vite `/api` proxy regex (medium).** New endpoints must be verified against the + negative-lookahead proxy in `vite.config.ts` so app source modules aren't proxied; `curl` + both a real endpoint and a `?import` source path. (`docs/solutions/integration-issues/vite-api-source-modules-proxied-to-backend.md`.) +- **CSS IACVT token trap (high).** Chart/loader animations must use `--duration-*` tokens, not + `--transition-*` (which are duration+easing pairs); misuse silently drops the whole + declaration. Extend `animation-duration-tokens.css.test.ts`; verify in a real browser. + (`docs/solutions/ui-bugs/css-animation-frozen-by-transition-token-shape-mismatch.md`.) +- **SWR identity-reset trap (medium).** View state keyed on revalidated array identity resets + every poll tick; key on derived semantic values. (`docs/solutions/ui-bugs/skill-autocomplete-highlight-reset-on-swr-revalidation.md`.) +- **Browser verification hazard (process).** Verify with `fn dashboard --dev` on a **free, + non-4040** port with `FUSION_CLIENT_DIR=$PWD/packages/dashboard/dist/client` after a fresh + build; never `fn daemon`/`fn serve` (engine + shared DB). If a chart renders empty, check the + served bundle hash first. (`docs/solutions/developer-experience/browser-testing-dashboard-from-worktree-safely.md`; aligns with the port-4040 kill-guard.) +- **Phase C breadth.** Three units are brainstorm-candidates; do not let Phase C block the + Command Center shipping from Phases A–B. + +--- + +## Sources & Research + +- **External analytics product** (six measurement areas — tokens, tools, activity, productivity, + users, agent readiness — plus CSV/OTel/API): factory.ai/news/factory-analytics. +- **External model router** (per-task/per-request auto model selection, ~20–25% cost reduction, + respects org/project/user model controls): docs.factory.ai/web/factory-router → grounds R13/U17. +- **External end-to-end delivery loop + mission-control framing**: factory.ai homepage + (Signal→Triage→Plan→Execute→Validate→Ship→Monitor) and that product's release notes + (mission control, sessions, knowledge wiki, computer use, missions, subagents). +- The X thread that prompted this work (x.com/factoryai/status/2066588050617249904) was + paywalled (HTTP 402); its subject was reconstructed from the public pages above. +- **Fusion grounding**: `STRATEGY.md` (key metrics), `CONCEPTS.md` (Column/Trait/Workflow + Extension, Effective agent, Task lifecycle), and repo research into `packages/dashboard` + + `packages/core` (data model, view registration, `ApiRouteRegistrar`, existing + `ReliabilityView`/`AgentTokenStatsPanel`/`agent-token-usage.ts`). +- **Institutional learnings**: the six `docs/solutions/` entries cited in Risks. + +--- + +## Deferred / Open Questions + +### From 2026-06-15 review + +These are genuine forks the review surfaced that depend on your priorities — left open rather than +decided here. (The factual/feasibility/security findings from the same review were applied inline.) + +- **Plan scope — ship A–B alone, or bundle Phase C?** Three reviewers flagged that Phases A–B + (the Command Center) have a clean, strategy-aligned premise, while Phase C's stages were derived + from a competitor's feature set rather than observed Fusion user pain, and bundling them means + approving the dashboard implicitly blesses the broader SDLC-platform direction. Options: (a) ship + A–B as the plan of record and split Phase C into its own strategy-grounded brainstorm; (b) keep + one plan but state explicitly that approving it is not approving Phase C's direction; (c) proceed + as one plan (current state, per your "build all gaps" instruction). *No change made — your call.* +- **Positioning: neutral orchestrator vs opinionated delivery system.** An opinionated + Signal→…→Monitor pipeline (Monitor stage, MTTR, role/lifecycle features) pulls against + `STRATEGY.md`'s "neutral by design, plugin ecosystem" thesis. Should the Monitor/Signal/Knowledge + stages be core product surface or live in the plugin ecosystem the strategy names as its + extension mechanism? +- **`usage_events` table vs lazy-materialization (KTD3 / U1).** The plan now notes both; the + architecture choice (always-on events table + multi-path instrumentation, vs a cache table + materialized on first query) is unresolved. R1/R2/R5 state no sub-second requirement, which keeps + lazy-materialization on the table. +- **`usage_events` field-carrying mechanism (U1).** `appendAgentLog`/`appendRunLog` have DB handles + but their signatures (and `AgentLogEntry`) carry none of `model/provider/nodeId/category`. The + plan proposes a dedicated `emitUsageEvent` call from the session layer; the alternatives are + widening the log signatures (~20+ call sites) or a per-write DB lookup. **Resolved (2026-06-15): + dedicated `emitUsageEvent(...)` call from the executor/session layer — do not widen the log method + signatures.** +- **OTel export (U10) timing.** CSV (U8) already satisfies R4 for the Command Center's developer + audience; OTLP targets an ops team running a collector. Build now, or defer U10 until a concrete + consumer exists (avoids adding the OTel SDK dependency for a default-disabled feature)? +- **Mission Control placement (D2).** Dedicated tab only (polling stops when inactive), a + persistent live strip across all tabs (SSE always subscribed), or embedded in Overview? Changes + the polling architecture U6 implements. +- **Date-range picker affordance (D3).** Preset labels/windows, calendar vs free-text custom range, + explicit Apply vs update-on-select, and the in-flight refetch state per area. +- **Mobile layout of dense charts (D4).** How charts/tabs reflow on narrow/landscape phones + (collapse to sparklines, hide behind a toggle, horizontal-scroll tab strip) — the mobile + breakpoint includes landscape (`max-height: 480px`). +- **AgentTokenStatsPanel consolidation (D5).** Deprecate it once the Tokens tab ships, keep it as a + linked inline summary, or keep it standalone with explicitly different scope (lifetime vs + windowed) — and document which data source each uses so the numbers don't silently diverge.