@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.
- package/CHANGELOG.md +12 -0
- package/package.json +10 -9
- package/src/commands/codebase-command.ts +168 -0
- package/src/commands/flow-command.ts +1137 -0
- package/src/commands/flow-orchestrator.ts +296 -0
- package/src/commands/hook-command.ts +444 -0
- package/src/commands/init-command.ts +92 -0
- package/src/commands/init-core.ts +322 -0
- package/src/commands/knowledge-command.ts +161 -0
- package/src/commands/run-command.ts +120 -0
- package/src/components/benchmark-monitor.tsx +331 -0
- package/src/components/reindex-progress.tsx +261 -0
- package/src/composables/functional/index.ts +14 -0
- package/src/composables/functional/useEnvironment.ts +171 -0
- package/src/composables/functional/useFileSystem.ts +139 -0
- package/src/composables/index.ts +5 -0
- package/src/composables/useEnv.ts +13 -0
- package/src/composables/useRuntimeConfig.ts +27 -0
- package/src/composables/useTargetConfig.ts +45 -0
- package/src/config/ai-config.ts +376 -0
- package/src/config/constants.ts +35 -0
- package/src/config/index.ts +27 -0
- package/src/config/rules.ts +43 -0
- package/src/config/servers.ts +371 -0
- package/src/config/targets.ts +126 -0
- package/src/core/agent-loader.ts +141 -0
- package/src/core/agent-manager.ts +174 -0
- package/src/core/ai-sdk.ts +603 -0
- package/src/core/app-factory.ts +381 -0
- package/src/core/builtin-agents.ts +9 -0
- package/src/core/command-system.ts +550 -0
- package/src/core/config-system.ts +550 -0
- package/src/core/connection-pool.ts +390 -0
- package/src/core/di-container.ts +155 -0
- package/src/core/error-handling.ts +519 -0
- package/src/core/formatting/bytes.test.ts +115 -0
- package/src/core/formatting/bytes.ts +64 -0
- package/src/core/functional/async.ts +313 -0
- package/src/core/functional/either.ts +109 -0
- package/src/core/functional/error-handler.ts +135 -0
- package/src/core/functional/error-types.ts +311 -0
- package/src/core/functional/index.ts +19 -0
- package/src/core/functional/option.ts +142 -0
- package/src/core/functional/pipe.ts +189 -0
- package/src/core/functional/result.ts +204 -0
- package/src/core/functional/validation.ts +138 -0
- package/src/core/headless-display.ts +96 -0
- package/src/core/index.ts +6 -0
- package/src/core/installers/file-installer.ts +303 -0
- package/src/core/installers/mcp-installer.ts +213 -0
- package/src/core/interfaces/index.ts +22 -0
- package/src/core/interfaces/repository.interface.ts +91 -0
- package/src/core/interfaces/service.interface.ts +133 -0
- package/src/core/interfaces.ts +129 -0
- package/src/core/loop-controller.ts +200 -0
- package/src/core/result.ts +351 -0
- package/src/core/rule-loader.ts +147 -0
- package/src/core/rule-manager.ts +240 -0
- package/src/core/service-config.ts +252 -0
- package/src/core/session-service.ts +121 -0
- package/src/core/state-detector.ts +389 -0
- package/src/core/storage-factory.ts +115 -0
- package/src/core/stream-handler.ts +288 -0
- package/src/core/target-manager.ts +161 -0
- package/src/core/type-utils.ts +427 -0
- package/src/core/unified-storage.ts +456 -0
- package/src/core/upgrade-manager.ts +300 -0
- package/src/core/validation/limit.test.ts +155 -0
- package/src/core/validation/limit.ts +46 -0
- package/src/core/validation/query.test.ts +44 -0
- package/src/core/validation/query.ts +20 -0
- package/src/db/auto-migrate.ts +322 -0
- package/src/db/base-database-client.ts +144 -0
- package/src/db/cache-db.ts +218 -0
- package/src/db/cache-schema.ts +75 -0
- package/src/db/database.ts +70 -0
- package/src/db/index.ts +252 -0
- package/src/db/memory-db.ts +153 -0
- package/src/db/memory-schema.ts +29 -0
- package/src/db/schema.ts +289 -0
- package/src/db/session-repository.ts +733 -0
- package/src/domains/codebase/index.ts +5 -0
- package/src/domains/codebase/tools.ts +139 -0
- package/src/domains/index.ts +8 -0
- package/src/domains/knowledge/index.ts +10 -0
- package/src/domains/knowledge/resources.ts +537 -0
- package/src/domains/knowledge/tools.ts +174 -0
- package/src/domains/utilities/index.ts +6 -0
- package/src/domains/utilities/time/index.ts +5 -0
- package/src/domains/utilities/time/tools.ts +291 -0
- package/src/index.ts +211 -0
- package/src/services/agent-service.ts +273 -0
- package/src/services/claude-config-service.ts +252 -0
- package/src/services/config-service.ts +258 -0
- package/src/services/evaluation-service.ts +271 -0
- package/src/services/functional/evaluation-logic.ts +296 -0
- package/src/services/functional/file-processor.ts +273 -0
- package/src/services/functional/index.ts +12 -0
- package/src/services/index.ts +13 -0
- package/src/services/mcp-service.ts +432 -0
- package/src/services/memory.service.ts +476 -0
- package/src/services/search/base-indexer.ts +156 -0
- package/src/services/search/codebase-indexer-types.ts +38 -0
- package/src/services/search/codebase-indexer.ts +647 -0
- package/src/services/search/embeddings-provider.ts +455 -0
- package/src/services/search/embeddings.ts +316 -0
- package/src/services/search/functional-indexer.ts +323 -0
- package/src/services/search/index.ts +27 -0
- package/src/services/search/indexer.ts +380 -0
- package/src/services/search/knowledge-indexer.ts +422 -0
- package/src/services/search/semantic-search.ts +244 -0
- package/src/services/search/tfidf.ts +559 -0
- package/src/services/search/unified-search-service.ts +888 -0
- package/src/services/smart-config-service.ts +385 -0
- package/src/services/storage/cache-storage.ts +487 -0
- package/src/services/storage/drizzle-storage.ts +581 -0
- package/src/services/storage/index.ts +15 -0
- package/src/services/storage/lancedb-vector-storage.ts +494 -0
- package/src/services/storage/memory-storage.ts +268 -0
- package/src/services/storage/separated-storage.ts +467 -0
- package/src/services/storage/vector-storage.ts +13 -0
- package/src/shared/agents/index.ts +63 -0
- package/src/shared/files/index.ts +99 -0
- package/src/shared/index.ts +32 -0
- package/src/shared/logging/index.ts +24 -0
- package/src/shared/processing/index.ts +153 -0
- package/src/shared/types/index.ts +25 -0
- package/src/targets/claude-code.ts +574 -0
- package/src/targets/functional/claude-code-logic.ts +185 -0
- package/src/targets/functional/index.ts +6 -0
- package/src/targets/opencode.ts +529 -0
- package/src/types/agent.types.ts +32 -0
- package/src/types/api/batch.ts +108 -0
- package/src/types/api/errors.ts +118 -0
- package/src/types/api/index.ts +55 -0
- package/src/types/api/requests.ts +76 -0
- package/src/types/api/responses.ts +180 -0
- package/src/types/api/websockets.ts +85 -0
- package/src/types/api.types.ts +9 -0
- package/src/types/benchmark.ts +49 -0
- package/src/types/cli.types.ts +87 -0
- package/src/types/common.types.ts +35 -0
- package/src/types/database.types.ts +510 -0
- package/src/types/mcp-config.types.ts +448 -0
- package/src/types/mcp.types.ts +69 -0
- package/src/types/memory-types.ts +63 -0
- package/src/types/provider.types.ts +28 -0
- package/src/types/rule.types.ts +24 -0
- package/src/types/session.types.ts +214 -0
- package/src/types/target-config.types.ts +295 -0
- package/src/types/target.types.ts +140 -0
- package/src/types/todo.types.ts +25 -0
- package/src/types.ts +40 -0
- package/src/utils/advanced-tokenizer.ts +191 -0
- package/src/utils/agent-enhancer.ts +114 -0
- package/src/utils/ai-model-fetcher.ts +19 -0
- package/src/utils/async-file-operations.ts +516 -0
- package/src/utils/audio-player.ts +345 -0
- package/src/utils/cli-output.ts +266 -0
- package/src/utils/codebase-helpers.ts +211 -0
- package/src/utils/console-ui.ts +79 -0
- package/src/utils/database-errors.ts +140 -0
- package/src/utils/debug-logger.ts +49 -0
- package/src/utils/error-handler.ts +53 -0
- package/src/utils/file-operations.ts +310 -0
- package/src/utils/file-scanner.ts +259 -0
- package/src/utils/functional/array.ts +355 -0
- package/src/utils/functional/index.ts +15 -0
- package/src/utils/functional/object.ts +279 -0
- package/src/utils/functional/string.ts +281 -0
- package/src/utils/functional.ts +543 -0
- package/src/utils/help.ts +20 -0
- package/src/utils/immutable-cache.ts +106 -0
- package/src/utils/index.ts +78 -0
- package/src/utils/jsonc.ts +158 -0
- package/src/utils/logger.ts +396 -0
- package/src/utils/mcp-config.ts +249 -0
- package/src/utils/memory-tui.ts +414 -0
- package/src/utils/models-dev.ts +91 -0
- package/src/utils/notifications.ts +169 -0
- package/src/utils/object-utils.ts +51 -0
- package/src/utils/parallel-operations.ts +487 -0
- package/src/utils/paths.ts +143 -0
- package/src/utils/process-manager.ts +155 -0
- package/src/utils/prompts.ts +120 -0
- package/src/utils/search-tool-builder.ts +214 -0
- package/src/utils/secret-utils.ts +179 -0
- package/src/utils/security.ts +537 -0
- package/src/utils/session-manager.ts +168 -0
- package/src/utils/session-title.ts +87 -0
- package/src/utils/settings.ts +182 -0
- package/src/utils/simplified-errors.ts +410 -0
- package/src/utils/sync-utils.ts +159 -0
- package/src/utils/target-config.ts +570 -0
- package/src/utils/target-utils.ts +394 -0
- package/src/utils/template-engine.ts +94 -0
- package/src/utils/test-audio.ts +71 -0
- package/src/utils/todo-context.ts +46 -0
- package/src/utils/token-counter.ts +288 -0
- package/dist/index.d.ts +0 -10
- package/dist/index.js +0 -59554
- package/dist/lancedb.linux-x64-gnu-b7f0jgsz.node +0 -0
- package/dist/lancedb.linux-x64-musl-tgcv22rx.node +0 -0
- package/dist/shared/chunk-25dwp0dp.js +0 -89
- package/dist/shared/chunk-3pjb6063.js +0 -208
- package/dist/shared/chunk-4d6ydpw7.js +0 -2854
- package/dist/shared/chunk-4wjcadjk.js +0 -225
- package/dist/shared/chunk-5j4w74t6.js +0 -30
- package/dist/shared/chunk-5j8m3dh3.js +0 -58
- package/dist/shared/chunk-5thh3qem.js +0 -91
- package/dist/shared/chunk-6g9xy73m.js +0 -252
- package/dist/shared/chunk-7eq34c42.js +0 -23
- package/dist/shared/chunk-c2gwgx3r.js +0 -115
- package/dist/shared/chunk-cjd3mk4c.js +0 -1320
- package/dist/shared/chunk-g5cv6703.js +0 -368
- package/dist/shared/chunk-hpkhykhq.js +0 -574
- package/dist/shared/chunk-m2322pdk.js +0 -122
- package/dist/shared/chunk-nd5fdvaq.js +0 -26
- package/dist/shared/chunk-pgd3m6zf.js +0 -108
- package/dist/shared/chunk-qk8n91hw.js +0 -494
- package/dist/shared/chunk-rkkn8szp.js +0 -16855
- package/dist/shared/chunk-t16rfxh0.js +0 -61
- package/dist/shared/chunk-t4fbfa5v.js +0 -19
- package/dist/shared/chunk-t77h86w6.js +0 -276
- package/dist/shared/chunk-v0ez4aef.js +0 -71
- package/dist/shared/chunk-v29j2r3s.js +0 -32051
- package/dist/shared/chunk-vfbc6ew5.js +0 -765
- package/dist/shared/chunk-vmeqwm1c.js +0 -204
- 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
|
+
}
|