@danielsimonjr/memory-mcp 12.2.3 → 12.5.2

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 (158) hide show
  1. package/README.md +88 -102
  2. package/dist/index.js +6 -0
  3. package/dist/server/toolDefinitions.d.ts.map +1 -1
  4. package/dist/server/toolDefinitions.js +745 -3
  5. package/dist/server/toolHandlers.d.ts +1 -9
  6. package/dist/server/toolHandlers.d.ts.map +1 -1
  7. package/dist/server/toolHandlers.js +859 -11
  8. package/package.json +9 -6
  9. package/dist/core/EntityManager.d.ts +0 -263
  10. package/dist/core/EntityManager.d.ts.map +0 -1
  11. package/dist/core/EntityManager.js +0 -478
  12. package/dist/core/GraphStorage.d.ts +0 -344
  13. package/dist/core/GraphStorage.d.ts.map +0 -1
  14. package/dist/core/GraphStorage.js +0 -708
  15. package/dist/core/GraphTraversal.d.ts +0 -136
  16. package/dist/core/GraphTraversal.d.ts.map +0 -1
  17. package/dist/core/GraphTraversal.js +0 -556
  18. package/dist/core/HierarchyManager.d.ts +0 -111
  19. package/dist/core/HierarchyManager.d.ts.map +0 -1
  20. package/dist/core/HierarchyManager.js +0 -224
  21. package/dist/core/ManagerContext.d.ts +0 -72
  22. package/dist/core/ManagerContext.d.ts.map +0 -1
  23. package/dist/core/ManagerContext.js +0 -118
  24. package/dist/core/ObservationManager.d.ts +0 -85
  25. package/dist/core/ObservationManager.d.ts.map +0 -1
  26. package/dist/core/ObservationManager.js +0 -123
  27. package/dist/core/RelationManager.d.ts +0 -131
  28. package/dist/core/RelationManager.d.ts.map +0 -1
  29. package/dist/core/RelationManager.js +0 -210
  30. package/dist/core/SQLiteStorage.d.ts +0 -354
  31. package/dist/core/SQLiteStorage.d.ts.map +0 -1
  32. package/dist/core/SQLiteStorage.js +0 -917
  33. package/dist/core/StorageFactory.d.ts +0 -45
  34. package/dist/core/StorageFactory.d.ts.map +0 -1
  35. package/dist/core/StorageFactory.js +0 -64
  36. package/dist/core/TransactionManager.d.ts +0 -259
  37. package/dist/core/TransactionManager.d.ts.map +0 -1
  38. package/dist/core/TransactionManager.js +0 -392
  39. package/dist/core/index.d.ts +0 -16
  40. package/dist/core/index.d.ts.map +0 -1
  41. package/dist/core/index.js +0 -17
  42. package/dist/features/AnalyticsManager.d.ts +0 -44
  43. package/dist/features/AnalyticsManager.d.ts.map +0 -1
  44. package/dist/features/AnalyticsManager.js +0 -223
  45. package/dist/features/ArchiveManager.d.ts +0 -121
  46. package/dist/features/ArchiveManager.d.ts.map +0 -1
  47. package/dist/features/ArchiveManager.js +0 -240
  48. package/dist/features/CompressionManager.d.ts +0 -109
  49. package/dist/features/CompressionManager.d.ts.map +0 -1
  50. package/dist/features/CompressionManager.js +0 -404
  51. package/dist/features/IOManager.d.ts +0 -221
  52. package/dist/features/IOManager.d.ts.map +0 -1
  53. package/dist/features/IOManager.js +0 -997
  54. package/dist/features/StreamingExporter.d.ts +0 -105
  55. package/dist/features/StreamingExporter.d.ts.map +0 -1
  56. package/dist/features/StreamingExporter.js +0 -150
  57. package/dist/features/TagManager.d.ts +0 -147
  58. package/dist/features/TagManager.d.ts.map +0 -1
  59. package/dist/features/TagManager.js +0 -210
  60. package/dist/features/index.d.ts +0 -12
  61. package/dist/features/index.d.ts.map +0 -1
  62. package/dist/features/index.js +0 -11
  63. package/dist/search/BasicSearch.d.ts +0 -51
  64. package/dist/search/BasicSearch.d.ts.map +0 -1
  65. package/dist/search/BasicSearch.js +0 -137
  66. package/dist/search/BooleanSearch.d.ts +0 -98
  67. package/dist/search/BooleanSearch.d.ts.map +0 -1
  68. package/dist/search/BooleanSearch.js +0 -430
  69. package/dist/search/EmbeddingService.d.ts +0 -178
  70. package/dist/search/EmbeddingService.d.ts.map +0 -1
  71. package/dist/search/EmbeddingService.js +0 -358
  72. package/dist/search/FuzzySearch.d.ts +0 -118
  73. package/dist/search/FuzzySearch.d.ts.map +0 -1
  74. package/dist/search/FuzzySearch.js +0 -312
  75. package/dist/search/RankedSearch.d.ts +0 -71
  76. package/dist/search/RankedSearch.d.ts.map +0 -1
  77. package/dist/search/RankedSearch.js +0 -238
  78. package/dist/search/SavedSearchManager.d.ts +0 -79
  79. package/dist/search/SavedSearchManager.d.ts.map +0 -1
  80. package/dist/search/SavedSearchManager.js +0 -145
  81. package/dist/search/SearchFilterChain.d.ts +0 -120
  82. package/dist/search/SearchFilterChain.d.ts.map +0 -1
  83. package/dist/search/SearchFilterChain.js +0 -185
  84. package/dist/search/SearchManager.d.ts +0 -285
  85. package/dist/search/SearchManager.d.ts.map +0 -1
  86. package/dist/search/SearchManager.js +0 -338
  87. package/dist/search/SearchSuggestions.d.ts +0 -27
  88. package/dist/search/SearchSuggestions.d.ts.map +0 -1
  89. package/dist/search/SearchSuggestions.js +0 -57
  90. package/dist/search/SemanticSearch.d.ts +0 -146
  91. package/dist/search/SemanticSearch.d.ts.map +0 -1
  92. package/dist/search/SemanticSearch.js +0 -313
  93. package/dist/search/TFIDFIndexManager.d.ts +0 -63
  94. package/dist/search/TFIDFIndexManager.d.ts.map +0 -1
  95. package/dist/search/TFIDFIndexManager.js +0 -215
  96. package/dist/search/VectorStore.d.ts +0 -235
  97. package/dist/search/VectorStore.d.ts.map +0 -1
  98. package/dist/search/VectorStore.js +0 -311
  99. package/dist/search/index.d.ts +0 -18
  100. package/dist/search/index.d.ts.map +0 -1
  101. package/dist/search/index.js +0 -21
  102. package/dist/types/index.d.ts +0 -13
  103. package/dist/types/index.d.ts.map +0 -1
  104. package/dist/types/index.js +0 -12
  105. package/dist/types/types.d.ts +0 -1234
  106. package/dist/types/types.d.ts.map +0 -1
  107. package/dist/types/types.js +0 -9
  108. package/dist/utils/compressedCache.d.ts +0 -192
  109. package/dist/utils/compressedCache.d.ts.map +0 -1
  110. package/dist/utils/compressedCache.js +0 -309
  111. package/dist/utils/compressionUtil.d.ts +0 -214
  112. package/dist/utils/compressionUtil.d.ts.map +0 -1
  113. package/dist/utils/compressionUtil.js +0 -247
  114. package/dist/utils/constants.d.ts +0 -245
  115. package/dist/utils/constants.d.ts.map +0 -1
  116. package/dist/utils/constants.js +0 -252
  117. package/dist/utils/entityUtils.d.ts +0 -321
  118. package/dist/utils/entityUtils.d.ts.map +0 -1
  119. package/dist/utils/entityUtils.js +0 -538
  120. package/dist/utils/errors.d.ts +0 -77
  121. package/dist/utils/errors.d.ts.map +0 -1
  122. package/dist/utils/errors.js +0 -121
  123. package/dist/utils/formatters.d.ts +0 -145
  124. package/dist/utils/formatters.d.ts.map +0 -1
  125. package/dist/utils/formatters.js +0 -132
  126. package/dist/utils/index.d.ts +0 -22
  127. package/dist/utils/index.d.ts.map +0 -1
  128. package/dist/utils/index.js +0 -75
  129. package/dist/utils/indexes.d.ts +0 -270
  130. package/dist/utils/indexes.d.ts.map +0 -1
  131. package/dist/utils/indexes.js +0 -526
  132. package/dist/utils/logger.d.ts +0 -24
  133. package/dist/utils/logger.d.ts.map +0 -1
  134. package/dist/utils/logger.js +0 -33
  135. package/dist/utils/parallelUtils.d.ts +0 -72
  136. package/dist/utils/parallelUtils.d.ts.map +0 -1
  137. package/dist/utils/parallelUtils.js +0 -169
  138. package/dist/utils/schemas.d.ts +0 -374
  139. package/dist/utils/schemas.d.ts.map +0 -1
  140. package/dist/utils/schemas.js +0 -484
  141. package/dist/utils/searchAlgorithms.d.ts +0 -99
  142. package/dist/utils/searchAlgorithms.d.ts.map +0 -1
  143. package/dist/utils/searchAlgorithms.js +0 -167
  144. package/dist/utils/searchCache.d.ts +0 -108
  145. package/dist/utils/searchCache.d.ts.map +0 -1
  146. package/dist/utils/searchCache.js +0 -209
  147. package/dist/utils/taskScheduler.d.ts +0 -290
  148. package/dist/utils/taskScheduler.d.ts.map +0 -1
  149. package/dist/utils/taskScheduler.js +0 -466
  150. package/dist/workers/WorkerPool.d.ts +0 -81
  151. package/dist/workers/WorkerPool.d.ts.map +0 -1
  152. package/dist/workers/WorkerPool.js +0 -121
  153. package/dist/workers/index.d.ts +0 -12
  154. package/dist/workers/index.d.ts.map +0 -1
  155. package/dist/workers/index.js +0 -9
  156. package/dist/workers/levenshteinWorker.d.ts +0 -60
  157. package/dist/workers/levenshteinWorker.d.ts.map +0 -1
  158. package/dist/workers/levenshteinWorker.js +0 -98
@@ -8,7 +8,9 @@
8
8
  * @module server/toolHandlers
9
9
  */
10
10
  import path from 'node:path';
11
- import { formatToolResponse, formatTextResponse, formatRawResponse, validateWithSchema, validateFilePath, BatchCreateEntitiesSchema, BatchCreateRelationsSchema, EntityNamesSchema, DeleteRelationsSchema, AddObservationsInputSchema, DeleteObservationsInputSchema, ArchiveCriteriaSchema, SavedSearchInputSchema, SavedSearchUpdateSchema, ImportFormatSchema, ExtendedExportFormatSchema, MergeStrategySchema, ExportFilterSchema, SearchQuerySchema, HybridSearchManager, QueryAnalyzer, QueryPlanner, ReflectionManager, ObservationNormalizer, RefIndex, AuditLog, GovernanceManager, FreshnessManager, ArtifactManager, CollaborativeSynthesis, FailureDistillation, CognitiveLoadAnalyzer, ConsolidationScheduler, DreamEngine, DistillationPipeline, DefaultDistillationPolicy, NoOpDistillationPolicy, computeEntropy, passesEntropyFilter, EntropyFilterStage, getRoleProfile, listRoleProfiles, QueryCostEstimator, ContradictionDetector, PiiRedactor, } from '@danielsimonjr/memoryjs';
11
+ import { formatToolResponse, formatTextResponse, formatRawResponse, validateWithSchema, validateFilePath, BatchCreateEntitiesSchema, BatchCreateRelationsSchema, EntityNamesSchema, DeleteRelationsSchema, AddObservationsInputSchema, DeleteObservationsInputSchema, ArchiveCriteriaSchema, SavedSearchInputSchema, SavedSearchUpdateSchema, ImportFormatSchema, ExtendedExportFormatSchema, MergeStrategySchema, ExportFilterSchema, SearchQuerySchema, HybridSearchManager, QueryAnalyzer, QueryPlanner, ReflectionManager, ObservationNormalizer, RefIndex, AuditLog, GovernanceManager, FreshnessManager, ArtifactManager, CollaborativeSynthesis, FailureDistillation, CognitiveLoadAnalyzer, ConsolidationScheduler, DreamEngine, DistillationPipeline, DefaultDistillationPolicy, NoOpDistillationPolicy, computeEntropy, passesEntropyFilter, EntropyFilterStage, getRoleProfile, listRoleProfiles, QueryCostEstimator, ContradictionDetector, PiiRedactor, DecisionManager, RankedSearch, clearAllSearchCaches, getAllCacheStats, } from '@danielsimonjr/memoryjs';
12
+ import { promises as fs } from 'node:fs';
13
+ import { performance } from 'node:perf_hooks';
12
14
  import { z } from 'zod';
13
15
  import { maybeCompressResponse } from './responseCompressor.js';
14
16
  // ==================== SINGLETON INFRASTRUCTURE ====================
@@ -24,9 +26,21 @@ const failureDistillationMap = new WeakMap();
24
26
  const consolidationSchedulerMap = new WeakMap();
25
27
  const dreamEngineMap = new WeakMap();
26
28
  const queryCostEstimatorMap = new WeakMap();
29
+ // Active project scope (Phase 13 deferred item; ctx.defaultProjectId is readonly,
30
+ // so we keep mutable scope state in a WeakMap keyed on the context. Set via the
31
+ // `set_project_scope` tool; handlers may consult `getActiveProjectScope(ctx)`
32
+ // when they want to auto-apply the scope.
33
+ const projectScopeMap = new WeakMap();
34
+ export function getActiveProjectScope(ctx) {
35
+ return projectScopeMap.get(ctx);
36
+ }
27
37
  function getStorageFilePath(ctx) {
28
- // GraphStorage exposes filePath publicly; fall back to cwd-relative default
29
- return ctx.storage.filePath ?? 'memory.jsonl';
38
+ // GraphStorage stores the path on `memoryFilePath` (private but reachable via
39
+ // type-cast). Older versions of this helper looked for `filePath` and
40
+ // silently fell back to 'memory.jsonl' — which made diag / size / reindex
41
+ // reports point at the wrong file. Try both field names defensively.
42
+ const storage = ctx.storage;
43
+ return storage.memoryFilePath ?? storage.filePath ?? 'memory.jsonl';
30
44
  }
31
45
  function getRefIndex(ctx) {
32
46
  if (!refIndexMap.has(ctx)) {
@@ -125,11 +139,43 @@ async function withCompression(handler) {
125
139
  * Filtered/limited search tools (search_nodes_ranked, fuzzy_search, etc.) are not
126
140
  * wrapped because their results are bounded by query specificity or limit params.
127
141
  */
142
+ /**
143
+ * v2.1.0 — Parse `find_duplicate_observations` / `find_jaccard_duplicate_observations`
144
+ * filter args into the manager's `ObservationDedupFilter` shape. Validates each
145
+ * field independently so a stray garbage value falls back to the manager default
146
+ * rather than throwing — REST-like leniency since these are diagnostic tools.
147
+ */
148
+ function parseObservationDedupFilter(args) {
149
+ const out = {};
150
+ if (typeof args.entityType === 'string') {
151
+ out.entityType = args.entityType;
152
+ }
153
+ else if (Array.isArray(args.entityType) && args.entityType.every((s) => typeof s === 'string')) {
154
+ out.entityType = args.entityType;
155
+ }
156
+ if (typeof args.projectId === 'string')
157
+ out.projectId = args.projectId;
158
+ if (typeof args.sessionId === 'string')
159
+ out.sessionId = args.sessionId;
160
+ if (typeof args.minOccurrences === 'number' && Number.isFinite(args.minOccurrences) && args.minOccurrences >= 2) {
161
+ out.minOccurrences = args.minOccurrences;
162
+ }
163
+ if (typeof args.maxGroups === 'number' && Number.isFinite(args.maxGroups) && args.maxGroups >= 1) {
164
+ out.maxGroups = args.maxGroups;
165
+ }
166
+ return out;
167
+ }
128
168
  export const toolHandlers = {
129
169
  // ==================== ENTITY HANDLERS ====================
130
170
  create_entities: async (ctx, args) => {
131
171
  const entities = validateWithSchema(args.entities, BatchCreateEntitiesSchema, 'Invalid entities data');
132
- return formatToolResponse(await ctx.entityManager.createEntities(entities));
172
+ // Phase 13 (v12.4.0): auto-stamp active project scope on entities lacking
173
+ // an explicit projectId. Set via set_project_scope; explicit projectId wins.
174
+ const activeScope = getActiveProjectScope(ctx);
175
+ const scoped = activeScope
176
+ ? entities.map((e) => (e.projectId === undefined ? { ...e, projectId: activeScope } : e))
177
+ : entities;
178
+ return formatToolResponse(await ctx.entityManager.createEntities(scoped));
133
179
  },
134
180
  delete_entities: async (ctx, args) => {
135
181
  const entityNames = validateWithSchema(args.entityNames, EntityNamesSchema, 'Invalid entity names');
@@ -148,6 +194,19 @@ export const toolHandlers = {
148
194
  const projects = await ctx.entityManager.listProjects();
149
195
  return formatToolResponse({ projects, count: projects.length });
150
196
  },
197
+ set_project_scope: async (ctx, args) => {
198
+ const projectId = validateWithSchema(args.projectId, z.string(), 'Invalid projectId');
199
+ if (projectId === '') {
200
+ projectScopeMap.delete(ctx);
201
+ return formatToolResponse({ projectId: null });
202
+ }
203
+ projectScopeMap.set(ctx, projectId);
204
+ return formatToolResponse({ projectId });
205
+ },
206
+ get_project_scope: async (ctx, _args) => {
207
+ const projectId = projectScopeMap.get(ctx) ?? null;
208
+ return formatToolResponse({ projectId });
209
+ },
151
210
  get_entity_versions: async (ctx, args) => {
152
211
  const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
153
212
  const latest = await ctx.entityManager.getLatestVersion(entityName);
@@ -242,10 +301,20 @@ export const toolHandlers = {
242
301
  const entityName = args.entityName !== undefined
243
302
  ? validateWithSchema(args.entityName, z.string().min(1), 'Invalid entity name')
244
303
  : undefined;
245
- const options = args.options ?? {};
304
+ const options = args.options !== undefined
305
+ ? validateWithSchema(args.options, z.object({
306
+ resolveCoreferences: z.boolean().optional(),
307
+ anchorTimestamps: z.boolean().optional(),
308
+ extractKeywords: z.boolean().optional(),
309
+ }).strict(), 'Invalid options')
310
+ : {};
246
311
  const persist = args.persist === true;
247
312
  const normalizer = new ObservationNormalizer();
248
- const graph = await ctx.storage.loadGraph();
313
+ // Use getGraphForMutation() to acquire the storage write lock — prevents
314
+ // concurrent calls from overwriting each other's persisted normalizations.
315
+ const graph = persist
316
+ ? await ctx.storage.getGraphForMutation()
317
+ : await ctx.storage.loadGraph();
249
318
  const entities = entityName
250
319
  ? graph.entities.filter(e => e.name === entityName)
251
320
  : graph.entities;
@@ -350,8 +419,22 @@ export const toolHandlers = {
350
419
  // Phase 11 Sprint 2: Hybrid search
351
420
  hybrid_search: async (ctx, args) => {
352
421
  const query = validateWithSchema(args.query, SearchQuerySchema, 'Invalid search query');
353
- const weights = args.weights;
354
- const filters = args.filters;
422
+ const weights = args.weights !== undefined
423
+ ? validateWithSchema(args.weights, z.object({
424
+ semantic: z.number().optional(),
425
+ lexical: z.number().optional(),
426
+ symbolic: z.number().optional(),
427
+ }).strict(), 'Invalid weights')
428
+ : undefined;
429
+ const filters = args.filters !== undefined
430
+ ? validateWithSchema(args.filters, z.object({
431
+ tags: z.array(z.string()).optional(),
432
+ entityTypes: z.array(z.string()).optional(),
433
+ dateRange: z.object({ start: z.string(), end: z.string() }).strict().optional(),
434
+ minImportance: z.number().optional(),
435
+ maxImportance: z.number().optional(),
436
+ }).strict(), 'Invalid filters')
437
+ : undefined;
355
438
  const limit = args.limit !== undefined
356
439
  ? validateWithSchema(args.limit, z.number().int().positive().max(200), 'Invalid limit')
357
440
  : 10;
@@ -863,7 +946,7 @@ export const toolHandlers = {
863
946
  const entityName = args.entityName !== undefined
864
947
  ? validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName')
865
948
  : undefined;
866
- const refs = await getRefIndex(ctx).listRefs(entityName ? { entityName } : undefined);
949
+ const refs = await getRefIndex(ctx).listRefs(entityName);
867
950
  return formatToolResponse({ refs, count: refs.length });
868
951
  },
869
952
  // ==================== ARTIFACT HANDLERS ====================
@@ -1274,7 +1357,9 @@ export const toolHandlers = {
1274
1357
  const distillFailures = args.distillFailures !== undefined
1275
1358
  ? validateWithSchema(args.distillFailures, z.boolean(), 'Invalid distillFailures')
1276
1359
  : true;
1277
- const graph = await ctx.storage.loadGraph();
1360
+ // Use getGraphForMutation() to acquire the storage write lock — prevents
1361
+ // concurrent end_session calls from clobbering each other's outcome writes.
1362
+ const graph = await ctx.storage.getGraphForMutation();
1278
1363
  const sessionEntity = graph.entities.find(e => e.name === sessionId);
1279
1364
  if (!sessionEntity) {
1280
1365
  return formatTextResponse(`Session "${sessionId}" not found`);
@@ -1718,7 +1803,7 @@ export const toolHandlers = {
1718
1803
  // ==================== η.5.5.c OCC HANDLER ====================
1719
1804
  update_entity: async (ctx, args) => {
1720
1805
  const name = validateWithSchema(args.name, z.string().min(1), 'Invalid name');
1721
- const updates = validateWithSchema(args.updates, z.record(z.unknown()), 'Invalid updates');
1806
+ const updates = validateWithSchema(args.updates, z.record(z.string(), z.unknown()), 'Invalid updates');
1722
1807
  const expectedVersion = args.expectedVersion !== undefined
1723
1808
  ? validateWithSchema(args.expectedVersion, z.number().int().positive(), 'Invalid expectedVersion')
1724
1809
  : undefined;
@@ -1897,6 +1982,769 @@ export const toolHandlers = {
1897
1982
  const chains = await ctx.worldModelManager.predictOutcome(action, candidates);
1898
1983
  return formatToolResponse({ action, candidates, chains, count: chains.length });
1899
1984
  },
1985
+ // ==================== v2.1.0 TOOL AFFORDANCE ====================
1986
+ record_tool_outcome: async (ctx, args) => {
1987
+ const toolName = validateWithSchema(args.toolName, z.string().min(1), 'Invalid toolName');
1988
+ const outcome = validateWithSchema(args.outcome, z.enum(['success', 'failure', 'partial']), 'Invalid outcome');
1989
+ const errorMessage = args.errorMessage === undefined
1990
+ ? undefined
1991
+ : validateWithSchema(args.errorMessage, z.string(), 'Invalid errorMessage');
1992
+ const durationMs = args.durationMs === undefined
1993
+ ? undefined
1994
+ : validateWithSchema(args.durationMs, z.number().min(0), 'Invalid durationMs');
1995
+ const record = await ctx.toolAffordanceManager.recordOutcome(toolName, {
1996
+ outcome,
1997
+ errorMessage,
1998
+ durationMs,
1999
+ });
2000
+ return formatToolResponse({ record });
2001
+ },
2002
+ get_tool_affordance_stats: async (ctx, args) => {
2003
+ const toolName = validateWithSchema(args.toolName, z.string().min(1), 'Invalid toolName');
2004
+ const stats = ctx.toolAffordanceManager.rollingStats(toolName);
2005
+ return formatToolResponse({ toolName, stats: stats ?? null });
2006
+ },
2007
+ suggest_tool: async (ctx, args) => {
2008
+ const taskHint = validateWithSchema(args.taskHint, z.string().min(1), 'Invalid taskHint');
2009
+ const limit = args.limit === undefined
2010
+ ? undefined
2011
+ : validateWithSchema(args.limit, z.number().int().min(1), 'Invalid limit');
2012
+ const minScore = args.minScore === undefined
2013
+ ? undefined
2014
+ : validateWithSchema(args.minScore, z.number().min(0).max(1), 'Invalid minScore');
2015
+ const suggestions = await ctx.toolAffordanceManager.suggestTool(taskHint, { limit, minScore });
2016
+ return formatToolResponse({ taskHint, suggestions, count: suggestions.length });
2017
+ },
2018
+ list_tool_affordances: async (ctx) => {
2019
+ const records = await ctx.toolAffordanceManager.list();
2020
+ return formatToolResponse({ records, count: records.length });
2021
+ },
2022
+ remove_tool_affordance: async (ctx, args) => {
2023
+ const toolName = validateWithSchema(args.toolName, z.string().min(1), 'Invalid toolName');
2024
+ const removed = await ctx.toolAffordanceManager.remove(toolName);
2025
+ return formatToolResponse({ toolName, removed });
2026
+ },
2027
+ observe_tool_start: (ctx, args) => {
2028
+ const toolName = validateWithSchema(args.toolName, z.string().min(1), 'Invalid toolName');
2029
+ const argsField = args.args === undefined
2030
+ ? undefined
2031
+ : validateWithSchema(args.args, z.record(z.string(), z.unknown()), 'Invalid args (must be an object)');
2032
+ const callId = ctx.toolCallObserver.observeStart(toolName, argsField);
2033
+ return Promise.resolve(formatToolResponse({ callId, toolName }));
2034
+ },
2035
+ observe_tool_complete: async (ctx, args) => {
2036
+ const callId = validateWithSchema(args.callId, z.string().min(1), 'Invalid callId');
2037
+ const result = args.result === undefined
2038
+ ? undefined
2039
+ : validateWithSchema(args.result, z.string(), 'Invalid result');
2040
+ await ctx.toolCallObserver.observeComplete(callId, result === undefined ? undefined : { result });
2041
+ return formatToolResponse({ callId, recorded: 'success' });
2042
+ },
2043
+ observe_tool_error: async (ctx, args) => {
2044
+ const callId = validateWithSchema(args.callId, z.string().min(1), 'Invalid callId');
2045
+ const errorMessage = validateWithSchema(args.errorMessage, z.string().min(1), 'Invalid errorMessage');
2046
+ await ctx.toolCallObserver.observeError(callId, errorMessage);
2047
+ return formatToolResponse({ callId, recorded: 'failure', errorMessage });
2048
+ },
2049
+ observe_tool_partial: async (ctx, args) => {
2050
+ const callId = validateWithSchema(args.callId, z.string().min(1), 'Invalid callId');
2051
+ const reason = validateWithSchema(args.reason, z.string().min(1), 'Invalid reason');
2052
+ await ctx.toolCallObserver.observePartial(callId, reason);
2053
+ return formatToolResponse({ callId, recorded: 'partial', reason });
2054
+ },
2055
+ observe_tool_cancel: (ctx, args) => {
2056
+ const callId = validateWithSchema(args.callId, z.string().min(1), 'Invalid callId');
2057
+ ctx.toolCallObserver.cancel(callId);
2058
+ return Promise.resolve(formatToolResponse({ callId, cancelled: true }));
2059
+ },
2060
+ tool_observer_in_flight_count: (ctx) => {
2061
+ const count = ctx.toolCallObserver.inFlightCount();
2062
+ return Promise.resolve(formatToolResponse({ inFlightCount: count }));
2063
+ },
2064
+ // ==================== v2.1.0 HEURISTIC GUIDELINES ====================
2065
+ add_heuristic: async (ctx, args) => {
2066
+ const condition = validateWithSchema(args.condition, z.string().min(1), 'Invalid condition');
2067
+ const action = validateWithSchema(args.action, z.string().min(1), 'Invalid action');
2068
+ const priority = args.priority === undefined
2069
+ ? undefined
2070
+ : validateWithSchema(args.priority, z.number(), 'Invalid priority');
2071
+ const initialConfidence = args.initialConfidence === undefined
2072
+ ? undefined
2073
+ : validateWithSchema(args.initialConfidence, z.number().min(0).max(1), 'Invalid initialConfidence');
2074
+ const importance = args.importance === undefined
2075
+ ? undefined
2076
+ : validateWithSchema(args.importance, z.number().min(0).max(10), 'Invalid importance');
2077
+ const agentId = args.agentId === undefined
2078
+ ? undefined
2079
+ : validateWithSchema(args.agentId, z.string().min(1), 'Invalid agentId');
2080
+ const id = args.id === undefined
2081
+ ? undefined
2082
+ : validateWithSchema(args.id, z.string().min(1), 'Invalid id');
2083
+ const heuristicId = await ctx.heuristicManager.add({
2084
+ condition,
2085
+ action,
2086
+ priority,
2087
+ initialConfidence,
2088
+ importance,
2089
+ agentId,
2090
+ id: id,
2091
+ });
2092
+ return formatToolResponse({ id: heuristicId });
2093
+ },
2094
+ get_heuristic: async (ctx, args) => {
2095
+ const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
2096
+ const heuristic = ctx.heuristicManager.get(id);
2097
+ return formatToolResponse({ id, heuristic: heuristic ?? null });
2098
+ },
2099
+ list_heuristics: async (ctx) => {
2100
+ const heuristics = await ctx.heuristicManager.list();
2101
+ return formatToolResponse({ heuristics, count: heuristics.length });
2102
+ },
2103
+ heuristic_count: async (ctx) => {
2104
+ const size = await ctx.heuristicManager.size();
2105
+ return formatToolResponse({ count: size });
2106
+ },
2107
+ match_heuristics: async (ctx, args) => {
2108
+ const input = validateWithSchema(args.input, z.string().min(1), 'Invalid input');
2109
+ const limit = args.limit === undefined
2110
+ ? undefined
2111
+ : validateWithSchema(args.limit, z.number().int().min(1), 'Invalid limit');
2112
+ const minScore = args.minScore === undefined
2113
+ ? undefined
2114
+ : validateWithSchema(args.minScore, z.number().min(0).max(1), 'Invalid minScore');
2115
+ const matches = await ctx.heuristicManager.match(input, { limit, minScore });
2116
+ return formatToolResponse({ input, matches, count: matches.length });
2117
+ },
2118
+ reinforce_heuristic: async (ctx, args) => {
2119
+ const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
2120
+ const result = await ctx.heuristicManager.reinforce(id);
2121
+ return formatToolResponse({ id, result });
2122
+ },
2123
+ record_heuristic_contradiction: async (ctx, args) => {
2124
+ const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
2125
+ const result = await ctx.heuristicManager.recordContradiction(id);
2126
+ return formatToolResponse({ id, result });
2127
+ },
2128
+ detect_heuristic_conflicts: async (ctx) => {
2129
+ const conflicts = await ctx.heuristicManager.detectConflicts();
2130
+ return formatToolResponse({ conflicts, count: conflicts.length });
2131
+ },
2132
+ remove_heuristic: async (ctx, args) => {
2133
+ const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
2134
+ const removed = await ctx.heuristicManager.remove(id);
2135
+ return formatToolResponse({ id, removed });
2136
+ },
2137
+ clear_heuristics: async (ctx) => {
2138
+ await ctx.heuristicManager.clear();
2139
+ return formatToolResponse({ cleared: true });
2140
+ },
2141
+ // ==================== v2.1.0 PROJECT CONTEXT ====================
2142
+ upsert_project_context: async (ctx, args) => {
2143
+ const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
2144
+ const facts = args.facts === undefined
2145
+ ? undefined
2146
+ : validateWithSchema(args.facts, z.array(z.string()), 'Invalid facts');
2147
+ const conventions = args.conventions === undefined
2148
+ ? undefined
2149
+ : validateWithSchema(args.conventions, z.array(z.string()), 'Invalid conventions');
2150
+ const commands = args.commands === undefined
2151
+ ? undefined
2152
+ : validateWithSchema(args.commands, z.array(z.object({
2153
+ name: z.string().min(1),
2154
+ command: z.string().min(1),
2155
+ purpose: z.string().min(1),
2156
+ })), 'Invalid commands');
2157
+ const glossary = args.glossary === undefined
2158
+ ? undefined
2159
+ : validateWithSchema(args.glossary, z.array(z.object({
2160
+ term: z.string().min(1),
2161
+ definition: z.string().min(1),
2162
+ })), 'Invalid glossary');
2163
+ const record = await ctx.projectContextManager.upsert(projectId, {
2164
+ facts, conventions, commands, glossary,
2165
+ });
2166
+ return formatToolResponse({ record });
2167
+ },
2168
+ get_project_context: async (ctx, args) => {
2169
+ const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
2170
+ const record = ctx.projectContextManager.get(projectId);
2171
+ return formatToolResponse({ projectId, record: record ?? null });
2172
+ },
2173
+ append_project_fact: async (ctx, args) => {
2174
+ const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
2175
+ const fact = validateWithSchema(args.fact, z.string().min(1), 'Invalid fact');
2176
+ const record = await ctx.projectContextManager.appendFact(projectId, fact);
2177
+ return formatToolResponse({ record });
2178
+ },
2179
+ append_project_convention: async (ctx, args) => {
2180
+ const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
2181
+ const convention = validateWithSchema(args.convention, z.string().min(1), 'Invalid convention');
2182
+ const record = await ctx.projectContextManager.appendConvention(projectId, convention);
2183
+ return formatToolResponse({ record });
2184
+ },
2185
+ append_project_command: async (ctx, args) => {
2186
+ const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
2187
+ const name = validateWithSchema(args.name, z.string().min(1), 'Invalid name');
2188
+ const command = validateWithSchema(args.command, z.string().min(1), 'Invalid command');
2189
+ const purpose = validateWithSchema(args.purpose, z.string().min(1), 'Invalid purpose');
2190
+ const record = await ctx.projectContextManager.appendCommand(projectId, { name, command, purpose });
2191
+ return formatToolResponse({ record });
2192
+ },
2193
+ append_project_glossary_term: async (ctx, args) => {
2194
+ const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
2195
+ const term = validateWithSchema(args.term, z.string().min(1), 'Invalid term');
2196
+ const definition = validateWithSchema(args.definition, z.string().min(1), 'Invalid definition');
2197
+ const record = await ctx.projectContextManager.appendGlossaryTerm(projectId, { term, definition });
2198
+ return formatToolResponse({ record });
2199
+ },
2200
+ remove_project_fact: async (ctx, args) => {
2201
+ const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
2202
+ const fact = validateWithSchema(args.fact, z.string().min(1), 'Invalid fact');
2203
+ const removed = await ctx.projectContextManager.removeFact(projectId, fact);
2204
+ return formatToolResponse({ projectId, fact, removed });
2205
+ },
2206
+ remove_project_convention: async (ctx, args) => {
2207
+ const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
2208
+ const convention = validateWithSchema(args.convention, z.string().min(1), 'Invalid convention');
2209
+ const removed = await ctx.projectContextManager.removeConvention(projectId, convention);
2210
+ return formatToolResponse({ projectId, convention, removed });
2211
+ },
2212
+ remove_project_command: async (ctx, args) => {
2213
+ const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
2214
+ const commandName = validateWithSchema(args.commandName, z.string().min(1), 'Invalid commandName');
2215
+ const removed = await ctx.projectContextManager.removeCommand(projectId, commandName);
2216
+ return formatToolResponse({ projectId, commandName, removed });
2217
+ },
2218
+ remove_project_glossary_term: async (ctx, args) => {
2219
+ const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
2220
+ const term = validateWithSchema(args.term, z.string().min(1), 'Invalid term');
2221
+ const removed = await ctx.projectContextManager.removeGlossaryTerm(projectId, term);
2222
+ return formatToolResponse({ projectId, term, removed });
2223
+ },
2224
+ clear_project_context: async (ctx, args) => {
2225
+ const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
2226
+ const cleared = await ctx.projectContextManager.clear(projectId);
2227
+ return formatToolResponse({ projectId, cleared });
2228
+ },
2229
+ format_project_context_for_llm: async (ctx, args) => {
2230
+ const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
2231
+ const budgetChars = args.budgetChars === undefined
2232
+ ? undefined
2233
+ : validateWithSchema(args.budgetChars, z.number().int().min(1), 'Invalid budgetChars');
2234
+ const prose = await ctx.projectContextManager.forContext(projectId, { budgetChars });
2235
+ return formatTextResponse(prose);
2236
+ },
2237
+ // ==================== v2.1.0 DECISION RATIONALE ====================
2238
+ propose_decision: async (ctx, args) => {
2239
+ const context = validateWithSchema(args.context, z.string().min(1), 'Invalid context');
2240
+ const decision = validateWithSchema(args.decision, z.string().min(1), 'Invalid decision');
2241
+ const alternatives = args.alternatives === undefined
2242
+ ? []
2243
+ : validateWithSchema(args.alternatives, z.array(z.string()), 'Invalid alternatives');
2244
+ const consequences = args.consequences === undefined
2245
+ ? []
2246
+ : validateWithSchema(args.consequences, z.array(z.string()), 'Invalid consequences');
2247
+ const relatedFiles = args.relatedFiles === undefined
2248
+ ? undefined
2249
+ : validateWithSchema(args.relatedFiles, z.array(z.string()), 'Invalid relatedFiles');
2250
+ const supersedes = args.supersedes === undefined
2251
+ ? undefined
2252
+ : validateWithSchema(args.supersedes, z.string().min(1), 'Invalid supersedes');
2253
+ const sourceSessionId = args.sourceSessionId === undefined
2254
+ ? undefined
2255
+ : validateWithSchema(args.sourceSessionId, z.string().min(1), 'Invalid sourceSessionId');
2256
+ const sourceProjectId = args.sourceProjectId === undefined
2257
+ ? undefined
2258
+ : validateWithSchema(args.sourceProjectId, z.string().min(1), 'Invalid sourceProjectId');
2259
+ const importance = args.importance === undefined
2260
+ ? undefined
2261
+ : validateWithSchema(args.importance, z.number().min(0).max(10), 'Invalid importance');
2262
+ const agentId = args.agentId === undefined
2263
+ ? undefined
2264
+ : validateWithSchema(args.agentId, z.string().min(1), 'Invalid agentId');
2265
+ // `supersedes` is a branded DecisionId at the type level; the
2266
+ // manager casts internally so a string is fine here.
2267
+ const record = await ctx.decisionManager.propose({
2268
+ context,
2269
+ decision,
2270
+ alternatives,
2271
+ consequences,
2272
+ relatedFiles,
2273
+ supersedes: supersedes,
2274
+ sourceSessionId,
2275
+ sourceProjectId,
2276
+ }, { importance, agentId });
2277
+ return formatToolResponse({ record });
2278
+ },
2279
+ accept_decision: async (ctx, args) => {
2280
+ const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
2281
+ const result = await ctx.decisionManager.accept(id);
2282
+ return formatToolResponse({ id, result });
2283
+ },
2284
+ reject_decision: async (ctx, args) => {
2285
+ const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
2286
+ const reason = validateWithSchema(args.reason, z.string().min(1), 'Invalid reason');
2287
+ const result = await ctx.decisionManager.reject(id, reason);
2288
+ return formatToolResponse({ id, reason, result });
2289
+ },
2290
+ supersede_decision: async (ctx, args) => {
2291
+ const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
2292
+ const by = validateWithSchema(args.by, z.string().min(1), 'Invalid by');
2293
+ const result = await ctx.decisionManager.supersede(id, by);
2294
+ return formatToolResponse({ id, by, result });
2295
+ },
2296
+ find_decisions_by_context: async (ctx, args) => {
2297
+ const query = validateWithSchema(args.query, z.string().min(1), 'Invalid query');
2298
+ const records = await ctx.decisionManager.findByContext(query);
2299
+ return formatToolResponse({ query, records, count: records.length });
2300
+ },
2301
+ get_decision_chain: async (ctx, args) => {
2302
+ const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
2303
+ const chain = await ctx.decisionManager.getChain(id);
2304
+ return formatToolResponse({ id, chain, length: chain.length });
2305
+ },
2306
+ list_decisions: async (ctx, args) => {
2307
+ const status = args.status === undefined
2308
+ ? undefined
2309
+ : validateWithSchema(args.status, z.enum(['proposed', 'accepted', 'superseded', 'rejected']), 'Invalid status');
2310
+ const sourceSessionId = args.sourceSessionId === undefined
2311
+ ? undefined
2312
+ : validateWithSchema(args.sourceSessionId, z.string().min(1), 'Invalid sourceSessionId');
2313
+ const sourceProjectId = args.sourceProjectId === undefined
2314
+ ? undefined
2315
+ : validateWithSchema(args.sourceProjectId, z.string().min(1), 'Invalid sourceProjectId');
2316
+ const limit = args.limit === undefined
2317
+ ? undefined
2318
+ : validateWithSchema(args.limit, z.number().int().min(1), 'Invalid limit');
2319
+ const records = await ctx.decisionManager.list({ status, sourceSessionId, sourceProjectId, limit });
2320
+ return formatToolResponse({ records, count: records.length });
2321
+ },
2322
+ get_decision: async (ctx, args) => {
2323
+ const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
2324
+ const record = ctx.decisionManager.get(id);
2325
+ return formatToolResponse({ id, record: record ?? null });
2326
+ },
2327
+ export_decision_as_adr_markdown: async (ctx, args) => {
2328
+ const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
2329
+ const markdown = ctx.decisionManager.exportAsAdrMarkdown(id);
2330
+ return formatTextResponse(markdown);
2331
+ },
2332
+ parse_adr_markdown: async (_ctx, args) => {
2333
+ const text = validateWithSchema(args.text, z.string().min(1), 'Invalid text');
2334
+ const parsed = DecisionManager.parseAdrMarkdown(text);
2335
+ return formatToolResponse({ parsed });
2336
+ },
2337
+ // ==================== v2.1.0 do_not_remember (Exclusion) ====================
2338
+ add_exclusion_rule: async (ctx, args) => {
2339
+ const pattern = validateWithSchema(args.pattern, z.string().min(1), 'Invalid pattern');
2340
+ const scope = args.scope === undefined
2341
+ ? undefined
2342
+ : validateWithSchema(args.scope, z.enum(['future-only', 'past-only', 'both']), 'Invalid scope');
2343
+ const entityType = args.entityType === undefined
2344
+ ? undefined
2345
+ : validateWithSchema(args.entityType, z.string().min(1), 'Invalid entityType');
2346
+ const reason = args.reason === undefined
2347
+ ? undefined
2348
+ : validateWithSchema(args.reason, z.string().min(1), 'Invalid reason');
2349
+ const rule = await ctx.exclusionManager.add({ pattern, scope, entityType, reason });
2350
+ return formatToolResponse({ rule });
2351
+ },
2352
+ list_exclusion_rules: async (ctx) => {
2353
+ const rules = await ctx.exclusionManager.list();
2354
+ return formatToolResponse({ rules, count: rules.length });
2355
+ },
2356
+ remove_exclusion_rule: async (ctx, args) => {
2357
+ const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
2358
+ const removed = await ctx.exclusionManager.remove(id);
2359
+ return formatToolResponse({ id, removed });
2360
+ },
2361
+ check_exclusion: async (ctx, args) => {
2362
+ const content = validateWithSchema(args.content, z.string(), 'Invalid content');
2363
+ const entityType = args.entityType === undefined
2364
+ ? undefined
2365
+ : validateWithSchema(args.entityType, z.string().min(1), 'Invalid entityType');
2366
+ const verdict = await ctx.exclusionManager.check(content, entityType);
2367
+ return formatToolResponse(verdict);
2368
+ },
2369
+ find_matching_memories_for_rule: async (ctx, args) => {
2370
+ const pattern = validateWithSchema(args.pattern, z.string().min(1), 'Invalid pattern');
2371
+ const entityType = args.entityType === undefined
2372
+ ? undefined
2373
+ : validateWithSchema(args.entityType, z.string().min(1), 'Invalid entityType');
2374
+ const matches = await ctx.exclusionManager.findMatchingMemories({ pattern, entityType });
2375
+ return formatToolResponse({
2376
+ pattern,
2377
+ entityType,
2378
+ matches: matches.map((m) => ({ name: m.name, entityType: m.entityType })),
2379
+ count: matches.length,
2380
+ });
2381
+ },
2382
+ // ==================== v2.1.0 OBSERVATION DEDUP ====================
2383
+ find_duplicate_observations: async (ctx, args) => {
2384
+ const filter = parseObservationDedupFilter(args);
2385
+ const groups = await ctx.observationDedupManager.findDuplicateObservations(filter);
2386
+ return formatToolResponse({ filter, groups, count: groups.length });
2387
+ },
2388
+ find_jaccard_duplicate_observations: async (ctx, args) => {
2389
+ const filter = parseObservationDedupFilter(args);
2390
+ const groups = await ctx.observationDedupManager.findJaccardDuplicates(filter);
2391
+ return formatToolResponse({ filter, groups, count: groups.length });
2392
+ },
2393
+ // ==================== v2.1.0 SPELL CORRECTION ====================
2394
+ spell_suggest: async (ctx, args) => {
2395
+ const query = validateWithSchema(args.query, z.string().min(1), 'Invalid query');
2396
+ const limit = args.limit === undefined
2397
+ ? undefined
2398
+ : validateWithSchema(args.limit, z.number().int().min(1), 'Invalid limit');
2399
+ const minScore = args.minScore === undefined
2400
+ ? undefined
2401
+ : validateWithSchema(args.minScore, z.number().min(0).max(1), 'Invalid minScore');
2402
+ const maxDistance = args.maxDistance === undefined
2403
+ ? undefined
2404
+ : validateWithSchema(args.maxDistance, z.number().min(0), 'Invalid maxDistance');
2405
+ const suggestions = await ctx.spellChecker.suggest(query, { limit, minScore, maxDistance });
2406
+ return formatToolResponse({ query, suggestions, count: suggestions.length });
2407
+ },
2408
+ spell_rebuild_vocabulary: async (ctx) => {
2409
+ await ctx.spellChecker.rebuild();
2410
+ return formatToolResponse({ rebuilt: true, vocabularySize: ctx.spellChecker.vocabularySize() });
2411
+ },
2412
+ spell_vocabulary_size: async (ctx) => {
2413
+ const size = ctx.spellChecker.vocabularySize();
2414
+ return formatToolResponse({ vocabularySize: size });
2415
+ },
2416
+ // ==================== ENGINEERING / DIAGNOSTIC TOOLS (v12.5.0) ====================
2417
+ // Parallel to the `memory diag` / `memory check` / etc. CLI surface in memoryjs v2.2+.
2418
+ // Useful when the MCP server is up but the graph state is suspect.
2419
+ diag: async (ctx) => {
2420
+ const storagePath = getStorageFilePath(ctx);
2421
+ let exists = false;
2422
+ let sizeBytes = 0;
2423
+ try {
2424
+ const stat = await fs.stat(storagePath);
2425
+ exists = true;
2426
+ sizeBytes = stat.size;
2427
+ }
2428
+ catch { /* file may not exist yet */ }
2429
+ const stats = await ctx.analyticsManager.getGraphStats();
2430
+ return formatToolResponse({
2431
+ runtime: { node: process.version, platform: process.platform, arch: process.arch, pid: process.pid },
2432
+ storage: {
2433
+ path: storagePath,
2434
+ type: process.env.MEMORY_STORAGE_TYPE ?? 'jsonl',
2435
+ exists,
2436
+ sizeBytes,
2437
+ entities: stats.totalEntities,
2438
+ relations: stats.totalRelations,
2439
+ },
2440
+ loadedAt: new Date().toISOString(),
2441
+ });
2442
+ },
2443
+ health: async (ctx) => {
2444
+ const checks = [];
2445
+ const t1 = performance.now();
2446
+ let graph;
2447
+ try {
2448
+ graph = await ctx.storage.loadGraph();
2449
+ checks.push({ name: 'storage:loadGraph', ok: true, durationMs: performance.now() - t1 });
2450
+ }
2451
+ catch (e) {
2452
+ checks.push({
2453
+ name: 'storage:loadGraph',
2454
+ ok: false,
2455
+ durationMs: performance.now() - t1,
2456
+ detail: e instanceof Error ? e.message : String(e),
2457
+ });
2458
+ return formatToolResponse({ ok: false, failed: 1, totalChecks: 1, totalMs: performance.now() - t1, checks });
2459
+ }
2460
+ const t2 = performance.now();
2461
+ const names = new Set();
2462
+ const dupes = [];
2463
+ for (const e of graph.entities) {
2464
+ if (names.has(e.name))
2465
+ dupes.push(e.name);
2466
+ else
2467
+ names.add(e.name);
2468
+ }
2469
+ checks.push({
2470
+ name: 'entities:distinct-names',
2471
+ ok: dupes.length === 0,
2472
+ durationMs: performance.now() - t2,
2473
+ detail: dupes.length === 0 ? undefined : `${dupes.length} duplicate(s)`,
2474
+ });
2475
+ const t3 = performance.now();
2476
+ const orphans = [];
2477
+ for (const r of graph.relations) {
2478
+ if (!names.has(r.from) || !names.has(r.to)) {
2479
+ orphans.push(`${r.from} → ${r.to}`);
2480
+ }
2481
+ }
2482
+ checks.push({
2483
+ name: 'relations:no-orphans',
2484
+ ok: orphans.length === 0,
2485
+ durationMs: performance.now() - t3,
2486
+ detail: orphans.length === 0 ? undefined : `${orphans.length} orphan(s)`,
2487
+ });
2488
+ const t4 = performance.now();
2489
+ const byName = new Map();
2490
+ for (const e of graph.entities)
2491
+ byName.set(e.name, e);
2492
+ const cycleIssues = [];
2493
+ for (const e of graph.entities) {
2494
+ if (!e.parentId)
2495
+ continue;
2496
+ if (!byName.has(e.parentId)) {
2497
+ cycleIssues.push(`${e.name}.parentId='${e.parentId}' missing`);
2498
+ continue;
2499
+ }
2500
+ const visited = new Set();
2501
+ let cur = byName.get(e.parentId);
2502
+ while (cur && cur.parentId) {
2503
+ if (visited.has(cur.name)) {
2504
+ cycleIssues.push(`cycle through ${cur.name}`);
2505
+ break;
2506
+ }
2507
+ visited.add(cur.name);
2508
+ cur = byName.get(cur.parentId);
2509
+ }
2510
+ }
2511
+ checks.push({
2512
+ name: 'hierarchy:no-cycles-no-missing-parents',
2513
+ ok: cycleIssues.length === 0,
2514
+ durationMs: performance.now() - t4,
2515
+ detail: cycleIssues.length === 0 ? undefined : cycleIssues.slice(0, 3).join('; '),
2516
+ });
2517
+ const failed = checks.filter((c) => !c.ok).length;
2518
+ return formatToolResponse({
2519
+ ok: failed === 0,
2520
+ failed,
2521
+ totalChecks: checks.length,
2522
+ totalMs: Number(checks.reduce((a, c) => a + c.durationMs, 0).toFixed(2)),
2523
+ checks,
2524
+ });
2525
+ },
2526
+ check_graph: async (ctx, args) => {
2527
+ const apply = args.apply === undefined
2528
+ ? false
2529
+ : validateWithSchema(args.apply, z.boolean(), 'Invalid apply');
2530
+ const graph = await ctx.storage.loadGraph();
2531
+ const names = new Set(graph.entities.map((e) => e.name));
2532
+ const orphans = [];
2533
+ for (const r of graph.relations) {
2534
+ const fromMissing = !names.has(r.from);
2535
+ const toMissing = !names.has(r.to);
2536
+ if (fromMissing || toMissing) {
2537
+ orphans.push({
2538
+ from: r.from, to: r.to, relationType: r.relationType,
2539
+ reason: fromMissing && toMissing ? 'both-missing' : fromMissing ? 'from-missing' : 'to-missing',
2540
+ });
2541
+ }
2542
+ }
2543
+ const byName = new Map();
2544
+ for (const e of graph.entities)
2545
+ byName.set(e.name, { name: e.name, parentId: e.parentId });
2546
+ const missing = [];
2547
+ const cycles = [];
2548
+ for (const e of graph.entities) {
2549
+ if (!e.parentId)
2550
+ continue;
2551
+ if (!byName.has(e.parentId)) {
2552
+ missing.push({ entity: e.name, parentId: e.parentId });
2553
+ continue;
2554
+ }
2555
+ const visited = new Set([e.name]);
2556
+ let cur = byName.get(e.parentId);
2557
+ while (cur && cur.parentId) {
2558
+ if (visited.has(cur.name)) {
2559
+ cycles.push({ entityInCycle: e.name, cycleThrough: cur.name });
2560
+ break;
2561
+ }
2562
+ visited.add(cur.name);
2563
+ cur = byName.get(cur.parentId);
2564
+ }
2565
+ }
2566
+ const ok = orphans.length === 0 && missing.length === 0 && cycles.length === 0;
2567
+ const result = {
2568
+ ok, applied: apply, orphanRelations: orphans, missingParents: missing, hierarchyCycles: cycles,
2569
+ };
2570
+ if (apply && (orphans.length > 0 || missing.length > 0)) {
2571
+ let deleted = 0;
2572
+ let cleared = 0;
2573
+ if (orphans.length > 0) {
2574
+ await ctx.relationManager.deleteRelations(orphans.map((o) => ({ from: o.from, to: o.to, relationType: o.relationType })));
2575
+ deleted = orphans.length;
2576
+ }
2577
+ for (const m of missing) {
2578
+ try {
2579
+ await ctx.hierarchyManager.setEntityParent(m.entity, null);
2580
+ cleared += 1;
2581
+ }
2582
+ catch { /* skip; entity may have vanished */ }
2583
+ }
2584
+ result.actions = { orphanRelationsDeleted: deleted, missingParentsCleared: cleared };
2585
+ }
2586
+ return formatToolResponse(result);
2587
+ },
2588
+ reindex: async (ctx, args) => {
2589
+ const ranked = args.ranked === undefined
2590
+ ? true
2591
+ : validateWithSchema(args.ranked, z.boolean(), 'Invalid ranked');
2592
+ const spell = args.spell === undefined
2593
+ ? true
2594
+ : validateWithSchema(args.spell, z.boolean(), 'Invalid spell');
2595
+ if (!ranked && !spell) {
2596
+ return formatToolResponse({ ok: true, failed: 0, result: {} });
2597
+ }
2598
+ const result = {};
2599
+ if (ranked) {
2600
+ const t = performance.now();
2601
+ try {
2602
+ // Same workaround as the CLI: default ctx.rankedSearch is constructed
2603
+ // without a storageDir so buildIndex() refuses. Construct an ad-hoc one
2604
+ // alongside the JSONL.
2605
+ const storageDir = path.dirname(getStorageFilePath(ctx));
2606
+ const r = new RankedSearch(ctx.storage, storageDir);
2607
+ await r.buildIndex();
2608
+ result.ranked = { ok: true, durationMs: performance.now() - t };
2609
+ }
2610
+ catch (e) {
2611
+ result.ranked = {
2612
+ ok: false,
2613
+ durationMs: performance.now() - t,
2614
+ detail: e instanceof Error ? e.message : String(e),
2615
+ };
2616
+ }
2617
+ }
2618
+ if (spell) {
2619
+ const t = performance.now();
2620
+ try {
2621
+ await ctx.spellChecker.rebuild();
2622
+ result.spell = { ok: true, durationMs: performance.now() - t };
2623
+ }
2624
+ catch (e) {
2625
+ result.spell = {
2626
+ ok: false,
2627
+ durationMs: performance.now() - t,
2628
+ detail: e instanceof Error ? e.message : String(e),
2629
+ };
2630
+ }
2631
+ }
2632
+ const failed = Object.values(result).filter((r) => !r.ok).length;
2633
+ return formatToolResponse({ ok: failed === 0, failed, result });
2634
+ },
2635
+ cache_stats: async (_ctx) => {
2636
+ return formatToolResponse({ stats: getAllCacheStats() });
2637
+ },
2638
+ cache_clear: async (_ctx) => {
2639
+ clearAllSearchCaches();
2640
+ return formatToolResponse({ cleared: true, caches: ['basic', 'ranked', 'boolean', 'fuzzy'] });
2641
+ },
2642
+ graph_size: async (ctx) => {
2643
+ const storagePath = getStorageFilePath(ctx);
2644
+ const graph = await ctx.storage.loadGraph();
2645
+ let observationCount = 0;
2646
+ const tagSet = new Set();
2647
+ for (const e of graph.entities) {
2648
+ observationCount += e.observations.length;
2649
+ if (e.tags)
2650
+ for (const t of e.tags)
2651
+ tagSet.add(t);
2652
+ }
2653
+ let exists = false;
2654
+ let sizeBytes = 0;
2655
+ let lineCount = 0;
2656
+ try {
2657
+ const stat = await fs.stat(storagePath);
2658
+ exists = true;
2659
+ sizeBytes = stat.size;
2660
+ if (sizeBytes > 0) {
2661
+ const content = await fs.readFile(storagePath, 'utf8');
2662
+ lineCount = content.split('\n').filter((l) => l.length > 0).length;
2663
+ }
2664
+ }
2665
+ catch { /* file may not exist */ }
2666
+ return formatToolResponse({
2667
+ graph: {
2668
+ entities: graph.entities.length,
2669
+ relations: graph.relations.length,
2670
+ observations: observationCount,
2671
+ distinctTags: tagSet.size,
2672
+ avgObservationsPerEntity: graph.entities.length === 0
2673
+ ? 0
2674
+ : Number((observationCount / graph.entities.length).toFixed(2)),
2675
+ },
2676
+ storage: { path: storagePath, exists, sizeBytes, lineCount },
2677
+ });
2678
+ },
2679
+ inspect_entity: async (ctx, args) => {
2680
+ const name = validateWithSchema(args.name, z.string().min(1), 'Invalid name');
2681
+ // getEntityByName reads an in-memory nameIndex hydrated by loadGraph.
2682
+ const graph = await ctx.storage.loadGraph();
2683
+ const entity = ctx.storage.getEntityByName(name);
2684
+ if (!entity)
2685
+ throw new Error(`entity not found: ${name}`);
2686
+ const observations = await ctx.observationManager.getObservationsFor(name);
2687
+ const outgoing = graph.relations
2688
+ .filter((r) => r.from === name)
2689
+ .map((r) => ({ to: r.to, type: r.relationType }));
2690
+ const incoming = graph.relations
2691
+ .filter((r) => r.to === name)
2692
+ .map((r) => ({ from: r.from, type: r.relationType }));
2693
+ const children = (await ctx.hierarchyManager.getChildren(name)).map((c) => c.name);
2694
+ const ancestors = (await ctx.hierarchyManager.getAncestors(name)).map((a) => a.name);
2695
+ return formatToolResponse({
2696
+ name: entity.name,
2697
+ entityType: entity.entityType,
2698
+ observations,
2699
+ tags: entity.tags,
2700
+ importance: entity.importance,
2701
+ createdAt: entity.createdAt,
2702
+ lastModified: entity.lastModified,
2703
+ parentId: entity.parentId,
2704
+ children,
2705
+ ancestors,
2706
+ relations: { outgoing, incoming },
2707
+ });
2708
+ },
2709
+ hierarchy_tree: async (ctx, args) => {
2710
+ const root = args.root === undefined
2711
+ ? undefined
2712
+ : validateWithSchema(args.root, z.string().min(1), 'Invalid root');
2713
+ await ctx.storage.loadGraph();
2714
+ async function walk(n) {
2715
+ const e = ctx.storage.getEntityByName(n);
2716
+ const kids = await ctx.hierarchyManager.getChildren(n);
2717
+ const childNodes = [];
2718
+ for (const c of kids)
2719
+ childNodes.push(await walk(c.name));
2720
+ return { name: n, entityType: e?.entityType ?? 'unknown', children: childNodes };
2721
+ }
2722
+ if (root) {
2723
+ const e = ctx.storage.getEntityByName(root);
2724
+ if (!e)
2725
+ throw new Error(`entity not found: ${root}`);
2726
+ return formatToolResponse({ trees: [await walk(root)] });
2727
+ }
2728
+ const roots = await ctx.hierarchyManager.getRootEntities();
2729
+ const trees = await Promise.all(roots.map((r) => walk(r.name)));
2730
+ return formatToolResponse({ trees });
2731
+ },
2732
+ entity_neighbors: async (ctx, args) => {
2733
+ const name = validateWithSchema(args.name, z.string().min(1), 'Invalid name');
2734
+ const graph = await ctx.storage.loadGraph();
2735
+ const entity = ctx.storage.getEntityByName(name);
2736
+ if (!entity)
2737
+ throw new Error(`entity not found: ${name}`);
2738
+ const outgoing = graph.relations.filter((r) => r.from === name).map((r) => ({ to: r.to, type: r.relationType }));
2739
+ const incoming = graph.relations.filter((r) => r.to === name).map((r) => ({ from: r.from, type: r.relationType }));
2740
+ return formatToolResponse({
2741
+ entity: name,
2742
+ outgoing,
2743
+ incoming,
2744
+ outDegree: outgoing.length,
2745
+ inDegree: incoming.length,
2746
+ });
2747
+ },
1900
2748
  };
1901
2749
  /**
1902
2750
  * Handle a tool call by dispatching to the appropriate handler.