@mrclrchtr/supi-insights 0.1.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 (31) hide show
  1. package/README.md +234 -0
  2. package/node_modules/@mrclrchtr/supi-core/README.md +90 -0
  3. package/node_modules/@mrclrchtr/supi-core/package.json +30 -0
  4. package/node_modules/@mrclrchtr/supi-core/src/config-settings.ts +76 -0
  5. package/node_modules/@mrclrchtr/supi-core/src/config.ts +186 -0
  6. package/node_modules/@mrclrchtr/supi-core/src/context-messages.ts +119 -0
  7. package/node_modules/@mrclrchtr/supi-core/src/context-provider-registry.ts +36 -0
  8. package/node_modules/@mrclrchtr/supi-core/src/context-tag.ts +31 -0
  9. package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +255 -0
  10. package/node_modules/@mrclrchtr/supi-core/src/index.ts +83 -0
  11. package/node_modules/@mrclrchtr/supi-core/src/project-roots.ts +170 -0
  12. package/node_modules/@mrclrchtr/supi-core/src/registry-utils.ts +54 -0
  13. package/node_modules/@mrclrchtr/supi-core/src/session-utils.ts +29 -0
  14. package/node_modules/@mrclrchtr/supi-core/src/settings-command.ts +15 -0
  15. package/node_modules/@mrclrchtr/supi-core/src/settings-registry.ts +41 -0
  16. package/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +226 -0
  17. package/node_modules/@mrclrchtr/supi-core/src/terminal.ts +60 -0
  18. package/package.json +47 -0
  19. package/src/aggregator.ts +245 -0
  20. package/src/cache.ts +94 -0
  21. package/src/extractor.ts +189 -0
  22. package/src/generator.ts +395 -0
  23. package/src/html.ts +481 -0
  24. package/src/index.ts +1 -0
  25. package/src/insights.ts +416 -0
  26. package/src/parser.ts +373 -0
  27. package/src/report.css +411 -0
  28. package/src/report.js +35 -0
  29. package/src/scanner.ts +13 -0
  30. package/src/types.ts +114 -0
  31. package/src/utils.ts +265 -0
package/src/report.css ADDED
@@ -0,0 +1,411 @@
1
+ /* biome-ignore lint/nursery/noExcessiveLinesPerFile: report stylesheet is shipped as one runtime asset. */
2
+ * {
3
+ box-sizing: border-box;
4
+ margin: 0;
5
+ padding: 0;
6
+ }
7
+ body {
8
+ font-family: "Inter", -apple-system, BlinkMacSystemFont, sans-serif;
9
+ background: #f8fafc;
10
+ color: #334155;
11
+ line-height: 1.65;
12
+ padding: 48px 24px;
13
+ }
14
+ .container {
15
+ max-width: 800px;
16
+ margin: 0 auto;
17
+ }
18
+ h1 {
19
+ font-size: 32px;
20
+ font-weight: 700;
21
+ color: #0f172a;
22
+ margin-bottom: 8px;
23
+ }
24
+ h2 {
25
+ font-size: 20px;
26
+ font-weight: 600;
27
+ color: #0f172a;
28
+ margin-top: 48px;
29
+ margin-bottom: 16px;
30
+ }
31
+ .subtitle {
32
+ color: #64748b;
33
+ font-size: 15px;
34
+ margin-bottom: 32px;
35
+ }
36
+ .stats-row {
37
+ display: flex;
38
+ gap: 24px;
39
+ margin-bottom: 40px;
40
+ padding: 20px 0;
41
+ border-top: 1px solid #e2e8f0;
42
+ border-bottom: 1px solid #e2e8f0;
43
+ flex-wrap: wrap;
44
+ }
45
+ .stat {
46
+ text-align: center;
47
+ }
48
+ .stat-value {
49
+ font-size: 24px;
50
+ font-weight: 700;
51
+ color: #0f172a;
52
+ }
53
+ .stat-label {
54
+ font-size: 11px;
55
+ color: #64748b;
56
+ text-transform: uppercase;
57
+ }
58
+ .at-a-glance {
59
+ background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
60
+ border: 1px solid #f59e0b;
61
+ border-radius: 12px;
62
+ padding: 20px 24px;
63
+ margin-bottom: 32px;
64
+ }
65
+ .glance-title {
66
+ font-size: 16px;
67
+ font-weight: 700;
68
+ color: #92400e;
69
+ margin-bottom: 16px;
70
+ }
71
+ .glance-sections {
72
+ display: flex;
73
+ flex-direction: column;
74
+ gap: 12px;
75
+ }
76
+ .glance-section {
77
+ font-size: 14px;
78
+ color: #78350f;
79
+ line-height: 1.6;
80
+ }
81
+ .glance-section strong {
82
+ color: #92400e;
83
+ }
84
+ .project-areas {
85
+ display: flex;
86
+ flex-direction: column;
87
+ gap: 12px;
88
+ margin-bottom: 32px;
89
+ }
90
+ .project-area {
91
+ background: white;
92
+ border: 1px solid #e2e8f0;
93
+ border-radius: 8px;
94
+ padding: 16px;
95
+ }
96
+ .area-header {
97
+ display: flex;
98
+ justify-content: space-between;
99
+ align-items: center;
100
+ margin-bottom: 8px;
101
+ }
102
+ .area-name {
103
+ font-weight: 600;
104
+ font-size: 15px;
105
+ color: #0f172a;
106
+ }
107
+ .area-count {
108
+ font-size: 12px;
109
+ color: #64748b;
110
+ background: #f1f5f9;
111
+ padding: 2px 8px;
112
+ border-radius: 4px;
113
+ }
114
+ .area-desc {
115
+ font-size: 14px;
116
+ color: #475569;
117
+ line-height: 1.5;
118
+ }
119
+ .narrative {
120
+ background: white;
121
+ border: 1px solid #e2e8f0;
122
+ border-radius: 8px;
123
+ padding: 20px;
124
+ margin-bottom: 24px;
125
+ }
126
+ .narrative p {
127
+ margin-bottom: 12px;
128
+ font-size: 14px;
129
+ color: #475569;
130
+ line-height: 1.7;
131
+ }
132
+ .key-insight {
133
+ background: #f0fdf4;
134
+ border: 1px solid #bbf7d0;
135
+ border-radius: 8px;
136
+ padding: 12px 16px;
137
+ margin-top: 12px;
138
+ font-size: 14px;
139
+ color: #166534;
140
+ }
141
+ .section-intro {
142
+ font-size: 14px;
143
+ color: #64748b;
144
+ margin-bottom: 16px;
145
+ }
146
+ .big-wins {
147
+ display: flex;
148
+ flex-direction: column;
149
+ gap: 12px;
150
+ margin-bottom: 24px;
151
+ }
152
+ .big-win {
153
+ background: #f0fdf4;
154
+ border: 1px solid #bbf7d0;
155
+ border-radius: 8px;
156
+ padding: 16px;
157
+ }
158
+ .big-win-title {
159
+ font-weight: 600;
160
+ font-size: 15px;
161
+ color: #166534;
162
+ margin-bottom: 8px;
163
+ }
164
+ .big-win-desc {
165
+ font-size: 14px;
166
+ color: #15803d;
167
+ line-height: 1.5;
168
+ }
169
+ .friction-categories {
170
+ display: flex;
171
+ flex-direction: column;
172
+ gap: 16px;
173
+ margin-bottom: 24px;
174
+ }
175
+ .friction-category {
176
+ background: #fef2f2;
177
+ border: 1px solid #fca5a5;
178
+ border-radius: 8px;
179
+ padding: 16px;
180
+ }
181
+ .friction-title {
182
+ font-weight: 600;
183
+ font-size: 15px;
184
+ color: #991b1b;
185
+ margin-bottom: 6px;
186
+ }
187
+ .friction-desc {
188
+ font-size: 13px;
189
+ color: #7f1d1d;
190
+ margin-bottom: 10px;
191
+ }
192
+ .friction-examples {
193
+ margin: 0 0 0 20px;
194
+ font-size: 13px;
195
+ color: #334155;
196
+ }
197
+ .friction-examples li {
198
+ margin-bottom: 4px;
199
+ }
200
+ .claude-md-section {
201
+ background: #eff6ff;
202
+ border: 1px solid #bfdbfe;
203
+ border-radius: 8px;
204
+ padding: 16px;
205
+ margin-bottom: 20px;
206
+ }
207
+ .claude-md-item {
208
+ padding: 10px 0;
209
+ border-bottom: 1px solid #dbeafe;
210
+ }
211
+ .claude-md-item:last-child {
212
+ border-bottom: none;
213
+ }
214
+ .cmd-code {
215
+ background: white;
216
+ padding: 8px 12px;
217
+ border-radius: 4px;
218
+ font-size: 12px;
219
+ color: #1e40af;
220
+ border: 1px solid #bfdbfe;
221
+ font-family: monospace;
222
+ display: block;
223
+ white-space: pre-wrap;
224
+ word-break: break-word;
225
+ }
226
+ .cmd-why {
227
+ font-size: 12px;
228
+ color: #64748b;
229
+ margin-top: 4px;
230
+ }
231
+ .features-section,
232
+ .patterns-section {
233
+ display: flex;
234
+ flex-direction: column;
235
+ gap: 12px;
236
+ margin: 16px 0;
237
+ }
238
+ .feature-card {
239
+ background: #f0fdf4;
240
+ border: 1px solid #86efac;
241
+ border-radius: 8px;
242
+ padding: 16px;
243
+ }
244
+ .pattern-card {
245
+ background: #f0f9ff;
246
+ border: 1px solid #7dd3fc;
247
+ border-radius: 8px;
248
+ padding: 16px;
249
+ }
250
+ .feature-title,
251
+ .pattern-title {
252
+ font-weight: 600;
253
+ font-size: 15px;
254
+ color: #0f172a;
255
+ margin-bottom: 6px;
256
+ }
257
+ .feature-oneliner {
258
+ font-size: 14px;
259
+ color: #475569;
260
+ margin-bottom: 8px;
261
+ }
262
+ .pattern-summary {
263
+ font-size: 14px;
264
+ color: #475569;
265
+ margin-bottom: 8px;
266
+ }
267
+ .feature-why,
268
+ .pattern-detail {
269
+ font-size: 13px;
270
+ color: #334155;
271
+ line-height: 1.5;
272
+ }
273
+ .example-code,
274
+ .copyable-prompt {
275
+ display: block;
276
+ background: #f1f5f9;
277
+ padding: 8px 12px;
278
+ border-radius: 4px;
279
+ font-family: monospace;
280
+ font-size: 12px;
281
+ color: #334155;
282
+ margin-top: 8px;
283
+ white-space: pre-wrap;
284
+ }
285
+ .charts-row {
286
+ display: grid;
287
+ grid-template-columns: 1fr 1fr;
288
+ gap: 24px;
289
+ margin: 24px 0;
290
+ }
291
+ .chart-card {
292
+ background: white;
293
+ border: 1px solid #e2e8f0;
294
+ border-radius: 8px;
295
+ padding: 16px;
296
+ }
297
+ .chart-title {
298
+ font-size: 12px;
299
+ font-weight: 600;
300
+ color: #64748b;
301
+ text-transform: uppercase;
302
+ margin-bottom: 12px;
303
+ }
304
+ .bar-row {
305
+ display: flex;
306
+ align-items: center;
307
+ margin-bottom: 6px;
308
+ }
309
+ .bar-label {
310
+ width: 120px;
311
+ font-size: 11px;
312
+ color: #475569;
313
+ flex-shrink: 0;
314
+ overflow: hidden;
315
+ text-overflow: ellipsis;
316
+ white-space: nowrap;
317
+ }
318
+ .bar-track {
319
+ flex: 1;
320
+ height: 6px;
321
+ background: #f1f5f9;
322
+ border-radius: 3px;
323
+ margin: 0 8px;
324
+ }
325
+ .bar-fill {
326
+ height: 100%;
327
+ border-radius: 3px;
328
+ }
329
+ .bar-value {
330
+ width: 28px;
331
+ font-size: 11px;
332
+ font-weight: 500;
333
+ color: #64748b;
334
+ text-align: right;
335
+ }
336
+ .empty {
337
+ color: #94a3b8;
338
+ font-size: 13px;
339
+ }
340
+ .horizon-section {
341
+ display: flex;
342
+ flex-direction: column;
343
+ gap: 16px;
344
+ }
345
+ .horizon-card {
346
+ background: linear-gradient(135deg, #faf5ff 0%, #f5f3ff 100%);
347
+ border: 1px solid #c4b5fd;
348
+ border-radius: 8px;
349
+ padding: 16px;
350
+ }
351
+ .horizon-title {
352
+ font-weight: 600;
353
+ font-size: 15px;
354
+ color: #5b21b6;
355
+ margin-bottom: 8px;
356
+ }
357
+ .horizon-possible {
358
+ font-size: 14px;
359
+ color: #334155;
360
+ margin-bottom: 10px;
361
+ line-height: 1.5;
362
+ }
363
+ .horizon-tip {
364
+ font-size: 13px;
365
+ color: #6b21a8;
366
+ background: rgba(255, 255, 255, 0.6);
367
+ padding: 8px 12px;
368
+ border-radius: 4px;
369
+ }
370
+ .fun-ending {
371
+ background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
372
+ border: 1px solid #fbbf24;
373
+ border-radius: 12px;
374
+ padding: 24px;
375
+ margin-top: 40px;
376
+ text-align: center;
377
+ }
378
+ .fun-headline {
379
+ font-size: 18px;
380
+ font-weight: 600;
381
+ color: #78350f;
382
+ margin-bottom: 8px;
383
+ }
384
+ .fun-detail {
385
+ font-size: 14px;
386
+ color: #92400e;
387
+ }
388
+ .failure-banner {
389
+ background: #fef2f2;
390
+ border: 1px solid #fca5a5;
391
+ border-radius: 8px;
392
+ padding: 12px 16px;
393
+ margin-bottom: 24px;
394
+ font-size: 13px;
395
+ color: #991b1b;
396
+ display: flex;
397
+ flex-direction: column;
398
+ gap: 4px;
399
+ }
400
+ .failure-hint {
401
+ font-size: 12px;
402
+ color: #64748b;
403
+ }
404
+ @media (max-width: 640px) {
405
+ .charts-row {
406
+ grid-template-columns: 1fr;
407
+ }
408
+ .stats-row {
409
+ justify-content: center;
410
+ }
411
+ }
package/src/report.js ADDED
@@ -0,0 +1,35 @@
1
+ const rawHourCounts = __HOUR_COUNTS_JSON__;
2
+ function updateHourHistogram(utcOffset) {
3
+ const periods = [
4
+ { label: "Morning (6-12)", range: [6, 7, 8, 9, 10, 11] },
5
+ { label: "Afternoon (12-18)", range: [12, 13, 14, 15, 16, 17] },
6
+ { label: "Evening (18-24)", range: [18, 19, 20, 21, 22, 23] },
7
+ { label: "Night (0-6)", range: [0, 1, 2, 3, 4, 5] },
8
+ ];
9
+ const adjustedCounts = {};
10
+ for (const [hour, count] of Object.entries(rawHourCounts)) {
11
+ const newHour = (parseInt(hour, 10) + utcOffset + 24) % 24;
12
+ adjustedCounts[newHour] = (adjustedCounts[newHour] || 0) + count;
13
+ }
14
+ const periodCounts = periods.map((p) => ({
15
+ label: p.label,
16
+ count: p.range.reduce((sum, h) => sum + (adjustedCounts[h] || 0), 0),
17
+ }));
18
+ const maxCount = Math.max(...periodCounts.map((p) => p.count)) || 1;
19
+ const container = document.getElementById("hour-histogram");
20
+ if (!container) return;
21
+ container.innerHTML = periodCounts
22
+ .map(
23
+ (p) => `
24
+ <div class="bar-row">
25
+ <div class="bar-label">${p.label}</div>
26
+ <div class="bar-track"><div class="bar-fill" style="width:${(p.count / maxCount) * 100}%;background:#8b5cf6"></div></div>
27
+ <div class="bar-value">${p.count}</div>
28
+ </div>
29
+ `,
30
+ )
31
+ .join("");
32
+ }
33
+ document.getElementById("timezone-select")?.addEventListener("change", function () {
34
+ updateHourHistogram(parseInt(this.value, 10));
35
+ });
package/src/scanner.ts ADDED
@@ -0,0 +1,13 @@
1
+ // Session scanner — discover and list PI sessions
2
+
3
+ import type { SessionInfo } from "@earendil-works/pi-coding-agent";
4
+ import { SessionManager } from "@earendil-works/pi-coding-agent";
5
+
6
+ export async function scanAllSessions(
7
+ onProgress?: (loaded: number, total: number) => void,
8
+ ): Promise<SessionInfo[]> {
9
+ const sessions = await SessionManager.listAll(onProgress);
10
+ // Sort by modified date descending (most recent first)
11
+ sessions.sort((a, b) => b.modified.getTime() - a.modified.getTime());
12
+ return sessions;
13
+ }
package/src/types.ts ADDED
@@ -0,0 +1,114 @@
1
+ // Shared types for supi-insights
2
+
3
+ export type SessionMeta = {
4
+ sessionId: string;
5
+ projectPath: string;
6
+ startTime: string;
7
+ durationMinutes: number;
8
+ userMessageCount: number;
9
+ assistantMessageCount: number;
10
+ toolCounts: Record<string, number>;
11
+ languages: Record<string, number>;
12
+ gitCommits: number;
13
+ gitPushes: number;
14
+ inputTokens: number;
15
+ outputTokens: number;
16
+ firstPrompt: string;
17
+ summary?: string;
18
+ userInterruptions: number;
19
+ userResponseTimes: number[];
20
+ toolErrors: number;
21
+ toolErrorCategories: Record<string, number>;
22
+ usesTaskAgent: boolean;
23
+ usesMcp: boolean;
24
+ usesWebSearch: boolean;
25
+ usesWebFetch: boolean;
26
+ linesAdded: number;
27
+ linesRemoved: number;
28
+ filesModified: number;
29
+ messageHours: number[];
30
+ userMessageTimestamps: string[];
31
+ };
32
+
33
+ export type SessionFacets = {
34
+ sessionId: string;
35
+ underlyingGoal: string;
36
+ goalCategories: Record<string, number>;
37
+ outcome: string;
38
+ userSatisfactionCounts: Record<string, number>;
39
+ claudeHelpfulness: string;
40
+ sessionType: string;
41
+ frictionCounts: Record<string, number>;
42
+ frictionDetail: string;
43
+ primarySuccess: string;
44
+ briefSummary: string;
45
+ userInstructionsToClaude?: string[];
46
+ };
47
+
48
+ export type AggregatedData = {
49
+ totalSessions: number;
50
+ totalSessionsScanned?: number;
51
+ sessionsWithFacets: number;
52
+ dateRange: { start: string; end: string };
53
+ totalMessages: number;
54
+ totalDurationHours: number;
55
+ totalInputTokens: number;
56
+ totalOutputTokens: number;
57
+ toolCounts: Record<string, number>;
58
+ languages: Record<string, number>;
59
+ gitCommits: number;
60
+ gitPushes: number;
61
+ projects: Record<string, number>;
62
+ goalCategories: Record<string, number>;
63
+ outcomes: Record<string, number>;
64
+ satisfaction: Record<string, number>;
65
+ helpfulness: Record<string, number>;
66
+ sessionTypes: Record<string, number>;
67
+ friction: Record<string, number>;
68
+ success: Record<string, number>;
69
+ sessionSummaries: Array<{
70
+ id: string;
71
+ date: string;
72
+ summary: string;
73
+ goal?: string;
74
+ }>;
75
+ totalInterruptions: number;
76
+ totalToolErrors: number;
77
+ toolErrorCategories: Record<string, number>;
78
+ userResponseTimes: number[];
79
+ medianResponseTime: number;
80
+ avgResponseTime: number;
81
+ sessionsUsingTaskAgent: number;
82
+ sessionsUsingMcp: number;
83
+ sessionsUsingWebSearch: number;
84
+ sessionsUsingWebFetch: number;
85
+ totalLinesAdded: number;
86
+ totalLinesRemoved: number;
87
+ totalFilesModified: number;
88
+ daysActive: number;
89
+ messagesPerDay: number;
90
+ messageHours: number[];
91
+ multiClauding: {
92
+ overlapEvents: number;
93
+ sessionsInvolved: number;
94
+ userMessagesDuring: number;
95
+ };
96
+ /** Number of sessions attempted for LLM facet extraction. */
97
+ facetExtractionAttempted: number;
98
+ /** Number of sessions where LLM facet extraction failed. */
99
+ facetExtractionFailed: number;
100
+ /** List of insight section names that failed to generate. */
101
+ insightSectionsFailed: string[];
102
+ };
103
+
104
+ export type InsightSectionName =
105
+ | "projectAreas"
106
+ | "interactionStyle"
107
+ | "whatWorks"
108
+ | "frictionAnalysis"
109
+ | "suggestions"
110
+ | "onTheHorizon"
111
+ | "atAGlance"
112
+ | "funEnding";
113
+
114
+ export type InsightResults = Partial<Record<InsightSectionName, unknown>>;