gitnexus 1.1.8 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +50 -59
  2. package/dist/cli/ai-context.js +9 -9
  3. package/dist/cli/analyze.js +139 -47
  4. package/dist/cli/augment.d.ts +13 -0
  5. package/dist/cli/augment.js +33 -0
  6. package/dist/cli/claude-hooks.d.ts +22 -0
  7. package/dist/cli/claude-hooks.js +97 -0
  8. package/dist/cli/eval-server.d.ts +30 -0
  9. package/dist/cli/eval-server.js +372 -0
  10. package/dist/cli/index.js +56 -1
  11. package/dist/cli/mcp.js +9 -0
  12. package/dist/cli/setup.js +184 -5
  13. package/dist/cli/tool.d.ts +37 -0
  14. package/dist/cli/tool.js +91 -0
  15. package/dist/cli/wiki.d.ts +13 -0
  16. package/dist/cli/wiki.js +199 -0
  17. package/dist/core/augmentation/engine.d.ts +26 -0
  18. package/dist/core/augmentation/engine.js +213 -0
  19. package/dist/core/embeddings/embedder.d.ts +2 -2
  20. package/dist/core/embeddings/embedder.js +11 -11
  21. package/dist/core/embeddings/embedding-pipeline.d.ts +2 -1
  22. package/dist/core/embeddings/embedding-pipeline.js +13 -5
  23. package/dist/core/embeddings/types.d.ts +2 -2
  24. package/dist/core/ingestion/call-processor.d.ts +7 -0
  25. package/dist/core/ingestion/call-processor.js +61 -23
  26. package/dist/core/ingestion/community-processor.js +34 -26
  27. package/dist/core/ingestion/filesystem-walker.js +15 -10
  28. package/dist/core/ingestion/heritage-processor.d.ts +6 -0
  29. package/dist/core/ingestion/heritage-processor.js +68 -5
  30. package/dist/core/ingestion/import-processor.d.ts +22 -0
  31. package/dist/core/ingestion/import-processor.js +215 -20
  32. package/dist/core/ingestion/parsing-processor.d.ts +8 -1
  33. package/dist/core/ingestion/parsing-processor.js +66 -25
  34. package/dist/core/ingestion/pipeline.js +104 -40
  35. package/dist/core/ingestion/process-processor.js +1 -1
  36. package/dist/core/ingestion/workers/parse-worker.d.ts +58 -0
  37. package/dist/core/ingestion/workers/parse-worker.js +451 -0
  38. package/dist/core/ingestion/workers/worker-pool.d.ts +22 -0
  39. package/dist/core/ingestion/workers/worker-pool.js +65 -0
  40. package/dist/core/kuzu/kuzu-adapter.d.ts +15 -1
  41. package/dist/core/kuzu/kuzu-adapter.js +177 -63
  42. package/dist/core/kuzu/schema.d.ts +1 -1
  43. package/dist/core/kuzu/schema.js +3 -0
  44. package/dist/core/search/bm25-index.js +13 -15
  45. package/dist/core/wiki/generator.d.ts +96 -0
  46. package/dist/core/wiki/generator.js +674 -0
  47. package/dist/core/wiki/graph-queries.d.ts +80 -0
  48. package/dist/core/wiki/graph-queries.js +238 -0
  49. package/dist/core/wiki/html-viewer.d.ts +10 -0
  50. package/dist/core/wiki/html-viewer.js +297 -0
  51. package/dist/core/wiki/llm-client.d.ts +36 -0
  52. package/dist/core/wiki/llm-client.js +111 -0
  53. package/dist/core/wiki/prompts.d.ts +53 -0
  54. package/dist/core/wiki/prompts.js +174 -0
  55. package/dist/mcp/core/embedder.js +4 -2
  56. package/dist/mcp/core/kuzu-adapter.d.ts +2 -1
  57. package/dist/mcp/core/kuzu-adapter.js +35 -15
  58. package/dist/mcp/local/local-backend.d.ts +54 -1
  59. package/dist/mcp/local/local-backend.js +716 -171
  60. package/dist/mcp/resources.d.ts +1 -1
  61. package/dist/mcp/resources.js +111 -73
  62. package/dist/mcp/server.d.ts +1 -1
  63. package/dist/mcp/server.js +91 -22
  64. package/dist/mcp/tools.js +80 -61
  65. package/dist/storage/git.d.ts +0 -1
  66. package/dist/storage/git.js +1 -8
  67. package/dist/storage/repo-manager.d.ts +17 -0
  68. package/dist/storage/repo-manager.js +26 -0
  69. package/hooks/claude/gitnexus-hook.cjs +135 -0
  70. package/hooks/claude/pre-tool-use.sh +78 -0
  71. package/hooks/claude/session-start.sh +42 -0
  72. package/package.json +4 -2
  73. package/skills/debugging.md +24 -22
  74. package/skills/exploring.md +26 -24
  75. package/skills/impact-analysis.md +19 -13
  76. package/skills/refactoring.md +37 -26
@@ -0,0 +1,451 @@
1
+ import { parentPort } from 'node:worker_threads';
2
+ import Parser from 'tree-sitter';
3
+ import JavaScript from 'tree-sitter-javascript';
4
+ import TypeScript from 'tree-sitter-typescript';
5
+ import Python from 'tree-sitter-python';
6
+ import Java from 'tree-sitter-java';
7
+ import C from 'tree-sitter-c';
8
+ import CPP from 'tree-sitter-cpp';
9
+ import CSharp from 'tree-sitter-c-sharp';
10
+ import Go from 'tree-sitter-go';
11
+ import Rust from 'tree-sitter-rust';
12
+ import { SupportedLanguages } from '../../../config/supported-languages.js';
13
+ import { LANGUAGE_QUERIES } from '../tree-sitter-queries.js';
14
+ import { getLanguageFromFilename } from '../utils.js';
15
+ import { generateId } from '../../../lib/utils.js';
16
+ // ============================================================================
17
+ // Worker-local parser + language map
18
+ // ============================================================================
19
+ const parser = new Parser();
20
+ const languageMap = {
21
+ [SupportedLanguages.JavaScript]: JavaScript,
22
+ [SupportedLanguages.TypeScript]: TypeScript.typescript,
23
+ [`${SupportedLanguages.TypeScript}:tsx`]: TypeScript.tsx,
24
+ [SupportedLanguages.Python]: Python,
25
+ [SupportedLanguages.Java]: Java,
26
+ [SupportedLanguages.C]: C,
27
+ [SupportedLanguages.CPlusPlus]: CPP,
28
+ [SupportedLanguages.CSharp]: CSharp,
29
+ [SupportedLanguages.Go]: Go,
30
+ [SupportedLanguages.Rust]: Rust,
31
+ };
32
+ const setLanguage = (language, filePath) => {
33
+ const key = language === SupportedLanguages.TypeScript && filePath.endsWith('.tsx')
34
+ ? `${language}:tsx`
35
+ : language;
36
+ const lang = languageMap[key];
37
+ if (!lang)
38
+ throw new Error(`Unsupported language: ${language}`);
39
+ parser.setLanguage(lang);
40
+ };
41
+ // ============================================================================
42
+ // Export detection (copied — needs AST parent traversal, can't cross threads)
43
+ // ============================================================================
44
+ const isNodeExported = (node, name, language) => {
45
+ let current = node;
46
+ switch (language) {
47
+ case 'javascript':
48
+ case 'typescript':
49
+ while (current) {
50
+ const type = current.type;
51
+ if (type === 'export_statement' ||
52
+ type === 'export_specifier' ||
53
+ type === 'lexical_declaration' && current.parent?.type === 'export_statement') {
54
+ return true;
55
+ }
56
+ if (current.text?.startsWith('export ')) {
57
+ return true;
58
+ }
59
+ current = current.parent;
60
+ }
61
+ return false;
62
+ case 'python':
63
+ return !name.startsWith('_');
64
+ case 'java':
65
+ while (current) {
66
+ if (current.parent) {
67
+ const parent = current.parent;
68
+ for (let i = 0; i < parent.childCount; i++) {
69
+ const child = parent.child(i);
70
+ if (child?.type === 'modifiers' && child.text?.includes('public')) {
71
+ return true;
72
+ }
73
+ }
74
+ if (parent.type === 'method_declaration' || parent.type === 'constructor_declaration') {
75
+ if (parent.text?.trimStart().startsWith('public')) {
76
+ return true;
77
+ }
78
+ }
79
+ }
80
+ current = current.parent;
81
+ }
82
+ return false;
83
+ case 'csharp':
84
+ while (current) {
85
+ if (current.type === 'modifier' || current.type === 'modifiers') {
86
+ if (current.text?.includes('public'))
87
+ return true;
88
+ }
89
+ current = current.parent;
90
+ }
91
+ return false;
92
+ case 'go':
93
+ if (name.length === 0)
94
+ return false;
95
+ const first = name[0];
96
+ return first === first.toUpperCase() && first !== first.toLowerCase();
97
+ case 'rust':
98
+ while (current) {
99
+ if (current.type === 'visibility_modifier') {
100
+ if (current.text?.includes('pub'))
101
+ return true;
102
+ }
103
+ current = current.parent;
104
+ }
105
+ return false;
106
+ case 'c':
107
+ case 'cpp':
108
+ return false;
109
+ default:
110
+ return false;
111
+ }
112
+ };
113
+ // ============================================================================
114
+ // Enclosing function detection (for call extraction)
115
+ // ============================================================================
116
+ const FUNCTION_NODE_TYPES = new Set([
117
+ 'function_declaration', 'arrow_function', 'function_expression',
118
+ 'method_definition', 'generator_function_declaration',
119
+ 'function_definition', 'async_function_declaration', 'async_arrow_function',
120
+ 'method_declaration', 'constructor_declaration',
121
+ 'local_function_statement', 'function_item', 'impl_item',
122
+ ]);
123
+ /** Walk up AST to find enclosing function, return its generateId or null for top-level */
124
+ const findEnclosingFunctionId = (node, filePath) => {
125
+ let current = node.parent;
126
+ while (current) {
127
+ if (FUNCTION_NODE_TYPES.has(current.type)) {
128
+ let funcName = null;
129
+ let label = 'Function';
130
+ if (['function_declaration', 'function_definition', 'async_function_declaration',
131
+ 'generator_function_declaration', 'function_item'].includes(current.type)) {
132
+ const nameNode = current.childForFieldName?.('name') ||
133
+ current.children?.find((c) => c.type === 'identifier' || c.type === 'property_identifier');
134
+ funcName = nameNode?.text;
135
+ }
136
+ else if (current.type === 'impl_item') {
137
+ const funcItem = current.children?.find((c) => c.type === 'function_item');
138
+ if (funcItem) {
139
+ const nameNode = funcItem.childForFieldName?.('name') ||
140
+ funcItem.children?.find((c) => c.type === 'identifier');
141
+ funcName = nameNode?.text;
142
+ label = 'Method';
143
+ }
144
+ }
145
+ else if (current.type === 'method_definition') {
146
+ const nameNode = current.childForFieldName?.('name') ||
147
+ current.children?.find((c) => c.type === 'property_identifier');
148
+ funcName = nameNode?.text;
149
+ label = 'Method';
150
+ }
151
+ else if (current.type === 'method_declaration' || current.type === 'constructor_declaration') {
152
+ const nameNode = current.childForFieldName?.('name') ||
153
+ current.children?.find((c) => c.type === 'identifier');
154
+ funcName = nameNode?.text;
155
+ label = 'Method';
156
+ }
157
+ else if (current.type === 'arrow_function' || current.type === 'function_expression') {
158
+ const parent = current.parent;
159
+ if (parent?.type === 'variable_declarator') {
160
+ const nameNode = parent.childForFieldName?.('name') ||
161
+ parent.children?.find((c) => c.type === 'identifier');
162
+ funcName = nameNode?.text;
163
+ }
164
+ }
165
+ if (funcName) {
166
+ return generateId(label, `${filePath}:${funcName}`);
167
+ }
168
+ }
169
+ current = current.parent;
170
+ }
171
+ return null;
172
+ };
173
+ const BUILT_INS = new Set([
174
+ 'console', 'log', 'warn', 'error', 'info', 'debug',
175
+ 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval',
176
+ 'parseInt', 'parseFloat', 'isNaN', 'isFinite',
177
+ 'encodeURI', 'decodeURI', 'encodeURIComponent', 'decodeURIComponent',
178
+ 'JSON', 'parse', 'stringify',
179
+ 'Object', 'Array', 'String', 'Number', 'Boolean', 'Symbol', 'BigInt',
180
+ 'Map', 'Set', 'WeakMap', 'WeakSet',
181
+ 'Promise', 'resolve', 'reject', 'then', 'catch', 'finally',
182
+ 'Math', 'Date', 'RegExp', 'Error',
183
+ 'require', 'import', 'export', 'fetch', 'Response', 'Request',
184
+ 'useState', 'useEffect', 'useCallback', 'useMemo', 'useRef', 'useContext',
185
+ 'useReducer', 'useLayoutEffect', 'useImperativeHandle', 'useDebugValue',
186
+ 'createElement', 'createContext', 'createRef', 'forwardRef', 'memo', 'lazy',
187
+ 'map', 'filter', 'reduce', 'forEach', 'find', 'findIndex', 'some', 'every',
188
+ 'includes', 'indexOf', 'slice', 'splice', 'concat', 'join', 'split',
189
+ 'push', 'pop', 'shift', 'unshift', 'sort', 'reverse',
190
+ 'keys', 'values', 'entries', 'assign', 'freeze', 'seal',
191
+ 'hasOwnProperty', 'toString', 'valueOf',
192
+ 'print', 'len', 'range', 'str', 'int', 'float', 'list', 'dict', 'set', 'tuple',
193
+ 'open', 'read', 'write', 'close', 'append', 'extend', 'update',
194
+ 'super', 'type', 'isinstance', 'issubclass', 'getattr', 'setattr', 'hasattr',
195
+ 'enumerate', 'zip', 'sorted', 'reversed', 'min', 'max', 'sum', 'abs',
196
+ ]);
197
+ // ============================================================================
198
+ // Label detection from capture map
199
+ // ============================================================================
200
+ const getLabelFromCaptures = (captureMap) => {
201
+ // Skip imports (handled separately) and calls
202
+ if (captureMap['import'] || captureMap['call'])
203
+ return null;
204
+ if (!captureMap['name'])
205
+ return null;
206
+ if (captureMap['definition.function'])
207
+ return 'Function';
208
+ if (captureMap['definition.class'])
209
+ return 'Class';
210
+ if (captureMap['definition.interface'])
211
+ return 'Interface';
212
+ if (captureMap['definition.method'])
213
+ return 'Method';
214
+ if (captureMap['definition.struct'])
215
+ return 'Struct';
216
+ if (captureMap['definition.enum'])
217
+ return 'Enum';
218
+ if (captureMap['definition.namespace'])
219
+ return 'Namespace';
220
+ if (captureMap['definition.module'])
221
+ return 'Module';
222
+ if (captureMap['definition.trait'])
223
+ return 'Trait';
224
+ if (captureMap['definition.impl'])
225
+ return 'Impl';
226
+ if (captureMap['definition.type'])
227
+ return 'TypeAlias';
228
+ if (captureMap['definition.const'])
229
+ return 'Const';
230
+ if (captureMap['definition.static'])
231
+ return 'Static';
232
+ if (captureMap['definition.typedef'])
233
+ return 'Typedef';
234
+ if (captureMap['definition.macro'])
235
+ return 'Macro';
236
+ if (captureMap['definition.union'])
237
+ return 'Union';
238
+ if (captureMap['definition.property'])
239
+ return 'Property';
240
+ if (captureMap['definition.record'])
241
+ return 'Record';
242
+ if (captureMap['definition.delegate'])
243
+ return 'Delegate';
244
+ if (captureMap['definition.annotation'])
245
+ return 'Annotation';
246
+ if (captureMap['definition.constructor'])
247
+ return 'Constructor';
248
+ if (captureMap['definition.template'])
249
+ return 'Template';
250
+ return 'CodeElement';
251
+ };
252
+ // ============================================================================
253
+ // Process a batch of files
254
+ // ============================================================================
255
+ const processBatch = (files, onProgress) => {
256
+ const result = {
257
+ nodes: [],
258
+ relationships: [],
259
+ symbols: [],
260
+ imports: [],
261
+ calls: [],
262
+ heritage: [],
263
+ fileCount: 0,
264
+ };
265
+ // Group by language to minimize setLanguage calls
266
+ const byLanguage = new Map();
267
+ for (const file of files) {
268
+ const lang = getLanguageFromFilename(file.path);
269
+ if (!lang)
270
+ continue;
271
+ let list = byLanguage.get(lang);
272
+ if (!list) {
273
+ list = [];
274
+ byLanguage.set(lang, list);
275
+ }
276
+ list.push(file);
277
+ }
278
+ let totalProcessed = 0;
279
+ let lastReported = 0;
280
+ const PROGRESS_INTERVAL = 100; // report every 100 files
281
+ const onFileProcessed = onProgress ? () => {
282
+ totalProcessed++;
283
+ if (totalProcessed - lastReported >= PROGRESS_INTERVAL) {
284
+ lastReported = totalProcessed;
285
+ onProgress(totalProcessed);
286
+ }
287
+ } : undefined;
288
+ for (const [language, langFiles] of byLanguage) {
289
+ const queryString = LANGUAGE_QUERIES[language];
290
+ if (!queryString)
291
+ continue;
292
+ // Track if we need to handle tsx separately
293
+ const tsxFiles = [];
294
+ const regularFiles = [];
295
+ if (language === SupportedLanguages.TypeScript) {
296
+ for (const f of langFiles) {
297
+ if (f.path.endsWith('.tsx')) {
298
+ tsxFiles.push(f);
299
+ }
300
+ else {
301
+ regularFiles.push(f);
302
+ }
303
+ }
304
+ }
305
+ else {
306
+ regularFiles.push(...langFiles);
307
+ }
308
+ // Process regular files for this language
309
+ if (regularFiles.length > 0) {
310
+ setLanguage(language, regularFiles[0].path);
311
+ processFileGroup(regularFiles, language, queryString, result, onFileProcessed);
312
+ }
313
+ // Process tsx files separately (different grammar)
314
+ if (tsxFiles.length > 0) {
315
+ setLanguage(language, tsxFiles[0].path);
316
+ processFileGroup(tsxFiles, language, queryString, result, onFileProcessed);
317
+ }
318
+ }
319
+ return result;
320
+ };
321
+ const processFileGroup = (files, language, queryString, result, onFileProcessed) => {
322
+ let query;
323
+ try {
324
+ const lang = parser.getLanguage();
325
+ query = new Parser.Query(lang, queryString);
326
+ }
327
+ catch {
328
+ return;
329
+ }
330
+ for (const file of files) {
331
+ let tree;
332
+ try {
333
+ tree = parser.parse(file.content, undefined, { bufferSize: 1024 * 256 });
334
+ }
335
+ catch {
336
+ continue;
337
+ }
338
+ result.fileCount++;
339
+ onFileProcessed?.();
340
+ let matches;
341
+ try {
342
+ matches = query.matches(tree.rootNode);
343
+ }
344
+ catch {
345
+ continue;
346
+ }
347
+ for (const match of matches) {
348
+ const captureMap = {};
349
+ for (const c of match.captures) {
350
+ captureMap[c.name] = c.node;
351
+ }
352
+ // Extract import paths before skipping
353
+ if (captureMap['import'] && captureMap['import.source']) {
354
+ const rawImportPath = captureMap['import.source'].text.replace(/['"<>]/g, '');
355
+ result.imports.push({
356
+ filePath: file.path,
357
+ rawImportPath,
358
+ language: language,
359
+ });
360
+ continue;
361
+ }
362
+ // Extract call sites
363
+ if (captureMap['call']) {
364
+ const callNameNode = captureMap['call.name'];
365
+ if (callNameNode) {
366
+ const calledName = callNameNode.text;
367
+ if (!BUILT_INS.has(calledName)) {
368
+ const callNode = captureMap['call'];
369
+ const sourceId = findEnclosingFunctionId(callNode, file.path)
370
+ || generateId('File', file.path);
371
+ result.calls.push({ filePath: file.path, calledName, sourceId });
372
+ }
373
+ }
374
+ continue;
375
+ }
376
+ // Extract heritage (extends/implements)
377
+ if (captureMap['heritage.class']) {
378
+ if (captureMap['heritage.extends']) {
379
+ result.heritage.push({
380
+ filePath: file.path,
381
+ className: captureMap['heritage.class'].text,
382
+ parentName: captureMap['heritage.extends'].text,
383
+ kind: 'extends',
384
+ });
385
+ }
386
+ if (captureMap['heritage.implements']) {
387
+ result.heritage.push({
388
+ filePath: file.path,
389
+ className: captureMap['heritage.class'].text,
390
+ parentName: captureMap['heritage.implements'].text,
391
+ kind: 'implements',
392
+ });
393
+ }
394
+ if (captureMap['heritage.trait']) {
395
+ result.heritage.push({
396
+ filePath: file.path,
397
+ className: captureMap['heritage.class'].text,
398
+ parentName: captureMap['heritage.trait'].text,
399
+ kind: 'trait-impl',
400
+ });
401
+ }
402
+ if (captureMap['heritage.extends'] || captureMap['heritage.implements'] || captureMap['heritage.trait']) {
403
+ continue;
404
+ }
405
+ }
406
+ const nodeLabel = getLabelFromCaptures(captureMap);
407
+ if (!nodeLabel)
408
+ continue;
409
+ const nameNode = captureMap['name'];
410
+ const nodeName = nameNode.text;
411
+ const nodeId = generateId(nodeLabel, `${file.path}:${nodeName}`);
412
+ result.nodes.push({
413
+ id: nodeId,
414
+ label: nodeLabel,
415
+ properties: {
416
+ name: nodeName,
417
+ filePath: file.path,
418
+ startLine: nameNode.startPosition.row,
419
+ endLine: nameNode.endPosition.row,
420
+ language: language,
421
+ isExported: isNodeExported(nameNode, nodeName, language),
422
+ },
423
+ });
424
+ result.symbols.push({
425
+ filePath: file.path,
426
+ name: nodeName,
427
+ nodeId,
428
+ type: nodeLabel,
429
+ });
430
+ const fileId = generateId('File', file.path);
431
+ const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
432
+ result.relationships.push({
433
+ id: relId,
434
+ sourceId: fileId,
435
+ targetId: nodeId,
436
+ type: 'DEFINES',
437
+ confidence: 1.0,
438
+ reason: '',
439
+ });
440
+ }
441
+ }
442
+ };
443
+ // ============================================================================
444
+ // Worker message handler
445
+ // ============================================================================
446
+ parentPort.on('message', (files) => {
447
+ const result = processBatch(files, (filesProcessed) => {
448
+ parentPort.postMessage({ type: 'progress', filesProcessed });
449
+ });
450
+ parentPort.postMessage({ type: 'result', data: result });
451
+ });
@@ -0,0 +1,22 @@
1
+ export interface WorkerPool {
2
+ /**
3
+ * Dispatch items across workers. Items are split into chunks (one per worker),
4
+ * each worker processes its chunk, and results are concatenated back in order.
5
+ *
6
+ * @param onProgress - Called with cumulative files processed across all workers
7
+ */
8
+ dispatch<TInput, TResult>(items: TInput[], onProgress?: (filesProcessed: number) => void): Promise<TResult[]>;
9
+ /**
10
+ * Terminate all workers. Must be called when done.
11
+ */
12
+ terminate(): Promise<void>;
13
+ /** Number of workers in the pool */
14
+ readonly size: number;
15
+ }
16
+ /**
17
+ * Create a pool of worker threads.
18
+ *
19
+ * @param workerUrl - URL to the worker script (use `new URL('./parse-worker.js', import.meta.url)`)
20
+ * @param poolSize - Number of workers (defaults to cpus - 1, minimum 1)
21
+ */
22
+ export declare const createWorkerPool: (workerUrl: URL, poolSize?: number) => WorkerPool;
@@ -0,0 +1,65 @@
1
+ import { Worker } from 'node:worker_threads';
2
+ import os from 'node:os';
3
+ /**
4
+ * Create a pool of worker threads.
5
+ *
6
+ * @param workerUrl - URL to the worker script (use `new URL('./parse-worker.js', import.meta.url)`)
7
+ * @param poolSize - Number of workers (defaults to cpus - 1, minimum 1)
8
+ */
9
+ export const createWorkerPool = (workerUrl, poolSize) => {
10
+ const size = poolSize ?? Math.max(1, os.cpus().length - 1);
11
+ const workers = [];
12
+ for (let i = 0; i < size; i++) {
13
+ workers.push(new Worker(workerUrl));
14
+ }
15
+ const dispatch = (items, onProgress) => {
16
+ if (items.length === 0)
17
+ return Promise.resolve([]);
18
+ // Split items into one chunk per worker
19
+ const chunkSize = Math.ceil(items.length / size);
20
+ const chunks = [];
21
+ for (let i = 0; i < items.length; i += chunkSize) {
22
+ chunks.push(items.slice(i, i + chunkSize));
23
+ }
24
+ // Track per-worker progress for cumulative reporting
25
+ const workerProgress = new Array(chunks.length).fill(0);
26
+ // Send one chunk to each worker, collect results
27
+ const promises = chunks.map((chunk, i) => {
28
+ const worker = workers[i];
29
+ return new Promise((resolve, reject) => {
30
+ const handler = (msg) => {
31
+ if (msg && msg.type === 'progress') {
32
+ // Intermediate progress from worker
33
+ workerProgress[i] = msg.filesProcessed;
34
+ if (onProgress) {
35
+ const total = workerProgress.reduce((a, b) => a + b, 0);
36
+ onProgress(total);
37
+ }
38
+ }
39
+ else if (msg && msg.type === 'result') {
40
+ // Final result
41
+ worker.removeListener('message', handler);
42
+ resolve(msg.data);
43
+ }
44
+ else {
45
+ // Legacy: treat any non-typed message as result (backward compat)
46
+ worker.removeListener('message', handler);
47
+ resolve(msg);
48
+ }
49
+ };
50
+ worker.on('message', handler);
51
+ worker.once('error', (err) => {
52
+ worker.removeListener('message', handler);
53
+ reject(err);
54
+ });
55
+ worker.postMessage(chunk);
56
+ });
57
+ });
58
+ return Promise.all(promises);
59
+ };
60
+ const terminate = async () => {
61
+ await Promise.all(workers.map(w => w.terminate()));
62
+ workers.length = 0;
63
+ };
64
+ return { dispatch, terminate, size };
65
+ };
@@ -4,10 +4,12 @@ export declare const initKuzu: (dbPath: string) => Promise<{
4
4
  db: kuzu.Database;
5
5
  conn: kuzu.Connection;
6
6
  }>;
7
- export declare const loadGraphToKuzu: (graph: KnowledgeGraph, fileContents: Map<string, string>, storagePath: string) => Promise<{
7
+ export type KuzuProgressCallback = (message: string) => void;
8
+ export declare const loadGraphToKuzu: (graph: KnowledgeGraph, fileContents: Map<string, string>, storagePath: string, onProgress?: KuzuProgressCallback) => Promise<{
8
9
  success: boolean;
9
10
  insertedRels: number;
10
11
  skippedRels: number;
12
+ warnings: string[];
11
13
  }>;
12
14
  /**
13
15
  * Insert a single node to KuzuDB
@@ -35,6 +37,18 @@ export declare const getKuzuStats: () => Promise<{
35
37
  nodes: number;
36
38
  edges: number;
37
39
  }>;
40
+ /**
41
+ * Load cached embeddings from KuzuDB before a rebuild.
42
+ * Returns all embedding vectors so they can be re-inserted after the graph is reloaded,
43
+ * avoiding expensive re-embedding of unchanged nodes.
44
+ */
45
+ export declare const loadCachedEmbeddings: () => Promise<{
46
+ embeddingNodeIds: Set<string>;
47
+ embeddings: Array<{
48
+ nodeId: string;
49
+ embedding: number[];
50
+ }>;
51
+ }>;
38
52
  export declare const closeKuzu: () => Promise<void>;
39
53
  export declare const isKuzuReady: () => boolean;
40
54
  /**