agentfit 0.1.0 → 0.1.2
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/.github/workflows/release.yml +111 -0
- package/README.md +41 -38
- package/app/(dashboard)/daily/page.tsx +1 -1
- package/app/(dashboard)/data-management/page.tsx +180 -0
- package/app/(dashboard)/flow/page.tsx +17 -0
- package/app/(dashboard)/layout.tsx +2 -0
- package/app/(dashboard)/page.tsx +24 -5
- package/app/(dashboard)/reports/[id]/page.tsx +72 -0
- package/app/(dashboard)/reports/page.tsx +132 -0
- package/app/(dashboard)/sessions/[id]/page.tsx +167 -0
- package/app/api/backup/route.ts +215 -0
- package/app/api/check/route.ts +11 -1
- package/app/api/command-insights/route.ts +13 -0
- package/app/api/commands/route.ts +55 -1
- package/app/api/images-analysis/route.ts +3 -4
- package/app/api/reports/[id]/route.ts +23 -0
- package/app/api/reports/route.ts +50 -0
- package/app/api/reset/route.ts +21 -0
- package/app/api/session/route.ts +40 -0
- package/app/api/usage/route.ts +26 -1
- package/app/layout.tsx +1 -1
- package/bin/agentfit.mjs +2 -2
- package/components/agent-coach.tsx +256 -129
- package/components/app-sidebar.tsx +45 -10
- package/components/backup-section.tsx +236 -0
- package/components/daily-chart.tsx +447 -83
- package/components/dashboard-shell.tsx +29 -31
- package/components/data-provider.tsx +88 -8
- package/components/fitness-score.tsx +95 -54
- package/components/overview-cards.tsx +148 -41
- package/components/report-view.tsx +307 -0
- package/components/screenshots-analysis.tsx +51 -46
- package/components/session-chatlog.tsx +124 -0
- package/components/session-timeline.tsx +184 -0
- package/components/session-workflow.tsx +183 -0
- package/components/sessions-table.tsx +9 -1
- package/components/tool-flow-graph.tsx +144 -0
- package/components/ui/carousel.tsx +242 -0
- package/components/ui/sidebar.tsx +1 -1
- package/components/ui/sonner.tsx +51 -0
- package/electron/entitlements.mac.plist +16 -0
- package/electron/init-db.mjs +37 -0
- package/electron/main.mjs +203 -0
- package/generated/prisma/browser.ts +5 -0
- package/generated/prisma/client.ts +5 -0
- package/generated/prisma/internal/class.ts +14 -4
- package/generated/prisma/internal/prismaNamespace.ts +97 -2
- package/generated/prisma/internal/prismaNamespaceBrowser.ts +21 -1
- package/generated/prisma/models/Report.ts +1219 -0
- package/generated/prisma/models/Session.ts +221 -1
- package/generated/prisma/models.ts +1 -0
- package/lib/coach.ts +571 -211
- package/lib/command-insights.ts +231 -0
- package/lib/db.ts +2 -2
- package/lib/parse-codex.ts +6 -0
- package/lib/parse-logs.ts +80 -1
- package/lib/queries-codex.ts +24 -0
- package/lib/queries.ts +45 -0
- package/lib/report.ts +156 -0
- package/lib/session-detail.ts +382 -0
- package/lib/sync.ts +87 -0
- package/lib/tool-flow.ts +71 -0
- package/next.config.mjs +6 -1
- package/package.json +17 -2
- package/plugins/cost-heatmap/component.tsx +72 -50
- package/prisma/migrations/20260401144555_add_system_prompt_edits/migration.sql +80 -0
- package/prisma/schema.prisma +18 -0
- package/prisma/schema.sql +81 -0
- package/.claude/settings.local.json +0 -26
- package/CONTRIBUTING.md +0 -209
- package/prisma/migrations/20260328152517_init/migration.sql +0 -41
- package/prisma/migrations/20260328153801_add_image_model/migration.sql +0 -18
- package/prisma.config.ts +0 -14
- package/setup.sh +0 -73
package/lib/coach.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
// ───
|
|
1
|
+
// ─── CRAFT Coach ─────────────────────────────────────────────────────
|
|
2
2
|
// Analyzes usage data and generates actionable coaching insights,
|
|
3
3
|
// like a Garmin coach for your AI coding agent.
|
|
4
|
+
// Priority: behavioral improvement > workflow efficiency > cost (secondary)
|
|
4
5
|
|
|
5
6
|
import type { UsageData, SessionSummary } from './parse-logs'
|
|
6
7
|
|
|
7
8
|
export type InsightSeverity = 'tip' | 'warning' | 'achievement'
|
|
9
|
+
export type CraftDimension = 'context' | 'reach' | 'autonomy' | 'flow' | 'throughput'
|
|
8
10
|
export type InsightCategory =
|
|
9
11
|
| 'cost'
|
|
10
12
|
| 'efficiency'
|
|
@@ -21,13 +23,23 @@ export interface CoachInsight {
|
|
|
21
23
|
description: string
|
|
22
24
|
category: InsightCategory
|
|
23
25
|
severity: InsightSeverity
|
|
24
|
-
metric?: string
|
|
26
|
+
metric?: string
|
|
25
27
|
recommendation?: string
|
|
28
|
+
craft?: CraftDimension
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface CraftScores {
|
|
32
|
+
context: number
|
|
33
|
+
reach: number
|
|
34
|
+
autonomy: number
|
|
35
|
+
flow: number
|
|
36
|
+
throughput: number
|
|
26
37
|
}
|
|
27
38
|
|
|
28
39
|
export interface CoachSummary {
|
|
29
|
-
score: number
|
|
40
|
+
score: number
|
|
30
41
|
scoreLabel: string
|
|
42
|
+
craft: CraftScores
|
|
31
43
|
insights: CoachInsight[]
|
|
32
44
|
stats: {
|
|
33
45
|
avgCostPerSession: number
|
|
@@ -43,63 +55,37 @@ export interface CoachSummary {
|
|
|
43
55
|
}
|
|
44
56
|
}
|
|
45
57
|
|
|
46
|
-
// ───
|
|
58
|
+
// ─── Helpers ─────────────────────────────────────────────────────────
|
|
47
59
|
|
|
48
60
|
function calculateStreaks(sessions: SessionSummary[]): { longest: number; current: number } {
|
|
49
61
|
const dates = new Set(sessions.map(s => s.startTime.slice(0, 10)))
|
|
50
62
|
const sorted = Array.from(dates).sort()
|
|
51
63
|
if (sorted.length === 0) return { longest: 0, current: 0 }
|
|
52
64
|
|
|
53
|
-
let longest = 1
|
|
54
|
-
let current = 1
|
|
55
|
-
let streak = 1
|
|
56
|
-
|
|
65
|
+
let longest = 1, streak = 1
|
|
57
66
|
for (let i = 1; i < sorted.length; i++) {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (diffDays === 1) {
|
|
63
|
-
streak++
|
|
64
|
-
longest = Math.max(longest, streak)
|
|
65
|
-
} else {
|
|
66
|
-
streak = 1
|
|
67
|
-
}
|
|
67
|
+
const diff = (new Date(sorted[i]).getTime() - new Date(sorted[i - 1]).getTime()) / (1000 * 60 * 60 * 24)
|
|
68
|
+
if (diff === 1) { streak++; longest = Math.max(longest, streak) }
|
|
69
|
+
else streak = 1
|
|
68
70
|
}
|
|
69
71
|
|
|
70
|
-
// Check if current streak is still active (last active day is today or yesterday)
|
|
71
72
|
const lastDate = new Date(sorted[sorted.length - 1])
|
|
72
|
-
const today = new Date()
|
|
73
|
-
today.setHours(0, 0, 0, 0)
|
|
73
|
+
const today = new Date(); today.setHours(0, 0, 0, 0)
|
|
74
74
|
const diffFromToday = (today.getTime() - lastDate.getTime()) / (1000 * 60 * 60 * 24)
|
|
75
|
-
current = diffFromToday <= 1 ? streak : 0
|
|
76
|
-
|
|
75
|
+
const current = diffFromToday <= 1 ? streak : 0
|
|
77
76
|
return { longest, current }
|
|
78
77
|
}
|
|
79
78
|
|
|
80
|
-
// ─── Peak Hour ───────────────────────────────────────────────────────
|
|
81
|
-
|
|
82
79
|
function findPeakHour(sessions: SessionSummary[]): number {
|
|
83
80
|
const hourCounts = new Array(24).fill(0)
|
|
84
|
-
for (const s of sessions) {
|
|
85
|
-
if (s.startTime) {
|
|
86
|
-
const hour = new Date(s.startTime).getHours()
|
|
87
|
-
hourCounts[hour]++
|
|
88
|
-
}
|
|
89
|
-
}
|
|
81
|
+
for (const s of sessions) { if (s.startTime) hourCounts[new Date(s.startTime).getHours()]++ }
|
|
90
82
|
return hourCounts.indexOf(Math.max(...hourCounts))
|
|
91
83
|
}
|
|
92
84
|
|
|
93
|
-
// ─── Most Active Day ─────────────────────────────────────────────────
|
|
94
|
-
|
|
95
85
|
function findMostActiveDay(sessions: SessionSummary[]): string {
|
|
96
86
|
const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
|
|
97
87
|
const dayCounts = new Array(7).fill(0)
|
|
98
|
-
for (const s of sessions) {
|
|
99
|
-
if (s.startTime) {
|
|
100
|
-
dayCounts[new Date(s.startTime).getDay()]++
|
|
101
|
-
}
|
|
102
|
-
}
|
|
88
|
+
for (const s of sessions) { if (s.startTime) dayCounts[new Date(s.startTime).getDay()]++ }
|
|
103
89
|
return days[dayCounts.indexOf(Math.max(...dayCounts))]
|
|
104
90
|
}
|
|
105
91
|
|
|
@@ -111,14 +97,13 @@ export function generateCoachInsights(data: UsageData): CoachSummary {
|
|
|
111
97
|
|
|
112
98
|
if (sessions.length === 0) {
|
|
113
99
|
return {
|
|
114
|
-
score: 0,
|
|
115
|
-
|
|
100
|
+
score: 0, scoreLabel: 'No data',
|
|
101
|
+
craft: { context: 0, reach: 0, autonomy: 0, flow: 0, throughput: 0 },
|
|
116
102
|
insights: [{ id: 'no-data', title: 'No sessions found', description: 'Start using your coding agent to get coaching insights.', category: 'habits', severity: 'tip' }],
|
|
117
103
|
stats: { avgCostPerSession: 0, avgDurationMinutes: 0, avgMessagesPerSession: 0, avgToolCallsPerSession: 0, mostUsedModel: 'N/A', mostActiveDay: 'N/A', longestStreak: 0, currentStreak: 0, totalDays: 0, peakHour: 0 },
|
|
118
104
|
}
|
|
119
105
|
}
|
|
120
106
|
|
|
121
|
-
// Basic stats
|
|
122
107
|
const avgCost = overview.totalCostUSD / sessions.length
|
|
123
108
|
const avgDuration = overview.totalDurationMinutes / sessions.length
|
|
124
109
|
const avgMessages = overview.totalMessages / sessions.length
|
|
@@ -129,274 +114,648 @@ export function generateCoachInsights(data: UsageData): CoachSummary {
|
|
|
129
114
|
const peakHour = findPeakHour(sessions)
|
|
130
115
|
const mostActiveDay = findMostActiveDay(sessions)
|
|
131
116
|
const uniqueDates = new Set(sessions.map(s => s.startTime.slice(0, 10)))
|
|
117
|
+
const totalToolCalls = overview.totalToolCalls
|
|
118
|
+
|
|
119
|
+
const sortedByDate = [...sessions].sort((a, b) => b.startTime.localeCompare(a.startTime))
|
|
120
|
+
const recentSessions = sortedByDate.filter(s => {
|
|
121
|
+
return (Date.now() - new Date(s.startTime).getTime()) / (1000 * 60 * 60 * 24) <= 7
|
|
122
|
+
})
|
|
123
|
+
const olderSessions = sortedByDate.filter(s => {
|
|
124
|
+
const days = (Date.now() - new Date(s.startTime).getTime()) / (1000 * 60 * 60 * 24)
|
|
125
|
+
return days > 7 && days <= 14
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
const readCalls = (toolUsage['Read'] || 0) + (toolUsage['Grep'] || 0) + (toolUsage['Glob'] || 0)
|
|
129
|
+
const editCalls = (toolUsage['Edit'] || 0) + (toolUsage['Write'] || 0)
|
|
130
|
+
const bashCalls = toolUsage['Bash'] || 0
|
|
131
|
+
const agentCalls = toolUsage['Agent'] || 0
|
|
132
|
+
const skillCalls = toolUsage['Skill'] || 0
|
|
133
|
+
const bashRatio = totalToolCalls > 0 ? bashCalls / totalToolCalls : 0
|
|
134
|
+
const readEditRatio = editCalls > 0 ? readCalls / editCalls : 1
|
|
135
|
+
|
|
136
|
+
// Permission mode analysis
|
|
137
|
+
const pm = overview.permissionModes || {}
|
|
138
|
+
const totalPmMessages = Object.values(pm).reduce((a, b) => a + b, 0)
|
|
139
|
+
const bypassCount = pm['bypassPermissions'] || 0
|
|
140
|
+
const acceptEditsCount = pm['acceptEdits'] || 0
|
|
141
|
+
const elevatedCount = bypassCount + acceptEditsCount
|
|
142
|
+
const elevatedRate = totalPmMessages > 0 ? elevatedCount / totalPmMessages : 0
|
|
132
143
|
|
|
133
|
-
//
|
|
144
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
145
|
+
// BEHAVIORAL INSIGHTS (highest priority)
|
|
146
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
134
147
|
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
const opusRatio = opusSessions.length / sessions.length
|
|
138
|
-
if (opusRatio > 0.8 && sessions.length > 10) {
|
|
139
|
-
const opusCost = opusSessions.reduce((sum, s) => sum + s.costUSD, 0)
|
|
148
|
+
// Parallelization with subagents
|
|
149
|
+
if (agentCalls === 0 && totalToolCalls > 500) {
|
|
140
150
|
insights.push({
|
|
141
|
-
id: '
|
|
142
|
-
title: '
|
|
143
|
-
description:
|
|
144
|
-
category: '
|
|
151
|
+
id: 'no-agents',
|
|
152
|
+
title: 'Unlock parallel work with subagents',
|
|
153
|
+
description: 'You haven\'t used subagents yet. When Claude spawns subagents, it can research multiple files, explore alternatives, and run tests in parallel — cutting task time significantly.',
|
|
154
|
+
category: 'efficiency',
|
|
145
155
|
severity: 'warning',
|
|
146
|
-
|
|
147
|
-
recommendation: '
|
|
156
|
+
craft: 'reach',
|
|
157
|
+
recommendation: 'Ask Claude to "use agents to explore X and Y in parallel" or "spawn an agent to research this while you implement". Add "use subagents for research tasks" to your CLAUDE.md.',
|
|
148
158
|
})
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// High cost sessions
|
|
152
|
-
const expensiveSessions = sessions.filter(s => s.costUSD > avgCost * 3)
|
|
153
|
-
if (expensiveSessions.length > 3) {
|
|
159
|
+
} else if (agentCalls > 0 && agentCalls < 20 && totalToolCalls > 500) {
|
|
154
160
|
insights.push({
|
|
155
|
-
id: '
|
|
156
|
-
title: '
|
|
157
|
-
description:
|
|
158
|
-
category: '
|
|
159
|
-
severity: '
|
|
160
|
-
|
|
161
|
-
|
|
161
|
+
id: 'more-agents',
|
|
162
|
+
title: 'Use subagents more often',
|
|
163
|
+
description: `Only ${agentCalls} subagent calls across ${sessions.length} sessions. You're underusing one of the most powerful productivity features.`,
|
|
164
|
+
category: 'efficiency',
|
|
165
|
+
severity: 'tip',
|
|
166
|
+
craft: 'reach',
|
|
167
|
+
metric: `${agentCalls} agent calls`,
|
|
168
|
+
recommendation: 'Subagents shine for: codebase exploration, running multiple searches, investigating alternatives. Tell Claude "launch agents in parallel" for complex research tasks.',
|
|
169
|
+
})
|
|
170
|
+
} else if (agentCalls > 50) {
|
|
171
|
+
insights.push({
|
|
172
|
+
id: 'good-agents',
|
|
173
|
+
title: 'Strong parallel work habits',
|
|
174
|
+
description: `${agentCalls} subagent calls — you're effectively parallelizing research and exploration. This keeps the main context lean and speeds up complex tasks.`,
|
|
175
|
+
category: 'efficiency',
|
|
176
|
+
severity: 'achievement',
|
|
177
|
+
craft: 'reach',
|
|
178
|
+
metric: `${agentCalls} parallel tasks`,
|
|
162
179
|
})
|
|
163
180
|
}
|
|
164
181
|
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const d = new Date(s.startTime)
|
|
169
|
-
const now = new Date()
|
|
170
|
-
return (now.getTime() - d.getTime()) / (1000 * 60 * 60 * 24) <= 7
|
|
171
|
-
})
|
|
172
|
-
const olderSessions = sortedByDate.filter(s => {
|
|
173
|
-
const d = new Date(s.startTime)
|
|
174
|
-
const now = new Date()
|
|
175
|
-
const daysAgo = (now.getTime() - d.getTime()) / (1000 * 60 * 60 * 24)
|
|
176
|
-
return daysAgo > 7 && daysAgo <= 14
|
|
177
|
-
})
|
|
178
|
-
if (recentSessions.length > 3 && olderSessions.length > 3) {
|
|
179
|
-
const recentCost = recentSessions.reduce((sum, s) => sum + s.costUSD, 0)
|
|
180
|
-
const olderCost = olderSessions.reduce((sum, s) => sum + s.costUSD, 0)
|
|
181
|
-
if (recentCost < olderCost * 0.7) {
|
|
182
|
+
// Read-before-Edit ratio (prompting quality)
|
|
183
|
+
if (editCalls > 50) {
|
|
184
|
+
if (readEditRatio < 0.5) {
|
|
182
185
|
insights.push({
|
|
183
|
-
id: '
|
|
184
|
-
title: '
|
|
185
|
-
description: `
|
|
186
|
-
category: '
|
|
187
|
-
severity: '
|
|
188
|
-
|
|
186
|
+
id: 'read-before-edit',
|
|
187
|
+
title: 'Agent is editing without reading first',
|
|
188
|
+
description: `Read-to-edit ratio is ${readEditRatio.toFixed(1)}x. The agent is modifying code ${Math.round(editCalls / Math.max(readCalls, 1))}x more than it reads. This leads to more mistakes and retry loops.`,
|
|
189
|
+
category: 'efficiency',
|
|
190
|
+
severity: 'warning',
|
|
191
|
+
craft: 'autonomy',
|
|
192
|
+
metric: `${readEditRatio.toFixed(1)}x read/edit`,
|
|
193
|
+
recommendation: 'Add to your CLAUDE.md: "Always read the relevant file before editing it. Understand the existing code structure before making changes." This alone can reduce iteration cycles by 30-50%.',
|
|
189
194
|
})
|
|
190
|
-
} else if (
|
|
195
|
+
} else if (readEditRatio > 2.0) {
|
|
191
196
|
insights.push({
|
|
192
|
-
id: '
|
|
193
|
-
title: '
|
|
194
|
-
description: `
|
|
195
|
-
category: '
|
|
196
|
-
severity: '
|
|
197
|
-
|
|
198
|
-
|
|
197
|
+
id: 'good-read-ratio',
|
|
198
|
+
title: 'Careful code modification habits',
|
|
199
|
+
description: `Read-to-edit ratio is ${readEditRatio.toFixed(1)}x — your agent reads and understands before modifying. This typically means fewer errors and less rework.`,
|
|
200
|
+
category: 'efficiency',
|
|
201
|
+
severity: 'achievement',
|
|
202
|
+
craft: 'autonomy',
|
|
203
|
+
metric: `${readEditRatio.toFixed(1)}x read/edit`,
|
|
199
204
|
})
|
|
200
205
|
}
|
|
201
206
|
}
|
|
202
207
|
|
|
203
|
-
//
|
|
204
|
-
|
|
205
|
-
// Long sessions
|
|
208
|
+
// Session length optimization (like "rest between workouts")
|
|
206
209
|
const longSessions = sessions.filter(s => s.durationMinutes > 120)
|
|
207
210
|
if (longSessions.length > 5) {
|
|
208
|
-
const longAvgCost = longSessions.reduce((sum, s) => sum + s.costUSD, 0) / longSessions.length
|
|
209
211
|
insights.push({
|
|
210
212
|
id: 'long-sessions',
|
|
211
|
-
title: '
|
|
212
|
-
description: `${longSessions.length} sessions exceeded 2 hours.
|
|
213
|
+
title: 'Break long sessions into focused sprints',
|
|
214
|
+
description: `${longSessions.length} sessions exceeded 2 hours. After ~90 minutes, context quality degrades — the agent loses track of earlier decisions and starts contradicting itself.`,
|
|
213
215
|
category: 'efficiency',
|
|
214
|
-
severity: '
|
|
216
|
+
severity: 'warning',
|
|
217
|
+
craft: 'context',
|
|
215
218
|
metric: `${longSessions.length} sessions > 2h`,
|
|
216
|
-
recommendation: '
|
|
219
|
+
recommendation: 'Like exercise intervals: work in 30-60 min focused sessions. Use /compact when context gets heavy. Start a /clear session for new tasks. Use /rename to bookmark important sessions for /resume later.',
|
|
217
220
|
})
|
|
218
221
|
}
|
|
219
222
|
|
|
220
|
-
//
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
223
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
224
|
+
// CONTEXT ENGINEERING INSIGHTS
|
|
225
|
+
// Context = holistic curation of tokens available to the LLM:
|
|
226
|
+
// system prompts (CLAUDE.md), just-in-time retrieval, compaction,
|
|
227
|
+
// structured note-taking, sub-agent isolation, cache efficiency
|
|
228
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
229
|
+
|
|
230
|
+
// Context window management (overflow + compaction)
|
|
231
|
+
const highTokenSessions = sessions.filter(s => s.totalTokens > 200000)
|
|
232
|
+
const overflowRate = highTokenSessions.length / sessions.length
|
|
233
|
+
if (overflowRate > 0.2) {
|
|
234
|
+
insights.push({
|
|
235
|
+
id: 'context-overflow',
|
|
236
|
+
title: 'Context pressure hurting quality',
|
|
237
|
+
description: `${Math.round(overflowRate * 100)}% of sessions exceed 200K tokens. Research shows model recall degrades as context grows — every token depletes the model's finite attention budget.`,
|
|
238
|
+
category: 'context',
|
|
239
|
+
severity: 'warning',
|
|
240
|
+
craft: 'context',
|
|
241
|
+
metric: `${Math.round(overflowRate * 100)}% overflow`,
|
|
242
|
+
recommendation: 'Use /compact with focus instructions ("keep the database schema and current task") to distill context. Start a /clear session for unrelated tasks. The goal: smallest set of high-signal tokens that maximize outcome.',
|
|
243
|
+
})
|
|
244
|
+
} else if (overflowRate <= 0.05 && sessions.length > 10) {
|
|
245
|
+
insights.push({
|
|
246
|
+
id: 'good-context',
|
|
247
|
+
title: 'Excellent context window management',
|
|
248
|
+
description: `Only ${Math.round(overflowRate * 100)}% of sessions hit high token counts. You're curating context effectively — keeping the attention budget focused.`,
|
|
249
|
+
category: 'context',
|
|
250
|
+
severity: 'achievement',
|
|
251
|
+
craft: 'context',
|
|
252
|
+
metric: `${Math.round(overflowRate * 100)}% overflow`,
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Cache efficiency (stable context reuse)
|
|
257
|
+
const totalCacheTokens = overview.totalCacheCreationTokens + overview.totalCacheReadTokens
|
|
258
|
+
const cacheRate = totalCacheTokens > 0 ? overview.totalCacheReadTokens / totalCacheTokens : 0
|
|
259
|
+
if (cacheRate < 0.3 && totalCacheTokens > 10000) {
|
|
260
|
+
insights.push({
|
|
261
|
+
id: 'low-cache',
|
|
262
|
+
title: 'Low cache reuse — rebuild cost is high',
|
|
263
|
+
description: `Cache hit rate is ${Math.round(cacheRate * 100)}%. Context is being rebuilt from scratch each turn. Stable context (CLAUDE.md rules, schemas) should be cached across turns.`,
|
|
264
|
+
category: 'context',
|
|
265
|
+
severity: 'warning',
|
|
266
|
+
craft: 'context',
|
|
267
|
+
metric: `${Math.round(cacheRate * 100)}% cache hit`,
|
|
268
|
+
recommendation: 'Add stable project context to CLAUDE.md so it\'s cached across turns. Keep sessions alive longer. Use /compact to slim context without discarding it entirely.',
|
|
269
|
+
})
|
|
270
|
+
} else if (cacheRate > 0.7 && totalCacheTokens > 10000) {
|
|
271
|
+
insights.push({
|
|
272
|
+
id: 'good-cache',
|
|
273
|
+
title: 'Strong cache reuse',
|
|
274
|
+
description: `${Math.round(cacheRate * 100)}% cache hit rate — stable context (CLAUDE.md, project rules) is being efficiently recycled. This is good system prompt engineering.`,
|
|
275
|
+
category: 'context',
|
|
276
|
+
severity: 'achievement',
|
|
277
|
+
craft: 'context',
|
|
278
|
+
metric: `${Math.round(cacheRate * 100)}% cache hit`,
|
|
279
|
+
})
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Just-in-time retrieval (vs. dumping context upfront)
|
|
283
|
+
const retrievalCalls = (toolUsage['Read'] || 0) + (toolUsage['Grep'] || 0) + (toolUsage['Glob'] || 0)
|
|
284
|
+
const retrievalRatio = totalToolCalls > 0 ? retrievalCalls / totalToolCalls : 0
|
|
285
|
+
if (totalToolCalls > 100) {
|
|
286
|
+
if (retrievalRatio > 0.4) {
|
|
226
287
|
insights.push({
|
|
227
|
-
id: '
|
|
228
|
-
title: '
|
|
229
|
-
description:
|
|
230
|
-
category: '
|
|
288
|
+
id: 'good-jit-retrieval',
|
|
289
|
+
title: 'Strong just-in-time context retrieval',
|
|
290
|
+
description: `${Math.round(retrievalRatio * 100)}% of tool calls are targeted retrieval (Read/Grep/Glob). Like a human using bookmarks instead of memorizing everything — the agent loads data on demand rather than stuffing context upfront.`,
|
|
291
|
+
category: 'context',
|
|
292
|
+
severity: 'achievement',
|
|
293
|
+
craft: 'context',
|
|
294
|
+
metric: `${Math.round(retrievalRatio * 100)}% retrieval`,
|
|
295
|
+
})
|
|
296
|
+
} else if (retrievalRatio < 0.15) {
|
|
297
|
+
insights.push({
|
|
298
|
+
id: 'low-jit-retrieval',
|
|
299
|
+
title: 'Agent isn\'t using targeted retrieval',
|
|
300
|
+
description: `Only ${Math.round(retrievalRatio * 100)}% of tool calls are Read/Grep/Glob. Without just-in-time context loading, the agent is either working blind or relying on stale context rather than fresh on-demand data.`,
|
|
301
|
+
category: 'context',
|
|
231
302
|
severity: 'warning',
|
|
232
|
-
|
|
233
|
-
|
|
303
|
+
craft: 'context',
|
|
304
|
+
metric: `${Math.round(retrievalRatio * 100)}% retrieval`,
|
|
305
|
+
recommendation: 'Add to CLAUDE.md: "Always read files before editing. Use Grep for targeted search instead of guessing." Progressive disclosure — explore incrementally, don\'t load everything at once.',
|
|
234
306
|
})
|
|
235
|
-
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Structured note-taking (agentic memory)
|
|
311
|
+
const todoWriteCalls = toolUsage['TodoWrite'] || toolUsage['TaskCreate'] || 0
|
|
312
|
+
const todoReadCalls = toolUsage['TodoRead'] || toolUsage['TaskGet'] || 0
|
|
313
|
+
const notesCalls = todoWriteCalls + todoReadCalls + (toolUsage['TaskUpdate'] || 0)
|
|
314
|
+
if (totalToolCalls > 200) {
|
|
315
|
+
if (notesCalls > 10) {
|
|
236
316
|
insights.push({
|
|
237
|
-
id: 'good-
|
|
238
|
-
title: '
|
|
239
|
-
description:
|
|
240
|
-
category: '
|
|
317
|
+
id: 'good-notes',
|
|
318
|
+
title: 'Using structured note-taking',
|
|
319
|
+
description: `${notesCalls} note-taking calls (TodoWrite/TaskCreate). Structured notes persist outside the context window, letting the agent track progress across compactions and long tasks — like writing a checklist instead of holding everything in memory.`,
|
|
320
|
+
category: 'context',
|
|
241
321
|
severity: 'achievement',
|
|
242
|
-
|
|
322
|
+
craft: 'context',
|
|
323
|
+
metric: `${notesCalls} notes`,
|
|
324
|
+
})
|
|
325
|
+
} else if (notesCalls === 0 && longSessions.length > 3) {
|
|
326
|
+
insights.push({
|
|
327
|
+
id: 'no-notes',
|
|
328
|
+
title: 'No structured note-taking in long sessions',
|
|
329
|
+
description: `${longSessions.length} sessions exceeded 2 hours but none used structured notes. Without persistent notes, the agent loses track of progress, decisions, and dependencies when context is compacted.`,
|
|
330
|
+
category: 'context',
|
|
331
|
+
severity: 'tip',
|
|
332
|
+
craft: 'context',
|
|
333
|
+
metric: 'No notes used',
|
|
334
|
+
recommendation: 'Ask Claude to "create a todo list" or "track your progress in tasks" for complex multi-step work. Notes survive compaction and help the agent continue coherently after context resets.',
|
|
243
335
|
})
|
|
244
336
|
}
|
|
245
337
|
}
|
|
246
338
|
|
|
247
|
-
//
|
|
339
|
+
// Output token density (signal-to-noise ratio)
|
|
340
|
+
const outputInputRatio = overview.totalInputTokens > 0
|
|
341
|
+
? overview.totalOutputTokens / overview.totalInputTokens
|
|
342
|
+
: 0
|
|
343
|
+
if (sessions.length > 10 && overview.totalInputTokens > 100000) {
|
|
344
|
+
if (outputInputRatio < 0.05) {
|
|
345
|
+
insights.push({
|
|
346
|
+
id: 'low-output-density',
|
|
347
|
+
title: 'Very low output-to-input ratio',
|
|
348
|
+
description: `Output is only ${(outputInputRatio * 100).toFixed(1)}% of input tokens. The agent is consuming a lot of context but producing little output — a sign of context pollution or unfocused prompts.`,
|
|
349
|
+
category: 'context',
|
|
350
|
+
severity: 'warning',
|
|
351
|
+
craft: 'context',
|
|
352
|
+
metric: `${(outputInputRatio * 100).toFixed(1)}% density`,
|
|
353
|
+
recommendation: 'Write more specific prompts. Use /compact to discard irrelevant context. Avoid pasting large files into prompts — let the agent Read them just-in-time instead.',
|
|
354
|
+
})
|
|
355
|
+
} else if (outputInputRatio > 0.3) {
|
|
356
|
+
insights.push({
|
|
357
|
+
id: 'good-output-density',
|
|
358
|
+
title: 'High output-to-input efficiency',
|
|
359
|
+
description: `Output is ${(outputInputRatio * 100).toFixed(1)}% of input — the agent is producing substantial work relative to context consumed. Your prompts are driving productive output.`,
|
|
360
|
+
category: 'context',
|
|
361
|
+
severity: 'achievement',
|
|
362
|
+
craft: 'context',
|
|
363
|
+
metric: `${(outputInputRatio * 100).toFixed(1)}% density`,
|
|
364
|
+
})
|
|
365
|
+
}
|
|
366
|
+
}
|
|
248
367
|
|
|
249
|
-
//
|
|
250
|
-
|
|
251
|
-
const overflowRate = highTokenSessions.length / sessions.length
|
|
252
|
-
if (overflowRate > 0.2) {
|
|
368
|
+
// Sub-agent context isolation
|
|
369
|
+
if (agentCalls > 20 && totalToolCalls > 200) {
|
|
253
370
|
insights.push({
|
|
254
|
-
id: 'context-
|
|
255
|
-
title: '
|
|
256
|
-
description: `${
|
|
371
|
+
id: 'good-context-isolation',
|
|
372
|
+
title: 'Using subagents for context isolation',
|
|
373
|
+
description: `${agentCalls} subagent calls keep deep exploration out of the main context window. Each subagent works with a clean context and returns a condensed summary — the main agent stays focused.`,
|
|
374
|
+
category: 'context',
|
|
375
|
+
severity: 'achievement',
|
|
376
|
+
craft: 'context',
|
|
377
|
+
metric: `${agentCalls} isolated tasks`,
|
|
378
|
+
})
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// System prompt engineering (CLAUDE.md / agent.md maintenance)
|
|
382
|
+
const totalSPEdits = overview.totalSystemPromptEdits
|
|
383
|
+
if (sessions.length > 10) {
|
|
384
|
+
if (totalSPEdits === 0) {
|
|
385
|
+
insights.push({
|
|
386
|
+
id: 'no-system-prompt-updates',
|
|
387
|
+
title: 'No CLAUDE.md / agent.md updates detected',
|
|
388
|
+
description: 'Your system prompt files (CLAUDE.md for Claude Code, agent.md for Codex) are your most powerful context engineering tool — they\'re loaded into every turn and cached across interactions. Zero edits means you may be missing out on persistent project-level instructions.',
|
|
389
|
+
category: 'context',
|
|
390
|
+
severity: 'warning',
|
|
391
|
+
craft: 'context',
|
|
392
|
+
metric: '0 edits',
|
|
393
|
+
recommendation: 'Create or update your CLAUDE.md with: project architecture, coding conventions, common pitfalls, and behavioral rules ("always read before editing"). This context is cached and reused every turn — high-signal tokens that compound over time.',
|
|
394
|
+
})
|
|
395
|
+
} else if (totalSPEdits >= 5) {
|
|
396
|
+
insights.push({
|
|
397
|
+
id: 'active-system-prompt',
|
|
398
|
+
title: 'Actively maintaining your system prompt',
|
|
399
|
+
description: `${totalSPEdits} edits to CLAUDE.md/agent.md — you're iterating on your system prompt like a well-tuned configuration. Each improvement compounds across every future session, making the agent smarter about your project's specific context.`,
|
|
400
|
+
category: 'context',
|
|
401
|
+
severity: 'achievement',
|
|
402
|
+
craft: 'context',
|
|
403
|
+
metric: `${totalSPEdits} edits`,
|
|
404
|
+
})
|
|
405
|
+
} else {
|
|
406
|
+
const editsPerSession = totalSPEdits / sessions.length
|
|
407
|
+
insights.push({
|
|
408
|
+
id: 'some-system-prompt',
|
|
409
|
+
title: 'Keep iterating on your system prompt',
|
|
410
|
+
description: `${totalSPEdits} edits to CLAUDE.md/agent.md across ${sessions.length} sessions. The best context engineers treat their system prompt as a living document — update it when you discover rules the agent should follow, files it should know about, or patterns it should prefer.`,
|
|
411
|
+
category: 'context',
|
|
412
|
+
severity: 'tip',
|
|
413
|
+
craft: 'context',
|
|
414
|
+
metric: `${totalSPEdits} edits`,
|
|
415
|
+
recommendation: 'After each session, ask yourself: "Did I repeat any instruction that should be in CLAUDE.md?" Add project rules, schemas, and conventions. High-signal stable context that gets cached = free quality improvement on every turn.',
|
|
416
|
+
})
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Session naming / organization
|
|
421
|
+
const avgSessionTokens = overview.totalTokens / Math.max(sessions.length, 1)
|
|
422
|
+
if (avgSessionTokens > 150000 && longSessions.length <= 5) {
|
|
423
|
+
insights.push({
|
|
424
|
+
id: 'context-tips',
|
|
425
|
+
title: 'Keep sessions organized for easy recall',
|
|
426
|
+
description: 'Your sessions are token-heavy. Named sessions are easier to find and resume later.',
|
|
257
427
|
category: 'context',
|
|
428
|
+
severity: 'tip',
|
|
429
|
+
craft: 'context',
|
|
430
|
+
recommendation: 'Use /rename to label sessions by task ("fix auth bug", "add search"). Use /resume to pick up where you left off. Use /context mid-session to check how much window remains before it degrades.',
|
|
431
|
+
})
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Interruptions (agent doing wrong things)
|
|
435
|
+
const totalInterruptions = overview.totalUserInterruptions
|
|
436
|
+
if (totalInterruptions > 10) {
|
|
437
|
+
const interruptRate = totalInterruptions / overview.totalMessages * 100
|
|
438
|
+
insights.push({
|
|
439
|
+
id: 'high-interruptions',
|
|
440
|
+
title: 'Frequent interruptions — improve your prompts',
|
|
441
|
+
description: `You interrupted the agent ${totalInterruptions} times (${interruptRate.toFixed(1)}% of messages). Each interruption wastes context and tokens on work that gets thrown away.`,
|
|
442
|
+
category: 'efficiency',
|
|
258
443
|
severity: 'warning',
|
|
259
|
-
|
|
260
|
-
|
|
444
|
+
craft: 'autonomy',
|
|
445
|
+
metric: `${totalInterruptions} interruptions`,
|
|
446
|
+
recommendation: 'Be more specific upfront: describe the expected output, mention files to avoid, state constraints. Use /plan for complex tasks so you can review the approach before execution. Add common rules to CLAUDE.md.',
|
|
447
|
+
})
|
|
448
|
+
} else if (totalInterruptions <= 3 && sessions.length > 20) {
|
|
449
|
+
insights.push({
|
|
450
|
+
id: 'low-interruptions',
|
|
451
|
+
title: 'Clean collaboration — low interruption rate',
|
|
452
|
+
description: `Only ${totalInterruptions} interruptions across ${sessions.length} sessions. Your prompts are clear and the agent rarely does unwanted work.`,
|
|
453
|
+
category: 'efficiency',
|
|
454
|
+
severity: 'achievement',
|
|
455
|
+
craft: 'autonomy',
|
|
456
|
+
metric: `${totalInterruptions} interruptions`,
|
|
261
457
|
})
|
|
262
458
|
}
|
|
263
459
|
|
|
264
|
-
//
|
|
460
|
+
// Permission mode — trust signals
|
|
461
|
+
if (totalPmMessages > 10) {
|
|
462
|
+
if (bypassCount > 0) {
|
|
463
|
+
const bypassRate = Math.round((bypassCount / totalPmMessages) * 100)
|
|
464
|
+
insights.push({
|
|
465
|
+
id: 'yolo-mode',
|
|
466
|
+
title: `Auto-accept mode used (${bypassRate}% of messages)`,
|
|
467
|
+
description: `You used full auto-accept (bypass permissions) for ${bypassCount} messages. This shows high trust in the agent and enables maximum autonomy.`,
|
|
468
|
+
category: 'efficiency',
|
|
469
|
+
severity: 'achievement',
|
|
470
|
+
craft: 'autonomy',
|
|
471
|
+
metric: `${bypassRate}% auto`,
|
|
472
|
+
})
|
|
473
|
+
}
|
|
474
|
+
if (acceptEditsCount > 0 && bypassCount === 0) {
|
|
475
|
+
const acceptRate = Math.round((acceptEditsCount / totalPmMessages) * 100)
|
|
476
|
+
insights.push({
|
|
477
|
+
id: 'accept-edits',
|
|
478
|
+
title: `Accept-edits mode used (${acceptRate}% of messages)`,
|
|
479
|
+
description: `You granted "always allow" for edits in ${acceptRate}% of messages — a good balance of trust and oversight.`,
|
|
480
|
+
category: 'efficiency',
|
|
481
|
+
severity: 'achievement',
|
|
482
|
+
craft: 'autonomy',
|
|
483
|
+
metric: `${acceptRate}% accept`,
|
|
484
|
+
})
|
|
485
|
+
}
|
|
486
|
+
if (elevatedRate < 0.1) {
|
|
487
|
+
insights.push({
|
|
488
|
+
id: 'low-trust',
|
|
489
|
+
title: 'Consider granting more permissions',
|
|
490
|
+
description: `Only ${Math.round(elevatedRate * 100)}% of your messages used elevated permissions. Approving each tool call individually slows you down.`,
|
|
491
|
+
category: 'efficiency',
|
|
492
|
+
severity: 'tip',
|
|
493
|
+
craft: 'autonomy',
|
|
494
|
+
metric: `${Math.round(elevatedRate * 100)}% elevated`,
|
|
495
|
+
recommendation: 'Try "Yes, and don\'t ask again" for safe operations (Read, Grep, Glob). For trusted projects, use auto-accept mode to let the agent work uninterrupted. You can always /permissions to revoke.',
|
|
496
|
+
})
|
|
497
|
+
}
|
|
498
|
+
}
|
|
265
499
|
|
|
266
|
-
//
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
const bashRatio = totalToolCalls > 0 ? bashCalls / totalToolCalls : 0
|
|
270
|
-
if (bashRatio > 0.4 && totalToolCalls > 100) {
|
|
500
|
+
// API errors / rate limits
|
|
501
|
+
const totalApiErrors = overview.totalApiErrors
|
|
502
|
+
if (totalApiErrors > 5) {
|
|
271
503
|
insights.push({
|
|
272
|
-
id: '
|
|
273
|
-
title: '
|
|
274
|
-
description: `${
|
|
275
|
-
category: '
|
|
504
|
+
id: 'api-errors',
|
|
505
|
+
title: 'Rate limits impacting your flow',
|
|
506
|
+
description: `${totalApiErrors} API errors (mostly rate limits). Each one breaks your flow and forces you to wait or retry.`,
|
|
507
|
+
category: 'efficiency',
|
|
276
508
|
severity: 'tip',
|
|
277
|
-
|
|
278
|
-
|
|
509
|
+
craft: 'throughput',
|
|
510
|
+
metric: `${totalApiErrors} errors`,
|
|
511
|
+
recommendation: 'When hitting rate limits: use /effort low for quick tasks, break work into smaller sessions, or use /btw for side questions instead of new full prompts.',
|
|
279
512
|
})
|
|
280
513
|
}
|
|
281
514
|
|
|
282
|
-
//
|
|
283
|
-
|
|
284
|
-
|
|
515
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
516
|
+
// WORKFLOW INSIGHTS (medium priority)
|
|
517
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
518
|
+
|
|
519
|
+
// CLAUDE.md / prompting infrastructure
|
|
520
|
+
if (skillCalls === 0 && totalToolCalls > 200) {
|
|
285
521
|
insights.push({
|
|
286
|
-
id: 'no-
|
|
287
|
-
title: '
|
|
288
|
-
description: 'You haven\'t used
|
|
289
|
-
category: '
|
|
522
|
+
id: 'no-skills',
|
|
523
|
+
title: 'Create reusable skills for repeated tasks',
|
|
524
|
+
description: 'You haven\'t used custom skills yet. If you find yourself giving the same instructions repeatedly, a skill captures that workflow in a single command.',
|
|
525
|
+
category: 'discovery',
|
|
290
526
|
severity: 'tip',
|
|
291
|
-
|
|
527
|
+
craft: 'reach',
|
|
528
|
+
recommendation: 'Create .claude/skills/my-workflow/SKILL.md for any task you do regularly: deploy scripts, code review checklists, testing patterns. Type /skills to see what\'s available.',
|
|
292
529
|
})
|
|
293
|
-
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Bash overuse
|
|
533
|
+
if (bashRatio > 0.4 && totalToolCalls > 100) {
|
|
294
534
|
insights.push({
|
|
295
|
-
id: '
|
|
296
|
-
title: '
|
|
297
|
-
description: `${
|
|
535
|
+
id: 'bash-heavy',
|
|
536
|
+
title: 'Let the agent use specialized tools',
|
|
537
|
+
description: `${Math.round(bashRatio * 100)}% of tool calls are Bash commands. Specialized tools (Read, Edit, Grep) are faster, safer, and produce better results than shell equivalents.`,
|
|
298
538
|
category: 'tools',
|
|
299
|
-
severity: '
|
|
300
|
-
|
|
539
|
+
severity: 'tip',
|
|
540
|
+
craft: 'reach',
|
|
541
|
+
metric: `${Math.round(bashRatio * 100)}% Bash`,
|
|
542
|
+
recommendation: 'Add to CLAUDE.md: "Prefer Read over cat, Edit over sed, Grep over grep. Only use Bash for commands that require shell execution." This also makes tool calls easier to review.',
|
|
301
543
|
})
|
|
302
544
|
}
|
|
303
545
|
|
|
304
|
-
//
|
|
546
|
+
// Peak productivity
|
|
547
|
+
insights.push({
|
|
548
|
+
id: 'peak-hour',
|
|
549
|
+
title: `Your peak coding hour: ${peakHour}:00 on ${mostActiveDay}s`,
|
|
550
|
+
description: `Schedule your most complex AI-assisted tasks during this window when you're most productive and attentive. Use simpler tasks or autonomous runs during off-peak hours.`,
|
|
551
|
+
category: 'habits',
|
|
552
|
+
severity: 'tip',
|
|
553
|
+
craft: 'flow',
|
|
554
|
+
metric: `${peakHour}:00`,
|
|
555
|
+
})
|
|
556
|
+
|
|
557
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
558
|
+
// STREAK & HABIT INSIGHTS
|
|
559
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
305
560
|
|
|
306
|
-
// Streaks
|
|
307
561
|
if (streaks.current >= 7) {
|
|
308
562
|
insights.push({
|
|
309
563
|
id: 'streak-hot',
|
|
310
564
|
title: `${streaks.current}-day coding streak!`,
|
|
311
|
-
description: `You've been
|
|
565
|
+
description: `Consistency builds mastery. You've been using your AI agent every day for ${streaks.current} days — your prompts and workflows are likely improving steadily.`,
|
|
312
566
|
category: 'streak',
|
|
313
567
|
severity: 'achievement',
|
|
568
|
+
craft: 'flow',
|
|
314
569
|
metric: `${streaks.current} days`,
|
|
315
570
|
})
|
|
316
571
|
} else if (streaks.current >= 3) {
|
|
317
572
|
insights.push({
|
|
318
573
|
id: 'streak-building',
|
|
319
|
-
title: `${streaks.current}-day streak
|
|
320
|
-
description: `
|
|
574
|
+
title: `${streaks.current}-day streak — keep going!`,
|
|
575
|
+
description: `Your longest was ${streaks.longest} days. Consistent daily practice with AI coding tools is the fastest way to master human-AI collaboration.`,
|
|
321
576
|
category: 'streak',
|
|
322
577
|
severity: 'achievement',
|
|
323
|
-
|
|
578
|
+
craft: 'flow',
|
|
579
|
+
metric: `${streaks.current}/${streaks.longest}d`,
|
|
324
580
|
})
|
|
325
581
|
} else if (streaks.current === 0 && streaks.longest > 3) {
|
|
326
582
|
insights.push({
|
|
327
583
|
id: 'streak-broken',
|
|
328
|
-
title: 'Streak broken',
|
|
329
|
-
description: `Your ${streaks.longest}-day streak ended.
|
|
584
|
+
title: 'Streak broken — start fresh today',
|
|
585
|
+
description: `Your ${streaks.longest}-day streak ended. Like a fitness routine, consistency matters more than intensity. One session today restarts your momentum.`,
|
|
330
586
|
category: 'streak',
|
|
331
587
|
severity: 'tip',
|
|
332
|
-
|
|
588
|
+
craft: 'flow',
|
|
589
|
+
metric: `Best: ${streaks.longest}d`,
|
|
333
590
|
})
|
|
334
591
|
}
|
|
335
592
|
|
|
336
|
-
//
|
|
337
|
-
|
|
338
|
-
id: 'peak-hour',
|
|
339
|
-
title: `Peak hour: ${peakHour}:00`,
|
|
340
|
-
description: `You start the most sessions around ${peakHour}:00. Your most active day is ${mostActiveDay}. Schedule your most complex tasks during these peak windows.`,
|
|
341
|
-
category: 'habits',
|
|
342
|
-
severity: 'tip',
|
|
343
|
-
metric: `${peakHour}:00 ${mostActiveDay}s`,
|
|
344
|
-
})
|
|
345
|
-
|
|
346
|
-
// Session volume trend
|
|
347
|
-
if (recentSessions.length > 0 && olderSessions.length > 0) {
|
|
593
|
+
// Usage trend
|
|
594
|
+
if (recentSessions.length > 3 && olderSessions.length > 3) {
|
|
348
595
|
const recentPerDay = recentSessions.length / 7
|
|
349
596
|
const olderPerDay = olderSessions.length / 7
|
|
350
597
|
if (recentPerDay > olderPerDay * 1.5) {
|
|
351
598
|
insights.push({
|
|
352
599
|
id: 'usage-up',
|
|
353
|
-
title: '
|
|
354
|
-
description:
|
|
600
|
+
title: 'Accelerating AI adoption',
|
|
601
|
+
description: `${recentPerDay.toFixed(1)} sessions/day this week vs ${olderPerDay.toFixed(1)} last week. You're leaning deeper into AI-assisted development.`,
|
|
355
602
|
category: 'habits',
|
|
356
603
|
severity: 'achievement',
|
|
604
|
+
craft: 'flow',
|
|
357
605
|
metric: `${recentPerDay.toFixed(1)}/day`,
|
|
358
606
|
})
|
|
359
607
|
}
|
|
360
608
|
}
|
|
361
609
|
|
|
362
|
-
//
|
|
610
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
611
|
+
// COST INSIGHTS (lower priority — kept but not prominent)
|
|
612
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
363
613
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
614
|
+
if (recentSessions.length > 3 && olderSessions.length > 3) {
|
|
615
|
+
const recentCost = recentSessions.reduce((sum, s) => sum + s.costUSD, 0)
|
|
616
|
+
const olderCost = olderSessions.reduce((sum, s) => sum + s.costUSD, 0)
|
|
617
|
+
if (recentCost < olderCost * 0.7) {
|
|
618
|
+
insights.push({
|
|
619
|
+
id: 'cost-down',
|
|
620
|
+
title: 'More efficient this week',
|
|
621
|
+
description: `Token usage dropped ${Math.round((1 - recentCost / olderCost) * 100)}% vs last week while maintaining output. Your prompts may be getting more precise.`,
|
|
622
|
+
category: 'cost',
|
|
623
|
+
severity: 'achievement',
|
|
624
|
+
craft: 'throughput',
|
|
625
|
+
metric: `-${Math.round((1 - recentCost / olderCost) * 100)}%`,
|
|
626
|
+
})
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Parallel session usage
|
|
631
|
+
const parallelDays = new Map<string, number>()
|
|
632
|
+
for (const s of sessions) {
|
|
633
|
+
if (!s.startTime) continue
|
|
634
|
+
const date = s.startTime.slice(0, 10)
|
|
635
|
+
parallelDays.set(date, (parallelDays.get(date) || 0) + 1)
|
|
636
|
+
}
|
|
637
|
+
const avgParallelSessions = parallelDays.size > 0
|
|
638
|
+
? Array.from(parallelDays.values()).reduce((a, b) => a + b, 0) / parallelDays.size
|
|
639
|
+
: 0
|
|
640
|
+
if (avgParallelSessions >= 3 && sessions.length > 10) {
|
|
367
641
|
insights.push({
|
|
368
|
-
id: '
|
|
369
|
-
title: '
|
|
370
|
-
description:
|
|
371
|
-
category: '
|
|
642
|
+
id: 'good-parallel',
|
|
643
|
+
title: 'Strong parallel session usage',
|
|
644
|
+
description: `Averaging ${avgParallelSessions.toFixed(1)} sessions/day — you're running multiple tasks in parallel, maximizing throughput.`,
|
|
645
|
+
category: 'efficiency',
|
|
646
|
+
severity: 'achievement',
|
|
647
|
+
craft: 'throughput',
|
|
648
|
+
metric: `${avgParallelSessions.toFixed(1)} sessions/day`,
|
|
649
|
+
})
|
|
650
|
+
} else if (avgParallelSessions < 1.5 && sessions.length > 10) {
|
|
651
|
+
insights.push({
|
|
652
|
+
id: 'low-parallel',
|
|
653
|
+
title: 'Try running sessions in parallel',
|
|
654
|
+
description: `Averaging ${avgParallelSessions.toFixed(1)} sessions/day. Running multiple sessions on different tasks lets you keep working while the agent handles long-running work.`,
|
|
655
|
+
category: 'efficiency',
|
|
372
656
|
severity: 'tip',
|
|
373
|
-
|
|
657
|
+
craft: 'throughput',
|
|
658
|
+
metric: `${avgParallelSessions.toFixed(1)} sessions/day`,
|
|
659
|
+
recommendation: 'Open a second terminal for a separate task while waiting on the first. Use /rename to label each session by task so you can track them.',
|
|
374
660
|
})
|
|
375
661
|
}
|
|
376
662
|
|
|
377
|
-
// ── Compute
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
663
|
+
// ── Compute CRAFT dimension scores (each 0–100) ──
|
|
664
|
+
|
|
665
|
+
const clamp = (v: number) => Math.max(0, Math.min(100, Math.round(v)))
|
|
666
|
+
|
|
667
|
+
// C — Context Engineering: holistic curation of tokens available to the LLM
|
|
668
|
+
// system prompt maintenance + cache efficiency + overflow avoidance + session length
|
|
669
|
+
// + just-in-time retrieval + structured note-taking + output density + sub-agent isolation
|
|
670
|
+
const totalCache = overview.totalCacheCreationTokens + overview.totalCacheReadTokens
|
|
671
|
+
const cacheHitRate = totalCache > 0 ? overview.totalCacheReadTokens / totalCache : 0
|
|
672
|
+
const shortSessionRate = 1 - (longSessions.length / Math.max(sessions.length, 1))
|
|
673
|
+
const jitRetrievalRate = totalToolCalls > 0
|
|
674
|
+
? ((toolUsage['Read'] || 0) + (toolUsage['Grep'] || 0) + (toolUsage['Glob'] || 0)) / totalToolCalls
|
|
675
|
+
: 0
|
|
676
|
+
const noteTakingRate = totalToolCalls > 0
|
|
677
|
+
? ((toolUsage['TodoWrite'] || 0) + (toolUsage['TaskCreate'] || 0) + (toolUsage['TodoRead'] || 0) + (toolUsage['TaskGet'] || 0) + (toolUsage['TaskUpdate'] || 0)) / Math.max(sessions.length, 1)
|
|
678
|
+
: 0
|
|
679
|
+
const outputDensity = overview.totalInputTokens > 0
|
|
680
|
+
? Math.min(overview.totalOutputTokens / overview.totalInputTokens / 0.3, 1) // 0.3 ratio = full marks
|
|
681
|
+
: 0
|
|
682
|
+
const agentIsolationRate = totalToolCalls > 0
|
|
683
|
+
? Math.min((toolUsage['Agent'] || 0) / Math.max(sessions.length, 1) / 3, 1) // 3 agent calls/session = full marks
|
|
684
|
+
: 0
|
|
685
|
+
const sysPromptScore = Math.min(overview.totalSystemPromptEdits / 5, 1) // 5+ edits = full marks
|
|
686
|
+
const contextScore = clamp(
|
|
687
|
+
(sysPromptScore * 15) + // system prompt engineering (CLAUDE.md/agent.md)
|
|
688
|
+
(cacheHitRate * 15) + // stable context reuse
|
|
689
|
+
((1 - Math.min(overflowRate, 1)) * 15) + // context window management
|
|
690
|
+
(shortSessionRate * 10) + // session length discipline
|
|
691
|
+
(Math.min(jitRetrievalRate / 0.4, 1) * 20) + // just-in-time retrieval
|
|
692
|
+
(Math.min(noteTakingRate / 2, 1) * 10) + // structured note-taking
|
|
693
|
+
(outputDensity * 10) + // output token density
|
|
694
|
+
(agentIsolationRate * 5) // sub-agent context isolation
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
// R — Reach: tool diversity + subagent usage + skill adoption
|
|
396
698
|
const uniqueTools = Object.keys(toolUsage).length
|
|
397
|
-
|
|
699
|
+
const toolDiversityScore = Math.min(uniqueTools / 12, 1) * 35
|
|
700
|
+
const agentScore = agentCalls === 0 ? 0 : Math.min(agentCalls / 50, 1) * 35
|
|
701
|
+
const skillScore = skillCalls === 0 ? 0 : Math.min(skillCalls / 20, 1) * 30
|
|
702
|
+
const reachScore = clamp(toolDiversityScore + agentScore + skillScore)
|
|
703
|
+
|
|
704
|
+
// A — Autonomy: assistant/user ratio + low interruptions + read-before-edit + permission trust
|
|
705
|
+
const autonomyRatio = overview.totalAssistantMessages / Math.max(overview.totalUserMessages, 1)
|
|
706
|
+
const autonomyRatioScore = Math.min(autonomyRatio / 3, 1) * 25
|
|
707
|
+
const interruptScore = totalInterruptions === 0 ? 25 : Math.max(0, 25 - (totalInterruptions / overview.totalMessages * 100) * 8)
|
|
708
|
+
const readEditScore = Math.min(readEditRatio / 3, 1) * 25
|
|
709
|
+
const permTrustScore = Math.min(elevatedRate, 1) * 25 // higher elevated permission rate = more trust
|
|
710
|
+
const autonomyScore = clamp(autonomyRatioScore + interruptScore + readEditScore + permTrustScore)
|
|
711
|
+
|
|
712
|
+
// F — Flow: streak + daily consistency + active days coverage
|
|
713
|
+
const daysCovered = uniqueDates.size
|
|
714
|
+
const totalPossibleDays = sessions.length > 0
|
|
715
|
+
? Math.max(1, Math.ceil((Date.now() - new Date(sessions[sessions.length - 1]?.startTime || Date.now()).getTime()) / (1000 * 60 * 60 * 24)))
|
|
716
|
+
: 1
|
|
717
|
+
const consistencyRate = Math.min(daysCovered / totalPossibleDays, 1)
|
|
718
|
+
const streakScore = Math.min(streaks.current / 14, 1) * 35
|
|
719
|
+
const consistencyScore = consistencyRate * 35
|
|
720
|
+
const activeDaysScore = Math.min(daysCovered / 30, 1) * 30
|
|
721
|
+
const flowScore = clamp(streakScore + consistencyScore + activeDaysScore)
|
|
722
|
+
|
|
723
|
+
// T — Throughput: cost efficiency + output volume + parallel sessions + low error rate
|
|
724
|
+
const avgCostEfficiency = avgCost > 0 ? Math.min(30 / avgCost, 1) : 0 // $30/session = baseline
|
|
725
|
+
const outputVolume = Math.min(overview.totalOutputTokens / 1_000_000, 1) // 1M output tokens = full marks
|
|
726
|
+
const errorRate = overview.totalApiErrors / Math.max(sessions.length, 1)
|
|
727
|
+
const lowErrorScore = Math.max(0, 1 - errorRate / 5) // 5 errors/session = 0
|
|
728
|
+
// Parallel sessions: compute avg sessions per active day
|
|
729
|
+
const daySessions = new Map<string, number>()
|
|
730
|
+
for (const s of sessions) {
|
|
731
|
+
if (!s.startTime) continue
|
|
732
|
+
const date = s.startTime.slice(0, 10)
|
|
733
|
+
daySessions.set(date, (daySessions.get(date) || 0) + 1)
|
|
734
|
+
}
|
|
735
|
+
const avgParallel = daySessions.size > 0
|
|
736
|
+
? Array.from(daySessions.values()).reduce((a, b) => a + b, 0) / daySessions.size
|
|
737
|
+
: 0
|
|
738
|
+
const parallelScore = Math.min(avgParallel / 4, 1) // 4+ sessions/day = full marks
|
|
739
|
+
const throughputScore = clamp(
|
|
740
|
+
(avgCostEfficiency * 25) + (outputVolume * 25) + (parallelScore * 25) + (lowErrorScore * 25)
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
const craft: CraftScores = {
|
|
744
|
+
context: contextScore,
|
|
745
|
+
reach: reachScore,
|
|
746
|
+
autonomy: autonomyScore,
|
|
747
|
+
flow: flowScore,
|
|
748
|
+
throughput: throughputScore,
|
|
749
|
+
}
|
|
398
750
|
|
|
399
|
-
score =
|
|
751
|
+
// Overall score = weighted average of CRAFT dimensions
|
|
752
|
+
const score = clamp(
|
|
753
|
+
craft.context * 0.20 +
|
|
754
|
+
craft.reach * 0.20 +
|
|
755
|
+
craft.autonomy * 0.25 +
|
|
756
|
+
craft.flow * 0.15 +
|
|
757
|
+
craft.throughput * 0.20
|
|
758
|
+
)
|
|
400
759
|
|
|
401
760
|
const scoreLabel =
|
|
402
761
|
score >= 85 ? 'Elite' :
|
|
@@ -408,6 +767,7 @@ export function generateCoachInsights(data: UsageData): CoachSummary {
|
|
|
408
767
|
return {
|
|
409
768
|
score,
|
|
410
769
|
scoreLabel,
|
|
770
|
+
craft,
|
|
411
771
|
insights,
|
|
412
772
|
stats: {
|
|
413
773
|
avgCostPerSession: avgCost,
|