mcp-adr-analysis-server 2.0.21 → 2.0.24

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 (119) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/src/index.d.ts +9 -0
  3. package/dist/src/index.d.ts.map +1 -1
  4. package/dist/src/index.js +1380 -781
  5. package/dist/src/index.js.map +1 -1
  6. package/dist/src/tools/adr-suggestion-tool.d.ts.map +1 -1
  7. package/dist/src/tools/adr-suggestion-tool.js +32 -30
  8. package/dist/src/tools/adr-suggestion-tool.js.map +1 -1
  9. package/dist/src/tools/content-masking-tool.d.ts +4 -0
  10. package/dist/src/tools/content-masking-tool.d.ts.map +1 -1
  11. package/dist/src/tools/content-masking-tool.js +531 -28
  12. package/dist/src/tools/content-masking-tool.js.map +1 -1
  13. package/dist/src/tools/deployment-readiness-tool.d.ts +8 -5
  14. package/dist/src/tools/deployment-readiness-tool.d.ts.map +1 -1
  15. package/dist/src/tools/deployment-readiness-tool.js +741 -79
  16. package/dist/src/tools/deployment-readiness-tool.js.map +1 -1
  17. package/dist/src/tools/environment-analysis-tool.d.ts +4 -0
  18. package/dist/src/tools/environment-analysis-tool.d.ts.map +1 -1
  19. package/dist/src/tools/environment-analysis-tool.js +568 -24
  20. package/dist/src/tools/environment-analysis-tool.js.map +1 -1
  21. package/dist/src/tools/mcp-planning-tool.d.ts.map +1 -1
  22. package/dist/src/tools/mcp-planning-tool.js +298 -173
  23. package/dist/src/tools/mcp-planning-tool.js.map +1 -1
  24. package/dist/src/tools/memory-loading-tool.d.ts +60 -0
  25. package/dist/src/tools/memory-loading-tool.d.ts.map +1 -0
  26. package/dist/src/tools/memory-loading-tool.js +457 -0
  27. package/dist/src/tools/memory-loading-tool.js.map +1 -0
  28. package/dist/src/tools/smart-git-push-tool.d.ts +3 -0
  29. package/dist/src/tools/smart-git-push-tool.d.ts.map +1 -1
  30. package/dist/src/tools/smart-git-push-tool.js +177 -223
  31. package/dist/src/tools/smart-git-push-tool.js.map +1 -1
  32. package/dist/src/tools/troubleshoot-guided-workflow-tool.d.ts +20 -11
  33. package/dist/src/tools/troubleshoot-guided-workflow-tool.d.ts.map +1 -1
  34. package/dist/src/tools/troubleshoot-guided-workflow-tool.js +506 -22
  35. package/dist/src/tools/troubleshoot-guided-workflow-tool.js.map +1 -1
  36. package/dist/src/types/enhanced-errors.d.ts +173 -0
  37. package/dist/src/types/enhanced-errors.d.ts.map +1 -0
  38. package/dist/src/types/enhanced-errors.js +668 -0
  39. package/dist/src/types/enhanced-errors.js.map +1 -0
  40. package/dist/src/types/knowledge-graph-schemas.d.ts +92 -0
  41. package/dist/src/types/knowledge-graph-schemas.d.ts.map +1 -1
  42. package/dist/src/types/knowledge-graph-schemas.js +45 -17
  43. package/dist/src/types/knowledge-graph-schemas.js.map +1 -1
  44. package/dist/src/types/memory-entities.d.ts +7209 -0
  45. package/dist/src/types/memory-entities.d.ts.map +1 -0
  46. package/dist/src/types/memory-entities.js +395 -0
  47. package/dist/src/types/memory-entities.js.map +1 -0
  48. package/dist/src/types/todo-json-schemas.d.ts +7 -653
  49. package/dist/src/types/todo-json-schemas.d.ts.map +1 -1
  50. package/dist/src/types/todo-json-schemas.js +6 -110
  51. package/dist/src/types/todo-json-schemas.js.map +1 -1
  52. package/dist/src/utils/enhanced-logging.d.ts +195 -0
  53. package/dist/src/utils/enhanced-logging.d.ts.map +1 -0
  54. package/dist/src/utils/enhanced-logging.js +449 -0
  55. package/dist/src/utils/enhanced-logging.js.map +1 -0
  56. package/dist/src/utils/interactive-approval.d.ts +1 -1
  57. package/dist/src/utils/interactive-approval.d.ts.map +1 -1
  58. package/dist/src/utils/interactive-approval.js +20 -16
  59. package/dist/src/utils/interactive-approval.js.map +1 -1
  60. package/dist/src/utils/knowledge-graph-manager.d.ts +23 -1
  61. package/dist/src/utils/knowledge-graph-manager.d.ts.map +1 -1
  62. package/dist/src/utils/knowledge-graph-manager.js +297 -69
  63. package/dist/src/utils/knowledge-graph-manager.js.map +1 -1
  64. package/dist/src/utils/memory-entity-manager.d.ts +134 -0
  65. package/dist/src/utils/memory-entity-manager.d.ts.map +1 -0
  66. package/dist/src/utils/memory-entity-manager.js +848 -0
  67. package/dist/src/utils/memory-entity-manager.js.map +1 -0
  68. package/dist/src/utils/memory-health-scoring.d.ts +125 -0
  69. package/dist/src/utils/memory-health-scoring.d.ts.map +1 -0
  70. package/dist/src/utils/memory-health-scoring.js +277 -0
  71. package/dist/src/utils/memory-health-scoring.js.map +1 -0
  72. package/dist/src/utils/memory-migration-manager.d.ts +93 -0
  73. package/dist/src/utils/memory-migration-manager.d.ts.map +1 -0
  74. package/dist/src/utils/memory-migration-manager.js +834 -0
  75. package/dist/src/utils/memory-migration-manager.js.map +1 -0
  76. package/dist/src/utils/memory-relationship-mapper.d.ts +87 -0
  77. package/dist/src/utils/memory-relationship-mapper.d.ts.map +1 -0
  78. package/dist/src/utils/memory-relationship-mapper.js +481 -0
  79. package/dist/src/utils/memory-relationship-mapper.js.map +1 -0
  80. package/dist/src/utils/memory-rollback-manager.d.ts +93 -0
  81. package/dist/src/utils/memory-rollback-manager.d.ts.map +1 -0
  82. package/dist/src/utils/memory-rollback-manager.js +542 -0
  83. package/dist/src/utils/memory-rollback-manager.js.map +1 -0
  84. package/dist/src/utils/memory-transformation.d.ts +59 -0
  85. package/dist/src/utils/memory-transformation.d.ts.map +1 -0
  86. package/dist/src/utils/memory-transformation.js +742 -0
  87. package/dist/src/utils/memory-transformation.js.map +1 -0
  88. package/dist/src/utils/prompt-execution.d.ts.map +1 -1
  89. package/dist/src/utils/prompt-execution.js +72 -51
  90. package/dist/src/utils/prompt-execution.js.map +1 -1
  91. package/dist/src/utils/todo-file-watcher.d.ts +11 -23
  92. package/dist/src/utils/todo-file-watcher.d.ts.map +1 -1
  93. package/dist/src/utils/todo-file-watcher.js +18 -156
  94. package/dist/src/utils/todo-file-watcher.js.map +1 -1
  95. package/package.json +22 -6
  96. package/dist/src/tools/smart-score-tool.d.ts +0 -173
  97. package/dist/src/tools/smart-score-tool.d.ts.map +0 -1
  98. package/dist/src/tools/smart-score-tool.js +0 -681
  99. package/dist/src/tools/smart-score-tool.js.map +0 -1
  100. package/dist/src/tools/todo-management-tool-v2.d.ts +0 -368
  101. package/dist/src/tools/todo-management-tool-v2.d.ts.map +0 -1
  102. package/dist/src/tools/todo-management-tool-v2.js +0 -922
  103. package/dist/src/tools/todo-management-tool-v2.js.map +0 -1
  104. package/dist/src/utils/project-health-scoring.d.ts +0 -177
  105. package/dist/src/utils/project-health-scoring.d.ts.map +0 -1
  106. package/dist/src/utils/project-health-scoring.js +0 -370
  107. package/dist/src/utils/project-health-scoring.js.map +0 -1
  108. package/dist/src/utils/todo-json-manager.d.ts +0 -71
  109. package/dist/src/utils/todo-json-manager.d.ts.map +0 -1
  110. package/dist/src/utils/todo-json-manager.js +0 -396
  111. package/dist/src/utils/todo-json-manager.js.map +0 -1
  112. package/dist/src/utils/todo-markdown-converter.d.ts +0 -16
  113. package/dist/src/utils/todo-markdown-converter.d.ts.map +0 -1
  114. package/dist/src/utils/todo-markdown-converter.js +0 -362
  115. package/dist/src/utils/todo-markdown-converter.js.map +0 -1
  116. package/dist/src/utils/todo-migration.d.ts +0 -47
  117. package/dist/src/utils/todo-migration.d.ts.map +0 -1
  118. package/dist/src/utils/todo-migration.js +0 -261
  119. package/dist/src/utils/todo-migration.js.map +0 -1
@@ -0,0 +1,848 @@
1
+ /**
2
+ * Memory Entity Manager
3
+ *
4
+ * Core manager for the memory-centric architecture that handles storage, retrieval,
5
+ * relationship management, and intelligence operations for memory entities.
6
+ */
7
+ import * as fs from 'fs/promises';
8
+ import * as path from 'path';
9
+ import crypto from 'crypto';
10
+ import { MemoryEntitySchema, } from '../types/memory-entities.js';
11
+ import { McpAdrError } from '../types/index.js';
12
+ import { loadConfig } from './config.js';
13
+ import { EnhancedLogger } from './enhanced-logging.js';
14
+ export class MemoryEntityManager {
15
+ fsOverride;
16
+ memoryDir;
17
+ entitiesFile;
18
+ relationshipsFile;
19
+ intelligenceFile;
20
+ evolutionLogFile;
21
+ config;
22
+ logger;
23
+ isTestMode;
24
+ // In-memory caches for performance
25
+ entitiesCache = new Map();
26
+ relationshipsCache = new Map();
27
+ intelligence = null;
28
+ lastSnapshotTime = 0;
29
+ constructor(config, testMode = false, fsOverride) {
30
+ this.fsOverride = fsOverride;
31
+ this.isTestMode = testMode;
32
+ const projectConfig = loadConfig();
33
+ this.logger = new EnhancedLogger();
34
+ this.memoryDir = path.join(projectConfig.projectPath, '.mcp-adr-memory');
35
+ this.entitiesFile = path.join(this.memoryDir, 'entities.json');
36
+ this.relationshipsFile = path.join(this.memoryDir, 'relationships.json');
37
+ this.intelligenceFile = path.join(this.memoryDir, 'intelligence.json');
38
+ this.evolutionLogFile = path.join(this.memoryDir, 'evolution-log.json');
39
+ this.config = {
40
+ storageType: 'file',
41
+ snapshotFrequency: testMode ? 0 : 30, // No automatic persistence in test mode
42
+ compressionEnabled: true,
43
+ backupRetention: 30, // 30 days
44
+ syncEnabled: !testMode, // Disable sync in test mode
45
+ conflictResolution: 'merge',
46
+ ...config,
47
+ };
48
+ }
49
+ /**
50
+ * Initialize the memory system
51
+ */
52
+ async initialize() {
53
+ try {
54
+ await this.ensureMemoryDirectory();
55
+ await this.loadFromPersistence();
56
+ await this.initializeIntelligence();
57
+ this.logger.info('Memory Entity Manager initialized successfully', 'MemoryEntityManager', {
58
+ entityCount: this.entitiesCache.size,
59
+ relationshipCount: this.relationshipsCache.size,
60
+ });
61
+ }
62
+ catch (error) {
63
+ throw new McpAdrError(`Failed to initialize memory system: ${error instanceof Error ? error.message : String(error)}`, 'MEMORY_INIT_ERROR');
64
+ }
65
+ }
66
+ /**
67
+ * Create or update a memory entity
68
+ */
69
+ async upsertEntity(entity) {
70
+ try {
71
+ const now = new Date().toISOString();
72
+ const id = entity.id || crypto.randomUUID();
73
+ // Get existing entity if updating
74
+ const existing = entity.id ? this.entitiesCache.get(entity.id) : null;
75
+ // Create full entity with defaults
76
+ const fullEntity = {
77
+ id,
78
+ created: existing?.created || now,
79
+ lastModified: now,
80
+ version: (existing?.version || 0) + 1,
81
+ confidence: entity.confidence || 0.85,
82
+ relevance: entity.relevance || 0.7,
83
+ tags: entity.tags || [],
84
+ relationships: entity.relationships || existing?.relationships || [],
85
+ context: {
86
+ projectPhase: entity.context?.projectPhase,
87
+ businessDomain: entity.context?.businessDomain,
88
+ technicalStack: entity.context?.technicalStack || [],
89
+ environmentalFactors: entity.context?.environmentalFactors || [],
90
+ stakeholders: entity.context?.stakeholders || [],
91
+ },
92
+ accessPattern: {
93
+ lastAccessed: now,
94
+ accessCount: (existing?.accessPattern?.accessCount || 0) + 1,
95
+ accessContext: entity.accessPattern?.accessContext || [],
96
+ },
97
+ evolution: {
98
+ origin: entity.evolution?.origin || (existing ? existing.evolution.origin : 'created'),
99
+ transformations: [
100
+ ...(existing?.evolution?.transformations || []),
101
+ {
102
+ timestamp: now,
103
+ type: existing ? 'updated' : 'created',
104
+ description: existing
105
+ ? `Updated ${entity.type} entity`
106
+ : `Created ${entity.type} entity`,
107
+ agent: 'MemoryEntityManager',
108
+ },
109
+ ],
110
+ },
111
+ validation: {
112
+ isVerified: entity.validation?.isVerified || false,
113
+ verificationMethod: entity.validation?.verificationMethod,
114
+ verificationTimestamp: entity.validation?.verificationTimestamp,
115
+ conflictResolution: entity.validation?.conflictResolution,
116
+ },
117
+ ...entity,
118
+ };
119
+ // Validate the entity
120
+ try {
121
+ MemoryEntitySchema.parse(fullEntity);
122
+ }
123
+ catch (validationError) {
124
+ throw new McpAdrError(`Entity validation failed: ${validationError instanceof Error ? validationError.message : String(validationError)}`, 'ENTITY_VALIDATION_ERROR');
125
+ }
126
+ // Store in cache
127
+ this.entitiesCache.set(id, fullEntity);
128
+ // Log evolution event
129
+ await this.logEvolutionEvent({
130
+ id: crypto.randomUUID(),
131
+ entityId: id,
132
+ timestamp: now,
133
+ eventType: existing ? 'modified' : 'created',
134
+ agent: 'MemoryEntityManager',
135
+ changes: existing ? this.calculateChanges(existing, fullEntity) : {},
136
+ });
137
+ // Update intelligence
138
+ await this.updateIntelligence('entity_updated', {
139
+ entityId: id,
140
+ entityType: fullEntity.type,
141
+ });
142
+ // Persist if needed
143
+ await this.maybePersist();
144
+ this.logger.debug(`${existing ? 'Updated' : 'Created'} entity`, 'MemoryEntityManager', {
145
+ entityId: id,
146
+ type: fullEntity.type,
147
+ title: fullEntity.title.substring(0, 50),
148
+ });
149
+ return fullEntity;
150
+ }
151
+ catch (error) {
152
+ throw new McpAdrError(`Failed to upsert entity: ${error instanceof Error ? error.message : String(error)}`, 'ENTITY_UPSERT_ERROR');
153
+ }
154
+ }
155
+ /**
156
+ * Get an entity by ID
157
+ */
158
+ async getEntity(id) {
159
+ const entity = this.entitiesCache.get(id);
160
+ if (entity) {
161
+ // Update access pattern
162
+ entity.accessPattern.lastAccessed = new Date().toISOString();
163
+ entity.accessPattern.accessCount += 1;
164
+ await this.logEvolutionEvent({
165
+ id: crypto.randomUUID(),
166
+ entityId: id,
167
+ timestamp: new Date().toISOString(),
168
+ eventType: 'accessed',
169
+ agent: 'MemoryEntityManager',
170
+ });
171
+ }
172
+ return entity || null;
173
+ }
174
+ /**
175
+ * Delete an entity
176
+ */
177
+ async deleteEntity(id) {
178
+ const entity = this.entitiesCache.get(id);
179
+ if (!entity)
180
+ return false;
181
+ // Remove entity
182
+ this.entitiesCache.delete(id);
183
+ // Remove all relationships involving this entity
184
+ const relationshipsToRemove = Array.from(this.relationshipsCache.values()).filter(rel => rel.sourceId === id || rel.targetId === id);
185
+ relationshipsToRemove.forEach(rel => this.relationshipsCache.delete(rel.id));
186
+ // Log evolution event
187
+ await this.logEvolutionEvent({
188
+ id: crypto.randomUUID(),
189
+ entityId: id,
190
+ timestamp: new Date().toISOString(),
191
+ eventType: 'deprecated',
192
+ agent: 'MemoryEntityManager',
193
+ context: 'Entity deleted by user request',
194
+ });
195
+ await this.maybePersist();
196
+ return true;
197
+ }
198
+ /**
199
+ * Create or update a relationship between entities
200
+ */
201
+ async upsertRelationship(relationship) {
202
+ try {
203
+ const now = new Date().toISOString();
204
+ const id = relationship.id || crypto.randomUUID();
205
+ // Validate that both entities exist
206
+ const sourceEntity = this.entitiesCache.get(relationship.sourceId);
207
+ const targetEntity = this.entitiesCache.get(relationship.targetId);
208
+ if (!sourceEntity || !targetEntity) {
209
+ throw new McpAdrError('Source or target entity not found', 'RELATIONSHIP_INVALID_ENTITIES');
210
+ }
211
+ const fullRelationship = {
212
+ id,
213
+ sourceId: relationship.sourceId,
214
+ targetId: relationship.targetId,
215
+ type: relationship.type,
216
+ strength: relationship.strength || 0.7,
217
+ context: relationship.context || '',
218
+ evidence: relationship.evidence || [],
219
+ created: relationship.created || now,
220
+ lastValidated: relationship.lastValidated || now,
221
+ confidence: relationship.confidence || 0.85,
222
+ };
223
+ // Store in cache
224
+ this.relationshipsCache.set(id, fullRelationship);
225
+ // Update entity relationships for both source and target
226
+ this.updateEntityRelationships(sourceEntity, fullRelationship);
227
+ this.updateEntityRelationships(targetEntity, fullRelationship);
228
+ // Log evolution events for both entities
229
+ await Promise.all([
230
+ this.logEvolutionEvent({
231
+ id: crypto.randomUUID(),
232
+ entityId: relationship.sourceId,
233
+ timestamp: now,
234
+ eventType: 'related',
235
+ agent: 'MemoryEntityManager',
236
+ context: `Relationship ${relationship.type} to ${targetEntity.title}`,
237
+ }),
238
+ this.logEvolutionEvent({
239
+ id: crypto.randomUUID(),
240
+ entityId: relationship.targetId,
241
+ timestamp: now,
242
+ eventType: 'related',
243
+ agent: 'MemoryEntityManager',
244
+ context: `Relationship ${relationship.type} from ${sourceEntity.title}`,
245
+ }),
246
+ ]);
247
+ await this.maybePersist();
248
+ return fullRelationship;
249
+ }
250
+ catch (error) {
251
+ throw new McpAdrError(`Failed to create relationship: ${error instanceof Error ? error.message : String(error)}`, 'RELATIONSHIP_CREATE_ERROR');
252
+ }
253
+ }
254
+ /**
255
+ * Query entities with advanced filtering
256
+ */
257
+ async queryEntities(query) {
258
+ const startTime = Date.now();
259
+ try {
260
+ let entities = Array.from(this.entitiesCache.values());
261
+ const relationships = Array.from(this.relationshipsCache.values());
262
+ // Apply filters
263
+ if (query.entityTypes?.length) {
264
+ entities = entities.filter(e => query.entityTypes.includes(e.type));
265
+ }
266
+ if (query.tags?.length) {
267
+ entities = entities.filter(e => query.tags.some(tag => e.tags.includes(tag)));
268
+ }
269
+ if (query.textQuery) {
270
+ const searchTerm = query.textQuery.toLowerCase();
271
+ entities = entities.filter(e => e.title.toLowerCase().includes(searchTerm) ||
272
+ e.description.toLowerCase().includes(searchTerm) ||
273
+ e.tags.some(tag => tag.toLowerCase().includes(searchTerm)));
274
+ }
275
+ if (query.confidenceThreshold !== undefined) {
276
+ entities = entities.filter(e => e.confidence >= query.confidenceThreshold);
277
+ }
278
+ if (query.relevanceThreshold !== undefined) {
279
+ entities = entities.filter(e => e.relevance >= query.relevanceThreshold);
280
+ }
281
+ if (query.timeRange) {
282
+ const fromTime = new Date(query.timeRange.from).getTime();
283
+ const toTime = new Date(query.timeRange.to).getTime();
284
+ entities = entities.filter(e => {
285
+ const entityTime = new Date(e.lastModified).getTime();
286
+ return entityTime >= fromTime && entityTime <= toTime;
287
+ });
288
+ }
289
+ // Apply context filters
290
+ if (query.contextFilters) {
291
+ if (query.contextFilters.projectPhase) {
292
+ entities = entities.filter(e => e.context.projectPhase === query.contextFilters.projectPhase);
293
+ }
294
+ if (query.contextFilters.businessDomain) {
295
+ entities = entities.filter(e => e.context.businessDomain === query.contextFilters.businessDomain);
296
+ }
297
+ if (query.contextFilters.technicalStack?.length) {
298
+ entities = entities.filter(e => query.contextFilters.technicalStack.some(tech => e.context.technicalStack.includes(tech)));
299
+ }
300
+ }
301
+ // Sort entities
302
+ const sortBy = query.sortBy || 'relevance';
303
+ entities.sort((a, b) => {
304
+ switch (sortBy) {
305
+ case 'confidence':
306
+ return b.confidence - a.confidence;
307
+ case 'lastModified':
308
+ return new Date(b.lastModified).getTime() - new Date(a.lastModified).getTime();
309
+ case 'created':
310
+ return new Date(b.created).getTime() - new Date(a.created).getTime();
311
+ case 'accessCount':
312
+ return b.accessPattern.accessCount - a.accessPattern.accessCount;
313
+ case 'relevance':
314
+ default:
315
+ return b.relevance - a.relevance;
316
+ }
317
+ });
318
+ const totalCount = entities.length;
319
+ // Generate aggregations on ALL matching entities before applying limit
320
+ const aggregations = {
321
+ byType: {},
322
+ byTag: {},
323
+ byConfidence: {},
324
+ };
325
+ entities.forEach(entity => {
326
+ // By type
327
+ aggregations.byType[entity.type] = (aggregations.byType[entity.type] || 0) + 1;
328
+ // By tags
329
+ entity.tags.forEach(tag => {
330
+ aggregations.byTag[tag] = (aggregations.byTag[tag] || 0) + 1;
331
+ });
332
+ // By confidence range
333
+ const confidenceRange = Math.floor(entity.confidence * 10) / 10;
334
+ aggregations.byConfidence[`${confidenceRange}-${confidenceRange + 0.1}`] =
335
+ (aggregations.byConfidence[`${confidenceRange}-${confidenceRange + 0.1}`] || 0) + 1;
336
+ });
337
+ // Apply limit AFTER generating aggregations
338
+ if (query.limit && query.limit > 0) {
339
+ entities = entities.slice(0, query.limit);
340
+ }
341
+ // Include related entities if requested
342
+ let resultRelationships = relationships;
343
+ if (query.includeRelated) {
344
+ const entityIds = new Set(entities.map(e => e.id));
345
+ resultRelationships = relationships.filter(r => entityIds.has(r.sourceId) || entityIds.has(r.targetId));
346
+ }
347
+ const queryTime = Date.now() - startTime;
348
+ this.logger.debug('Entity query executed', 'MemoryEntityManager', {
349
+ queryTime,
350
+ totalCount,
351
+ returnedCount: entities.length,
352
+ filters: Object.keys(query).filter(key => query[key] !== undefined),
353
+ });
354
+ return {
355
+ entities,
356
+ relationships: resultRelationships,
357
+ totalCount,
358
+ queryTime,
359
+ aggregations,
360
+ };
361
+ }
362
+ catch (error) {
363
+ throw new McpAdrError(`Failed to query entities: ${error instanceof Error ? error.message : String(error)}`, 'QUERY_ERROR');
364
+ }
365
+ }
366
+ /**
367
+ * Find related entities through relationships
368
+ */
369
+ async findRelatedEntities(entityId, maxDepth = 2, relationshipTypes) {
370
+ const visited = new Set();
371
+ const paths = [];
372
+ const relatedEntities = new Map();
373
+ const traverse = (currentId, currentPath, currentRelationships, depth) => {
374
+ if (depth > maxDepth || visited.has(currentId))
375
+ return;
376
+ visited.add(currentId);
377
+ const currentEntity = this.entitiesCache.get(currentId);
378
+ if (!currentEntity)
379
+ return;
380
+ if (depth > 0) {
381
+ relatedEntities.set(currentId, currentEntity);
382
+ paths.push({
383
+ path: [...currentPath, currentId],
384
+ relationships: [...currentRelationships],
385
+ depth,
386
+ });
387
+ }
388
+ // Find both outgoing and incoming relationships
389
+ const allRels = Array.from(this.relationshipsCache.values()).filter(rel => {
390
+ const isOutgoing = rel.sourceId === currentId;
391
+ const isIncoming = rel.targetId === currentId;
392
+ if (!isOutgoing && !isIncoming)
393
+ return false;
394
+ if (relationshipTypes?.length && !relationshipTypes.includes(rel.type))
395
+ return false;
396
+ return true;
397
+ });
398
+ for (const rel of allRels) {
399
+ const targetId = rel.sourceId === currentId ? rel.targetId : rel.sourceId;
400
+ if (!visited.has(targetId)) {
401
+ traverse(targetId, [...currentPath, currentId], [...currentRelationships, rel], depth + 1);
402
+ }
403
+ }
404
+ };
405
+ traverse(entityId, [], [], 0);
406
+ return {
407
+ entities: Array.from(relatedEntities.values()),
408
+ relationshipPaths: paths,
409
+ };
410
+ }
411
+ /**
412
+ * Get memory intelligence data
413
+ */
414
+ async getIntelligence() {
415
+ if (!this.intelligence) {
416
+ await this.initializeIntelligence();
417
+ }
418
+ return this.intelligence;
419
+ }
420
+ /**
421
+ * Create a memory snapshot
422
+ */
423
+ async createSnapshot() {
424
+ const now = new Date().toISOString();
425
+ const intelligence = await this.getIntelligence();
426
+ const entities = Array.from(this.entitiesCache.values());
427
+ const relationships = Array.from(this.relationshipsCache.values());
428
+ const totalConfidence = entities.reduce((sum, e) => sum + e.confidence, 0);
429
+ const averageConfidence = entities.length > 0 ? totalConfidence / entities.length : 0;
430
+ return {
431
+ id: crypto.randomUUID(),
432
+ timestamp: now,
433
+ version: '1.0.0',
434
+ entities,
435
+ relationships,
436
+ intelligence,
437
+ metadata: {
438
+ totalEntities: entities.length,
439
+ totalRelationships: relationships.length,
440
+ averageConfidence,
441
+ lastOptimization: now,
442
+ },
443
+ };
444
+ }
445
+ // Private helper methods
446
+ async ensureMemoryDirectory() {
447
+ try {
448
+ const fsInstance = this.fsOverride || fs;
449
+ await fsInstance.access(this.memoryDir);
450
+ }
451
+ catch (error) {
452
+ // Directory doesn't exist, create it
453
+ const fsInstance = this.fsOverride || fs;
454
+ await fsInstance.mkdir(this.memoryDir, { recursive: true });
455
+ }
456
+ }
457
+ async loadFromPersistence() {
458
+ // Skip loading from persistence in test mode to avoid conflicts
459
+ if (this.isTestMode) {
460
+ return;
461
+ }
462
+ try {
463
+ // Load entities
464
+ try {
465
+ const fsInstance = this.fsOverride || fs;
466
+ const entitiesData = await fsInstance.readFile(this.entitiesFile, 'utf-8');
467
+ const entities = JSON.parse(entitiesData);
468
+ entities.forEach(entity => {
469
+ this.entitiesCache.set(entity.id, entity);
470
+ });
471
+ }
472
+ catch {
473
+ // No entities file exists yet
474
+ }
475
+ // Load relationships
476
+ try {
477
+ const fsInstance = this.fsOverride || fs;
478
+ const relationshipsData = await fsInstance.readFile(this.relationshipsFile, 'utf-8');
479
+ const relationships = JSON.parse(relationshipsData);
480
+ relationships.forEach(relationship => {
481
+ this.relationshipsCache.set(relationship.id, relationship);
482
+ });
483
+ }
484
+ catch {
485
+ // No relationships file exists yet
486
+ }
487
+ // Load intelligence
488
+ try {
489
+ const fsInstance = this.fsOverride || fs;
490
+ const intelligenceData = await fsInstance.readFile(this.intelligenceFile, 'utf-8');
491
+ this.intelligence = JSON.parse(intelligenceData);
492
+ }
493
+ catch {
494
+ // Will initialize default intelligence
495
+ }
496
+ }
497
+ catch (error) {
498
+ this.logger.warn('Failed to load some memory data from persistence', 'MemoryEntityManager', {
499
+ error,
500
+ });
501
+ }
502
+ }
503
+ async maybePersist() {
504
+ // In test mode, persist when there are enough changes for testing purposes
505
+ if (this.isTestMode) {
506
+ // Check if we have enough entities to test persistence (2+ entities)
507
+ const now = Date.now();
508
+ const timeSinceLastSnapshot = now - this.lastSnapshotTime;
509
+ // In test mode, persist after 30 minutes of simulated time OR when we have 2+ entities
510
+ if (this.entitiesCache.size >= 2 && timeSinceLastSnapshot >= 30 * 60 * 1000) {
511
+ await this.persistToStorage();
512
+ this.lastSnapshotTime = now;
513
+ }
514
+ return;
515
+ }
516
+ // Skip auto-persistence if disabled
517
+ if (this.config.snapshotFrequency === 0) {
518
+ return;
519
+ }
520
+ const now = Date.now();
521
+ const timeSinceLastSnapshot = now - this.lastSnapshotTime;
522
+ const snapshotIntervalMs = this.config.snapshotFrequency * 60 * 1000;
523
+ if (timeSinceLastSnapshot >= snapshotIntervalMs) {
524
+ await this.persistToStorage();
525
+ this.lastSnapshotTime = now;
526
+ }
527
+ }
528
+ async persistToStorage() {
529
+ try {
530
+ const fsInstance = this.fsOverride || fs;
531
+ await this.ensureMemoryDirectory();
532
+ // Save entities
533
+ const entities = Array.from(this.entitiesCache.values());
534
+ await fsInstance.writeFile(this.entitiesFile, JSON.stringify(entities, null, 2));
535
+ // Save relationships
536
+ const relationships = Array.from(this.relationshipsCache.values());
537
+ await fsInstance.writeFile(this.relationshipsFile, JSON.stringify(relationships, null, 2));
538
+ // Save intelligence
539
+ if (this.intelligence) {
540
+ await fsInstance.writeFile(this.intelligenceFile, JSON.stringify(this.intelligence, null, 2));
541
+ }
542
+ this.logger.debug('Memory data persisted to storage', 'MemoryEntityManager', {
543
+ entityCount: entities.length,
544
+ relationshipCount: relationships.length,
545
+ });
546
+ }
547
+ catch (error) {
548
+ this.logger.error('Failed to persist memory data', 'MemoryEntityManager', error instanceof Error ? error : undefined, { error });
549
+ }
550
+ }
551
+ async initializeIntelligence() {
552
+ if (!this.intelligence) {
553
+ this.intelligence = {
554
+ contextAwareness: {
555
+ currentContext: {},
556
+ contextHistory: [],
557
+ },
558
+ patternRecognition: {
559
+ discoveredPatterns: [],
560
+ patternConfidence: {},
561
+ emergentBehaviors: [],
562
+ },
563
+ relationshipInference: {
564
+ suggestedRelationships: [],
565
+ weakConnections: [],
566
+ conflictDetection: [],
567
+ },
568
+ adaptiveRecommendations: {
569
+ nextActions: [],
570
+ knowledgeGaps: [],
571
+ optimizationOpportunities: [],
572
+ },
573
+ };
574
+ // Initialize with some default knowledge gaps and recommendations
575
+ await this.updateRecommendations();
576
+ }
577
+ }
578
+ async updateIntelligence(eventType, context) {
579
+ if (!this.intelligence)
580
+ await this.initializeIntelligence();
581
+ // Update context awareness
582
+ this.intelligence.contextAwareness.currentContext = {
583
+ ...this.intelligence.contextAwareness.currentContext,
584
+ lastEvent: eventType,
585
+ lastEventTime: new Date().toISOString(),
586
+ ...context,
587
+ };
588
+ // Basic pattern recognition - could be enhanced with ML
589
+ if (eventType === 'entity_updated') {
590
+ const pattern = `${context.entityType}_update`;
591
+ this.intelligence.patternRecognition.patternConfidence[pattern] =
592
+ (this.intelligence.patternRecognition.patternConfidence[pattern] || 0) + 1;
593
+ }
594
+ // Update recommendations based on current state
595
+ await this.updateRecommendations();
596
+ }
597
+ async updateRecommendations() {
598
+ if (!this.intelligence)
599
+ return;
600
+ const entities = Array.from(this.entitiesCache.values());
601
+ const relationships = Array.from(this.relationshipsCache.values());
602
+ // Identify knowledge gaps (entities with low confidence OR system lacking entities)
603
+ const knowledgeGaps = [];
604
+ if (entities.length === 0) {
605
+ knowledgeGaps.push('No architectural decisions documented in memory system');
606
+ }
607
+ else {
608
+ // Add gaps for low confidence entities
609
+ const lowConfidenceGaps = entities
610
+ .filter(e => e.confidence < 0.5)
611
+ .map(e => `Low confidence in ${e.type}: ${e.title}`)
612
+ .slice(0, 3);
613
+ knowledgeGaps.push(...lowConfidenceGaps);
614
+ // Add gaps for missing entity types
615
+ if (entities.length === 1) {
616
+ knowledgeGaps.push('Limited diversity in architectural entities - consider documenting more types');
617
+ }
618
+ // Add gaps for entities without tags
619
+ const untaggedEntities = entities.filter(e => !e.tags || e.tags.length === 0);
620
+ if (untaggedEntities.length > 0) {
621
+ knowledgeGaps.push(`${untaggedEntities.length} entities lack proper categorization tags`);
622
+ }
623
+ }
624
+ // Suggest relationships for entities without many connections
625
+ const suggestedRelationships = [];
626
+ for (const entity of entities) {
627
+ const entityRelationships = relationships.filter(r => r.sourceId === entity.id || r.targetId === entity.id);
628
+ if (entityRelationships.length < 2) {
629
+ // Find potential relationships based on tags or type similarity
630
+ const similar = entities.filter(other => other.id !== entity.id &&
631
+ (other.type === entity.type || other.tags.some(tag => entity.tags.includes(tag))));
632
+ for (const similarEntity of similar.slice(0, 2)) {
633
+ suggestedRelationships.push({
634
+ sourceId: entity.id,
635
+ targetId: similarEntity.id,
636
+ type: 'relates_to',
637
+ confidence: 0.6,
638
+ reasoning: `Similar ${entity.type} entities with overlapping tags`,
639
+ });
640
+ }
641
+ }
642
+ }
643
+ this.intelligence.adaptiveRecommendations = {
644
+ nextActions: [
645
+ {
646
+ action: 'Validate low-confidence entities',
647
+ priority: 1,
648
+ reasoning: 'Several entities have confidence below 50%',
649
+ requiredEntities: entities
650
+ .filter(e => e.confidence < 0.5)
651
+ .map(e => e.id)
652
+ .slice(0, 3),
653
+ },
654
+ ],
655
+ knowledgeGaps,
656
+ optimizationOpportunities: [
657
+ 'Consider consolidating similar entities',
658
+ 'Review and strengthen weak relationships',
659
+ 'Add more context to entities with generic descriptions',
660
+ ],
661
+ };
662
+ this.intelligence.relationshipInference.suggestedRelationships = suggestedRelationships.slice(0, 10);
663
+ }
664
+ updateEntityRelationships(entity, relationship) {
665
+ // Determine the target entity ID based on which entity this is
666
+ const isSourceEntity = entity.id === relationship.sourceId;
667
+ const targetEntityId = isSourceEntity ? relationship.targetId : relationship.sourceId;
668
+ // Add relationship to entity's relationship list if not already present
669
+ const existingRelIndex = entity.relationships.findIndex(r => r.targetId === targetEntityId && r.type === relationship.type);
670
+ const entityRel = {
671
+ targetId: targetEntityId,
672
+ type: relationship.type,
673
+ strength: relationship.strength,
674
+ context: relationship.context,
675
+ created: relationship.created,
676
+ };
677
+ if (existingRelIndex >= 0) {
678
+ entity.relationships[existingRelIndex] = entityRel;
679
+ }
680
+ else {
681
+ entity.relationships.push(entityRel);
682
+ }
683
+ }
684
+ calculateChanges(oldEntity, newEntity) {
685
+ const changes = {};
686
+ // Convert to any to avoid index signature access issues
687
+ const oldAny = oldEntity;
688
+ const newAny = newEntity;
689
+ if (oldAny['title'] !== newAny['title'])
690
+ changes['title'] = { old: oldAny['title'], new: newAny['title'] };
691
+ if (oldAny['description'] !== newAny['description'])
692
+ changes['description'] = { old: oldAny['description'], new: newAny['description'] };
693
+ if (oldEntity['confidence'] !== newEntity['confidence'])
694
+ changes['confidence'] = { old: oldEntity['confidence'], new: newEntity['confidence'] };
695
+ if (oldEntity['relevance'] !== newEntity['relevance'])
696
+ changes['relevance'] = { old: oldEntity['relevance'], new: newEntity['relevance'] };
697
+ return changes;
698
+ }
699
+ async logEvolutionEvent(event) {
700
+ try {
701
+ let existingLog = [];
702
+ try {
703
+ const fsInstance = this.fsOverride || fs;
704
+ const logData = await fsInstance.readFile(this.evolutionLogFile, 'utf-8');
705
+ existingLog = JSON.parse(logData);
706
+ }
707
+ catch {
708
+ // Log file doesn't exist yet
709
+ }
710
+ existingLog.push(event);
711
+ // Keep only the last 1000 events to manage file size
712
+ if (existingLog.length > 1000) {
713
+ existingLog = existingLog.slice(-1000);
714
+ }
715
+ const fsInstance = this.fsOverride || fs;
716
+ await fsInstance.writeFile(this.evolutionLogFile, JSON.stringify(existingLog, null, 2));
717
+ }
718
+ catch (error) {
719
+ this.logger.warn('Failed to log evolution event', 'MemoryEntityManager', { event, error });
720
+ }
721
+ }
722
+ /**
723
+ * Clear all cached data (useful for testing)
724
+ */
725
+ clearCache() {
726
+ this.entitiesCache.clear();
727
+ this.relationshipsCache.clear();
728
+ this.intelligence = null;
729
+ this.lastSnapshotTime = 0;
730
+ }
731
+ /**
732
+ * Force persistence (useful for testing)
733
+ */
734
+ async forcePersist() {
735
+ return this.persistToStorage();
736
+ }
737
+ /**
738
+ * Create cross-tool relationships using the MemoryRelationshipMapper
739
+ */
740
+ async createCrossToolRelationships() {
741
+ const { MemoryRelationshipMapper } = await import('./memory-relationship-mapper.js');
742
+ const mapper = new MemoryRelationshipMapper(this);
743
+ this.logger.info('Starting cross-tool relationship creation', 'MemoryEntityManager');
744
+ const result = await mapper.createCrossToolRelationships();
745
+ const autoCreatedCount = result.suggestedRelationships.filter(rel => rel.confidence >= 0.85).length;
746
+ this.logger.info('Cross-tool relationships created', 'MemoryEntityManager', {
747
+ totalSuggested: result.suggestedRelationships.length,
748
+ autoCreated: autoCreatedCount,
749
+ conflicts: result.conflicts.length,
750
+ });
751
+ return {
752
+ ...result,
753
+ autoCreatedCount,
754
+ };
755
+ }
756
+ /**
757
+ * Infer and suggest new relationships based on existing entity patterns
758
+ */
759
+ async inferRelationships(entityTypes, minConfidence = 0.7) {
760
+ const { MemoryRelationshipMapper } = await import('./memory-relationship-mapper.js');
761
+ const mapper = new MemoryRelationshipMapper(this, { confidenceThreshold: minConfidence });
762
+ const result = await mapper.createCrossToolRelationships();
763
+ // Filter by entity types if specified
764
+ let filteredSuggestions = result.suggestedRelationships;
765
+ if (entityTypes && entityTypes.length > 0) {
766
+ const entityCache = this.entitiesCache;
767
+ filteredSuggestions = result.suggestedRelationships.filter(rel => {
768
+ const sourceEntity = entityCache.get(rel.sourceId);
769
+ const targetEntity = entityCache.get(rel.targetId);
770
+ return (sourceEntity &&
771
+ targetEntity &&
772
+ (entityTypes.includes(sourceEntity.type) || entityTypes.includes(targetEntity.type)));
773
+ });
774
+ }
775
+ return {
776
+ suggestedRelationships: filteredSuggestions.filter(rel => rel.confidence >= minConfidence),
777
+ };
778
+ }
779
+ /**
780
+ * Validate existing relationships and detect conflicts
781
+ */
782
+ async validateRelationships() {
783
+ const validRelationships = [];
784
+ const invalidRelationships = [];
785
+ const suggestedActions = [];
786
+ for (const [relationshipId, relationship] of this.relationshipsCache) {
787
+ // Check if both entities still exist
788
+ const sourceExists = this.entitiesCache.has(relationship.sourceId);
789
+ const targetExists = this.entitiesCache.has(relationship.targetId);
790
+ if (!sourceExists || !targetExists) {
791
+ invalidRelationships.push({
792
+ relationshipId,
793
+ reason: `${!sourceExists ? 'Source' : 'Target'} entity no longer exists`,
794
+ severity: 'high',
795
+ });
796
+ suggestedActions.push({
797
+ action: 'remove',
798
+ relationshipId,
799
+ reason: 'Orphaned relationship - entity deleted',
800
+ });
801
+ continue;
802
+ }
803
+ // Check relationship age and confidence
804
+ const createdDate = new Date(relationship.created);
805
+ const now = new Date();
806
+ const ageInDays = (now.getTime() - createdDate.getTime()) / (1000 * 60 * 60 * 24);
807
+ if (ageInDays > 90 && relationship.confidence < 0.6) {
808
+ invalidRelationships.push({
809
+ relationshipId,
810
+ reason: 'Old relationship with low confidence',
811
+ severity: 'medium',
812
+ });
813
+ suggestedActions.push({
814
+ action: 'investigate',
815
+ relationshipId,
816
+ reason: 'Requires validation - old and low confidence',
817
+ });
818
+ }
819
+ else if (relationship.strength < 0.3) {
820
+ invalidRelationships.push({
821
+ relationshipId,
822
+ reason: 'Very low relationship strength',
823
+ severity: 'low',
824
+ });
825
+ suggestedActions.push({
826
+ action: 'update',
827
+ relationshipId,
828
+ reason: 'Consider strengthening or removing weak relationship',
829
+ });
830
+ }
831
+ else {
832
+ validRelationships.push(relationshipId);
833
+ }
834
+ }
835
+ this.logger.info('Relationship validation completed', 'MemoryEntityManager', {
836
+ total: this.relationshipsCache.size,
837
+ valid: validRelationships.length,
838
+ invalid: invalidRelationships.length,
839
+ suggestedActions: suggestedActions.length,
840
+ });
841
+ return {
842
+ validRelationships,
843
+ invalidRelationships,
844
+ suggestedActions,
845
+ };
846
+ }
847
+ }
848
+ //# sourceMappingURL=memory-entity-manager.js.map