@ginkoai/cli 1.7.2 → 1.8.0

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 (50) hide show
  1. package/dist/commands/insights/index.d.ts +12 -0
  2. package/dist/commands/insights/index.d.ts.map +1 -0
  3. package/dist/commands/insights/index.js +12 -0
  4. package/dist/commands/insights/index.js.map +1 -0
  5. package/dist/commands/insights/insights-command.d.ts +20 -0
  6. package/dist/commands/insights/insights-command.d.ts.map +1 -0
  7. package/dist/commands/insights/insights-command.js +331 -0
  8. package/dist/commands/insights/insights-command.js.map +1 -0
  9. package/dist/commands/start/start-reflection.d.ts.map +1 -1
  10. package/dist/commands/start/start-reflection.js +50 -13
  11. package/dist/commands/start/start-reflection.js.map +1 -1
  12. package/dist/index.js +30 -0
  13. package/dist/index.js.map +1 -1
  14. package/dist/lib/insights/analyzers/anti-patterns.d.ts +32 -0
  15. package/dist/lib/insights/analyzers/anti-patterns.d.ts.map +1 -0
  16. package/dist/lib/insights/analyzers/anti-patterns.js +302 -0
  17. package/dist/lib/insights/analyzers/anti-patterns.js.map +1 -0
  18. package/dist/lib/insights/analyzers/efficiency.d.ts +22 -0
  19. package/dist/lib/insights/analyzers/efficiency.d.ts.map +1 -0
  20. package/dist/lib/insights/analyzers/efficiency.js +311 -0
  21. package/dist/lib/insights/analyzers/efficiency.js.map +1 -0
  22. package/dist/lib/insights/analyzers/index.d.ts +24 -0
  23. package/dist/lib/insights/analyzers/index.d.ts.map +1 -0
  24. package/dist/lib/insights/analyzers/index.js +37 -0
  25. package/dist/lib/insights/analyzers/index.js.map +1 -0
  26. package/dist/lib/insights/analyzers/patterns.d.ts +21 -0
  27. package/dist/lib/insights/analyzers/patterns.d.ts.map +1 -0
  28. package/dist/lib/insights/analyzers/patterns.js +327 -0
  29. package/dist/lib/insights/analyzers/patterns.js.map +1 -0
  30. package/dist/lib/insights/analyzers/quality.d.ts +29 -0
  31. package/dist/lib/insights/analyzers/quality.d.ts.map +1 -0
  32. package/dist/lib/insights/analyzers/quality.js +366 -0
  33. package/dist/lib/insights/analyzers/quality.js.map +1 -0
  34. package/dist/lib/insights/data-collector.d.ts +68 -0
  35. package/dist/lib/insights/data-collector.d.ts.map +1 -0
  36. package/dist/lib/insights/data-collector.js +467 -0
  37. package/dist/lib/insights/data-collector.js.map +1 -0
  38. package/dist/lib/insights/index.d.ts +14 -0
  39. package/dist/lib/insights/index.d.ts.map +1 -0
  40. package/dist/lib/insights/index.js +17 -0
  41. package/dist/lib/insights/index.js.map +1 -0
  42. package/dist/lib/insights/types.d.ts +216 -0
  43. package/dist/lib/insights/types.d.ts.map +1 -0
  44. package/dist/lib/insights/types.js +12 -0
  45. package/dist/lib/insights/types.js.map +1 -0
  46. package/dist/templates/ai-instructions-template.d.ts +1 -0
  47. package/dist/templates/ai-instructions-template.d.ts.map +1 -1
  48. package/dist/templates/ai-instructions-template.js +51 -0
  49. package/dist/templates/ai-instructions-template.js.map +1 -1
  50. package/package.json +1 -1
@@ -0,0 +1,302 @@
1
+ /**
2
+ * @fileType: analyzer
3
+ * @status: current
4
+ * @updated: 2025-12-15
5
+ * @tags: [insights, analyzer, anti-patterns, detection]
6
+ * @related: [../types.ts, ../data-collector.ts]
7
+ * @priority: high
8
+ * @complexity: medium
9
+ * @dependencies: []
10
+ */
11
+ // ============================================================================
12
+ // Configuration
13
+ // ============================================================================
14
+ const THRESHOLDS = {
15
+ // Abandoned task detection (days in progress)
16
+ abandonedTask: {
17
+ warning: 5, // > 5 days without completion
18
+ critical: 10, // > 10 days
19
+ },
20
+ // Silent session detection (sessions without events)
21
+ // Per PATTERN-001: Context is preserved via events, not handoffs
22
+ silentSessions: {
23
+ warning: 2, // > 2 sessions without events
24
+ critical: 4, // > 4 sessions
25
+ },
26
+ // Long silent session (hours without events)
27
+ silentSession: {
28
+ warning: 2, // > 2 hours
29
+ critical: 4, // > 4 hours
30
+ },
31
+ // Repeated gotcha encounters
32
+ repeatedGotcha: {
33
+ warning: 2, // > 2 encounters of same gotcha
34
+ critical: 4, // > 4 encounters
35
+ },
36
+ };
37
+ // ============================================================================
38
+ // Anti-Pattern Detector
39
+ // ============================================================================
40
+ export class AntiPatternDetector {
41
+ category = 'anti-patterns';
42
+ async analyze(data) {
43
+ const insights = [];
44
+ // Detect each anti-pattern
45
+ insights.push(...this.detectAbandonedTasks(data));
46
+ insights.push(...this.detectContextLoss(data));
47
+ insights.push(...this.detectLongSilentSessions(data));
48
+ insights.push(...this.detectRepeatedGotchas(data));
49
+ insights.push(...this.detectScopeCreep(data));
50
+ // If no anti-patterns found, add positive insight
51
+ if (insights.length === 0) {
52
+ insights.push({
53
+ category: this.category,
54
+ severity: 'info',
55
+ title: 'No anti-patterns detected',
56
+ description: 'Your workflow is clean. No concerning patterns found.',
57
+ scoreImpact: 10,
58
+ evidence: [],
59
+ recommendations: [],
60
+ });
61
+ }
62
+ return insights;
63
+ }
64
+ // ===========================================================================
65
+ // Abandoned Tasks Detection
66
+ // ===========================================================================
67
+ detectAbandonedTasks(data) {
68
+ const insights = [];
69
+ const abandonedTasks = data.tasks.abandoned;
70
+ const stuckTasks = data.tasks.inProgress.filter(t => t.daysInProgress > THRESHOLDS.abandonedTask.warning);
71
+ const allProblematicTasks = [...abandonedTasks, ...stuckTasks];
72
+ if (allProblematicTasks.length === 0)
73
+ return insights;
74
+ const criticalTasks = allProblematicTasks.filter(t => t.daysInProgress >= THRESHOLDS.abandonedTask.critical);
75
+ const evidence = allProblematicTasks.slice(0, 5).map(t => ({
76
+ type: 'task',
77
+ id: t.id,
78
+ description: `${t.title} - ${t.daysInProgress} days in progress`,
79
+ }));
80
+ if (criticalTasks.length > 0) {
81
+ insights.push({
82
+ category: this.category,
83
+ severity: 'critical',
84
+ title: 'Critical: Stuck tasks',
85
+ description: `${criticalTasks.length} tasks stuck for 10+ days. These may need to be descoped or unblocked.`,
86
+ metricName: 'stuck_task_count',
87
+ metricValue: criticalTasks.length,
88
+ scoreImpact: -25,
89
+ evidence: evidence.slice(0, 3),
90
+ recommendations: [
91
+ 'Review blockers for each stuck task',
92
+ 'Consider breaking into smaller tasks',
93
+ 'Descope if task is no longer relevant',
94
+ 'Mark as paused if blocked externally',
95
+ ],
96
+ });
97
+ }
98
+ else if (allProblematicTasks.length > 0) {
99
+ insights.push({
100
+ category: this.category,
101
+ severity: 'warning',
102
+ title: 'Tasks in progress too long',
103
+ description: `${allProblematicTasks.length} tasks have been in progress for 5+ days.`,
104
+ metricName: 'stuck_task_count',
105
+ metricValue: allProblematicTasks.length,
106
+ scoreImpact: -15,
107
+ evidence,
108
+ recommendations: [
109
+ 'Review task scope and break down if too large',
110
+ 'Check for blockers or missing information',
111
+ 'Update status if task is actually paused',
112
+ ],
113
+ });
114
+ }
115
+ return insights;
116
+ }
117
+ // ===========================================================================
118
+ // Silent Session Detection (PATTERN-001: Ephemeral Sessions)
119
+ // ===========================================================================
120
+ /**
121
+ * Detects sessions without event logging (silent sessions).
122
+ *
123
+ * Per PATTERN-001: Context is preserved via defensive logging (events),
124
+ * not handoffs. Handoffs are optional. What matters is whether events
125
+ * were captured during the session.
126
+ *
127
+ * A "silent session" with no events means context was lost.
128
+ * A session with events but no handoff is fine - context is in the events.
129
+ */
130
+ detectContextLoss(data) {
131
+ const insights = [];
132
+ // Silent sessions = no events logged (context truly lost)
133
+ const silentSessions = data.sessions.filter(s => s.eventCount === 0);
134
+ const count = silentSessions.length;
135
+ if (count === 0)
136
+ return insights;
137
+ const evidence = silentSessions.slice(0, 5).map(s => ({
138
+ type: 'session',
139
+ id: s.id,
140
+ description: `Silent session (${Math.round(s.durationMinutes)}min, no events)`,
141
+ timestamp: s.startedAt,
142
+ }));
143
+ if (count >= THRESHOLDS.silentSessions.critical) {
144
+ insights.push({
145
+ category: this.category,
146
+ severity: 'warning',
147
+ title: 'Silent sessions detected',
148
+ description: `${count} sessions had no events logged. Context from these sessions is lost.`,
149
+ metricName: 'silent_session_count',
150
+ metricValue: count,
151
+ scoreImpact: -15,
152
+ evidence,
153
+ recommendations: [
154
+ 'Use `ginko log` after completing fixes or features',
155
+ 'Log decisions and insights during the session',
156
+ 'Defensive logging captures context at low pressure',
157
+ ],
158
+ });
159
+ }
160
+ else if (count >= THRESHOLDS.silentSessions.warning) {
161
+ insights.push({
162
+ category: this.category,
163
+ severity: 'suggestion',
164
+ title: 'Some silent sessions',
165
+ description: `${count} sessions had no events logged. Consider logging more.`,
166
+ metricName: 'silent_session_count',
167
+ metricValue: count,
168
+ scoreImpact: -5,
169
+ evidence,
170
+ recommendations: [
171
+ 'Use `ginko log` to capture insights as you work',
172
+ 'Aim for 3-5 events per session',
173
+ ],
174
+ });
175
+ }
176
+ return insights;
177
+ }
178
+ // ===========================================================================
179
+ // Long Silent Sessions Detection
180
+ // ===========================================================================
181
+ detectLongSilentSessions(data) {
182
+ const insights = [];
183
+ // Sessions with long duration but few events
184
+ const silentSessions = data.sessions.filter(s => {
185
+ const eventsPerHour = s.eventCount / (s.durationMinutes / 60);
186
+ return s.durationMinutes > 120 && eventsPerHour < 1;
187
+ });
188
+ if (silentSessions.length === 0)
189
+ return insights;
190
+ const evidence = silentSessions.slice(0, 3).map(s => ({
191
+ type: 'session',
192
+ id: s.id,
193
+ description: `${Math.round(s.durationMinutes / 60)}h session with ${s.eventCount} events`,
194
+ timestamp: s.startedAt,
195
+ }));
196
+ insights.push({
197
+ category: this.category,
198
+ severity: 'suggestion',
199
+ title: 'Long sessions without logging',
200
+ description: `${silentSessions.length} sessions over 2 hours with minimal logging. Insights may be lost.`,
201
+ metricName: 'silent_session_count',
202
+ metricValue: silentSessions.length,
203
+ scoreImpact: -5,
204
+ evidence,
205
+ recommendations: [
206
+ 'Use `ginko log` periodically during long sessions',
207
+ 'Log decisions and insights as you work',
208
+ 'Break into multiple focused sessions',
209
+ ],
210
+ });
211
+ return insights;
212
+ }
213
+ // ===========================================================================
214
+ // Repeated Gotchas Detection
215
+ // ===========================================================================
216
+ detectRepeatedGotchas(data) {
217
+ const insights = [];
218
+ const repeatedGotchas = data.gotchas.filter(g => g.encounters >= THRESHOLDS.repeatedGotcha.warning);
219
+ if (repeatedGotchas.length === 0)
220
+ return insights;
221
+ const criticalRepeats = repeatedGotchas.filter(g => g.encounters >= THRESHOLDS.repeatedGotcha.critical);
222
+ const evidence = repeatedGotchas.map(g => ({
223
+ type: 'gotcha',
224
+ id: g.id,
225
+ description: `${g.title} - encountered ${g.encounters} times`,
226
+ }));
227
+ if (criticalRepeats.length > 0) {
228
+ insights.push({
229
+ category: this.category,
230
+ severity: 'critical',
231
+ title: 'Critical: Gotchas not being avoided',
232
+ description: `${criticalRepeats.length} gotchas encountered 4+ times. Prevention isn't working.`,
233
+ metricName: 'critical_repeat_gotchas',
234
+ metricValue: criticalRepeats.length,
235
+ scoreImpact: -25,
236
+ evidence: evidence.slice(0, 3),
237
+ recommendations: [
238
+ 'Review gotcha documentation for clearer prevention steps',
239
+ 'Add automated checks or linting rules',
240
+ 'Consider refactoring to eliminate the gotcha source',
241
+ ],
242
+ });
243
+ }
244
+ else {
245
+ insights.push({
246
+ category: this.category,
247
+ severity: 'warning',
248
+ title: 'Repeated gotcha encounters',
249
+ description: `${repeatedGotchas.length} gotchas encountered multiple times.`,
250
+ metricName: 'repeat_gotcha_count',
251
+ metricValue: repeatedGotchas.length,
252
+ scoreImpact: -10,
253
+ evidence,
254
+ recommendations: [
255
+ 'Review gotcha docs before starting related work',
256
+ 'Update gotcha descriptions with better prevention',
257
+ 'Consider adding pre-commit checks',
258
+ ],
259
+ });
260
+ }
261
+ return insights;
262
+ }
263
+ // ===========================================================================
264
+ // Scope Creep Detection
265
+ // ===========================================================================
266
+ detectScopeCreep(data) {
267
+ const insights = [];
268
+ // Detect tasks added mid-sprint (tasks without IDs following naming convention)
269
+ // This is a heuristic - actual detection would need more context
270
+ const adhocTasks = data.tasks.inProgress.filter(t => t.id.includes('adhoc') || !t.id.match(/e\d+_s\d+_t\d+/));
271
+ if (adhocTasks.length === 0)
272
+ return insights;
273
+ // Only flag if significant
274
+ const adhocRatio = adhocTasks.length / data.tasks.total;
275
+ if (adhocRatio < 0.2)
276
+ return insights;
277
+ const evidence = adhocTasks.slice(0, 3).map(t => ({
278
+ type: 'task',
279
+ id: t.id,
280
+ description: `Ad-hoc: ${t.title}`,
281
+ }));
282
+ insights.push({
283
+ category: this.category,
284
+ severity: 'suggestion',
285
+ title: 'Scope creep detected',
286
+ description: `${adhocTasks.length} ad-hoc tasks added (${Math.round(adhocRatio * 100)}% of total). Sprint scope may be growing.`,
287
+ metricName: 'adhoc_task_ratio',
288
+ metricValue: Math.round(adhocRatio * 100),
289
+ metricUnit: '%',
290
+ scoreImpact: -5,
291
+ evidence,
292
+ recommendations: [
293
+ 'Review if ad-hoc work should be in sprint scope',
294
+ 'Consider deferring non-critical items',
295
+ 'Track ad-hoc work separately for visibility',
296
+ ],
297
+ });
298
+ return insights;
299
+ }
300
+ }
301
+ export default AntiPatternDetector;
302
+ //# sourceMappingURL=anti-patterns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anti-patterns.js","sourceRoot":"","sources":["../../../../src/lib/insights/analyzers/anti-patterns.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @fileType: analyzer
3
+ * @status: current
4
+ * @updated: 2025-12-15
5
+ * @tags: [insights, analyzer, efficiency, sessions]
6
+ * @related: [../types.ts, ../data-collector.ts]
7
+ * @priority: high
8
+ * @complexity: medium
9
+ * @dependencies: []
10
+ */
11
+ import { InsightAnalyzer, InsightData, RawInsight } from '../types.js';
12
+ export declare class EfficiencyAnalyzer implements InsightAnalyzer {
13
+ category: "efficiency";
14
+ analyze(data: InsightData): Promise<RawInsight[]>;
15
+ private analyzeTimeToFlow;
16
+ private analyzeContextLoadTime;
17
+ private analyzeSessionDuration;
18
+ private analyzeColdStartRatio;
19
+ private estimateAverageTimeToFlow;
20
+ }
21
+ export default EfficiencyAnalyzer;
22
+ //# sourceMappingURL=efficiency.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"efficiency.d.ts","sourceRoot":"","sources":["../../../../src/lib/insights/analyzers/efficiency.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,eAAe,EACf,WAAW,EACX,UAAU,EAGX,MAAM,aAAa,CAAC;AAmCrB,qBAAa,kBAAmB,YAAW,eAAe;IACxD,QAAQ,EAAG,YAAY,CAAU;IAE3B,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IA8BvD,OAAO,CAAC,iBAAiB;IAyEzB,OAAO,CAAC,sBAAsB;IA2D9B,OAAO,CAAC,sBAAsB;IA4D9B,OAAO,CAAC,qBAAqB;IA0D7B,OAAO,CAAC,yBAAyB;CAelC;AAED,eAAe,kBAAkB,CAAC"}
@@ -0,0 +1,311 @@
1
+ /**
2
+ * @fileType: analyzer
3
+ * @status: current
4
+ * @updated: 2025-12-15
5
+ * @tags: [insights, analyzer, efficiency, sessions]
6
+ * @related: [../types.ts, ../data-collector.ts]
7
+ * @priority: high
8
+ * @complexity: medium
9
+ * @dependencies: []
10
+ */
11
+ // ============================================================================
12
+ // Configuration
13
+ // ============================================================================
14
+ const THRESHOLDS = {
15
+ // Time-to-flow (seconds from session start to first event)
16
+ timeToFlow: {
17
+ excellent: 60, // < 60s is excellent
18
+ good: 120, // < 120s is good
19
+ warning: 300, // > 300s is warning
20
+ },
21
+ // Context load time (milliseconds)
22
+ contextLoadTime: {
23
+ excellent: 3000, // < 3s is excellent
24
+ good: 5000, // < 5s is good
25
+ warning: 10000, // > 10s is warning
26
+ },
27
+ // Session duration (minutes)
28
+ sessionDuration: {
29
+ optimal: { min: 60, max: 240 }, // 1-4 hours is optimal
30
+ warning: { max: 360 }, // > 6 hours is warning
31
+ },
32
+ // Cold start ratio (percentage)
33
+ coldStartRatio: {
34
+ good: 30, // < 30% cold starts is good
35
+ warning: 60, // > 60% cold starts is warning
36
+ },
37
+ };
38
+ // ============================================================================
39
+ // Efficiency Analyzer
40
+ // ============================================================================
41
+ export class EfficiencyAnalyzer {
42
+ category = 'efficiency';
43
+ async analyze(data) {
44
+ const insights = [];
45
+ // Skip if no sessions
46
+ if (data.sessions.length === 0) {
47
+ insights.push({
48
+ category: this.category,
49
+ severity: 'info',
50
+ title: 'No session data available',
51
+ description: 'Start using `ginko start` to track your sessions and get efficiency insights.',
52
+ scoreImpact: 0,
53
+ evidence: [],
54
+ recommendations: ['Run `ginko start` at the beginning of each work session'],
55
+ });
56
+ return insights;
57
+ }
58
+ // Analyze each metric
59
+ insights.push(...this.analyzeTimeToFlow(data));
60
+ insights.push(...this.analyzeContextLoadTime(data));
61
+ insights.push(...this.analyzeSessionDuration(data));
62
+ insights.push(...this.analyzeColdStartRatio(data));
63
+ return insights;
64
+ }
65
+ // ===========================================================================
66
+ // Time-to-Flow Analysis
67
+ // ===========================================================================
68
+ analyzeTimeToFlow(data) {
69
+ const insights = [];
70
+ // Calculate average time-to-flow from session start to first event
71
+ const sessionsWithEvents = data.sessions.filter(s => s.eventCount > 0);
72
+ if (sessionsWithEvents.length === 0)
73
+ return insights;
74
+ // Estimate time-to-flow based on session data
75
+ // In reality, this would use actual timestamps from events
76
+ const avgTimeToFlow = this.estimateAverageTimeToFlow(sessionsWithEvents);
77
+ const evidence = sessionsWithEvents.slice(0, 3).map(s => ({
78
+ type: 'session',
79
+ id: s.id,
80
+ description: `Session with ${s.eventCount} events, ${s.flowState} start`,
81
+ timestamp: s.startedAt,
82
+ }));
83
+ if (avgTimeToFlow < THRESHOLDS.timeToFlow.excellent) {
84
+ insights.push({
85
+ category: this.category,
86
+ severity: 'info',
87
+ title: 'Excellent time-to-flow',
88
+ description: `Average ${avgTimeToFlow}s to first productive action. You're getting into flow quickly.`,
89
+ metricName: 'time_to_flow',
90
+ metricValue: avgTimeToFlow,
91
+ metricTarget: THRESHOLDS.timeToFlow.excellent,
92
+ metricUnit: 'seconds',
93
+ scoreImpact: 15,
94
+ evidence,
95
+ recommendations: [],
96
+ });
97
+ }
98
+ else if (avgTimeToFlow < THRESHOLDS.timeToFlow.good) {
99
+ insights.push({
100
+ category: this.category,
101
+ severity: 'info',
102
+ title: 'Good time-to-flow',
103
+ description: `Average ${avgTimeToFlow}s to first productive action.`,
104
+ metricName: 'time_to_flow',
105
+ metricValue: avgTimeToFlow,
106
+ metricTarget: THRESHOLDS.timeToFlow.good,
107
+ metricUnit: 'seconds',
108
+ scoreImpact: 5,
109
+ evidence,
110
+ recommendations: ['Use `ginko handoff` to reduce context rebuild time'],
111
+ });
112
+ }
113
+ else {
114
+ insights.push({
115
+ category: this.category,
116
+ severity: 'suggestion',
117
+ title: 'Slow time-to-flow',
118
+ description: `Average ${avgTimeToFlow}s to first productive action. Consider improving session handoffs.`,
119
+ metricName: 'time_to_flow',
120
+ metricValue: avgTimeToFlow,
121
+ metricTarget: THRESHOLDS.timeToFlow.good,
122
+ metricUnit: 'seconds',
123
+ scoreImpact: -10,
124
+ evidence,
125
+ recommendations: [
126
+ 'Use `ginko handoff` before ending sessions',
127
+ 'Review your CLAUDE.md for faster context loading',
128
+ 'Archive old events with `ginko archive`',
129
+ ],
130
+ });
131
+ }
132
+ return insights;
133
+ }
134
+ // ===========================================================================
135
+ // Context Load Time Analysis
136
+ // ===========================================================================
137
+ analyzeContextLoadTime(data) {
138
+ const insights = [];
139
+ const sessionsWithLoadTime = data.sessions.filter(s => s.contextLoadTimeMs);
140
+ if (sessionsWithLoadTime.length === 0)
141
+ return insights;
142
+ const avgLoadTime = Math.round(sessionsWithLoadTime.reduce((sum, s) => sum + (s.contextLoadTimeMs || 0), 0) /
143
+ sessionsWithLoadTime.length);
144
+ const evidence = sessionsWithLoadTime.slice(0, 3).map(s => ({
145
+ type: 'session',
146
+ id: s.id,
147
+ description: `Context loaded in ${s.contextLoadTimeMs}ms`,
148
+ timestamp: s.startedAt,
149
+ }));
150
+ if (avgLoadTime < THRESHOLDS.contextLoadTime.excellent) {
151
+ insights.push({
152
+ category: this.category,
153
+ severity: 'info',
154
+ title: 'Fast context loading',
155
+ description: `Average ${avgLoadTime}ms context load time. Event-based loading is working well.`,
156
+ metricName: 'context_load_time',
157
+ metricValue: avgLoadTime,
158
+ metricTarget: THRESHOLDS.contextLoadTime.excellent,
159
+ metricUnit: 'ms',
160
+ scoreImpact: 10,
161
+ evidence,
162
+ recommendations: [],
163
+ });
164
+ }
165
+ else if (avgLoadTime > THRESHOLDS.contextLoadTime.warning) {
166
+ insights.push({
167
+ category: this.category,
168
+ severity: 'warning',
169
+ title: 'Slow context loading',
170
+ description: `Average ${avgLoadTime}ms context load time. Consider archiving old events.`,
171
+ metricName: 'context_load_time',
172
+ metricValue: avgLoadTime,
173
+ metricTarget: THRESHOLDS.contextLoadTime.good,
174
+ metricUnit: 'ms',
175
+ scoreImpact: -15,
176
+ evidence,
177
+ recommendations: [
178
+ 'Run `ginko archive` to move old events',
179
+ 'Check network connectivity to graph API',
180
+ 'Review event stream size',
181
+ ],
182
+ });
183
+ }
184
+ return insights;
185
+ }
186
+ // ===========================================================================
187
+ // Session Duration Analysis
188
+ // ===========================================================================
189
+ analyzeSessionDuration(data) {
190
+ const insights = [];
191
+ const durations = data.sessions.map(s => s.durationMinutes);
192
+ const avgDuration = Math.round(durations.reduce((a, b) => a + b, 0) / durations.length);
193
+ const longSessions = data.sessions.filter(s => s.durationMinutes > THRESHOLDS.sessionDuration.warning.max);
194
+ const evidence = longSessions.slice(0, 3).map(s => ({
195
+ type: 'session',
196
+ id: s.id,
197
+ description: `${Math.round(s.durationMinutes / 60)}h session`,
198
+ timestamp: s.startedAt,
199
+ }));
200
+ if (longSessions.length > 0) {
201
+ insights.push({
202
+ category: this.category,
203
+ severity: 'suggestion',
204
+ title: 'Long sessions detected',
205
+ description: `${longSessions.length} sessions exceeded 6 hours. Long sessions may reduce productivity.`,
206
+ metricName: 'avg_session_duration',
207
+ metricValue: avgDuration,
208
+ metricTarget: THRESHOLDS.sessionDuration.optimal.max,
209
+ metricUnit: 'minutes',
210
+ scoreImpact: -5,
211
+ evidence,
212
+ recommendations: [
213
+ 'Consider taking breaks every 2-3 hours',
214
+ 'Use `ginko handoff` to save progress and start fresh',
215
+ 'Break large tasks into smaller chunks',
216
+ ],
217
+ });
218
+ }
219
+ else if (avgDuration >= THRESHOLDS.sessionDuration.optimal.min &&
220
+ avgDuration <= THRESHOLDS.sessionDuration.optimal.max) {
221
+ insights.push({
222
+ category: this.category,
223
+ severity: 'info',
224
+ title: 'Healthy session duration',
225
+ description: `Average ${Math.round(avgDuration / 60)}h sessions. This is in the optimal range for focused work.`,
226
+ metricName: 'avg_session_duration',
227
+ metricValue: avgDuration,
228
+ metricUnit: 'minutes',
229
+ scoreImpact: 5,
230
+ evidence: [],
231
+ recommendations: [],
232
+ });
233
+ }
234
+ return insights;
235
+ }
236
+ // ===========================================================================
237
+ // Cold Start Analysis
238
+ // ===========================================================================
239
+ analyzeColdStartRatio(data) {
240
+ const insights = [];
241
+ const coldStarts = data.sessions.filter(s => s.flowState === 'cold').length;
242
+ const totalSessions = data.sessions.length;
243
+ const coldRatio = Math.round((coldStarts / totalSessions) * 100);
244
+ const evidence = data.sessions
245
+ .filter(s => s.flowState === 'cold')
246
+ .slice(0, 3)
247
+ .map(s => ({
248
+ type: 'session',
249
+ id: s.id,
250
+ description: 'Cold start session',
251
+ timestamp: s.startedAt,
252
+ }));
253
+ if (coldRatio < THRESHOLDS.coldStartRatio.good) {
254
+ insights.push({
255
+ category: this.category,
256
+ severity: 'info',
257
+ title: 'Good session continuity',
258
+ description: `Only ${coldRatio}% cold starts. Your handoffs are preserving context effectively.`,
259
+ metricName: 'cold_start_ratio',
260
+ metricValue: coldRatio,
261
+ metricTarget: THRESHOLDS.coldStartRatio.good,
262
+ metricUnit: '%',
263
+ scoreImpact: 10,
264
+ evidence: [],
265
+ recommendations: [],
266
+ });
267
+ }
268
+ else if (coldRatio > THRESHOLDS.coldStartRatio.warning) {
269
+ insights.push({
270
+ category: this.category,
271
+ severity: 'warning',
272
+ title: 'High cold start ratio',
273
+ description: `${coldRatio}% of sessions are cold starts. Context is being lost between sessions.`,
274
+ metricName: 'cold_start_ratio',
275
+ metricValue: coldRatio,
276
+ metricTarget: THRESHOLDS.coldStartRatio.good,
277
+ metricUnit: '%',
278
+ scoreImpact: -15,
279
+ evidence,
280
+ recommendations: [
281
+ 'Always run `ginko handoff` before ending a session',
282
+ 'Review session logs for completeness',
283
+ 'Consider shorter, more focused sessions',
284
+ ],
285
+ });
286
+ }
287
+ return insights;
288
+ }
289
+ // ===========================================================================
290
+ // Helper Methods
291
+ // ===========================================================================
292
+ estimateAverageTimeToFlow(sessions) {
293
+ // Estimate based on flow state
294
+ // Hot/warm starts are faster than cold starts
295
+ let totalSeconds = 0;
296
+ for (const session of sessions) {
297
+ if (session.flowState === 'hot') {
298
+ totalSeconds += 30;
299
+ }
300
+ else if (session.flowState === 'warm') {
301
+ totalSeconds += 60;
302
+ }
303
+ else {
304
+ totalSeconds += 120;
305
+ }
306
+ }
307
+ return Math.round(totalSeconds / sessions.length);
308
+ }
309
+ }
310
+ export default EfficiencyAnalyzer;
311
+ //# sourceMappingURL=efficiency.js.map