Skip to content

feat: devflow stats — developer productivity dashboard #243

Description

@dean0x

Plan: devflow stats — Developer Productivity Dashboard

⚠️ Revised 2026-06-15 — This plan was re-validated against the current codebase after a /explore feasibility pass. The original draft was written against an older snapshot and had drifted significantly: it assumed .memory/ paths (the repo committed to .devflow/), a learning pipeline (removed in ADR-015), and a router skill instrumentation point (never existed). The body below is the corrected, buildable plan. See the pinned comment for the full staleness delta.

Context

Devflow accumulates data across sessions (decisions/pitfalls, knowledge bases, turns, git history) but has no unified view. Inspired by analytics-style commands, this adds a devflow stats CLI rendering a terminal dashboard with sparkline trends, before/after comparisons, and "intelligence growth" metrics. Goal: visually show how AI-assisted development accelerates over time.

Two parts:

  1. Analytics tracking module — explicit event tracking for workflow usage (new)
  2. Stats dashboard — terminal rendering of collected metrics (new)

Build order / phasing (driven by data availability)

Panels split cleanly into "buildable today" vs "needs new infrastructure". Recommend shipping v1 first:

Phase Panels Why
v1 — ready now Velocity, Before/After, Intelligence (decisions/knowledge snapshot), Features All data sources exist today; pure path/refactor work + new renderers
v2 — needs new infra Workflows Requires the new analytics tracking module (Part 1) + preamble instrumentation
v2 — needs new infra Sessions Requires a new persistent sessions ledger.pending-turns.jsonl is an ephemeral queue, not a history (see below)
v2 — needs new infra Intelligence growth over time No time-series is recorded today; only current snapshots. Must reconstruct from git history or start recording

Part 1: Analytics Tracking Module

Explicit event tracking via our own CLI. No signal extraction — only deliberate tracking calls.

File: src/cli/utils/analytics.ts

interface AnalyticsEvent {
  type: 'intent' | 'cli';
  name: string;       // e.g., 'implement', 'explore', 'stats'
  ts: string;         // ISO timestamp (written by trackEvent)
  project?: string;   // git root basename
  metadata?: Record<string, string | number>;
}

Two functions:

  • trackEvent(projectRoot, event) — append one JSONL line to the analytics file (atomic append, same pattern as fs-atomic.ts / pending-turns)
  • readEvents(projectRoot, since?) — read + parse + optional time filter

File: src/cli/commands/track.ts — thin CLI wrapper:

devflow track intent implement   # log intent dispatch
devflow track cli stats          # log CLI command usage

Analytics file location

{projectRoot}/.devflow/memory/.analytics.jsonl — co-located with other per-project JSONL. Add a path helper getAnalyticsPath(projectRoot) to src/cli/utils/project-paths.ts (next to getPendingTurnsPath, line ~157). ⚠️ project-paths.ts has a CJS mirror at scripts/hooks/lib/project-paths.cjs that must be kept in sync — but the preamble hook (bash) will not compute the path itself; it delegates to the track CLI via --cwd, so only the .ts helper is strictly needed.

Instrumentation point — preamble hook (NOT a router skill)

The original plan said to instrument shared/skills/router/SKILL.md. There is no router skill. Workflow dispatch happens in the ambient preamble hook: scripts/hooks/preamble.

The preamble hook (UserPromptSubmit) does first-word keyword detection (scripts/hooks/preamble:54-61) and recognizes exactly: implement, explore, research, debug, plan (+ a structured-plan path → implement). It currently only emits a directive via json_prompt_output (line 70); it makes no external calls.

Inject the tracking call just before the directive emission (around line 68-70):

if [[ -n "$SKILL" && -n "$REST" ]]; then
  dbg "WORKFLOW_KEYWORD detected: $SKILL — injecting directive"

  # Fire-and-forget intent tracking. Must NOT block prompt submission.
  DEVFLOW_BIN="$(command -v devflow 2>/dev/null || true)"
  if [ -n "$DEVFLOW_BIN" ]; then
    ( "$DEVFLOW_BIN" track intent "$SKILL" --cwd "$CWD" >/dev/null 2>&1 & ) || true
  fi

  json_prompt_output "..."

Two reliability caveats (must be documented in the dashboard, not hidden):

  1. Detection ≠ execution. The hook tracks that an intent was detected, not that the skill actually ran. The hook emits a directive the model may not comply with. Counts are "intents dispatched," not "workflows completed."
  2. Only 5 ambient keywords are captured. Command-style workflows — code-review, resolve, release, bug-analysis, self-review — are invoked as slash-commands/skills, not as preamble first-word keywords, so the hook never sees them. The Workflows panel can only honestly show the 5 ambient intents (+ structured-plan→implement) unless those commands are separately instrumented. (Optionally, each devflow subcommand can call trackEvent on invocation for type: 'cli' metrics — but the workflow commands are not devflow subcommands.)

Hook → CLI precedent: background-memory-update already shells out to claude -p, and decisions-usage-scan.cjs writes lockable JSON — so a hook→CLI call is established. The fire-and-forget wrapper avoids adding node-startup latency (~100-300ms) to every matching prompt.


Part 2: Stats Dashboard

CLI Interface

devflow stats                   # default: 90 days
devflow stats --period 30d      # 7d, 30d, 90d, all
devflow stats --json            # machine-readable JSON
devflow stats --compact         # minimal summary without charts

Dashboard Layout (box-border + sparklines)

╭─── devflow stats ──────────────────────────────── period: 90 days ───╮
│                                                                       │
│  ┌── VELOCITY ─────────────────┐  ┌── INTELLIGENCE ────────────────┐ │
│  │  Commits         83 (+127%) │  │  Decisions (ADR)        19     │ │
│  │  PRs merged      12 (+220%) │  │  Pitfalls (PF)          10     │ │
│  │  Lines changed  2.4k        │  │  Knowledge bases         3     │ │
│  │                              │  │  Citations               0     │ │
│  │  Commits/week               │  │                                │ │
│  │  ▁▂▃▃▅▆▅▇▇█▉█▊█            │  │  (growth-over-time = v2)       │ │
│  └──────────────────────────────┘  └────────────────────────────────┘│
│                                                                       │
│  ┌── BEFORE → AFTER DEVFLOW ──────────────────────────────────────┐  │
│  │  Install: 2026-03-27                                            │  │
│  │  Commits/wk    3.2  ███░░░░░  →   8.1  ████████  (+153%)       │  │
│  │  PRs/wk        0.6  █░░░░░░░  →   2.4  ████░░░░  (+300%)       │  │
│  └──────────────────────────────────────────────────────────────────┘│
│                                                                       │
│  ┌── COMMIT VELOCITY (weekly) ── 8-row ASCII line chart ──────────┐  │
│  └──────────────────────────────────────────────────────────────────┘│
│                                                                       │
│  ┌── WORKFLOWS  (v2 — needs analytics module) ────────────────────┐  │
│  │  implement ████████ 18   explore ███ 3   plan ████ 8 …         │  │
│  │  (5 ambient intents only; see reliability caveat)              │  │
│  └──────────────────────────────────────────────────────────────────┘│
│                                                                       │
│  ┌── SESSIONS  (v2 — needs persistent sessions ledger) ───────────┐  │
│  └──────────────────────────────────────────────────────────────────┘│
│                                                                       │
│  ┌── FEATURES ────────────────────────────────────────────────────┐  │
│  │  ● ambient ● memory ● hud ● knowledge ● decisions ● rules      │  │
│  │  Flags: tui, tool-search, lsp, prompt-caching-1h, …            │  │
│  │  v2.0.0 · Installed 2026-03-27 · Updated 2026-06-12            │  │
│  └──────────────────────────────────────────────────────────────────┘│
╰───────────────────────────────────────────────────────────────────────╯

Note vs original mockup: removed the learning-observation breakdown (workflow/procedural/promoted — learning pipeline gone) and the learn/teams feature dots (learn removed; teams is now the agent-teams flag).

File Structure

src/cli/
├── commands/
│   ├── stats/
│   │   ├── index.ts              # Commander command, orchestration
│   │   ├── types.ts              # DashboardData + metric types
│   │   ├── collectors/
│   │   │   ├── git.ts            # commits, PRs, lines, weekly buckets
│   │   │   ├── intelligence.ts   # decisions ledger + knowledge + citations (NO learning)
│   │   │   ├── workflows.ts      # analytics JSONL → intent counts (v2)
│   │   │   ├── sessions.ts       # sessions ledger → session/turn counts (v2, new ledger)
│   │   │   └── features.ts       # manifest toggles, flags, plugins, dates
│   │   ├── renderers/
│   │   │   ├── sparkline.ts      # ▁▂▃▅▇█ — BUILD NEW (no existing impl)
│   │   │   ├── chart.ts          # 8-row ASCII line chart — BUILD NEW
│   │   │   ├── bar.ts            # horizontal bars — REUSE renderBar() from hud/components/usage-quota.ts
│   │   │   ├── box.ts            # box-drawing layout engine — BUILD NEW
│   │   │   └── dashboard.ts      # panel composition → full string
│   │   └── format-json.ts        # --json output
│   └── track.ts                  # `devflow track` command (Part 1)
└── utils/
    └── analytics.ts              # trackEvent + readEvents (Part 1)

Modify: src/cli/cli.ts (register track + stats via program.addCommand(...), same pattern as decisions.ts / knowledge/index.ts); src/cli/utils/project-paths.ts (+getAnalyticsPath); scripts/hooks/preamble (tracking call).

Data Sources (CORRECTED to .devflow/ layout)

Section Source (actual path / helper) How collected Status
Velocity git log execFile w/ timeout; commits, PR refs via (#\d+), --numstat for lines ✅ ready
Intelligence .devflow/decisions/decisions-ledger.jsonl (getDecisionsLedgerPath), .devflow/features/index.json (getFeaturesIndexPath), .devflow/decisions/.decisions-usage.json (getDecisionsUsagePath) Count ledger rows where decisions_status !== "Retired", split ADR/PF by type; count features keys; sum cites ✅ ready (snapshot)
Before/After git log + ~/.devflow/manifest.json installedAt (readManifest) Split git history at install date, per-week averages ✅ ready
Workflows .devflow/memory/.analytics.jsonl (getAnalyticsPath) readEvents(), count by name 🟡 v2 (needs Part 1)
Sessions NEW persistent sessions ledger (e.g. .devflow/memory/sessions-ledger.jsonl) Append a session summary record from background-memory-update after it drains the queue 🔴 v2 (new infra)
Features ~/.devflow/manifest.json (readManifest) features.{ambient,memory,hud,knowledge,decisions,rules} toggles, features.flags[], plugins[], version, dates ✅ ready

Decisions counting: read decisions-ledger.jsonl and filter live rows (decisions_status !== "Retired"), splitting on type ("decision"→ADR, "pitfall"→PF). Do not parse decisions.md/pitfalls.md headings — the ledger is the render source of truth (render-decisions.cjs derives the markdown from it).

Sessions panel (why it's v2/new-infra): .devflow/memory/.pending-turns.jsonl is an ephemeral queue, not a history. background-memory-update atomically renames it to .processing and deletes it after each refresh (~120s cadence). It cannot back a "47 sessions · 281 turns" historical view. A new durable ledger must be written by the worker post-drain.

Intelligence growth-over-time (why it's v2): only current snapshots exist; no daily/weekly counts are recorded anywhere. Reconstruct from git history of decisions-ledger.jsonl/features/index.json, or start recording counts going forward. Velocity time-series is fine (git timestamps).

Reusable Utilities (CORRECTED)

What Where Notes
readManifest() src/cli/utils/manifest.ts:30 (devflowDir) => Promise<ManifestData | null>
getGitRoot() src/cli/utils/git.ts:16 ✅ resolve git root
getDevFlowDirectory() src/cli/utils/paths.ts:70 ✅ resolve ~/.devflow (user scope)
project .devflow/ path helpers src/cli/utils/project-paths.ts getDecisionsLedgerPath, getFeaturesIndexPath, getDecisionsUsagePath, getMemoryDir, … (this is the canonical project-path resolver — it was not deleted)
stripAnsi() + 14 color fns src/cli/hud/colors.ts:64 ✅ ANSI-aware width + styling
shellExec (execFile + timeout) src/cli/hud/git.ts:6 ✅ pattern to copy for git collection
renderBar() (8-char █/░, 3-tier color) src/cli/hud/components/usage-quota.ts:6-28 directly reusable for before/after + workflows bars
parseLearningLog() / getLearningCounts() learning ❌ learning pipeline removed (ADR-015); learning-counts.ts deleted from source. observations.ts/observation-io.ts still exist but only because decisions-log.jsonl reuses the format — not needed for the ledger-based Intelligence collector

Must build new (no existing implementation in src/cli/hud/): sparkline.ts, chart.ts (8-row line chart), box.ts (box-drawing layout). These are pure functions — low risk, well-suited to unit tests.

Renderer details

  • sparkline.tssparkline(values: number[]): string maps to ▁▂▃▄▅▆▇█ via min/max normalization (~15 lines).
  • chart.tslineChart(data, labels, width, installIdx?): string[] — 8-row ASCII with ╭╮╯╰─│, y-axis labels, optional install marker (~60 lines).
  • bar.ts — reuse/extract renderBar() from usage-quota.ts; add a comparisonBar(before, after, maxWidth) wrapper with % suffix.
  • box.tsbox(title, lines, width), sideBySide(left, right, width), outerFrame(sections, width). Width: process.stdout.columns || 80, min 72. Use stripAnsi() for ANSI-aware width.
  • dashboard.tsrenderDashboard(data, width) + renderCompact(data, width); null/empty panels render placeholder text.

Empty-state handling

  • No git history → velocity: "No git history available"
  • No ledger → intelligence: "No decisions recorded yet"
  • No installedAt → before/after panel omitted
  • No analytics file → workflows: "No workflow data yet"
  • No sessions ledger → sessions: "No session data yet"

JSON output

--json serializes the DashboardData object directly.

Implementation Sequence

v1 (ready now):

  1. Stats typesstats/types.ts
  2. Renderers (pure, testable): sparkline.ts → reuse/extract bar.tsbox.tschart.tsdashboard.ts
  3. Collectors: git.tsintelligence.ts (ledger + features + usage) → features.ts (manifest)
  4. Stats commandstats/index.ts + format-json.ts + register in cli.ts
  5. Build + testnpm run build, manual run on this repo

v2 (new infra):
6. Analytics moduleutils/analytics.ts + getAnalyticsPath in project-paths.ts (+ CJS mirror)
7. Track commandcommands/track.ts + register in cli.ts
8. Preamble instrumentation — fire-and-forget devflow track intent (scripts/hooks/preamble ~L68)
9. Workflows collectorworkflows.ts reads analytics JSONL
10. Sessions ledger — append session summaries from background-memory-update post-drain; sessions.ts collector
11. Intelligence growth — record/reconstruct time-series

Verification

  1. npm run build — compiles clean
  2. devflow stats — renders v1 panels (velocity, intelligence, before/after, features) with real repo data
  3. devflow stats --period 7d / --period all — sparklines/buckets adjust
  4. devflow stats --json — valid JSON
  5. devflow stats --compact — omits charts
  6. 80-col terminal (min-width handling); no-.devflow/ project (empty states)
  7. (v2) devflow track intent implement --cwd $PWD → writes .devflow/memory/.analytics.jsonl
  8. (v2) trigger an ambient workflow (implement …) → confirm preamble fires the tracking call without adding perceptible prompt latency
  9. Confirm Workflows panel only claims the 5 ambient intents (honest caveat rendered)

Revision history

  • 2026-06-15 — Full re-validation pass (/explore). Corrected: all paths .memory/.devflow/ (via project-paths.ts helpers); router-skill instrumentation → preamble hook (with detection≠execution + 5-keyword caveats); Intelligence panel de-coupled from removed learning pipeline (now decisions-ledger + knowledge + citations); Features panel updated to real toggles/flags; Sessions + intelligence-growth flagged as v2 new-infrastructure; decisions counting moved from markdown-heading parsing to ledger rows; confirmed renderBar() reuse and that sparkline/chart/box must be built new.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions