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,154 @@
1
+ /**
2
+ * [WHO]: Provides reinforceProcedural, reinforceEpisodeMemories, reinforceEpisodeFacets, reinforceV2SemanticMemories, reinforceWork, reconsolidateV2AfterRecall, reconsolidateIfNeeded
3
+ * [FROM]: Depends on ./store.js, ./store-v2.js, ./eviction.js, ./linking.js, ./scoring.js, ./reconsolidate-v2.js, ./i18n.js, ./types.js, ./types-v2.js
4
+ * [TO]: Consumed by engine.ts (getMemoryInjection)
5
+ * [HERE]: packages/mem-core/src/engine-reinforce.ts - memory reinforcement and reconsolidation after recall
6
+ */
7
+
8
+ import { PROMPTS } from "./i18n.js";
9
+ import { reinforceRelations } from "./linking.js";
10
+ import { reconsolidateV2Memories } from "./reconsolidate-v2.js";
11
+ import { extractTags, tagOverlap } from "./scoring.js";
12
+ import { saveEntries, saveWork } from "./store.js";
13
+ import {
14
+ loadV2Meta,
15
+ saveV2Episodes,
16
+ saveV2Facets,
17
+ saveV2Meta,
18
+ saveV2Procedural,
19
+ saveV2Semantic,
20
+ type NanoMemV2Paths,
21
+ } from "./store-v2.js";
22
+ import type { LlmFn, MemoryEntry, WorkEntry } from "./types.js";
23
+ import type { EpisodeFacet, EpisodeMemory, ProceduralMemory, SemanticMemory } from "./types-v2.js";
24
+ import { utilityEntry, utilityWork } from "./eviction.js";
25
+ import type { NanomemConfig } from "./config.js";
26
+
27
+ export async function reinforceProcedural(recalled: ProceduralMemory[], all: ProceduralMemory[], v2Paths: NanoMemV2Paths): Promise<void> {
28
+ if (!recalled.length) return;
29
+ const ids = new Set(recalled.map((entry) => entry.id));
30
+ const now = new Date().toISOString();
31
+ for (const entry of all) {
32
+ if (!ids.has(entry.id)) continue;
33
+ entry.accessCount = (entry.accessCount ?? 0) + 1;
34
+ entry.lastAccessedAt = now;
35
+ entry.updatedAt = entry.updatedAt || now;
36
+ }
37
+ await saveV2Procedural(v2Paths, all);
38
+ }
39
+
40
+ export async function reinforceEpisodeMemories(recalled: EpisodeMemory[], all: EpisodeMemory[], v2Paths: NanoMemV2Paths): Promise<void> {
41
+ if (!recalled.length) return;
42
+ const ids = new Set(recalled.map((entry) => entry.id));
43
+ const now = new Date().toISOString();
44
+ for (const entry of all) {
45
+ if (!ids.has(entry.id)) continue;
46
+ entry.accessCount = (entry.accessCount ?? 0) + 1;
47
+ entry.lastAccessedAt = now;
48
+ entry.updatedAt = entry.updatedAt || now;
49
+ }
50
+ await saveV2Episodes(v2Paths, all);
51
+ }
52
+
53
+ export async function reinforceEpisodeFacets(recalled: EpisodeFacet[], all: EpisodeFacet[], v2Paths: NanoMemV2Paths): Promise<void> {
54
+ if (!recalled.length) return;
55
+ const ids = new Set(recalled.map((entry) => entry.id));
56
+ const now = new Date().toISOString();
57
+ for (const entry of all) {
58
+ if (!ids.has(entry.id)) continue;
59
+ entry.accessCount = (entry.accessCount ?? 0) + 1;
60
+ entry.lastAccessedAt = now;
61
+ entry.updatedAt = entry.updatedAt || now;
62
+ }
63
+ await saveV2Facets(v2Paths, all);
64
+ }
65
+
66
+ export async function reinforceV2SemanticMemories(recalled: SemanticMemory[], all: SemanticMemory[], v2Paths: NanoMemV2Paths): Promise<void> {
67
+ if (!recalled.length) return;
68
+ const ids = new Set(recalled.map((entry) => entry.id));
69
+ const now = new Date().toISOString();
70
+ for (const entry of all) {
71
+ if (!ids.has(entry.id)) continue;
72
+ entry.accessCount = (entry.accessCount ?? 0) + 1;
73
+ entry.lastAccessedAt = now;
74
+ entry.updatedAt = entry.updatedAt || now;
75
+ }
76
+ await saveV2Semantic(v2Paths, all);
77
+ }
78
+
79
+ export async function reinforceWork(
80
+ recalled: WorkEntry[], all: WorkEntry[],
81
+ workPath: string, cfg: NanomemConfig,
82
+ ): Promise<void> {
83
+ const ids = new Set(recalled.map((w) => w.id));
84
+ const now = new Date().toISOString();
85
+ for (const w of all) {
86
+ if (ids.has(w.id)) {
87
+ w.accessCount = (w.accessCount ?? 0) + 1;
88
+ w.lastAccessed = now;
89
+ w.strength = (w.strength || 45) * cfg.strengthGrowthFactor;
90
+ }
91
+ }
92
+ await saveWork(workPath, all, cfg.maxEntries.work, (w) =>
93
+ utilityWork(w, cfg.halfLife, cfg.evictionWeights),
94
+ );
95
+ }
96
+
97
+ export async function reconsolidateV2AfterRecall(
98
+ episodes: EpisodeMemory[],
99
+ facets: EpisodeFacet[],
100
+ procedural: ProceduralMemory[],
101
+ recalledEpisodes: EpisodeMemory[],
102
+ recalledFacets: EpisodeFacet[],
103
+ recalledProcedural: ProceduralMemory[],
104
+ v2Paths: NanoMemV2Paths,
105
+ rebuildLinksFn: () => Promise<void>,
106
+ ): Promise<void> {
107
+ const reconsolidated = reconsolidateV2Memories(
108
+ episodes,
109
+ facets,
110
+ procedural,
111
+ recalledEpisodes.map((entry) => entry.id),
112
+ recalledFacets.map((entry) => entry.id),
113
+ recalledProcedural.map((entry) => entry.id),
114
+ );
115
+ await Promise.all([
116
+ saveV2Episodes(v2Paths, reconsolidated.episodes),
117
+ saveV2Facets(v2Paths, reconsolidated.facets),
118
+ saveV2Procedural(v2Paths, reconsolidated.procedural),
119
+ saveV2Meta(v2Paths, {
120
+ ...(await loadV2Meta(v2Paths)),
121
+ lastReconsolidationAt: new Date().toISOString(),
122
+ }),
123
+ ]);
124
+ await rebuildLinksFn();
125
+ }
126
+
127
+ export async function reconsolidateIfNeeded(
128
+ recalled: MemoryEntry[],
129
+ contextTags: string[],
130
+ locale: string,
131
+ llmFn: LlmFn,
132
+ ): Promise<void> {
133
+ const p = PROMPTS[locale];
134
+ for (const entry of recalled) {
135
+ const overlap = tagOverlap(entry.tags, contextTags);
136
+ if (overlap >= 0.3) continue;
137
+ try {
138
+ const memText = entry.detail || entry.summary || entry.content || "";
139
+ const updated = await llmFn(
140
+ p.reconsolidationSystem,
141
+ `Original memory: ${memText}\n\nCurrent context tags: ${contextTags.join(", ")}`,
142
+ );
143
+ if (updated && updated.length > 10) {
144
+ const trimmed = updated.trim();
145
+ entry.detail = trimmed;
146
+ entry.content = trimmed;
147
+ entry.summary = trimmed.length <= 150 ? trimmed : `${trimmed.slice(0, 147)}...`;
148
+ entry.tags = extractTags(trimmed);
149
+ }
150
+ } catch {
151
+ /* graceful degradation */
152
+ }
153
+ }
154
+ }
@@ -0,0 +1,188 @@
1
+ /**
2
+ * [WHO]: Provides currentStructuralAnchor, computeStructuralBoost, scoreEpisodeMemory, scoreEpisodeFacet, scoreV2SemanticMemory, scoreProceduralMemory
3
+ * [FROM]: Depends on ./scoring.js for daysSince, extractTags, tagOverlap; ./types-v2.js for V2 memory types; ./turn-context.js for the generic per-turn hint bus
4
+ * [TO]: Consumed by engine.ts, engine-recall-select.ts, engine-episode-sync.ts, engine-v2-mapping.ts
5
+ * [HERE]: packages/mem-core/src/engine-scoring-v2.ts - V2 memory scoring and structural proximity computation; reads structural anchors from any extension that publishes to the turn-context bus
6
+ */
7
+
8
+ import { daysSince, extractTags, tagOverlap } from "./scoring.js";
9
+ import { getTurnContext } from "./turn-context.js";
10
+ import type { BaseMemoryV2, EpisodeFacet, EpisodeMemory, ProceduralMemory, SemanticMemory } from "./types-v2.js";
11
+
12
+ type MemoryWithModifiedFiles = BaseMemoryV2 & { filesModified?: unknown };
13
+
14
+ /** Read the current structural anchor (or undefined when no producer has published one). */
15
+ export function currentStructuralAnchor(): { modulePath?: string; filePath?: string } | undefined {
16
+ const anchor = getTurnContext("structuralAnchor");
17
+ if (!anchor || (!anchor.modulePath && !anchor.filePath)) return undefined;
18
+ return {
19
+ modulePath: anchor.modulePath,
20
+ filePath: anchor.filePath,
21
+ };
22
+ }
23
+
24
+ /** Match two paths that may differ in absolute/relative form. */
25
+ function pathsOverlap(a: string, b: string): boolean {
26
+ if (a === b) return true;
27
+ if (a.startsWith(b + "/") || b.startsWith(a + "/")) return true;
28
+ // Suffix alignment: absolute vs relative (e.g. "/root/proj/core/x.ts" vs "core/x.ts")
29
+ if (a.endsWith("/" + b) || b.endsWith("/" + a)) return true;
30
+ return false;
31
+ }
32
+
33
+ /**
34
+ * Compute structural proximity boost from the active turn's structural anchor.
35
+ * Reads from the generic turn-context bus; returns 0 when no anchor has been
36
+ * published or when no overlap with the entry's paths is found. Producer-agnostic.
37
+ */
38
+ export function computeStructuralBoost(entry: BaseMemoryV2): number {
39
+ const anchor = getTurnContext("structuralAnchor");
40
+ const anchorPaths = anchor?.candidatePaths;
41
+ if (!anchorPaths || anchorPaths.length === 0) return 0;
42
+
43
+ const entryPaths: string[] = [];
44
+ if (entry.structuralAnchor) {
45
+ if (entry.structuralAnchor.modulePath) entryPaths.push(entry.structuralAnchor.modulePath);
46
+ if (entry.structuralAnchor.filePath) entryPaths.push(entry.structuralAnchor.filePath);
47
+ }
48
+ if (entry.evidence) {
49
+ for (const ev of entry.evidence) {
50
+ if (ev.filePath) entryPaths.push(ev.filePath);
51
+ }
52
+ }
53
+ const modifiedFiles = (entry as MemoryWithModifiedFiles).filesModified;
54
+ if (Array.isArray(modifiedFiles)) {
55
+ for (const f of modifiedFiles) {
56
+ if (typeof f === "string") entryPaths.push(f);
57
+ }
58
+ }
59
+
60
+ // Fallback: infer module paths from tags when no explicit paths exist.
61
+ // Tags often contain path segments like "core/platform/config" or filenames.
62
+ // Use a weak match (0.5× weight via reduced return) to avoid conflating
63
+ // with the relevance signal which already scores tag overlap.
64
+ if (entryPaths.length === 0) {
65
+ const PATH_LIKE = /^[a-z][\w-]*(?:\/[\w.-]+)+$/;
66
+ const tagPaths = entry.tags.filter((t) => PATH_LIKE.test(t));
67
+ if (tagPaths.length === 0) return 0;
68
+ let tagHits = 0;
69
+ for (const t of tagPaths) {
70
+ for (const a of anchorPaths) {
71
+ if (pathsOverlap(t, a)) { tagHits++; break; }
72
+ }
73
+ }
74
+ // Attenuate: tag-inferred paths are weaker evidence than explicit anchors
75
+ return Math.min(tagHits / tagPaths.length, 1) * 0.5;
76
+ }
77
+
78
+ let hits = 0;
79
+ for (const p of entryPaths) {
80
+ for (const a of anchorPaths) {
81
+ if (pathsOverlap(p, a)) { hits++; break; }
82
+ }
83
+ }
84
+ return Math.min(hits / entryPaths.length, 1);
85
+ }
86
+
87
+ /** Score breakdown for eval collection. All fields are the raw component values before final combination. */
88
+ export interface V2ScoreBreakdown {
89
+ recency: number;
90
+ importance: number;
91
+ relevance: number;
92
+ structural: number;
93
+ final: number;
94
+ }
95
+
96
+ export function breakdownEpisodeMemory(entry: EpisodeMemory, project: string, contextTags: string[], structuralWeight: number): V2ScoreBreakdown {
97
+ const projectBoost = !project ? 0.8 : entry.scope?.project === project ? 1 : 0.55;
98
+ const tagScore = tagOverlap(entry.tags, contextTags);
99
+ const summaryTags = extractTags(`${entry.title ?? ""} ${entry.summary} ${entry.userGoal ?? ""} ${entry.outcome ?? ""}`);
100
+ const semanticScore = tagOverlap(summaryTags, contextTags);
101
+ const recency = 1 / (1 + daysSince(entry.updatedAt || entry.createdAt));
102
+ const salienceBoost = (entry.salience ?? entry.importance) / 10;
103
+ const structural = computeStructuralBoost(entry);
104
+ const relevance = projectBoost * (0.45 + 0.55 * Math.max(tagScore, semanticScore));
105
+ const final = relevance + recency * 0.18 + salienceBoost * 0.22 + structural * structuralWeight;
106
+ return { recency, importance: salienceBoost, relevance, structural, final };
107
+ }
108
+
109
+ export function breakdownEpisodeFacet(entry: EpisodeFacet, project: string, contextTags: string[], structuralWeight: number): V2ScoreBreakdown {
110
+ const projectBoost = !project ? 0.8 : entry.scope?.project === project ? 1 : 0.55;
111
+ const tagScore = tagOverlap(entry.tags, contextTags);
112
+ const semanticTags = extractTags(`${entry.searchText} ${entry.anchorText ?? ""} ${entry.summary ?? ""}`);
113
+ const semanticScore = tagOverlap(semanticTags, contextTags);
114
+ const recency = 1 / (1 + daysSince(entry.updatedAt || entry.createdAt));
115
+ const salienceBoost = (entry.salience ?? entry.importance) / 10;
116
+ const structural = computeStructuralBoost(entry);
117
+ const relevance = projectBoost * (0.45 + 0.55 * Math.max(tagScore, semanticScore));
118
+ const final = relevance + recency * 0.15 + salienceBoost * 0.24 + structural * structuralWeight;
119
+ return { recency, importance: salienceBoost, relevance, structural, final };
120
+ }
121
+
122
+ export function breakdownV2Semantic(entry: SemanticMemory, project: string, contextTags: string[], structuralWeight: number): V2ScoreBreakdown {
123
+ const projectBoost = !project ? 0.8 : entry.scope?.project === project ? 1 : 0.55;
124
+ const tagScore = tagOverlap(entry.tags, contextTags);
125
+ const semanticTags = extractTags(`${entry.name} ${entry.summary} ${entry.detail ?? ""}`);
126
+ const semanticScore = tagOverlap(semanticTags, contextTags);
127
+ const recency = 1 / (1 + daysSince(entry.updatedAt || entry.createdAt));
128
+ const salienceBoost = (entry.salience ?? entry.importance) / 10;
129
+ const structural = computeStructuralBoost(entry);
130
+ const relevance = projectBoost * (0.45 + 0.55 * Math.max(tagScore, semanticScore));
131
+ const final = relevance + recency * 0.14 + salienceBoost * 0.2 + structural * structuralWeight;
132
+ return { recency, importance: salienceBoost, relevance, structural, final };
133
+ }
134
+
135
+ export function breakdownProcedural(entry: ProceduralMemory, project: string, contextTags: string[], structuralWeight: number): V2ScoreBreakdown {
136
+ const projectBoost = !project ? 0.8 : entry.scope?.project === project ? 1 : 0.55;
137
+ const tagScore = tagOverlap(entry.tags, contextTags);
138
+ const summaryTags = extractTags(`${entry.searchText} ${entry.summary} ${entry.contextText ?? ""}`);
139
+ const semanticScore = tagOverlap(summaryTags, contextTags);
140
+ const recency = 1 / (1 + daysSince(entry.updatedAt || entry.createdAt));
141
+ const statusBoost = entry.status === "active" ? 0.2 : entry.status === "draft" ? 0.05 : -0.2;
142
+ const salienceBoost = (entry.salience ?? entry.importance) / 10;
143
+ const structural = computeStructuralBoost(entry);
144
+ const relevance = projectBoost * (0.45 + 0.55 * Math.max(tagScore, semanticScore));
145
+ const final = relevance + recency * 0.15 + salienceBoost * 0.2 + statusBoost + structural * structuralWeight;
146
+ return { recency, importance: salienceBoost, relevance, structural, final };
147
+ }
148
+
149
+ export function scoreEpisodeMemory(entry: EpisodeMemory, project: string, contextTags: string[], structuralWeight: number): number {
150
+ const projectBoost = !project ? 0.8 : entry.scope?.project === project ? 1 : 0.55;
151
+ const tagScore = tagOverlap(entry.tags, contextTags);
152
+ const summaryTags = extractTags(`${entry.title ?? ""} ${entry.summary} ${entry.userGoal ?? ""} ${entry.outcome ?? ""}`);
153
+ const semanticScore = tagOverlap(summaryTags, contextTags);
154
+ const recency = 1 / (1 + daysSince(entry.updatedAt || entry.createdAt));
155
+ const salienceBoost = (entry.salience ?? entry.importance) / 10;
156
+ return projectBoost * (0.45 + 0.55 * Math.max(tagScore, semanticScore)) + recency * 0.18 + salienceBoost * 0.22 + computeStructuralBoost(entry) * structuralWeight;
157
+ }
158
+
159
+ export function scoreEpisodeFacet(entry: EpisodeFacet, project: string, contextTags: string[], structuralWeight: number): number {
160
+ const projectBoost = !project ? 0.8 : entry.scope?.project === project ? 1 : 0.55;
161
+ const tagScore = tagOverlap(entry.tags, contextTags);
162
+ const semanticTags = extractTags(`${entry.searchText} ${entry.anchorText ?? ""} ${entry.summary ?? ""}`);
163
+ const semanticScore = tagOverlap(semanticTags, contextTags);
164
+ const recency = 1 / (1 + daysSince(entry.updatedAt || entry.createdAt));
165
+ const salienceBoost = (entry.salience ?? entry.importance) / 10;
166
+ return projectBoost * (0.45 + 0.55 * Math.max(tagScore, semanticScore)) + recency * 0.15 + salienceBoost * 0.24 + computeStructuralBoost(entry) * structuralWeight;
167
+ }
168
+
169
+ export function scoreV2SemanticMemory(entry: SemanticMemory, project: string, contextTags: string[], structuralWeight: number): number {
170
+ const projectBoost = !project ? 0.8 : entry.scope?.project === project ? 1 : 0.55;
171
+ const tagScore = tagOverlap(entry.tags, contextTags);
172
+ const semanticTags = extractTags(`${entry.name} ${entry.summary} ${entry.detail ?? ""}`);
173
+ const semanticScore = tagOverlap(semanticTags, contextTags);
174
+ const recency = 1 / (1 + daysSince(entry.updatedAt || entry.createdAt));
175
+ const salienceBoost = (entry.salience ?? entry.importance) / 10;
176
+ return projectBoost * (0.45 + 0.55 * Math.max(tagScore, semanticScore)) + recency * 0.14 + salienceBoost * 0.2 + computeStructuralBoost(entry) * structuralWeight;
177
+ }
178
+
179
+ export function scoreProceduralMemory(entry: ProceduralMemory, project: string, contextTags: string[], structuralWeight: number): number {
180
+ const projectBoost = !project ? 0.8 : entry.scope?.project === project ? 1 : 0.55;
181
+ const tagScore = tagOverlap(entry.tags, contextTags);
182
+ const summaryTags = extractTags(`${entry.searchText} ${entry.summary} ${entry.contextText ?? ""}`);
183
+ const semanticScore = tagOverlap(summaryTags, contextTags);
184
+ const recency = 1 / (1 + daysSince(entry.updatedAt || entry.createdAt));
185
+ const statusBoost = entry.status === "active" ? 0.2 : entry.status === "draft" ? 0.05 : -0.2;
186
+ const salienceBoost = (entry.salience ?? entry.importance) / 10;
187
+ return projectBoost * (0.45 + 0.55 * Math.max(tagScore, semanticScore)) + recency * 0.15 + salienceBoost * 0.2 + statusBoost + computeStructuralBoost(entry) * structuralWeight;
188
+ }
@@ -0,0 +1,204 @@
1
+ /**
2
+ * [WHO]: Provides inferSemanticRetention/Stability/Importance, mapExtractedItemToSemanticType, upsertSemanticFromExtractedItem, semanticKindToLegacyType, semanticToRuntimeEntry, proceduralToRuntimeEntry
3
+ * [FROM]: Depends on ./privacy.js for filterPII; ./scoring.js for extractTags, tagOverlap; ./types.js, ./types-v2.js; ./engine-scoring-v2.js for currentStructuralAnchor
4
+ * [TO]: Consumed by engine.ts (extractAndStore, buildRuntimeMemoryView)
5
+ * [HERE]: packages/mem-core/src/engine-v2-mapping.ts - V2 type mapping and extraction-to-semantic conversion
6
+ */
7
+
8
+ import { currentStructuralAnchor } from "./engine-scoring-v2.js";
9
+ import { extractTags, tagOverlap } from "./scoring.js";
10
+ import { filterPII } from "./privacy.js";
11
+ import type { ExtractedItem, MemoryEntry, MemoryScope } from "./types.js";
12
+ import type { ProceduralMemory, SemanticMemory } from "./types-v2.js";
13
+
14
+ export function inferSemanticRetention(item: ExtractedItem): SemanticMemory["retention"] {
15
+ if (item.retention) return item.retention;
16
+ if (item.stability === "situational" || item.stateData) return "ambient";
17
+ switch (item.type) {
18
+ case "preference":
19
+ case "pattern":
20
+ return "core";
21
+ case "lesson":
22
+ case "decision":
23
+ case "event":
24
+ case "struggle":
25
+ return "key-event";
26
+ default:
27
+ return "ambient";
28
+ }
29
+ }
30
+
31
+ export function inferSemanticStability(item: ExtractedItem): SemanticMemory["stability"] {
32
+ if (item.stability === "situational") return "situational";
33
+ if (item.stateData) return "volatile";
34
+ switch (item.type) {
35
+ case "preference":
36
+ case "pattern":
37
+ return "stable";
38
+ case "event":
39
+ case "struggle":
40
+ return "situational";
41
+ default:
42
+ return "stable";
43
+ }
44
+ }
45
+
46
+ export function inferSemanticImportance(item: ExtractedItem): number {
47
+ switch (item.type) {
48
+ case "struggle":
49
+ case "event":
50
+ return 9;
51
+ case "lesson":
52
+ case "decision":
53
+ return 8;
54
+ case "pattern":
55
+ return 7;
56
+ case "preference":
57
+ return 6;
58
+ default:
59
+ return 5;
60
+ }
61
+ }
62
+
63
+ export function mapExtractedItemToSemanticType(item: ExtractedItem): SemanticMemory["semanticType"] {
64
+ switch (item.type) {
65
+ case "lesson":
66
+ return "lesson";
67
+ case "preference":
68
+ return "preference";
69
+ case "decision":
70
+ return "decision";
71
+ case "pattern":
72
+ return "pattern";
73
+ case "struggle":
74
+ return "struggle";
75
+ case "event":
76
+ return "event";
77
+ default:
78
+ return "fact";
79
+ }
80
+ }
81
+
82
+ export function upsertSemanticFromExtractedItem(
83
+ semantic: SemanticMemory[],
84
+ item: ExtractedItem,
85
+ project: string,
86
+ defaultScope: MemoryScope | undefined,
87
+ ): void {
88
+ if (item.type === "retract") return;
89
+ const detail = filterPII(item.detail || item.content || "");
90
+ const name = item.name || detail.slice(0, 30) || item.summary || "memory";
91
+ const summary = item.summary || detail.slice(0, 150) || name;
92
+ const semanticType = mapExtractedItemToSemanticType(item);
93
+ const tags = extractTags(`${name} ${summary} ${detail}`);
94
+ const now = new Date().toISOString();
95
+ const existing = semantic.find(
96
+ (entry) =>
97
+ entry.semanticType === semanticType &&
98
+ entry.scope?.project === project &&
99
+ (tagOverlap(entry.tags, tags) >= 0.72 ||
100
+ `${entry.name} ${entry.summary}`.trim().toLowerCase() === `${name} ${summary}`.trim().toLowerCase()),
101
+ );
102
+ if (existing) {
103
+ existing.name = name;
104
+ existing.summary = summary;
105
+ existing.detail = detail || existing.detail;
106
+ existing.tags = [...new Set(tags)];
107
+ existing.updatedAt = now;
108
+ existing.retention = inferSemanticRetention(item);
109
+ existing.stability = inferSemanticStability(item);
110
+ existing.salience = Math.max(existing.salience, inferSemanticImportance(item));
111
+ existing.importance = Math.max(existing.importance, inferSemanticImportance(item));
112
+ return;
113
+ }
114
+
115
+ semantic.push({
116
+ id: `semantic:extract:${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
117
+ kind: "semantic",
118
+ semanticType,
119
+ name,
120
+ summary,
121
+ detail,
122
+ accessCount: 0,
123
+ importance: inferSemanticImportance(item),
124
+ salience: Math.max(4, inferSemanticImportance(item)),
125
+ confidence: 0.8,
126
+ retention: inferSemanticRetention(item),
127
+ stability: inferSemanticStability(item),
128
+ tags,
129
+ evidence: [],
130
+ scope: { ...defaultScope, project },
131
+ createdAt: now,
132
+ updatedAt: now,
133
+ abstractionLevel: semanticType === "event" ? "instance" : "generalization",
134
+ structuralAnchor: currentStructuralAnchor(),
135
+ });
136
+ }
137
+
138
+ export function semanticKindToLegacyType(kind: SemanticMemory["semanticType"]): MemoryEntry["type"] {
139
+ switch (kind) {
140
+ case "lesson":
141
+ return "lesson";
142
+ case "preference":
143
+ return "preference";
144
+ case "decision":
145
+ return "decision";
146
+ case "event":
147
+ return "event";
148
+ case "pattern":
149
+ return "pattern";
150
+ case "struggle":
151
+ return "struggle";
152
+ default:
153
+ return "fact";
154
+ }
155
+ }
156
+
157
+ export function semanticToRuntimeEntry(entry: SemanticMemory): MemoryEntry {
158
+ const type = semanticKindToLegacyType(entry.semanticType);
159
+ return {
160
+ id: entry.id,
161
+ type,
162
+ name: entry.name,
163
+ summary: entry.summary,
164
+ detail: entry.detail,
165
+ content: entry.detail || entry.summary,
166
+ tags: entry.tags,
167
+ project: entry.scope?.project || "default",
168
+ importance: entry.importance,
169
+ strength: undefined,
170
+ created: entry.createdAt,
171
+ eventTime: entry.validFrom ?? entry.createdAt,
172
+ lastAccessed: entry.lastAccessedAt,
173
+ accessCount: entry.accessCount,
174
+ relatedIds: [...new Set([...(entry.supersedesIds ?? []), ...(entry.conflictWithIds ?? [])])],
175
+ scope: entry.scope,
176
+ retention: entry.retention === "ambient" ? "ambient" : entry.retention === "core" ? "core" : "key-event",
177
+ salience: entry.salience,
178
+ stability: entry.stability === "volatile" ? "situational" : entry.stability === "stable" ? "stable" : "situational",
179
+ };
180
+ }
181
+
182
+ export function proceduralToRuntimeEntry(entry: ProceduralMemory): MemoryEntry {
183
+ return {
184
+ id: entry.id,
185
+ type: "decision",
186
+ name: entry.name,
187
+ summary: entry.summary,
188
+ detail: [entry.contextText, entry.boundaries, entry.steps.map((step) => step.text).join("\n")].filter(Boolean).join("\n"),
189
+ content: entry.summary,
190
+ tags: entry.tags,
191
+ project: entry.scope?.project || "default",
192
+ importance: entry.importance,
193
+ strength: undefined,
194
+ created: entry.createdAt,
195
+ eventTime: entry.validFrom ?? entry.createdAt,
196
+ lastAccessed: entry.lastAccessedAt,
197
+ accessCount: entry.accessCount,
198
+ relatedIds: entry.supersedesIds,
199
+ scope: entry.scope,
200
+ retention: entry.retention === "ambient" ? "ambient" : entry.retention === "core" ? "core" : "key-event",
201
+ salience: entry.salience,
202
+ stability: entry.stability === "volatile" ? "situational" : entry.stability === "stable" ? "stable" : "situational",
203
+ };
204
+ }