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,334 @@
1
+ /**
2
+ * [WHO]: Provides buildProgressiveInjectionText, CONVERSATION_PREFERENCE_PATTERNS, isConversationPreference, selectConversationPreferences, rankConversationPreference, mergeUniqueEntries
3
+ * [FROM]: Depends on ./i18n.js for PromptSet; ./linking.js for getGraphContextSummaries, GraphNeighbor; ./scoring.js for daysSince; ./types.js, ./types-v2.js for memory types
4
+ * [TO]: Consumed by engine.ts (getMemoryInjection)
5
+ * [HERE]: packages/mem-core/src/engine-injection-text.ts - injection text formatting and conversation preference detection
6
+ */
7
+
8
+ import type { PromptSet } from "./i18n.js";
9
+ import { getGraphContextSummaries, type GraphNeighbor } from "./linking.js";
10
+ import { daysSince } from "./scoring.js";
11
+ import type { Episode, MemoryEntry, WorkEntry } from "./types.js";
12
+ import type { EpisodeFacet, EpisodeMemory, ProceduralMemory, SemanticMemory } from "./types-v2.js";
13
+
14
+ export const CONVERSATION_PREFERENCE_PATTERNS = [
15
+ /\bcall me\b/i,
16
+ /\baddress me\b/i,
17
+ /\bmy name is\b/i,
18
+ /\bi am called\b/i,
19
+ /\bspeak (?:to me )?(?:like|in)\b/i,
20
+ /\btalk (?:to me )?(?:like|in)\b/i,
21
+ /\buse (?:a |the )?(?:tone|style|voice)\b/i,
22
+ /\b(?:tone|style|voice|persona)\b/i,
23
+ /call me/,
24
+ /address me as/,
25
+ /address user/,
26
+ /tone/,
27
+ /style of speaking/,
28
+ /speaking style/,
29
+ /communication style/,
30
+ ];
31
+
32
+ export function isConversationPreference(entry: MemoryEntry): boolean {
33
+ if (entry.type !== "preference") return false;
34
+ const text = `${entry.name || ""}\n${entry.summary || ""}\n${entry.detail || ""}\n${entry.content || ""}`;
35
+ return CONVERSATION_PREFERENCE_PATTERNS.some((pattern) => pattern.test(text));
36
+ }
37
+
38
+ export function rankConversationPreference(entry: MemoryEntry): number {
39
+ return (entry.salience ?? entry.importance ?? 0) * 10 + (entry.accessCount ?? 0) * 2 - daysSince(entry.created);
40
+ }
41
+
42
+ export function selectConversationPreferences(entries: MemoryEntry[]): MemoryEntry[] {
43
+ return entries
44
+ .filter((entry) => isConversationPreference(entry))
45
+ .filter((entry) => entry.stability !== "situational")
46
+ .sort((a, b) => rankConversationPreference(b) - rankConversationPreference(a))
47
+ .slice(0, 3);
48
+ }
49
+
50
+ export function mergeUniqueEntries(preferred: MemoryEntry[], fallback: MemoryEntry[]): MemoryEntry[] {
51
+ const merged: MemoryEntry[] = [];
52
+ const seen = new Set<string>();
53
+ for (const entry of [...preferred, ...fallback]) {
54
+ if (seen.has(entry.id)) continue;
55
+ seen.add(entry.id);
56
+ merged.push(entry);
57
+ }
58
+ return merged;
59
+ }
60
+
61
+ export interface ActiveInjectionData {
62
+ knowledge: MemoryEntry[];
63
+ lessons: MemoryEntry[];
64
+ events: MemoryEntry[];
65
+ preferences: MemoryEntry[];
66
+ facets: MemoryEntry[];
67
+ episodeMemories: EpisodeMemory[];
68
+ episodeFacets: EpisodeFacet[];
69
+ semanticMemories: SemanticMemory[];
70
+ procedural: ProceduralMemory[];
71
+ }
72
+
73
+ export interface CueInjectionData {
74
+ knowledge: MemoryEntry[];
75
+ lessons: MemoryEntry[];
76
+ events: MemoryEntry[];
77
+ preferences: MemoryEntry[];
78
+ facets: MemoryEntry[];
79
+ episodes: Episode[];
80
+ work: WorkEntry[];
81
+ episodeMemories: EpisodeMemory[];
82
+ episodeFacets: EpisodeFacet[];
83
+ semanticMemories: SemanticMemory[];
84
+ procedural: ProceduralMemory[];
85
+ }
86
+
87
+ export interface InjectedMemoryOrderRecord {
88
+ memoryId: string;
89
+ memoryKind: string;
90
+ }
91
+
92
+ function toLegacyMemoryKind(entry: MemoryEntry): string {
93
+ switch (entry.type) {
94
+ case "fact":
95
+ case "decision":
96
+ case "entity":
97
+ return "knowledge";
98
+ case "lesson":
99
+ case "preference":
100
+ case "event":
101
+ return entry.type;
102
+ case "pattern":
103
+ case "struggle":
104
+ return "facet";
105
+ default:
106
+ return "memory";
107
+ }
108
+ }
109
+
110
+ export function buildInjectedMemoryOrder(
111
+ active: ActiveInjectionData,
112
+ cue: CueInjectionData,
113
+ ): InjectedMemoryOrderRecord[] {
114
+ const conversationPreferenceEntries: InjectedMemoryOrderRecord[] = [];
115
+ const activeEntries: InjectedMemoryOrderRecord[] = [];
116
+ const keyEventEntries: InjectedMemoryOrderRecord[] = [];
117
+ const stateEntries: InjectedMemoryOrderRecord[] = [];
118
+
119
+ const pushActiveLegacyEntry = (entry: MemoryEntry) => {
120
+ const record = {
121
+ memoryId: entry.id,
122
+ memoryKind: toLegacyMemoryKind(entry),
123
+ };
124
+ if (isConversationPreference(entry)) {
125
+ conversationPreferenceEntries.push(record);
126
+ return;
127
+ }
128
+ if (entry.stability === "situational" || entry.stateData) {
129
+ stateEntries.push(record);
130
+ return;
131
+ }
132
+ activeEntries.push(record);
133
+ };
134
+
135
+ for (const entry of active.lessons) pushActiveLegacyEntry(entry);
136
+ for (const entry of active.knowledge) pushActiveLegacyEntry(entry);
137
+ for (const entry of active.preferences) pushActiveLegacyEntry(entry);
138
+ for (const entry of active.facets) {
139
+ activeEntries.push({ memoryId: entry.id, memoryKind: "facet" });
140
+ }
141
+ for (const entry of active.events) {
142
+ keyEventEntries.push({ memoryId: entry.id, memoryKind: "event" });
143
+ }
144
+
145
+ return [
146
+ ...conversationPreferenceEntries,
147
+ ...activeEntries,
148
+ ...active.episodeMemories.map((entry) => ({ memoryId: entry.id, memoryKind: "episode" })),
149
+ ...active.episodeFacets.map((entry) => ({ memoryId: entry.id, memoryKind: "episodeFacet" })),
150
+ ...active.semanticMemories.map((entry) => ({ memoryId: entry.id, memoryKind: "semantic" })),
151
+ ...active.procedural.map((entry) => ({ memoryId: entry.id, memoryKind: "procedural" })),
152
+ ...keyEventEntries,
153
+ ...stateEntries,
154
+ ...cue.lessons.map((entry) => ({ memoryId: entry.id, memoryKind: "lesson" })),
155
+ ...cue.knowledge.map((entry) => ({ memoryId: entry.id, memoryKind: "knowledge" })),
156
+ ...cue.events.map((entry) => ({ memoryId: entry.id, memoryKind: "event" })),
157
+ ...cue.preferences.map((entry) => ({ memoryId: entry.id, memoryKind: "preference" })),
158
+ ...cue.facets.map((entry) => ({ memoryId: entry.id, memoryKind: "facet" })),
159
+ ...cue.episodeMemories.map((entry) => ({ memoryId: entry.id, memoryKind: "episode" })),
160
+ ...cue.episodeFacets.map((entry) => ({ memoryId: entry.id, memoryKind: "episodeFacet" })),
161
+ ...cue.semanticMemories.map((entry) => ({ memoryId: entry.id, memoryKind: "semantic" })),
162
+ ...cue.procedural.map((entry) => ({ memoryId: entry.id, memoryKind: "procedural" })),
163
+ ];
164
+ }
165
+
166
+ export function buildProgressiveInjectionText(
167
+ active: ActiveInjectionData,
168
+ cue: CueInjectionData,
169
+ allEntries: MemoryEntry[],
170
+ graphContext: GraphNeighbor[],
171
+ p: PromptSet,
172
+ ): string {
173
+ const sections: string[] = [];
174
+ const proceduralSectionTitle = "Procedures";
175
+ const episodicSectionTitle = "Episode Threads";
176
+ const semanticSectionTitle = "Semantic Abstractions";
177
+
178
+ // ── Active tier: full detail ──
179
+ const activeLines: string[] = [];
180
+ const conversationPreferenceLines: string[] = [];
181
+ const keyEventLines: string[] = [];
182
+ const stateLines: string[] = [];
183
+
184
+ const formatActiveEntry = (e: MemoryEntry): string => {
185
+ const related = getGraphContextSummaries(e, allEntries, 3);
186
+ const suffix = related.length ? ` [→ ${related.join("; ")}]` : "";
187
+ return `- [ID: ${e.id}] **${e.name || "—"}**: ${e.summary || ""}\n ${e.detail || ""}${suffix}`;
188
+ };
189
+
190
+ const formatActiveEvent = (e: MemoryEntry): string => {
191
+ const related = getGraphContextSummaries(e, allEntries, 4);
192
+ const outcome = e.eventData?.outcome ? `\n Outcome: ${e.eventData.outcome}` : "";
193
+ const suffix = related.length ? ` [→ ${related.join("; ")}]` : "";
194
+ return `- [ID: ${e.id}] **${e.name || "—"}**: ${e.summary || ""}\n ${e.detail || ""}${outcome}${suffix}`;
195
+ };
196
+
197
+ const formatActiveFacet = (e: MemoryEntry): string => {
198
+ if (e.facetData?.kind === "pattern") {
199
+ return `- [ID: ${e.id}] **${e.name || "—"}**: When ${e.facetData.trigger} → ${e.facetData.behavior}\n ${e.detail || ""}`;
200
+ }
201
+ if (e.facetData?.kind === "struggle") {
202
+ return `- [ID: ${e.id}] **${e.name || "—"}**: ${e.facetData.problem}\n Tried: ${e.facetData.attempts.join(", ")} | Solved: ${e.facetData.solution}\n ${e.detail || ""}`;
203
+ }
204
+ return formatActiveEntry(e);
205
+ };
206
+
207
+ const pushActiveEntry = (entry: MemoryEntry) => {
208
+ if (isConversationPreference(entry)) {
209
+ conversationPreferenceLines.push(formatActiveEntry(entry));
210
+ return;
211
+ }
212
+ if (entry.stability === "situational" || entry.stateData) {
213
+ const mood = entry.stateData?.mood ? ` (${entry.stateData.mood})` : "";
214
+ stateLines.push(`- [ID: ${entry.id}] **${entry.name || "Unknown"}**${mood}: ${entry.summary || ""}`);
215
+ return;
216
+ }
217
+ activeLines.push(formatActiveEntry(entry));
218
+ };
219
+
220
+ for (const e of active.lessons) pushActiveEntry(e);
221
+ for (const e of active.knowledge) pushActiveEntry(e);
222
+ for (const e of active.preferences) pushActiveEntry(e);
223
+ for (const e of active.facets) activeLines.push(formatActiveFacet(e));
224
+ for (const e of active.events) keyEventLines.push(formatActiveEvent(e));
225
+ const episodicLines = active.episodeMemories.map((entry) => {
226
+ const goal = entry.userGoal ? ` | Goal: ${entry.userGoal}` : "";
227
+ const outcome = entry.outcome ? ` | Outcome: ${entry.outcome}` : "";
228
+ return `- [ID: ${entry.id}] **${entry.title || "Episode"}**: ${entry.summary}${goal}${outcome}`;
229
+ });
230
+ const episodicFacetLines = active.episodeFacets.map((entry) => {
231
+ const detail = entry.anchorText ? `\n ${entry.anchorText}` : "";
232
+ return `- [ID: ${entry.id}] [${entry.facetType}] **${entry.searchText}**${detail}`;
233
+ });
234
+ const semanticLines = active.semanticMemories.map((entry) => {
235
+ const detail = entry.detail ? `\n ${entry.detail}` : "";
236
+ return `- [ID: ${entry.id}] [${entry.semanticType}] **${entry.name}**: ${entry.summary}${detail}`;
237
+ });
238
+ const proceduralLines = active.procedural.map((entry) => {
239
+ const steps = entry.steps.slice(0, 4).map((step, index) => `${index + 1}. ${step.text}`).join("\n ");
240
+ const boundaries = entry.boundaries ? `\n Boundaries: ${entry.boundaries}` : "";
241
+ return `- [ID: ${entry.id}] **${entry.name}**: ${entry.summary}\n ${steps}${boundaries}`;
242
+ });
243
+
244
+ if (conversationPreferenceLines.length) {
245
+ sections.push(`### ${p.sectionConversationPreferences}\n${conversationPreferenceLines.join("\n")}`);
246
+ }
247
+ if (activeLines.length) {
248
+ sections.push(`### ${p.sectionActiveMemories}\n${activeLines.join("\n")}`);
249
+ }
250
+ if (episodicLines.length || episodicFacetLines.length) {
251
+ sections.push(`### ${episodicSectionTitle}\n${[...episodicLines, ...episodicFacetLines].join("\n")}`);
252
+ }
253
+ if (semanticLines.length) {
254
+ sections.push(`### ${semanticSectionTitle}\n${semanticLines.join("\n")}`);
255
+ }
256
+ if (proceduralLines.length) {
257
+ sections.push(`### ${proceduralSectionTitle}\n${proceduralLines.join("\n")}`);
258
+ }
259
+ if (keyEventLines.length) {
260
+ sections.push(`### ${p.sectionKeyEvents ?? "Key Events"}\n${keyEventLines.join("\n")}`);
261
+ }
262
+ if (stateLines.length) {
263
+ sections.push(`### ${p.sectionCurrentState ?? "Current State Signals"}\n${stateLines.join("\n")}`);
264
+ }
265
+
266
+ const relatedLines = graphContext.map(
267
+ (neighbor) =>
268
+ `- [${neighbor.relation}] [ID: ${neighbor.entry.id}] [${neighbor.entry.type}] **${
269
+ neighbor.entry.name || "Unknown"
270
+ }**: ${neighbor.entry.summary || ""}`,
271
+ );
272
+ if (relatedLines.length) {
273
+ sections.push(`### ${p.sectionRelatedContext ?? "Related Context"}\n${relatedLines.join("\n")}`);
274
+ }
275
+
276
+ // ── Cue tier: name + summary + id ──
277
+ const cueLines: string[] = [];
278
+
279
+ const formatCueEntry = (e: MemoryEntry): string =>
280
+ `- [ID: ${e.id}] [${e.type}] **${e.name || "—"}**: ${e.summary || ""}`;
281
+
282
+ const formatCueFacet = (e: MemoryEntry): string => {
283
+ if (e.facetData?.kind === "pattern") {
284
+ return `- [ID: ${e.id}] [pattern] **${e.name || "—"}**: When ${e.facetData.trigger} → ${e.facetData.behavior}`;
285
+ }
286
+ if (e.facetData?.kind === "struggle") {
287
+ return `- [ID: ${e.id}] [struggle] **${e.name || "—"}**: ${e.facetData.problem}`;
288
+ }
289
+ return formatCueEntry(e);
290
+ };
291
+
292
+ const formatCueEvent = (e: MemoryEntry): string =>
293
+ `- [ID: ${e.id}] [event] **${e.name || "—"}**: ${e.summary || ""}`;
294
+
295
+ for (const e of cue.lessons) cueLines.push(formatCueEntry(e));
296
+ for (const e of cue.knowledge) cueLines.push(formatCueEntry(e));
297
+ for (const e of cue.events) cueLines.push(formatCueEvent(e));
298
+ for (const e of cue.preferences) cueLines.push(formatCueEntry(e));
299
+ for (const e of cue.facets) cueLines.push(formatCueFacet(e));
300
+ for (const episode of cue.episodeMemories) {
301
+ cueLines.push(`- [ID: ${episode.id}] [episode] **${episode.title || "Episode"}**: ${episode.summary}`);
302
+ }
303
+ for (const facet of cue.episodeFacets) {
304
+ cueLines.push(`- [ID: ${facet.id}] [episode-${facet.facetType}] **${facet.searchText}**: ${facet.summary || facet.anchorText || ""}`);
305
+ }
306
+ for (const semantic of cue.semanticMemories) {
307
+ cueLines.push(`- [ID: ${semantic.id}] [semantic-${semantic.semanticType}] **${semantic.name}**: ${semantic.summary}`);
308
+ }
309
+ for (const procedure of cue.procedural) {
310
+ cueLines.push(`- [ID: ${procedure.id}] [procedural] **${procedure.name}**: ${procedure.summary}`);
311
+ }
312
+
313
+ // Episodes in cue layer (no ID-based recall)
314
+ for (const ep of cue.episodes) {
315
+ cueLines.push(
316
+ `- [${ep.date}] ${ep.project}: ${ep.summary}${ep.userGoal ? ` (Goal: ${ep.userGoal})` : ""}`,
317
+ );
318
+ }
319
+ // Work in cue layer
320
+ for (const w of cue.work) {
321
+ cueLines.push(`- [${w.created.slice(0, 10)}] ${w.goal}: ${w.summary}`);
322
+ }
323
+
324
+ if (cueLines.length) {
325
+ sections.push(`### ${p.sectionMemoryCues}\n*${p.memoryCueHint}*\n${cueLines.join("\n")}`);
326
+ }
327
+
328
+ if (!sections.length) return "";
329
+ const soulStyle = undefined;
330
+ const behaviorBlock = soulStyle
331
+ ? `${p.memoryBehavior}\n\n${soulStyle}`
332
+ : p.memoryBehavior;
333
+ return `## ${p.injectionHeader}\n\n${sections.join("\n\n")}\n\n---\n${behaviorBlock}`;
334
+ }
@@ -0,0 +1,182 @@
1
+ /**
2
+ * [WHO]: Provides generateInsightsReport, generateRecommendations, generateRulesBasedRecommendations
3
+ * [FROM]: Depends on ./i18n.js for PROMPTS; ./llm-json.js for tolerant structured parsing; ./types.js for memory types and insights
4
+ * [TO]: Consumed by engine.ts (generateInsights)
5
+ * [HERE]: packages/mem-core/src/engine-insights.ts - insights report generation (LLM + rules-based)
6
+ */
7
+
8
+ import { PROMPTS } from "./i18n.js";
9
+ import { parseLlmJson } from "./llm-json.js";
10
+ import type { InsightsReport, LlmFn, MemoryEntry, PatternInsight, StruggleInsight, Meta, WorkEntry, Episode } from "./types.js";
11
+
12
+ export interface RuntimeMemoryView {
13
+ knowledge: MemoryEntry[];
14
+ lessons: MemoryEntry[];
15
+ events: MemoryEntry[];
16
+ preferences: MemoryEntry[];
17
+ facets: MemoryEntry[];
18
+ work: WorkEntry[];
19
+ episodes: Episode[];
20
+ meta: Meta;
21
+ v2SearchEntries: MemoryEntry[];
22
+ }
23
+
24
+ export async function generateInsightsReport(
25
+ all: RuntimeMemoryView,
26
+ locale: string,
27
+ llmFn?: LlmFn,
28
+ ): Promise<InsightsReport> {
29
+ const stats = {
30
+ knowledge: all.knowledge.length,
31
+ lessons: all.lessons.length,
32
+ preferences: all.preferences.length,
33
+ facets: all.facets.length,
34
+ episodes: all.episodes.length,
35
+ work: all.work.length,
36
+ totalSessions: all.meta.totalSessions,
37
+ };
38
+
39
+ // Separate patterns and struggles from facets
40
+ const patternEntries = all.facets.filter((e) => e.type === "pattern");
41
+ const struggleEntries = all.facets.filter((e) => e.type === "struggle");
42
+
43
+ // Weight calculation: (accessCount + 1) × (importance / 10)
44
+ const calcWeight = (e: MemoryEntry, unresolvedBonus = false): number => {
45
+ const base = (e.accessCount + 1) * (e.importance / 10);
46
+ return unresolvedBonus ? base * 1.5 : base;
47
+ };
48
+
49
+ // Build PatternInsight[]
50
+ const patterns: PatternInsight[] = patternEntries
51
+ .map((e) => ({
52
+ entry: e,
53
+ weight: calcWeight(e),
54
+ trigger: e.facetData?.kind === "pattern" ? e.facetData.trigger : (e.summary || e.detail || "").slice(0, 50),
55
+ behavior: e.facetData?.kind === "pattern" ? e.facetData.behavior : (e.summary || e.detail || ""),
56
+ }))
57
+ .sort((a, b) => b.weight - a.weight);
58
+
59
+ // Build StruggleInsight[]
60
+ const struggles: StruggleInsight[] = struggleEntries
61
+ .map((e) => {
62
+ const isResolved = e.facetData?.kind === "struggle" ? !!e.facetData.solution : false;
63
+ return {
64
+ entry: e,
65
+ weight: calcWeight(e, !isResolved),
66
+ problem: e.facetData?.kind === "struggle" ? e.facetData.problem : (e.summary || e.detail || ""),
67
+ attempts: e.facetData?.kind === "struggle" ? e.facetData.attempts : [],
68
+ solution: e.facetData?.kind === "struggle" ? e.facetData.solution : "",
69
+ resolved: isResolved,
70
+ };
71
+ })
72
+ .sort((a, b) => {
73
+ // Unresolved first, then by weight
74
+ if (a.resolved !== b.resolved) return a.resolved ? 1 : -1;
75
+ return b.weight - a.weight;
76
+ });
77
+
78
+ // Top lessons and knowledge by importance × accessCount
79
+ const sortByRelevance = (arr: MemoryEntry[]) =>
80
+ [...arr].sort((a, b) => b.importance * (b.accessCount + 1) - a.importance * (a.accessCount + 1));
81
+
82
+ const topLessons = sortByRelevance(all.lessons).slice(0, 10);
83
+ const topKnowledge = sortByRelevance(all.knowledge).slice(0, 10);
84
+
85
+ // Generate recommendations (LLM or rules-based fallback)
86
+ const recommendations = await generateRecommendations(patterns, struggles, topLessons, locale, llmFn);
87
+
88
+ return {
89
+ patterns,
90
+ struggles,
91
+ topLessons,
92
+ topKnowledge,
93
+ preferences: all.preferences,
94
+ stats,
95
+ recommendations,
96
+ generatedAt: new Date().toISOString(),
97
+ };
98
+ }
99
+
100
+ export async function generateRecommendations(
101
+ patterns: PatternInsight[],
102
+ struggles: StruggleInsight[],
103
+ lessons: MemoryEntry[],
104
+ locale: string,
105
+ llmFn?: LlmFn,
106
+ ): Promise<string[]> {
107
+ // Try LLM-based recommendations if available
108
+ if (llmFn) {
109
+ try {
110
+ const p = PROMPTS[locale] ?? PROMPTS.en;
111
+ const input = JSON.stringify({
112
+ patterns: patterns.slice(0, 5).map((pa) => ({ trigger: pa.trigger, behavior: pa.behavior })),
113
+ struggles: struggles.slice(0, 5).map((s) => ({ problem: s.problem, resolved: s.resolved })),
114
+ lessons: lessons.slice(0, 5).map((l) => l.summary || l.detail || l.content || ""),
115
+ });
116
+ const raw = await llmFn(p.insightsRecommendationSystem, input);
117
+ const result = parseLlmJson<string[]>(raw);
118
+ if (Array.isArray(result) && result.length > 0) {
119
+ return result.slice(0, 5);
120
+ }
121
+ } catch {
122
+ // Fall through to rules-based
123
+ }
124
+ }
125
+
126
+ // Rules-based fallback recommendations
127
+ return generateRulesBasedRecommendations(patterns, struggles, lessons);
128
+ }
129
+
130
+ export function generateRulesBasedRecommendations(
131
+ patterns: PatternInsight[],
132
+ struggles: StruggleInsight[],
133
+ lessons: MemoryEntry[],
134
+ ): string[] {
135
+ const recommendations: string[] = [];
136
+
137
+ // High-weight patterns → automation suggestion
138
+ if (patterns.length > 0) {
139
+ const top = patterns[0]!;
140
+ recommendations.push(
141
+ `You consistently ${top.behavior} when ${top.trigger}. Consider automating this behavior.`,
142
+ );
143
+ }
144
+
145
+ // Unresolved struggles → systematic review suggestion
146
+ const unresolved = struggles.filter((s) => !s.resolved);
147
+ if (unresolved.length >= 2) {
148
+ recommendations.push(
149
+ `You have ${unresolved.length} unresolved issues. Consider tackling them systematically.`,
150
+ );
151
+ }
152
+
153
+ // Recurring tag patterns in struggles → domain-specific review
154
+ const struggleTags = struggles.flatMap((s) => s.entry.tags);
155
+ const tagCounts = struggleTags.reduce(
156
+ (acc, tag) => {
157
+ acc[tag] = (acc[tag] || 0) + 1;
158
+ return acc;
159
+ },
160
+ {} as Record<string, number>,
161
+ );
162
+ const topTag = Object.entries(tagCounts).sort((a, b) => b[1] - a[1])[0];
163
+ if (topTag && topTag[1] >= 3) {
164
+ recommendations.push(
165
+ `Issues related to "${topTag[0]}" appear frequently. Consider deeper learning in this area.`,
166
+ );
167
+ }
168
+
169
+ // Lessons accumulation → expertise recognition
170
+ if (lessons.length >= 5) {
171
+ recommendations.push(
172
+ `You've accumulated ${lessons.length} lessons. This is valuable expertise.`,
173
+ );
174
+ }
175
+
176
+ // No data → encouragement
177
+ if (patterns.length === 0 && struggles.length === 0 && lessons.length === 0) {
178
+ recommendations.push(`Keep using the system to let it learn your work habits.`);
179
+ }
180
+
181
+ return recommendations.slice(0, 5);
182
+ }