stellar-memory 0.5.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 (197) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +362 -0
  3. package/dist/api/routes/analytics.d.ts +15 -0
  4. package/dist/api/routes/analytics.js +131 -0
  5. package/dist/api/routes/analytics.js.map +1 -0
  6. package/dist/api/routes/conflicts.d.ts +12 -0
  7. package/dist/api/routes/conflicts.js +67 -0
  8. package/dist/api/routes/conflicts.js.map +1 -0
  9. package/dist/api/routes/consolidation.d.ts +11 -0
  10. package/dist/api/routes/consolidation.js +63 -0
  11. package/dist/api/routes/consolidation.js.map +1 -0
  12. package/dist/api/routes/constellation.d.ts +4 -0
  13. package/dist/api/routes/constellation.js +84 -0
  14. package/dist/api/routes/constellation.js.map +1 -0
  15. package/dist/api/routes/memories.d.ts +4 -0
  16. package/dist/api/routes/memories.js +219 -0
  17. package/dist/api/routes/memories.js.map +1 -0
  18. package/dist/api/routes/observations.d.ts +10 -0
  19. package/dist/api/routes/observations.js +42 -0
  20. package/dist/api/routes/observations.js.map +1 -0
  21. package/dist/api/routes/orbit.d.ts +4 -0
  22. package/dist/api/routes/orbit.js +71 -0
  23. package/dist/api/routes/orbit.js.map +1 -0
  24. package/dist/api/routes/projects.d.ts +15 -0
  25. package/dist/api/routes/projects.js +121 -0
  26. package/dist/api/routes/projects.js.map +1 -0
  27. package/dist/api/routes/scan.d.ts +4 -0
  28. package/dist/api/routes/scan.js +403 -0
  29. package/dist/api/routes/scan.js.map +1 -0
  30. package/dist/api/routes/sun.d.ts +4 -0
  31. package/dist/api/routes/sun.js +43 -0
  32. package/dist/api/routes/sun.js.map +1 -0
  33. package/dist/api/routes/system.d.ts +4 -0
  34. package/dist/api/routes/system.js +70 -0
  35. package/dist/api/routes/system.js.map +1 -0
  36. package/dist/api/routes/temporal.d.ts +13 -0
  37. package/dist/api/routes/temporal.js +82 -0
  38. package/dist/api/routes/temporal.js.map +1 -0
  39. package/dist/api/server.d.ts +2 -0
  40. package/dist/api/server.js +99 -0
  41. package/dist/api/server.js.map +1 -0
  42. package/dist/api/websocket.d.ts +53 -0
  43. package/dist/api/websocket.js +168 -0
  44. package/dist/api/websocket.js.map +1 -0
  45. package/dist/cli/index.d.ts +12 -0
  46. package/dist/cli/index.js +35 -0
  47. package/dist/cli/index.js.map +1 -0
  48. package/dist/cli/init.d.ts +10 -0
  49. package/dist/cli/init.js +163 -0
  50. package/dist/cli/init.js.map +1 -0
  51. package/dist/engine/analytics.d.ts +93 -0
  52. package/dist/engine/analytics.js +437 -0
  53. package/dist/engine/analytics.js.map +1 -0
  54. package/dist/engine/conflict.d.ts +54 -0
  55. package/dist/engine/conflict.js +322 -0
  56. package/dist/engine/conflict.js.map +1 -0
  57. package/dist/engine/consolidation.d.ts +83 -0
  58. package/dist/engine/consolidation.js +368 -0
  59. package/dist/engine/consolidation.js.map +1 -0
  60. package/dist/engine/constellation.d.ts +66 -0
  61. package/dist/engine/constellation.js +382 -0
  62. package/dist/engine/constellation.js.map +1 -0
  63. package/dist/engine/corona.d.ts +53 -0
  64. package/dist/engine/corona.js +181 -0
  65. package/dist/engine/corona.js.map +1 -0
  66. package/dist/engine/embedding.d.ts +44 -0
  67. package/dist/engine/embedding.js +168 -0
  68. package/dist/engine/embedding.js.map +1 -0
  69. package/dist/engine/gravity.d.ts +63 -0
  70. package/dist/engine/gravity.js +121 -0
  71. package/dist/engine/gravity.js.map +1 -0
  72. package/dist/engine/multiproject.d.ts +75 -0
  73. package/dist/engine/multiproject.js +241 -0
  74. package/dist/engine/multiproject.js.map +1 -0
  75. package/dist/engine/observation.d.ts +82 -0
  76. package/dist/engine/observation.js +357 -0
  77. package/dist/engine/observation.js.map +1 -0
  78. package/dist/engine/orbit.d.ts +91 -0
  79. package/dist/engine/orbit.js +249 -0
  80. package/dist/engine/orbit.js.map +1 -0
  81. package/dist/engine/planet.d.ts +64 -0
  82. package/dist/engine/planet.js +432 -0
  83. package/dist/engine/planet.js.map +1 -0
  84. package/dist/engine/procedural.d.ts +71 -0
  85. package/dist/engine/procedural.js +259 -0
  86. package/dist/engine/procedural.js.map +1 -0
  87. package/dist/engine/quality.d.ts +48 -0
  88. package/dist/engine/quality.js +245 -0
  89. package/dist/engine/quality.js.map +1 -0
  90. package/dist/engine/repository.d.ts +79 -0
  91. package/dist/engine/repository.js +13 -0
  92. package/dist/engine/repository.js.map +1 -0
  93. package/dist/engine/sun.d.ts +61 -0
  94. package/dist/engine/sun.js +240 -0
  95. package/dist/engine/sun.js.map +1 -0
  96. package/dist/engine/temporal.d.ts +67 -0
  97. package/dist/engine/temporal.js +283 -0
  98. package/dist/engine/temporal.js.map +1 -0
  99. package/dist/engine/types.d.ts +179 -0
  100. package/dist/engine/types.js +27 -0
  101. package/dist/engine/types.js.map +1 -0
  102. package/dist/index.d.ts +2 -0
  103. package/dist/index.js +60 -0
  104. package/dist/index.js.map +1 -0
  105. package/dist/mcp/connector-registry.d.ts +20 -0
  106. package/dist/mcp/connector-registry.js +35 -0
  107. package/dist/mcp/connector-registry.js.map +1 -0
  108. package/dist/mcp/server.d.ts +13 -0
  109. package/dist/mcp/server.js +242 -0
  110. package/dist/mcp/server.js.map +1 -0
  111. package/dist/mcp/tools/daemon-tool.d.ts +16 -0
  112. package/dist/mcp/tools/daemon-tool.js +58 -0
  113. package/dist/mcp/tools/daemon-tool.js.map +1 -0
  114. package/dist/mcp/tools/ingestion-tools.d.ts +20 -0
  115. package/dist/mcp/tools/ingestion-tools.js +34 -0
  116. package/dist/mcp/tools/ingestion-tools.js.map +1 -0
  117. package/dist/mcp/tools/memory-tools.d.ts +122 -0
  118. package/dist/mcp/tools/memory-tools.js +1037 -0
  119. package/dist/mcp/tools/memory-tools.js.map +1 -0
  120. package/dist/scanner/cloud/github.d.ts +34 -0
  121. package/dist/scanner/cloud/github.js +260 -0
  122. package/dist/scanner/cloud/github.js.map +1 -0
  123. package/dist/scanner/cloud/google-drive.d.ts +30 -0
  124. package/dist/scanner/cloud/google-drive.js +289 -0
  125. package/dist/scanner/cloud/google-drive.js.map +1 -0
  126. package/dist/scanner/cloud/notion.d.ts +33 -0
  127. package/dist/scanner/cloud/notion.js +231 -0
  128. package/dist/scanner/cloud/notion.js.map +1 -0
  129. package/dist/scanner/cloud/slack.d.ts +38 -0
  130. package/dist/scanner/cloud/slack.js +282 -0
  131. package/dist/scanner/cloud/slack.js.map +1 -0
  132. package/dist/scanner/cloud/types.d.ts +73 -0
  133. package/dist/scanner/cloud/types.js +9 -0
  134. package/dist/scanner/cloud/types.js.map +1 -0
  135. package/dist/scanner/index.d.ts +35 -0
  136. package/dist/scanner/index.js +420 -0
  137. package/dist/scanner/index.js.map +1 -0
  138. package/dist/scanner/local/filesystem.d.ts +33 -0
  139. package/dist/scanner/local/filesystem.js +203 -0
  140. package/dist/scanner/local/filesystem.js.map +1 -0
  141. package/dist/scanner/local/git.d.ts +24 -0
  142. package/dist/scanner/local/git.js +161 -0
  143. package/dist/scanner/local/git.js.map +1 -0
  144. package/dist/scanner/local/parsers/code.d.ts +3 -0
  145. package/dist/scanner/local/parsers/code.js +127 -0
  146. package/dist/scanner/local/parsers/code.js.map +1 -0
  147. package/dist/scanner/local/parsers/index.d.ts +11 -0
  148. package/dist/scanner/local/parsers/index.js +24 -0
  149. package/dist/scanner/local/parsers/index.js.map +1 -0
  150. package/dist/scanner/local/parsers/json-parser.d.ts +3 -0
  151. package/dist/scanner/local/parsers/json-parser.js +117 -0
  152. package/dist/scanner/local/parsers/json-parser.js.map +1 -0
  153. package/dist/scanner/local/parsers/markdown.d.ts +3 -0
  154. package/dist/scanner/local/parsers/markdown.js +120 -0
  155. package/dist/scanner/local/parsers/markdown.js.map +1 -0
  156. package/dist/scanner/local/parsers/text.d.ts +3 -0
  157. package/dist/scanner/local/parsers/text.js +41 -0
  158. package/dist/scanner/local/parsers/text.js.map +1 -0
  159. package/dist/scanner/metadata-scanner.d.ts +67 -0
  160. package/dist/scanner/metadata-scanner.js +356 -0
  161. package/dist/scanner/metadata-scanner.js.map +1 -0
  162. package/dist/scanner/types.d.ts +47 -0
  163. package/dist/scanner/types.js +19 -0
  164. package/dist/scanner/types.js.map +1 -0
  165. package/dist/service/daemon.d.ts +23 -0
  166. package/dist/service/daemon.js +105 -0
  167. package/dist/service/daemon.js.map +1 -0
  168. package/dist/service/scheduler.d.ts +73 -0
  169. package/dist/service/scheduler.js +281 -0
  170. package/dist/service/scheduler.js.map +1 -0
  171. package/dist/storage/database.d.ts +10 -0
  172. package/dist/storage/database.js +265 -0
  173. package/dist/storage/database.js.map +1 -0
  174. package/dist/storage/queries.d.ts +85 -0
  175. package/dist/storage/queries.js +865 -0
  176. package/dist/storage/queries.js.map +1 -0
  177. package/dist/storage/sqlite-repository.d.ts +32 -0
  178. package/dist/storage/sqlite-repository.js +68 -0
  179. package/dist/storage/sqlite-repository.js.map +1 -0
  180. package/dist/storage/vec.d.ts +62 -0
  181. package/dist/storage/vec.js +111 -0
  182. package/dist/storage/vec.js.map +1 -0
  183. package/dist/utils/config.d.ts +5 -0
  184. package/dist/utils/config.js +60 -0
  185. package/dist/utils/config.js.map +1 -0
  186. package/dist/utils/logger.d.ts +36 -0
  187. package/dist/utils/logger.js +86 -0
  188. package/dist/utils/logger.js.map +1 -0
  189. package/dist/utils/time.d.ts +21 -0
  190. package/dist/utils/time.js +42 -0
  191. package/dist/utils/time.js.map +1 -0
  192. package/dist/utils/tokenizer.d.ts +13 -0
  193. package/dist/utils/tokenizer.js +46 -0
  194. package/dist/utils/tokenizer.js.map +1 -0
  195. package/package.json +77 -0
  196. package/scripts/check-node.mjs +36 -0
  197. package/scripts/setup.mjs +157 -0
@@ -0,0 +1,1037 @@
1
+ /**
2
+ * mcp/tools/memory-tools.ts — Handler functions for memory-related MCP tools.
3
+ *
4
+ * Exported functions:
5
+ * handleStatus — status tool
6
+ * handleCommit — commit tool
7
+ * handleRecall — recall tool
8
+ * handleRemember — remember tool
9
+ * handleOrbit — orbit tool
10
+ * handleForget — forget tool
11
+ * handleExport — export tool
12
+ *
13
+ * Each function receives only the parsed tool arguments it needs plus any
14
+ * dependencies injected at call-site. It returns the MCP response object
15
+ * `{ content: [{ type: 'text', text: string }] }`.
16
+ *
17
+ * No McpServer or SDK imports are needed here — that coupling lives in server.ts.
18
+ */
19
+ import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
20
+ import { ORBIT_ZONES } from '../../engine/types.js';
21
+ import { getSunContent, commitToSun } from '../../engine/sun.js';
22
+ import { createMemory, recallMemoriesAsync, forgetMemory } from '../../engine/planet.js';
23
+ import { recalculateOrbits, getOrbitZone } from '../../engine/orbit.js';
24
+ import { getMemoriesByProject } from '../../storage/queries.js';
25
+ import { getConfig } from '../../utils/config.js';
26
+ import { listDataSources } from '../../scanner/index.js';
27
+ import { extractRelationships, getConstellationGraph, findRelatedMemories, } from '../../engine/constellation.js';
28
+ import { switchProject, getCurrentProject, listAllProjects, createProject, getProjectStats, markUniversal, getUniversalContext, detectUniversalCandidates, } from '../../engine/multiproject.js';
29
+ import { getFullAnalytics, getSurvivalCurve, getOrbitMovements, getTopicClusters, detectAccessPatterns, getMemoryHealth, generateReport, } from '../../engine/analytics.js';
30
+ import { calculateQuality, getQualityFeedback } from '../../engine/quality.js';
31
+ import { detectConflicts, formatConflictWarnings, getUnresolvedConflicts, resolveConflict as resolveConflictEngine, } from '../../engine/conflict.js';
32
+ import { detectSupersession, supersedeMemory, getContextAtTime, getEvolutionChain, getTemporalSummary, setTemporalBounds, } from '../../engine/temporal.js';
33
+ import { processConversation } from '../../engine/observation.js';
34
+ import { findConsolidationCandidates, runConsolidation } from '../../engine/consolidation.js';
35
+ import { getProceduralMemories, formatProceduralSection, } from '../../engine/procedural.js';
36
+ import { corona } from '../../engine/corona.js';
37
+ // ---------------------------------------------------------------------------
38
+ // Corona lazy initialization
39
+ // ---------------------------------------------------------------------------
40
+ let coronaReady = false;
41
+ function ensureCorona() {
42
+ if (!coronaReady) {
43
+ corona.warmup(resolveProject());
44
+ coronaReady = true;
45
+ }
46
+ }
47
+ // ---------------------------------------------------------------------------
48
+ // Async background task error tracking
49
+ // ---------------------------------------------------------------------------
50
+ const bgErrors = {
51
+ embedding: 0,
52
+ constellation: 0,
53
+ consolidation: 0,
54
+ };
55
+ /** Increment a background error counter. Called from fire-and-forget tasks. */
56
+ export function trackBgError(category) {
57
+ bgErrors[category]++;
58
+ }
59
+ /** Get background error stats snapshot. */
60
+ export function getBgErrorStats() {
61
+ return { ...bgErrors };
62
+ }
63
+ // ---------------------------------------------------------------------------
64
+ // Shared helpers
65
+ // ---------------------------------------------------------------------------
66
+ function resolveProject() {
67
+ // getCurrentProject() reflects runtime switches via the galaxy tool;
68
+ // falls back to the config default on first call (same initial value).
69
+ return getCurrentProject();
70
+ }
71
+ function labelToZoneKey(label) {
72
+ for (const [key, info] of Object.entries(ORBIT_ZONES)) {
73
+ if (info.label === label)
74
+ return key;
75
+ }
76
+ return 'forgotten';
77
+ }
78
+ function formatDistance(distance) {
79
+ const label = getOrbitZone(distance);
80
+ return `${distance.toFixed(2)} AU (${label})`;
81
+ }
82
+ function formatMemoryLine(m) {
83
+ const pct = (m.importance * 100).toFixed(0);
84
+ return ` [${m.type.toUpperCase()}] ${m.summary} | ${m.distance.toFixed(2)} AU | ${pct}% | ${m.id}`;
85
+ }
86
+ // ---------------------------------------------------------------------------
87
+ // status
88
+ // ---------------------------------------------------------------------------
89
+ export async function handleStatus(args) {
90
+ try {
91
+ const proj = resolveProject();
92
+ ensureCorona();
93
+ const effectiveLimit = args.limit ?? 50;
94
+ const effectiveZone = args.zone ?? 'all';
95
+ const effectiveShow = args.show ?? 'memories';
96
+ const lines = [];
97
+ if (effectiveShow === 'memories' || effectiveShow === 'all') {
98
+ const all = getMemoriesByProject(proj);
99
+ const memories = all.slice(0, effectiveLimit);
100
+ const filtered = effectiveZone === 'all'
101
+ ? memories
102
+ : memories.filter((m) => {
103
+ const zoneKey = labelToZoneKey(getOrbitZone(m.distance));
104
+ return zoneKey === effectiveZone;
105
+ });
106
+ const byZone = {};
107
+ for (const m of filtered) {
108
+ const zoneKey = labelToZoneKey(getOrbitZone(m.distance));
109
+ const bucket = byZone[zoneKey] ?? [];
110
+ bucket.push(m);
111
+ byZone[zoneKey] = bucket;
112
+ }
113
+ const zoneOrder = ['core', 'near', 'active', 'archive', 'fading', 'forgotten'];
114
+ // Stats summary
115
+ const unresolvedConflicts = getUnresolvedConflicts(proj);
116
+ const proceduralCount = all.filter(m => m.type === 'procedural').length;
117
+ const qualityValues = all.map(m => m.quality_score).filter((q) => q !== undefined && q !== null);
118
+ const avgQuality = qualityValues.length > 0
119
+ ? qualityValues.reduce((a, b) => a + b, 0) / qualityValues.length
120
+ : null;
121
+ const coronaStats = corona.stats();
122
+ lines.push(`☀ Project: ${proj} | ${filtered.length} memories | corona: ${coronaStats.core} core, ${coronaStats.near} near cached`);
123
+ if (filtered.length > 0) {
124
+ const zoneCounts = zoneOrder
125
+ .map((z) => `${z}: ${(byZone[z] ?? []).length}`)
126
+ .join(' ');
127
+ lines.push(` ${zoneCounts}`);
128
+ }
129
+ // Extended stats
130
+ const statsLine = [];
131
+ if (avgQuality !== null)
132
+ statsLine.push(`avg quality: ${(avgQuality * 100).toFixed(0)}%`);
133
+ if (unresolvedConflicts.length > 0)
134
+ statsLine.push(`conflicts: ${unresolvedConflicts.length}`);
135
+ if (proceduralCount > 0)
136
+ statsLine.push(`procedural: ${proceduralCount}`);
137
+ // Background error stats
138
+ const errors = getBgErrorStats();
139
+ const totalBgErrors = errors.embedding + errors.constellation + errors.consolidation;
140
+ if (totalBgErrors > 0)
141
+ statsLine.push(`bg errors: ${totalBgErrors}`);
142
+ if (statsLine.length > 0)
143
+ lines.push(` ${statsLine.join(' | ')}`);
144
+ if (filtered.length === 0) {
145
+ lines.push(effectiveZone !== 'all'
146
+ ? `No memories in zone "${effectiveZone}".`
147
+ : 'No memories yet. Use remember or scan to add some.');
148
+ }
149
+ else {
150
+ lines.push('');
151
+ for (const zoneName of zoneOrder) {
152
+ const zoneMemories = byZone[zoneName];
153
+ if (!zoneMemories || zoneMemories.length === 0)
154
+ continue;
155
+ lines.push(`▸ ${ORBIT_ZONES[zoneName].label} (${zoneMemories.length})`);
156
+ for (const m of zoneMemories)
157
+ lines.push(formatMemoryLine(m));
158
+ lines.push('');
159
+ }
160
+ }
161
+ }
162
+ if (effectiveShow === 'sources' || effectiveShow === 'all') {
163
+ if (effectiveShow === 'all')
164
+ lines.push('─────────────────────────────────');
165
+ const sources = listDataSources();
166
+ if (sources.length === 0) {
167
+ lines.push('No data sources registered yet. Use scan to index a directory.');
168
+ }
169
+ else {
170
+ lines.push(`Data sources (${sources.length}):`);
171
+ lines.push('');
172
+ for (const ds of sources) {
173
+ const lastScan = ds.last_scanned_at
174
+ ? new Date(ds.last_scanned_at).toLocaleString()
175
+ : 'never';
176
+ const sizeMB = (ds.total_size / 1_048_576).toFixed(2);
177
+ lines.push(` ${ds.path} | ${ds.status} | ${ds.file_count} files (${sizeMB} MB) | last: ${lastScan} | id: ${ds.id}`);
178
+ }
179
+ }
180
+ }
181
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
182
+ }
183
+ catch (err) {
184
+ if (err instanceof McpError)
185
+ throw err;
186
+ throw new McpError(ErrorCode.InternalError, `status failed: ${String(err)}`);
187
+ }
188
+ }
189
+ // ---------------------------------------------------------------------------
190
+ // commit
191
+ // ---------------------------------------------------------------------------
192
+ export async function handleCommit(args) {
193
+ try {
194
+ const proj = resolveProject();
195
+ const config = getConfig();
196
+ commitToSun(proj, {
197
+ current_work: args.current_work,
198
+ decisions: args.decisions ?? [],
199
+ next_steps: args.next_steps ?? [],
200
+ errors: args.errors ?? [],
201
+ context: args.context ?? '',
202
+ });
203
+ const changes = recalculateOrbits(proj, config);
204
+ const lines = [
205
+ `✓ Committed | decisions: ${(args.decisions ?? []).length} | steps: ${(args.next_steps ?? []).length} | errors: ${(args.errors ?? []).length} | orbit changes: ${changes.length}`,
206
+ ];
207
+ // Include procedural section if any procedural memories exist
208
+ const proceduralMems = getProceduralMemories(proj);
209
+ if (proceduralMems.length > 0) {
210
+ lines.push('');
211
+ lines.push(formatProceduralSection(proceduralMems));
212
+ }
213
+ // Include temporal summary
214
+ const temporalSummary = getTemporalSummary(proj);
215
+ if (temporalSummary) {
216
+ lines.push('');
217
+ lines.push(temporalSummary);
218
+ }
219
+ // Include unresolved conflict count
220
+ const unresolvedConflicts = getUnresolvedConflicts(proj);
221
+ if (unresolvedConflicts.length > 0) {
222
+ lines.push('');
223
+ lines.push(`Unresolved conflicts: ${unresolvedConflicts.length} — use resolve_conflict tool to review.`);
224
+ }
225
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
226
+ }
227
+ catch (err) {
228
+ if (err instanceof McpError)
229
+ throw err;
230
+ throw new McpError(ErrorCode.InternalError, `commit failed: ${String(err)}`);
231
+ }
232
+ }
233
+ // ---------------------------------------------------------------------------
234
+ // recall
235
+ // ---------------------------------------------------------------------------
236
+ export async function handleRecall(args) {
237
+ try {
238
+ const proj = resolveProject();
239
+ const limit = args.limit ?? 10;
240
+ ensureCorona();
241
+ const memoryType = args.type === 'all' || args.type === undefined ? undefined : args.type;
242
+ // If `at` is provided, use temporal point-in-time query instead of normal recall
243
+ let results;
244
+ if (args.at) {
245
+ results = getContextAtTime(proj, args.at).slice(0, limit);
246
+ }
247
+ else {
248
+ // Exclude memories already visible in the Sun resource (corona cache)
249
+ // to avoid token-wasting duplication between Sun and recall output.
250
+ const coreIds = corona.getCoreMemories().map(m => m.id);
251
+ const nearIds = corona.getNearMemories().map(m => m.id);
252
+ const excludeIds = new Set([...coreIds, ...nearIds]);
253
+ results = await recallMemoriesAsync(proj, args.query, {
254
+ type: memoryType,
255
+ maxDistance: args.max_au,
256
+ limit,
257
+ excludeIds,
258
+ });
259
+ }
260
+ // Optionally merge universal memories from other projects
261
+ let universals = [];
262
+ if (args.include_universal) {
263
+ universals = getUniversalContext(proj, Math.ceil(limit / 2));
264
+ }
265
+ if (results.length === 0 && universals.length === 0) {
266
+ return {
267
+ content: [{ type: 'text', text: `No memories found matching "${args.query}".` }],
268
+ };
269
+ }
270
+ const temporalNote = args.at ? ` (at ${args.at})` : ' (pulled closer to Sun)';
271
+ const lines = [
272
+ `Recall: "${args.query}" — ${results.length} result${results.length === 1 ? '' : 's'}${temporalNote}`,
273
+ '',
274
+ ];
275
+ for (const m of results) {
276
+ const preview = m.content.slice(0, 100) + (m.content.length > 100 ? '…' : '');
277
+ const tags = m.tags.length > 0 ? ` [${m.tags.join(', ')}]` : '';
278
+ const shortId = m.id.slice(0, 8);
279
+ lines.push(`[${m.type.toUpperCase()}] ${m.summary}${tags} | ${formatDistance(m.distance)} | ${shortId}`);
280
+ lines.push(` ${preview}`);
281
+ // Include top 3 related memories (constellation)
282
+ const related = findRelatedMemories(m.id, proj, 3);
283
+ if (related.length > 0) {
284
+ lines.push(` Related: ${related.map(r => `${r.summary.slice(0, 40)} (${r.id.slice(0, 8)})`).join(', ')}`);
285
+ }
286
+ lines.push('');
287
+ }
288
+ if (universals.length > 0) {
289
+ lines.push(`Universal memories (${universals.length} from other projects):`);
290
+ lines.push('');
291
+ for (const m of universals) {
292
+ const preview = m.content.slice(0, 120) + (m.content.length > 120 ? '…' : '');
293
+ lines.push(`[UNIVERSAL/${m.project}] ${m.summary} | ${m.id}`);
294
+ lines.push(` ${preview}`);
295
+ lines.push('');
296
+ }
297
+ }
298
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
299
+ }
300
+ catch (err) {
301
+ if (err instanceof McpError)
302
+ throw err;
303
+ throw new McpError(ErrorCode.InternalError, `recall failed: ${String(err)}`);
304
+ }
305
+ }
306
+ // ---------------------------------------------------------------------------
307
+ // remember
308
+ // ---------------------------------------------------------------------------
309
+ export async function handleRemember(args) {
310
+ try {
311
+ const proj = resolveProject();
312
+ const memory = createMemory({
313
+ project: proj,
314
+ content: args.content,
315
+ summary: args.summary,
316
+ type: (args.type ?? 'observation'),
317
+ impact: args.impact,
318
+ tags: args.tags,
319
+ });
320
+ // Background: auto-extract relationships with existing memories.
321
+ // Fire-and-forget — does not block the response.
322
+ extractRelationships(memory, proj).catch(() => {
323
+ trackBgError('constellation');
324
+ });
325
+ // Set valid_from to now on the new memory
326
+ setTemporalBounds(memory.id, new Date().toISOString(), undefined);
327
+ // Calculate quality score
328
+ const existingMemories = getMemoriesByProject(proj);
329
+ const quality = calculateQuality(memory, existingMemories);
330
+ const qualityFeedback = getQualityFeedback(quality);
331
+ // Detect conflicts
332
+ const conflicts = await detectConflicts(memory, proj);
333
+ const conflictWarnings = formatConflictWarnings(conflicts);
334
+ // Detect temporal supersession — auto-supersede if detected
335
+ const supersessionCandidate = detectSupersession(memory, existingMemories);
336
+ let supersessionNote = '';
337
+ if (supersessionCandidate) {
338
+ supersedeMemory(supersessionCandidate.id, memory.id);
339
+ supersessionNote = `\nSuperseded: ${supersessionCandidate.id.slice(0, 8)} → this memory (${supersessionCandidate.summary.slice(0, 60)})`;
340
+ }
341
+ const zoneLabel = getOrbitZone(memory.distance);
342
+ const lines = [
343
+ `✦ Stored [${memory.type.toUpperCase()}] at ${memory.distance.toFixed(2)} AU (${zoneLabel}) | ID: ${memory.id} | quality: ${(quality.overall * 100).toFixed(0)}%`,
344
+ ];
345
+ if (supersessionNote)
346
+ lines.push(supersessionNote);
347
+ if (qualityFeedback)
348
+ lines.push(`\nQuality tip: ${qualityFeedback}`);
349
+ if (conflictWarnings)
350
+ lines.push(`\n${conflictWarnings}`);
351
+ return {
352
+ content: [{
353
+ type: 'text',
354
+ text: lines.join(''),
355
+ }],
356
+ };
357
+ }
358
+ catch (err) {
359
+ if (err instanceof McpError)
360
+ throw err;
361
+ throw new McpError(ErrorCode.InternalError, `remember failed: ${String(err)}`);
362
+ }
363
+ }
364
+ // ---------------------------------------------------------------------------
365
+ // orbit
366
+ // ---------------------------------------------------------------------------
367
+ export async function handleOrbit(_args) {
368
+ try {
369
+ const proj = resolveProject();
370
+ const config = getConfig();
371
+ const changes = recalculateOrbits(proj, config);
372
+ if (changes.length === 0) {
373
+ return {
374
+ content: [{ type: 'text', text: `Orbit: ${proj} — no changes. All memories are stable.` }],
375
+ };
376
+ }
377
+ let closerCount = 0;
378
+ let furtherCount = 0;
379
+ const lines = [];
380
+ for (const change of changes) {
381
+ const delta = change.new_distance - change.old_distance;
382
+ const direction = delta < 0 ? '↓' : '↑';
383
+ const absAU = Math.abs(delta).toFixed(2);
384
+ if (delta < 0)
385
+ closerCount++;
386
+ else
387
+ furtherCount++;
388
+ const oldZone = getOrbitZone(change.old_distance);
389
+ const newZone = getOrbitZone(change.new_distance);
390
+ const zoneChange = oldZone !== newZone ? ` ${oldZone}→${newZone}` : '';
391
+ lines.push(` ${direction}${absAU} AU (${change.old_distance.toFixed(2)}→${change.new_distance.toFixed(2)})${zoneChange} | ${change.trigger} | ${change.memory_id}`);
392
+ }
393
+ const header = `Orbit: ${proj} — ${changes.length} change${changes.length === 1 ? '' : 's'} | ↓${closerCount} closer ↑${furtherCount} further`;
394
+ return { content: [{ type: 'text', text: [header, '', ...lines].join('\n') }] };
395
+ }
396
+ catch (err) {
397
+ if (err instanceof McpError)
398
+ throw err;
399
+ throw new McpError(ErrorCode.InternalError, `orbit failed: ${String(err)}`);
400
+ }
401
+ }
402
+ // ---------------------------------------------------------------------------
403
+ // forget
404
+ // ---------------------------------------------------------------------------
405
+ export async function handleForget(args) {
406
+ try {
407
+ const effectiveMode = args.mode ?? 'push';
408
+ // Clean up constellation edges before forgetting
409
+ const { cleanupEdges } = await import('../../engine/constellation.js');
410
+ cleanupEdges(args.id);
411
+ forgetMemory(args.id, effectiveMode);
412
+ if (effectiveMode === 'delete') {
413
+ return { content: [{ type: 'text', text: `✗ Deleted memory ${args.id} (constellation edges removed).` }] };
414
+ }
415
+ const oortDistance = 95.0;
416
+ const zoneLabel = getOrbitZone(oortDistance);
417
+ return {
418
+ content: [{
419
+ type: 'text',
420
+ text: `↑ Pushed ${args.id} to ${oortDistance.toFixed(2)} AU (${zoneLabel}) — still recoverable via recall. Constellation edges removed.`,
421
+ }],
422
+ };
423
+ }
424
+ catch (err) {
425
+ if (err instanceof McpError)
426
+ throw err;
427
+ throw new McpError(ErrorCode.InternalError, `forget failed: ${String(err)}`);
428
+ }
429
+ }
430
+ // ---------------------------------------------------------------------------
431
+ // export
432
+ // ---------------------------------------------------------------------------
433
+ export async function handleExport(args) {
434
+ try {
435
+ const proj = resolveProject();
436
+ const effectiveFormat = args.format ?? 'json';
437
+ let memories = getMemoriesByProject(proj);
438
+ if (args.type && args.type !== 'all') {
439
+ memories = memories.filter((m) => m.type === args.type);
440
+ }
441
+ if (args.zone && args.zone !== 'all') {
442
+ memories = memories.filter((m) => labelToZoneKey(getOrbitZone(m.distance)) === args.zone);
443
+ }
444
+ if (effectiveFormat === 'json') {
445
+ const payload = memories.map((m) => ({
446
+ id: m.id,
447
+ type: m.type,
448
+ summary: m.summary,
449
+ content: m.content,
450
+ tags: m.tags,
451
+ distance: m.distance,
452
+ importance: m.importance,
453
+ created_at: m.created_at,
454
+ }));
455
+ return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };
456
+ }
457
+ // markdown format
458
+ const lines = [
459
+ `# Stellar Memory Export`,
460
+ `Project: ${proj} | ${memories.length} memories | ${new Date().toISOString()}`,
461
+ '',
462
+ ];
463
+ for (const m of memories) {
464
+ const zoneLabel = getOrbitZone(m.distance);
465
+ const tagsStr = m.tags.length > 0 ? m.tags.join(', ') : '—';
466
+ const dateStr = m.created_at.slice(0, 10);
467
+ lines.push(`## [${m.type.toUpperCase()}] ${m.summary}`);
468
+ lines.push(`- **Distance**: ${m.distance.toFixed(2)} AU (${zoneLabel})`);
469
+ lines.push(`- **Impact**: ${m.importance.toFixed(2)}`);
470
+ lines.push(`- **Tags**: ${tagsStr}`);
471
+ lines.push(`- **Created**: ${dateStr}`);
472
+ lines.push('');
473
+ lines.push(m.content);
474
+ lines.push('');
475
+ lines.push('---');
476
+ lines.push('');
477
+ }
478
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
479
+ }
480
+ catch (err) {
481
+ if (err instanceof McpError)
482
+ throw err;
483
+ throw new McpError(ErrorCode.InternalError, `export failed: ${String(err)}`);
484
+ }
485
+ }
486
+ // ---------------------------------------------------------------------------
487
+ // constellation
488
+ // ---------------------------------------------------------------------------
489
+ export async function handleConstellation(args) {
490
+ try {
491
+ const proj = resolveProject();
492
+ const action = args.action ?? 'graph';
493
+ if (action === 'extract') {
494
+ // Fetch the memory to extract relationships for
495
+ const { getMemoryById } = await import('../../storage/queries.js');
496
+ const memory = getMemoryById(args.id);
497
+ if (!memory) {
498
+ throw new McpError(ErrorCode.InvalidParams, `Memory not found: ${args.id}`);
499
+ }
500
+ const edges = await extractRelationships(memory, proj);
501
+ const text = edges.length === 0
502
+ ? `No relationships found for memory ${args.id} (weight threshold not met).`
503
+ : [
504
+ `Extracted ${edges.length} relationship${edges.length === 1 ? '' : 's'} for ${args.id}:`,
505
+ '',
506
+ ...edges.map(e => ` [${e.relation}] → ${e.target_id} (weight: ${e.weight.toFixed(3)})`),
507
+ ].join('\n');
508
+ return { content: [{ type: 'text', text }] };
509
+ }
510
+ if (action === 'related') {
511
+ const limit = args.limit ?? 10;
512
+ const related = findRelatedMemories(args.id, proj, limit);
513
+ if (related.length === 0) {
514
+ return {
515
+ content: [{ type: 'text', text: `No related memories found for ${args.id}.` }],
516
+ };
517
+ }
518
+ const lines = [
519
+ `Related memories for ${args.id} (${related.length}):`,
520
+ '',
521
+ ...related.map(m => ` [${m.type.toUpperCase()}] ${m.summary} | ${m.distance.toFixed(2)} AU | ${m.id}`),
522
+ ];
523
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
524
+ }
525
+ // Default: graph
526
+ const depth = args.depth ?? 1;
527
+ const { nodes, edges } = getConstellationGraph(args.id, proj, depth);
528
+ if (edges.length === 0) {
529
+ return {
530
+ content: [{
531
+ type: 'text',
532
+ text: `Constellation for ${args.id}: no edges yet. Use action="extract" to auto-discover relationships.`,
533
+ }],
534
+ };
535
+ }
536
+ const lines = [
537
+ `Constellation graph for ${args.id} (depth=${depth}):`,
538
+ ` ${nodes.length} nodes ${edges.length} edges`,
539
+ '',
540
+ 'Edges:',
541
+ ...edges.map(e => ` ${e.source_id.slice(0, 8)} --[${e.relation}]--> ${e.target_id.slice(0, 8)} (w=${e.weight.toFixed(3)})`),
542
+ '',
543
+ 'Nodes:',
544
+ ...nodes.map(n => ` [${n.type.toUpperCase()}] ${n.summary} | ${n.distance.toFixed(2)} AU | ${n.id}`),
545
+ ];
546
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
547
+ }
548
+ catch (err) {
549
+ if (err instanceof McpError)
550
+ throw err;
551
+ throw new McpError(ErrorCode.InternalError, `constellation failed: ${String(err)}`);
552
+ }
553
+ }
554
+ // ---------------------------------------------------------------------------
555
+ // galaxy — multi-project management
556
+ // ---------------------------------------------------------------------------
557
+ export async function handleGalaxy(args) {
558
+ try {
559
+ const lines = [];
560
+ switch (args.action) {
561
+ case 'switch': {
562
+ if (!args.project)
563
+ throw new McpError(ErrorCode.InvalidParams, 'project is required for action="switch"');
564
+ const result = switchProject(args.project);
565
+ lines.push(`Switched project: ${result.previous} → ${result.current}`);
566
+ lines.push(`Memories in "${result.current}": ${result.memoryCount}`);
567
+ break;
568
+ }
569
+ case 'list': {
570
+ const projects = listAllProjects();
571
+ const current = getCurrentProject();
572
+ lines.push(`Galaxy — ${projects.length} project${projects.length === 1 ? '' : 's'} (active: ${current})`);
573
+ lines.push('');
574
+ for (const p of projects) {
575
+ const active = p.project === current ? ' *' : '';
576
+ const univFlag = p.hasUniversal ? ' [universal]' : '';
577
+ const updated = p.lastUpdated.slice(0, 10);
578
+ lines.push(` ${p.project}${active}${univFlag} | ${p.memoryCount} memories | updated: ${updated}`);
579
+ }
580
+ break;
581
+ }
582
+ case 'create': {
583
+ if (!args.project)
584
+ throw new McpError(ErrorCode.InvalidParams, 'project is required for action="create"');
585
+ const result = createProject(args.project);
586
+ lines.push(result.created
587
+ ? `Created project "${result.project}".`
588
+ : `Project "${result.project}" already exists.`);
589
+ break;
590
+ }
591
+ case 'stats': {
592
+ const proj = args.project ?? getCurrentProject();
593
+ const stats = getProjectStats(proj);
594
+ lines.push(`Stats — project: ${proj}`);
595
+ lines.push(`Total memories: ${stats.memoryCount}`);
596
+ lines.push(`Universal: ${stats.universalCount}`);
597
+ lines.push(`Oldest memory: ${stats.oldestMemory.slice(0, 10) || '—'}`);
598
+ lines.push(`Newest memory: ${stats.newestMemory.slice(0, 10) || '—'}`);
599
+ lines.push('');
600
+ lines.push('Zone distribution:');
601
+ for (const [zone, count] of Object.entries(stats.zoneDistribution)) {
602
+ lines.push(` ${zone.padEnd(10)} ${count}`);
603
+ }
604
+ lines.push('');
605
+ lines.push('Type distribution:');
606
+ for (const [type, count] of Object.entries(stats.typeDistribution)) {
607
+ lines.push(` ${type.padEnd(12)} ${count}`);
608
+ }
609
+ break;
610
+ }
611
+ case 'mark_universal': {
612
+ if (!args.memory_id)
613
+ throw new McpError(ErrorCode.InvalidParams, 'memory_id is required for action="mark_universal"');
614
+ const flag = args.is_universal ?? true;
615
+ markUniversal(args.memory_id, flag);
616
+ lines.push(`Memory ${args.memory_id} marked as ${flag ? 'universal' : 'project-specific'}.`);
617
+ break;
618
+ }
619
+ case 'universal_context': {
620
+ const proj = args.project ?? getCurrentProject();
621
+ const limit = args.limit ?? 10;
622
+ const memories = getUniversalContext(proj, limit);
623
+ if (memories.length === 0) {
624
+ lines.push('No universal memories from other projects.');
625
+ }
626
+ else {
627
+ lines.push(`Universal context for "${proj}" (${memories.length} memories from other projects):`);
628
+ lines.push('');
629
+ for (const m of memories) {
630
+ lines.push(`[${m.project}/${m.type.toUpperCase()}] ${m.summary} | ${m.id}`);
631
+ lines.push(` ${m.content.slice(0, 120)}${m.content.length > 120 ? '…' : ''}`);
632
+ lines.push('');
633
+ }
634
+ }
635
+ break;
636
+ }
637
+ case 'candidates': {
638
+ const proj = args.project ?? getCurrentProject();
639
+ const candidates = detectUniversalCandidates(proj);
640
+ if (candidates.length === 0) {
641
+ lines.push(`No universal candidates found in project "${proj}".`);
642
+ }
643
+ else {
644
+ lines.push(`Universal candidates in "${proj}" (${candidates.length}) — review and mark with mark_universal:`);
645
+ lines.push('');
646
+ for (const m of candidates) {
647
+ lines.push(`[${m.type.toUpperCase()}] ${m.summary} | importance: ${(m.importance * 100).toFixed(0)}% | ${m.id}`);
648
+ }
649
+ }
650
+ break;
651
+ }
652
+ default:
653
+ throw new McpError(ErrorCode.InvalidParams, `Unknown action: ${String(args.action)}`);
654
+ }
655
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
656
+ }
657
+ catch (err) {
658
+ if (err instanceof McpError)
659
+ throw err;
660
+ throw new McpError(ErrorCode.InternalError, `galaxy failed: ${String(err)}`);
661
+ }
662
+ }
663
+ // ---------------------------------------------------------------------------
664
+ // analytics — memory insights and pattern detection
665
+ // ---------------------------------------------------------------------------
666
+ export async function handleAnalytics(args) {
667
+ try {
668
+ const proj = args.project ?? resolveProject();
669
+ const lines = [];
670
+ // Support both `report` (new) and `action` (legacy) param names
671
+ const effectiveAction = args.report ?? args.action ?? 'overview';
672
+ switch (effectiveAction) {
673
+ case 'survival': {
674
+ const curve = getSurvivalCurve(proj);
675
+ lines.push(`Memory survival curve — project: ${proj}`);
676
+ lines.push('');
677
+ lines.push('Age (days) Surviving Accessed Forgotten');
678
+ lines.push('─────────────────────────────────────────');
679
+ for (const row of curve) {
680
+ const age = String(row.ageInDays).padStart(10);
681
+ const surv = String(row.survivingCount).padStart(9);
682
+ const acc = String(row.accessedCount).padStart(8);
683
+ const forg = String(row.forgottenCount).padStart(9);
684
+ lines.push(`${age} ${surv} ${acc} ${forg}`);
685
+ }
686
+ break;
687
+ }
688
+ case 'movements': {
689
+ const days = args.days ?? 30;
690
+ const movements = getOrbitMovements(proj, days);
691
+ if (movements.length === 0) {
692
+ lines.push(`No orbit movements in the last ${days} days for project "${proj}".`);
693
+ }
694
+ else {
695
+ lines.push(`Orbit movements — project: ${proj} (last ${days} days, top ${movements.length})`);
696
+ lines.push('');
697
+ for (const m of movements) {
698
+ const dir = m.netMovement < 0 ? '↓' : '↑';
699
+ lines.push(`${dir} ${m.summary} (${m.movements.length} moves, net: ${m.netMovement > 0 ? '+' : ''}${m.netMovement.toFixed(2)} AU) | ${m.memoryId}`);
700
+ for (const mv of m.movements.slice(-3)) {
701
+ lines.push(` ${mv.timestamp.slice(0, 16)} ${mv.oldDistance.toFixed(2)}→${mv.newDistance.toFixed(2)} AU [${mv.trigger}]`);
702
+ }
703
+ lines.push('');
704
+ }
705
+ }
706
+ break;
707
+ }
708
+ case 'clusters': {
709
+ const topicClusters = getTopicClusters(proj);
710
+ if (topicClusters.length === 0) {
711
+ lines.push(`No topic clusters found for project "${proj}". Add tags to your memories.`);
712
+ }
713
+ else {
714
+ lines.push(`Topic clusters — project: ${proj} (${topicClusters.length} topics)`);
715
+ lines.push('');
716
+ lines.push('Topic Count Avg Imp Avg Dist Recent');
717
+ lines.push('────────────────────────────────────────────────────');
718
+ for (const cl of topicClusters) {
719
+ const topicLabel = cl.topic.slice(0, 18).padEnd(18);
720
+ const clCount = String(cl.memoryCount).padStart(5);
721
+ const imp = (cl.avgImportance * 100).toFixed(0).padStart(6) + '%';
722
+ const dist = cl.avgDistance.toFixed(1).padStart(7) + ' AU';
723
+ const recent = String(cl.recentActivity).padStart(6);
724
+ lines.push(`${topicLabel} ${clCount} ${imp} ${dist} ${recent}`);
725
+ }
726
+ }
727
+ break;
728
+ }
729
+ case 'patterns': {
730
+ const patterns = detectAccessPatterns(proj);
731
+ if (patterns.length === 0) {
732
+ lines.push(`No access patterns detected yet for project "${proj}". Keep using recall to build history.`);
733
+ }
734
+ else {
735
+ lines.push(`Access patterns — project: ${proj}`);
736
+ lines.push('');
737
+ for (const p of patterns) {
738
+ lines.push(`[${p.pattern.toUpperCase()}] ${p.description}`);
739
+ lines.push(` Frequency: ${p.frequency}`);
740
+ lines.push('');
741
+ }
742
+ }
743
+ break;
744
+ }
745
+ case 'health': {
746
+ const h = getMemoryHealth(proj);
747
+ lines.push(`Memory health — project: ${proj}`);
748
+ lines.push('');
749
+ lines.push(`Total memories: ${h.totalMemories}`);
750
+ lines.push(`Active ratio (close zones): ${(h.activeRatio * 100).toFixed(1)}%`);
751
+ lines.push(`Stale ratio (30+ days idle): ${(h.staleRatio * 100).toFixed(1)}%`);
752
+ lines.push(`Avg quality score: ${(h.qualityAvg * 100).toFixed(1)}%`);
753
+ lines.push(`Conflict ratio: ${(h.conflictRatio * 100).toFixed(1)}%`);
754
+ lines.push(`Consolidation opportunities: ${h.consolidationOpportunities}`);
755
+ if (h.recommendations.length > 0) {
756
+ lines.push('');
757
+ lines.push('Recommendations:');
758
+ for (const rec of h.recommendations) {
759
+ lines.push(` - ${rec}`);
760
+ }
761
+ }
762
+ else {
763
+ lines.push('');
764
+ lines.push('Memory system is healthy. No action needed.');
765
+ }
766
+ break;
767
+ }
768
+ case 'report':
769
+ case 'full': {
770
+ const report = generateReport(proj);
771
+ lines.push(report);
772
+ break;
773
+ }
774
+ case 'topics': {
775
+ // Alias for 'clusters'
776
+ const clusters = getTopicClusters(proj);
777
+ if (clusters.length === 0) {
778
+ lines.push(`No topic clusters found for project "${proj}". Add tags to your memories.`);
779
+ }
780
+ else {
781
+ lines.push(`Topic clusters — project: ${proj} (${clusters.length} topics)`);
782
+ lines.push('');
783
+ lines.push('Topic Count Avg Imp Avg Dist Recent');
784
+ lines.push('────────────────────────────────────────────────────');
785
+ for (const cl of clusters) {
786
+ const topic = cl.topic.slice(0, 18).padEnd(18);
787
+ const count = String(cl.memoryCount).padStart(5);
788
+ const imp = (cl.avgImportance * 100).toFixed(0).padStart(6) + '%';
789
+ const dist = cl.avgDistance.toFixed(1).padStart(7) + ' AU';
790
+ const recent = String(cl.recentActivity).padStart(6);
791
+ lines.push(`${topic} ${count} ${imp} ${dist} ${recent}`);
792
+ }
793
+ }
794
+ break;
795
+ }
796
+ case 'summary':
797
+ case 'overview': {
798
+ const a = getFullAnalytics(proj);
799
+ lines.push(`Analytics — project: ${proj}`);
800
+ lines.push(`Total memories: ${a.total_memories}`);
801
+ lines.push(`Avg importance: ${(a.avg_importance * 100).toFixed(1)}%`);
802
+ lines.push(`Avg quality: ${(a.avg_quality * 100).toFixed(1)}%`);
803
+ lines.push(`Recall success rate: ${(a.recall_success_rate * 100).toFixed(1)}%`);
804
+ lines.push(`Consolidations: ${a.consolidation_count}`);
805
+ lines.push(`Open conflicts: ${a.conflict_count}`);
806
+ lines.push('');
807
+ lines.push('Zone distribution:');
808
+ for (const [zone, count] of Object.entries(a.zone_distribution)) {
809
+ lines.push(` ${zone.padEnd(10)} ${count}`);
810
+ }
811
+ lines.push('');
812
+ lines.push('Type distribution:');
813
+ for (const [type, count] of Object.entries(a.type_distribution).sort(([, a], [, b]) => b - a)) {
814
+ lines.push(` ${type.padEnd(12)} ${count}`);
815
+ }
816
+ if (a.top_tags.length > 0) {
817
+ lines.push('');
818
+ lines.push('Top tags:');
819
+ for (const { tag, count } of a.top_tags.slice(0, 10)) {
820
+ lines.push(` ${tag.padEnd(20)} ${count}`);
821
+ }
822
+ }
823
+ break;
824
+ }
825
+ default:
826
+ throw new McpError(ErrorCode.InvalidParams, `Unknown analytics report: ${String(effectiveAction)}`);
827
+ }
828
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
829
+ }
830
+ catch (err) {
831
+ if (err instanceof McpError)
832
+ throw err;
833
+ throw new McpError(ErrorCode.InternalError, `analytics failed: ${String(err)}`);
834
+ }
835
+ }
836
+ // ---------------------------------------------------------------------------
837
+ // sun resource handler (not a tool, but lives logically with memory tools)
838
+ // ---------------------------------------------------------------------------
839
+ export function handleSunResource(uriHref) {
840
+ const config = getConfig();
841
+ const proj = resolveProject();
842
+ ensureCorona();
843
+ let content = getSunContent(proj);
844
+ // Append procedural memories section (Navigation Rules)
845
+ const proceduralMems = getProceduralMemories(proj);
846
+ if (proceduralMems.length > 0) {
847
+ content += '\n\n' + formatProceduralSection(proceduralMems);
848
+ }
849
+ // Append temporal summary
850
+ const temporalSummary = getTemporalSummary(proj);
851
+ if (temporalSummary) {
852
+ content += '\n\n' + temporalSummary;
853
+ }
854
+ // Append unresolved conflict count
855
+ const unresolvedConflicts = getUnresolvedConflicts(proj);
856
+ if (unresolvedConflicts.length > 0) {
857
+ content += `\n\nUnresolved conflicts: ${unresolvedConflicts.length} — use resolve_conflict tool to review.`;
858
+ }
859
+ return { contents: [{ uri: uriHref, text: content }] };
860
+ }
861
+ // ---------------------------------------------------------------------------
862
+ // New tool handlers
863
+ // ---------------------------------------------------------------------------
864
+ export async function handleObserve(args) {
865
+ try {
866
+ const proj = args.project ?? resolveProject();
867
+ const stats = await processConversation(args.conversation, proj);
868
+ const text = [
869
+ `Observation complete for project "${proj}":`,
870
+ ` Memories created: ${stats.memoriesCreated}`,
871
+ ` Memories reinforced: ${stats.memoriesReinforced}`,
872
+ ` Conflicts detected: ${stats.conflictsDetected}`,
873
+ ].join('\n');
874
+ return { content: [{ type: 'text', text }] };
875
+ }
876
+ catch (err) {
877
+ if (err instanceof McpError)
878
+ throw err;
879
+ throw new McpError(ErrorCode.InternalError, `observe failed: ${String(err)}`);
880
+ }
881
+ }
882
+ export async function handleConsolidate(args) {
883
+ try {
884
+ const proj = args.project ?? resolveProject();
885
+ const dryRun = args.dry_run ?? true;
886
+ if (dryRun) {
887
+ const candidates = await findConsolidationCandidates(proj);
888
+ if (candidates.length === 0) {
889
+ return {
890
+ content: [{ type: 'text', text: `No consolidation candidates found in project "${proj}".` }],
891
+ };
892
+ }
893
+ const lines = [
894
+ `Consolidation candidates in "${proj}" (${candidates.length} groups) — dry run:`,
895
+ '',
896
+ ];
897
+ for (const { memories, similarity } of candidates) {
898
+ lines.push(` Group (similarity: ${(similarity * 100).toFixed(0)}%, ${memories.length} memories):`);
899
+ for (const m of memories) {
900
+ lines.push(` [${m.type.toUpperCase()}] ${m.summary} | ${m.id.slice(0, 8)}`);
901
+ }
902
+ lines.push('');
903
+ }
904
+ lines.push('Run with dry_run=false to merge these groups.');
905
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
906
+ }
907
+ const stats = await runConsolidation(proj);
908
+ const text = [
909
+ `Consolidation complete for "${proj}":`,
910
+ ` Groups found: ${stats.groupsFound}`,
911
+ ` Memories consolidated: ${stats.memoriesConsolidated}`,
912
+ ` New memories created: ${stats.newMemoriesCreated}`,
913
+ ].join('\n');
914
+ return { content: [{ type: 'text', text }] };
915
+ }
916
+ catch (err) {
917
+ if (err instanceof McpError)
918
+ throw err;
919
+ throw new McpError(ErrorCode.InternalError, `consolidate failed: ${String(err)}`);
920
+ }
921
+ }
922
+ export async function handleResolveConflict(args) {
923
+ try {
924
+ const proj = args.project ?? resolveProject();
925
+ if (args.action === 'list') {
926
+ const conflicts = getUnresolvedConflicts(proj);
927
+ if (conflicts.length === 0) {
928
+ return {
929
+ content: [{ type: 'text', text: `No unresolved conflicts in project "${proj}".` }],
930
+ };
931
+ }
932
+ const lines = [
933
+ `Unresolved conflicts in "${proj}" (${conflicts.length}):`,
934
+ '',
935
+ ];
936
+ for (const c of conflicts) {
937
+ lines.push(` [${c.severity.toUpperCase()}] ${c.id}`);
938
+ lines.push(` Memory: ${c.memory_id.slice(0, 8)}`);
939
+ lines.push(` Conflicts: ${c.conflicting_memory_id.slice(0, 8)}`);
940
+ lines.push(` Reason: ${c.description}`);
941
+ lines.push('');
942
+ }
943
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
944
+ }
945
+ if (args.action === 'resolve' || args.action === 'dismiss') {
946
+ if (!args.conflict_id) {
947
+ throw new McpError(ErrorCode.InvalidParams, 'conflict_id is required for resolve/dismiss actions');
948
+ }
949
+ const resolution = args.resolution ?? (args.action === 'dismiss' ? 'Dismissed by user' : 'Resolved by user');
950
+ const resolveAction = args.resolve_action ?? (args.action === 'dismiss' ? 'dismiss' : 'supersede');
951
+ resolveConflictEngine(args.conflict_id, resolution, resolveAction);
952
+ return {
953
+ content: [{
954
+ type: 'text',
955
+ text: `Conflict ${args.conflict_id.slice(0, 8)} resolved with action "${resolveAction}": ${resolution}`,
956
+ }],
957
+ };
958
+ }
959
+ throw new McpError(ErrorCode.InvalidParams, `Unknown action: ${String(args.action)}`);
960
+ }
961
+ catch (err) {
962
+ if (err instanceof McpError)
963
+ throw err;
964
+ throw new McpError(ErrorCode.InternalError, `resolve_conflict failed: ${String(err)}`);
965
+ }
966
+ }
967
+ export async function handleTemporal(args) {
968
+ try {
969
+ const proj = args.project ?? resolveProject();
970
+ switch (args.action) {
971
+ case 'at': {
972
+ if (!args.timestamp)
973
+ throw new McpError(ErrorCode.InvalidParams, 'timestamp is required for action="at"');
974
+ const memories = getContextAtTime(proj, args.timestamp);
975
+ if (memories.length === 0) {
976
+ return {
977
+ content: [{ type: 'text', text: `No memories active at ${args.timestamp} in "${proj}".` }],
978
+ };
979
+ }
980
+ const lines = [
981
+ `Context at ${args.timestamp} — "${proj}" (${memories.length} active memories):`,
982
+ '',
983
+ ...memories.slice(0, 20).map(m => ` [${m.type.toUpperCase()}] ${m.summary} | ${m.distance.toFixed(2)} AU | ${m.id.slice(0, 8)}`),
984
+ ];
985
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
986
+ }
987
+ case 'chain': {
988
+ if (!args.memory_id)
989
+ throw new McpError(ErrorCode.InvalidParams, 'memory_id is required for action="chain"');
990
+ const chain = getEvolutionChain(args.memory_id);
991
+ if (chain.length === 0) {
992
+ return {
993
+ content: [{ type: 'text', text: `No evolution chain found for memory ${args.memory_id}.` }],
994
+ };
995
+ }
996
+ const lines = [
997
+ `Evolution chain for ${args.memory_id.slice(0, 8)} (${chain.length} entries, oldest first):`,
998
+ '',
999
+ ...chain.map((m, i) => {
1000
+ const date = m.created_at.slice(0, 10);
1001
+ const superseded = m.superseded_by ? ` → ${m.superseded_by.slice(0, 8)}` : ' [current]';
1002
+ return ` ${i + 1}. [${date}] ${m.summary.slice(0, 80)}${superseded}`;
1003
+ }),
1004
+ ];
1005
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
1006
+ }
1007
+ case 'summary': {
1008
+ const summary = getTemporalSummary(proj);
1009
+ return { content: [{ type: 'text', text: summary }] };
1010
+ }
1011
+ case 'set_bounds': {
1012
+ if (!args.memory_id)
1013
+ throw new McpError(ErrorCode.InvalidParams, 'memory_id is required for action="set_bounds"');
1014
+ setTemporalBounds(args.memory_id, args.valid_from, args.valid_until);
1015
+ const parts = [];
1016
+ if (args.valid_from)
1017
+ parts.push(`valid_from: ${args.valid_from}`);
1018
+ if (args.valid_until)
1019
+ parts.push(`valid_until: ${args.valid_until}`);
1020
+ return {
1021
+ content: [{
1022
+ type: 'text',
1023
+ text: `Temporal bounds set for ${args.memory_id.slice(0, 8)}: ${parts.join(', ')}`,
1024
+ }],
1025
+ };
1026
+ }
1027
+ default:
1028
+ throw new McpError(ErrorCode.InvalidParams, `Unknown action: ${String(args.action)}`);
1029
+ }
1030
+ }
1031
+ catch (err) {
1032
+ if (err instanceof McpError)
1033
+ throw err;
1034
+ throw new McpError(ErrorCode.InternalError, `temporal failed: ${String(err)}`);
1035
+ }
1036
+ }
1037
+ //# sourceMappingURL=memory-tools.js.map