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:
- Analytics tracking module — explicit event tracking for workflow usage (new)
- 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):
- 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."
- 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.ts —
sparkline(values: number[]): string maps to ▁▂▃▄▅▆▇█ via min/max normalization (~15 lines).
- chart.ts —
lineChart(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.ts —
box(title, lines, width), sideBySide(left, right, width), outerFrame(sections, width). Width: process.stdout.columns || 80, min 72. Use stripAnsi() for ANSI-aware width.
- dashboard.ts —
renderDashboard(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):
- Stats types —
stats/types.ts
- Renderers (pure, testable):
sparkline.ts → reuse/extract bar.ts → box.ts → chart.ts → dashboard.ts
- Collectors:
git.ts → intelligence.ts (ledger + features + usage) → features.ts (manifest)
- Stats command —
stats/index.ts + format-json.ts + register in cli.ts
- Build + test —
npm run build, manual run on this repo
v2 (new infra):
6. Analytics module — utils/analytics.ts + getAnalyticsPath in project-paths.ts (+ CJS mirror)
7. Track command — commands/track.ts + register in cli.ts
8. Preamble instrumentation — fire-and-forget devflow track intent (scripts/hooks/preamble ~L68)
9. Workflows collector — workflows.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
npm run build — compiles clean
devflow stats — renders v1 panels (velocity, intelligence, before/after, features) with real repo data
devflow stats --period 7d / --period all — sparklines/buckets adjust
devflow stats --json — valid JSON
devflow stats --compact — omits charts
- 80-col terminal (min-width handling); no-
.devflow/ project (empty states)
- (v2)
devflow track intent implement --cwd $PWD → writes .devflow/memory/.analytics.jsonl
- (v2) trigger an ambient workflow (
implement …) → confirm preamble fires the tracking call without adding perceptible prompt latency
- 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.
Plan:
devflow stats— Developer Productivity DashboardContext
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 statsCLI 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:
Build order / phasing (driven by data availability)
Panels split cleanly into "buildable today" vs "needs new infrastructure". Recommend shipping v1 first:
.pending-turns.jsonlis an ephemeral queue, not a history (see below)Part 1: Analytics Tracking Module
Explicit event tracking via our own CLI. No signal extraction — only deliberate tracking calls.
File:
src/cli/utils/analytics.tsTwo functions:
trackEvent(projectRoot, event)— append one JSONL line to the analytics file (atomic append, same pattern asfs-atomic.ts/ pending-turns)readEvents(projectRoot, since?)— read + parse + optional time filterFile:
src/cli/commands/track.ts— thin CLI wrapper:Analytics file location
{projectRoot}/.devflow/memory/.analytics.jsonl— co-located with other per-project JSONL. Add a path helpergetAnalyticsPath(projectRoot)tosrc/cli/utils/project-paths.ts(next togetPendingTurnsPath, line ~157).project-paths.tshas a CJS mirror atscripts/hooks/lib/project-paths.cjsthat must be kept in sync — but the preamble hook (bash) will not compute the path itself; it delegates to thetrackCLI via--cwd, so only the.tshelper is strictly needed.Instrumentation point — preamble hook (NOT a router skill)
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 viajson_prompt_output(line 70); it makes no external calls.Inject the tracking call just before the directive emission (around line 68-70):
Two reliability caveats (must be documented in the dashboard, not hidden):
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, eachdevflowsubcommand can calltrackEventon invocation fortype: 'cli'metrics — but the workflow commands are notdevflowsubcommands.)Hook → CLI precedent:
background-memory-updatealready shells out toclaude -p, anddecisions-usage-scan.cjswrites 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
Dashboard Layout (box-border + sparklines)
File Structure
Modify:
src/cli/cli.ts(registertrack+statsviaprogram.addCommand(...), same pattern asdecisions.ts/knowledge/index.ts);src/cli/utils/project-paths.ts(+getAnalyticsPath);scripts/hooks/preamble(tracking call).Data Sources (CORRECTED to
.devflow/layout)git log(#\d+),--numstatfor lines.devflow/decisions/decisions-ledger.jsonl(getDecisionsLedgerPath),.devflow/features/index.json(getFeaturesIndexPath),.devflow/decisions/.decisions-usage.json(getDecisionsUsagePath)decisions_status !== "Retired", split ADR/PF bytype; countfeatureskeys; sumcitesgit log+~/.devflow/manifest.jsoninstalledAt(readManifest).devflow/memory/.analytics.jsonl(getAnalyticsPath)readEvents(), count byname.devflow/memory/sessions-ledger.jsonl)background-memory-updateafter it drains the queue~/.devflow/manifest.json(readManifest)features.{ambient,memory,hud,knowledge,decisions,rules}toggles,features.flags[],plugins[],version, datesReusable Utilities (CORRECTED)
readManifest()src/cli/utils/manifest.ts:30(devflowDir) => Promise<ManifestData | null>getGitRoot()src/cli/utils/git.ts:16getDevFlowDirectory()src/cli/utils/paths.ts:70~/.devflow(user scope).devflow/path helperssrc/cli/utils/project-paths.tsgetDecisionsLedgerPath,getFeaturesIndexPath,getDecisionsUsagePath,getMemoryDir, … (this is the canonical project-path resolver — it was not deleted)stripAnsi()+ 14 color fnssrc/cli/hud/colors.ts:64shellExec(execFile + timeout)src/cli/hud/git.ts:6renderBar()(8-char█/░, 3-tier color)src/cli/hud/components/usage-quota.ts:6-28parseLearningLog()/getLearningCounts()learninglearning-counts.tsdeleted from source.observations.ts/observation-io.tsstill exist but only becausedecisions-log.jsonlreuses the format — not needed for the ledger-based Intelligence collectorMust 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(values: number[]): stringmaps to▁▂▃▄▅▆▇█via min/max normalization (~15 lines).lineChart(data, labels, width, installIdx?): string[]— 8-row ASCII with╭╮╯╰─│, y-axis labels, optional install marker↑(~60 lines).renderBar()fromusage-quota.ts; add acomparisonBar(before, after, maxWidth)wrapper with%suffix.box(title, lines, width),sideBySide(left, right, width),outerFrame(sections, width). Width:process.stdout.columns || 80, min 72. UsestripAnsi()for ANSI-aware width.renderDashboard(data, width)+renderCompact(data, width); null/empty panels render placeholder text.Empty-state handling
installedAt→ before/after panel omittedJSON output
--jsonserializes theDashboardDataobject directly.Implementation Sequence
v1 (ready now):
stats/types.tssparkline.ts→ reuse/extractbar.ts→box.ts→chart.ts→dashboard.tsgit.ts→intelligence.ts(ledger + features + usage) →features.ts(manifest)stats/index.ts+format-json.ts+ register incli.tsnpm run build, manual run on this repov2 (new infra):
6. Analytics module —
utils/analytics.ts+getAnalyticsPathinproject-paths.ts(+ CJS mirror)7. Track command —
commands/track.ts+ register incli.ts8. Preamble instrumentation — fire-and-forget
devflow track intent(scripts/hooks/preamble ~L68)9. Workflows collector —
workflows.tsreads analytics JSONL10. Sessions ledger — append session summaries from
background-memory-updatepost-drain;sessions.tscollector11. Intelligence growth — record/reconstruct time-series
Verification
npm run build— compiles cleandevflow stats— renders v1 panels (velocity, intelligence, before/after, features) with real repo datadevflow stats --period 7d/--period all— sparklines/buckets adjustdevflow stats --json— valid JSONdevflow stats --compact— omits charts.devflow/project (empty states)devflow track intent implement --cwd $PWD→ writes.devflow/memory/.analytics.jsonlimplement …) → confirm preamble fires the tracking call without adding perceptible prompt latencyRevision history
/explore). Corrected: all paths.memory/→.devflow/(viaproject-paths.tshelpers); 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; confirmedrenderBar()reuse and that sparkline/chart/box must be built new.