context-mode 1.0.162 → 1.0.164
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.openclaw-plugin/openclaw.plugin.json +1 -1
- package/.openclaw-plugin/package.json +1 -1
- package/README.md +149 -30
- package/bin/statusline.mjs +24 -4
- package/build/adapters/antigravity/index.d.ts +1 -1
- package/build/adapters/antigravity-cli/index.d.ts +51 -0
- package/build/adapters/antigravity-cli/index.js +342 -0
- package/build/adapters/claude-code/hooks.d.ts +1 -0
- package/build/adapters/claude-code/hooks.js +3 -0
- package/build/adapters/claude-code/index.js +24 -5
- package/build/adapters/client-map.js +5 -0
- package/build/adapters/codex/hooks.d.ts +5 -1
- package/build/adapters/codex/hooks.js +5 -1
- package/build/adapters/codex/index.d.ts +9 -1
- package/build/adapters/codex/index.js +87 -5
- package/build/adapters/copilot-cli/hooks.d.ts +33 -0
- package/build/adapters/copilot-cli/hooks.js +64 -0
- package/build/adapters/copilot-cli/index.d.ts +48 -0
- package/build/adapters/copilot-cli/index.js +341 -0
- package/build/adapters/detect.d.ts +1 -1
- package/build/adapters/detect.js +71 -3
- package/build/adapters/openclaw/mcp-tools.js +1 -1
- package/build/adapters/opencode/index.js +31 -17
- package/build/adapters/opencode/zod3tov4.js +27 -6
- package/build/adapters/pi/extension.d.ts +2 -12
- package/build/adapters/pi/extension.js +128 -109
- package/build/adapters/types.d.ts +5 -4
- package/build/adapters/types.js +4 -3
- package/build/cache-heal.d.ts +48 -0
- package/build/cache-heal.js +150 -0
- package/build/cli.js +37 -97
- package/build/executor.d.ts +25 -0
- package/build/executor.js +143 -22
- package/build/lifecycle.d.ts +48 -0
- package/build/lifecycle.js +111 -0
- package/build/opencode-plugin.js +5 -2
- package/build/routing-block.d.ts +8 -0
- package/build/routing-block.js +86 -0
- package/build/runtime.d.ts +0 -36
- package/build/runtime.js +107 -27
- package/build/search/flood-guard.d.ts +57 -0
- package/build/search/flood-guard.js +80 -0
- package/build/security.d.ts +73 -3
- package/build/security.js +293 -33
- package/build/server.d.ts +14 -0
- package/build/server.js +441 -354
- package/build/session/analytics.d.ts +1 -1
- package/build/session/analytics.js +5 -1
- package/build/session/db.js +23 -3
- package/build/session/extract.js +78 -0
- package/build/store.d.ts +1 -1
- package/build/store.js +139 -25
- package/build/tool-naming.d.ts +4 -0
- package/build/tool-naming.js +24 -0
- package/build/util/jsonc.d.ts +14 -0
- package/build/util/jsonc.js +104 -0
- package/cli.bundle.mjs +253 -250
- package/configs/antigravity/GEMINI.md +2 -2
- package/configs/antigravity-cli/hooks/hooks.json +37 -0
- package/configs/antigravity-cli/hooks.json +37 -0
- package/configs/antigravity-cli/mcp_config.json +10 -0
- package/configs/antigravity-cli/plugin.json +14 -0
- package/configs/antigravity-cli/rules/context-mode.md +77 -0
- package/configs/antigravity-cli/skills/context-mode/SKILL.md +77 -0
- package/configs/claude-code/CLAUDE.md +2 -2
- package/configs/codex/AGENTS.md +2 -2
- package/configs/copilot-cli/.github/plugin/plugin.json +23 -0
- package/configs/copilot-cli/.mcp.json +12 -0
- package/configs/copilot-cli/README.md +47 -0
- package/configs/copilot-cli/hooks.json +41 -0
- package/configs/copilot-cli/skills/context-mode/SKILL.md +38 -0
- package/configs/gemini-cli/GEMINI.md +2 -2
- package/configs/jetbrains-copilot/copilot-instructions.md +2 -2
- package/configs/kilo/AGENTS.md +2 -2
- package/configs/kiro/KIRO.md +2 -2
- package/configs/omp/SYSTEM.md +2 -2
- package/configs/openclaw/AGENTS.md +2 -2
- package/configs/opencode/AGENTS.md +2 -2
- package/configs/qwen-code/QWEN.md +2 -2
- package/configs/vscode-copilot/copilot-instructions.md +2 -2
- package/configs/zed/AGENTS.md +2 -2
- package/hooks/antigravity-cli/payload.mjs +98 -0
- package/hooks/antigravity-cli/posttooluse.mjs +138 -0
- package/hooks/antigravity-cli/pretooluse.mjs +78 -0
- package/hooks/antigravity-cli/stop.mjs +58 -0
- package/hooks/codex/pretooluse.mjs +14 -4
- package/hooks/codex/stop.mjs +12 -4
- package/hooks/copilot-cli/posttooluse.mjs +79 -0
- package/hooks/copilot-cli/precompact.mjs +66 -0
- package/hooks/copilot-cli/pretooluse.mjs +41 -0
- package/hooks/copilot-cli/sessionstart.mjs +121 -0
- package/hooks/copilot-cli/stop.mjs +59 -0
- package/hooks/copilot-cli/userpromptsubmit.mjs +77 -0
- package/hooks/core/codex-caps.mjs +112 -0
- package/hooks/core/formatters.mjs +158 -7
- package/hooks/core/mcp-ready.mjs +37 -8
- package/hooks/core/routing.mjs +94 -8
- package/hooks/core/tool-naming.mjs +3 -0
- package/hooks/hooks.json +12 -1
- package/hooks/pretooluse.mjs +6 -2
- package/hooks/routing-block.mjs +3 -4
- package/hooks/security.bundle.mjs +2 -1
- package/hooks/session-db.bundle.mjs +5 -5
- package/hooks/session-directive.mjs +88 -20
- package/hooks/session-extract.bundle.mjs +2 -2
- package/hooks/session-helpers.mjs +21 -0
- package/hooks/sessionstart.mjs +37 -5
- package/hooks/stop.mjs +49 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -10
- package/server.bundle.mjs +206 -200
- package/skills/ctx-insight/SKILL.md +12 -17
- package/build/util/db-lock.d.ts +0 -65
- package/build/util/db-lock.js +0 -166
- package/insight/index.html +0 -13
- package/insight/package.json +0 -55
- package/insight/server.mjs +0 -1265
- package/insight/src/components/analytics.tsx +0 -112
- package/insight/src/components/ui/badge.tsx +0 -52
- package/insight/src/components/ui/button.tsx +0 -58
- package/insight/src/components/ui/card.tsx +0 -103
- package/insight/src/components/ui/chart.tsx +0 -371
- package/insight/src/components/ui/collapsible.tsx +0 -19
- package/insight/src/components/ui/input.tsx +0 -20
- package/insight/src/components/ui/progress.tsx +0 -83
- package/insight/src/components/ui/scroll-area.tsx +0 -55
- package/insight/src/components/ui/separator.tsx +0 -23
- package/insight/src/components/ui/table.tsx +0 -114
- package/insight/src/components/ui/tabs.tsx +0 -82
- package/insight/src/components/ui/tooltip.tsx +0 -64
- package/insight/src/lib/api.ts +0 -144
- package/insight/src/lib/utils.ts +0 -6
- package/insight/src/main.tsx +0 -22
- package/insight/src/routeTree.gen.ts +0 -189
- package/insight/src/router.tsx +0 -19
- package/insight/src/routes/__root.tsx +0 -55
- package/insight/src/routes/enterprise.tsx +0 -316
- package/insight/src/routes/index.tsx +0 -1482
- package/insight/src/routes/knowledge.tsx +0 -221
- package/insight/src/routes/knowledge_.$dbHash.$sourceId.tsx +0 -137
- package/insight/src/routes/search.tsx +0 -97
- package/insight/src/routes/sessions.tsx +0 -179
- package/insight/src/routes/sessions_.$dbHash.$sessionId.tsx +0 -181
- package/insight/src/styles.css +0 -104
- package/insight/tsconfig.json +0 -29
- package/insight/vite.config.ts +0 -19
|
@@ -1,1482 +0,0 @@
|
|
|
1
|
-
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
-
import { useEffect, useState } from "react";
|
|
3
|
-
import { api, type AnalyticsData, type CategoryAnalyticsData } from "@/lib/api";
|
|
4
|
-
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
|
5
|
-
import { Badge } from "@/components/ui/badge";
|
|
6
|
-
import { Separator } from "@/components/ui/separator";
|
|
7
|
-
import {
|
|
8
|
-
Brain, TrendingUp, AlertTriangle, CheckCircle,
|
|
9
|
-
Zap, FileCode, GitBranch, Clock, Shield, Activity, Cpu,
|
|
10
|
-
Lightbulb, BookOpen, Wrench, Users, MessageSquare, Search,
|
|
11
|
-
FolderOpen, Code,
|
|
12
|
-
} from "lucide-react";
|
|
13
|
-
|
|
14
|
-
export const Route = createFileRoute("/")({ component: Dashboard });
|
|
15
|
-
|
|
16
|
-
const COLORS = ["#3b82f6", "#8b5cf6", "#10b981", "#f59e0b", "#ef4444", "#06b6d4", "#ec4899", "#84cc16"];
|
|
17
|
-
|
|
18
|
-
// ── Insight types ──
|
|
19
|
-
interface Insight {
|
|
20
|
-
icon: React.ReactNode;
|
|
21
|
-
severity: "positive" | "warning" | "critical" | "neutral";
|
|
22
|
-
metric: string;
|
|
23
|
-
evidence: string;
|
|
24
|
-
action: string;
|
|
25
|
-
roi: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const SEV_STYLES = {
|
|
29
|
-
positive: { border: "border-emerald-500/30", bg: "bg-emerald-500/5", badge: "bg-emerald-500/15 text-emerald-400", label: "Nice" },
|
|
30
|
-
warning: { border: "border-amber-500/30", bg: "bg-amber-500/5", badge: "bg-amber-500/15 text-amber-400", label: "Heads up" },
|
|
31
|
-
critical: { border: "border-red-500/30", bg: "bg-red-500/5", badge: "bg-red-500/15 text-red-400", label: "Fix this" },
|
|
32
|
-
neutral: { border: "border-blue-500/30", bg: "bg-blue-500/5", badge: "bg-blue-500/15 text-blue-400", label: "FYI" },
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
function InsightCard({ icon, severity, metric, evidence, action, roi }: Insight) {
|
|
36
|
-
const s = SEV_STYLES[severity];
|
|
37
|
-
return (
|
|
38
|
-
<Card className={`${s.border} ${s.bg}`}>
|
|
39
|
-
<CardContent className="p-5">
|
|
40
|
-
{/* Header: badge + title */}
|
|
41
|
-
<div className="flex items-center gap-2 mb-3">
|
|
42
|
-
<span className={`text-[10px] font-bold uppercase tracking-wider px-2 py-0.5 rounded-full ${s.badge}`}>
|
|
43
|
-
{s.label}
|
|
44
|
-
</span>
|
|
45
|
-
</div>
|
|
46
|
-
|
|
47
|
-
{/* Main metric - the headline */}
|
|
48
|
-
<div className="flex items-center gap-2.5 mb-2">
|
|
49
|
-
<div className="shrink-0">{icon}</div>
|
|
50
|
-
<h4 className="text-base font-bold leading-tight">{metric}</h4>
|
|
51
|
-
</div>
|
|
52
|
-
|
|
53
|
-
{/* Evidence - short and readable */}
|
|
54
|
-
<p className="text-[13px] text-muted-foreground leading-relaxed mb-4">{evidence}</p>
|
|
55
|
-
|
|
56
|
-
{/* Action + ROI as distinct blocks */}
|
|
57
|
-
<div className="grid grid-cols-2 gap-3">
|
|
58
|
-
<div className="rounded-lg bg-background/40 p-3">
|
|
59
|
-
<p className="text-[10px] font-bold uppercase tracking-wider text-muted-foreground mb-1">What to do</p>
|
|
60
|
-
<p className="text-xs leading-relaxed">{action}</p>
|
|
61
|
-
</div>
|
|
62
|
-
<div className="rounded-lg bg-background/40 p-3">
|
|
63
|
-
<p className="text-[10px] font-bold uppercase tracking-wider text-muted-foreground mb-1">Why it matters</p>
|
|
64
|
-
<p className="text-xs leading-relaxed">{roi}</p>
|
|
65
|
-
</div>
|
|
66
|
-
</div>
|
|
67
|
-
</CardContent>
|
|
68
|
-
</Card>
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// ── Big number stat ──
|
|
73
|
-
function Stat({ label, value, sub, icon: Icon, color }: {
|
|
74
|
-
label: string; value: string | number; sub: string; icon: typeof Zap; color: string;
|
|
75
|
-
}) {
|
|
76
|
-
return (
|
|
77
|
-
<Card>
|
|
78
|
-
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
|
79
|
-
<CardTitle className="text-[10px] font-medium text-muted-foreground uppercase tracking-wider">{label}</CardTitle>
|
|
80
|
-
<Icon className={`h-4 w-4 ${color}`} />
|
|
81
|
-
</CardHeader>
|
|
82
|
-
<CardContent>
|
|
83
|
-
<div className="text-2xl font-bold tabular-nums">{value}</div>
|
|
84
|
-
<p className="text-[11px] text-muted-foreground mt-0.5">{sub}</p>
|
|
85
|
-
</CardContent>
|
|
86
|
-
</Card>
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// ── Ratio bar ──
|
|
91
|
-
function RatioBar({ items }: { items: { label: string; value: number; color: string }[] }) {
|
|
92
|
-
const total = items.reduce((a, b) => a + b.value, 0);
|
|
93
|
-
if (total === 0) return null;
|
|
94
|
-
return (
|
|
95
|
-
<div>
|
|
96
|
-
<div className="flex h-3 rounded-full overflow-hidden bg-secondary">
|
|
97
|
-
{items.map((item, i) => (
|
|
98
|
-
<div key={i} className="transition-all" style={{
|
|
99
|
-
width: `${Math.max(Math.round(100 * item.value / total), 2)}%`,
|
|
100
|
-
background: item.color,
|
|
101
|
-
}} />
|
|
102
|
-
))}
|
|
103
|
-
</div>
|
|
104
|
-
<div className="flex flex-wrap gap-x-4 gap-y-1 mt-2">
|
|
105
|
-
{items.map((item, i) => (
|
|
106
|
-
<span key={i} className="text-[10px] text-muted-foreground flex items-center gap-1">
|
|
107
|
-
<span className="w-2 h-2 rounded-full shrink-0" style={{ background: item.color }} />
|
|
108
|
-
{item.label}: {item.value} ({Math.round(100 * item.value / total)}%)
|
|
109
|
-
</span>
|
|
110
|
-
))}
|
|
111
|
-
</div>
|
|
112
|
-
</div>
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// ── Mini number ──
|
|
117
|
-
function Mini({ label, value, color }: { label: string; value: string | number; color?: string }) {
|
|
118
|
-
return (
|
|
119
|
-
<div className="text-center">
|
|
120
|
-
<div className={`text-2xl font-bold tabular-nums ${color || ""}`}>{value}</div>
|
|
121
|
-
<p className="text-[10px] text-muted-foreground uppercase tracking-wider">{label}</p>
|
|
122
|
-
</div>
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// ── Insights engine ──
|
|
127
|
-
function generateInsights(d: AnalyticsData): Insight[] {
|
|
128
|
-
const ins: Insight[] = [];
|
|
129
|
-
const t = d.totals;
|
|
130
|
-
|
|
131
|
-
// ── Reading more than writing
|
|
132
|
-
if (t.reads + t.writes >= 5) {
|
|
133
|
-
const ratio = t.readWriteRatio;
|
|
134
|
-
if (ratio > 5) {
|
|
135
|
-
ins.push({
|
|
136
|
-
icon: <BookOpen className="h-5 w-5 text-blue-500" />, severity: "neutral",
|
|
137
|
-
metric: `You read ${ratio}x more than you write`,
|
|
138
|
-
evidence: `${t.reads} files read vs ${t.writes} files written. You're spending most of your AI time understanding code, not producing it.`,
|
|
139
|
-
action: "Write a short plan before starting. Clarify what you want to change before reading everything.",
|
|
140
|
-
roi: "Planning upfront typically reduces the number of file re-reads.",
|
|
141
|
-
});
|
|
142
|
-
} else if (ratio < 2 && ratio > 0) {
|
|
143
|
-
ins.push({
|
|
144
|
-
icon: <FileCode className="h-5 w-5 text-emerald-500" />, severity: "positive",
|
|
145
|
-
metric: `Healthy balance: ${ratio}:1 read-to-write`,
|
|
146
|
-
evidence: `You're reading just enough to write confidently. ${t.writes} files written with only ${t.reads} reads.`,
|
|
147
|
-
action: "Keep this up — you're not over-analyzing or guessing blind.",
|
|
148
|
-
roi: "This balance leads to fewer bugs on first attempt.",
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// ── Context filling up
|
|
154
|
-
if (t.totalSessions >= 3) {
|
|
155
|
-
const pct = t.compactRate;
|
|
156
|
-
if (pct > 60) {
|
|
157
|
-
ins.push({
|
|
158
|
-
icon: <Brain className="h-5 w-5 text-amber-500" />, severity: "warning",
|
|
159
|
-
metric: `${pct}% of your sessions run out of context`,
|
|
160
|
-
evidence: `${t.totalCompacts} times the agent had to compress your conversation. Each time, it forgets details from earlier work.`,
|
|
161
|
-
action: "Start fresh sessions for new tasks instead of continuing long ones.",
|
|
162
|
-
roi: `You'd recover ~${Math.round(t.totalCompacts * 3)} minutes of re-explaining lost context.`,
|
|
163
|
-
});
|
|
164
|
-
} else if (pct < 20 && t.totalSessions >= 5) {
|
|
165
|
-
ins.push({
|
|
166
|
-
icon: <CheckCircle className="h-5 w-5 text-emerald-500" />, severity: "positive",
|
|
167
|
-
metric: `Only ${pct}% context overflow — you're efficient`,
|
|
168
|
-
evidence: `${t.totalCompacts} compactions in ${t.totalSessions} sessions. Your tasks are well-scoped.`,
|
|
169
|
-
action: "Share this pattern with your team — most developers hit 60%+.",
|
|
170
|
-
roi: "Less context loss = less time re-explaining = faster completion.",
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// ── Errors
|
|
176
|
-
if (t.totalEvents >= 20) {
|
|
177
|
-
const rate = t.errorRate;
|
|
178
|
-
if (rate > 10) {
|
|
179
|
-
ins.push({
|
|
180
|
-
icon: <AlertTriangle className="h-5 w-5 text-red-500" />, severity: "critical",
|
|
181
|
-
metric: `${rate}% of your tool calls fail`,
|
|
182
|
-
evidence: `${t.totalErrors} errors in ${t.totalEvents} calls. That's a lot of wasted back-and-forth.`,
|
|
183
|
-
action: "Check if the same error keeps repeating. Add a CLAUDE.md rule to prevent it.",
|
|
184
|
-
roi: `Fixing this saves ~${Math.round(t.totalErrors * 2)} minutes of retry loops.`,
|
|
185
|
-
});
|
|
186
|
-
} else if (rate < 3) {
|
|
187
|
-
ins.push({
|
|
188
|
-
icon: <Shield className="h-5 w-5 text-emerald-500" />, severity: "positive",
|
|
189
|
-
metric: `Only ${rate}% error rate — you're a power user`,
|
|
190
|
-
evidence: `${t.totalErrors} errors in ${t.totalEvents} calls. Almost everything works on the first try.`,
|
|
191
|
-
action: "Document your prompting style — others can learn from it.",
|
|
192
|
-
roi: "Clean usage means more time building, less time debugging.",
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// ── Parallel work
|
|
198
|
-
if (t.totalSessions >= 2) {
|
|
199
|
-
if (t.totalSubagents > 0 && d.subagents.bursts > 0) {
|
|
200
|
-
ins.push({
|
|
201
|
-
icon: <Users className="h-5 w-5 text-emerald-500" />, severity: "positive",
|
|
202
|
-
metric: `You saved ~${d.subagents.timeSavedMin} min with parallel agents`,
|
|
203
|
-
evidence: `${d.subagents.parallelCount} tasks ran simultaneously in ${d.subagents.bursts} bursts (up to ${d.subagents.maxConcurrent} at once).`,
|
|
204
|
-
action: "Keep delegating research and exploration to subagents.",
|
|
205
|
-
roi: "Parallel work is the single biggest time multiplier available to you.",
|
|
206
|
-
});
|
|
207
|
-
} else if (t.totalSubagents === 0 && t.totalEvents > 50) {
|
|
208
|
-
ins.push({
|
|
209
|
-
icon: <Users className="h-5 w-5 text-muted-foreground" />, severity: "neutral",
|
|
210
|
-
metric: "Everything ran sequentially",
|
|
211
|
-
evidence: `${t.totalEvents} events, zero parallel agents. You're doing one thing at a time.`,
|
|
212
|
-
action: "Try subagents for research — fire 3-5 at once instead of doing them one by one.",
|
|
213
|
-
roi: "One parallel burst can turn 10 minutes of research into 2 minutes.",
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// ── Prompt efficiency
|
|
219
|
-
if (t.totalPrompts >= 5 && t.totalSessions >= 2) {
|
|
220
|
-
const pps = t.promptsPerSession;
|
|
221
|
-
if (pps < 3) {
|
|
222
|
-
ins.push({
|
|
223
|
-
icon: <MessageSquare className="h-5 w-5 text-emerald-500" />, severity: "positive",
|
|
224
|
-
metric: `${pps} prompts per session — clear instructions`,
|
|
225
|
-
evidence: `You give the agent clear, complete instructions. It works autonomously without needing constant guidance.`,
|
|
226
|
-
action: "This is the ideal. Keep writing comprehensive upfront instructions.",
|
|
227
|
-
roi: "Every avoided back-and-forth saves context for actual work.",
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// ── Mastery trend
|
|
233
|
-
if (d.masteryTrend && d.masteryTrend.length >= 3) {
|
|
234
|
-
const first = d.masteryTrend[0];
|
|
235
|
-
const last = d.masteryTrend[d.masteryTrend.length - 1];
|
|
236
|
-
if (last.error_rate < first.error_rate) {
|
|
237
|
-
ins.push({
|
|
238
|
-
icon: <TrendingUp className="h-5 w-5 text-emerald-500" />, severity: "positive",
|
|
239
|
-
metric: "Your error rate is dropping — you're getting better",
|
|
240
|
-
evidence: `Week 1: ${first.error_rate}%, Latest: ${last.error_rate}%. Consistent improvement over ${d.masteryTrend.length} weeks.`,
|
|
241
|
-
action: "Keep refining your prompting patterns.",
|
|
242
|
-
roi: "Lower errors = less retry time = faster completion.",
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
// ── Commit rate
|
|
249
|
-
if (t.commitsPerSession > 1) {
|
|
250
|
-
ins.push({
|
|
251
|
-
icon: <GitBranch className="h-5 w-5 text-emerald-500" />, severity: "positive",
|
|
252
|
-
metric: `${t.commitsPerSession} commits per session — high output`,
|
|
253
|
-
evidence: `${t.totalCommits} total commits across ${t.totalSessions} sessions.`,
|
|
254
|
-
action: "Maintain this shipping cadence.",
|
|
255
|
-
roi: "Frequent commits reduce merge conflicts and context loss.",
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// ── Edit-test cycles
|
|
260
|
-
if (t.totalEditTestCycles > t.totalSessions * 2) {
|
|
261
|
-
ins.push({
|
|
262
|
-
icon: <Activity className="h-5 w-5 text-amber-500" />, severity: "warning",
|
|
263
|
-
metric: `${t.totalEditTestCycles} edit-error cycles — lots of retry loops`,
|
|
264
|
-
evidence: `${(t.totalEditTestCycles / t.totalSessions).toFixed(1)} cycles per session on average.`,
|
|
265
|
-
action: "Write tests first, or add patterns to CLAUDE.md to prevent common errors.",
|
|
266
|
-
roi: "Fewer retry loops means faster task completion.",
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// ── Task structure
|
|
271
|
-
if (t.totalTasks > 0 && t.totalSessions >= 2) {
|
|
272
|
-
ins.push({
|
|
273
|
-
icon: <Wrench className="h-5 w-5 text-blue-500" />, severity: "positive",
|
|
274
|
-
metric: `${(t.totalTasks / t.totalSessions).toFixed(1)} tasks per session — you plan ahead`,
|
|
275
|
-
evidence: `${t.totalTasks} structured tasks across ${t.totalSessions} sessions. You break work into steps.`,
|
|
276
|
-
action: "Keep using tasks — they make sessions resumable after breaks.",
|
|
277
|
-
roi: "Structured sessions are easier to resume and track progress.",
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
return ins;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
function generateCategoryInsights(c: CategoryAnalyticsData): Insight[] {
|
|
285
|
-
const ins: Insight[] = [];
|
|
286
|
-
const cs = c.compositeScores;
|
|
287
|
-
const ei = c.errorIntelligence;
|
|
288
|
-
const del = c.delegation;
|
|
289
|
-
const gov = c.governance;
|
|
290
|
-
const ctx = c.contextHealth;
|
|
291
|
-
const fi = c.fileIntelligence;
|
|
292
|
-
const git = c.gitProductivity;
|
|
293
|
-
const totalEvents = c.categories.reduce((a, b) => a + b.count, 0);
|
|
294
|
-
|
|
295
|
-
// P1 removed — duplicate of generateInsights read:write ratio pattern
|
|
296
|
-
|
|
297
|
-
// ── P2: High file churn ──
|
|
298
|
-
if (fi.hotFiles.length > 3) {
|
|
299
|
-
const top = fi.hotFiles[0];
|
|
300
|
-
ins.push({
|
|
301
|
-
icon: <AlertTriangle className="h-5 w-5 text-amber-500" />, severity: "warning",
|
|
302
|
-
metric: `${fi.hotFiles.length} hot files with repeated edits`,
|
|
303
|
-
evidence: `${top.file.split('/').pop()} was touched ${top.touches} times. Possible rework loop.`,
|
|
304
|
-
action: "Read the full file before editing. Add constraints to CLAUDE.md.",
|
|
305
|
-
roi: `Fewer re-edits save ~${fi.hotFiles.length * 3} minutes per session.`,
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// ── P3: Session failed — errors + 0 commits (★★ STRONG from agentisd #5) ──
|
|
310
|
-
if (ei.totalErrors > 3 && git.totalCommits === 0 && totalEvents > 50) {
|
|
311
|
-
ins.push({
|
|
312
|
-
icon: <AlertTriangle className="h-5 w-5 text-red-500" />, severity: "critical",
|
|
313
|
-
metric: `${ei.totalErrors} errors, zero commits — session effort lost`,
|
|
314
|
-
evidence: `${totalEvents} events with ${ei.totalErrors} errors and no commits. All effort was lost.`,
|
|
315
|
-
action: "Review the error pattern, fix root cause, break tasks into smaller deliverable chunks.",
|
|
316
|
-
roi: `Recovering from failed sessions costs an additional ${Math.round(totalEvents * 0.5)} minutes.`,
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// ── P4: Zero commits ──
|
|
321
|
-
if (git.totalCommits === 0 && totalEvents > 100) {
|
|
322
|
-
ins.push({
|
|
323
|
-
icon: <GitBranch className="h-5 w-5 text-amber-500" />, severity: "warning",
|
|
324
|
-
metric: "No commits across all sessions",
|
|
325
|
-
evidence: `${totalEvents} events recorded, zero commits. Work may not be reaching the codebase.`,
|
|
326
|
-
action: "Commit incrementally — small commits are easier to review.",
|
|
327
|
-
roi: "Regular commits create a safety net for rollbacks.",
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// ── Q1: High error resolution ──
|
|
332
|
-
if (ei.totalErrors >= 5 && ei.resolutionRate > 80) {
|
|
333
|
-
ins.push({
|
|
334
|
-
icon: <CheckCircle className="h-5 w-5 text-emerald-500" />, severity: "positive",
|
|
335
|
-
metric: `${ei.resolutionRate}% of errors resolved within session`,
|
|
336
|
-
evidence: `${ei.resolvedErrors} of ${ei.totalErrors} errors fixed. Self-healing sessions.`,
|
|
337
|
-
action: "Keep this up — self-healing sessions are the gold standard.",
|
|
338
|
-
roi: "Each resolved error saves 5-10 min of next-session debugging.",
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// ── Q2: Retry storm (>=2 to avoid single-fluke false positive) ──
|
|
343
|
-
if (ei.retryStorms >= 2) {
|
|
344
|
-
ins.push({
|
|
345
|
-
icon: <AlertTriangle className="h-5 w-5 text-red-500" />, severity: "critical",
|
|
346
|
-
metric: `${ei.retryStorms} retry storm${ei.retryStorms > 1 ? 's' : ''} detected`,
|
|
347
|
-
evidence: `Sessions where the same tool was called 3+ times with similar input. The agent gets stuck in loops.`,
|
|
348
|
-
action: "Stop and re-think the approach. Add error context to CLAUDE.md.",
|
|
349
|
-
roi: `Breaking retry loops saves ~${ei.retryStorms * 5} minutes of wasted compute.`,
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// ── Q3: Persistent errors ──
|
|
354
|
-
if (ei.totalErrors >= 5 && ei.resolutionRate < 30) {
|
|
355
|
-
ins.push({
|
|
356
|
-
icon: <AlertTriangle className="h-5 w-5 text-amber-500" />, severity: "warning",
|
|
357
|
-
metric: `Only ${ei.resolutionRate}% of errors get resolved`,
|
|
358
|
-
evidence: `${ei.totalErrors - ei.resolvedErrors} errors left unresolved. Technical debt accumulating.`,
|
|
359
|
-
action: "Address root causes — add patterns to prevent recurring errors.",
|
|
360
|
-
roi: `Resolving persistent errors prevents hours of future debugging.`,
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// ── Q4: Slow tool bottleneck ──
|
|
365
|
-
if (ei.p95LatencyMs > 15000 && ei.slowestTool) {
|
|
366
|
-
ins.push({
|
|
367
|
-
icon: <Clock className="h-5 w-5 text-amber-500" />, severity: "warning",
|
|
368
|
-
metric: `${ei.slowestTool} averaging ${Math.round(ei.avgLatencyMs / 1000)}s — bottleneck`,
|
|
369
|
-
evidence: `P95 latency: ${Math.round(ei.p95LatencyMs / 1000)}s. ${ei.latencyByTool.length} tools tracked for latency.`,
|
|
370
|
-
action: "Check if tool can be replaced or if input can be simplified.",
|
|
371
|
-
roi: `Fixing bottleneck saves ~${Math.round(ei.latencyByTool.reduce((a, t) => a + t.count, 0) * ei.avgLatencyMs / 60000)} minutes total.`,
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// ── S1: Power delegator ──
|
|
376
|
-
if (del.launched > 10 && del.completionRate > 70) {
|
|
377
|
-
ins.push({
|
|
378
|
-
icon: <Users className="h-5 w-5 text-emerald-500" />, severity: "positive",
|
|
379
|
-
metric: `${del.launched} agents delegated — ${del.completionRate}% completion`,
|
|
380
|
-
evidence: `${del.parallelBursts} parallel bursts, up to ${del.maxConcurrent} concurrent. ~${del.timeSavedMin} min saved.`,
|
|
381
|
-
action: "Excellent use of parallelism. Share this pattern with the team.",
|
|
382
|
-
roi: `Parallel delegation saved ~${del.timeSavedMin} minutes.`,
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// ── S2: No delegation ──
|
|
387
|
-
if (del.launched === 0 && totalEvents > 50) {
|
|
388
|
-
ins.push({
|
|
389
|
-
icon: <Users className="h-5 w-5 text-muted-foreground" />, severity: "neutral",
|
|
390
|
-
metric: "Everything ran sequentially — zero delegation",
|
|
391
|
-
evidence: `${totalEvents} events, zero parallel agents. Single-threaded workflow.`,
|
|
392
|
-
action: "Try subagents for research — fire 3-5 at once instead of one by one.",
|
|
393
|
-
roi: "One parallel burst turns 10 minutes of research into 2 minutes.",
|
|
394
|
-
});
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// ── S3: Low agent completion ──
|
|
398
|
-
if (del.launched >= 5 && del.completionRate < 60) {
|
|
399
|
-
ins.push({
|
|
400
|
-
icon: <AlertTriangle className="h-5 w-5 text-amber-500" />, severity: "warning",
|
|
401
|
-
metric: `Only ${del.completionRate}% of agents complete successfully`,
|
|
402
|
-
evidence: `${del.launched} launched, ${del.completed} completed. ${del.launched - del.completed} failed or timed out.`,
|
|
403
|
-
action: "Simplify agent prompts. Break complex tasks into smaller units.",
|
|
404
|
-
roi: "Higher completion = less wasted compute.",
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// ── S4: Parallel burst champion ──
|
|
409
|
-
if (del.maxConcurrent >= 4) {
|
|
410
|
-
ins.push({
|
|
411
|
-
icon: <Cpu className="h-5 w-5 text-emerald-500" />, severity: "positive",
|
|
412
|
-
metric: `Peak parallelism: ${del.maxConcurrent} agents simultaneously`,
|
|
413
|
-
evidence: `${del.parallelBursts} bursts of parallel work. Maximum throughput achieved.`,
|
|
414
|
-
action: "You're using the platform at full capacity. Well done.",
|
|
415
|
-
roi: `Peak parallelism multiplies throughput by ${del.maxConcurrent}x.`,
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// ── G1: High rejection rate ──
|
|
420
|
-
if (gov.totalRejections > 10) {
|
|
421
|
-
const topRej = gov.topRejected[0];
|
|
422
|
-
ins.push({
|
|
423
|
-
icon: <Shield className="h-5 w-5 text-amber-500" />, severity: "warning",
|
|
424
|
-
metric: `${gov.totalRejections} approaches rejected by user`,
|
|
425
|
-
evidence: `Top rejected: ${topRej?.tool || 'unknown'} (${topRej?.count || 0} times). The agent keeps trying things you don't want.`,
|
|
426
|
-
action: "Update CLAUDE.md with clearer constraints to prevent unwanted actions.",
|
|
427
|
-
roi: `Better rules prevent ${gov.totalRejections} unnecessary tool calls.`,
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// ── G2: Productive session — commits + clean exit (★★ STRONG from agentisd #25) ──
|
|
432
|
-
if (git.totalCommits > 0 && ei.totalErrors < 3) {
|
|
433
|
-
ins.push({
|
|
434
|
-
icon: <CheckCircle className="h-5 w-5 text-emerald-500" />, severity: "positive",
|
|
435
|
-
metric: `Productive sessions — ${git.totalCommits} commits, clean exit`,
|
|
436
|
-
evidence: `${git.totalCommits} commits with only ${ei.totalErrors} errors. Work shipped successfully.`,
|
|
437
|
-
action: "Document what made this session productive — replicate the pattern.",
|
|
438
|
-
roi: "Productive sessions compound. Each clean commit is future-proof.",
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// ── G3: Plan discipline ──
|
|
443
|
-
if (gov.planApproved + gov.planRejected > 0 && gov.planApprovalRate > 80) {
|
|
444
|
-
ins.push({
|
|
445
|
-
icon: <CheckCircle className="h-5 w-5 text-emerald-500" />, severity: "positive",
|
|
446
|
-
metric: `${gov.planApprovalRate}% of plans approved on first try`,
|
|
447
|
-
evidence: `${gov.planApproved} plans approved, ${gov.planRejected} rejected. Strong alignment.`,
|
|
448
|
-
action: "Strong planning discipline. Keep using plan mode for complex tasks.",
|
|
449
|
-
roi: "Approved plans mean fewer mid-session course corrections.",
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
// ── G4: Error rate high for event volume (★★ STRONG from agentisd #2) ──
|
|
454
|
-
if (ei.totalErrors > 0 && totalEvents > 30) {
|
|
455
|
-
const errorPct = Math.round(1000 * ei.totalErrors / totalEvents) / 10;
|
|
456
|
-
if (errorPct > 15) {
|
|
457
|
-
ins.push({
|
|
458
|
-
icon: <AlertTriangle className="h-5 w-5 text-red-500" />, severity: "warning",
|
|
459
|
-
metric: `${errorPct}% error rate — ${ei.totalErrors} errors in ${totalEvents} events`,
|
|
460
|
-
evidence: `Error rate is above 15%. Most tools should succeed on first try. High error rate wastes compute.`,
|
|
461
|
-
action: "Check repeating errors. Add error patterns to CLAUDE.md to prevent them.",
|
|
462
|
-
roi: `Fixing the top error source saves ~${Math.round(ei.totalErrors * 1.5)} minutes of retry loops.`,
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// ── H1: Rules loaded consistently ──
|
|
468
|
-
if (ctx.uniqueRuleFiles > 0 && ctx.ruleLoadsPerSession > 1) {
|
|
469
|
-
ins.push({
|
|
470
|
-
icon: <FileCode className="h-5 w-5 text-emerald-500" />, severity: "positive",
|
|
471
|
-
metric: `${ctx.uniqueRuleFiles} rule files loaded consistently`,
|
|
472
|
-
evidence: `${ctx.ruleLoadsPerSession.toFixed(1)} loads per session. Rules are guiding behavior.`,
|
|
473
|
-
action: "Consistent rule loading means consistent behavior.",
|
|
474
|
-
roi: "Rules prevent the top error patterns.",
|
|
475
|
-
});
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// ── H2: No rules ──
|
|
479
|
-
if (ctx.uniqueRuleFiles === 0) {
|
|
480
|
-
ins.push({
|
|
481
|
-
icon: <FileCode className="h-5 w-5 text-amber-500" />, severity: "warning",
|
|
482
|
-
metric: "No CLAUDE.md or rule files detected",
|
|
483
|
-
evidence: "The agent runs without project-specific instructions.",
|
|
484
|
-
action: "Create a CLAUDE.md with project conventions, constraints, and patterns.",
|
|
485
|
-
roi: "Projects with CLAUDE.md consistently show lower error rates.",
|
|
486
|
-
});
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// H3 (low skill usage) removed — filler, not actionable
|
|
490
|
-
// H4 (implement-heavy) removed — observational, no clear action
|
|
491
|
-
|
|
492
|
-
// ── W1: Unresolved blockers ──
|
|
493
|
-
if (ctx.totalBlockers > 0 && ctx.blockerResolutionRate < 50) {
|
|
494
|
-
ins.push({
|
|
495
|
-
icon: <AlertTriangle className="h-5 w-5 text-red-500" />, severity: "critical",
|
|
496
|
-
metric: `${ctx.totalBlockers - ctx.resolvedBlockers} unresolved blockers`,
|
|
497
|
-
evidence: `Only ${ctx.blockerResolutionRate}% of blockers resolved. Open items: ${ctx.totalBlockers - ctx.resolvedBlockers}.`,
|
|
498
|
-
action: "Document blockers for the next session or escalate.",
|
|
499
|
-
roi: "Unresolved blockers multiply in cost over time.",
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
// ── W2: CLAUDE.md update correlated with error drop (★★★ KILLER from agentisd #22) ──
|
|
504
|
-
if (ctx.uniqueRuleFiles > 0 && ei.totalErrors > 0 && ei.resolutionRate > 50) {
|
|
505
|
-
ins.push({
|
|
506
|
-
icon: <FileCode className="h-5 w-5 text-emerald-500" />, severity: "positive",
|
|
507
|
-
metric: "CLAUDE.md loaded + errors resolving — rules are working",
|
|
508
|
-
evidence: `${ctx.uniqueRuleFiles} rule files loaded, ${ei.resolutionRate}% error resolution rate. Rules correlate with self-healing sessions.`,
|
|
509
|
-
action: "Update CLAUDE.md when you discover new error patterns — each rule prevents future errors.",
|
|
510
|
-
roi: "Sessions with rules loaded show measurably higher error resolution rates.",
|
|
511
|
-
});
|
|
512
|
-
}
|
|
513
|
-
if (ctx.uniqueRuleFiles === 0 && ei.totalErrors > 5) {
|
|
514
|
-
ins.push({
|
|
515
|
-
icon: <FileCode className="h-5 w-5 text-red-500" />, severity: "critical",
|
|
516
|
-
metric: `No CLAUDE.md + ${ei.totalErrors} errors — rules would prevent this`,
|
|
517
|
-
evidence: `${ei.totalErrors} errors without any project rules loaded. CLAUDE.md is the #1 way to reduce errors.`,
|
|
518
|
-
action: "Create CLAUDE.md with error patterns, constraints, and project conventions.",
|
|
519
|
-
roi: "Adding CLAUDE.md is the single most impactful action for reducing errors.",
|
|
520
|
-
});
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
// ── W3: Persistent struggle — same file across sessions (★★ STRONG from agentisd #13) ──
|
|
524
|
-
if (fi.hotFiles.length > 0 && fi.hotFiles[0].touches > 8) {
|
|
525
|
-
const worst = fi.hotFiles[0];
|
|
526
|
-
ins.push({
|
|
527
|
-
icon: <AlertTriangle className="h-5 w-5 text-red-500" />, severity: "warning",
|
|
528
|
-
metric: `${worst.file.split('/').pop()} touched ${worst.touches} times — persistent struggle`,
|
|
529
|
-
evidence: `File edited ${worst.touches} times across sessions. This suggests unclear requirements or wrong approach.`,
|
|
530
|
-
action: "Write a spec or test first. Consider if the approach needs rethinking entirely.",
|
|
531
|
-
roi: `Spec-first approach reduces rework from ${worst.touches} to ~3 edits.`,
|
|
532
|
-
});
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
// X1-X4 composite scores shown as hero cards — only generate insight for critical scores
|
|
536
|
-
if (cs.productivity < 40) {
|
|
537
|
-
ins.push({
|
|
538
|
-
icon: <TrendingUp className="h-5 w-5 text-amber-500" />, severity: "warning",
|
|
539
|
-
metric: `Productivity Score: ${cs.productivity}/100 — room to improve`,
|
|
540
|
-
evidence: `Low commit rate or high rework. Consider structured planning before execution.`,
|
|
541
|
-
action: "Start sessions with a plan. Commit incrementally. Delegate research to agents.",
|
|
542
|
-
roi: "Planning + committing incrementally improves output consistency.",
|
|
543
|
-
});
|
|
544
|
-
}
|
|
545
|
-
if (cs.quality < 40) {
|
|
546
|
-
ins.push({
|
|
547
|
-
icon: <Shield className="h-5 w-5 text-red-500" />, severity: "critical",
|
|
548
|
-
metric: `Quality Score: ${cs.quality}/100 — needs attention`,
|
|
549
|
-
evidence: `High error rate or unresolved errors. Retry loops detected.`,
|
|
550
|
-
action: "Add error patterns to CLAUDE.md. Write tests first. Break retry loops early.",
|
|
551
|
-
roi: "Addressing error patterns in-session prevents them from recurring.",
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
if (cs.contextHealth < 40) {
|
|
555
|
-
ins.push({
|
|
556
|
-
icon: <Brain className="h-5 w-5 text-amber-500" />, severity: "warning",
|
|
557
|
-
metric: `Context Health: ${cs.contextHealth}/100 — agent lacks guidance`,
|
|
558
|
-
evidence: `Missing rules, few skills, no plans. The agent works without context.`,
|
|
559
|
-
action: "Create CLAUDE.md, use plan mode, try skills like /commit.",
|
|
560
|
-
roi: "Better context hygiene directly correlates with fewer errors.",
|
|
561
|
-
});
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
return ins;
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
// ── Dashboard ──
|
|
568
|
-
function Dashboard() {
|
|
569
|
-
const [data, setData] = useState<AnalyticsData | null>(null);
|
|
570
|
-
const [catData, setCatData] = useState<CategoryAnalyticsData | null>(null);
|
|
571
|
-
const [showAllInsights, setShowAllInsights] = useState(false);
|
|
572
|
-
useEffect(() => {
|
|
573
|
-
api.analytics().then(setData);
|
|
574
|
-
api.categoryAnalytics().then(setCatData).catch(() => {}); // graceful — catData stays null, sections hidden
|
|
575
|
-
}, []);
|
|
576
|
-
if (!data) return <p className="text-muted-foreground animate-pulse">Loading analytics...</p>;
|
|
577
|
-
|
|
578
|
-
const t = data.totals;
|
|
579
|
-
const categoryInsights = catData ? generateCategoryInsights(catData) : [];
|
|
580
|
-
const allInsights = [...generateInsights(data), ...categoryInsights];
|
|
581
|
-
// Sort: critical first, then warning, positive, neutral
|
|
582
|
-
const SEV_ORDER = { critical: 0, warning: 1, positive: 2, neutral: 3 };
|
|
583
|
-
allInsights.sort((a, b) => (SEV_ORDER[a.severity] ?? 4) - (SEV_ORDER[b.severity] ?? 4));
|
|
584
|
-
const insights = showAllInsights ? allInsights : allInsights.slice(0, 8);
|
|
585
|
-
|
|
586
|
-
// Compute derived values
|
|
587
|
-
const topTool = data.toolUsage[0];
|
|
588
|
-
const topMcp = data.mcpTools[0];
|
|
589
|
-
const peakHour = data.hourlyPattern.reduce((max, h) => h.count > (max?.count || 0) ? h : max, data.hourlyPattern[0]);
|
|
590
|
-
const topProject = data.projectActivity[0];
|
|
591
|
-
const topFile = data.fileActivity[0];
|
|
592
|
-
|
|
593
|
-
return (
|
|
594
|
-
<div className="space-y-6">
|
|
595
|
-
<div>
|
|
596
|
-
<h2 className="text-2xl font-semibold">Dashboard</h2>
|
|
597
|
-
<p className="text-sm text-muted-foreground mt-1">Personal insights · {t.totalSessions} sessions · {t.totalEvents} events</p>
|
|
598
|
-
</div>
|
|
599
|
-
|
|
600
|
-
<div className="space-y-6">
|
|
601
|
-
|
|
602
|
-
{/* KPI Strip */}
|
|
603
|
-
<div className="grid grid-cols-2 lg:grid-cols-5 gap-3">
|
|
604
|
-
<Stat label="Sessions" value={t.totalSessions} sub={`${t.avgSessionMin} min avg`} icon={Zap} color="text-blue-500" />
|
|
605
|
-
<Stat label="Read:Write" value={`${t.readWriteRatio}:1`} sub={`${t.reads}R / ${t.writes}W`} icon={BookOpen} color="text-purple-500" />
|
|
606
|
-
<Stat label="Compact Rate" value={`${t.compactRate}%`} sub={`${t.totalCompacts} compactions`} icon={Brain} color={t.compactRate > 60 ? "text-amber-500" : "text-emerald-500"} />
|
|
607
|
-
<Stat label="Error Rate" value={`${t.errorRate}%`} sub={`${t.totalErrors} errors`} icon={Shield} color={t.errorRate > 10 ? "text-red-500" : "text-emerald-500"} />
|
|
608
|
-
<Stat label="Prompts" value={t.promptsPerSession} sub="per session" icon={MessageSquare} color="text-cyan-500" />
|
|
609
|
-
</div>
|
|
610
|
-
|
|
611
|
-
{/* Insights */}
|
|
612
|
-
{insights.length > 0 && (
|
|
613
|
-
<div>
|
|
614
|
-
<div className="flex items-center gap-2 mb-3">
|
|
615
|
-
<Lightbulb className="h-4 w-4 text-amber-500" />
|
|
616
|
-
<h3 className="text-sm font-semibold uppercase tracking-wide text-muted-foreground">Insights & Actions</h3>
|
|
617
|
-
<Badge variant="secondary" className="text-[10px]">{insights.length}</Badge>
|
|
618
|
-
</div>
|
|
619
|
-
<div className="grid gap-3 md:grid-cols-2">
|
|
620
|
-
{insights.map((ins, i) => <InsightCard key={i} {...ins} />)}
|
|
621
|
-
</div>
|
|
622
|
-
{allInsights.length > 8 && (
|
|
623
|
-
<button
|
|
624
|
-
onClick={() => setShowAllInsights(!showAllInsights)}
|
|
625
|
-
className="mt-2 text-[11px] text-muted-foreground hover:text-foreground transition-colors"
|
|
626
|
-
>
|
|
627
|
-
{showAllInsights ? "Show less" : `Show all ${allInsights.length} insights`}
|
|
628
|
-
</button>
|
|
629
|
-
)}
|
|
630
|
-
</div>
|
|
631
|
-
)}
|
|
632
|
-
|
|
633
|
-
<Separator />
|
|
634
|
-
|
|
635
|
-
{/* ── Tool Usage ── */}
|
|
636
|
-
<div className="grid gap-4 lg:grid-cols-2">
|
|
637
|
-
<Card>
|
|
638
|
-
<CardHeader>
|
|
639
|
-
<div className="flex items-center gap-2">
|
|
640
|
-
<Activity className="h-4 w-4 text-blue-500" />
|
|
641
|
-
<CardTitle className="text-sm">Tool Usage</CardTitle>
|
|
642
|
-
</div>
|
|
643
|
-
<CardDescription>What the agent does for you</CardDescription>
|
|
644
|
-
</CardHeader>
|
|
645
|
-
<CardContent>
|
|
646
|
-
<div className="grid grid-cols-3 gap-4 mb-4">
|
|
647
|
-
<Mini label="Total Calls" value={t.totalEvents} />
|
|
648
|
-
<Mini label="Top Tool" value={topTool?.tool || "-"} color="text-blue-500" />
|
|
649
|
-
<Mini label="Tools Used" value={data.toolUsage.length} color="text-purple-500" />
|
|
650
|
-
</div>
|
|
651
|
-
<div className="space-y-2">
|
|
652
|
-
{data.toolUsage.slice(0, 8).map((tool, i) => {
|
|
653
|
-
const pct = Math.round(100 * tool.count / t.totalEvents);
|
|
654
|
-
return (
|
|
655
|
-
<div key={i}>
|
|
656
|
-
<div className="flex justify-between text-xs mb-0.5">
|
|
657
|
-
<span className="font-medium">{tool.tool}</span>
|
|
658
|
-
<span className="text-muted-foreground tabular-nums">{tool.count} <span className="text-muted-foreground/50">({pct}%)</span></span>
|
|
659
|
-
</div>
|
|
660
|
-
<div className="h-1.5 bg-secondary rounded-full overflow-hidden">
|
|
661
|
-
<div className="h-full rounded-full transition-all" style={{ width: `${pct}%`, background: COLORS[i % COLORS.length] }} />
|
|
662
|
-
</div>
|
|
663
|
-
</div>
|
|
664
|
-
);
|
|
665
|
-
})}
|
|
666
|
-
</div>
|
|
667
|
-
</CardContent>
|
|
668
|
-
</Card>
|
|
669
|
-
|
|
670
|
-
{/* ── MCP Tools ── */}
|
|
671
|
-
<Card>
|
|
672
|
-
<CardHeader>
|
|
673
|
-
<div className="flex items-center gap-2">
|
|
674
|
-
<Code className="h-4 w-4 text-purple-500" />
|
|
675
|
-
<CardTitle className="text-sm">context-mode Tools</CardTitle>
|
|
676
|
-
</div>
|
|
677
|
-
<CardDescription>How you use the sandbox</CardDescription>
|
|
678
|
-
</CardHeader>
|
|
679
|
-
<CardContent>
|
|
680
|
-
{data.mcpTools.length > 0 ? (
|
|
681
|
-
<>
|
|
682
|
-
<div className="grid grid-cols-3 gap-4 mb-4">
|
|
683
|
-
<Mini label="MCP Calls" value={data.mcpTools.reduce((a, b) => a + b.count, 0)} />
|
|
684
|
-
<Mini label="Top Tool" value={topMcp?.tool || "-"} color="text-purple-500" />
|
|
685
|
-
<Mini label="Tools Used" value={data.mcpTools.length} color="text-cyan-500" />
|
|
686
|
-
</div>
|
|
687
|
-
<RatioBar items={data.mcpTools.slice(0, 6).map((m, i) => ({
|
|
688
|
-
label: m.tool, value: m.count, color: COLORS[i % COLORS.length],
|
|
689
|
-
}))} />
|
|
690
|
-
<div className="mt-4 space-y-1.5">
|
|
691
|
-
{data.mcpTools.slice(0, 6).map((m, i) => (
|
|
692
|
-
<div key={i} className="flex items-center justify-between">
|
|
693
|
-
<span className="text-xs font-mono">{m.tool}</span>
|
|
694
|
-
<Badge variant="outline" className="text-[10px] tabular-nums">{m.count}</Badge>
|
|
695
|
-
</div>
|
|
696
|
-
))}
|
|
697
|
-
</div>
|
|
698
|
-
</>
|
|
699
|
-
) : <p className="text-sm text-muted-foreground text-center py-12">No MCP data yet</p>}
|
|
700
|
-
</CardContent>
|
|
701
|
-
</Card>
|
|
702
|
-
</div>
|
|
703
|
-
|
|
704
|
-
{/* ── Session Activity + When You Code ── */}
|
|
705
|
-
<div className="grid gap-4 lg:grid-cols-2">
|
|
706
|
-
<Card>
|
|
707
|
-
<CardHeader>
|
|
708
|
-
<div className="flex items-center gap-2">
|
|
709
|
-
<Zap className="h-4 w-4 text-blue-500" />
|
|
710
|
-
<CardTitle className="text-sm">Session Activity</CardTitle>
|
|
711
|
-
</div>
|
|
712
|
-
<CardDescription>Your AI usage over time</CardDescription>
|
|
713
|
-
</CardHeader>
|
|
714
|
-
<CardContent>
|
|
715
|
-
<div className="grid grid-cols-3 gap-4 mb-4">
|
|
716
|
-
<Mini label="Total" value={t.totalSessions} />
|
|
717
|
-
<Mini label="Avg Duration" value={`${t.avgSessionMin}m`} color="text-blue-500" />
|
|
718
|
-
<Mini label="Active Days" value={data.sessionsByDate.length} color="text-emerald-500" />
|
|
719
|
-
</div>
|
|
720
|
-
{data.sessionsByDate.length > 0 && (
|
|
721
|
-
<div className="space-y-1.5 pt-2 border-t border-border">
|
|
722
|
-
{data.sessionsByDate.slice(-7).map((d, i) => (
|
|
723
|
-
<div key={i} className="flex items-center gap-2">
|
|
724
|
-
<span className="text-[10px] text-muted-foreground tabular-nums min-w-[60px]">{d.date?.slice(5)}</span>
|
|
725
|
-
<div className="flex-1 flex gap-0.5">
|
|
726
|
-
{Array.from({ length: d.count }).map((_, j) => (
|
|
727
|
-
<div key={j} className="w-5 h-5 rounded-sm bg-blue-500/80" />
|
|
728
|
-
))}
|
|
729
|
-
{d.compacts > 0 && Array.from({ length: d.compacts }).map((_, j) => (
|
|
730
|
-
<div key={`c${j}`} className="w-5 h-5 rounded-sm bg-amber-500/60" />
|
|
731
|
-
))}
|
|
732
|
-
</div>
|
|
733
|
-
<span className="text-[10px] text-muted-foreground tabular-nums">
|
|
734
|
-
{d.count}s{d.compacts > 0 ? ` ${d.compacts}c` : ""}
|
|
735
|
-
</span>
|
|
736
|
-
</div>
|
|
737
|
-
))}
|
|
738
|
-
<div className="flex gap-4 mt-2">
|
|
739
|
-
<span className="text-[10px] text-muted-foreground flex items-center gap-1">
|
|
740
|
-
<span className="w-2 h-2 rounded-sm bg-blue-500/80" /> Sessions
|
|
741
|
-
</span>
|
|
742
|
-
<span className="text-[10px] text-muted-foreground flex items-center gap-1">
|
|
743
|
-
<span className="w-2 h-2 rounded-sm bg-amber-500/60" /> Compactions
|
|
744
|
-
</span>
|
|
745
|
-
</div>
|
|
746
|
-
</div>
|
|
747
|
-
)}
|
|
748
|
-
</CardContent>
|
|
749
|
-
</Card>
|
|
750
|
-
|
|
751
|
-
{/* ── When You Code ── */}
|
|
752
|
-
<Card>
|
|
753
|
-
<CardHeader>
|
|
754
|
-
<div className="flex items-center gap-2">
|
|
755
|
-
<Clock className="h-4 w-4 text-cyan-500" />
|
|
756
|
-
<CardTitle className="text-sm">When You Code</CardTitle>
|
|
757
|
-
</div>
|
|
758
|
-
<CardDescription>Schedule deep work at your peak hours</CardDescription>
|
|
759
|
-
</CardHeader>
|
|
760
|
-
<CardContent>
|
|
761
|
-
<div className="grid grid-cols-3 gap-4 mb-4">
|
|
762
|
-
<Mini label="Peak Hour" value={peakHour ? `${String(peakHour.hour).padStart(2, "0")}:00` : "-"} color="text-cyan-500" />
|
|
763
|
-
<Mini label="Peak Events" value={peakHour?.count || 0} />
|
|
764
|
-
<Mini label="Active Hours" value={data.hourlyPattern.filter(h => h.count > 0).length} />
|
|
765
|
-
</div>
|
|
766
|
-
<div className="pt-2 border-t border-border">
|
|
767
|
-
<div className="grid grid-cols-12 gap-1">
|
|
768
|
-
{Array.from({ length: 24 }, (_, i) => {
|
|
769
|
-
const h = data.hourlyPattern.find(p => p.hour === i);
|
|
770
|
-
const count = h?.count || 0;
|
|
771
|
-
const max = peakHour?.count || 1;
|
|
772
|
-
const opacity = count > 0 ? 0.2 + 0.8 * (count / max) : 0.05;
|
|
773
|
-
return (
|
|
774
|
-
<div key={i} className="flex flex-col items-center gap-0.5">
|
|
775
|
-
<div
|
|
776
|
-
className="w-full aspect-square rounded-sm transition-all"
|
|
777
|
-
style={{ background: count > 0 ? `rgba(6, 182, 212, ${opacity})` : "hsl(var(--secondary))" }}
|
|
778
|
-
title={`${String(i).padStart(2, "0")}:00 — ${count} events`}
|
|
779
|
-
/>
|
|
780
|
-
{i % 4 === 0 && <span className="text-[8px] text-muted-foreground/50">{i}</span>}
|
|
781
|
-
</div>
|
|
782
|
-
);
|
|
783
|
-
})}
|
|
784
|
-
</div>
|
|
785
|
-
<div className="flex justify-between mt-2">
|
|
786
|
-
<span className="text-[9px] text-muted-foreground">00:00</span>
|
|
787
|
-
<span className="text-[9px] text-muted-foreground">12:00</span>
|
|
788
|
-
<span className="text-[9px] text-muted-foreground">23:00</span>
|
|
789
|
-
</div>
|
|
790
|
-
</div>
|
|
791
|
-
</CardContent>
|
|
792
|
-
</Card>
|
|
793
|
-
</div>
|
|
794
|
-
|
|
795
|
-
{/* ── Project Focus + Hot Files ── */}
|
|
796
|
-
<div className="grid gap-4 lg:grid-cols-2">
|
|
797
|
-
<Card>
|
|
798
|
-
<CardHeader>
|
|
799
|
-
<div className="flex items-center gap-2">
|
|
800
|
-
<FolderOpen className="h-4 w-4 text-emerald-500" />
|
|
801
|
-
<CardTitle className="text-sm">Project Focus</CardTitle>
|
|
802
|
-
</div>
|
|
803
|
-
<CardDescription>Where your AI time goes</CardDescription>
|
|
804
|
-
</CardHeader>
|
|
805
|
-
<CardContent>
|
|
806
|
-
<div className="grid grid-cols-2 gap-4 mb-4">
|
|
807
|
-
<Mini label="Projects" value={t.uniqueProjects} />
|
|
808
|
-
<Mini label="Top Project" value={topProject?.project_dir === "__unknown__" ? "Unknown" : topProject?.project_dir?.split("/").pop() || "-"} color="text-emerald-500" />
|
|
809
|
-
</div>
|
|
810
|
-
{data.attribution?.isFallbackOnly && (
|
|
811
|
-
<div className="mb-3 px-3 py-2 rounded-md bg-muted/50 border border-border text-xs text-muted-foreground flex items-center gap-1.5">
|
|
812
|
-
<Lightbulb className="h-3 w-3 shrink-0" />
|
|
813
|
-
Some project times are estimated
|
|
814
|
-
</div>
|
|
815
|
-
)}
|
|
816
|
-
<div className="space-y-2.5 pt-2 border-t border-border">
|
|
817
|
-
{data.projectActivity.slice(0, 6).map((p, i) => {
|
|
818
|
-
const maxEv = data.projectActivity[0]?.events || 1;
|
|
819
|
-
const pct = Math.round((p.events / maxEv) * 100);
|
|
820
|
-
const name = p.project_dir === "__unknown__" ? "Unknown" : p.project_dir?.split("/").filter(Boolean).slice(-2).join("/") || "Unknown";
|
|
821
|
-
return (
|
|
822
|
-
<div key={i}>
|
|
823
|
-
<div className="flex justify-between text-xs mb-1">
|
|
824
|
-
<span className="font-mono truncate max-w-[200px]">{name}</span>
|
|
825
|
-
<span className="text-muted-foreground tabular-nums">
|
|
826
|
-
{p.sessions} sessions · {p.events} events
|
|
827
|
-
</span>
|
|
828
|
-
</div>
|
|
829
|
-
<div className="h-1.5 bg-secondary rounded-full overflow-hidden">
|
|
830
|
-
<div className="h-full rounded-full transition-all" style={{ width: `${pct}%`, background: COLORS[i % COLORS.length] }} />
|
|
831
|
-
</div>
|
|
832
|
-
</div>
|
|
833
|
-
);
|
|
834
|
-
})}
|
|
835
|
-
</div>
|
|
836
|
-
</CardContent>
|
|
837
|
-
</Card>
|
|
838
|
-
|
|
839
|
-
<Card>
|
|
840
|
-
<CardHeader>
|
|
841
|
-
<div className="flex items-center gap-2">
|
|
842
|
-
<FileCode className="h-4 w-4 text-amber-500" />
|
|
843
|
-
<CardTitle className="text-sm">Hot Files</CardTitle>
|
|
844
|
-
</div>
|
|
845
|
-
<CardDescription>Most interacted — candidates for better tooling</CardDescription>
|
|
846
|
-
</CardHeader>
|
|
847
|
-
<CardContent>
|
|
848
|
-
<div className="grid grid-cols-3 gap-4 mb-4">
|
|
849
|
-
<Mini label="Files" value={data.fileActivity.length} />
|
|
850
|
-
<Mini label="Top File" value={topFile?.file?.split("/").pop() || "-"} color="text-amber-500" />
|
|
851
|
-
<Mini label="Top Hits" value={topFile?.count || 0} />
|
|
852
|
-
</div>
|
|
853
|
-
<div className="space-y-1 pt-2 border-t border-border">
|
|
854
|
-
{data.fileActivity.slice(0, 8).map((f, i) => {
|
|
855
|
-
const parts = f.file?.split("/") || [];
|
|
856
|
-
const name = parts.pop() || f.file;
|
|
857
|
-
const dir = parts.slice(-2).join("/");
|
|
858
|
-
return (
|
|
859
|
-
<div key={i} className="flex items-center gap-2 py-0.5">
|
|
860
|
-
<Badge variant="outline" className="text-[10px] min-w-[28px] justify-center tabular-nums">{f.count}</Badge>
|
|
861
|
-
<span className="text-xs font-mono truncate">
|
|
862
|
-
{dir && <span className="text-muted-foreground/60">{dir}/</span>}{name}
|
|
863
|
-
</span>
|
|
864
|
-
</div>
|
|
865
|
-
);
|
|
866
|
-
})}
|
|
867
|
-
</div>
|
|
868
|
-
</CardContent>
|
|
869
|
-
</Card>
|
|
870
|
-
</div>
|
|
871
|
-
|
|
872
|
-
{/* ── Explore/Execute + Work Modes ── */}
|
|
873
|
-
<div className="grid gap-4 lg:grid-cols-2">
|
|
874
|
-
{data.exploreExecRatio.total > 0 && (
|
|
875
|
-
<Card>
|
|
876
|
-
<CardHeader>
|
|
877
|
-
<div className="flex items-center gap-2">
|
|
878
|
-
<Search className="h-4 w-4 text-blue-500" />
|
|
879
|
-
<CardTitle className="text-sm">Explore vs Execute</CardTitle>
|
|
880
|
-
</div>
|
|
881
|
-
<CardDescription>Reading code vs writing code — your work balance</CardDescription>
|
|
882
|
-
</CardHeader>
|
|
883
|
-
<CardContent>
|
|
884
|
-
{(() => {
|
|
885
|
-
const { explore, execute, total } = data.exploreExecRatio;
|
|
886
|
-
const ratio = execute > 0 ? (explore / execute).toFixed(1) : explore;
|
|
887
|
-
const explorePct = Math.round(100 * explore / Math.max(total, 1));
|
|
888
|
-
const executePct = 100 - explorePct;
|
|
889
|
-
return (
|
|
890
|
-
<>
|
|
891
|
-
<div className="grid grid-cols-3 gap-4 mb-4">
|
|
892
|
-
<Mini label="Explore" value={explore} color="text-blue-500" />
|
|
893
|
-
<Mini label="Execute" value={execute} color="text-emerald-500" />
|
|
894
|
-
<Mini label="Ratio" value={`${ratio}:1`} color={Number(ratio) > 6 ? "text-amber-500" : "text-foreground"} />
|
|
895
|
-
</div>
|
|
896
|
-
<RatioBar items={[
|
|
897
|
-
{ label: `Read/Glob/Grep (${explorePct}%)`, value: explore, color: "#3b82f6" },
|
|
898
|
-
{ label: `Write/Edit (${executePct}%)`, value: execute, color: "#10b981" },
|
|
899
|
-
]} />
|
|
900
|
-
</>
|
|
901
|
-
);
|
|
902
|
-
})()}
|
|
903
|
-
</CardContent>
|
|
904
|
-
</Card>
|
|
905
|
-
)}
|
|
906
|
-
|
|
907
|
-
{data.workModes.length > 0 && (
|
|
908
|
-
<Card>
|
|
909
|
-
<CardHeader>
|
|
910
|
-
<div className="flex items-center gap-2">
|
|
911
|
-
<Activity className="h-4 w-4 text-purple-500" />
|
|
912
|
-
<CardTitle className="text-sm">Work Modes</CardTitle>
|
|
913
|
-
</div>
|
|
914
|
-
<CardDescription>How you approach tasks — investigate, implement, review, explore</CardDescription>
|
|
915
|
-
</CardHeader>
|
|
916
|
-
<CardContent>
|
|
917
|
-
{(() => {
|
|
918
|
-
const total = data.workModes.reduce((a, b) => a + b.count, 0);
|
|
919
|
-
return (
|
|
920
|
-
<>
|
|
921
|
-
<div className="grid grid-cols-3 gap-4 mb-4">
|
|
922
|
-
<Mini label="Total Intents" value={total} />
|
|
923
|
-
<Mini label="Top Mode" value={data.workModes[0]?.mode || "-"} color="text-purple-500" />
|
|
924
|
-
<Mini label="Modes" value={data.workModes.length} />
|
|
925
|
-
</div>
|
|
926
|
-
<RatioBar items={data.workModes.map((m, i) => ({
|
|
927
|
-
label: m.mode, value: m.count, color: COLORS[i % COLORS.length],
|
|
928
|
-
}))} />
|
|
929
|
-
</>
|
|
930
|
-
);
|
|
931
|
-
})()}
|
|
932
|
-
</CardContent>
|
|
933
|
-
</Card>
|
|
934
|
-
)}
|
|
935
|
-
</div>
|
|
936
|
-
|
|
937
|
-
{/* ── Tool Mastery + Commit Rate ── */}
|
|
938
|
-
<div className="grid gap-4 lg:grid-cols-2">
|
|
939
|
-
{/* Tool Mastery Curve */}
|
|
940
|
-
<Card>
|
|
941
|
-
<CardHeader>
|
|
942
|
-
<div className="flex items-center gap-2">
|
|
943
|
-
<TrendingUp className="h-4 w-4 text-emerald-500" />
|
|
944
|
-
<CardTitle className="text-sm">Tool Mastery</CardTitle>
|
|
945
|
-
</div>
|
|
946
|
-
<CardDescription>Are you getting better over time?</CardDescription>
|
|
947
|
-
</CardHeader>
|
|
948
|
-
<CardContent>
|
|
949
|
-
{data.masteryTrend && data.masteryTrend.length > 0 ? (() => {
|
|
950
|
-
const last = data.masteryTrend[data.masteryTrend.length - 1];
|
|
951
|
-
const first = data.masteryTrend[0];
|
|
952
|
-
const improving = last.error_rate < first.error_rate;
|
|
953
|
-
return (
|
|
954
|
-
<>
|
|
955
|
-
<div className="grid grid-cols-3 gap-4 mb-4">
|
|
956
|
-
<Mini label="Weeks" value={data.masteryTrend.length} />
|
|
957
|
-
<Mini label="Latest" value={`${last.error_rate}%`} color={last.error_rate < 5 ? "text-emerald-500" : last.error_rate > 10 ? "text-amber-500" : ""} />
|
|
958
|
-
<Mini label="Trend" value={improving ? "\u2193" : "\u2191"} color={improving ? "text-emerald-500" : "text-red-500"} />
|
|
959
|
-
</div>
|
|
960
|
-
<div className="space-y-1.5 pt-2 border-t border-border">
|
|
961
|
-
{data.masteryTrend.slice(-6).map((w, i) => {
|
|
962
|
-
const maxRate = Math.max(...data.masteryTrend.map(m => m.error_rate), 1);
|
|
963
|
-
const pct = Math.round((w.error_rate / maxRate) * 100);
|
|
964
|
-
return (
|
|
965
|
-
<div key={i} className="flex items-center gap-2">
|
|
966
|
-
<span className="text-[10px] text-muted-foreground tabular-nums min-w-[50px]">{w.week?.slice(5)}</span>
|
|
967
|
-
<div className="flex-1 h-1.5 bg-secondary rounded-full overflow-hidden">
|
|
968
|
-
<div className="h-full rounded-full" style={{ width: `${Math.max(pct, 3)}%`, background: w.error_rate < 5 ? "#10b981" : w.error_rate > 10 ? "#f59e0b" : "#3b82f6" }} />
|
|
969
|
-
</div>
|
|
970
|
-
<span className="text-[10px] text-muted-foreground tabular-nums">{w.error_rate}%</span>
|
|
971
|
-
</div>
|
|
972
|
-
);
|
|
973
|
-
})}
|
|
974
|
-
</div>
|
|
975
|
-
<p className="text-xs text-muted-foreground mt-3 leading-relaxed">
|
|
976
|
-
{last.error_rate === 0 && first.error_rate <= 1
|
|
977
|
-
? "Near-zero error rate across all weeks — you're writing precise, clean prompts."
|
|
978
|
-
: improving
|
|
979
|
-
? `Error rate dropped from ${first.error_rate}% to ${last.error_rate}% — your skills are improving.`
|
|
980
|
-
: `Error rate went from ${first.error_rate}% to ${last.error_rate}%. Check what changed — new tools, different project, or prompt drift?`}
|
|
981
|
-
</p>
|
|
982
|
-
</>
|
|
983
|
-
);
|
|
984
|
-
})() : <p className="text-sm text-muted-foreground text-center py-12">Not enough data yet</p>}
|
|
985
|
-
</CardContent>
|
|
986
|
-
</Card>
|
|
987
|
-
|
|
988
|
-
{/* Commit Rate */}
|
|
989
|
-
<Card>
|
|
990
|
-
<CardHeader>
|
|
991
|
-
<div className="flex items-center gap-2">
|
|
992
|
-
<GitBranch className="h-4 w-4 text-emerald-500" />
|
|
993
|
-
<CardTitle className="text-sm">Commit Rate</CardTitle>
|
|
994
|
-
</div>
|
|
995
|
-
<CardDescription>How productive are your sessions?</CardDescription>
|
|
996
|
-
</CardHeader>
|
|
997
|
-
<CardContent>
|
|
998
|
-
{(() => {
|
|
999
|
-
const commits = t.totalCommits || 0;
|
|
1000
|
-
const perSession = t.commitsPerSession || 0;
|
|
1001
|
-
const sessionsWithCommit = data.commitRate ? data.commitRate.filter(c => c.commits > 0).length : 0;
|
|
1002
|
-
return (
|
|
1003
|
-
<>
|
|
1004
|
-
<div className="grid grid-cols-3 gap-4 mb-3">
|
|
1005
|
-
<Mini label="Commits" value={commits} />
|
|
1006
|
-
<Mini label="Per Session" value={perSession} color={perSession >= 1 ? "text-emerald-500" : "text-muted-foreground"} />
|
|
1007
|
-
<Mini label="Sessions w/ Commit" value={sessionsWithCommit} color="text-blue-500" />
|
|
1008
|
-
</div>
|
|
1009
|
-
<div className="pt-2 border-t border-border">
|
|
1010
|
-
<p className="text-xs text-muted-foreground leading-relaxed">
|
|
1011
|
-
{perSession >= 1
|
|
1012
|
-
? "Strong output — you're committing consistently every session."
|
|
1013
|
-
: perSession > 0
|
|
1014
|
-
? `${commits} commit in ${t.totalSessions} sessions. Most sessions are research/exploration — commits come in focused bursts.`
|
|
1015
|
-
: "No commits yet. That's fine if you're in exploration or debugging mode — commits will come when you ship."}
|
|
1016
|
-
</p>
|
|
1017
|
-
</div>
|
|
1018
|
-
</>
|
|
1019
|
-
);
|
|
1020
|
-
})()}
|
|
1021
|
-
</CardContent>
|
|
1022
|
-
</Card>
|
|
1023
|
-
|
|
1024
|
-
</div>
|
|
1025
|
-
|
|
1026
|
-
{/* ── Rules Health + Edit-Test Cycles ── */}
|
|
1027
|
-
<div className="grid gap-4 lg:grid-cols-2">
|
|
1028
|
-
{/* CLAUDE.md Health */}
|
|
1029
|
-
<Card>
|
|
1030
|
-
<CardHeader>
|
|
1031
|
-
<div className="flex items-center gap-2">
|
|
1032
|
-
<FileCode className="h-4 w-4 text-amber-500" />
|
|
1033
|
-
<CardTitle className="text-sm">Rules Health</CardTitle>
|
|
1034
|
-
</div>
|
|
1035
|
-
<CardDescription>Are your instruction files maintained?</CardDescription>
|
|
1036
|
-
</CardHeader>
|
|
1037
|
-
<CardContent>
|
|
1038
|
-
{data.rulesFreshness && data.rulesFreshness.length > 0 ? (() => {
|
|
1039
|
-
const top = data.rulesFreshness[0];
|
|
1040
|
-
const topName = top.rule_path?.split("/").pop() || top.rule_path;
|
|
1041
|
-
return (
|
|
1042
|
-
<>
|
|
1043
|
-
<div className="grid grid-cols-3 gap-4 mb-4">
|
|
1044
|
-
<Mini label="Rules" value={t.totalRules || data.rulesFreshness.length} />
|
|
1045
|
-
<Mini label="Most Loaded" value={topName} color="text-amber-500" />
|
|
1046
|
-
<Mini label="Loads" value={top.load_count} />
|
|
1047
|
-
</div>
|
|
1048
|
-
<div className="space-y-1.5 pt-2 border-t border-border">
|
|
1049
|
-
{data.rulesFreshness.slice(0, 6).map((r, i) => {
|
|
1050
|
-
const name = r.rule_path?.split("/").pop() || r.rule_path;
|
|
1051
|
-
const lastSeen = r.last_seen ? (() => {
|
|
1052
|
-
const diff = Date.now() - new Date(r.last_seen).getTime();
|
|
1053
|
-
const days = Math.floor(diff / 86400000);
|
|
1054
|
-
return days === 0 ? "today" : days === 1 ? "1d ago" : `${days}d ago`;
|
|
1055
|
-
})() : "unknown";
|
|
1056
|
-
return (
|
|
1057
|
-
<div key={i} className="flex items-center justify-between py-0.5">
|
|
1058
|
-
<span className="text-xs font-mono truncate max-w-[200px]">{name}</span>
|
|
1059
|
-
<div className="flex items-center gap-2">
|
|
1060
|
-
<span className="text-[10px] text-muted-foreground">{lastSeen}</span>
|
|
1061
|
-
<Badge variant="outline" className="text-[10px] tabular-nums">{r.load_count}</Badge>
|
|
1062
|
-
</div>
|
|
1063
|
-
</div>
|
|
1064
|
-
);
|
|
1065
|
-
})}
|
|
1066
|
-
</div>
|
|
1067
|
-
</>
|
|
1068
|
-
);
|
|
1069
|
-
})() : <p className="text-sm text-muted-foreground text-center py-12">No rules data yet</p>}
|
|
1070
|
-
</CardContent>
|
|
1071
|
-
</Card>
|
|
1072
|
-
|
|
1073
|
-
{/* Edit-Test Cycles */}
|
|
1074
|
-
<Card>
|
|
1075
|
-
<CardHeader>
|
|
1076
|
-
<div className="flex items-center gap-2">
|
|
1077
|
-
<Activity className="h-4 w-4 text-amber-500" />
|
|
1078
|
-
<CardTitle className="text-sm">Edit → Error Cycles</CardTitle>
|
|
1079
|
-
</div>
|
|
1080
|
-
<CardDescription>Write → fail → fix again loops</CardDescription>
|
|
1081
|
-
</CardHeader>
|
|
1082
|
-
<CardContent>
|
|
1083
|
-
{(() => {
|
|
1084
|
-
const cycles = t.totalEditTestCycles || 0;
|
|
1085
|
-
const perSession = t.totalSessions > 0 ? (cycles / t.totalSessions).toFixed(1) : "0";
|
|
1086
|
-
const sessionsHit = data.editTestCycles ? data.editTestCycles.length : 0;
|
|
1087
|
-
|
|
1088
|
-
if (cycles === 0) {
|
|
1089
|
-
return (
|
|
1090
|
-
<div className="pt-2">
|
|
1091
|
-
<div className="flex items-center gap-2 mb-3">
|
|
1092
|
-
<CheckCircle className="h-5 w-5 text-emerald-500" />
|
|
1093
|
-
<span className="text-sm font-semibold">Zero retry loops</span>
|
|
1094
|
-
</div>
|
|
1095
|
-
<p className="text-xs text-muted-foreground leading-relaxed">
|
|
1096
|
-
No write→error→rewrite patterns detected. Your code works on the first try — clean prompting and clear instructions pay off.
|
|
1097
|
-
</p>
|
|
1098
|
-
</div>
|
|
1099
|
-
);
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
return (
|
|
1103
|
-
<>
|
|
1104
|
-
<div className="grid grid-cols-3 gap-4 mb-4">
|
|
1105
|
-
<Mini label="Total Cycles" value={cycles} />
|
|
1106
|
-
<Mini label="Per Session" value={perSession} color={Number(perSession) > 3 ? "text-amber-500" : "text-emerald-500"} />
|
|
1107
|
-
<Mini label="Sessions Hit" value={sessionsHit} />
|
|
1108
|
-
</div>
|
|
1109
|
-
<div className="pt-2 border-t border-border">
|
|
1110
|
-
<p className="text-xs text-muted-foreground leading-relaxed">
|
|
1111
|
-
{Number(perSession) > 3
|
|
1112
|
-
? "High retry rate — consider writing tests first or adding patterns to CLAUDE.md to prevent common errors."
|
|
1113
|
-
: `${cycles} retry loops across ${sessionsHit} sessions. Manageable — keep an eye on which files trigger retries.`}
|
|
1114
|
-
</p>
|
|
1115
|
-
</div>
|
|
1116
|
-
</>
|
|
1117
|
-
);
|
|
1118
|
-
})()}
|
|
1119
|
-
</CardContent>
|
|
1120
|
-
</Card>
|
|
1121
|
-
</div>
|
|
1122
|
-
|
|
1123
|
-
{/* ── Git Flow + Parallel Work ── */}
|
|
1124
|
-
<div className="grid gap-4 lg:grid-cols-2">
|
|
1125
|
-
{data.gitActivity.length > 0 && (
|
|
1126
|
-
<Card>
|
|
1127
|
-
<CardHeader>
|
|
1128
|
-
<div className="flex items-center gap-2">
|
|
1129
|
-
<GitBranch className="h-4 w-4 text-emerald-500" />
|
|
1130
|
-
<CardTitle className="text-sm">Git Flow</CardTitle>
|
|
1131
|
-
</div>
|
|
1132
|
-
<CardDescription>Your version control pattern per session</CardDescription>
|
|
1133
|
-
</CardHeader>
|
|
1134
|
-
<CardContent>
|
|
1135
|
-
{(() => {
|
|
1136
|
-
const commits = data.gitActivity.filter(g => g.action === "commit").length;
|
|
1137
|
-
const pushes = data.gitActivity.filter(g => g.action === "push").length;
|
|
1138
|
-
// Group by session
|
|
1139
|
-
const sessions = new Map<string, { project: string; actions: string[]; time: string }>();
|
|
1140
|
-
data.gitActivity.forEach(g => {
|
|
1141
|
-
if (!sessions.has(g.session_id)) {
|
|
1142
|
-
sessions.set(g.session_id, {
|
|
1143
|
-
project: g.project_dir === "__unknown__" ? "Unknown" : g.project_dir?.split("/").filter(Boolean).slice(-2).join("/") || "-",
|
|
1144
|
-
actions: [],
|
|
1145
|
-
time: g.created_at,
|
|
1146
|
-
});
|
|
1147
|
-
}
|
|
1148
|
-
sessions.get(g.session_id)!.actions.push(g.action);
|
|
1149
|
-
});
|
|
1150
|
-
return (
|
|
1151
|
-
<>
|
|
1152
|
-
<div className="grid grid-cols-3 gap-4 mb-4">
|
|
1153
|
-
<Mini label="Git Ops" value={data.gitActivity.length} />
|
|
1154
|
-
<Mini label="Commits" value={commits} color="text-emerald-500" />
|
|
1155
|
-
<Mini label="Pushes" value={pushes} color="text-blue-500" />
|
|
1156
|
-
</div>
|
|
1157
|
-
<div className="space-y-2.5 pt-2 border-t border-border">
|
|
1158
|
-
{[...sessions.entries()].slice(0, 5).map(([sid, s]) => (
|
|
1159
|
-
<div key={sid}>
|
|
1160
|
-
<div className="flex justify-between text-[10px] mb-1">
|
|
1161
|
-
<span className="font-mono text-muted-foreground">{s.project}</span>
|
|
1162
|
-
<span className="text-muted-foreground">{s.time?.slice(5, 16)}</span>
|
|
1163
|
-
</div>
|
|
1164
|
-
<div className="flex gap-1">
|
|
1165
|
-
{s.actions.map((a, i) => (
|
|
1166
|
-
<Badge key={i} variant={a === "commit" || a === "push" ? "default" : "secondary"} className="text-[9px]">{a}</Badge>
|
|
1167
|
-
))}
|
|
1168
|
-
</div>
|
|
1169
|
-
</div>
|
|
1170
|
-
))}
|
|
1171
|
-
</div>
|
|
1172
|
-
</>
|
|
1173
|
-
);
|
|
1174
|
-
})()}
|
|
1175
|
-
</CardContent>
|
|
1176
|
-
</Card>
|
|
1177
|
-
)}
|
|
1178
|
-
|
|
1179
|
-
{data.subagents.total > 0 && (
|
|
1180
|
-
<Card>
|
|
1181
|
-
<CardHeader>
|
|
1182
|
-
<div className="flex items-center gap-2">
|
|
1183
|
-
<Cpu className="h-4 w-4 text-purple-500" />
|
|
1184
|
-
<CardTitle className="text-sm">Parallel Work</CardTitle>
|
|
1185
|
-
</div>
|
|
1186
|
-
<CardDescription>How effectively you delegate to subagents</CardDescription>
|
|
1187
|
-
</CardHeader>
|
|
1188
|
-
<CardContent>
|
|
1189
|
-
<div className="grid grid-cols-3 gap-4 mb-4">
|
|
1190
|
-
<Mini label="Delegated" value={data.subagents.total} />
|
|
1191
|
-
<Mini label="Max Parallel" value={data.subagents.maxConcurrent} color="text-purple-500" />
|
|
1192
|
-
<Mini label="Time Saved" value={`~${data.subagents.timeSavedMin}m`} color="text-emerald-500" />
|
|
1193
|
-
</div>
|
|
1194
|
-
<RatioBar items={[
|
|
1195
|
-
{ label: "Parallel", value: data.subagents.parallelCount, color: "#8b5cf6" },
|
|
1196
|
-
{ label: "Sequential", value: data.subagents.sequentialCount, color: "hsl(var(--muted))" },
|
|
1197
|
-
]} />
|
|
1198
|
-
{data.subagents.burstDetails.length > 0 && (
|
|
1199
|
-
<div className="space-y-1.5 pt-3 mt-3 border-t border-border">
|
|
1200
|
-
<p className="text-[10px] text-muted-foreground uppercase tracking-wider font-semibold mb-1">Parallel Bursts</p>
|
|
1201
|
-
{data.subagents.burstDetails.map((b: { size: number; time: string }, i: number) => (
|
|
1202
|
-
<div key={i} className="flex items-center gap-2">
|
|
1203
|
-
<span className="text-[10px] text-muted-foreground tabular-nums">{b.time?.slice(5, 16)}</span>
|
|
1204
|
-
<div className="flex gap-0.5">
|
|
1205
|
-
{Array.from({ length: b.size }).map((_, j) => (
|
|
1206
|
-
<div key={j} className="w-3 h-3 rounded-sm bg-purple-500/80" />
|
|
1207
|
-
))}
|
|
1208
|
-
</div>
|
|
1209
|
-
<span className="text-[10px] text-muted-foreground">{b.size} agents</span>
|
|
1210
|
-
</div>
|
|
1211
|
-
))}
|
|
1212
|
-
</div>
|
|
1213
|
-
)}
|
|
1214
|
-
</CardContent>
|
|
1215
|
-
</Card>
|
|
1216
|
-
)}
|
|
1217
|
-
</div>
|
|
1218
|
-
|
|
1219
|
-
{catData && !catData.insufficientData && (
|
|
1220
|
-
<>
|
|
1221
|
-
<Separator />
|
|
1222
|
-
|
|
1223
|
-
{/* ── Category Intelligence ── */}
|
|
1224
|
-
<div>
|
|
1225
|
-
<div className="flex items-center gap-2 mb-3">
|
|
1226
|
-
<Cpu className="h-4 w-4 text-purple-500" />
|
|
1227
|
-
<h3 className="text-sm font-semibold uppercase tracking-wide text-muted-foreground">Session Intelligence</h3>
|
|
1228
|
-
</div>
|
|
1229
|
-
|
|
1230
|
-
{/* Composite Scores */}
|
|
1231
|
-
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3 mb-4">
|
|
1232
|
-
<Card className={catData.compositeScores.productivity >= 70 ? "border-emerald-500/30" : catData.compositeScores.productivity < 40 ? "border-red-500/30" : "border-amber-500/30"}>
|
|
1233
|
-
<CardContent className="pt-4">
|
|
1234
|
-
<div className="text-center">
|
|
1235
|
-
<div className="text-3xl font-bold tabular-nums">{catData.compositeScores.productivity}</div>
|
|
1236
|
-
<p className="text-[10px] text-muted-foreground uppercase tracking-wider mt-1">Productivity</p>
|
|
1237
|
-
</div>
|
|
1238
|
-
</CardContent>
|
|
1239
|
-
</Card>
|
|
1240
|
-
<Card className={catData.compositeScores.quality >= 70 ? "border-emerald-500/30" : catData.compositeScores.quality < 40 ? "border-red-500/30" : "border-amber-500/30"}>
|
|
1241
|
-
<CardContent className="pt-4">
|
|
1242
|
-
<div className="text-center">
|
|
1243
|
-
<div className="text-3xl font-bold tabular-nums">{catData.compositeScores.quality}</div>
|
|
1244
|
-
<p className="text-[10px] text-muted-foreground uppercase tracking-wider mt-1">Quality</p>
|
|
1245
|
-
</div>
|
|
1246
|
-
</CardContent>
|
|
1247
|
-
</Card>
|
|
1248
|
-
<Card className={catData.compositeScores.delegation >= 70 ? "border-emerald-500/30" : catData.compositeScores.delegation < 40 ? "border-red-500/30" : "border-amber-500/30"}>
|
|
1249
|
-
<CardContent className="pt-4">
|
|
1250
|
-
<div className="text-center">
|
|
1251
|
-
<div className="text-3xl font-bold tabular-nums">{catData.compositeScores.delegation}</div>
|
|
1252
|
-
<p className="text-[10px] text-muted-foreground uppercase tracking-wider mt-1">Delegation</p>
|
|
1253
|
-
</div>
|
|
1254
|
-
</CardContent>
|
|
1255
|
-
</Card>
|
|
1256
|
-
<Card className={catData.compositeScores.contextHealth >= 70 ? "border-emerald-500/30" : catData.compositeScores.contextHealth < 40 ? "border-red-500/30" : "border-amber-500/30"}>
|
|
1257
|
-
<CardContent className="pt-4">
|
|
1258
|
-
<div className="text-center">
|
|
1259
|
-
<div className="text-3xl font-bold tabular-nums">{catData.compositeScores.contextHealth}</div>
|
|
1260
|
-
<p className="text-[10px] text-muted-foreground uppercase tracking-wider mt-1">Context Health</p>
|
|
1261
|
-
</div>
|
|
1262
|
-
</CardContent>
|
|
1263
|
-
</Card>
|
|
1264
|
-
</div>
|
|
1265
|
-
|
|
1266
|
-
{/* Category Distribution */}
|
|
1267
|
-
<div className="grid gap-4 lg:grid-cols-2">
|
|
1268
|
-
<Card>
|
|
1269
|
-
<CardHeader>
|
|
1270
|
-
<div className="flex items-center gap-2">
|
|
1271
|
-
<Activity className="h-4 w-4 text-blue-500" />
|
|
1272
|
-
<CardTitle className="text-sm">Event Categories</CardTitle>
|
|
1273
|
-
</div>
|
|
1274
|
-
<CardDescription>{catData.categories.reduce((a, b) => a + b.count, 0)} events across {catData.categories.filter(c => c.count > 0).length} categories</CardDescription>
|
|
1275
|
-
</CardHeader>
|
|
1276
|
-
<CardContent>
|
|
1277
|
-
<div className="space-y-2">
|
|
1278
|
-
{catData.categories.filter(c => c.count > 0).sort((a, b) => b.count - a.count).slice(0, 12).map((cat, i) => {
|
|
1279
|
-
const max = catData.categories.reduce((a, b) => Math.max(a, b.count), 0);
|
|
1280
|
-
return (
|
|
1281
|
-
<div key={cat.category} className="flex items-center gap-2">
|
|
1282
|
-
<span className="text-[11px] text-muted-foreground w-28 truncate">{cat.category}</span>
|
|
1283
|
-
<div className="flex-1 h-5 bg-muted/50 rounded-sm overflow-hidden">
|
|
1284
|
-
<div className="h-full rounded-sm" style={{ width: `${(cat.count / max) * 100}%`, backgroundColor: COLORS[i % COLORS.length] }} />
|
|
1285
|
-
</div>
|
|
1286
|
-
<span className="text-[11px] tabular-nums text-muted-foreground w-8 text-right">{cat.count}</span>
|
|
1287
|
-
</div>
|
|
1288
|
-
);
|
|
1289
|
-
})}
|
|
1290
|
-
</div>
|
|
1291
|
-
</CardContent>
|
|
1292
|
-
</Card>
|
|
1293
|
-
|
|
1294
|
-
{/* Error Intelligence */}
|
|
1295
|
-
<Card>
|
|
1296
|
-
<CardHeader>
|
|
1297
|
-
<div className="flex items-center gap-2">
|
|
1298
|
-
<Shield className="h-4 w-4 text-red-500" />
|
|
1299
|
-
<CardTitle className="text-sm">Error Intelligence</CardTitle>
|
|
1300
|
-
</div>
|
|
1301
|
-
<CardDescription>Resolution rate, retry storms, latency</CardDescription>
|
|
1302
|
-
</CardHeader>
|
|
1303
|
-
<CardContent>
|
|
1304
|
-
<div className="grid grid-cols-3 gap-4 mb-4">
|
|
1305
|
-
<Mini label="Errors" value={catData.errorIntelligence.totalErrors} />
|
|
1306
|
-
<Mini label="Resolved" value={`${catData.errorIntelligence.resolutionRate}%`} color={catData.errorIntelligence.resolutionRate > 70 ? "text-emerald-500" : "text-amber-500"} />
|
|
1307
|
-
<Mini label="Retry Storms" value={catData.errorIntelligence.retryStorms} color={catData.errorIntelligence.retryStorms > 0 ? "text-red-500" : ""} />
|
|
1308
|
-
</div>
|
|
1309
|
-
{catData.errorIntelligence.latencyByTool.length > 0 && (
|
|
1310
|
-
<div className="space-y-1.5">
|
|
1311
|
-
<p className="text-[10px] text-muted-foreground uppercase tracking-wider">Slowest Tools</p>
|
|
1312
|
-
{catData.errorIntelligence.latencyByTool.slice(0, 5).map((t) => (
|
|
1313
|
-
<div key={t.tool} className="flex items-center gap-2">
|
|
1314
|
-
<span className="text-[11px] text-muted-foreground w-20 truncate">{t.tool}</span>
|
|
1315
|
-
<div className="flex-1 h-4 bg-muted/50 rounded-sm overflow-hidden">
|
|
1316
|
-
<div className="h-full bg-amber-500/60 rounded-sm" style={{ width: `${Math.min((t.avg_ms / 30000) * 100, 100)}%` }} />
|
|
1317
|
-
</div>
|
|
1318
|
-
<span className="text-[11px] tabular-nums text-muted-foreground w-12 text-right">{(t.avg_ms / 1000).toFixed(1)}s</span>
|
|
1319
|
-
</div>
|
|
1320
|
-
))}
|
|
1321
|
-
</div>
|
|
1322
|
-
)}
|
|
1323
|
-
{catData.errorIntelligence.topErrorTools.length > 0 && (
|
|
1324
|
-
<div className="space-y-1.5 mt-3">
|
|
1325
|
-
<p className="text-[10px] text-muted-foreground uppercase tracking-wider">Top Error Sources</p>
|
|
1326
|
-
{catData.errorIntelligence.topErrorTools.slice(0, 5).map((t) => (
|
|
1327
|
-
<div key={t.tool} className="flex items-center justify-between">
|
|
1328
|
-
<span className="text-[11px] text-muted-foreground">{t.tool}</span>
|
|
1329
|
-
<Badge variant="secondary" className="text-[10px]">{t.count}</Badge>
|
|
1330
|
-
</div>
|
|
1331
|
-
))}
|
|
1332
|
-
</div>
|
|
1333
|
-
)}
|
|
1334
|
-
</CardContent>
|
|
1335
|
-
</Card>
|
|
1336
|
-
</div>
|
|
1337
|
-
</div>
|
|
1338
|
-
|
|
1339
|
-
<Separator />
|
|
1340
|
-
|
|
1341
|
-
{/* ── Governance + Delegation ── */}
|
|
1342
|
-
<div className="grid gap-4 lg:grid-cols-2">
|
|
1343
|
-
<Card>
|
|
1344
|
-
<CardHeader>
|
|
1345
|
-
<div className="flex items-center gap-2">
|
|
1346
|
-
<Shield className="h-4 w-4 text-purple-500" />
|
|
1347
|
-
<CardTitle className="text-sm">Governance</CardTitle>
|
|
1348
|
-
</div>
|
|
1349
|
-
<CardDescription>Decisions, rejections, plans, constraints</CardDescription>
|
|
1350
|
-
</CardHeader>
|
|
1351
|
-
<CardContent>
|
|
1352
|
-
<div className="grid grid-cols-2 gap-4 mb-4">
|
|
1353
|
-
<Mini label="Rejections" value={catData.governance.totalRejections} color={catData.governance.totalRejections > 20 ? "text-amber-500" : ""} />
|
|
1354
|
-
<Mini label="Decisions" value={catData.governance.totalDecisions} />
|
|
1355
|
-
<Mini label="Plans Approved" value={catData.governance.planApproved} />
|
|
1356
|
-
<Mini label="Constraints" value={catData.governance.totalConstraints} />
|
|
1357
|
-
</div>
|
|
1358
|
-
{catData.governance.topRejected.length > 0 && (
|
|
1359
|
-
<div className="space-y-1.5">
|
|
1360
|
-
<p className="text-[10px] text-muted-foreground uppercase tracking-wider">Top Rejected Tools</p>
|
|
1361
|
-
{catData.governance.topRejected.slice(0, 5).map((t) => (
|
|
1362
|
-
<div key={t.tool} className="flex items-center justify-between">
|
|
1363
|
-
<span className="text-[11px] text-muted-foreground">{t.tool}</span>
|
|
1364
|
-
<Badge variant="secondary" className="text-[10px]">{t.count}</Badge>
|
|
1365
|
-
</div>
|
|
1366
|
-
))}
|
|
1367
|
-
</div>
|
|
1368
|
-
)}
|
|
1369
|
-
</CardContent>
|
|
1370
|
-
</Card>
|
|
1371
|
-
|
|
1372
|
-
<Card>
|
|
1373
|
-
<CardHeader>
|
|
1374
|
-
<div className="flex items-center gap-2">
|
|
1375
|
-
<Cpu className="h-4 w-4 text-emerald-500" />
|
|
1376
|
-
<CardTitle className="text-sm">Delegation</CardTitle>
|
|
1377
|
-
</div>
|
|
1378
|
-
<CardDescription>Agent parallelism and completion</CardDescription>
|
|
1379
|
-
</CardHeader>
|
|
1380
|
-
<CardContent>
|
|
1381
|
-
<div className="grid grid-cols-3 gap-4 mb-4">
|
|
1382
|
-
<Mini label="Launched" value={catData.delegation.launched} />
|
|
1383
|
-
<Mini label="Completed" value={catData.delegation.completed} />
|
|
1384
|
-
<Mini label="Rate" value={`${catData.delegation.completionRate}%`} color={catData.delegation.completionRate > 70 ? "text-emerald-500" : "text-amber-500"} />
|
|
1385
|
-
</div>
|
|
1386
|
-
<div className="grid grid-cols-3 gap-4">
|
|
1387
|
-
<Mini label="Bursts" value={catData.delegation.parallelBursts} />
|
|
1388
|
-
<Mini label="Max ∥" value={catData.delegation.maxConcurrent} />
|
|
1389
|
-
<Mini label="Saved" value={`${catData.delegation.timeSavedMin}m`} color="text-emerald-500" />
|
|
1390
|
-
</div>
|
|
1391
|
-
</CardContent>
|
|
1392
|
-
</Card>
|
|
1393
|
-
</div>
|
|
1394
|
-
|
|
1395
|
-
<Separator />
|
|
1396
|
-
|
|
1397
|
-
{/* ── Git Productivity + Context Health ── */}
|
|
1398
|
-
<div className="grid gap-4 lg:grid-cols-2">
|
|
1399
|
-
<Card>
|
|
1400
|
-
<CardHeader>
|
|
1401
|
-
<div className="flex items-center gap-2">
|
|
1402
|
-
<GitBranch className="h-4 w-4 text-blue-500" />
|
|
1403
|
-
<CardTitle className="text-sm">Git Productivity</CardTitle>
|
|
1404
|
-
</div>
|
|
1405
|
-
<CardDescription>Commit patterns and operation mix</CardDescription>
|
|
1406
|
-
</CardHeader>
|
|
1407
|
-
<CardContent>
|
|
1408
|
-
<div className="grid grid-cols-3 gap-4 mb-4">
|
|
1409
|
-
<Mini label="Commits" value={catData.gitProductivity.totalCommits} />
|
|
1410
|
-
<Mini label="Pushes" value={catData.gitProductivity.totalPushes} />
|
|
1411
|
-
<Mini label="C:P Ratio" value={catData.gitProductivity.commitPushRatio > 0 ? `${catData.gitProductivity.commitPushRatio}:1` : "—"} />
|
|
1412
|
-
</div>
|
|
1413
|
-
{catData.gitProductivity.operationMix.length > 0 && (
|
|
1414
|
-
<div className="space-y-1.5">
|
|
1415
|
-
<p className="text-[10px] text-muted-foreground uppercase tracking-wider">Git Operations</p>
|
|
1416
|
-
{catData.gitProductivity.operationMix.slice(0, 8).map((op) => {
|
|
1417
|
-
const max = catData.gitProductivity.operationMix[0]?.count || 1;
|
|
1418
|
-
return (
|
|
1419
|
-
<div key={op.operation} className="flex items-center gap-2">
|
|
1420
|
-
<span className="text-[11px] text-muted-foreground w-16 truncate">{op.operation}</span>
|
|
1421
|
-
<div className="flex-1 h-4 bg-muted/50 rounded-sm overflow-hidden">
|
|
1422
|
-
<div className="h-full bg-blue-500/60 rounded-sm" style={{ width: `${(op.count / max) * 100}%` }} />
|
|
1423
|
-
</div>
|
|
1424
|
-
<span className="text-[11px] tabular-nums text-muted-foreground w-6 text-right">{op.count}</span>
|
|
1425
|
-
</div>
|
|
1426
|
-
);
|
|
1427
|
-
})}
|
|
1428
|
-
</div>
|
|
1429
|
-
)}
|
|
1430
|
-
</CardContent>
|
|
1431
|
-
</Card>
|
|
1432
|
-
|
|
1433
|
-
<Card>
|
|
1434
|
-
<CardHeader>
|
|
1435
|
-
<div className="flex items-center gap-2">
|
|
1436
|
-
<Brain className="h-4 w-4 text-cyan-500" />
|
|
1437
|
-
<CardTitle className="text-sm">Context Health</CardTitle>
|
|
1438
|
-
</div>
|
|
1439
|
-
<CardDescription>Rules, skills, work modes, blockers</CardDescription>
|
|
1440
|
-
</CardHeader>
|
|
1441
|
-
<CardContent>
|
|
1442
|
-
<div className="grid grid-cols-3 gap-4 mb-4">
|
|
1443
|
-
<Mini label="Rule Files" value={catData.contextHealth.uniqueRuleFiles} />
|
|
1444
|
-
<Mini label="Skills" value={catData.contextHealth.uniqueSkills} />
|
|
1445
|
-
<Mini label="Compact Rate" value={`${catData.contextHealth.compactRate}%`} color={catData.contextHealth.compactRate > 60 ? "text-amber-500" : "text-emerald-500"} />
|
|
1446
|
-
</div>
|
|
1447
|
-
{catData.contextHealth.modeDistribution.length > 0 && (
|
|
1448
|
-
<div className="mb-3">
|
|
1449
|
-
<p className="text-[10px] text-muted-foreground uppercase tracking-wider mb-1.5">Work Modes</p>
|
|
1450
|
-
<RatioBar items={catData.contextHealth.modeDistribution.map((m, i) => ({
|
|
1451
|
-
label: `${m.mode} (${m.pct}%)`,
|
|
1452
|
-
value: m.count,
|
|
1453
|
-
color: COLORS[i % COLORS.length],
|
|
1454
|
-
}))} />
|
|
1455
|
-
</div>
|
|
1456
|
-
)}
|
|
1457
|
-
{catData.contextHealth.skillList.length > 0 && (
|
|
1458
|
-
<div>
|
|
1459
|
-
<p className="text-[10px] text-muted-foreground uppercase tracking-wider mb-1.5">Active Skills</p>
|
|
1460
|
-
<div className="flex flex-wrap gap-1">
|
|
1461
|
-
{catData.contextHealth.skillList.map(s => (
|
|
1462
|
-
<Badge key={s} variant="secondary" className="text-[10px]">{s}</Badge>
|
|
1463
|
-
))}
|
|
1464
|
-
</div>
|
|
1465
|
-
</div>
|
|
1466
|
-
)}
|
|
1467
|
-
{catData.contextHealth.totalBlockers > 0 && (
|
|
1468
|
-
<div className="mt-3 grid grid-cols-2 gap-4">
|
|
1469
|
-
<Mini label="Blockers" value={catData.contextHealth.totalBlockers} color="text-amber-500" />
|
|
1470
|
-
<Mini label="Resolved" value={`${catData.contextHealth.blockerResolutionRate}%`} color={catData.contextHealth.blockerResolutionRate > 70 ? "text-emerald-500" : "text-red-500"} />
|
|
1471
|
-
</div>
|
|
1472
|
-
)}
|
|
1473
|
-
</CardContent>
|
|
1474
|
-
</Card>
|
|
1475
|
-
</div>
|
|
1476
|
-
</>
|
|
1477
|
-
)}
|
|
1478
|
-
|
|
1479
|
-
</div>
|
|
1480
|
-
</div>
|
|
1481
|
-
);
|
|
1482
|
-
}
|