anno-extension 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 (199) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +30 -0
  3. package/THIRD_PARTY_NOTICES.md +8 -0
  4. package/dist/browser-fs.d.ts +14 -0
  5. package/dist/browser-fs.d.ts.map +1 -0
  6. package/dist/browser-fs.js +69 -0
  7. package/dist/browser-fs.js.map +1 -0
  8. package/dist/config.d.ts +81 -0
  9. package/dist/config.d.ts.map +1 -0
  10. package/dist/config.js +82 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/consolidate-v2.d.ts +15 -0
  13. package/dist/consolidate-v2.d.ts.map +1 -0
  14. package/dist/consolidate-v2.js +125 -0
  15. package/dist/consolidate-v2.js.map +1 -0
  16. package/dist/consolidation.d.ts +12 -0
  17. package/dist/consolidation.d.ts.map +1 -0
  18. package/dist/consolidation.js +199 -0
  19. package/dist/consolidation.js.map +1 -0
  20. package/dist/dedup.d.ts +18 -0
  21. package/dist/dedup.d.ts.map +1 -0
  22. package/dist/dedup.js +85 -0
  23. package/dist/dedup.js.map +1 -0
  24. package/dist/diagnostics.d.ts +24 -0
  25. package/dist/diagnostics.d.ts.map +1 -0
  26. package/dist/diagnostics.js +53 -0
  27. package/dist/diagnostics.js.map +1 -0
  28. package/dist/embedding-index.d.ts +27 -0
  29. package/dist/embedding-index.d.ts.map +1 -0
  30. package/dist/embedding-index.js +111 -0
  31. package/dist/embedding-index.js.map +1 -0
  32. package/dist/engine-archive.d.ts +37 -0
  33. package/dist/engine-archive.d.ts.map +1 -0
  34. package/dist/engine-archive.js +155 -0
  35. package/dist/engine-archive.js.map +1 -0
  36. package/dist/engine-episode-sync.d.ts +19 -0
  37. package/dist/engine-episode-sync.d.ts.map +1 -0
  38. package/dist/engine-episode-sync.js +186 -0
  39. package/dist/engine-episode-sync.js.map +1 -0
  40. package/dist/engine-injection-text.d.ts +46 -0
  41. package/dist/engine-injection-text.d.ts.map +1 -0
  42. package/dist/engine-injection-text.js +271 -0
  43. package/dist/engine-injection-text.js.map +1 -0
  44. package/dist/engine-insights.d.ts +22 -0
  45. package/dist/engine-insights.d.ts.map +1 -0
  46. package/dist/engine-insights.js +127 -0
  47. package/dist/engine-insights.js.map +1 -0
  48. package/dist/engine-links.d.ts +43 -0
  49. package/dist/engine-links.d.ts.map +1 -0
  50. package/dist/engine-links.js +223 -0
  51. package/dist/engine-links.js.map +1 -0
  52. package/dist/engine-recall-select.d.ts +84 -0
  53. package/dist/engine-recall-select.d.ts.map +1 -0
  54. package/dist/engine-recall-select.js +127 -0
  55. package/dist/engine-recall-select.js.map +1 -0
  56. package/dist/engine-reinforce.d.ts +18 -0
  57. package/dist/engine-reinforce.d.ts.map +1 -0
  58. package/dist/engine-reinforce.js +116 -0
  59. package/dist/engine-reinforce.js.map +1 -0
  60. package/dist/engine-scoring-v2.d.ts +35 -0
  61. package/dist/engine-scoring-v2.d.ts.map +1 -0
  62. package/dist/engine-scoring-v2.js +178 -0
  63. package/dist/engine-scoring-v2.js.map +1 -0
  64. package/dist/engine-v2-mapping.d.ts +17 -0
  65. package/dist/engine-v2-mapping.d.ts.map +1 -0
  66. package/dist/engine-v2-mapping.js +190 -0
  67. package/dist/engine-v2-mapping.js.map +1 -0
  68. package/dist/engine.d.ts +339 -0
  69. package/dist/engine.d.ts.map +1 -0
  70. package/dist/engine.js +1964 -0
  71. package/dist/engine.js.map +1 -0
  72. package/dist/eviction.d.ts +14 -0
  73. package/dist/eviction.d.ts.map +1 -0
  74. package/dist/eviction.js +26 -0
  75. package/dist/eviction.js.map +1 -0
  76. package/dist/extraction.d.ts +11 -0
  77. package/dist/extraction.d.ts.map +1 -0
  78. package/dist/extraction.js +261 -0
  79. package/dist/extraction.js.map +1 -0
  80. package/dist/full-insights-html.d.ts +9 -0
  81. package/dist/full-insights-html.d.ts.map +1 -0
  82. package/dist/full-insights-html.js +422 -0
  83. package/dist/full-insights-html.js.map +1 -0
  84. package/dist/full-insights-sections.d.ts +48 -0
  85. package/dist/full-insights-sections.d.ts.map +1 -0
  86. package/dist/full-insights-sections.js +231 -0
  87. package/dist/full-insights-sections.js.map +1 -0
  88. package/dist/full-insights.d.ts +22 -0
  89. package/dist/full-insights.d.ts.map +1 -0
  90. package/dist/full-insights.js +370 -0
  91. package/dist/full-insights.js.map +1 -0
  92. package/dist/hash-embedding.d.ts +9 -0
  93. package/dist/hash-embedding.d.ts.map +1 -0
  94. package/dist/hash-embedding.js +44 -0
  95. package/dist/hash-embedding.js.map +1 -0
  96. package/dist/human-insights.d.ts +28 -0
  97. package/dist/human-insights.d.ts.map +1 -0
  98. package/dist/human-insights.js +260 -0
  99. package/dist/human-insights.js.map +1 -0
  100. package/dist/i18n.d.ts +64 -0
  101. package/dist/i18n.d.ts.map +1 -0
  102. package/dist/i18n.js +367 -0
  103. package/dist/i18n.js.map +1 -0
  104. package/dist/index.d.ts +27 -0
  105. package/dist/index.d.ts.map +1 -0
  106. package/dist/index.js +21 -0
  107. package/dist/index.js.map +1 -0
  108. package/dist/insights-html.d.ts +9 -0
  109. package/dist/insights-html.d.ts.map +1 -0
  110. package/dist/insights-html.js +432 -0
  111. package/dist/insights-html.js.map +1 -0
  112. package/dist/linking.d.ts +22 -0
  113. package/dist/linking.d.ts.map +1 -0
  114. package/dist/linking.js +192 -0
  115. package/dist/linking.js.map +1 -0
  116. package/dist/llm-json.d.ts +10 -0
  117. package/dist/llm-json.d.ts.map +1 -0
  118. package/dist/llm-json.js +193 -0
  119. package/dist/llm-json.js.map +1 -0
  120. package/dist/privacy.d.ts +17 -0
  121. package/dist/privacy.d.ts.map +1 -0
  122. package/dist/privacy.js +53 -0
  123. package/dist/privacy.js.map +1 -0
  124. package/dist/procedural-v2.d.ts +9 -0
  125. package/dist/procedural-v2.d.ts.map +1 -0
  126. package/dist/procedural-v2.js +123 -0
  127. package/dist/procedural-v2.js.map +1 -0
  128. package/dist/reconsolidate-v2.d.ts +19 -0
  129. package/dist/reconsolidate-v2.d.ts.map +1 -0
  130. package/dist/reconsolidate-v2.js +115 -0
  131. package/dist/reconsolidate-v2.js.map +1 -0
  132. package/dist/scoring.d.ts +32 -0
  133. package/dist/scoring.d.ts.map +1 -0
  134. package/dist/scoring.js +108 -0
  135. package/dist/scoring.js.map +1 -0
  136. package/dist/store-v2.d.ts +35 -0
  137. package/dist/store-v2.d.ts.map +1 -0
  138. package/dist/store-v2.js +98 -0
  139. package/dist/store-v2.js.map +1 -0
  140. package/dist/store.d.ts +22 -0
  141. package/dist/store.d.ts.map +1 -0
  142. package/dist/store.js +116 -0
  143. package/dist/store.js.map +1 -0
  144. package/dist/turn-context.d.ts +37 -0
  145. package/dist/turn-context.d.ts.map +1 -0
  146. package/dist/turn-context.js +31 -0
  147. package/dist/turn-context.js.map +1 -0
  148. package/dist/types-v2.d.ts +190 -0
  149. package/dist/types-v2.d.ts.map +1 -0
  150. package/dist/types-v2.js +8 -0
  151. package/dist/types-v2.js.map +1 -0
  152. package/dist/types.d.ts +330 -0
  153. package/dist/types.d.ts.map +1 -0
  154. package/dist/types.js +8 -0
  155. package/dist/types.js.map +1 -0
  156. package/dist/update.d.ts +31 -0
  157. package/dist/update.d.ts.map +1 -0
  158. package/dist/update.js +250 -0
  159. package/dist/update.js.map +1 -0
  160. package/package.json +34 -0
  161. package/src/browser-fs.ts +67 -0
  162. package/src/config.ts +138 -0
  163. package/src/consolidate-v2.ts +158 -0
  164. package/src/consolidation.ts +229 -0
  165. package/src/dedup.ts +91 -0
  166. package/src/diagnostics.ts +81 -0
  167. package/src/embedding-index.ts +138 -0
  168. package/src/engine-archive.ts +167 -0
  169. package/src/engine-episode-sync.ts +237 -0
  170. package/src/engine-injection-text.ts +334 -0
  171. package/src/engine-insights.ts +182 -0
  172. package/src/engine-links.ts +311 -0
  173. package/src/engine-recall-select.ts +298 -0
  174. package/src/engine-reinforce.ts +154 -0
  175. package/src/engine-scoring-v2.ts +188 -0
  176. package/src/engine-v2-mapping.ts +204 -0
  177. package/src/engine.ts +2499 -0
  178. package/src/eviction.ts +38 -0
  179. package/src/extraction.ts +300 -0
  180. package/src/full-insights-html.ts +474 -0
  181. package/src/full-insights-sections.ts +305 -0
  182. package/src/full-insights.ts +423 -0
  183. package/src/hash-embedding.ts +48 -0
  184. package/src/human-insights.ts +345 -0
  185. package/src/i18n.ts +455 -0
  186. package/src/index.ts +117 -0
  187. package/src/insights-html.ts +478 -0
  188. package/src/linking.ts +228 -0
  189. package/src/llm-json.ts +194 -0
  190. package/src/privacy.ts +54 -0
  191. package/src/procedural-v2.ts +142 -0
  192. package/src/reconsolidate-v2.ts +145 -0
  193. package/src/scoring.ts +156 -0
  194. package/src/store-v2.ts +138 -0
  195. package/src/store.ts +137 -0
  196. package/src/turn-context.ts +56 -0
  197. package/src/types-v2.ts +244 -0
  198. package/src/types.ts +372 -0
  199. package/src/update.ts +271 -0
@@ -0,0 +1,345 @@
1
+ /**
2
+ * [WHO]: DeveloperPersona, HumanInsightsReport, generateHumanInsights
3
+ * [FROM]: Depends on ./llm-json.js, ./types.js
4
+ * [TO]: Consumed by packages/mem-core/src/index.ts
5
+ * [HERE]: packages/mem-core/src/human-insights.ts - LLM-powered usage review generation with developer persona analysis
6
+ */
7
+
8
+
9
+ import type {
10
+ DeveloperPersona,
11
+ EnhancedInsightsReport,
12
+ HumanInsight,
13
+ LlmFn,
14
+ MemoryEntry,
15
+ Episode,
16
+ RootCauseInsight,
17
+ WorkEntry,
18
+ } from "./types.js";
19
+ import { parseLlmJson } from "./llm-json.js";
20
+
21
+ interface ExportAllResult {
22
+ knowledge: MemoryEntry[];
23
+ lessons: MemoryEntry[];
24
+ preferences: MemoryEntry[];
25
+ facets: MemoryEntry[];
26
+ work: WorkEntry[];
27
+ episodes: Episode[];
28
+ meta: { totalSessions: number; lastConsolidation?: string; version: number };
29
+ }
30
+
31
+ const HUMAN_INSIGHTS_SYSTEM_PROMPT = `You are an elite AI product analyst and developer workflow coach.
32
+
33
+ You are reviewing one specific user's real usage history over time.
34
+ Write like a warm, observant expert who deeply understands how experienced AI users actually work.
35
+
36
+ Goals:
37
+ - Sound human, perceptive, and respectful rather than robotic or generic
38
+ - Give clear corrections when the user's habits are inefficient
39
+ - Back every major conclusion with concrete evidence from the supplied data
40
+ - Prefer precise language, plain English, and practical recommendations
41
+ - Explain what the user is doing well, where they are losing time, and what they should change next
42
+
43
+ Output requirements:
44
+ - Output ONLY valid JSON
45
+ - Do not use markdown or code fences
46
+ - Use the supplied data only
47
+ - Be specific, not motivational fluff
48
+ - Each insight should feel like part of a thoughtful performance review
49
+ - Recommendations should be direct, concrete, and easy to act on
50
+ - Evidence should reference counts, repeated behaviors, or recurring issues when possible
51
+ - If locale is "zh", write the JSON string values in Simplified Chinese; otherwise write in English
52
+
53
+ Return JSON matching this schema:
54
+ {
55
+ "persona": {
56
+ "whatTheyDo": "1-2 sentences",
57
+ "experienceLevel": "1 sentence",
58
+ "superpowers": ["...", "..."],
59
+ "painPoints": ["...", "..."],
60
+ "workStyle": "1-2 sentences",
61
+ "summary": "1 sentence"
62
+ },
63
+ "insights": [
64
+ {
65
+ "title": "short title",
66
+ "content": "3-5 sentences combining observation, evidence, correction, and advice",
67
+ "icon": "emoji",
68
+ "utility": "high|medium|low",
69
+ "tags": ["tag1", "tag2"]
70
+ }
71
+ ],
72
+ "rootCauses": [
73
+ {
74
+ "symptom": "what keeps happening",
75
+ "rootCause": "why it likely happens",
76
+ "evidence": ["fact 1", "fact 2"],
77
+ "suggestion": "what to change next"
78
+ }
79
+ ]
80
+ }`.trim();
81
+
82
+ function summarizeCounts(rows: Array<[string, number]>, formatter: (label: string, value: number) => string): string[] {
83
+ return rows.map(([label, value]) => formatter(label, value));
84
+ }
85
+
86
+ function buildHumanInsightsData(all: ExportAllResult) {
87
+ const totalToolUses = all.episodes.reduce(
88
+ (total, episode) =>
89
+ total +
90
+ Object.values(episode.toolsUsed ?? {}).reduce<number>((sum, count) => sum + count, 0),
91
+ 0,
92
+ );
93
+
94
+ const toolCounts = all.episodes.reduce(
95
+ (acc, episode) => {
96
+ for (const [tool, count] of Object.entries(episode.toolsUsed ?? {})) {
97
+ acc[tool] = (acc[tool] ?? 0) + count;
98
+ }
99
+ return acc;
100
+ },
101
+ {} as Record<string, number>,
102
+ );
103
+
104
+ const languageCounts = all.episodes.reduce(
105
+ (acc, episode) => {
106
+ for (const file of episode.filesModified ?? []) {
107
+ const ext = file.includes(".") ? file.split(".").pop()?.toLowerCase() ?? "other" : "other";
108
+ acc[ext] = (acc[ext] ?? 0) + 1;
109
+ }
110
+ return acc;
111
+ },
112
+ {} as Record<string, number>,
113
+ );
114
+
115
+ const errorCounts = all.episodes.reduce(
116
+ (acc, episode) => {
117
+ for (const error of episode.errors ?? []) {
118
+ const key = error.replace(/\s+/g, " ").trim().slice(0, 120);
119
+ if (!key) continue;
120
+ acc[key] = (acc[key] ?? 0) + 1;
121
+ }
122
+ return acc;
123
+ },
124
+ {} as Record<string, number>,
125
+ );
126
+
127
+ const resolvedStruggles = all.facets.filter(
128
+ (entry) => entry.type === "struggle" && entry.facetData?.kind === "struggle" && !!entry.facetData.solution,
129
+ );
130
+ const unresolvedStruggles = all.facets.filter(
131
+ (entry) => entry.type === "struggle" && entry.facetData?.kind === "struggle" && !entry.facetData.solution,
132
+ );
133
+ const patternEntries = all.facets.filter(
134
+ (entry) => entry.type === "pattern" && entry.facetData?.kind === "pattern",
135
+ );
136
+
137
+ const topTools = Object.entries(toolCounts)
138
+ .sort((a, b) => b[1] - a[1])
139
+ .slice(0, 8);
140
+ const topLanguages = Object.entries(languageCounts)
141
+ .sort((a, b) => b[1] - a[1])
142
+ .slice(0, 8);
143
+ const topErrors = Object.entries(errorCounts)
144
+ .sort((a, b) => b[1] - a[1])
145
+ .slice(0, 8);
146
+ const topPatterns = patternEntries
147
+ .slice()
148
+ .sort((a, b) => (b.accessCount + 1) * b.importance - (a.accessCount + 1) * a.importance)
149
+ .slice(0, 6)
150
+ .map((entry) => ({
151
+ trigger: entry.facetData?.kind === "pattern" ? entry.facetData.trigger : "",
152
+ behavior: entry.facetData?.kind === "pattern" ? entry.facetData.behavior : "",
153
+ importance: entry.importance,
154
+ accessCount: entry.accessCount,
155
+ }));
156
+
157
+ const notableWins = resolvedStruggles.slice(0, 6).map((entry) => ({
158
+ problem: entry.facetData?.kind === "struggle" ? entry.facetData.problem : entry.summary || "",
159
+ solution: entry.facetData?.kind === "struggle" ? entry.facetData.solution : "",
160
+ importance: entry.importance,
161
+ }));
162
+
163
+ const notableFrictions = unresolvedStruggles.slice(0, 6).map((entry) => ({
164
+ problem: entry.facetData?.kind === "struggle" ? entry.facetData.problem : entry.summary || "",
165
+ attempts: entry.facetData?.kind === "struggle" ? entry.facetData.attempts : [],
166
+ importance: entry.importance,
167
+ }));
168
+
169
+ const topLessons = all.lessons
170
+ .slice()
171
+ .sort((a, b) => (b.accessCount + 1) * b.importance - (a.accessCount + 1) * a.importance)
172
+ .slice(0, 8)
173
+ .map((entry) => entry.summary || entry.detail || entry.content || "")
174
+ .filter(Boolean);
175
+
176
+ const projectCounts = all.episodes.reduce(
177
+ (acc, episode) => {
178
+ const key = episode.project || "default";
179
+ acc[key] = (acc[key] ?? 0) + 1;
180
+ return acc;
181
+ },
182
+ {} as Record<string, number>,
183
+ );
184
+
185
+ return {
186
+ overview: {
187
+ totalSessions: all.meta.totalSessions,
188
+ episodes: all.episodes.length,
189
+ workEntries: all.work.length,
190
+ knowledgeEntries: all.knowledge.length,
191
+ lessonEntries: all.lessons.length,
192
+ preferenceEntries: all.preferences.length,
193
+ facetEntries: all.facets.length,
194
+ totalToolUses,
195
+ resolvedStruggleCount: resolvedStruggles.length,
196
+ unresolvedStruggleCount: unresolvedStruggles.length,
197
+ },
198
+ topTools: topTools.map(([tool, count]) => ({
199
+ tool,
200
+ count,
201
+ share: totalToolUses > 0 ? Number(((count / totalToolUses) * 100).toFixed(1)) : 0,
202
+ })),
203
+ topLanguages: topLanguages.map(([language, fileCount]) => ({ language, fileCount })),
204
+ topErrors: topErrors.map(([error, count]) => ({ error, count })),
205
+ topPatterns,
206
+ notableWins,
207
+ notableFrictions,
208
+ topLessons,
209
+ projectDistribution: Object.entries(projectCounts)
210
+ .sort((a, b) => b[1] - a[1])
211
+ .slice(0, 6)
212
+ .map(([project, sessions]) => ({ project, sessions })),
213
+ evidenceDigest: {
214
+ tools: summarizeCounts(topTools, (tool, count) => `${tool}: ${count} uses`),
215
+ languages: summarizeCounts(topLanguages, (language, count) => `${language}: ${count} files`),
216
+ errors: summarizeCounts(topErrors, (error, count) => `${error}: ${count} times`),
217
+ },
218
+ };
219
+ }
220
+
221
+ function parseHumanInsightsResponse(raw: string): {
222
+ persona?: DeveloperPersona;
223
+ insights: HumanInsight[];
224
+ rootCauses: RootCauseInsight[];
225
+ } | null {
226
+ try {
227
+ const parsed = parseLlmJson<{
228
+ persona?: Record<string, unknown>;
229
+ insights?: Array<Record<string, unknown>>;
230
+ rootCauses?: Array<Record<string, unknown>>;
231
+ }>(raw);
232
+
233
+ if (typeof parsed !== "object" || parsed === null) {
234
+ return null;
235
+ }
236
+
237
+ const persona = parsed.persona
238
+ ? {
239
+ whatTheyDo: String(parsed.persona.whatTheyDo || ""),
240
+ experienceLevel: String(parsed.persona.experienceLevel || ""),
241
+ superpowers: Array.isArray(parsed.persona.superpowers) ? parsed.persona.superpowers.map(String) : [],
242
+ painPoints: Array.isArray(parsed.persona.painPoints) ? parsed.persona.painPoints.map(String) : [],
243
+ workStyle: String(parsed.persona.workStyle || ""),
244
+ summary: String(parsed.persona.summary || ""),
245
+ }
246
+ : undefined;
247
+
248
+ const insights: HumanInsight[] = Array.isArray(parsed.insights)
249
+ ? parsed.insights
250
+ .map((item) => ({
251
+ title: String(item.title || "").trim(),
252
+ content: String(item.content || "").trim(),
253
+ icon: String(item.icon || "Insight").trim(),
254
+ utility: ["high", "medium", "low"].includes(String(item.utility))
255
+ ? (String(item.utility) as "high" | "medium" | "low")
256
+ : "medium",
257
+ tags: Array.isArray(item.tags) ? item.tags.map(String) : [],
258
+ }))
259
+ .filter((item) => item.title && item.content)
260
+ : [];
261
+
262
+ const rootCauses: RootCauseInsight[] = Array.isArray(parsed.rootCauses)
263
+ ? parsed.rootCauses
264
+ .map((item) => ({
265
+ symptom: String(item.symptom || "").trim(),
266
+ rootCause: String(item.rootCause || "").trim(),
267
+ evidence: Array.isArray(item.evidence) ? item.evidence.map(String) : [],
268
+ suggestion: String(item.suggestion || "").trim(),
269
+ }))
270
+ .filter((item) => item.symptom && item.rootCause)
271
+ : [];
272
+
273
+ return { persona, insights, rootCauses };
274
+ } catch {
275
+ return null;
276
+ }
277
+ }
278
+
279
+ export async function generateHumanInsights(
280
+ all: ExportAllResult,
281
+ llmFn: LlmFn | undefined,
282
+ locale: string,
283
+ ): Promise<{
284
+ persona?: DeveloperPersona;
285
+ humanInsights: HumanInsight[];
286
+ rootCauses: RootCauseInsight[];
287
+ }> {
288
+ if (!llmFn) {
289
+ return { humanInsights: [], rootCauses: [] };
290
+ }
291
+
292
+ const data = buildHumanInsightsData(all);
293
+ const userPrompt = JSON.stringify({ locale, reviewData: data });
294
+
295
+ try {
296
+ const raw = await llmFn(HUMAN_INSIGHTS_SYSTEM_PROMPT, userPrompt);
297
+ const parsed = parseHumanInsightsResponse(raw);
298
+ if (parsed) {
299
+ return {
300
+ persona: parsed.persona,
301
+ humanInsights: parsed.insights,
302
+ rootCauses: parsed.rootCauses,
303
+ };
304
+ }
305
+ } catch {
306
+ // Fall back to empty enhanced insights when the LLM path is unavailable.
307
+ }
308
+
309
+ return { humanInsights: [], rootCauses: [] };
310
+ }
311
+
312
+ export async function buildEnhancedInsightsReport(
313
+ all: ExportAllResult,
314
+ llmFn: LlmFn | undefined,
315
+ locale: string,
316
+ ): Promise<EnhancedInsightsReport> {
317
+ const humanData = await generateHumanInsights(all, llmFn, locale);
318
+
319
+ return {
320
+ stats: {
321
+ knowledge: all.knowledge.length,
322
+ lessons: all.lessons.length,
323
+ preferences: all.preferences.length,
324
+ facets: all.facets.length,
325
+ episodes: all.episodes.length,
326
+ work: all.work.length,
327
+ totalSessions: all.meta.totalSessions,
328
+ },
329
+ atAGlance: { working: "", hindering: "", quickWins: "", ambitious: "" },
330
+ projectAreas: [],
331
+ charts: [],
332
+ wins: [],
333
+ frictions: [],
334
+ patterns: [],
335
+ recommendations: [],
336
+ featuresToTry: [],
337
+ usagePatterns: [],
338
+ generatedAt: new Date().toISOString(),
339
+ locale,
340
+ persona: humanData.persona,
341
+ humanInsights: humanData.humanInsights,
342
+ rootCauses: humanData.rootCauses,
343
+ comparisons: [],
344
+ };
345
+ }