@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.
- package/dist/commands/insights/index.d.ts +12 -0
- package/dist/commands/insights/index.d.ts.map +1 -0
- package/dist/commands/insights/index.js +12 -0
- package/dist/commands/insights/index.js.map +1 -0
- package/dist/commands/insights/insights-command.d.ts +20 -0
- package/dist/commands/insights/insights-command.d.ts.map +1 -0
- package/dist/commands/insights/insights-command.js +331 -0
- package/dist/commands/insights/insights-command.js.map +1 -0
- package/dist/commands/start/start-reflection.d.ts.map +1 -1
- package/dist/commands/start/start-reflection.js +50 -13
- package/dist/commands/start/start-reflection.js.map +1 -1
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/insights/analyzers/anti-patterns.d.ts +32 -0
- package/dist/lib/insights/analyzers/anti-patterns.d.ts.map +1 -0
- package/dist/lib/insights/analyzers/anti-patterns.js +302 -0
- package/dist/lib/insights/analyzers/anti-patterns.js.map +1 -0
- package/dist/lib/insights/analyzers/efficiency.d.ts +22 -0
- package/dist/lib/insights/analyzers/efficiency.d.ts.map +1 -0
- package/dist/lib/insights/analyzers/efficiency.js +311 -0
- package/dist/lib/insights/analyzers/efficiency.js.map +1 -0
- package/dist/lib/insights/analyzers/index.d.ts +24 -0
- package/dist/lib/insights/analyzers/index.d.ts.map +1 -0
- package/dist/lib/insights/analyzers/index.js +37 -0
- package/dist/lib/insights/analyzers/index.js.map +1 -0
- package/dist/lib/insights/analyzers/patterns.d.ts +21 -0
- package/dist/lib/insights/analyzers/patterns.d.ts.map +1 -0
- package/dist/lib/insights/analyzers/patterns.js +327 -0
- package/dist/lib/insights/analyzers/patterns.js.map +1 -0
- package/dist/lib/insights/analyzers/quality.d.ts +29 -0
- package/dist/lib/insights/analyzers/quality.d.ts.map +1 -0
- package/dist/lib/insights/analyzers/quality.js +366 -0
- package/dist/lib/insights/analyzers/quality.js.map +1 -0
- package/dist/lib/insights/data-collector.d.ts +68 -0
- package/dist/lib/insights/data-collector.d.ts.map +1 -0
- package/dist/lib/insights/data-collector.js +467 -0
- package/dist/lib/insights/data-collector.js.map +1 -0
- package/dist/lib/insights/index.d.ts +14 -0
- package/dist/lib/insights/index.d.ts.map +1 -0
- package/dist/lib/insights/index.js +17 -0
- package/dist/lib/insights/index.js.map +1 -0
- package/dist/lib/insights/types.d.ts +216 -0
- package/dist/lib/insights/types.d.ts.map +1 -0
- package/dist/lib/insights/types.js +12 -0
- package/dist/lib/insights/types.js.map +1 -0
- package/dist/templates/ai-instructions-template.d.ts +1 -0
- package/dist/templates/ai-instructions-template.d.ts.map +1 -1
- package/dist/templates/ai-instructions-template.js +51 -0
- package/dist/templates/ai-instructions-template.js.map +1 -1
- 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
|