@danielsimonjr/memory-mcp 12.5.2 → 12.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/README.md +158 -14
  2. package/dist/core/EntityManager.d.ts +263 -0
  3. package/dist/core/EntityManager.d.ts.map +1 -0
  4. package/dist/core/EntityManager.js +478 -0
  5. package/dist/core/GraphStorage.d.ts +344 -0
  6. package/dist/core/GraphStorage.d.ts.map +1 -0
  7. package/dist/core/GraphStorage.js +708 -0
  8. package/dist/core/GraphTraversal.d.ts +136 -0
  9. package/dist/core/GraphTraversal.d.ts.map +1 -0
  10. package/dist/core/GraphTraversal.js +556 -0
  11. package/dist/core/HierarchyManager.d.ts +111 -0
  12. package/dist/core/HierarchyManager.d.ts.map +1 -0
  13. package/dist/core/HierarchyManager.js +224 -0
  14. package/dist/core/ManagerContext.d.ts +72 -0
  15. package/dist/core/ManagerContext.d.ts.map +1 -0
  16. package/dist/core/ManagerContext.js +118 -0
  17. package/dist/core/ObservationManager.d.ts +85 -0
  18. package/dist/core/ObservationManager.d.ts.map +1 -0
  19. package/dist/core/ObservationManager.js +123 -0
  20. package/dist/core/RelationManager.d.ts +131 -0
  21. package/dist/core/RelationManager.d.ts.map +1 -0
  22. package/dist/core/RelationManager.js +210 -0
  23. package/dist/core/SQLiteStorage.d.ts +354 -0
  24. package/dist/core/SQLiteStorage.d.ts.map +1 -0
  25. package/dist/core/SQLiteStorage.js +917 -0
  26. package/dist/core/StorageFactory.d.ts +45 -0
  27. package/dist/core/StorageFactory.d.ts.map +1 -0
  28. package/dist/core/StorageFactory.js +64 -0
  29. package/dist/core/TransactionManager.d.ts +259 -0
  30. package/dist/core/TransactionManager.d.ts.map +1 -0
  31. package/dist/core/TransactionManager.js +392 -0
  32. package/dist/core/index.d.ts +16 -0
  33. package/dist/core/index.d.ts.map +1 -0
  34. package/dist/core/index.js +17 -0
  35. package/dist/features/AnalyticsManager.d.ts +44 -0
  36. package/dist/features/AnalyticsManager.d.ts.map +1 -0
  37. package/dist/features/AnalyticsManager.js +223 -0
  38. package/dist/features/ArchiveManager.d.ts +121 -0
  39. package/dist/features/ArchiveManager.d.ts.map +1 -0
  40. package/dist/features/ArchiveManager.js +240 -0
  41. package/dist/features/CompressionManager.d.ts +109 -0
  42. package/dist/features/CompressionManager.d.ts.map +1 -0
  43. package/dist/features/CompressionManager.js +404 -0
  44. package/dist/features/IOManager.d.ts +221 -0
  45. package/dist/features/IOManager.d.ts.map +1 -0
  46. package/dist/features/IOManager.js +997 -0
  47. package/dist/features/StreamingExporter.d.ts +105 -0
  48. package/dist/features/StreamingExporter.d.ts.map +1 -0
  49. package/dist/features/StreamingExporter.js +150 -0
  50. package/dist/features/TagManager.d.ts +147 -0
  51. package/dist/features/TagManager.d.ts.map +1 -0
  52. package/dist/features/TagManager.js +210 -0
  53. package/dist/features/index.d.ts +12 -0
  54. package/dist/features/index.d.ts.map +1 -0
  55. package/dist/features/index.js +11 -0
  56. package/dist/index.js +8 -2
  57. package/dist/search/BasicSearch.d.ts +51 -0
  58. package/dist/search/BasicSearch.d.ts.map +1 -0
  59. package/dist/search/BasicSearch.js +137 -0
  60. package/dist/search/BooleanSearch.d.ts +98 -0
  61. package/dist/search/BooleanSearch.d.ts.map +1 -0
  62. package/dist/search/BooleanSearch.js +430 -0
  63. package/dist/search/EmbeddingService.d.ts +178 -0
  64. package/dist/search/EmbeddingService.d.ts.map +1 -0
  65. package/dist/search/EmbeddingService.js +358 -0
  66. package/dist/search/FuzzySearch.d.ts +118 -0
  67. package/dist/search/FuzzySearch.d.ts.map +1 -0
  68. package/dist/search/FuzzySearch.js +312 -0
  69. package/dist/search/RankedSearch.d.ts +71 -0
  70. package/dist/search/RankedSearch.d.ts.map +1 -0
  71. package/dist/search/RankedSearch.js +238 -0
  72. package/dist/search/SavedSearchManager.d.ts +79 -0
  73. package/dist/search/SavedSearchManager.d.ts.map +1 -0
  74. package/dist/search/SavedSearchManager.js +145 -0
  75. package/dist/search/SearchFilterChain.d.ts +120 -0
  76. package/dist/search/SearchFilterChain.d.ts.map +1 -0
  77. package/dist/search/SearchFilterChain.js +185 -0
  78. package/dist/search/SearchManager.d.ts +285 -0
  79. package/dist/search/SearchManager.d.ts.map +1 -0
  80. package/dist/search/SearchManager.js +338 -0
  81. package/dist/search/SearchSuggestions.d.ts +27 -0
  82. package/dist/search/SearchSuggestions.d.ts.map +1 -0
  83. package/dist/search/SearchSuggestions.js +57 -0
  84. package/dist/search/SemanticSearch.d.ts +146 -0
  85. package/dist/search/SemanticSearch.d.ts.map +1 -0
  86. package/dist/search/SemanticSearch.js +313 -0
  87. package/dist/search/TFIDFIndexManager.d.ts +63 -0
  88. package/dist/search/TFIDFIndexManager.d.ts.map +1 -0
  89. package/dist/search/TFIDFIndexManager.js +215 -0
  90. package/dist/search/VectorStore.d.ts +235 -0
  91. package/dist/search/VectorStore.d.ts.map +1 -0
  92. package/dist/search/VectorStore.js +311 -0
  93. package/dist/search/index.d.ts +18 -0
  94. package/dist/search/index.d.ts.map +1 -0
  95. package/dist/search/index.js +21 -0
  96. package/dist/server/MCPServer.d.ts.map +1 -1
  97. package/dist/server/toolDefinitions.d.ts +1 -1
  98. package/dist/server/toolDefinitions.d.ts.map +1 -1
  99. package/dist/server/toolDefinitions.js +254 -2
  100. package/dist/server/toolHandlers.d.ts.map +1 -1
  101. package/dist/server/toolHandlers.js +241 -25
  102. package/dist/types/index.d.ts +13 -0
  103. package/dist/types/index.d.ts.map +1 -0
  104. package/dist/types/index.js +12 -0
  105. package/dist/types/types.d.ts +1234 -0
  106. package/dist/types/types.d.ts.map +1 -0
  107. package/dist/types/types.js +9 -0
  108. package/dist/utils/compressedCache.d.ts +192 -0
  109. package/dist/utils/compressedCache.d.ts.map +1 -0
  110. package/dist/utils/compressedCache.js +309 -0
  111. package/dist/utils/compressionUtil.d.ts +214 -0
  112. package/dist/utils/compressionUtil.d.ts.map +1 -0
  113. package/dist/utils/compressionUtil.js +247 -0
  114. package/dist/utils/constants.d.ts +245 -0
  115. package/dist/utils/constants.d.ts.map +1 -0
  116. package/dist/utils/constants.js +252 -0
  117. package/dist/utils/entityUtils.d.ts +321 -0
  118. package/dist/utils/entityUtils.d.ts.map +1 -0
  119. package/dist/utils/entityUtils.js +538 -0
  120. package/dist/utils/errors.d.ts +77 -0
  121. package/dist/utils/errors.d.ts.map +1 -0
  122. package/dist/utils/errors.js +121 -0
  123. package/dist/utils/formatters.d.ts +145 -0
  124. package/dist/utils/formatters.d.ts.map +1 -0
  125. package/dist/utils/formatters.js +132 -0
  126. package/dist/utils/index.d.ts +22 -0
  127. package/dist/utils/index.d.ts.map +1 -0
  128. package/dist/utils/index.js +75 -0
  129. package/dist/utils/indexes.d.ts +270 -0
  130. package/dist/utils/indexes.d.ts.map +1 -0
  131. package/dist/utils/indexes.js +526 -0
  132. package/dist/utils/logger.d.ts +24 -0
  133. package/dist/utils/logger.d.ts.map +1 -0
  134. package/dist/utils/logger.js +33 -0
  135. package/dist/utils/parallelUtils.d.ts +72 -0
  136. package/dist/utils/parallelUtils.d.ts.map +1 -0
  137. package/dist/utils/parallelUtils.js +169 -0
  138. package/dist/utils/schemas.d.ts +374 -0
  139. package/dist/utils/schemas.d.ts.map +1 -0
  140. package/dist/utils/schemas.js +484 -0
  141. package/dist/utils/searchAlgorithms.d.ts +99 -0
  142. package/dist/utils/searchAlgorithms.d.ts.map +1 -0
  143. package/dist/utils/searchAlgorithms.js +167 -0
  144. package/dist/utils/searchCache.d.ts +108 -0
  145. package/dist/utils/searchCache.d.ts.map +1 -0
  146. package/dist/utils/searchCache.js +209 -0
  147. package/dist/utils/taskScheduler.d.ts +290 -0
  148. package/dist/utils/taskScheduler.d.ts.map +1 -0
  149. package/dist/utils/taskScheduler.js +466 -0
  150. package/dist/workers/WorkerPool.d.ts +81 -0
  151. package/dist/workers/WorkerPool.d.ts.map +1 -0
  152. package/dist/workers/WorkerPool.js +121 -0
  153. package/dist/workers/index.d.ts +12 -0
  154. package/dist/workers/index.d.ts.map +1 -0
  155. package/dist/workers/index.js +9 -0
  156. package/dist/workers/levenshteinWorker.d.ts +60 -0
  157. package/dist/workers/levenshteinWorker.d.ts.map +1 -0
  158. package/dist/workers/levenshteinWorker.js +98 -0
  159. package/package.json +7 -7
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Graph Traversal
3
+ *
4
+ * Phase 4 Sprints 6-8: Graph traversal algorithms for knowledge graph analysis.
5
+ * Includes BFS, DFS, shortest path, all paths, connected components, and centrality.
6
+ *
7
+ * @module core/GraphTraversal
8
+ */
9
+ import type { Relation, TraversalOptions, TraversalResult, PathResult, ConnectedComponentsResult, CentralityResult } from '../types/index.js';
10
+ import type { GraphStorage } from './GraphStorage.js';
11
+ /**
12
+ * Graph traversal algorithms for knowledge graph analysis.
13
+ *
14
+ * Provides BFS, DFS, shortest path finding, connected component detection,
15
+ * and centrality metrics for analyzing graph structure.
16
+ */
17
+ export declare class GraphTraversal {
18
+ private storage;
19
+ constructor(storage: GraphStorage);
20
+ /**
21
+ * Get neighbors of a node based on traversal direction and filters.
22
+ *
23
+ * @param entityName - Entity to get neighbors for
24
+ * @param options - Traversal options
25
+ * @returns Array of neighbor entity names with their relations
26
+ */
27
+ getNeighborsWithRelations(entityName: string, options?: TraversalOptions): Array<{
28
+ neighbor: string;
29
+ relation: Relation;
30
+ }>;
31
+ /**
32
+ * Breadth-First Search traversal starting from a given entity.
33
+ *
34
+ * @param startEntity - Entity name to start traversal from
35
+ * @param options - Traversal options
36
+ * @returns Traversal result with visited nodes, depths, and parent pointers
37
+ */
38
+ bfs(startEntity: string, options?: TraversalOptions): TraversalResult;
39
+ /**
40
+ * Depth-First Search traversal starting from a given entity.
41
+ *
42
+ * @param startEntity - Entity name to start traversal from
43
+ * @param options - Traversal options
44
+ * @returns Traversal result with visited nodes, depths, and parent pointers
45
+ */
46
+ dfs(startEntity: string, options?: TraversalOptions): TraversalResult;
47
+ /**
48
+ * Find the shortest path between two entities using BFS.
49
+ *
50
+ * @param source - Source entity name
51
+ * @param target - Target entity name
52
+ * @param options - Traversal options
53
+ * @returns PathResult if path exists, null otherwise
54
+ */
55
+ findShortestPath(source: string, target: string, options?: TraversalOptions): Promise<PathResult | null>;
56
+ /**
57
+ * Reconstruct path from parent pointers.
58
+ */
59
+ private reconstructPath;
60
+ /**
61
+ * Find all paths between two entities up to a maximum depth.
62
+ *
63
+ * @param source - Source entity name
64
+ * @param target - Target entity name
65
+ * @param maxDepth - Maximum path length (default: 5)
66
+ * @param options - Traversal options
67
+ * @returns Array of PathResult objects for all found paths
68
+ */
69
+ findAllPaths(source: string, target: string, maxDepth?: number, options?: TraversalOptions): Promise<PathResult[]>;
70
+ /**
71
+ * Find all connected components in the graph.
72
+ *
73
+ * Uses BFS to find all weakly connected components (treating the graph as undirected).
74
+ *
75
+ * @returns ConnectedComponentsResult with all components
76
+ */
77
+ findConnectedComponents(): Promise<ConnectedComponentsResult>;
78
+ /**
79
+ * Calculate degree centrality for all entities.
80
+ *
81
+ * Degree centrality is the number of connections an entity has,
82
+ * normalized by the maximum possible connections.
83
+ *
84
+ * @param direction - Direction to count: 'in', 'out', or 'both' (default)
85
+ * @param topN - Number of top entities to return (default: 10)
86
+ * @returns CentralityResult with scores and top entities
87
+ */
88
+ calculateDegreeCentrality(direction?: 'in' | 'out' | 'both', topN?: number): Promise<CentralityResult>;
89
+ /**
90
+ * Calculate betweenness centrality for all entities.
91
+ *
92
+ * Betweenness centrality measures how often a node appears on shortest paths
93
+ * between other nodes. Uses Brandes' algorithm for efficiency.
94
+ *
95
+ * @param options - Configuration options
96
+ * @param options.topN - Number of top entities to return (default: 10)
97
+ * @param options.chunkSize - Yield control every N vertices (default: 50)
98
+ * @param options.onProgress - Progress callback (0.0 to 1.0)
99
+ * @param options.approximate - Use approximation for faster results (default: false)
100
+ * @param options.sampleRate - Sample rate for approximation (default: 0.2)
101
+ * @returns CentralityResult with scores and top entities
102
+ */
103
+ calculateBetweennessCentrality(options?: {
104
+ topN?: number;
105
+ chunkSize?: number;
106
+ onProgress?: (progress: number) => void;
107
+ approximate?: boolean;
108
+ sampleRate?: number;
109
+ }): Promise<CentralityResult>;
110
+ /**
111
+ * Sample entities randomly for approximation algorithms.
112
+ *
113
+ * @param entities - Array of entities to sample from
114
+ * @param sampleSize - Number of entities to sample
115
+ * @returns Array of sampled entities
116
+ */
117
+ private sampleEntities;
118
+ /**
119
+ * Calculate PageRank centrality for all entities.
120
+ *
121
+ * PageRank measures importance based on incoming connections from
122
+ * other important nodes. Uses iterative power method.
123
+ *
124
+ * @param dampingFactor - Damping factor (default: 0.85)
125
+ * @param maxIterations - Maximum iterations (default: 100)
126
+ * @param tolerance - Convergence tolerance (default: 1e-6)
127
+ * @param topN - Number of top entities to return (default: 10)
128
+ * @returns CentralityResult with scores and top entities
129
+ */
130
+ calculatePageRank(dampingFactor?: number, maxIterations?: number, tolerance?: number, topN?: number): Promise<CentralityResult>;
131
+ /**
132
+ * Get top N entities from a scores map.
133
+ */
134
+ private getTopEntities;
135
+ }
136
+ //# sourceMappingURL=GraphTraversal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GraphTraversal.d.ts","sourceRoot":"","sources":["../../src/core/GraphTraversal.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAEV,QAAQ,EACR,gBAAgB,EAChB,eAAe,EACf,UAAU,EACV,yBAAyB,EACzB,gBAAgB,EACjB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAYtD;;;;;GAKG;AACH,qBAAa,cAAc;IACb,OAAO,CAAC,OAAO;gBAAP,OAAO,EAAE,YAAY;IAIzC;;;;;;OAMG;IACH,yBAAyB,CACvB,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,gBAAqB,GAC7B,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,QAAQ,CAAA;KAAE,CAAC;IA4ClD;;;;;;OAMG;IACH,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,eAAe;IAwCzE;;;;;;OAMG;IACH,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,eAAe;IA8CzE;;;;;;;OAOG;IACG,gBAAgB,CACpB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IA6C7B;;OAEG;IACH,OAAO,CAAC,eAAe;IA2BvB;;;;;;;;OAQG;IACG,YAAY,CAChB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,QAAQ,GAAE,MAAU,EACpB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,UAAU,EAAE,CAAC;IAiDxB;;;;;;OAMG;IACG,uBAAuB,IAAI,OAAO,CAAC,yBAAyB,CAAC;IA0CnE;;;;;;;;;OASG;IACG,yBAAyB,CAC7B,SAAS,GAAE,IAAI,GAAG,KAAK,GAAG,MAAe,EACzC,IAAI,GAAE,MAAW,GAChB,OAAO,CAAC,gBAAgB,CAAC;IA+B5B;;;;;;;;;;;;;OAaG;IACG,8BAA8B,CAClC,OAAO,GAAE;QACP,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;QACxC,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,UAAU,CAAC,EAAE,MAAM,CAAC;KAChB,GACL,OAAO,CAAC,gBAAgB,CAAC;IAiH5B;;;;;;OAMG;IACH,OAAO,CAAC,cAAc;IAUtB;;;;;;;;;;;OAWG;IACG,iBAAiB,CACrB,aAAa,GAAE,MAAa,EAC5B,aAAa,GAAE,MAAY,EAC3B,SAAS,GAAE,MAAa,EACxB,IAAI,GAAE,MAAW,GAChB,OAAO,CAAC,gBAAgB,CAAC;IA0E5B;;OAEG;IACH,OAAO,CAAC,cAAc;CASvB"}
@@ -0,0 +1,556 @@
1
+ /**
2
+ * Graph Traversal
3
+ *
4
+ * Phase 4 Sprints 6-8: Graph traversal algorithms for knowledge graph analysis.
5
+ * Includes BFS, DFS, shortest path, all paths, connected components, and centrality.
6
+ *
7
+ * @module core/GraphTraversal
8
+ */
9
+ /**
10
+ * Phase 4 Sprint 6: Default traversal options.
11
+ */
12
+ const DEFAULT_OPTIONS = {
13
+ direction: 'both',
14
+ maxDepth: Infinity,
15
+ relationTypes: [],
16
+ entityTypes: [],
17
+ };
18
+ /**
19
+ * Graph traversal algorithms for knowledge graph analysis.
20
+ *
21
+ * Provides BFS, DFS, shortest path finding, connected component detection,
22
+ * and centrality metrics for analyzing graph structure.
23
+ */
24
+ export class GraphTraversal {
25
+ storage;
26
+ constructor(storage) {
27
+ this.storage = storage;
28
+ }
29
+ // ==================== Sprint 6: BFS and DFS Traversal ====================
30
+ /**
31
+ * Get neighbors of a node based on traversal direction and filters.
32
+ *
33
+ * @param entityName - Entity to get neighbors for
34
+ * @param options - Traversal options
35
+ * @returns Array of neighbor entity names with their relations
36
+ */
37
+ getNeighborsWithRelations(entityName, options = {}) {
38
+ // Filter out undefined values before merging with defaults
39
+ const definedOptions = Object.fromEntries(Object.entries(options).filter(([, v]) => v !== undefined));
40
+ const opts = { ...DEFAULT_OPTIONS, ...definedOptions };
41
+ const neighbors = [];
42
+ // Get relations based on direction
43
+ let relations = [];
44
+ if (opts.direction === 'outgoing' || opts.direction === 'both') {
45
+ relations = relations.concat(this.storage.getRelationsFrom(entityName));
46
+ }
47
+ if (opts.direction === 'incoming' || opts.direction === 'both') {
48
+ relations = relations.concat(this.storage.getRelationsTo(entityName));
49
+ }
50
+ // Filter by relation types if specified
51
+ if (opts.relationTypes && opts.relationTypes.length > 0) {
52
+ const typeSet = new Set(opts.relationTypes.map(t => t.toLowerCase()));
53
+ relations = relations.filter(r => typeSet.has(r.relationType.toLowerCase()));
54
+ }
55
+ // Process relations to get neighbors
56
+ for (const relation of relations) {
57
+ const neighbor = relation.from === entityName ? relation.to : relation.from;
58
+ // Skip self-loops
59
+ if (neighbor === entityName)
60
+ continue;
61
+ // Filter by entity types if specified
62
+ if (opts.entityTypes && opts.entityTypes.length > 0) {
63
+ const entity = this.storage.getEntityByName(neighbor);
64
+ if (!entity)
65
+ continue;
66
+ const typeSet = new Set(opts.entityTypes.map(t => t.toLowerCase()));
67
+ if (!typeSet.has(entity.entityType.toLowerCase()))
68
+ continue;
69
+ }
70
+ neighbors.push({ neighbor, relation });
71
+ }
72
+ return neighbors;
73
+ }
74
+ /**
75
+ * Breadth-First Search traversal starting from a given entity.
76
+ *
77
+ * @param startEntity - Entity name to start traversal from
78
+ * @param options - Traversal options
79
+ * @returns Traversal result with visited nodes, depths, and parent pointers
80
+ */
81
+ bfs(startEntity, options = {}) {
82
+ const opts = { ...DEFAULT_OPTIONS, ...options };
83
+ // Validate start entity exists
84
+ if (!this.storage.hasEntity(startEntity)) {
85
+ return { nodes: [], depths: new Map(), parents: new Map() };
86
+ }
87
+ const visited = new Set();
88
+ const queue = [{ node: startEntity, depth: 0 }];
89
+ const nodes = [];
90
+ const depths = new Map();
91
+ const parents = new Map();
92
+ visited.add(startEntity);
93
+ parents.set(startEntity, null);
94
+ while (queue.length > 0) {
95
+ const { node, depth } = queue.shift();
96
+ // Respect maxDepth limit
97
+ if (depth > opts.maxDepth)
98
+ continue;
99
+ nodes.push(node);
100
+ depths.set(node, depth);
101
+ // Get neighbors and add unvisited ones to queue
102
+ const neighbors = this.getNeighborsWithRelations(node, opts);
103
+ for (const { neighbor } of neighbors) {
104
+ if (!visited.has(neighbor)) {
105
+ visited.add(neighbor);
106
+ queue.push({ node: neighbor, depth: depth + 1 });
107
+ parents.set(neighbor, node);
108
+ }
109
+ }
110
+ }
111
+ return { nodes, depths, parents };
112
+ }
113
+ /**
114
+ * Depth-First Search traversal starting from a given entity.
115
+ *
116
+ * @param startEntity - Entity name to start traversal from
117
+ * @param options - Traversal options
118
+ * @returns Traversal result with visited nodes, depths, and parent pointers
119
+ */
120
+ dfs(startEntity, options = {}) {
121
+ const opts = { ...DEFAULT_OPTIONS, ...options };
122
+ // Validate start entity exists
123
+ if (!this.storage.hasEntity(startEntity)) {
124
+ return { nodes: [], depths: new Map(), parents: new Map() };
125
+ }
126
+ const visited = new Set();
127
+ const stack = [{ node: startEntity, depth: 0 }];
128
+ const nodes = [];
129
+ const depths = new Map();
130
+ const parents = new Map();
131
+ parents.set(startEntity, null);
132
+ while (stack.length > 0) {
133
+ const { node, depth } = stack.pop();
134
+ // Skip if already visited
135
+ if (visited.has(node))
136
+ continue;
137
+ // Respect maxDepth limit
138
+ if (depth > opts.maxDepth)
139
+ continue;
140
+ visited.add(node);
141
+ nodes.push(node);
142
+ depths.set(node, depth);
143
+ // Get neighbors and add unvisited ones to stack
144
+ const neighbors = this.getNeighborsWithRelations(node, opts);
145
+ for (const { neighbor } of neighbors) {
146
+ if (!visited.has(neighbor)) {
147
+ stack.push({ node: neighbor, depth: depth + 1 });
148
+ if (!parents.has(neighbor)) {
149
+ parents.set(neighbor, node);
150
+ }
151
+ }
152
+ }
153
+ }
154
+ return { nodes, depths, parents };
155
+ }
156
+ // ==================== Sprint 7: Path Finding Algorithms ====================
157
+ /**
158
+ * Find the shortest path between two entities using BFS.
159
+ *
160
+ * @param source - Source entity name
161
+ * @param target - Target entity name
162
+ * @param options - Traversal options
163
+ * @returns PathResult if path exists, null otherwise
164
+ */
165
+ async findShortestPath(source, target, options = {}) {
166
+ // Ensure graph is loaded to populate indexes
167
+ await this.storage.loadGraph();
168
+ // Validate entities exist
169
+ if (!this.storage.hasEntity(source) || !this.storage.hasEntity(target)) {
170
+ return null;
171
+ }
172
+ // Same source and target
173
+ if (source === target) {
174
+ return { path: [source], length: 0, relations: [] };
175
+ }
176
+ const opts = { ...DEFAULT_OPTIONS, ...options };
177
+ const visited = new Set();
178
+ const queue = [source];
179
+ const parents = new Map();
180
+ visited.add(source);
181
+ parents.set(source, null);
182
+ while (queue.length > 0) {
183
+ const current = queue.shift();
184
+ // Found target, reconstruct path
185
+ if (current === target) {
186
+ return this.reconstructPath(source, target, parents);
187
+ }
188
+ // Get neighbors
189
+ const neighbors = this.getNeighborsWithRelations(current, opts);
190
+ for (const { neighbor, relation } of neighbors) {
191
+ if (!visited.has(neighbor)) {
192
+ visited.add(neighbor);
193
+ queue.push(neighbor);
194
+ parents.set(neighbor, { parent: current, relation });
195
+ }
196
+ }
197
+ }
198
+ // No path found
199
+ return null;
200
+ }
201
+ /**
202
+ * Reconstruct path from parent pointers.
203
+ */
204
+ reconstructPath(_source, target, parents) {
205
+ const path = [];
206
+ const relations = [];
207
+ let current = target;
208
+ while (current !== null) {
209
+ path.unshift(current);
210
+ const parentInfo = parents.get(current);
211
+ if (parentInfo) {
212
+ relations.unshift(parentInfo.relation);
213
+ current = parentInfo.parent;
214
+ }
215
+ else {
216
+ current = null;
217
+ }
218
+ }
219
+ return {
220
+ path,
221
+ length: path.length - 1,
222
+ relations,
223
+ };
224
+ }
225
+ /**
226
+ * Find all paths between two entities up to a maximum depth.
227
+ *
228
+ * @param source - Source entity name
229
+ * @param target - Target entity name
230
+ * @param maxDepth - Maximum path length (default: 5)
231
+ * @param options - Traversal options
232
+ * @returns Array of PathResult objects for all found paths
233
+ */
234
+ async findAllPaths(source, target, maxDepth = 5, options = {}) {
235
+ // Ensure graph is loaded to populate indexes
236
+ await this.storage.loadGraph();
237
+ // Validate entities exist
238
+ if (!this.storage.hasEntity(source) || !this.storage.hasEntity(target)) {
239
+ return [];
240
+ }
241
+ const opts = { ...DEFAULT_OPTIONS, ...options };
242
+ const allPaths = [];
243
+ const currentPath = [source];
244
+ const currentRelations = [];
245
+ const visited = new Set([source]);
246
+ const dfsAllPaths = (current, depth) => {
247
+ if (depth > maxDepth)
248
+ return;
249
+ if (current === target && depth > 0) {
250
+ allPaths.push({
251
+ path: [...currentPath],
252
+ length: currentPath.length - 1,
253
+ relations: [...currentRelations],
254
+ });
255
+ return;
256
+ }
257
+ const neighbors = this.getNeighborsWithRelations(current, opts);
258
+ for (const { neighbor, relation } of neighbors) {
259
+ if (!visited.has(neighbor)) {
260
+ visited.add(neighbor);
261
+ currentPath.push(neighbor);
262
+ currentRelations.push(relation);
263
+ dfsAllPaths(neighbor, depth + 1);
264
+ currentPath.pop();
265
+ currentRelations.pop();
266
+ visited.delete(neighbor);
267
+ }
268
+ }
269
+ };
270
+ dfsAllPaths(source, 0);
271
+ return allPaths;
272
+ }
273
+ // ==================== Sprint 8: Connected Components ====================
274
+ /**
275
+ * Find all connected components in the graph.
276
+ *
277
+ * Uses BFS to find all weakly connected components (treating the graph as undirected).
278
+ *
279
+ * @returns ConnectedComponentsResult with all components
280
+ */
281
+ async findConnectedComponents() {
282
+ const graph = await this.storage.loadGraph();
283
+ const visited = new Set();
284
+ const components = [];
285
+ for (const entity of graph.entities) {
286
+ if (!visited.has(entity.name)) {
287
+ // BFS to find all nodes in this component
288
+ const component = [];
289
+ const queue = [entity.name];
290
+ visited.add(entity.name);
291
+ while (queue.length > 0) {
292
+ const current = queue.shift();
293
+ component.push(current);
294
+ // Get all neighbors (both directions for weakly connected)
295
+ const neighbors = this.getNeighborsWithRelations(current, { direction: 'both' });
296
+ for (const { neighbor } of neighbors) {
297
+ if (!visited.has(neighbor)) {
298
+ visited.add(neighbor);
299
+ queue.push(neighbor);
300
+ }
301
+ }
302
+ }
303
+ components.push(component);
304
+ }
305
+ }
306
+ // Sort components by size (largest first)
307
+ components.sort((a, b) => b.length - a.length);
308
+ return {
309
+ components,
310
+ count: components.length,
311
+ largestComponentSize: components.length > 0 ? components[0].length : 0,
312
+ };
313
+ }
314
+ // ==================== Sprint 8: Centrality Algorithms ====================
315
+ /**
316
+ * Calculate degree centrality for all entities.
317
+ *
318
+ * Degree centrality is the number of connections an entity has,
319
+ * normalized by the maximum possible connections.
320
+ *
321
+ * @param direction - Direction to count: 'in', 'out', or 'both' (default)
322
+ * @param topN - Number of top entities to return (default: 10)
323
+ * @returns CentralityResult with scores and top entities
324
+ */
325
+ async calculateDegreeCentrality(direction = 'both', topN = 10) {
326
+ const graph = await this.storage.loadGraph();
327
+ const scores = new Map();
328
+ const n = graph.entities.length;
329
+ // Calculate degree for each entity
330
+ for (const entity of graph.entities) {
331
+ let degree = 0;
332
+ if (direction === 'in' || direction === 'both') {
333
+ degree += this.storage.getRelationsTo(entity.name).length;
334
+ }
335
+ if (direction === 'out' || direction === 'both') {
336
+ degree += this.storage.getRelationsFrom(entity.name).length;
337
+ }
338
+ // Normalize by maximum possible degree
339
+ const normalizedDegree = n > 1 ? degree / (n - 1) : 0;
340
+ scores.set(entity.name, normalizedDegree);
341
+ }
342
+ // Get top N entities
343
+ const topEntities = this.getTopEntities(scores, topN);
344
+ return {
345
+ scores,
346
+ topEntities,
347
+ algorithm: 'degree',
348
+ };
349
+ }
350
+ /**
351
+ * Calculate betweenness centrality for all entities.
352
+ *
353
+ * Betweenness centrality measures how often a node appears on shortest paths
354
+ * between other nodes. Uses Brandes' algorithm for efficiency.
355
+ *
356
+ * @param options - Configuration options
357
+ * @param options.topN - Number of top entities to return (default: 10)
358
+ * @param options.chunkSize - Yield control every N vertices (default: 50)
359
+ * @param options.onProgress - Progress callback (0.0 to 1.0)
360
+ * @param options.approximate - Use approximation for faster results (default: false)
361
+ * @param options.sampleRate - Sample rate for approximation (default: 0.2)
362
+ * @returns CentralityResult with scores and top entities
363
+ */
364
+ async calculateBetweennessCentrality(options = {}) {
365
+ const { topN = 10, chunkSize = 50, onProgress, approximate = false, sampleRate = 0.2 } = options;
366
+ const graph = await this.storage.loadGraph();
367
+ const scores = new Map();
368
+ // Initialize scores
369
+ for (const entity of graph.entities) {
370
+ scores.set(entity.name, 0);
371
+ }
372
+ // Determine which sources to process (full or sampled)
373
+ let sourcesToProcess = graph.entities;
374
+ if (approximate && graph.entities.length > 100) {
375
+ const sampleSize = Math.max(10, Math.floor(graph.entities.length * sampleRate));
376
+ sourcesToProcess = this.sampleEntities(graph.entities, sampleSize);
377
+ }
378
+ // Brandes' algorithm with chunked processing
379
+ let processed = 0;
380
+ for (const source of sourcesToProcess) {
381
+ const stack = [];
382
+ const predecessors = new Map();
383
+ const sigma = new Map(); // Number of shortest paths
384
+ const distance = new Map(); // Distance from source
385
+ const delta = new Map(); // Dependency
386
+ // Initialize
387
+ for (const entity of graph.entities) {
388
+ predecessors.set(entity.name, []);
389
+ sigma.set(entity.name, 0);
390
+ distance.set(entity.name, -1);
391
+ delta.set(entity.name, 0);
392
+ }
393
+ sigma.set(source.name, 1);
394
+ distance.set(source.name, 0);
395
+ // BFS
396
+ const queue = [source.name];
397
+ while (queue.length > 0) {
398
+ const v = queue.shift();
399
+ stack.push(v);
400
+ const neighbors = this.getNeighborsWithRelations(v, { direction: 'both' });
401
+ for (const { neighbor: w } of neighbors) {
402
+ // First time w is discovered
403
+ if (distance.get(w) === -1) {
404
+ distance.set(w, distance.get(v) + 1);
405
+ queue.push(w);
406
+ }
407
+ // w is on a shortest path from source via v
408
+ if (distance.get(w) === distance.get(v) + 1) {
409
+ sigma.set(w, sigma.get(w) + sigma.get(v));
410
+ predecessors.get(w).push(v);
411
+ }
412
+ }
413
+ }
414
+ // Accumulation
415
+ while (stack.length > 0) {
416
+ const w = stack.pop();
417
+ for (const v of predecessors.get(w)) {
418
+ const contribution = (sigma.get(v) / sigma.get(w)) * (1 + delta.get(w));
419
+ delta.set(v, delta.get(v) + contribution);
420
+ }
421
+ if (w !== source.name) {
422
+ scores.set(w, scores.get(w) + delta.get(w));
423
+ }
424
+ }
425
+ // Yield control periodically to prevent blocking event loop
426
+ processed++;
427
+ if (processed % chunkSize === 0) {
428
+ // Yield control to allow event loop to process other events
429
+ await new Promise(resolve => setImmediate(resolve));
430
+ // Report progress
431
+ if (onProgress) {
432
+ onProgress(processed / sourcesToProcess.length);
433
+ }
434
+ }
435
+ }
436
+ // Final progress update
437
+ if (onProgress) {
438
+ onProgress(1);
439
+ }
440
+ // Scale scores if using approximation
441
+ if (approximate && sampleRate < 1.0) {
442
+ const scaleFactor = 1 / sampleRate;
443
+ for (const [entity, score] of scores) {
444
+ scores.set(entity, score * scaleFactor);
445
+ }
446
+ }
447
+ // Normalize scores
448
+ const n = graph.entities.length;
449
+ const normalization = n > 2 ? 2 / ((n - 1) * (n - 2)) : 1;
450
+ for (const [name, score] of scores) {
451
+ scores.set(name, score * normalization);
452
+ }
453
+ const topEntities = this.getTopEntities(scores, topN);
454
+ return {
455
+ scores,
456
+ topEntities,
457
+ algorithm: 'betweenness',
458
+ };
459
+ }
460
+ /**
461
+ * Sample entities randomly for approximation algorithms.
462
+ *
463
+ * @param entities - Array of entities to sample from
464
+ * @param sampleSize - Number of entities to sample
465
+ * @returns Array of sampled entities
466
+ */
467
+ sampleEntities(entities, sampleSize) {
468
+ const shuffled = [...entities];
469
+ // Fisher-Yates shuffle
470
+ for (let i = shuffled.length - 1; i > 0; i--) {
471
+ const j = Math.floor(Math.random() * (i + 1));
472
+ [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
473
+ }
474
+ return shuffled.slice(0, sampleSize);
475
+ }
476
+ /**
477
+ * Calculate PageRank centrality for all entities.
478
+ *
479
+ * PageRank measures importance based on incoming connections from
480
+ * other important nodes. Uses iterative power method.
481
+ *
482
+ * @param dampingFactor - Damping factor (default: 0.85)
483
+ * @param maxIterations - Maximum iterations (default: 100)
484
+ * @param tolerance - Convergence tolerance (default: 1e-6)
485
+ * @param topN - Number of top entities to return (default: 10)
486
+ * @returns CentralityResult with scores and top entities
487
+ */
488
+ async calculatePageRank(dampingFactor = 0.85, maxIterations = 100, tolerance = 1e-6, topN = 10) {
489
+ const graph = await this.storage.loadGraph();
490
+ const n = graph.entities.length;
491
+ if (n === 0) {
492
+ return { scores: new Map(), topEntities: [], algorithm: 'pagerank' };
493
+ }
494
+ // Initialize PageRank scores
495
+ const scores = new Map();
496
+ const initialScore = 1 / n;
497
+ for (const entity of graph.entities) {
498
+ scores.set(entity.name, initialScore);
499
+ }
500
+ // Build outgoing links map
501
+ const outLinks = new Map();
502
+ for (const entity of graph.entities) {
503
+ const outgoing = this.storage.getRelationsFrom(entity.name);
504
+ outLinks.set(entity.name, outgoing.map(r => r.to));
505
+ }
506
+ // Power iteration
507
+ for (let iteration = 0; iteration < maxIterations; iteration++) {
508
+ const newScores = new Map();
509
+ let totalDiff = 0;
510
+ // Calculate dangling node contribution (nodes with no outgoing links)
511
+ let danglingSum = 0;
512
+ for (const entity of graph.entities) {
513
+ if (outLinks.get(entity.name).length === 0) {
514
+ danglingSum += scores.get(entity.name);
515
+ }
516
+ }
517
+ const danglingContribution = (dampingFactor * danglingSum) / n;
518
+ // Calculate new scores
519
+ for (const entity of graph.entities) {
520
+ let incomingScore = 0;
521
+ const incoming = this.storage.getRelationsTo(entity.name);
522
+ for (const relation of incoming) {
523
+ const source = relation.from;
524
+ const sourceOutCount = outLinks.get(source)?.length || 1;
525
+ incomingScore += scores.get(source) / sourceOutCount;
526
+ }
527
+ const newScore = (1 - dampingFactor) / n + dampingFactor * incomingScore + danglingContribution;
528
+ newScores.set(entity.name, newScore);
529
+ totalDiff += Math.abs(newScore - scores.get(entity.name));
530
+ }
531
+ // Update scores
532
+ for (const [name, score] of newScores) {
533
+ scores.set(name, score);
534
+ }
535
+ // Check convergence
536
+ if (totalDiff < tolerance) {
537
+ break;
538
+ }
539
+ }
540
+ const topEntities = this.getTopEntities(scores, topN);
541
+ return {
542
+ scores,
543
+ topEntities,
544
+ algorithm: 'pagerank',
545
+ };
546
+ }
547
+ /**
548
+ * Get top N entities from a scores map.
549
+ */
550
+ getTopEntities(scores, topN) {
551
+ return Array.from(scores.entries())
552
+ .sort((a, b) => b[1] - a[1])
553
+ .slice(0, topN)
554
+ .map(([name, score]) => ({ name, score }));
555
+ }
556
+ }