@sylphx/flow 1.1.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/package.json +1 -1
- package/src/commands/flow-command.ts +28 -0
- package/src/commands/hook-command.ts +10 -230
- package/src/composables/index.ts +0 -1
- package/src/config/servers.ts +35 -78
- package/src/core/interfaces.ts +0 -33
- package/src/domains/index.ts +0 -2
- package/src/index.ts +0 -4
- package/src/services/mcp-service.ts +0 -16
- package/src/targets/claude-code.ts +3 -9
- package/src/targets/functional/claude-code-logic.ts +4 -22
- package/src/targets/opencode.ts +0 -6
- package/src/types/mcp.types.ts +29 -38
- package/src/types/target.types.ts +0 -2
- package/src/types.ts +0 -1
- package/src/utils/sync-utils.ts +106 -0
- package/src/commands/codebase-command.ts +0 -168
- package/src/commands/knowledge-command.ts +0 -161
- package/src/composables/useTargetConfig.ts +0 -45
- package/src/core/formatting/bytes.test.ts +0 -115
- package/src/core/validation/limit.test.ts +0 -155
- package/src/core/validation/query.test.ts +0 -44
- package/src/domains/codebase/index.ts +0 -5
- package/src/domains/codebase/tools.ts +0 -139
- package/src/domains/knowledge/index.ts +0 -10
- package/src/domains/knowledge/resources.ts +0 -537
- package/src/domains/knowledge/tools.ts +0 -174
- package/src/services/search/base-indexer.ts +0 -156
- package/src/services/search/codebase-indexer-types.ts +0 -38
- package/src/services/search/codebase-indexer.ts +0 -647
- package/src/services/search/embeddings-provider.ts +0 -455
- package/src/services/search/embeddings.ts +0 -316
- package/src/services/search/functional-indexer.ts +0 -323
- package/src/services/search/index.ts +0 -27
- package/src/services/search/indexer.ts +0 -380
- package/src/services/search/knowledge-indexer.ts +0 -422
- package/src/services/search/semantic-search.ts +0 -244
- package/src/services/search/tfidf.ts +0 -559
- package/src/services/search/unified-search-service.ts +0 -888
- package/src/services/storage/cache-storage.ts +0 -487
- package/src/services/storage/drizzle-storage.ts +0 -581
- package/src/services/storage/index.ts +0 -15
- package/src/services/storage/lancedb-vector-storage.ts +0 -494
- package/src/services/storage/memory-storage.ts +0 -268
- package/src/services/storage/separated-storage.ts +0 -467
- package/src/services/storage/vector-storage.ts +0 -13
|
@@ -1,380 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unified Indexer Service - 統一索引服務
|
|
3
|
-
* 處理所有domain嘅索引建立和維護
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import fs from 'node:fs';
|
|
7
|
-
import path from 'node:path';
|
|
8
|
-
import type { EmbeddingProvider } from '../../utils/embeddings.js';
|
|
9
|
-
import { VectorStorage } from '../../utils/lancedb-vector-storage.js';
|
|
10
|
-
import { buildSearchIndex, type SearchIndex } from '../../utils/tfidf.js';
|
|
11
|
-
import type { ContentMetadata } from './search-service.js';
|
|
12
|
-
|
|
13
|
-
export interface IndexingOptions {
|
|
14
|
-
batchSize?: number;
|
|
15
|
-
includeVectorIndex?: boolean;
|
|
16
|
-
forceRebuild?: boolean;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface IndexingProgress {
|
|
20
|
-
domain: string;
|
|
21
|
-
totalFiles: number;
|
|
22
|
-
processedFiles: number;
|
|
23
|
-
status: 'scanning' | 'indexing' | 'vectorizing' | 'complete' | 'error';
|
|
24
|
-
error?: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Internal state for IndexerService
|
|
29
|
-
*/
|
|
30
|
-
interface IndexerServiceState {
|
|
31
|
-
readonly embeddingProvider?: EmbeddingProvider;
|
|
32
|
-
readonly vectorStorages: ReadonlyMap<string, VectorStorage>;
|
|
33
|
-
readonly progressCallbacks: ReadonlySet<(progress: IndexingProgress) => void>;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* IndexerService Interface
|
|
38
|
-
* Unified indexer for building and maintaining search indices
|
|
39
|
-
*/
|
|
40
|
-
export interface IndexerService {
|
|
41
|
-
readonly initialize: () => Promise<void>;
|
|
42
|
-
readonly onProgress: (callback: (progress: IndexingProgress) => void) => void;
|
|
43
|
-
readonly offProgress: (callback: (progress: IndexingProgress) => void) => void;
|
|
44
|
-
readonly buildIndex: (
|
|
45
|
-
domain: 'knowledge' | 'codebase',
|
|
46
|
-
options?: IndexingOptions
|
|
47
|
-
) => Promise<SearchIndex>;
|
|
48
|
-
readonly updateIndex: (domain: string, content: ContentMetadata) => Promise<void>;
|
|
49
|
-
readonly removeFromIndex: (domain: string, uri: string) => Promise<void>;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Create Indexer Service (Factory Function)
|
|
54
|
-
* Handles all domain indexing and maintenance
|
|
55
|
-
*/
|
|
56
|
-
export const createIndexerService = (embeddingProvider?: EmbeddingProvider): IndexerService => {
|
|
57
|
-
// Mutable state in closure (will be updated immutably)
|
|
58
|
-
let state: IndexerServiceState = {
|
|
59
|
-
embeddingProvider,
|
|
60
|
-
vectorStorages: new Map(),
|
|
61
|
-
progressCallbacks: new Set(),
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
// Helper: Update state immutably
|
|
65
|
-
const updateState = (updates: Partial<IndexerServiceState>): void => {
|
|
66
|
-
state = { ...state, ...updates };
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* 初始化索引服務
|
|
71
|
-
*/
|
|
72
|
-
const initialize = async (): Promise<void> => {
|
|
73
|
-
if (!state.embeddingProvider) {
|
|
74
|
-
// Import here to avoid circular dependencies
|
|
75
|
-
const { getDefaultEmbeddingProvider } = await import('../../utils/embeddings.js');
|
|
76
|
-
const provider = await getDefaultEmbeddingProvider();
|
|
77
|
-
updateState({ embeddingProvider: provider });
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* 註冊進度回調
|
|
83
|
-
*/
|
|
84
|
-
const onProgress = (callback: (progress: IndexingProgress) => void): void => {
|
|
85
|
-
const newCallbacks = new Set(state.progressCallbacks);
|
|
86
|
-
newCallbacks.add(callback);
|
|
87
|
-
updateState({ progressCallbacks: newCallbacks });
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* 移除進度回調
|
|
92
|
-
*/
|
|
93
|
-
const offProgress = (callback: (progress: IndexingProgress) => void): void => {
|
|
94
|
-
const newCallbacks = new Set(state.progressCallbacks);
|
|
95
|
-
newCallbacks.delete(callback);
|
|
96
|
-
updateState({ progressCallbacks: newCallbacks });
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* 報告進度
|
|
101
|
-
*/
|
|
102
|
-
const reportProgress = (progress: IndexingProgress): void => {
|
|
103
|
-
state.progressCallbacks.forEach((callback) => callback(progress));
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* 為指定domain建立索引
|
|
108
|
-
*/
|
|
109
|
-
const buildIndex = async (
|
|
110
|
-
domain: 'knowledge' | 'codebase',
|
|
111
|
-
options: IndexingOptions = {}
|
|
112
|
-
): Promise<SearchIndex> => {
|
|
113
|
-
const { batchSize = 10, includeVectorIndex = true, forceRebuild = false } = options;
|
|
114
|
-
|
|
115
|
-
reportProgress({
|
|
116
|
-
domain,
|
|
117
|
-
totalFiles: 0,
|
|
118
|
-
processedFiles: 0,
|
|
119
|
-
status: 'scanning',
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
try {
|
|
123
|
-
// 掃描文件
|
|
124
|
-
const files = await scanDomainFiles(domain);
|
|
125
|
-
|
|
126
|
-
reportProgress({
|
|
127
|
-
domain,
|
|
128
|
-
totalFiles: files.length,
|
|
129
|
-
processedFiles: 0,
|
|
130
|
-
status: 'indexing',
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
// 建立TF-IDF索引
|
|
134
|
-
const searchIndex = buildSearchIndex(files);
|
|
135
|
-
|
|
136
|
-
reportProgress({
|
|
137
|
-
domain,
|
|
138
|
-
totalFiles: files.length,
|
|
139
|
-
processedFiles: files.length,
|
|
140
|
-
status: includeVectorIndex ? 'vectorizing' : 'complete',
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
// 建立向量索引
|
|
144
|
-
if (includeVectorIndex && state.embeddingProvider) {
|
|
145
|
-
await buildVectorIndex(domain, files, batchSize);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
reportProgress({
|
|
149
|
-
domain,
|
|
150
|
-
totalFiles: files.length,
|
|
151
|
-
processedFiles: files.length,
|
|
152
|
-
status: 'complete',
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
return searchIndex;
|
|
156
|
-
} catch (error) {
|
|
157
|
-
reportProgress({
|
|
158
|
-
domain,
|
|
159
|
-
totalFiles: 0,
|
|
160
|
-
processedFiles: 0,
|
|
161
|
-
status: 'error',
|
|
162
|
-
error: error instanceof Error ? error.message : String(error),
|
|
163
|
-
});
|
|
164
|
-
throw error;
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* 掃描指定domain嘅文件
|
|
170
|
-
*/
|
|
171
|
-
const scanDomainFiles = async (
|
|
172
|
-
domain: string
|
|
173
|
-
): Promise<Array<{ uri: string; content: string }>> => {
|
|
174
|
-
switch (domain) {
|
|
175
|
-
case 'knowledge':
|
|
176
|
-
return scanKnowledgeFiles();
|
|
177
|
-
case 'codebase':
|
|
178
|
-
return scanCodebaseFiles();
|
|
179
|
-
default:
|
|
180
|
-
throw new Error(`Unknown domain: ${domain}`);
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* 掃描knowledge文件
|
|
186
|
-
*/
|
|
187
|
-
const scanKnowledgeFiles = async (): Promise<Array<{ uri: string; content: string }>> => {
|
|
188
|
-
const { getKnowledgeDir } = await import('../../utils/paths.js');
|
|
189
|
-
const knowledgeDir = getKnowledgeDir();
|
|
190
|
-
|
|
191
|
-
if (!fs.existsSync(knowledgeDir)) {
|
|
192
|
-
return [];
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const files: Array<{ uri: string; content: string }> = [];
|
|
196
|
-
|
|
197
|
-
const scan = (currentDir: string, baseDir: string) => {
|
|
198
|
-
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
199
|
-
|
|
200
|
-
for (const entry of entries) {
|
|
201
|
-
const fullPath = path.join(currentDir, entry.name);
|
|
202
|
-
|
|
203
|
-
if (entry.isDirectory()) {
|
|
204
|
-
scan(fullPath, baseDir);
|
|
205
|
-
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
206
|
-
const relativePath = path.relative(baseDir, fullPath);
|
|
207
|
-
const uriPath = relativePath.replace(/\.md$/, '').replace(/\\/g, '/');
|
|
208
|
-
const content = fs.readFileSync(fullPath, 'utf8');
|
|
209
|
-
|
|
210
|
-
files.push({
|
|
211
|
-
uri: `knowledge://${uriPath}`,
|
|
212
|
-
content,
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
scan(knowledgeDir, knowledgeDir);
|
|
219
|
-
return files;
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* 掃描codebase文件
|
|
224
|
-
*/
|
|
225
|
-
const scanCodebaseFiles = async (): Promise<Array<{ uri: string; content: string }>> => {
|
|
226
|
-
// 實現codebase文件掃描邏輯
|
|
227
|
-
// 這裡需要根據實際需求實現
|
|
228
|
-
return [];
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* 建立向量索引
|
|
233
|
-
*/
|
|
234
|
-
const buildVectorIndex = async (
|
|
235
|
-
domain: string,
|
|
236
|
-
files: Array<{ uri: string; content: string }>,
|
|
237
|
-
batchSize: number
|
|
238
|
-
): Promise<void> => {
|
|
239
|
-
if (!state.embeddingProvider) {
|
|
240
|
-
throw new Error('Embedding provider not available');
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// 初始化向量存儲
|
|
244
|
-
const vectorPath = path.join(process.cwd(), '.sylphx-flow', `${domain}-vectors.hnsw`);
|
|
245
|
-
|
|
246
|
-
const vectorStorage = new VectorStorage(vectorPath, state.embeddingProvider.dimensions || 1536);
|
|
247
|
-
await vectorStorage.initialize();
|
|
248
|
-
|
|
249
|
-
// FUNCTIONAL: Update map immutably
|
|
250
|
-
const newStorages = new Map(state.vectorStorages);
|
|
251
|
-
newStorages.set(domain, vectorStorage);
|
|
252
|
-
updateState({ vectorStorages: newStorages });
|
|
253
|
-
|
|
254
|
-
// 批量處理文件
|
|
255
|
-
for (let i = 0; i < files.length; i += batchSize) {
|
|
256
|
-
const batch = files.slice(i, i + batchSize);
|
|
257
|
-
const embeddings = await state.embeddingProvider.generateEmbeddings(
|
|
258
|
-
batch.map((file) => file.content)
|
|
259
|
-
);
|
|
260
|
-
|
|
261
|
-
for (let j = 0; j < batch.length; j++) {
|
|
262
|
-
const file = batch[j];
|
|
263
|
-
const embedding = embeddings[j];
|
|
264
|
-
|
|
265
|
-
await vectorStorage.addDocument({
|
|
266
|
-
id: file.uri,
|
|
267
|
-
embedding,
|
|
268
|
-
metadata: {
|
|
269
|
-
type: domain,
|
|
270
|
-
content: file.content.slice(0, 500),
|
|
271
|
-
language: detectLanguage(file.uri),
|
|
272
|
-
},
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// 報告向量化進度
|
|
277
|
-
const progress = Math.min(i + batchSize, files.length);
|
|
278
|
-
reportProgress({
|
|
279
|
-
domain,
|
|
280
|
-
totalFiles: files.length,
|
|
281
|
-
processedFiles: progress,
|
|
282
|
-
status: 'vectorizing',
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
await vectorStorage.save();
|
|
287
|
-
};
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* 檢測文件語言
|
|
291
|
-
*/
|
|
292
|
-
const detectLanguage = (uri: string): string => {
|
|
293
|
-
const ext = path.extname(uri).toLowerCase();
|
|
294
|
-
const languageMap: Record<string, string> = {
|
|
295
|
-
'.ts': 'typescript',
|
|
296
|
-
'.js': 'javascript',
|
|
297
|
-
'.jsx': 'jsx',
|
|
298
|
-
'.tsx': 'tsx',
|
|
299
|
-
'.py': 'python',
|
|
300
|
-
'.java': 'java',
|
|
301
|
-
'.cpp': 'cpp',
|
|
302
|
-
'.c': 'c',
|
|
303
|
-
'.cs': 'csharp',
|
|
304
|
-
'.go': 'go',
|
|
305
|
-
'.rs': 'rust',
|
|
306
|
-
'.php': 'php',
|
|
307
|
-
'.rb': 'ruby',
|
|
308
|
-
'.swift': 'swift',
|
|
309
|
-
'.kt': 'kotlin',
|
|
310
|
-
'.scala': 'scala',
|
|
311
|
-
'.md': 'markdown',
|
|
312
|
-
'.txt': 'text',
|
|
313
|
-
'.json': 'json',
|
|
314
|
-
'.yaml': 'yaml',
|
|
315
|
-
'.yml': 'yaml',
|
|
316
|
-
'.xml': 'xml',
|
|
317
|
-
'.html': 'html',
|
|
318
|
-
'.css': 'css',
|
|
319
|
-
'.scss': 'scss',
|
|
320
|
-
'.sass': 'sass',
|
|
321
|
-
'.less': 'less',
|
|
322
|
-
'.sql': 'sql',
|
|
323
|
-
};
|
|
324
|
-
|
|
325
|
-
return languageMap[ext] || 'unknown';
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* 更新索引
|
|
330
|
-
*/
|
|
331
|
-
const updateIndex = async (domain: string, content: ContentMetadata): Promise<void> => {
|
|
332
|
-
// 更新TF-IDF索引(需要重新建立整個index)
|
|
333
|
-
// 更新向量索引(可以單獨更新)
|
|
334
|
-
if (state.vectorStorages.has(domain) && state.embeddingProvider) {
|
|
335
|
-
const vectorStorage = state.vectorStorages.get(domain)!;
|
|
336
|
-
const embedding = await state.embeddingProvider.generateEmbeddings([content.content]);
|
|
337
|
-
|
|
338
|
-
await vectorStorage.addDocument({
|
|
339
|
-
id: content.uri,
|
|
340
|
-
embedding: embedding[0],
|
|
341
|
-
metadata: {
|
|
342
|
-
type: content.type,
|
|
343
|
-
content: content.content.slice(0, 500),
|
|
344
|
-
category: content.category,
|
|
345
|
-
language: content.language,
|
|
346
|
-
path: content.path,
|
|
347
|
-
},
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
await vectorStorage.save();
|
|
351
|
-
}
|
|
352
|
-
};
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* 從索引中移除文檔
|
|
356
|
-
*/
|
|
357
|
-
const removeFromIndex = async (domain: string, uri: string): Promise<void> => {
|
|
358
|
-
// 從向量索引中移除
|
|
359
|
-
if (state.vectorStorages.has(domain)) {
|
|
360
|
-
const vectorStorage = state.vectorStorages.get(domain)!;
|
|
361
|
-
await vectorStorage.removeDocument(uri);
|
|
362
|
-
await vectorStorage.save();
|
|
363
|
-
}
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
// Return service interface
|
|
367
|
-
return {
|
|
368
|
-
initialize,
|
|
369
|
-
onProgress,
|
|
370
|
-
offProgress,
|
|
371
|
-
buildIndex,
|
|
372
|
-
updateIndex,
|
|
373
|
-
removeFromIndex,
|
|
374
|
-
};
|
|
375
|
-
};
|
|
376
|
-
|
|
377
|
-
// Factory function for creating default instance
|
|
378
|
-
export function getIndexerService(): IndexerService {
|
|
379
|
-
return createIndexerService();
|
|
380
|
-
}
|