@claude-flow/memory 3.0.0-alpha.2 → 3.0.0-alpha.20

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 (253) hide show
  1. package/README.md +858 -249
  2. package/dist/agent-memory-scope.d.ts +131 -0
  3. package/dist/agent-memory-scope.d.ts.map +1 -0
  4. package/dist/agent-memory-scope.js +223 -0
  5. package/dist/agent-memory-scope.js.map +1 -0
  6. package/dist/agent-memory-scope.test.d.ts +8 -0
  7. package/dist/agent-memory-scope.test.d.ts.map +1 -0
  8. package/dist/agent-memory-scope.test.js +463 -0
  9. package/dist/agent-memory-scope.test.js.map +1 -0
  10. package/dist/agentdb-adapter.d.ts +82 -4
  11. package/dist/agentdb-adapter.d.ts.map +1 -1
  12. package/dist/agentdb-adapter.js +388 -17
  13. package/dist/agentdb-adapter.js.map +1 -1
  14. package/dist/agentdb-backend.d.ts +6 -0
  15. package/dist/agentdb-backend.d.ts.map +1 -1
  16. package/dist/agentdb-backend.js +115 -51
  17. package/dist/agentdb-backend.js.map +1 -1
  18. package/dist/auto-memory-bridge.d.ts +237 -0
  19. package/dist/auto-memory-bridge.d.ts.map +1 -0
  20. package/dist/auto-memory-bridge.js +758 -0
  21. package/dist/auto-memory-bridge.js.map +1 -0
  22. package/dist/auto-memory-bridge.test.d.ts +8 -0
  23. package/dist/auto-memory-bridge.test.d.ts.map +1 -0
  24. package/dist/auto-memory-bridge.test.js +849 -0
  25. package/dist/auto-memory-bridge.test.js.map +1 -0
  26. package/dist/benchmark.test.d.ts +2 -0
  27. package/dist/benchmark.test.d.ts.map +1 -0
  28. package/dist/benchmark.test.js +277 -0
  29. package/dist/benchmark.test.js.map +1 -0
  30. package/dist/consolidator.d.ts +96 -0
  31. package/dist/consolidator.d.ts.map +1 -0
  32. package/dist/consolidator.js +209 -0
  33. package/dist/consolidator.js.map +1 -0
  34. package/dist/consolidator.test.d.ts +17 -0
  35. package/dist/consolidator.test.d.ts.map +1 -0
  36. package/dist/consolidator.test.js +239 -0
  37. package/dist/consolidator.test.js.map +1 -0
  38. package/dist/controller-registry.d.ts +232 -0
  39. package/dist/controller-registry.d.ts.map +1 -0
  40. package/dist/controller-registry.js +1049 -0
  41. package/dist/controller-registry.js.map +1 -0
  42. package/dist/controller-registry.test.d.ts +14 -0
  43. package/dist/controller-registry.test.d.ts.map +1 -0
  44. package/dist/controller-registry.test.js +691 -0
  45. package/dist/controller-registry.test.js.map +1 -0
  46. package/dist/database-provider.d.ts +14 -2
  47. package/dist/database-provider.d.ts.map +1 -1
  48. package/dist/database-provider.js +63 -3
  49. package/dist/database-provider.js.map +1 -1
  50. package/dist/database-provider.test.js +56 -3
  51. package/dist/database-provider.test.js.map +1 -1
  52. package/dist/entity-tagger.d.ts +31 -0
  53. package/dist/entity-tagger.d.ts.map +1 -0
  54. package/dist/entity-tagger.js +80 -0
  55. package/dist/entity-tagger.js.map +1 -0
  56. package/dist/entity-tagger.test.d.ts +12 -0
  57. package/dist/entity-tagger.test.d.ts.map +1 -0
  58. package/dist/entity-tagger.test.js +70 -0
  59. package/dist/entity-tagger.test.js.map +1 -0
  60. package/dist/fts5.test.d.ts +13 -0
  61. package/dist/fts5.test.d.ts.map +1 -0
  62. package/dist/fts5.test.js +144 -0
  63. package/dist/fts5.test.js.map +1 -0
  64. package/dist/graceful-retrieval.test.d.ts +14 -0
  65. package/dist/graceful-retrieval.test.d.ts.map +1 -0
  66. package/dist/graceful-retrieval.test.js +192 -0
  67. package/dist/graceful-retrieval.test.js.map +1 -0
  68. package/dist/hnsw-index.d.ts +51 -0
  69. package/dist/hnsw-index.d.ts.map +1 -1
  70. package/dist/hnsw-index.js +396 -12
  71. package/dist/hnsw-index.js.map +1 -1
  72. package/dist/hnsw-persistence.test.d.ts +13 -0
  73. package/dist/hnsw-persistence.test.d.ts.map +1 -0
  74. package/dist/hnsw-persistence.test.js +208 -0
  75. package/dist/hnsw-persistence.test.js.map +1 -0
  76. package/dist/hybrid-backend.d.ts +28 -0
  77. package/dist/hybrid-backend.d.ts.map +1 -1
  78. package/dist/hybrid-backend.js +82 -4
  79. package/dist/hybrid-backend.js.map +1 -1
  80. package/dist/hybrid-backend.test.js +6 -5
  81. package/dist/hybrid-backend.test.js.map +1 -1
  82. package/dist/index.d.ts +167 -9
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +277 -30
  85. package/dist/index.js.map +1 -1
  86. package/dist/index.test.d.ts +10 -0
  87. package/dist/index.test.d.ts.map +1 -0
  88. package/dist/index.test.js +71 -0
  89. package/dist/index.test.js.map +1 -0
  90. package/dist/json-security.d.ts +12 -0
  91. package/dist/json-security.d.ts.map +1 -0
  92. package/dist/json-security.js +20 -0
  93. package/dist/json-security.js.map +1 -0
  94. package/dist/learning-bridge.d.ts +137 -0
  95. package/dist/learning-bridge.d.ts.map +1 -0
  96. package/dist/learning-bridge.js +335 -0
  97. package/dist/learning-bridge.js.map +1 -0
  98. package/dist/learning-bridge.test.d.ts +8 -0
  99. package/dist/learning-bridge.test.d.ts.map +1 -0
  100. package/dist/learning-bridge.test.js +578 -0
  101. package/dist/learning-bridge.test.js.map +1 -0
  102. package/dist/memory-graph.d.ts +100 -0
  103. package/dist/memory-graph.d.ts.map +1 -0
  104. package/dist/memory-graph.js +333 -0
  105. package/dist/memory-graph.js.map +1 -0
  106. package/dist/memory-graph.test.d.ts +8 -0
  107. package/dist/memory-graph.test.d.ts.map +1 -0
  108. package/dist/memory-graph.test.js +609 -0
  109. package/dist/memory-graph.test.js.map +1 -0
  110. package/dist/persistent-sona.d.ts +144 -0
  111. package/dist/persistent-sona.d.ts.map +1 -0
  112. package/dist/persistent-sona.js +332 -0
  113. package/dist/persistent-sona.js.map +1 -0
  114. package/dist/rvf-backend.d.ts +53 -0
  115. package/dist/rvf-backend.d.ts.map +1 -0
  116. package/dist/rvf-backend.js +643 -0
  117. package/dist/rvf-backend.js.map +1 -0
  118. package/dist/rvf-learning-store.d.ts +139 -0
  119. package/dist/rvf-learning-store.d.ts.map +1 -0
  120. package/dist/rvf-learning-store.js +295 -0
  121. package/dist/rvf-learning-store.js.map +1 -0
  122. package/dist/rvf-migration.d.ts +45 -0
  123. package/dist/rvf-migration.d.ts.map +1 -0
  124. package/dist/rvf-migration.js +254 -0
  125. package/dist/rvf-migration.js.map +1 -0
  126. package/dist/smart-retrieval.d.ts +118 -0
  127. package/dist/smart-retrieval.d.ts.map +1 -0
  128. package/dist/smart-retrieval.js +322 -0
  129. package/dist/smart-retrieval.js.map +1 -0
  130. package/dist/smart-retrieval.test.d.ts +2 -0
  131. package/dist/smart-retrieval.test.d.ts.map +1 -0
  132. package/dist/smart-retrieval.test.js +238 -0
  133. package/dist/smart-retrieval.test.js.map +1 -0
  134. package/dist/sqlite-backend.d.ts +10 -0
  135. package/dist/sqlite-backend.d.ts.map +1 -1
  136. package/dist/sqlite-backend.js +170 -74
  137. package/dist/sqlite-backend.js.map +1 -1
  138. package/dist/sqljs-backend.d.ts +9 -0
  139. package/dist/sqljs-backend.d.ts.map +1 -1
  140. package/dist/sqljs-backend.js +120 -29
  141. package/dist/sqljs-backend.js.map +1 -1
  142. package/dist/types.d.ts +3 -0
  143. package/dist/types.d.ts.map +1 -1
  144. package/package.json +48 -35
  145. package/.agentic-flow/intelligence.json +0 -16
  146. package/__tests__/coverage/base.css +0 -224
  147. package/__tests__/coverage/block-navigation.js +0 -87
  148. package/__tests__/coverage/coverage-final.json +0 -19
  149. package/__tests__/coverage/favicon.png +0 -0
  150. package/__tests__/coverage/index.html +0 -206
  151. package/__tests__/coverage/lcov-report/base.css +0 -224
  152. package/__tests__/coverage/lcov-report/block-navigation.js +0 -87
  153. package/__tests__/coverage/lcov-report/favicon.png +0 -0
  154. package/__tests__/coverage/lcov-report/index.html +0 -206
  155. package/__tests__/coverage/lcov-report/prettify.css +0 -1
  156. package/__tests__/coverage/lcov-report/prettify.js +0 -2
  157. package/__tests__/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  158. package/__tests__/coverage/lcov-report/sorter.js +0 -210
  159. package/__tests__/coverage/lcov-report/src/agentdb-adapter.ts.html +0 -2737
  160. package/__tests__/coverage/lcov-report/src/agentdb-backend.ts.html +0 -3130
  161. package/__tests__/coverage/lcov-report/src/application/commands/delete-memory.command.ts.html +0 -601
  162. package/__tests__/coverage/lcov-report/src/application/commands/index.html +0 -131
  163. package/__tests__/coverage/lcov-report/src/application/commands/store-memory.command.ts.html +0 -394
  164. package/__tests__/coverage/lcov-report/src/application/queries/index.html +0 -116
  165. package/__tests__/coverage/lcov-report/src/application/queries/search-memory.query.ts.html +0 -796
  166. package/__tests__/coverage/lcov-report/src/application/services/index.html +0 -116
  167. package/__tests__/coverage/lcov-report/src/application/services/memory-application-service.ts.html +0 -793
  168. package/__tests__/coverage/lcov-report/src/cache-manager.ts.html +0 -1633
  169. package/__tests__/coverage/lcov-report/src/database-provider.ts.html +0 -1618
  170. package/__tests__/coverage/lcov-report/src/domain/entities/index.html +0 -116
  171. package/__tests__/coverage/lcov-report/src/domain/entities/memory-entry.ts.html +0 -952
  172. package/__tests__/coverage/lcov-report/src/domain/services/index.html +0 -116
  173. package/__tests__/coverage/lcov-report/src/domain/services/memory-domain-service.ts.html +0 -1294
  174. package/__tests__/coverage/lcov-report/src/hnsw-index.ts.html +0 -3124
  175. package/__tests__/coverage/lcov-report/src/hybrid-backend.ts.html +0 -2167
  176. package/__tests__/coverage/lcov-report/src/index.html +0 -266
  177. package/__tests__/coverage/lcov-report/src/infrastructure/repositories/hybrid-memory-repository.ts.html +0 -1633
  178. package/__tests__/coverage/lcov-report/src/infrastructure/repositories/index.html +0 -116
  179. package/__tests__/coverage/lcov-report/src/migration.ts.html +0 -2092
  180. package/__tests__/coverage/lcov-report/src/query-builder.ts.html +0 -1711
  181. package/__tests__/coverage/lcov-report/src/sqlite-backend.ts.html +0 -2281
  182. package/__tests__/coverage/lcov-report/src/sqljs-backend.ts.html +0 -2374
  183. package/__tests__/coverage/lcov-report/src/types.ts.html +0 -2266
  184. package/__tests__/coverage/lcov.info +0 -10238
  185. package/__tests__/coverage/prettify.css +0 -1
  186. package/__tests__/coverage/prettify.js +0 -2
  187. package/__tests__/coverage/sort-arrow-sprite.png +0 -0
  188. package/__tests__/coverage/sorter.js +0 -210
  189. package/__tests__/coverage/src/agentdb-adapter.ts.html +0 -2737
  190. package/__tests__/coverage/src/agentdb-backend.ts.html +0 -3130
  191. package/__tests__/coverage/src/application/commands/delete-memory.command.ts.html +0 -601
  192. package/__tests__/coverage/src/application/commands/index.html +0 -131
  193. package/__tests__/coverage/src/application/commands/store-memory.command.ts.html +0 -394
  194. package/__tests__/coverage/src/application/queries/index.html +0 -116
  195. package/__tests__/coverage/src/application/queries/search-memory.query.ts.html +0 -796
  196. package/__tests__/coverage/src/application/services/index.html +0 -116
  197. package/__tests__/coverage/src/application/services/memory-application-service.ts.html +0 -793
  198. package/__tests__/coverage/src/cache-manager.ts.html +0 -1633
  199. package/__tests__/coverage/src/database-provider.ts.html +0 -1618
  200. package/__tests__/coverage/src/domain/entities/index.html +0 -116
  201. package/__tests__/coverage/src/domain/entities/memory-entry.ts.html +0 -952
  202. package/__tests__/coverage/src/domain/services/index.html +0 -116
  203. package/__tests__/coverage/src/domain/services/memory-domain-service.ts.html +0 -1294
  204. package/__tests__/coverage/src/hnsw-index.ts.html +0 -3124
  205. package/__tests__/coverage/src/hybrid-backend.ts.html +0 -2167
  206. package/__tests__/coverage/src/index.html +0 -266
  207. package/__tests__/coverage/src/infrastructure/repositories/hybrid-memory-repository.ts.html +0 -1633
  208. package/__tests__/coverage/src/infrastructure/repositories/index.html +0 -116
  209. package/__tests__/coverage/src/migration.ts.html +0 -2092
  210. package/__tests__/coverage/src/query-builder.ts.html +0 -1711
  211. package/__tests__/coverage/src/sqlite-backend.ts.html +0 -2281
  212. package/__tests__/coverage/src/sqljs-backend.ts.html +0 -2374
  213. package/__tests__/coverage/src/types.ts.html +0 -2266
  214. package/benchmarks/cache-hit-rate.bench.ts +0 -535
  215. package/benchmarks/hnsw-indexing.bench.ts +0 -552
  216. package/benchmarks/memory-write.bench.ts +0 -469
  217. package/benchmarks/vector-search.bench.ts +0 -449
  218. package/docs/AGENTDB-INTEGRATION.md +0 -388
  219. package/docs/CROSS_PLATFORM.md +0 -505
  220. package/docs/WINDOWS_SUPPORT.md +0 -422
  221. package/examples/agentdb-example.ts +0 -345
  222. package/examples/cross-platform-usage.ts +0 -326
  223. package/framework/benchmark.ts +0 -112
  224. package/src/agentdb-adapter.ts +0 -884
  225. package/src/agentdb-backend.test.ts +0 -339
  226. package/src/agentdb-backend.ts +0 -1016
  227. package/src/application/commands/delete-memory.command.ts +0 -172
  228. package/src/application/commands/store-memory.command.ts +0 -103
  229. package/src/application/index.ts +0 -36
  230. package/src/application/queries/search-memory.query.ts +0 -237
  231. package/src/application/services/memory-application-service.ts +0 -236
  232. package/src/cache-manager.ts +0 -516
  233. package/src/database-provider.test.ts +0 -364
  234. package/src/database-provider.ts +0 -511
  235. package/src/domain/entities/memory-entry.ts +0 -289
  236. package/src/domain/index.ts +0 -35
  237. package/src/domain/repositories/memory-repository.interface.ts +0 -120
  238. package/src/domain/services/memory-domain-service.ts +0 -403
  239. package/src/hnsw-index.ts +0 -1013
  240. package/src/hybrid-backend.test.ts +0 -399
  241. package/src/hybrid-backend.ts +0 -694
  242. package/src/index.ts +0 -515
  243. package/src/infrastructure/index.ts +0 -23
  244. package/src/infrastructure/repositories/hybrid-memory-repository.ts +0 -516
  245. package/src/migration.ts +0 -669
  246. package/src/query-builder.ts +0 -542
  247. package/src/sqlite-backend.ts +0 -732
  248. package/src/sqljs-backend.ts +0 -763
  249. package/src/types.ts +0 -727
  250. package/tmp.json +0 -0
  251. package/tsconfig.json +0 -9
  252. package/tsconfig.tsbuildinfo +0 -1
  253. package/verify-cross-platform.ts +0 -170
@@ -0,0 +1,691 @@
1
+ /**
2
+ * Comprehensive tests for ControllerRegistry (ADR-053)
3
+ *
4
+ * Covers:
5
+ * - Initialization lifecycle and level-based ordering
6
+ * - Graceful degradation (isolated controller failures)
7
+ * - Config-driven activation
8
+ * - Health check aggregation
9
+ * - Shutdown ordering
10
+ * - Cross-platform path handling (Linux/Mac/Windows)
11
+ * - AgentDB unavailable scenarios
12
+ */
13
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
14
+ import { ControllerRegistry, INIT_LEVELS, } from './controller-registry.js';
15
+ import { LearningBridge } from './learning-bridge.js';
16
+ import { MemoryGraph } from './memory-graph.js';
17
+ import { TieredCacheManager } from './cache-manager.js';
18
+ // ===== Mock Backend =====
19
+ function createMockBackend() {
20
+ const entries = new Map();
21
+ return {
22
+ async initialize() { },
23
+ async shutdown() { },
24
+ async store(entry) {
25
+ entries.set(entry.id, entry);
26
+ },
27
+ async get(id) {
28
+ return entries.get(id) ?? null;
29
+ },
30
+ async getByKey(namespace, key) {
31
+ for (const e of entries.values()) {
32
+ if (e.namespace === namespace && e.key === key)
33
+ return e;
34
+ }
35
+ return null;
36
+ },
37
+ async update(id, update) {
38
+ const entry = entries.get(id);
39
+ if (!entry)
40
+ return null;
41
+ Object.assign(entry, update, { updatedAt: Date.now() });
42
+ return entry;
43
+ },
44
+ async delete(id) {
45
+ return entries.delete(id);
46
+ },
47
+ async query(query) {
48
+ const results = Array.from(entries.values());
49
+ if (query.namespace) {
50
+ return results.filter((e) => e.namespace === query.namespace).slice(0, query.limit);
51
+ }
52
+ return results.slice(0, query.limit);
53
+ },
54
+ async search(_embedding, _options) {
55
+ return [];
56
+ },
57
+ async bulkInsert(newEntries) {
58
+ for (const entry of newEntries)
59
+ entries.set(entry.id, entry);
60
+ },
61
+ async bulkDelete(ids) {
62
+ let count = 0;
63
+ for (const id of ids) {
64
+ if (entries.delete(id))
65
+ count++;
66
+ }
67
+ return count;
68
+ },
69
+ async count(namespace) {
70
+ if (namespace) {
71
+ return Array.from(entries.values()).filter((e) => e.namespace === namespace).length;
72
+ }
73
+ return entries.size;
74
+ },
75
+ async listNamespaces() {
76
+ return [...new Set(Array.from(entries.values()).map((e) => e.namespace))];
77
+ },
78
+ async clearNamespace(namespace) {
79
+ let count = 0;
80
+ for (const [id, entry] of entries) {
81
+ if (entry.namespace === namespace) {
82
+ entries.delete(id);
83
+ count++;
84
+ }
85
+ }
86
+ return count;
87
+ },
88
+ async getStats() {
89
+ return {
90
+ totalEntries: entries.size,
91
+ entriesByNamespace: {},
92
+ entriesByType: { episodic: 0, semantic: 0, procedural: 0, working: 0, cache: 0 },
93
+ memoryUsage: 0,
94
+ avgQueryTime: 0,
95
+ avgSearchTime: 0,
96
+ };
97
+ },
98
+ async healthCheck() {
99
+ return {
100
+ status: 'healthy',
101
+ components: {
102
+ storage: { status: 'healthy', latency: 0 },
103
+ index: { status: 'healthy', latency: 0 },
104
+ cache: { status: 'healthy', latency: 0 },
105
+ },
106
+ timestamp: Date.now(),
107
+ issues: [],
108
+ recommendations: [],
109
+ };
110
+ },
111
+ };
112
+ }
113
+ // ===== Test Suite =====
114
+ describe('ControllerRegistry', () => {
115
+ let registry;
116
+ let mockBackend;
117
+ beforeEach(() => {
118
+ registry = new ControllerRegistry();
119
+ mockBackend = createMockBackend();
120
+ });
121
+ afterEach(async () => {
122
+ if (registry.isInitialized()) {
123
+ await registry.shutdown();
124
+ }
125
+ });
126
+ // ----- Lifecycle Tests -----
127
+ describe('initialization lifecycle', () => {
128
+ it('should initialize with default config', async () => {
129
+ await registry.initialize({ backend: mockBackend });
130
+ expect(registry.isInitialized()).toBe(true);
131
+ });
132
+ it('should not initialize twice', async () => {
133
+ await registry.initialize({ backend: mockBackend });
134
+ const count1 = registry.getActiveCount();
135
+ await registry.initialize({ backend: mockBackend });
136
+ expect(registry.getActiveCount()).toBe(count1);
137
+ });
138
+ it('should initialize with empty config', async () => {
139
+ await registry.initialize();
140
+ expect(registry.isInitialized()).toBe(true);
141
+ });
142
+ it('should emit initialized event', async () => {
143
+ const handler = vi.fn();
144
+ registry.on('initialized', handler);
145
+ await registry.initialize({ backend: mockBackend });
146
+ expect(handler).toHaveBeenCalledOnce();
147
+ expect(handler).toHaveBeenCalledWith(expect.objectContaining({
148
+ initTimeMs: expect.any(Number),
149
+ activeControllers: expect.any(Number),
150
+ totalControllers: expect.any(Number),
151
+ }));
152
+ });
153
+ it('should emit controller:initialized events', async () => {
154
+ const handler = vi.fn();
155
+ registry.on('controller:initialized', handler);
156
+ await registry.initialize({ backend: mockBackend });
157
+ // At minimum learningBridge and tieredCache should init
158
+ expect(handler.mock.calls.length).toBeGreaterThanOrEqual(1);
159
+ });
160
+ it('should track init time', async () => {
161
+ await registry.initialize({ backend: mockBackend });
162
+ const report = await registry.healthCheck();
163
+ expect(report.initTimeMs).toBeGreaterThan(0);
164
+ });
165
+ });
166
+ // ----- Level-Based Ordering -----
167
+ describe('level-based initialization ordering', () => {
168
+ it('should define 7 initialization levels (0-6)', () => {
169
+ expect(INIT_LEVELS).toHaveLength(7);
170
+ expect(INIT_LEVELS[0].level).toBe(0);
171
+ expect(INIT_LEVELS[6].level).toBe(6);
172
+ });
173
+ it('should have monotonically increasing levels', () => {
174
+ for (let i = 1; i < INIT_LEVELS.length; i++) {
175
+ expect(INIT_LEVELS[i].level).toBeGreaterThan(INIT_LEVELS[i - 1].level);
176
+ }
177
+ });
178
+ it('should include core controllers in level 1', () => {
179
+ const level1 = INIT_LEVELS.find((l) => l.level === 1);
180
+ expect(level1?.controllers).toContain('reasoningBank');
181
+ expect(level1?.controllers).toContain('learningBridge');
182
+ expect(level1?.controllers).toContain('tieredCache');
183
+ });
184
+ it('should include graph controllers in level 2', () => {
185
+ const level2 = INIT_LEVELS.find((l) => l.level === 2);
186
+ expect(level2?.controllers).toContain('memoryGraph');
187
+ expect(level2?.controllers).toContain('agentMemoryScope');
188
+ });
189
+ it('should include specialization controllers in level 3', () => {
190
+ const level3 = INIT_LEVELS.find((l) => l.level === 3);
191
+ expect(level3?.controllers).toContain('skills');
192
+ expect(level3?.controllers).toContain('explainableRecall');
193
+ expect(level3?.controllers).toContain('reflexion');
194
+ });
195
+ it('should include causal controllers in level 4', () => {
196
+ const level4 = INIT_LEVELS.find((l) => l.level === 4);
197
+ expect(level4?.controllers).toContain('causalGraph');
198
+ expect(level4?.controllers).toContain('nightlyLearner');
199
+ });
200
+ it('should include advanced services in level 5', () => {
201
+ const level5 = INIT_LEVELS.find((l) => l.level === 5);
202
+ expect(level5?.controllers).toContain('graphTransformer');
203
+ expect(level5?.controllers).toContain('sonaTrajectory');
204
+ });
205
+ it('should include session management in level 6', () => {
206
+ const level6 = INIT_LEVELS.find((l) => l.level === 6);
207
+ expect(level6?.controllers).toContain('federatedSession');
208
+ });
209
+ it('should not have duplicate controller names across levels', () => {
210
+ const allNames = [];
211
+ for (const level of INIT_LEVELS) {
212
+ for (const name of level.controllers) {
213
+ expect(allNames).not.toContain(name);
214
+ allNames.push(name);
215
+ }
216
+ }
217
+ });
218
+ });
219
+ // ----- Graceful Degradation -----
220
+ describe('graceful degradation', () => {
221
+ it('should continue when AgentDB is unavailable', async () => {
222
+ // No AgentDB module available — should still init CLI-layer controllers
223
+ await registry.initialize({ backend: mockBackend });
224
+ expect(registry.isInitialized()).toBe(true);
225
+ });
226
+ it('should mark failed controllers as unavailable without crashing', async () => {
227
+ await registry.initialize({ backend: mockBackend });
228
+ const report = await registry.healthCheck();
229
+ // Some controllers should be unavailable (no AgentDB)
230
+ // but the registry itself should be functional
231
+ expect(report.status).not.toBe('unhealthy');
232
+ });
233
+ it('should emit controller:failed for failed controllers', async () => {
234
+ const handler = vi.fn();
235
+ registry.on('controller:failed', handler);
236
+ // Enable a controller that requires AgentDB (which is unavailable)
237
+ await registry.initialize({
238
+ backend: mockBackend,
239
+ controllers: { reasoningBank: true },
240
+ });
241
+ // ReasoningBank requires AgentDB, so it should fail or be unavailable
242
+ // The exact behavior depends on whether agentdb is importable
243
+ });
244
+ it('should handle null backend gracefully', async () => {
245
+ await registry.initialize({});
246
+ expect(registry.isInitialized()).toBe(true);
247
+ expect(registry.getBackend()).toBeNull();
248
+ });
249
+ it('should isolate controller failures from each other', async () => {
250
+ // Initialize with backend - learningBridge and tieredCache should work
251
+ await registry.initialize({ backend: mockBackend });
252
+ // LearningBridge should be available (it only needs backend)
253
+ const bridge = registry.get('learningBridge');
254
+ expect(bridge).toBeInstanceOf(LearningBridge);
255
+ // TieredCache should be available
256
+ const cache = registry.get('tieredCache');
257
+ expect(cache).toBeInstanceOf(TieredCacheManager);
258
+ });
259
+ });
260
+ // ----- Config-Driven Activation -----
261
+ describe('config-driven activation', () => {
262
+ it('should respect explicit controller enable/disable', async () => {
263
+ await registry.initialize({
264
+ backend: mockBackend,
265
+ controllers: {
266
+ learningBridge: false,
267
+ tieredCache: true,
268
+ },
269
+ });
270
+ expect(registry.isEnabled('learningBridge')).toBe(false);
271
+ expect(registry.isEnabled('tieredCache')).toBe(true);
272
+ });
273
+ it('should enable learningBridge by default when backend is available', async () => {
274
+ await registry.initialize({ backend: mockBackend });
275
+ expect(registry.isEnabled('learningBridge')).toBe(true);
276
+ });
277
+ it('should enable tieredCache by default', async () => {
278
+ await registry.initialize({ backend: mockBackend });
279
+ expect(registry.isEnabled('tieredCache')).toBe(true);
280
+ });
281
+ it('should pass SONA mode to LearningBridge', async () => {
282
+ await registry.initialize({
283
+ backend: mockBackend,
284
+ neural: { enabled: true, sonaMode: 'research' },
285
+ });
286
+ const bridge = registry.get('learningBridge');
287
+ expect(bridge).toBeInstanceOf(LearningBridge);
288
+ });
289
+ it('should pass memoryGraph config', async () => {
290
+ await registry.initialize({
291
+ backend: mockBackend,
292
+ memory: {
293
+ memoryGraph: { pageRankDamping: 0.9, maxNodes: 1000 },
294
+ },
295
+ });
296
+ const graph = registry.get('memoryGraph');
297
+ expect(graph).toBeInstanceOf(MemoryGraph);
298
+ });
299
+ it('should pass tieredCache config', async () => {
300
+ await registry.initialize({
301
+ backend: mockBackend,
302
+ memory: {
303
+ tieredCache: { maxSize: 5000, ttl: 60000 },
304
+ },
305
+ });
306
+ const cache = registry.get('tieredCache');
307
+ expect(cache).toBeInstanceOf(TieredCacheManager);
308
+ });
309
+ it('should not enable optional controllers by default', async () => {
310
+ await registry.initialize({ backend: mockBackend });
311
+ expect(registry.isEnabled('hybridSearch')).toBe(false);
312
+ expect(registry.isEnabled('federatedSession')).toBe(false);
313
+ // semanticRouter auto-enables when agentdb is available (since alpha.10)
314
+ expect(registry.isEnabled('sonaTrajectory')).toBe(false);
315
+ });
316
+ });
317
+ // ----- Controller Access -----
318
+ describe('controller access (get/isEnabled)', () => {
319
+ it('should return null for unregistered controllers', async () => {
320
+ await registry.initialize({ backend: mockBackend });
321
+ expect(registry.get('hybridSearch')).toBeNull();
322
+ });
323
+ it('should return typed controller instances', async () => {
324
+ await registry.initialize({ backend: mockBackend });
325
+ const bridge = registry.get('learningBridge');
326
+ if (bridge) {
327
+ expect(typeof bridge.consolidate).toBe('function');
328
+ expect(typeof bridge.getStats).toBe('function');
329
+ }
330
+ });
331
+ it('should return false for disabled controllers', async () => {
332
+ await registry.initialize({
333
+ backend: mockBackend,
334
+ controllers: { learningBridge: false },
335
+ });
336
+ expect(registry.isEnabled('learningBridge')).toBe(false);
337
+ });
338
+ // Regression guard for ruvnet/ruflo#2019.
339
+ //
340
+ // agentdb@3.0.0-alpha.14's `getController()` switch only handles
341
+ // memory/reflexion/skills/causal/causalGraph and THROWS
342
+ // `Unknown controller: vectorBackend` for everything else. The
343
+ // registry's old try/catch silently swallowed that throw and
344
+ // returned null — so `vectorBackend` (and `graphAdapter`) reported
345
+ // `enabled: false` even though the field is right there on the
346
+ // agentdb instance. The fix probes `agentdb[name]` directly before
347
+ // falling back to getController.
348
+ it('vectorBackend/graphAdapter: prefers direct property over getController (issue #2019)', async () => {
349
+ const fakeVectorBackend = { kind: 'vectorBackend' };
350
+ const fakeGraphAdapter = { kind: 'graphAdapter' };
351
+ // Simulate agentdb@3.0.0-alpha.14: fields exist, but getController
352
+ // throws Unknown controller for anything not in the small switch.
353
+ const fakeAgentDb = {
354
+ vectorBackend: fakeVectorBackend,
355
+ graphAdapter: fakeGraphAdapter,
356
+ getController(name) {
357
+ if (name === 'memory' || name === 'reflexion')
358
+ return null;
359
+ throw new Error(`Unknown controller: ${name}`);
360
+ },
361
+ };
362
+ await registry.initialize({
363
+ backend: mockBackend,
364
+ agentdb: fakeAgentDb,
365
+ });
366
+ // Both controllers must be reachable via the direct property
367
+ // probe, NOT silently null because getController threw.
368
+ expect(registry.get('vectorBackend')).toBe(fakeVectorBackend);
369
+ expect(registry.get('graphAdapter')).toBe(fakeGraphAdapter);
370
+ expect(registry.isEnabled('vectorBackend')).toBe(true);
371
+ expect(registry.isEnabled('graphAdapter')).toBe(true);
372
+ });
373
+ // Companion guard: if a future agentdb exposes these via
374
+ // getController instead of as direct fields, we must still find
375
+ // them — proves the fallback path stays intact.
376
+ it('vectorBackend: falls back to getController when no direct property exists (issue #2019)', async () => {
377
+ const fakeVectorBackend = { kind: 'vectorBackend-via-controller' };
378
+ const fakeAgentDb = {
379
+ // No `.vectorBackend` field at all.
380
+ getController(name) {
381
+ if (name === 'vectorBackend')
382
+ return fakeVectorBackend;
383
+ return null;
384
+ },
385
+ };
386
+ await registry.initialize({
387
+ backend: mockBackend,
388
+ agentdb: fakeAgentDb,
389
+ });
390
+ expect(registry.get('vectorBackend')).toBe(fakeVectorBackend);
391
+ expect(registry.isEnabled('vectorBackend')).toBe(true);
392
+ });
393
+ });
394
+ // ----- Health Check -----
395
+ describe('health check', () => {
396
+ it('should return healthy when controllers are active', async () => {
397
+ await registry.initialize({ backend: mockBackend });
398
+ const report = await registry.healthCheck();
399
+ expect(report.timestamp).toBeGreaterThan(0);
400
+ expect(report.initTimeMs).toBeGreaterThanOrEqual(0);
401
+ expect(report.controllers).toBeInstanceOf(Array);
402
+ });
403
+ it('should report active and total controller counts', async () => {
404
+ await registry.initialize({ backend: mockBackend });
405
+ const report = await registry.healthCheck();
406
+ expect(report.activeControllers).toBeGreaterThanOrEqual(0);
407
+ expect(report.totalControllers).toBeGreaterThanOrEqual(report.activeControllers);
408
+ });
409
+ it('should report agentdb availability', async () => {
410
+ await registry.initialize({ backend: mockBackend });
411
+ const report = await registry.healthCheck();
412
+ expect(typeof report.agentdbAvailable).toBe('boolean');
413
+ });
414
+ it('should classify status correctly', async () => {
415
+ await registry.initialize({ backend: mockBackend });
416
+ const report = await registry.healthCheck();
417
+ expect(['healthy', 'degraded', 'unhealthy']).toContain(report.status);
418
+ });
419
+ it('should include individual controller health', async () => {
420
+ await registry.initialize({ backend: mockBackend });
421
+ const report = await registry.healthCheck();
422
+ for (const controller of report.controllers) {
423
+ expect(controller).toHaveProperty('name');
424
+ expect(controller).toHaveProperty('status');
425
+ expect(controller).toHaveProperty('initTimeMs');
426
+ expect(['healthy', 'degraded', 'unavailable']).toContain(controller.status);
427
+ }
428
+ });
429
+ });
430
+ // ----- Shutdown -----
431
+ describe('shutdown', () => {
432
+ it('should shutdown cleanly', async () => {
433
+ await registry.initialize({ backend: mockBackend });
434
+ await registry.shutdown();
435
+ expect(registry.isInitialized()).toBe(false);
436
+ });
437
+ it('should emit shutdown event', async () => {
438
+ const handler = vi.fn();
439
+ registry.on('shutdown', handler);
440
+ await registry.initialize({ backend: mockBackend });
441
+ await registry.shutdown();
442
+ expect(handler).toHaveBeenCalledOnce();
443
+ });
444
+ it('should handle double shutdown', async () => {
445
+ await registry.initialize({ backend: mockBackend });
446
+ await registry.shutdown();
447
+ await registry.shutdown(); // Should be a no-op
448
+ expect(registry.isInitialized()).toBe(false);
449
+ });
450
+ it('should handle shutdown without initialization', async () => {
451
+ await registry.shutdown(); // Should be a no-op
452
+ expect(registry.isInitialized()).toBe(false);
453
+ });
454
+ it('should clean up controllers', async () => {
455
+ await registry.initialize({ backend: mockBackend });
456
+ const countBefore = registry.getActiveCount();
457
+ await registry.shutdown();
458
+ expect(registry.getActiveCount()).toBe(0);
459
+ });
460
+ it('should allow re-initialization after shutdown', async () => {
461
+ await registry.initialize({ backend: mockBackend });
462
+ await registry.shutdown();
463
+ await registry.initialize({ backend: mockBackend });
464
+ expect(registry.isInitialized()).toBe(true);
465
+ });
466
+ });
467
+ // ----- Controller Listing -----
468
+ describe('listControllers', () => {
469
+ it('should return list of all registered controllers', async () => {
470
+ await registry.initialize({ backend: mockBackend });
471
+ const list = registry.listControllers();
472
+ expect(list).toBeInstanceOf(Array);
473
+ for (const item of list) {
474
+ expect(item).toHaveProperty('name');
475
+ expect(item).toHaveProperty('enabled');
476
+ expect(item).toHaveProperty('level');
477
+ expect(typeof item.name).toBe('string');
478
+ expect(typeof item.enabled).toBe('boolean');
479
+ expect(typeof item.level).toBe('number');
480
+ }
481
+ });
482
+ });
483
+ // ----- AgentDB Integration -----
484
+ describe('AgentDB integration', () => {
485
+ it('should handle missing agentdb module', async () => {
486
+ // With no agentdb installed, should still work
487
+ await registry.initialize({ backend: mockBackend });
488
+ expect(registry.isInitialized()).toBe(true);
489
+ });
490
+ it('should return null AgentDB when unavailable', async () => {
491
+ await registry.initialize({ backend: mockBackend });
492
+ // May or may not be available depending on test environment
493
+ const agentdb = registry.getAgentDB();
494
+ // Just ensure it doesn't throw
495
+ expect(agentdb === null || agentdb !== null).toBe(true);
496
+ });
497
+ });
498
+ // ----- Cross-Platform Path Handling -----
499
+ describe('cross-platform compatibility', () => {
500
+ it('should handle forward slash paths', async () => {
501
+ await registry.initialize({
502
+ backend: mockBackend,
503
+ dbPath: '/tmp/test/memory.db',
504
+ });
505
+ expect(registry.isInitialized()).toBe(true);
506
+ });
507
+ it('should handle relative paths', async () => {
508
+ await registry.initialize({
509
+ backend: mockBackend,
510
+ dbPath: './data/memory.db',
511
+ });
512
+ expect(registry.isInitialized()).toBe(true);
513
+ });
514
+ it('should handle :memory: path', async () => {
515
+ await registry.initialize({
516
+ backend: mockBackend,
517
+ dbPath: ':memory:',
518
+ });
519
+ expect(registry.isInitialized()).toBe(true);
520
+ });
521
+ });
522
+ // ----- LearningBridge Integration -----
523
+ describe('LearningBridge via registry', () => {
524
+ it('should create LearningBridge with backend', async () => {
525
+ await registry.initialize({ backend: mockBackend });
526
+ const bridge = registry.get('learningBridge');
527
+ expect(bridge).toBeInstanceOf(LearningBridge);
528
+ });
529
+ it('should pass config to LearningBridge', async () => {
530
+ await registry.initialize({
531
+ backend: mockBackend,
532
+ memory: {
533
+ learningBridge: {
534
+ sonaMode: 'edge',
535
+ confidenceDecayRate: 0.01,
536
+ accessBoostAmount: 0.05,
537
+ consolidationThreshold: 5,
538
+ },
539
+ },
540
+ });
541
+ const bridge = registry.get('learningBridge');
542
+ expect(bridge).toBeInstanceOf(LearningBridge);
543
+ const stats = bridge.getStats();
544
+ expect(stats.totalTrajectories).toBe(0);
545
+ expect(stats.neuralAvailable).toBe(false); // No neural module in tests
546
+ });
547
+ it('should not create LearningBridge without backend', async () => {
548
+ await registry.initialize({});
549
+ const bridge = registry.get('learningBridge');
550
+ // Without backend, LearningBridge returns null
551
+ expect(bridge).toBeNull();
552
+ });
553
+ });
554
+ // ----- MemoryGraph Integration -----
555
+ describe('MemoryGraph via registry', () => {
556
+ it('should create MemoryGraph when configured', async () => {
557
+ await registry.initialize({
558
+ backend: mockBackend,
559
+ memory: {
560
+ memoryGraph: { pageRankDamping: 0.85, maxNodes: 5000 },
561
+ },
562
+ });
563
+ const graph = registry.get('memoryGraph');
564
+ expect(graph).toBeInstanceOf(MemoryGraph);
565
+ });
566
+ it('should report graph stats', async () => {
567
+ await registry.initialize({
568
+ backend: mockBackend,
569
+ memory: {
570
+ memoryGraph: {},
571
+ },
572
+ });
573
+ const graph = registry.get('memoryGraph');
574
+ if (graph) {
575
+ const stats = graph.getStats();
576
+ expect(stats.nodeCount).toBeGreaterThanOrEqual(0);
577
+ expect(stats.edgeCount).toBeGreaterThanOrEqual(0);
578
+ }
579
+ });
580
+ });
581
+ // ----- TieredCache Integration -----
582
+ describe('TieredCacheManager via registry', () => {
583
+ it('should create TieredCacheManager', async () => {
584
+ await registry.initialize({ backend: mockBackend });
585
+ const cache = registry.get('tieredCache');
586
+ expect(cache).toBeInstanceOf(TieredCacheManager);
587
+ });
588
+ it('should respect cache config', async () => {
589
+ await registry.initialize({
590
+ backend: mockBackend,
591
+ memory: {
592
+ tieredCache: { maxSize: 500, ttl: 10000 },
593
+ },
594
+ });
595
+ const cache = registry.get('tieredCache');
596
+ expect(cache).toBeInstanceOf(TieredCacheManager);
597
+ });
598
+ });
599
+ // ----- Event Emission -----
600
+ describe('events', () => {
601
+ it('should emit agentdb:unavailable when module missing', async () => {
602
+ const handler = vi.fn();
603
+ registry.on('agentdb:unavailable', handler);
604
+ await registry.initialize({ backend: mockBackend });
605
+ // AgentDB may or may not be available in test environment
606
+ // Just verify the listener doesn't break anything
607
+ });
608
+ it('should emit all lifecycle events', async () => {
609
+ const events = [];
610
+ registry.on('initialized', () => events.push('initialized'));
611
+ registry.on('shutdown', () => events.push('shutdown'));
612
+ await registry.initialize({ backend: mockBackend });
613
+ expect(events).toContain('initialized');
614
+ await registry.shutdown();
615
+ expect(events).toContain('shutdown');
616
+ });
617
+ });
618
+ // ----- Performance -----
619
+ describe('performance', () => {
620
+ it('should initialize within 500ms', async () => {
621
+ const start = performance.now();
622
+ await registry.initialize({ backend: mockBackend });
623
+ const duration = performance.now() - start;
624
+ // Per ADR-053: "No regression beyond 10% in CLI startup time"
625
+ expect(duration).toBeLessThan(500);
626
+ });
627
+ it('should shutdown within 100ms', async () => {
628
+ await registry.initialize({ backend: mockBackend });
629
+ const start = performance.now();
630
+ await registry.shutdown();
631
+ const duration = performance.now() - start;
632
+ expect(duration).toBeLessThan(100);
633
+ });
634
+ it('should have low overhead for controller access', async () => {
635
+ await registry.initialize({ backend: mockBackend });
636
+ const start = performance.now();
637
+ for (let i = 0; i < 1000; i++) {
638
+ registry.get('learningBridge');
639
+ registry.get('tieredCache');
640
+ registry.isEnabled('reasoningBank');
641
+ }
642
+ const duration = performance.now() - start;
643
+ // 3000 lookups should complete in under 10ms
644
+ expect(duration).toBeLessThan(10);
645
+ });
646
+ });
647
+ });
648
+ // ===== HybridBackend Proxy Methods Tests =====
649
+ describe('HybridBackend proxy methods', () => {
650
+ it('should export recordFeedback method', async () => {
651
+ const { HybridBackend } = await import('./hybrid-backend.js');
652
+ const backend = new HybridBackend();
653
+ expect(typeof backend.recordFeedback).toBe('function');
654
+ });
655
+ it('should export verifyWitnessChain method', async () => {
656
+ const { HybridBackend } = await import('./hybrid-backend.js');
657
+ const backend = new HybridBackend();
658
+ expect(typeof backend.verifyWitnessChain).toBe('function');
659
+ });
660
+ it('should export getWitnessChain method', async () => {
661
+ const { HybridBackend } = await import('./hybrid-backend.js');
662
+ const backend = new HybridBackend();
663
+ expect(typeof backend.getWitnessChain).toBe('function');
664
+ });
665
+ it('should return false for recordFeedback when AgentDB unavailable', async () => {
666
+ const { HybridBackend } = await import('./hybrid-backend.js');
667
+ const backend = new HybridBackend();
668
+ await backend.initialize();
669
+ const result = await backend.recordFeedback('entry-1', { score: 0.9 });
670
+ expect(result).toBe(false);
671
+ await backend.shutdown();
672
+ });
673
+ it('should return invalid for verifyWitnessChain when AgentDB unavailable', async () => {
674
+ const { HybridBackend } = await import('./hybrid-backend.js');
675
+ const backend = new HybridBackend();
676
+ await backend.initialize();
677
+ const result = await backend.verifyWitnessChain('entry-1');
678
+ expect(result.valid).toBe(false);
679
+ expect(result.errors).toContain('AgentDB not available');
680
+ await backend.shutdown();
681
+ });
682
+ it('should return empty array for getWitnessChain when AgentDB unavailable', async () => {
683
+ const { HybridBackend } = await import('./hybrid-backend.js');
684
+ const backend = new HybridBackend();
685
+ await backend.initialize();
686
+ const result = await backend.getWitnessChain('entry-1');
687
+ expect(result).toEqual([]);
688
+ await backend.shutdown();
689
+ });
690
+ });
691
+ //# sourceMappingURL=controller-registry.test.js.map