@mrclrchtr/supi-insights 4.7.0 → 4.9.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 (30) hide show
  1. package/node_modules/@mrclrchtr/supi-core/README.md +29 -35
  2. package/node_modules/@mrclrchtr/supi-core/package.json +4 -7
  3. package/node_modules/@mrclrchtr/supi-core/src/api.ts +4 -6
  4. package/node_modules/@mrclrchtr/supi-core/src/config/config.ts +0 -20
  5. package/node_modules/@mrclrchtr/supi-core/src/config/prompt-surface.ts +7 -1
  6. package/node_modules/@mrclrchtr/supi-core/src/config.ts +0 -1
  7. package/node_modules/@mrclrchtr/supi-core/src/context.ts +1 -9
  8. package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +8 -0
  9. package/node_modules/@mrclrchtr/supi-core/src/debug-timing.ts +107 -0
  10. package/node_modules/@mrclrchtr/supi-core/src/index.ts +4 -6
  11. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +54 -28
  12. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +91 -125
  13. package/node_modules/@mrclrchtr/supi-core/src/settings.ts +10 -7
  14. package/package.json +2 -5
  15. package/src/generator.ts +44 -147
  16. package/src/html.ts +8 -55
  17. package/src/insight-schemas.ts +91 -0
  18. package/src/insights.ts +33 -32
  19. package/src/parser.ts +24 -5
  20. package/src/types.ts +4 -10
  21. package/node_modules/@mrclrchtr/supi-core/src/context/context-messages.ts +0 -119
  22. package/node_modules/@mrclrchtr/supi-core/src/progress-widget.ts +0 -189
  23. package/node_modules/@mrclrchtr/supi-core/src/settings/scoped-settings-list.ts +0 -373
  24. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +0 -102
  25. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-command.ts +0 -15
  26. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-submenus.ts +0 -141
  27. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +0 -118
  28. package/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +0 -3
  29. package/node_modules/@mrclrchtr/supi-core/src/tool-framework.ts +0 -192
  30. package/src/api.ts +0 -1
@@ -1,26 +1,19 @@
1
- // Declarative settings schema for SuPi extensions.
1
+ // Fixed SuPi-config adapter for the canonical settings module interface.
2
2
  //
3
- // Replaces the imperative config-backed buildItems/persistChange contribution
4
- // with a declarative field descriptor model. The shared settings module owns
5
- // scope inheritance, source-state resolution, value rendering, persistence,
6
- // and Inherit/Reset-to-default actions.
3
+ // Declarative field descriptors let the adapter own scope inheritance,
4
+ // source-state resolution, value rendering, persistence, and Unset actions.
7
5
  //
8
6
  // Custom fields remain for nested or unusual config; they report the same
9
7
  // source state as declarative flat fields.
10
8
 
11
- import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
9
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
12
10
  import type { Component } from "@earendil-works/pi-tui";
13
11
  import {
14
12
  loadSupiConfigSectionForScope,
15
13
  removeSupiConfigKey,
16
14
  writeSupiConfig,
17
15
  } from "../config/config.ts";
18
- import {
19
- isSettingsContributionCollector,
20
- type SettingsScope,
21
- type SettingsSection,
22
- SUPI_SETTINGS_COLLECT_EVENT,
23
- } from "./settings-registry.ts";
16
+ import type { SettingsApplyResult, SettingsModule, SettingsScope } from "./settings-registry.ts";
24
17
 
25
18
  // ── Types ──────────────────────────────────────────────────────────────────
26
19
 
@@ -52,10 +45,7 @@ export interface ConfigHelpers {
52
45
  // ── Field actions ─────────────────────────────────────────────────────────
53
46
 
54
47
  /** A user-initiated action on a settings row. */
55
- export type SettingsFieldAction =
56
- | { kind: "set"; value: string }
57
- | { kind: "inherit" }
58
- | { kind: "resetToDefault" };
48
+ export type SettingsAction = { kind: "set"; value: string } | { kind: "unset" };
59
49
 
60
50
  // ── Field descriptors ─────────────────────────────────────────────────────
61
51
 
@@ -153,15 +143,15 @@ export interface CustomField extends BaseField {
153
143
  ctx?: ExtensionContext,
154
144
  ) => Component;
155
145
  /**
156
- * Persist handler called on set/inherit/resetToDefault actions.
146
+ * Persist handler called on set or unset actions.
157
147
  * Required for custom fields so they can write their nested config.
158
148
  */
159
149
  persist: (
160
150
  scope: SettingsScope,
161
151
  cwd: string,
162
- action: SettingsFieldAction,
152
+ action: SettingsAction,
163
153
  helpers: ConfigHelpers,
164
- ) => void;
154
+ ) => void | Promise<void>;
165
155
  }
166
156
 
167
157
  /** Union of all supported field kinds. */
@@ -176,8 +166,8 @@ export type SettingsField =
176
166
 
177
167
  // ── Contribution options ──────────────────────────────────────────────────
178
168
 
179
- /** Options for registerDeclarativeSettings. */
180
- export interface DeclarativeSettingsOptions {
169
+ /** Options for the fixed SuPi-config settings adapter. */
170
+ export interface ConfigSettingsOptions {
181
171
  /** Stable contribution identifier — e.g. "lsp", "claude-md". */
182
172
  id: string;
183
173
  /** Human-readable label shown in the UI. */
@@ -194,7 +184,7 @@ export interface DeclarativeSettingsOptions {
194
184
  homeDir?: string;
195
185
  }
196
186
 
197
- // ── Scoped section interface ─────────────────────────────────────────────
187
+ // ── Source-aware row interface ───────────────────────────────────────────
198
188
 
199
189
  /** Resolved value for one field in one scope. */
200
190
  export interface ScopedFieldValue {
@@ -305,14 +295,14 @@ function createConfigHelpers(
305
295
  };
306
296
  }
307
297
 
308
- // ── Scoped section factory ────────────────────────────────────────────────
298
+ // ── Fixed config adapter ─────────────────────────────────────────────────
309
299
 
310
300
  interface NotifyAfterPersistInput {
311
- options: DeclarativeSettingsOptions;
301
+ options: ConfigSettingsOptions;
312
302
  field: SettingsField;
313
303
  scope: SettingsScope;
314
304
  cwd: string;
315
- action: SettingsFieldAction;
305
+ action: SettingsAction;
316
306
  storedValue: unknown;
317
307
  ctx?: ExtensionContext;
318
308
  }
@@ -354,86 +344,86 @@ function notifyAfterPersist(input: NotifyAfterPersistInput): void {
354
344
  options.afterPersist(change);
355
345
  }
356
346
 
357
- function toDeclarativeSection(options: DeclarativeSettingsOptions): SettingsSection {
347
+ function resolveConfigRows(
348
+ options: ConfigSettingsOptions,
349
+ scope: SettingsScope,
350
+ cwd: string,
351
+ ctx?: ExtensionContext,
352
+ ): ScopedFieldValue[] {
358
353
  const defaults = options.defaults as Record<string, unknown>;
354
+ const projectRaw = loadSupiConfigSectionForScope(options.section, cwd, {
355
+ scope: "project",
356
+ homeDir: options.homeDir,
357
+ });
358
+ const globalRaw = loadSupiConfigSectionForScope(options.section, cwd, {
359
+ scope: "global",
360
+ homeDir: options.homeDir,
361
+ });
362
+
363
+ return options.fields.map((field) => {
364
+ if (field.kind === "custom") {
365
+ const resolved = field.resolve(scope, cwd, ctx);
366
+ return {
367
+ field,
368
+ displayValue: resolved.displayValue
369
+ ? sourceBadge(resolved.displayValue, resolved.source)
370
+ : "",
371
+ editValue: resolved.editValue ?? resolved.displayValue,
372
+ source: resolved.source,
373
+ inheritanceSource: resolved.inheritanceSource,
374
+ };
375
+ }
376
+
377
+ const { value, source } = resolveValue(field.key, defaults, projectRaw, globalRaw, scope);
378
+ const displayValue = formatValue(value, field);
379
+ const inheritanceSource =
380
+ scope === "project" && source === "project"
381
+ ? globalRaw && field.key in globalRaw
382
+ ? "global"
383
+ : "default"
384
+ : undefined;
385
+
386
+ return {
387
+ field,
388
+ displayValue: sourceBadge(displayValue, source),
389
+ editValue: formatEditValue(value, field),
390
+ source,
391
+ inheritanceSource,
392
+ };
393
+ });
394
+ }
395
+
396
+ async function applyConfigAction(
397
+ options: ConfigSettingsOptions,
398
+ request: Parameters<SettingsModule["apply"]>[0],
399
+ ): Promise<SettingsApplyResult> {
400
+ const { scope, cwd, fieldKey, action, ctx } = request;
401
+ const field = options.fields.find((candidate) => candidate.key === fieldKey);
402
+ if (!field) return {};
403
+
404
+ const helpers = createConfigHelpers(options.section, scope, cwd, options.homeDir);
405
+ let storedValue: unknown;
406
+ if (field.kind === "custom") {
407
+ await field.persist(scope, cwd, action, helpers);
408
+ storedValue = action.kind === "set" ? action.value : undefined;
409
+ } else if (action.kind === "set") {
410
+ storedValue = parseTypedValue(action.value, field);
411
+ helpers.set(field.key, storedValue);
412
+ } else {
413
+ helpers.unset(field.key);
414
+ }
415
+ notifyAfterPersist({ options, field, scope, cwd, action, storedValue, ctx });
416
+ return {};
417
+ }
418
+
419
+ /** Adapt one fixed SuPi config section to the canonical settings interface. */
420
+ export function defineConfigSettings(options: ConfigSettingsOptions): SettingsModule {
359
421
  return {
360
422
  id: options.id,
361
423
  label: options.label,
362
- loadValues: (scope, cwd, ctx) => {
363
- // Load raw section data for both scopes (no defaults)
364
- const projectRaw = loadSupiConfigSectionForScope(options.section, cwd, {
365
- scope: "project",
366
- homeDir: options.homeDir,
367
- });
368
- const globalRaw = loadSupiConfigSectionForScope(options.section, cwd, {
369
- scope: "global",
370
- homeDir: options.homeDir,
371
- });
372
-
373
- // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: natural discriminator on field kind and source
374
- return options.fields.map((field) => {
375
- if (field.kind === "custom") {
376
- const resolved = field.resolve(scope, cwd, ctx);
377
- return {
378
- field,
379
- displayValue: resolved.displayValue
380
- ? sourceBadge(resolved.displayValue, resolved.source)
381
- : "",
382
- editValue: resolved.editValue ?? resolved.displayValue,
383
- source: resolved.source,
384
- inheritanceSource: resolved.inheritanceSource,
385
- };
386
- }
387
-
388
- const { value, source } = resolveValue(field.key, defaults, projectRaw, globalRaw, scope);
389
- const displayValue = formatValue(value, field);
390
-
391
- // Compute inheritanceSource for project-scope overrides
392
- let inheritanceSource: "global" | "default" | undefined;
393
- if (scope === "project" && source === "project") {
394
- inheritanceSource = globalRaw && field.key in globalRaw ? "global" : "default";
395
- }
396
-
397
- return {
398
- field,
399
- displayValue: sourceBadge(displayValue, source),
400
- editValue: formatEditValue(value, field),
401
- source,
402
- inheritanceSource,
403
- };
404
- });
405
- },
406
- // biome-ignore lint/complexity/useMaxParams: SettingsSection action handlers receive scope, cwd, field, action, and optional context
407
- handleAction: (scope, cwd, fieldKey, action, ctx) => {
408
- const field = options.fields.find((f) => f.key === fieldKey);
409
- if (!field) return;
410
-
411
- const section = options.section;
412
- const helpers = createConfigHelpers(section, scope, cwd, options.homeDir);
413
- let storedValue: unknown;
414
-
415
- if (field.kind === "custom") {
416
- field.persist(scope, cwd, action, helpers);
417
- storedValue = action.kind === "set" ? action.value : undefined;
418
- notifyAfterPersist({ options, field, scope, cwd, action, storedValue, ctx });
419
- return;
420
- }
421
-
422
- switch (action.kind) {
423
- case "set": {
424
- storedValue = parseTypedValue(action.value, field);
425
- helpers.set(field.key, storedValue);
426
- break;
427
- }
428
- case "inherit":
429
- case "resetToDefault": {
430
- helpers.unset(field.key);
431
- break;
432
- }
433
- }
434
-
435
- notifyAfterPersist({ options, field, scope, cwd, action, storedValue, ctx });
436
- },
424
+ read: ({ scope, cwd, ctx }) =>
425
+ Promise.resolve({ rows: resolveConfigRows(options, scope, cwd, ctx) }),
426
+ apply: (request) => applyConfigAction(options, request),
437
427
  };
438
428
  }
439
429
 
@@ -461,27 +451,3 @@ export function parseTypedValue(value: string, field: SettingsField): unknown {
461
451
  return value;
462
452
  }
463
453
  }
464
-
465
- // ── Registration ──────────────────────────────────────────────────────────
466
-
467
- /**
468
- * Register a declarative settings contribution for `/supi-settings`.
469
- *
470
- * Contributions are collected through PI's process-local event bus. Call this
471
- * during the extension factory function, not in async session handlers.
472
- */
473
- export function registerDeclarativeSettings(
474
- pi: ExtensionAPI,
475
- options: DeclarativeSettingsOptions,
476
- ): void {
477
- const section = toDeclarativeSection(options);
478
- const dispose = pi.events.on(SUPI_SETTINGS_COLLECT_EVENT, (collector) => {
479
- if (isSettingsContributionCollector(collector)) {
480
- collector.add(section);
481
- }
482
- });
483
-
484
- pi.on("session_shutdown", () => {
485
- dispose();
486
- });
487
- }
@@ -1,33 +1,36 @@
1
- // supi-core settings domain — event-backed declarative settings contributions and command wiring.
2
-
3
- export { registerSettingsCommand } from "./settings/settings-command.ts";
1
+ // supi-core settings domain — settings modules and fixed-config adapters.
4
2
  export type {
3
+ SettingsActionRequest,
4
+ SettingsApplyResult,
5
5
  SettingsCollectionDiagnostic,
6
6
  SettingsCollectionResult,
7
+ SettingsContext,
7
8
  SettingsContributionCollector,
9
+ SettingsModule,
8
10
  SettingsScope,
9
- SettingsSection,
11
+ SettingsSnapshot,
10
12
  } from "./settings/settings-registry.ts";
11
13
  export {
12
14
  createSettingsContributionCollector,
13
15
  isSettingsContributionCollector,
16
+ registerSettings,
14
17
  SUPI_SETTINGS_COLLECT_EVENT,
15
18
  } from "./settings/settings-registry.ts";
16
19
  export type {
17
20
  BoolField,
18
21
  ConfigHelpers,
22
+ ConfigSettingsOptions,
19
23
  CustomField,
20
- DeclarativeSettingsOptions,
21
24
  EnumField,
22
25
  ModelPickerField,
23
26
  ModelPickerStaticOption,
24
27
  NumberField,
25
28
  ScopedFieldValue,
29
+ SettingsAction,
26
30
  SettingsField,
27
- SettingsFieldAction,
28
31
  SettingsPersistedChange,
29
32
  StringField,
30
33
  StringListField,
31
34
  ValueSource,
32
35
  } from "./settings/settings-schema.ts";
33
- export { registerDeclarativeSettings } from "./settings/settings-schema.ts";
36
+ export { defineConfigSettings } from "./settings/settings-schema.ts";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-insights",
3
- "version": "4.7.0",
3
+ "version": "4.9.0",
4
4
  "description": "Historical Pi-session reports and shareable HTML",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -34,8 +34,7 @@
34
34
  "README.md"
35
35
  ],
36
36
  "dependencies": {
37
- "diff": "^9.0.0",
38
- "@mrclrchtr/supi-core": "4.7.0"
37
+ "@mrclrchtr/supi-core": "4.9.0"
39
38
  },
40
39
  "bundledDependencies": [
41
40
  "@mrclrchtr/supi-core"
@@ -62,9 +61,7 @@
62
61
  ],
63
62
  "image": "https://raw.githubusercontent.com/mrclrchtr/supi/main/packages/supi-insights/assets/social-preview.png"
64
63
  },
65
- "main": "src/api.ts",
66
64
  "exports": {
67
- "./api": "./src/api.ts",
68
65
  "./extension": "./src/extension.ts",
69
66
  "./package.json": "./package.json"
70
67
  }
package/src/generator.ts CHANGED
@@ -2,108 +2,29 @@
2
2
 
3
3
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
4
4
  import { callWithJsonResponse } from "@mrclrchtr/supi-core/llm";
5
- import { Type } from "typebox";
5
+ import {
6
+ INSIGHT_SCHEMAS,
7
+ type InsightResultMap,
8
+ type InsightSchemaMap,
9
+ } from "./insight-schemas.ts";
6
10
  import type { AggregatedData, InsightResults, SessionFacets } from "./types.ts";
7
11
 
8
- // ── TypeBox schemas for each insight section ───────────────────────────────
9
-
10
- const ProjectAreasSchema = Type.Object({
11
- areas: Type.Array(
12
- Type.Object({
13
- name: Type.String(),
14
- sessionCount: Type.Number(),
15
- description: Type.String(),
16
- }),
17
- ),
18
- });
19
-
20
- const InteractionStyleSchema = Type.Object({
21
- narrative: Type.String(),
22
- keyPattern: Type.String(),
23
- });
24
-
25
- const WhatWorksSchema = Type.Object({
26
- intro: Type.String(),
27
- impressiveWorkflows: Type.Array(
28
- Type.Object({
29
- title: Type.String(),
30
- description: Type.String(),
31
- }),
32
- ),
33
- });
34
-
35
- const FrictionAnalysisSchema = Type.Object({
36
- intro: Type.String(),
37
- categories: Type.Array(
38
- Type.Object({
39
- category: Type.String(),
40
- description: Type.String(),
41
- examples: Type.Array(Type.String()),
42
- }),
43
- ),
44
- });
45
-
46
- const SuggestionsSchema = Type.Object({
47
- claudeMdAdditions: Type.Array(
48
- Type.Object({
49
- addition: Type.String(),
50
- why: Type.String(),
51
- promptScaffold: Type.String(),
52
- }),
53
- ),
54
- featuresToTry: Type.Array(
55
- Type.Object({
56
- feature: Type.String(),
57
- oneLiner: Type.String(),
58
- whyForYou: Type.String(),
59
- exampleCode: Type.String(),
60
- }),
61
- ),
62
- usagePatterns: Type.Array(
63
- Type.Object({
64
- title: Type.String(),
65
- suggestion: Type.String(),
66
- detail: Type.String(),
67
- copyablePrompt: Type.String(),
68
- }),
69
- ),
70
- });
71
-
72
- const OnTheHorizonSchema = Type.Object({
73
- intro: Type.String(),
74
- opportunities: Type.Array(
75
- Type.Object({
76
- title: Type.String(),
77
- whatsPossible: Type.String(),
78
- howToTry: Type.String(),
79
- copyablePrompt: Type.String(),
80
- }),
81
- ),
82
- });
83
-
84
- const FunEndingSchema = Type.Object({
85
- headline: Type.String(),
86
- detail: Type.String(),
87
- });
88
-
89
- const AtAGlanceSchema = Type.Object({
90
- whatsWorking: Type.String(),
91
- whatsHindering: Type.String(),
92
- quickWins: Type.String(),
93
- ambitiousWorkflows: Type.String(),
94
- });
95
-
96
12
  // ── Section definitions ──────────────────────────────────────────────────
97
13
 
98
- type InsightSection = {
99
- name: keyof InsightResults;
14
+ type ParallelInsightSectionName = Exclude<keyof InsightResultMap, "atAGlance">;
15
+
16
+ type InsightSection<Name extends ParallelInsightSectionName> = {
17
+ name: Name;
100
18
  prompt: string;
101
- // biome-ignore lint/suspicious/noExplicitAny: TypeBox schema union
102
- schema: ReturnType<typeof Type.Object<any>>;
19
+ schema: InsightSchemaMap[Name];
103
20
  maxTokens: number;
104
21
  };
105
22
 
106
- const INSIGHT_SECTIONS: InsightSection[] = [
23
+ type ParallelInsightSection = {
24
+ [Name in ParallelInsightSectionName]: InsightSection<Name>;
25
+ }[ParallelInsightSectionName];
26
+
27
+ const INSIGHT_SECTIONS = [
107
28
  {
108
29
  name: "projectAreas",
109
30
  prompt: `Analyze this PI usage data and identify project areas.
@@ -116,7 +37,7 @@ RESPOND WITH ONLY A VALID JSON OBJECT:
116
37
  }
117
38
 
118
39
  Include 4-5 areas.`,
119
- schema: ProjectAreasSchema,
40
+ schema: INSIGHT_SCHEMAS.projectAreas,
120
41
  maxTokens: 4096,
121
42
  },
122
43
  {
@@ -128,7 +49,7 @@ RESPOND WITH ONLY A VALID JSON OBJECT:
128
49
  "narrative": "2-3 paragraphs analyzing HOW the user interacts with PI. Use second person 'you'. Describe patterns: iterate quickly vs detailed upfront specs? Interrupt often or let the agent run? Include specific examples. Use **bold** for key insights.",
129
50
  "keyPattern": "One sentence summary of most distinctive interaction style"
130
51
  }`,
131
- schema: InteractionStyleSchema,
52
+ schema: INSIGHT_SCHEMAS.interactionStyle,
132
53
  maxTokens: 4096,
133
54
  },
134
55
  {
@@ -144,7 +65,7 @@ RESPOND WITH ONLY A VALID JSON OBJECT:
144
65
  }
145
66
 
146
67
  Include 3 impressive workflows.`,
147
- schema: WhatWorksSchema,
68
+ schema: INSIGHT_SCHEMAS.whatWorks,
148
69
  maxTokens: 4096,
149
70
  },
150
71
  {
@@ -160,7 +81,7 @@ RESPOND WITH ONLY A VALID JSON OBJECT:
160
81
  }
161
82
 
162
83
  Include 3 friction categories with 2 examples each.`,
163
- schema: FrictionAnalysisSchema,
84
+ schema: INSIGHT_SCHEMAS.frictionAnalysis,
164
85
  maxTokens: 4096,
165
86
  },
166
87
  {
@@ -181,7 +102,7 @@ RESPOND WITH ONLY A VALID JSON OBJECT:
181
102
  }
182
103
 
183
104
  IMPORTANT: PRIORITIZE instructions that appear MULTIPLE TIMES in the user data.`,
184
- schema: SuggestionsSchema,
105
+ schema: INSIGHT_SCHEMAS.suggestions,
185
106
  maxTokens: 4096,
186
107
  },
187
108
  {
@@ -197,7 +118,7 @@ RESPOND WITH ONLY A VALID JSON OBJECT:
197
118
  }
198
119
 
199
120
  Include 3 opportunities. Think BIG - autonomous workflows, parallel agents, iterating against tests.`,
200
- schema: OnTheHorizonSchema,
121
+ schema: INSIGHT_SCHEMAS.onTheHorizon,
201
122
  maxTokens: 4096,
202
123
  },
203
124
  {
@@ -211,10 +132,10 @@ RESPOND WITH ONLY A VALID JSON OBJECT:
211
132
  }
212
133
 
213
134
  Find something genuinely interesting or amusing from the session summaries.`,
214
- schema: FunEndingSchema,
135
+ schema: INSIGHT_SCHEMAS.funEnding,
215
136
  maxTokens: 2048,
216
137
  },
217
- ];
138
+ ] satisfies readonly ParallelInsightSection[];
218
139
 
219
140
  // ── Public API ─────────────────────────────────────────────────────────────
220
141
 
@@ -232,13 +153,11 @@ export async function generateInsights(
232
153
 
233
154
  const insights: InsightResults = {};
234
155
  for (const { name, result } of results) {
235
- if (result) {
236
- insights[name] = result;
237
- }
156
+ if (result) Object.assign(insights, { [name]: result });
238
157
  }
239
158
 
240
159
  // Generate at_a_glance sequentially (needs other sections)
241
- const atAGlance = await generateAtAGlance(data, insights, dataContext, ctx);
160
+ const atAGlance = await generateAtAGlance(insights, dataContext, ctx);
242
161
  if (atAGlance) {
243
162
  insights.atAGlance = atAGlance;
244
163
  }
@@ -248,11 +167,11 @@ export async function generateInsights(
248
167
 
249
168
  // ── Section generators ────────────────────────────────────────────────────
250
169
 
251
- async function generateSectionInsight(
252
- section: InsightSection,
170
+ async function generateSectionInsight<Name extends ParallelInsightSectionName>(
171
+ section: InsightSection<Name>,
253
172
  dataContext: string,
254
173
  ctx: ExtensionContext,
255
- ): Promise<{ name: keyof InsightResults; result: unknown }> {
174
+ ) {
256
175
  const result = await callWithJsonResponse(
257
176
  ctx,
258
177
  {
@@ -268,55 +187,33 @@ async function generateSectionInsight(
268
187
  }
269
188
 
270
189
  async function generateAtAGlance(
271
- _data: AggregatedData,
272
190
  insights: InsightResults,
273
191
  dataContext: string,
274
192
  ctx: ExtensionContext,
275
- ): Promise<unknown> {
193
+ ): Promise<InsightResultMap["atAGlance"] | null> {
276
194
  const projectAreasText =
277
- (
278
- insights.projectAreas as {
279
- areas?: Array<{ name: string; description: string }>;
280
- }
281
- )?.areas
282
- ?.map((a) => `- ${a.name}: ${a.description}`)
283
- .join("\n") || "";
195
+ insights.projectAreas?.areas.map((area) => `- ${area.name}: ${area.description}`).join("\n") ??
196
+ "";
284
197
 
285
198
  const bigWinsText =
286
- (
287
- insights.whatWorks as {
288
- impressiveWorkflows?: Array<{ title: string; description: string }>;
289
- }
290
- )?.impressiveWorkflows
291
- ?.map((w) => `- ${w.title}: ${w.description}`)
292
- .join("\n") || "";
199
+ insights.whatWorks?.impressiveWorkflows
200
+ .map((workflow) => `- ${workflow.title}: ${workflow.description}`)
201
+ .join("\n") ?? "";
293
202
 
294
203
  const frictionText =
295
- (
296
- insights.frictionAnalysis as {
297
- categories?: Array<{ category: string; description: string }>;
298
- }
299
- )?.categories
300
- ?.map((c) => `- ${c.category}: ${c.description}`)
301
- .join("\n") || "";
204
+ insights.frictionAnalysis?.categories
205
+ .map((category) => `- ${category.category}: ${category.description}`)
206
+ .join("\n") ?? "";
302
207
 
303
208
  const featuresText =
304
- (
305
- insights.suggestions as {
306
- featuresToTry?: Array<{ feature: string; oneLiner: string }>;
307
- }
308
- )?.featuresToTry
309
- ?.map((f) => `- ${f.feature}: ${f.oneLiner}`)
310
- .join("\n") || "";
209
+ insights.suggestions?.featuresToTry
210
+ .map((feature) => `- ${feature.feature}: ${feature.oneLiner}`)
211
+ .join("\n") ?? "";
311
212
 
312
213
  const horizonText =
313
- (
314
- insights.onTheHorizon as {
315
- opportunities?: Array<{ title: string; whatsPossible: string }>;
316
- }
317
- )?.opportunities
318
- ?.map((o) => `- ${o.title}: ${o.whatsPossible}`)
319
- .join("\n") || "";
214
+ insights.onTheHorizon?.opportunities
215
+ .map((opportunity) => `- ${opportunity.title}: ${opportunity.whatsPossible}`)
216
+ .join("\n") ?? "";
320
217
 
321
218
  const prompt = `You're writing an "At a Glance" summary for a PI usage insights report.
322
219
 
@@ -365,7 +262,7 @@ ${horizonText}`;
365
262
  maxTokens: 4096,
366
263
  retries: 2,
367
264
  },
368
- AtAGlanceSchema,
265
+ INSIGHT_SCHEMAS.atAGlance,
369
266
  );
370
267
 
371
268
  return result?.parsed ?? null;