@sylphx/flow 1.0.1 → 1.0.3

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 (229) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +10 -9
  3. package/src/commands/codebase-command.ts +168 -0
  4. package/src/commands/flow-command.ts +1137 -0
  5. package/src/commands/flow-orchestrator.ts +296 -0
  6. package/src/commands/hook-command.ts +444 -0
  7. package/src/commands/init-command.ts +92 -0
  8. package/src/commands/init-core.ts +322 -0
  9. package/src/commands/knowledge-command.ts +161 -0
  10. package/src/commands/run-command.ts +120 -0
  11. package/src/components/benchmark-monitor.tsx +331 -0
  12. package/src/components/reindex-progress.tsx +261 -0
  13. package/src/composables/functional/index.ts +14 -0
  14. package/src/composables/functional/useEnvironment.ts +171 -0
  15. package/src/composables/functional/useFileSystem.ts +139 -0
  16. package/src/composables/index.ts +5 -0
  17. package/src/composables/useEnv.ts +13 -0
  18. package/src/composables/useRuntimeConfig.ts +27 -0
  19. package/src/composables/useTargetConfig.ts +45 -0
  20. package/src/config/ai-config.ts +376 -0
  21. package/src/config/constants.ts +35 -0
  22. package/src/config/index.ts +27 -0
  23. package/src/config/rules.ts +43 -0
  24. package/src/config/servers.ts +371 -0
  25. package/src/config/targets.ts +126 -0
  26. package/src/core/agent-loader.ts +141 -0
  27. package/src/core/agent-manager.ts +174 -0
  28. package/src/core/ai-sdk.ts +603 -0
  29. package/src/core/app-factory.ts +381 -0
  30. package/src/core/builtin-agents.ts +9 -0
  31. package/src/core/command-system.ts +550 -0
  32. package/src/core/config-system.ts +550 -0
  33. package/src/core/connection-pool.ts +390 -0
  34. package/src/core/di-container.ts +155 -0
  35. package/src/core/error-handling.ts +519 -0
  36. package/src/core/formatting/bytes.test.ts +115 -0
  37. package/src/core/formatting/bytes.ts +64 -0
  38. package/src/core/functional/async.ts +313 -0
  39. package/src/core/functional/either.ts +109 -0
  40. package/src/core/functional/error-handler.ts +135 -0
  41. package/src/core/functional/error-types.ts +311 -0
  42. package/src/core/functional/index.ts +19 -0
  43. package/src/core/functional/option.ts +142 -0
  44. package/src/core/functional/pipe.ts +189 -0
  45. package/src/core/functional/result.ts +204 -0
  46. package/src/core/functional/validation.ts +138 -0
  47. package/src/core/headless-display.ts +96 -0
  48. package/src/core/index.ts +6 -0
  49. package/src/core/installers/file-installer.ts +303 -0
  50. package/src/core/installers/mcp-installer.ts +213 -0
  51. package/src/core/interfaces/index.ts +22 -0
  52. package/src/core/interfaces/repository.interface.ts +91 -0
  53. package/src/core/interfaces/service.interface.ts +133 -0
  54. package/src/core/interfaces.ts +129 -0
  55. package/src/core/loop-controller.ts +200 -0
  56. package/src/core/result.ts +351 -0
  57. package/src/core/rule-loader.ts +147 -0
  58. package/src/core/rule-manager.ts +240 -0
  59. package/src/core/service-config.ts +252 -0
  60. package/src/core/session-service.ts +121 -0
  61. package/src/core/state-detector.ts +389 -0
  62. package/src/core/storage-factory.ts +115 -0
  63. package/src/core/stream-handler.ts +288 -0
  64. package/src/core/target-manager.ts +161 -0
  65. package/src/core/type-utils.ts +427 -0
  66. package/src/core/unified-storage.ts +456 -0
  67. package/src/core/upgrade-manager.ts +300 -0
  68. package/src/core/validation/limit.test.ts +155 -0
  69. package/src/core/validation/limit.ts +46 -0
  70. package/src/core/validation/query.test.ts +44 -0
  71. package/src/core/validation/query.ts +20 -0
  72. package/src/db/auto-migrate.ts +322 -0
  73. package/src/db/base-database-client.ts +144 -0
  74. package/src/db/cache-db.ts +218 -0
  75. package/src/db/cache-schema.ts +75 -0
  76. package/src/db/database.ts +70 -0
  77. package/src/db/index.ts +252 -0
  78. package/src/db/memory-db.ts +153 -0
  79. package/src/db/memory-schema.ts +29 -0
  80. package/src/db/schema.ts +289 -0
  81. package/src/db/session-repository.ts +733 -0
  82. package/src/domains/codebase/index.ts +5 -0
  83. package/src/domains/codebase/tools.ts +139 -0
  84. package/src/domains/index.ts +8 -0
  85. package/src/domains/knowledge/index.ts +10 -0
  86. package/src/domains/knowledge/resources.ts +537 -0
  87. package/src/domains/knowledge/tools.ts +174 -0
  88. package/src/domains/utilities/index.ts +6 -0
  89. package/src/domains/utilities/time/index.ts +5 -0
  90. package/src/domains/utilities/time/tools.ts +291 -0
  91. package/src/index.ts +211 -0
  92. package/src/services/agent-service.ts +273 -0
  93. package/src/services/claude-config-service.ts +252 -0
  94. package/src/services/config-service.ts +258 -0
  95. package/src/services/evaluation-service.ts +271 -0
  96. package/src/services/functional/evaluation-logic.ts +296 -0
  97. package/src/services/functional/file-processor.ts +273 -0
  98. package/src/services/functional/index.ts +12 -0
  99. package/src/services/index.ts +13 -0
  100. package/src/services/mcp-service.ts +432 -0
  101. package/src/services/memory.service.ts +476 -0
  102. package/src/services/search/base-indexer.ts +156 -0
  103. package/src/services/search/codebase-indexer-types.ts +38 -0
  104. package/src/services/search/codebase-indexer.ts +647 -0
  105. package/src/services/search/embeddings-provider.ts +455 -0
  106. package/src/services/search/embeddings.ts +316 -0
  107. package/src/services/search/functional-indexer.ts +323 -0
  108. package/src/services/search/index.ts +27 -0
  109. package/src/services/search/indexer.ts +380 -0
  110. package/src/services/search/knowledge-indexer.ts +422 -0
  111. package/src/services/search/semantic-search.ts +244 -0
  112. package/src/services/search/tfidf.ts +559 -0
  113. package/src/services/search/unified-search-service.ts +888 -0
  114. package/src/services/smart-config-service.ts +385 -0
  115. package/src/services/storage/cache-storage.ts +487 -0
  116. package/src/services/storage/drizzle-storage.ts +581 -0
  117. package/src/services/storage/index.ts +15 -0
  118. package/src/services/storage/lancedb-vector-storage.ts +494 -0
  119. package/src/services/storage/memory-storage.ts +268 -0
  120. package/src/services/storage/separated-storage.ts +467 -0
  121. package/src/services/storage/vector-storage.ts +13 -0
  122. package/src/shared/agents/index.ts +63 -0
  123. package/src/shared/files/index.ts +99 -0
  124. package/src/shared/index.ts +32 -0
  125. package/src/shared/logging/index.ts +24 -0
  126. package/src/shared/processing/index.ts +153 -0
  127. package/src/shared/types/index.ts +25 -0
  128. package/src/targets/claude-code.ts +574 -0
  129. package/src/targets/functional/claude-code-logic.ts +185 -0
  130. package/src/targets/functional/index.ts +6 -0
  131. package/src/targets/opencode.ts +529 -0
  132. package/src/types/agent.types.ts +32 -0
  133. package/src/types/api/batch.ts +108 -0
  134. package/src/types/api/errors.ts +118 -0
  135. package/src/types/api/index.ts +55 -0
  136. package/src/types/api/requests.ts +76 -0
  137. package/src/types/api/responses.ts +180 -0
  138. package/src/types/api/websockets.ts +85 -0
  139. package/src/types/api.types.ts +9 -0
  140. package/src/types/benchmark.ts +49 -0
  141. package/src/types/cli.types.ts +87 -0
  142. package/src/types/common.types.ts +35 -0
  143. package/src/types/database.types.ts +510 -0
  144. package/src/types/mcp-config.types.ts +448 -0
  145. package/src/types/mcp.types.ts +69 -0
  146. package/src/types/memory-types.ts +63 -0
  147. package/src/types/provider.types.ts +28 -0
  148. package/src/types/rule.types.ts +24 -0
  149. package/src/types/session.types.ts +214 -0
  150. package/src/types/target-config.types.ts +295 -0
  151. package/src/types/target.types.ts +140 -0
  152. package/src/types/todo.types.ts +25 -0
  153. package/src/types.ts +40 -0
  154. package/src/utils/advanced-tokenizer.ts +191 -0
  155. package/src/utils/agent-enhancer.ts +114 -0
  156. package/src/utils/ai-model-fetcher.ts +19 -0
  157. package/src/utils/async-file-operations.ts +516 -0
  158. package/src/utils/audio-player.ts +345 -0
  159. package/src/utils/cli-output.ts +266 -0
  160. package/src/utils/codebase-helpers.ts +211 -0
  161. package/src/utils/console-ui.ts +79 -0
  162. package/src/utils/database-errors.ts +140 -0
  163. package/src/utils/debug-logger.ts +49 -0
  164. package/src/utils/error-handler.ts +53 -0
  165. package/src/utils/file-operations.ts +310 -0
  166. package/src/utils/file-scanner.ts +259 -0
  167. package/src/utils/functional/array.ts +355 -0
  168. package/src/utils/functional/index.ts +15 -0
  169. package/src/utils/functional/object.ts +279 -0
  170. package/src/utils/functional/string.ts +281 -0
  171. package/src/utils/functional.ts +543 -0
  172. package/src/utils/help.ts +20 -0
  173. package/src/utils/immutable-cache.ts +106 -0
  174. package/src/utils/index.ts +78 -0
  175. package/src/utils/jsonc.ts +158 -0
  176. package/src/utils/logger.ts +396 -0
  177. package/src/utils/mcp-config.ts +249 -0
  178. package/src/utils/memory-tui.ts +414 -0
  179. package/src/utils/models-dev.ts +91 -0
  180. package/src/utils/notifications.ts +169 -0
  181. package/src/utils/object-utils.ts +51 -0
  182. package/src/utils/parallel-operations.ts +487 -0
  183. package/src/utils/paths.ts +143 -0
  184. package/src/utils/process-manager.ts +155 -0
  185. package/src/utils/prompts.ts +120 -0
  186. package/src/utils/search-tool-builder.ts +214 -0
  187. package/src/utils/secret-utils.ts +179 -0
  188. package/src/utils/security.ts +537 -0
  189. package/src/utils/session-manager.ts +168 -0
  190. package/src/utils/session-title.ts +87 -0
  191. package/src/utils/settings.ts +182 -0
  192. package/src/utils/simplified-errors.ts +410 -0
  193. package/src/utils/sync-utils.ts +159 -0
  194. package/src/utils/target-config.ts +570 -0
  195. package/src/utils/target-utils.ts +394 -0
  196. package/src/utils/template-engine.ts +94 -0
  197. package/src/utils/test-audio.ts +71 -0
  198. package/src/utils/todo-context.ts +46 -0
  199. package/src/utils/token-counter.ts +288 -0
  200. package/dist/index.d.ts +0 -10
  201. package/dist/index.js +0 -59554
  202. package/dist/lancedb.linux-x64-gnu-b7f0jgsz.node +0 -0
  203. package/dist/lancedb.linux-x64-musl-tgcv22rx.node +0 -0
  204. package/dist/shared/chunk-25dwp0dp.js +0 -89
  205. package/dist/shared/chunk-3pjb6063.js +0 -208
  206. package/dist/shared/chunk-4d6ydpw7.js +0 -2854
  207. package/dist/shared/chunk-4wjcadjk.js +0 -225
  208. package/dist/shared/chunk-5j4w74t6.js +0 -30
  209. package/dist/shared/chunk-5j8m3dh3.js +0 -58
  210. package/dist/shared/chunk-5thh3qem.js +0 -91
  211. package/dist/shared/chunk-6g9xy73m.js +0 -252
  212. package/dist/shared/chunk-7eq34c42.js +0 -23
  213. package/dist/shared/chunk-c2gwgx3r.js +0 -115
  214. package/dist/shared/chunk-cjd3mk4c.js +0 -1320
  215. package/dist/shared/chunk-g5cv6703.js +0 -368
  216. package/dist/shared/chunk-hpkhykhq.js +0 -574
  217. package/dist/shared/chunk-m2322pdk.js +0 -122
  218. package/dist/shared/chunk-nd5fdvaq.js +0 -26
  219. package/dist/shared/chunk-pgd3m6zf.js +0 -108
  220. package/dist/shared/chunk-qk8n91hw.js +0 -494
  221. package/dist/shared/chunk-rkkn8szp.js +0 -16855
  222. package/dist/shared/chunk-t16rfxh0.js +0 -61
  223. package/dist/shared/chunk-t4fbfa5v.js +0 -19
  224. package/dist/shared/chunk-t77h86w6.js +0 -276
  225. package/dist/shared/chunk-v0ez4aef.js +0 -71
  226. package/dist/shared/chunk-v29j2r3s.js +0 -32051
  227. package/dist/shared/chunk-vfbc6ew5.js +0 -765
  228. package/dist/shared/chunk-vmeqwm1c.js +0 -204
  229. package/dist/shared/chunk-x66eh37x.js +0 -137
@@ -0,0 +1,476 @@
1
+ /**
2
+ * Memory Service Layer
3
+ *
4
+ * Business logic layer for memory operations
5
+ * Handles memory management with validation, caching, and business rules
6
+ * Uses functional Result type for error handling
7
+ */
8
+
9
+ import { type Result, tryCatchAsync } from '../core/functional/result.js';
10
+ import type { ILogger } from '../core/interfaces.js';
11
+ import {
12
+ MemoryError,
13
+ type MemoryErrorType,
14
+ MemoryNotFoundError,
15
+ MemorySizeError,
16
+ MemoryValidationError,
17
+ } from '../errors/memory-errors.js';
18
+ import type {
19
+ CreateMemoryData,
20
+ MemoryEntry,
21
+ MemoryRepository,
22
+ MemorySearchParams,
23
+ } from '../repositories/memory.repository.js';
24
+ import type { MemoryStatsResult, MemoryValue } from '../types/memory-types.js';
25
+ import {
26
+ type CacheState,
27
+ cacheDelete,
28
+ cacheDeleteWhere,
29
+ cacheEnforceLimit,
30
+ cacheGet,
31
+ cacheSet,
32
+ createCache,
33
+ } from '../utils/immutable-cache.js';
34
+
35
+ export interface MemoryServiceConfig {
36
+ defaultNamespace?: string;
37
+ maxEntrySize?: number;
38
+ enableCaching?: boolean;
39
+ cacheMaxSize?: number;
40
+ retentionPolicy?: {
41
+ enabled: boolean;
42
+ maxAge: number; // milliseconds
43
+ cleanupInterval?: number; // milliseconds
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Dependencies for MemoryService
49
+ */
50
+ export interface MemoryServiceDeps {
51
+ readonly repository: MemoryRepository;
52
+ readonly logger: ILogger;
53
+ }
54
+
55
+ /**
56
+ * Internal state for MemoryService
57
+ */
58
+ interface MemoryServiceState {
59
+ readonly cache: CacheState<string, MemoryEntry>;
60
+ readonly cleanupTimer?: NodeJS.Timeout;
61
+ }
62
+
63
+ /**
64
+ * MemoryService Interface
65
+ * Business logic layer for memory operations
66
+ */
67
+ export interface MemoryService {
68
+ readonly get: (key: string, namespace?: string) => Promise<Result<MemoryValue, MemoryErrorType>>;
69
+ readonly set: (
70
+ key: string,
71
+ value: string,
72
+ namespace?: string
73
+ ) => Promise<Result<MemoryEntry, MemoryErrorType>>;
74
+ readonly delete: (key: string, namespace?: string) => Promise<Result<boolean, MemoryError>>;
75
+ readonly list: (namespace?: string) => Promise<Result<string[], MemoryError>>;
76
+ readonly search: (params: MemorySearchParams) => Promise<Result<MemoryEntry[], MemoryError>>;
77
+ readonly clear: (namespace?: string) => Promise<Result<number, MemoryError>>;
78
+ readonly getStats: () => Promise<Result<MemoryStatsResult, MemoryError>>;
79
+ readonly bulkSet: (
80
+ entries: Array<{ key: string; value: string; namespace?: string }>,
81
+ namespace?: string
82
+ ) => Promise<Result<MemoryEntry[], MemoryError>>;
83
+ readonly dispose: () => Promise<void>;
84
+ }
85
+
86
+ /**
87
+ * Create Memory Service (Factory Function)
88
+ * Handles memory management with validation, caching, and business rules
89
+ */
90
+ export const createMemoryService = (
91
+ deps: MemoryServiceDeps,
92
+ config: MemoryServiceConfig = {}
93
+ ): MemoryService => {
94
+ // Service configuration in closure
95
+ const serviceConfig: MemoryServiceConfig = {
96
+ defaultNamespace: 'default',
97
+ enableCaching: true,
98
+ cacheMaxSize: 1000,
99
+ ...config,
100
+ };
101
+
102
+ // Mutable state in closure (will be updated immutably)
103
+ let state: MemoryServiceState = {
104
+ cache: createCache(),
105
+ cleanupTimer: undefined,
106
+ };
107
+
108
+ // Helper: Update state immutably
109
+ const updateState = (updates: Partial<MemoryServiceState>): void => {
110
+ state = { ...state, ...updates };
111
+ };
112
+
113
+ /**
114
+ * Get a memory value
115
+ */
116
+ const get = async (
117
+ key: string,
118
+ namespace: string = serviceConfig.defaultNamespace || 'default'
119
+ ): Promise<Result<MemoryValue, MemoryErrorType>> => {
120
+ return await tryCatchAsync(
121
+ async () => {
122
+ // Check cache first if enabled
123
+ if (serviceConfig.enableCaching) {
124
+ const cacheKey = `${namespace}:${key}`;
125
+ // FUNCTIONAL: Use immutable cache get
126
+ const cached = cacheGet(state.cache, cacheKey);
127
+ if (cached) {
128
+ return {
129
+ value: cached.value,
130
+ metadata: {
131
+ namespace,
132
+ timestamp: cached.timestamp,
133
+ size: cached.value.length,
134
+ },
135
+ };
136
+ }
137
+ }
138
+
139
+ // Fetch from repository
140
+ const entry = await deps.repository.getByKey(key, namespace);
141
+
142
+ if (!entry) {
143
+ throw new MemoryNotFoundError(key, namespace);
144
+ }
145
+
146
+ // Cache the result if enabled
147
+ if (serviceConfig.enableCaching) {
148
+ updateCache(entry);
149
+ }
150
+
151
+ return {
152
+ value: entry.value,
153
+ metadata: {
154
+ namespace,
155
+ timestamp: entry.timestamp,
156
+ size: entry.value.length,
157
+ },
158
+ };
159
+ },
160
+ (error) => {
161
+ if (error instanceof MemoryNotFoundError) {
162
+ return error;
163
+ }
164
+ deps.logger.error(`Failed to get memory entry: ${key}`, error);
165
+ return new MemoryError(`Failed to get memory entry: ${key}`, error);
166
+ }
167
+ );
168
+ };
169
+
170
+ /**
171
+ * Set a memory value
172
+ */
173
+ const set = async (
174
+ key: string,
175
+ value: string,
176
+ namespace: string = serviceConfig.defaultNamespace || 'default'
177
+ ): Promise<Result<MemoryEntry, MemoryErrorType>> => {
178
+ return await tryCatchAsync(
179
+ async () => {
180
+ // Validate inputs
181
+ const validationError = validateMemoryEntry(key, value);
182
+ if (validationError) {
183
+ throw validationError;
184
+ }
185
+
186
+ const timestamp = Date.now();
187
+ const data: CreateMemoryData = {
188
+ key,
189
+ namespace,
190
+ value,
191
+ timestamp,
192
+ };
193
+
194
+ // Store in repository
195
+ const entry = await deps.repository.setMemory(data);
196
+
197
+ // Update cache if enabled
198
+ if (serviceConfig.enableCaching) {
199
+ updateCache(entry);
200
+ enforceCacheLimit();
201
+ }
202
+
203
+ deps.logger.debug(`Memory entry set: ${key} in namespace: ${namespace}`);
204
+
205
+ return entry;
206
+ },
207
+ (error) => {
208
+ if (error instanceof MemoryValidationError || error instanceof MemorySizeError) {
209
+ return error;
210
+ }
211
+ deps.logger.error(`Failed to set memory entry: ${key}`, error);
212
+ return new MemoryError(`Failed to set memory entry: ${key}`, error);
213
+ }
214
+ );
215
+ };
216
+
217
+ /**
218
+ * Delete a memory entry
219
+ */
220
+ const deleteEntry = async (
221
+ key: string,
222
+ namespace: string = serviceConfig.defaultNamespace || 'default'
223
+ ): Promise<Result<boolean, MemoryError>> => {
224
+ return await tryCatchAsync(
225
+ async () => {
226
+ const deleted = await deps.repository.deleteMemory(key, namespace);
227
+
228
+ // Remove from cache if present
229
+ if (serviceConfig.enableCaching) {
230
+ const cacheKey = `${namespace}:${key}`;
231
+ // FUNCTIONAL: Use immutable cache delete, returns new state
232
+ updateState({ cache: cacheDelete(state.cache, cacheKey) });
233
+ }
234
+
235
+ deps.logger.debug(`Memory entry deleted: ${key} in namespace: ${namespace}`);
236
+
237
+ return deleted;
238
+ },
239
+ (error) => {
240
+ deps.logger.error(`Failed to delete memory entry: ${key}`, error);
241
+ return new MemoryError(`Failed to delete memory entry: ${key}`, error);
242
+ }
243
+ );
244
+ };
245
+
246
+ /**
247
+ * List all keys in a namespace
248
+ */
249
+ const list = async (
250
+ namespace: string = serviceConfig.defaultNamespace || 'default'
251
+ ): Promise<Result<string[], MemoryError>> => {
252
+ return await tryCatchAsync(
253
+ async () => {
254
+ const keys = await deps.repository.listKeys(namespace);
255
+ return keys;
256
+ },
257
+ (error) => {
258
+ deps.logger.error(`Failed to list keys in namespace: ${namespace}`, error);
259
+ return new MemoryError(`Failed to list keys in namespace: ${namespace}`, error);
260
+ }
261
+ );
262
+ };
263
+
264
+ /**
265
+ * Search memory entries
266
+ */
267
+ const search = async (
268
+ params: MemorySearchParams
269
+ ): Promise<Result<MemoryEntry[], MemoryError>> => {
270
+ return await tryCatchAsync(
271
+ async () => {
272
+ const entries = await deps.repository.searchMemory(params);
273
+ return entries;
274
+ },
275
+ (error) => {
276
+ deps.logger.error('Failed to search memory entries', error);
277
+ return new MemoryError('Failed to search memory entries', error);
278
+ }
279
+ );
280
+ };
281
+
282
+ /**
283
+ * Clear all entries in a namespace
284
+ */
285
+ const clear = async (
286
+ namespace: string = serviceConfig.defaultNamespace || 'default'
287
+ ): Promise<Result<number, MemoryError>> => {
288
+ return await tryCatchAsync(
289
+ async () => {
290
+ const deletedCount = await deps.repository.clearNamespace(namespace);
291
+
292
+ // Clear cache entries for this namespace
293
+ if (serviceConfig.enableCaching) {
294
+ // FUNCTIONAL: Use immutable cache deleteWhere, returns new state
295
+ updateState({
296
+ cache: cacheDeleteWhere(state.cache, (key) => key.startsWith(`${namespace}:`)),
297
+ });
298
+ }
299
+
300
+ deps.logger.info(`Cleared ${deletedCount} entries from namespace: ${namespace}`);
301
+
302
+ return deletedCount;
303
+ },
304
+ (error) => {
305
+ deps.logger.error(`Failed to clear namespace: ${namespace}`, error);
306
+ return new MemoryError(`Failed to clear namespace: ${namespace}`, error);
307
+ }
308
+ );
309
+ };
310
+
311
+ /**
312
+ * Get memory statistics
313
+ */
314
+ const getStats = async (): Promise<Result<MemoryStatsResult, MemoryError>> => {
315
+ return await tryCatchAsync(
316
+ async () => {
317
+ const stats = await deps.repository.getStats();
318
+
319
+ return {
320
+ totalEntries: stats.totalEntries,
321
+ totalSize: stats.totalSize,
322
+ namespaces: stats.namespaces,
323
+ oldestEntry: stats.oldestEntry,
324
+ newestEntry: stats.newestEntry,
325
+ };
326
+ },
327
+ (error) => {
328
+ deps.logger.error('Failed to get memory statistics', error);
329
+ return new MemoryError('Failed to get memory statistics', error);
330
+ }
331
+ );
332
+ };
333
+
334
+ /**
335
+ * Perform bulk operations
336
+ */
337
+ const bulkSet = async (
338
+ entries: Array<{ key: string; value: string; namespace?: string }>,
339
+ namespace: string = serviceConfig.defaultNamespace || 'default'
340
+ ): Promise<Result<MemoryEntry[], MemoryError>> => {
341
+ return await tryCatchAsync(
342
+ async () => {
343
+ const results: MemoryEntry[] = [];
344
+ const errors: string[] = [];
345
+
346
+ for (const entry of entries) {
347
+ const result = await set(entry.key, entry.value, entry.namespace || namespace);
348
+ if (result._tag === 'Success') {
349
+ results.push(result.value);
350
+ } else {
351
+ errors.push(`${entry.key}: ${result.error.message}`);
352
+ }
353
+ }
354
+
355
+ if (errors.length > 0) {
356
+ deps.logger.warn(`Bulk set completed with ${errors.length} errors`, { errors });
357
+ throw new MemoryError(`Bulk set completed with errors: ${errors.join('; ')}`);
358
+ }
359
+
360
+ return results;
361
+ },
362
+ (error) => {
363
+ if (error instanceof MemoryError) {
364
+ return error;
365
+ }
366
+ deps.logger.error('Failed to perform bulk set', error);
367
+ return new MemoryError('Failed to perform bulk set', error);
368
+ }
369
+ );
370
+ };
371
+
372
+ /**
373
+ * Validate memory entry data
374
+ */
375
+ const validateMemoryEntry = (
376
+ key: string,
377
+ value: string
378
+ ): MemoryValidationError | MemorySizeError | null => {
379
+ if (!key || key.trim().length === 0) {
380
+ return new MemoryValidationError('Key cannot be empty', 'key', key);
381
+ }
382
+
383
+ if (key.length > 255) {
384
+ return new MemoryValidationError('Key cannot exceed 255 characters', 'key', key);
385
+ }
386
+
387
+ if (serviceConfig.maxEntrySize && value.length > serviceConfig.maxEntrySize) {
388
+ return new MemorySizeError(value.length, serviceConfig.maxEntrySize);
389
+ }
390
+
391
+ // Validate key format (no special characters that could cause issues)
392
+ if (!/^[a-zA-Z0-9._-]+$/.test(key)) {
393
+ return new MemoryValidationError(
394
+ 'Key can only contain alphanumeric characters, dots, hyphens, and underscores',
395
+ 'key',
396
+ key
397
+ );
398
+ }
399
+
400
+ return null;
401
+ };
402
+
403
+ /**
404
+ * Update cache with new entry
405
+ * FUNCTIONAL: Returns new cache state instead of mutating
406
+ */
407
+ const updateCache = (entry: MemoryEntry): void => {
408
+ const cacheKey = `${entry.namespace}:${entry.key}`;
409
+ // FUNCTIONAL: Use immutable cache set, returns new state
410
+ updateState({ cache: cacheSet(state.cache, cacheKey, entry) });
411
+ };
412
+
413
+ /**
414
+ * Enforce cache size limit
415
+ * FUNCTIONAL: Uses immutable cache operations
416
+ */
417
+ const enforceCacheLimit = (): void => {
418
+ if (serviceConfig.cacheMaxSize && state.cache.size > serviceConfig.cacheMaxSize) {
419
+ // FUNCTIONAL: Use immutable cache enforceLimit, returns new state
420
+ const entriesToRemove = state.cache.size - serviceConfig.cacheMaxSize;
421
+ updateState({ cache: cacheEnforceLimit(state.cache, serviceConfig.cacheMaxSize) });
422
+ deps.logger.debug(`Cache eviction: removed ${entriesToRemove} entries`);
423
+ }
424
+ };
425
+
426
+ /**
427
+ * Setup cleanup timer for retention policy
428
+ */
429
+ const setupCleanupTimer = (): void => {
430
+ if (serviceConfig.retentionPolicy?.enabled && serviceConfig.retentionPolicy.cleanupInterval) {
431
+ const timer = setInterval(async () => {
432
+ try {
433
+ const deletedCount = await deps.repository.cleanupOldEntries(
434
+ serviceConfig.retentionPolicy?.maxAge
435
+ );
436
+ if (deletedCount > 0) {
437
+ deps.logger.info(`Automatic cleanup: removed ${deletedCount} old entries`);
438
+ }
439
+ } catch (error) {
440
+ deps.logger.error('Automatic cleanup failed', error);
441
+ }
442
+ }, serviceConfig.retentionPolicy.cleanupInterval);
443
+ updateState({ cleanupTimer: timer });
444
+ }
445
+ };
446
+
447
+ /**
448
+ * Cleanup resources
449
+ */
450
+ const dispose = async (): Promise<void> => {
451
+ if (state.cleanupTimer) {
452
+ clearInterval(state.cleanupTimer);
453
+ updateState({ cleanupTimer: undefined });
454
+ }
455
+
456
+ // FUNCTIONAL: Replace cache with new empty cache instead of clearing
457
+ updateState({ cache: createCache() });
458
+ deps.logger.info('Memory service disposed');
459
+ };
460
+
461
+ // Initialize cleanup timer
462
+ setupCleanupTimer();
463
+
464
+ // Return service interface
465
+ return {
466
+ get,
467
+ set,
468
+ delete: deleteEntry,
469
+ list,
470
+ search,
471
+ clear,
472
+ getStats,
473
+ bulkSet,
474
+ dispose,
475
+ };
476
+ };
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Base indexer with common indexing logic
3
+ * Shared by knowledge and codebase indexers
4
+ */
5
+
6
+ import type { SearchIndex } from './tfidf.js';
7
+ import { createLogger } from '../../utils/debug-logger.js';
8
+
9
+ const log = createLogger('search:indexing');
10
+
11
+ export interface IndexingStatus {
12
+ isIndexing: boolean;
13
+ progress: number; // 0-100
14
+ totalItems: number;
15
+ indexedItems: number;
16
+ startTime: number;
17
+ error?: string;
18
+ }
19
+
20
+ export interface IndexerConfig {
21
+ name: string; // 'knowledge' or 'codebase'
22
+ }
23
+
24
+ /**
25
+ * Base class for indexers with common functionality
26
+ *
27
+ * IMPORTANT: Indexing always starts automatically on initialization.
28
+ * This is mandatory - without indexing, search cannot work and stale data misleads users.
29
+ */
30
+ export abstract class BaseIndexer {
31
+ protected cachedIndex: SearchIndex | null = null;
32
+ protected indexingPromise: Promise<SearchIndex> | null = null;
33
+ protected status: IndexingStatus = {
34
+ isIndexing: false,
35
+ progress: 0,
36
+ totalItems: 0,
37
+ indexedItems: 0,
38
+ startTime: 0,
39
+ };
40
+
41
+ constructor(protected config: IndexerConfig) {
42
+ // MANDATORY: Start background indexing immediately
43
+ // Without indexing, search won't work. Stale data misleads users.
44
+ setTimeout(() => this.startBackgroundIndexing(), 0);
45
+ }
46
+
47
+ /**
48
+ * Abstract method: Build index (implemented by subclasses)
49
+ */
50
+ protected abstract buildIndex(): Promise<SearchIndex>;
51
+
52
+ /**
53
+ * Get current indexing status
54
+ */
55
+ getStatus(): IndexingStatus {
56
+ return { ...this.status };
57
+ }
58
+
59
+ /**
60
+ * Check if index is ready
61
+ */
62
+ isReady(): boolean {
63
+ return this.cachedIndex !== null && !this.status.isIndexing;
64
+ }
65
+
66
+ /**
67
+ * Start background indexing (non-blocking)
68
+ */
69
+ startBackgroundIndexing(): void {
70
+ if (this.status.isIndexing || this.cachedIndex) {
71
+ return;
72
+ }
73
+
74
+ log(`Starting background ${this.config.name} indexing`);
75
+ this.loadIndex().catch((error) => {
76
+ log(`Background ${this.config.name} indexing failed:`, error instanceof Error ? error.message : String(error));
77
+ });
78
+ }
79
+
80
+ /**
81
+ * Load or build index (with caching)
82
+ */
83
+ async loadIndex(): Promise<SearchIndex> {
84
+ // Return cached index if available
85
+ if (this.cachedIndex) {
86
+ return this.cachedIndex;
87
+ }
88
+
89
+ // If already indexing, wait for it
90
+ if (this.indexingPromise) {
91
+ return this.indexingPromise;
92
+ }
93
+
94
+ // Start indexing
95
+ this.status.isIndexing = true;
96
+ this.status.progress = 0;
97
+ this.status.startTime = Date.now();
98
+ this.status.error = undefined;
99
+
100
+ this.indexingPromise = this.buildIndex()
101
+ .then((index) => {
102
+ this.cachedIndex = index;
103
+ this.status.isIndexing = false;
104
+ this.status.progress = 100;
105
+ this.status.totalItems = index.totalDocuments;
106
+ this.status.indexedItems = index.totalDocuments;
107
+ log(`${this.config.name} indexing complete:`, index.totalDocuments, 'documents');
108
+ return index;
109
+ })
110
+ .catch((error) => {
111
+ this.status.isIndexing = false;
112
+ this.status.error = error instanceof Error ? error.message : String(error);
113
+ log(`${this.config.name} indexing failed:`, error instanceof Error ? error.message : String(error));
114
+ throw error;
115
+ });
116
+
117
+ return this.indexingPromise;
118
+ }
119
+
120
+ /**
121
+ * Clear cache
122
+ */
123
+ clearCache(): void {
124
+ this.cachedIndex = null;
125
+ this.indexingPromise = null;
126
+ this.status = {
127
+ isIndexing: false,
128
+ progress: 0,
129
+ totalItems: 0,
130
+ indexedItems: 0,
131
+ startTime: 0,
132
+ };
133
+ }
134
+
135
+ /**
136
+ * Get index statistics
137
+ */
138
+ async getStats(): Promise<{
139
+ totalDocuments: number;
140
+ uniqueTerms: number;
141
+ generatedAt: string;
142
+ version: string;
143
+ } | null> {
144
+ const index = await this.loadIndex();
145
+ if (!index) {
146
+ return null;
147
+ }
148
+
149
+ return {
150
+ totalDocuments: index.totalDocuments,
151
+ uniqueTerms: index.idf.size,
152
+ generatedAt: index.metadata.generatedAt,
153
+ version: index.metadata.version,
154
+ };
155
+ }
156
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Types and interfaces for the codebase indexer
3
+ */
4
+
5
+ import type { SearchIndex } from './tfidf.js';
6
+
7
+ export interface CodebaseFile {
8
+ path: string; // Relative path from codebase root
9
+ absolutePath: string;
10
+ content: string;
11
+ language?: string; // Detected programming language
12
+ size: number;
13
+ mtime: number; // Last modified time
14
+ }
15
+
16
+ export interface IndexCache {
17
+ version: string;
18
+ codebaseRoot: string;
19
+ indexedAt: string;
20
+ fileCount: number;
21
+ files: Map<string, { mtime: number; hash: string }>; // Track file changes
22
+ tfidfIndex?: SearchIndex;
23
+ vectorIndexPath?: string;
24
+ }
25
+
26
+ export interface CodebaseIndexerOptions {
27
+ codebaseRoot?: string;
28
+ cacheDir?: string;
29
+ batchSize?: number;
30
+ }
31
+
32
+ export interface IndexingStatus {
33
+ isIndexing: boolean;
34
+ progress: number; // 0-100
35
+ currentFile?: string;
36
+ totalFiles: number;
37
+ indexedFiles: number;
38
+ }