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.
Files changed (74) hide show
  1. package/.github/workflows/release.yml +111 -0
  2. package/README.md +41 -38
  3. package/app/(dashboard)/daily/page.tsx +1 -1
  4. package/app/(dashboard)/data-management/page.tsx +180 -0
  5. package/app/(dashboard)/flow/page.tsx +17 -0
  6. package/app/(dashboard)/layout.tsx +2 -0
  7. package/app/(dashboard)/page.tsx +24 -5
  8. package/app/(dashboard)/reports/[id]/page.tsx +72 -0
  9. package/app/(dashboard)/reports/page.tsx +132 -0
  10. package/app/(dashboard)/sessions/[id]/page.tsx +167 -0
  11. package/app/api/backup/route.ts +215 -0
  12. package/app/api/check/route.ts +11 -1
  13. package/app/api/command-insights/route.ts +13 -0
  14. package/app/api/commands/route.ts +55 -1
  15. package/app/api/images-analysis/route.ts +3 -4
  16. package/app/api/reports/[id]/route.ts +23 -0
  17. package/app/api/reports/route.ts +50 -0
  18. package/app/api/reset/route.ts +21 -0
  19. package/app/api/session/route.ts +40 -0
  20. package/app/api/usage/route.ts +26 -1
  21. package/app/layout.tsx +1 -1
  22. package/bin/agentfit.mjs +2 -2
  23. package/components/agent-coach.tsx +256 -129
  24. package/components/app-sidebar.tsx +45 -10
  25. package/components/backup-section.tsx +236 -0
  26. package/components/daily-chart.tsx +447 -83
  27. package/components/dashboard-shell.tsx +29 -31
  28. package/components/data-provider.tsx +88 -8
  29. package/components/fitness-score.tsx +95 -54
  30. package/components/overview-cards.tsx +148 -41
  31. package/components/report-view.tsx +307 -0
  32. package/components/screenshots-analysis.tsx +51 -46
  33. package/components/session-chatlog.tsx +124 -0
  34. package/components/session-timeline.tsx +184 -0
  35. package/components/session-workflow.tsx +183 -0
  36. package/components/sessions-table.tsx +9 -1
  37. package/components/tool-flow-graph.tsx +144 -0
  38. package/components/ui/carousel.tsx +242 -0
  39. package/components/ui/sidebar.tsx +1 -1
  40. package/components/ui/sonner.tsx +51 -0
  41. package/electron/entitlements.mac.plist +16 -0
  42. package/electron/init-db.mjs +37 -0
  43. package/electron/main.mjs +203 -0
  44. package/generated/prisma/browser.ts +5 -0
  45. package/generated/prisma/client.ts +5 -0
  46. package/generated/prisma/internal/class.ts +14 -4
  47. package/generated/prisma/internal/prismaNamespace.ts +97 -2
  48. package/generated/prisma/internal/prismaNamespaceBrowser.ts +21 -1
  49. package/generated/prisma/models/Report.ts +1219 -0
  50. package/generated/prisma/models/Session.ts +221 -1
  51. package/generated/prisma/models.ts +1 -0
  52. package/lib/coach.ts +571 -211
  53. package/lib/command-insights.ts +231 -0
  54. package/lib/db.ts +2 -2
  55. package/lib/parse-codex.ts +6 -0
  56. package/lib/parse-logs.ts +80 -1
  57. package/lib/queries-codex.ts +24 -0
  58. package/lib/queries.ts +45 -0
  59. package/lib/report.ts +156 -0
  60. package/lib/session-detail.ts +382 -0
  61. package/lib/sync.ts +87 -0
  62. package/lib/tool-flow.ts +71 -0
  63. package/next.config.mjs +6 -1
  64. package/package.json +17 -2
  65. package/plugins/cost-heatmap/component.tsx +72 -50
  66. package/prisma/migrations/20260401144555_add_system_prompt_edits/migration.sql +80 -0
  67. package/prisma/schema.prisma +18 -0
  68. package/prisma/schema.sql +81 -0
  69. package/.claude/settings.local.json +0 -26
  70. package/CONTRIBUTING.md +0 -209
  71. package/prisma/migrations/20260328152517_init/migration.sql +0 -41
  72. package/prisma/migrations/20260328153801_add_image_model/migration.sql +0 -18
  73. package/prisma.config.ts +0 -14
  74. package/setup.sh +0 -73
package/lib/coach.ts CHANGED
@@ -1,10 +1,12 @@
1
- // ─── Agent Coach ─────────────────────────────────────────────────────
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 // e.g., "$142.30" or "26.8%"
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 // 0-100 overall "fitness score"
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
- // ─── Streak Calculation ──────────────────────────────────────────────
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 prev = new Date(sorted[i - 1])
59
- const curr = new Date(sorted[i])
60
- const diffDays = (curr.getTime() - prev.getTime()) / (1000 * 60 * 60 * 24)
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
- scoreLabel: 'No data',
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
- // ── Cost insights ──
144
+ // ═══════════════════════════════════════════════════════════════════
145
+ // BEHAVIORAL INSIGHTS (highest priority)
146
+ // ═══════════════════════════════════════════════════════════════════
134
147
 
135
- // Model cost optimization
136
- const opusSessions = sessions.filter(s => s.model.includes('opus'))
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: 'model-diversity',
142
- title: 'Heavy Opus usage detected',
143
- description: `${Math.round(opusRatio * 100)}% of your sessions use Opus, costing $${opusCost.toFixed(0)} total. Many routine tasks (git operations, simple edits, file reading) can be handled by Haiku or Sonnet at a fraction of the cost.`,
144
- category: 'cost',
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
- metric: `${Math.round(opusRatio * 100)}% Opus`,
147
- recommendation: 'Use /model to switch to Sonnet or Haiku for routine tasks. Reserve Opus for complex architecture decisions and debugging.',
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: 'expensive-sessions',
156
- title: 'Cost spikes detected',
157
- description: `${expensiveSessions.length} sessions cost 3x+ your average ($${avgCost.toFixed(2)}). These outliers often indicate context overflow or runaway tool loops.`,
158
- category: 'cost',
159
- severity: 'warning',
160
- metric: `${expensiveSessions.length} expensive sessions`,
161
- recommendation: 'Use /compact proactively during long sessions. Watch for repeated tool call patterns that indicate the agent is stuck.',
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
- // Cost trend (last 7 days vs previous 7 days)
166
- const sortedByDate = [...sessions].sort((a, b) => b.startTime.localeCompare(a.startTime))
167
- const recentSessions = sortedByDate.filter(s => {
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: 'cost-down',
184
- title: 'Costs trending down',
185
- description: `Your spending dropped ${Math.round((1 - recentCost / olderCost) * 100)}% this week vs last week. You're becoming more efficient with your agent.`,
186
- category: 'cost',
187
- severity: 'achievement',
188
- metric: `-${Math.round((1 - recentCost / olderCost) * 100)}%`,
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 (recentCost > olderCost * 1.5) {
195
+ } else if (readEditRatio > 2.0) {
191
196
  insights.push({
192
- id: 'cost-up',
193
- title: 'Cost spike this week',
194
- description: `Spending is up ${Math.round((recentCost / olderCost - 1) * 100)}% vs last week ($${recentCost.toFixed(0)} vs $${olderCost.toFixed(0)}).`,
195
- category: 'cost',
196
- severity: 'warning',
197
- metric: `+${Math.round((recentCost / olderCost - 1) * 100)}%`,
198
- recommendation: 'Check if long-running sessions or model upgrades are driving the increase.',
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
- // ── Efficiency insights ──
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: 'Many long sessions (2h+)',
212
- description: `${longSessions.length} sessions exceeded 2 hours. Long sessions often hit context limits and lose coherence. Average cost per long session: $${longAvgCost.toFixed(2)}.`,
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: 'tip',
216
+ severity: 'warning',
217
+ craft: 'context',
215
218
  metric: `${longSessions.length} sessions > 2h`,
216
- recommendation: 'Break complex tasks into focused 30-60 min sessions. Use /clear between distinct tasks. Use /compact if you need to continue.',
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
- // Read-before-Edit ratio
221
- const readCalls = (toolUsage['Read'] || 0) + (toolUsage['Grep'] || 0) + (toolUsage['Glob'] || 0)
222
- const editCalls = (toolUsage['Edit'] || 0) + (toolUsage['Write'] || 0)
223
- if (editCalls > 0) {
224
- const ratio = readCalls / editCalls
225
- if (ratio < 0.5 && editCalls > 50) {
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: 'read-before-edit',
228
- title: 'Low read-before-edit ratio',
229
- description: `Your agent reads ${ratio.toFixed(1)}x for every edit. A ratio below 1.0 suggests the agent may be editing without fully understanding the code first, leading to more iterations.`,
230
- category: 'efficiency',
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
- metric: `${ratio.toFixed(1)}x ratio`,
233
- recommendation: 'Encourage the agent to read files before modifying them. Add "always read the file first" to your CLAUDE.md.',
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
- } else if (ratio > 2.0) {
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-read-ratio',
238
- title: 'Strong read-before-edit habit',
239
- description: `Your agent reads ${ratio.toFixed(1)}x for every edit a sign of careful, well-informed modifications. This typically leads to fewer iterations and less rework.`,
240
- category: 'efficiency',
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
- metric: `${ratio.toFixed(1)}x ratio`,
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
- // ── Context insights ──
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
- // High-token sessions (proxy for context overflow)
250
- const highTokenSessions = sessions.filter(s => s.totalTokens > 200000)
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-overflow',
255
- title: 'Frequent high-token sessions',
256
- description: `${Math.round(overflowRate * 100)}% of sessions exceed 200K tokens, suggesting frequent context pressure. This increases cost and can reduce response quality.`,
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
- metric: `${Math.round(overflowRate * 100)}% overflow rate`,
260
- recommendation: 'Use /compact with focus instructions to preserve important context. Use /context to monitor usage. Start fresh sessions for new tasks.',
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
- // ── Tool insights ──
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
- // Bash overuse
267
- const bashCalls = toolUsage['Bash'] || 0
268
- const totalToolCalls = overview.totalToolCalls
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: 'bash-heavy',
273
- title: 'Heavy Bash usage',
274
- description: `${Math.round(bashRatio * 100)}% of tool calls are Bash commands. Some of these may be better handled by dedicated tools (Read instead of cat, Edit instead of sed, Grep instead of grep).`,
275
- category: 'tools',
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
- metric: `${Math.round(bashRatio * 100)}% Bash`,
278
- recommendation: 'Dedicated tools are faster, safer, and easier to review. Add guidance to CLAUDE.md to prefer Read/Edit/Grep over Bash equivalents.',
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
- // Agent/subagent usage
283
- const agentCalls = toolUsage['Agent'] || 0
284
- if (agentCalls === 0 && totalToolCalls > 500) {
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-agents',
287
- title: 'Not using subagents',
288
- description: 'You haven\'t used the Agent tool for parallel research or exploration. Subagents can significantly speed up tasks that require searching across multiple files or investigating multiple approaches.',
289
- category: 'tools',
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
- recommendation: 'Try asking Claude to "use an agent to research X while you work on Y". This parallelizes work and keeps the main context clean.',
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
- } else if (agentCalls > 50) {
530
+ }
531
+
532
+ // Bash overuse
533
+ if (bashRatio > 0.4 && totalToolCalls > 100) {
294
534
  insights.push({
295
- id: 'good-agents',
296
- title: 'Effective subagent usage',
297
- description: `${agentCalls} subagent calls show you're effectively parallelizing work. This keeps the main context lean while delegating research tasks.`,
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: 'achievement',
300
- metric: `${agentCalls} agent calls`,
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
- // ── Habit insights ──
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 coding with your agent every day for ${streaks.current} days straight. Consistency is the key to mastering human-AI collaboration.`,
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 building`,
320
- description: `You're on a ${streaks.current}-day streak. Your longest was ${streaks.longest} days keep going!`,
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
- metric: `${streaks.current} / ${streaks.longest} days`,
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. Start a new one today!`,
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
- metric: `Best: ${streaks.longest} days`,
588
+ craft: 'flow',
589
+ metric: `Best: ${streaks.longest}d`,
333
590
  })
334
591
  }
335
592
 
336
- // Peak productivity time
337
- insights.push({
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: 'Usage ramping up',
354
- description: `You're averaging ${recentPerDay.toFixed(1)} sessions/day this week, up from ${olderPerDay.toFixed(1)} last week. You're leaning more into AI-assisted development.`,
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
- // ── Discovery insights ──
610
+ // ═══════════════════════════════════════════════════════════════════
611
+ // COST INSIGHTS (lower priority — kept but not prominent)
612
+ // ═══════════════════════════════════════════════════════════════════
363
613
 
364
- // Skill/command usage from tool calls
365
- const skillCalls = toolUsage['Skill'] || 0
366
- if (skillCalls === 0 && totalToolCalls > 200) {
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: 'no-skills',
369
- title: 'Unused: Custom Skills',
370
- description: 'You haven\'t used any custom skills yet. Skills are reusable prompt templates that automate repetitive workflows like /simplify for code review or custom deploy scripts.',
371
- category: 'discovery',
642
+ id: 'good-parallel',
643
+ title: 'Strong parallel session usage',
644
+ description: `Averaging ${avgParallelSessions.toFixed(1)} sessions/dayyou'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
- recommendation: 'Try /skills to see available skills. Create your own in .claude/skills/ for tasks you repeat often.',
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 fitness score ──
378
-
379
- let score = 50 // baseline
380
- const achievements = insights.filter(i => i.severity === 'achievement').length
381
- const warnings = insights.filter(i => i.severity === 'warning').length
382
-
383
- score += achievements * 8
384
- score -= warnings * 6
385
-
386
- // Bonus for streaks
387
- score += Math.min(streaks.current, 10) * 2
388
-
389
- // Bonus for good read ratio
390
- if (readCalls > 0 && editCalls > 0 && readCalls / editCalls > 1.5) score += 5
391
-
392
- // Penalty for high overflow rate
393
- if (overflowRate > 0.3) score -= 10
394
-
395
- // Bonus for tool diversity
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
- if (uniqueTools > 10) score += 5
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 = Math.max(0, Math.min(100, 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,