smart-coding-mcp-gemini-milvus 1.0.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.
@@ -0,0 +1,2178 @@
1
+ [
2
+ {
3
+ "file": "/Users/jun/Library/Mobile Documents/iCloud~md~obsidian/Documents/new/700_projects/smart-coding-mcp/test/index-codebase-stats.test.js",
4
+ "startLine": 1,
5
+ "endLine": 60,
6
+ "content": "import { describe, expect, it, vi } from \"vitest\";\nimport { handleToolCall } from \"../features/index-codebase.js\";\n\nfunction makeRequest(force = false) {\n return {\n params: {\n arguments: { force }\n }\n };\n}\n\ndescribe(\"index-codebase stats contract\", () => {\n it(\"should use cache.getStats() when index result omits totals\", async () => {\n const indexer = {\n indexAll: vi.fn().mockResolvedValue({\n skipped: false,\n filesProcessed: 0,\n chunksCreated: 0,\n message: \"All files up to date\"\n }),\n cache: {\n getStats: vi.fn().mockResolvedValue({ totalChunks: 11, totalFiles: 3 })\n }\n };\n\n const result = await handleToolCall(makeRequest(false), indexer);\n const text = result.content[0].text;\n\n expect(indexer.indexAll).toHaveBeenCalledWith(false);\n expect(indexer.cache.getStats).toHaveBeenCalledTimes(1);\n expect(text).toContain(\"Total files in index: 3\");\n expect(text).toContain(\"Total code chunks: 11\");\n });\n\n it(\"should fall back to getVectorStore() when getStats is unavailable\", async () => {\n const indexer = {\n indexAll: vi.fn().mockResolvedValue({\n skipped: false,\n filesProcessed: 1,\n chunksCreated: 2\n }),\n cache: {\n getVectorStore: vi.fn(() => [\n { file: \"a.js\" },\n { file: \"b.js\" },\n { file: \"b.js\" }\n ])\n }\n };\n\n const result = await handleToolCall(makeRequest(true), indexer);\n const text = result.content[0].text;\n\n expect(indexer.cache.getVectorStore).toHaveBeenCalledTimes(1);\n expect(text).toContain(\"Total files in index: 2\");\n expect(text).toContain(\"Total code chunks: 3\");\n });\n});\n\n",
7
+ "vector": [
8
+ 0.054598335176706314,
9
+ 0.0674959048628807,
10
+ -0.2977464199066162,
11
+ -0.1877332180738449,
12
+ 0.1941823959350586,
13
+ -0.06262926757335663,
14
+ 0.014063971117138863,
15
+ 0.044108547270298004,
16
+ -0.0016840137541294098,
17
+ -0.06465877592563629,
18
+ -0.04713379591703415,
19
+ 0.020002197474241257,
20
+ 0.1804225891828537,
21
+ -0.07261928170919418,
22
+ -0.03599085658788681,
23
+ -0.08713425695896149,
24
+ 0.049842264503240585,
25
+ -0.06274900585412979,
26
+ 0.08184421807527542,
27
+ 0.04594619199633598,
28
+ -0.017797201871871948,
29
+ -0.15160997211933136,
30
+ -0.020963022485375404,
31
+ -0.06086591258645058,
32
+ 0.11810649931430817,
33
+ 0.09126118570566177,
34
+ -0.11911560595035553,
35
+ 0.1672920435667038,
36
+ -0.06647289544343948,
37
+ -0.0013188609154894948,
38
+ 0.04308583587408066,
39
+ 0.10684814304113388,
40
+ 0.02013748697936535,
41
+ -0.11750737577676773,
42
+ -0.06019354611635208,
43
+ -0.000051985196478199214,
44
+ -0.004939420148730278,
45
+ -0.02915136143565178,
46
+ -0.05990036204457283,
47
+ -0.007823914289474487,
48
+ -0.045967426151037216,
49
+ -0.03369491174817085,
50
+ 0.015698451548814774,
51
+ -0.04279430955648422,
52
+ 0.0814020112156868,
53
+ 0.07186364382505417,
54
+ 0.08396220952272415,
55
+ 0.02557535283267498,
56
+ 0.10089369118213654,
57
+ -0.06484086811542511,
58
+ 0.022015566006302834,
59
+ -0.08719930052757263,
60
+ -0.014924122020602226,
61
+ 0.04639714956283569,
62
+ 0.3118610978126526,
63
+ 0.08965770155191422,
64
+ -0.05254017561674118,
65
+ 0.003778001293540001,
66
+ 0.1080775260925293,
67
+ -0.0830996111035347,
68
+ 0.0740845575928688,
69
+ 0.044824566692113876,
70
+ -0.024893954396247864,
71
+ 0.175155907869339,
72
+ 0.1429317593574524,
73
+ -0.036782972514629364,
74
+ -0.11077208071947098,
75
+ 0.11984791606664658,
76
+ -0.14056235551834106,
77
+ 0.06692388653755188,
78
+ 0.03456650301814079,
79
+ 0.02841077372431755,
80
+ 0.0051514506340026855,
81
+ -0.06547945737838745,
82
+ -0.15418709814548492,
83
+ 0.047584325075149536,
84
+ -0.056872494518756866,
85
+ -0.021452568471431732,
86
+ -0.039552222937345505,
87
+ 0.137307271361351,
88
+ -0.08838097006082535,
89
+ 0.059921711683273315,
90
+ 0.06751919537782669,
91
+ -0.02543141320347786,
92
+ 0.0352645143866539,
93
+ -0.06271366775035858,
94
+ -0.057530585676431656,
95
+ 0.09484885632991791,
96
+ -0.04472501948475838,
97
+ 0.19828885793685913,
98
+ -0.008319707587361336,
99
+ -0.024846144020557404,
100
+ 0.01583508402109146,
101
+ 0.015131562948226929,
102
+ -0.07787294685840607,
103
+ -0.012766721658408642,
104
+ -0.09671417623758316,
105
+ 0.0002252580743515864,
106
+ -0.022073201835155487,
107
+ -0.05513177067041397,
108
+ 0.07003970444202423,
109
+ 0.0023956180084496737,
110
+ -0.019540993496775627,
111
+ -0.10549686849117279,
112
+ 0.012302329763770103,
113
+ 0.12458087503910065,
114
+ 0.10899897664785385,
115
+ 0.06216801702976227,
116
+ 0.04711635410785675,
117
+ -0.09572803229093552,
118
+ 0.033523693680763245,
119
+ 0.13049595057964325,
120
+ -0.09976539015769958,
121
+ -0.14194761216640472,
122
+ -0.08948231488466263,
123
+ -0.1370280683040619,
124
+ 0.034138672053813934,
125
+ 0.03195072337985039,
126
+ -0.1576949954032898,
127
+ 0.041998155415058136,
128
+ -0.01155532244592905,
129
+ -0.043790582567453384,
130
+ 0.038083307445049286,
131
+ 0.062390852719545364,
132
+ 0.08895697444677353,
133
+ 0.04169796034693718,
134
+ -0.00823562778532505,
135
+ 0.06302276253700256
136
+ ]
137
+ },
138
+ {
139
+ "file": "/Users/jun/Library/Mobile Documents/iCloud~md~obsidian/Documents/new/700_projects/smart-coding-mcp/test/get-status.test.js",
140
+ "startLine": 1,
141
+ "endLine": 92,
142
+ "content": "import { describe, expect, it, vi } from \"vitest\";\nimport { StatusReporter } from \"../features/get-status.js\";\n\nfunction makeConfig(overrides = {}) {\n return {\n searchDirectory: \"/tmp/workspace\",\n cacheDirectory: \"/tmp/workspace/.smart-coding-cache\",\n vectorStoreProvider: \"milvus\",\n embeddingProvider: \"gemini\",\n geminiModel: \"gemini-embedding-001\",\n geminiDimensions: 768,\n embeddingModel: \"nomic-embed-text-v1.5\",\n embeddingDimension: 128,\n chunkingMode: \"smart\",\n maxResults: 5,\n chunkSize: 25,\n semanticWeight: 0.7,\n exactMatchBoost: 1.5,\n workerThreads: 2,\n enableCache: true,\n milvusAddress: \"http://127.0.0.1:19530\",\n maxCpuPercent: 70,\n batchDelay: 100,\n maxWorkers: 4,\n ...overrides\n };\n}\n\ndescribe(\"StatusReporter\", () => {\n it(\"should prefer cache.getStats() when available\", async () => {\n const cache = {\n getStats: vi.fn().mockResolvedValue({ totalChunks: 12, totalFiles: 4 }),\n getVectorStore: vi.fn(() => {\n throw new Error(\"getVectorStore should not be called when getStats works\");\n })\n };\n const indexer = { isIndexing: false, indexingStatus: null };\n const embedder = { modelName: \"gemini-embedding-001\", dimension: 768, device: \"cpu\" };\n const reporter = new StatusReporter(makeConfig(), cache, indexer, embedder);\n\n const status = await reporter.getStatus();\n\n expect(cache.getStats).toHaveBeenCalledTimes(1);\n expect(status.index.filesIndexed).toBe(4);\n expect(status.index.chunksCount).toBe(12);\n expect(status.index.status).toBe(\"ready\");\n expect(status.cache.type).toBe(\"milvus\");\n });\n\n it(\"should fall back to getVectorStore() when getStats fails\", async () => {\n const cache = {\n getStats: vi.fn().mockRejectedValue(new Error(\"stats unavailable\")),\n getVectorStore: vi.fn(() => [\n { file: \"a.js\" },\n { file: \"a.js\" },\n { file: \"b.js\" }\n ])\n };\n const indexer = { isIndexing: false, indexingStatus: null };\n const reporter = new StatusReporter(makeConfig(), cache, indexer, null);\n\n const status = await reporter.getStatus();\n\n expect(cache.getStats).toHaveBeenCalledTimes(1);\n expect(cache.getVectorStore).toHaveBeenCalledTimes(1);\n expect(status.index.filesIndexed).toBe(2);\n expect(status.index.chunksCount).toBe(3);\n });\n\n it(\"should report indexing status while in progress\", async () => {\n const cache = {\n getStats: vi.fn().mockResolvedValue({ totalChunks: 0, totalFiles: 0 }),\n getVectorStore: vi.fn(() => [])\n };\n const indexer = {\n isIndexing: true,\n indexingStatus: {\n inProgress: true,\n totalFiles: 100,\n processedFiles: 20,\n percentage: 20\n }\n };\n const reporter = new StatusReporter(makeConfig(), cache, indexer, null);\n\n const status = await reporter.getStatus();\n expect(status.index.status).toBe(\"indexing\");\n expect(status.index.progressiveIndexing.percentage).toBe(20);\n });\n});\n\n",
143
+ "vector": [
144
+ 0.04583364725112915,
145
+ 0.052701517939567566,
146
+ -0.31435897946357727,
147
+ -0.19019412994384766,
148
+ 0.15509077906608582,
149
+ -0.0923856645822525,
150
+ 0.092455193400383,
151
+ 0.023575186729431152,
152
+ 0.023716764524579048,
153
+ -0.12136812508106232,
154
+ -0.076286181807518,
155
+ 0.05254514515399933,
156
+ 0.10938583314418793,
157
+ 0.014749512076377869,
158
+ -0.00716559449210763,
159
+ -0.08028094470500946,
160
+ 0.07627272605895996,
161
+ 0.009165692143142223,
162
+ 0.08740431070327759,
163
+ 0.020610565319657326,
164
+ -0.053938042372465134,
165
+ -0.17580999433994293,
166
+ -0.00385354389436543,
167
+ 0.03373296558856964,
168
+ 0.1359092742204666,
169
+ 0.1723298281431198,
170
+ -0.04108219966292381,
171
+ 0.06922119110822678,
172
+ -0.033320289105176926,
173
+ 0.09653919190168381,
174
+ 0.13303333520889282,
175
+ 0.03103306144475937,
176
+ 0.0314786396920681,
177
+ -0.046766918152570724,
178
+ 0.013277743943035603,
179
+ 0.0026263121981173754,
180
+ -0.027143660932779312,
181
+ -0.039895713329315186,
182
+ -0.1140158548951149,
183
+ -0.03089936636388302,
184
+ -0.0412115603685379,
185
+ -0.037832219153642654,
186
+ -0.05735001340508461,
187
+ -0.027620377019047737,
188
+ 0.06747128814458847,
189
+ 0.09258429706096649,
190
+ 0.11416105180978775,
191
+ -0.004929475486278534,
192
+ 0.061462000012397766,
193
+ -0.054039016366004944,
194
+ 0.022203557193279266,
195
+ -0.05990877002477646,
196
+ -0.041919123381376266,
197
+ 0.04625212773680687,
198
+ 0.37580668926239014,
199
+ 0.01396937295794487,
200
+ -0.055026568472385406,
201
+ -0.04819362610578537,
202
+ 0.11839242279529572,
203
+ -0.0811220332980156,
204
+ 0.028673093765974045,
205
+ 0.0797637477517128,
206
+ -0.07266566902399063,
207
+ 0.17753095924854279,
208
+ 0.12957090139389038,
209
+ -0.045078471302986145,
210
+ -0.07025127112865448,
211
+ 0.13666021823883057,
212
+ -0.13088734447956085,
213
+ 0.047984976321458817,
214
+ 0.036127638071775436,
215
+ 0.02965630404651165,
216
+ 0.015829386189579964,
217
+ -0.08131137490272522,
218
+ -0.1041240319609642,
219
+ 0.046233464032411575,
220
+ -0.027813412249088287,
221
+ 0.004127973224967718,
222
+ -0.020484164357185364,
223
+ 0.10217394679784775,
224
+ -0.02917255461215973,
225
+ 0.05965561419725418,
226
+ 0.15591692924499512,
227
+ -0.07778796553611755,
228
+ 0.02838732860982418,
229
+ -0.0528959259390831,
230
+ 0.027625927701592445,
231
+ 0.04659384861588478,
232
+ -0.10126794874668121,
233
+ 0.15252704918384552,
234
+ 0.07509565353393555,
235
+ -0.025725649669766426,
236
+ -0.008682353422045708,
237
+ -0.008050068281590939,
238
+ -0.053889382630586624,
239
+ 0.00427358690649271,
240
+ -0.06075211986899376,
241
+ -0.06988363713026047,
242
+ -0.04479391127824783,
243
+ -0.08921290934085846,
244
+ 0.11082886904478073,
245
+ -0.029360154643654823,
246
+ -0.04365053400397301,
247
+ -0.1183936819434166,
248
+ -0.0024439096450805664,
249
+ 0.20160333812236786,
250
+ 0.026828859001398087,
251
+ 0.06399279087781906,
252
+ 0.03326292708516121,
253
+ -0.07607757300138474,
254
+ -0.011858376674354076,
255
+ 0.12419266253709793,
256
+ -0.04933495074510574,
257
+ -0.11230161041021347,
258
+ -0.06248500943183899,
259
+ -0.10743910074234009,
260
+ 0.059233929961919785,
261
+ 0.006495467387139797,
262
+ -0.10230936855077744,
263
+ 0.05885843560099602,
264
+ -0.0014504410792142153,
265
+ -0.04485231265425682,
266
+ -0.05425828695297241,
267
+ 0.002610561903566122,
268
+ 0.08578766882419586,
269
+ 0.0730641633272171,
270
+ -0.05205298215150833,
271
+ -0.023540278896689415
272
+ ]
273
+ },
274
+ {
275
+ "file": "/Users/jun/Library/Mobile Documents/iCloud~md~obsidian/Documents/new/700_projects/smart-coding-mcp/test/hybrid-search-ann.test.js",
276
+ "startLine": 1,
277
+ "endLine": 92,
278
+ "content": "import { describe, expect, it, vi } from \"vitest\";\nimport { HybridSearch } from \"../features/hybrid-search.js\";\n\nfunction makeConfig(overrides = {}) {\n return {\n semanticWeight: 0.7,\n exactMatchBoost: 1.5,\n searchDirectory: \"/tmp/workspace\",\n ...overrides\n };\n}\n\nfunction makeEmbedder() {\n return vi.fn().mockResolvedValue({\n data: new Float32Array([0.1, 0.2, 0.3])\n });\n}\n\ndescribe(\"HybridSearch ANN path\", () => {\n it(\"should use cache.searchByVector with expanded topK and return ranked results\", async () => {\n const embedder = makeEmbedder();\n const cache = {\n searchByVector: vi.fn().mockResolvedValue([\n {\n file: \"/tmp/workspace/src/auth.js\",\n startLine: 1,\n endLine: 20,\n content: \"export async function login() {}\",\n score: 0.6\n },\n {\n file: \"/tmp/workspace/src/other.js\",\n startLine: 1,\n endLine: 20,\n content: \"export async function fetchProfile() {}\",\n score: 0.9\n }\n ]),\n getStats: vi.fn().mockResolvedValue({ totalChunks: 2, totalFiles: 2 })\n };\n const indexer = { indexingStatus: { inProgress: false, percentage: 0 } };\n const search = new HybridSearch(embedder, cache, makeConfig(), indexer);\n\n const { results, message } = await search.search(\"login\", 3);\n\n expect(message).toBeNull();\n expect(cache.searchByVector).toHaveBeenCalledTimes(1);\n // max(3 * 5, 20) = 20\n expect(cache.searchByVector.mock.calls[0][1]).toBe(20);\n expect(results.length).toBe(2);\n // Exact-match boost should put login result on top despite lower base score.\n expect(results[0].file).toContain(\"auth.js\");\n });\n\n it(\"should return no-index message when ANN results are empty and cache has no chunks\", async () => {\n const embedder = makeEmbedder();\n const cache = {\n searchByVector: vi.fn().mockResolvedValue([]),\n getStats: vi.fn().mockResolvedValue({ totalChunks: 0, totalFiles: 0 })\n };\n const search = new HybridSearch(\n embedder,\n cache,\n makeConfig(),\n { indexingStatus: { inProgress: false, percentage: 0 } }\n );\n\n const { results, message } = await search.search(\"anything\", 3);\n expect(results).toEqual([]);\n expect(message).toContain(\"No code has been indexed yet\");\n });\n\n it(\"should return indexing-progress message when ANN results are empty during indexing\", async () => {\n const embedder = makeEmbedder();\n const cache = {\n searchByVector: vi.fn().mockResolvedValue([]),\n getStats: vi.fn().mockResolvedValue({ totalChunks: 0, totalFiles: 0 })\n };\n const search = new HybridSearch(\n embedder,\n cache,\n makeConfig(),\n { indexingStatus: { inProgress: true, percentage: 35 } }\n );\n\n const { results, message } = await search.search(\"anything\", 3);\n expect(results).toEqual([]);\n expect(message).toContain(\"Indexing in progress (35% complete)\");\n });\n});\n\n",
279
+ "vector": [
280
+ 0.005819670390337706,
281
+ 0.08258644491434097,
282
+ -0.31773853302001953,
283
+ -0.12712150812149048,
284
+ 0.16253821551799774,
285
+ -0.05547786131501198,
286
+ 0.09177308529615402,
287
+ 0.05209217220544815,
288
+ 0.05419207364320755,
289
+ -0.1235148161649704,
290
+ -0.028270350769162178,
291
+ 0.04541080445051193,
292
+ 0.1574595868587494,
293
+ -0.04133984446525574,
294
+ -0.11200789362192154,
295
+ -0.02112571708858013,
296
+ 0.11526186764240265,
297
+ -0.02122793160378933,
298
+ 0.07672590762376785,
299
+ 0.032133549451828,
300
+ -0.080794557929039,
301
+ -0.12684716284275055,
302
+ -0.0021642011124640703,
303
+ 0.03603367879986763,
304
+ 0.1363755166530609,
305
+ 0.14416658878326416,
306
+ -0.1187567263841629,
307
+ 0.09125413000583649,
308
+ -0.049319587647914886,
309
+ 0.10815376043319702,
310
+ 0.059690989553928375,
311
+ 0.048823848366737366,
312
+ 0.026760630309581757,
313
+ 0.02360844798386097,
314
+ -0.12874287366867065,
315
+ -0.044424381107091904,
316
+ 0.005943460389971733,
317
+ 0.007701973430812359,
318
+ -0.07650420069694519,
319
+ 0.07300559431314468,
320
+ -0.011420217342674732,
321
+ 0.013670342043042183,
322
+ -0.033739592880010605,
323
+ -0.08197066932916641,
324
+ 0.04918273910880089,
325
+ 0.0289541594684124,
326
+ 0.07401403039693832,
327
+ 0.013368534855544567,
328
+ 0.1713346540927887,
329
+ -0.14328284561634064,
330
+ -0.027488917112350464,
331
+ -0.029979176819324493,
332
+ -0.027084555476903915,
333
+ -0.00564170116558671,
334
+ 0.31498318910598755,
335
+ 0.04420626536011696,
336
+ -0.06595029681921005,
337
+ -0.060268741101026535,
338
+ 0.16311311721801758,
339
+ -0.012370992451906204,
340
+ 0.05471157282590866,
341
+ 0.05713430419564247,
342
+ -0.08896459639072418,
343
+ 0.18114115297794342,
344
+ 0.1609184592962265,
345
+ -0.09326232224702835,
346
+ -0.036435265094041824,
347
+ 0.08508171886205673,
348
+ -0.07250279933214188,
349
+ 0.014586848206818104,
350
+ 0.053035397082567215,
351
+ 0.00719853863120079,
352
+ 0.012193208560347557,
353
+ -0.04582884535193443,
354
+ -0.14658495783805847,
355
+ 0.0822305977344513,
356
+ -0.07350815087556839,
357
+ -0.018065890297293663,
358
+ 0.004484567791223526,
359
+ 0.15102313458919525,
360
+ 0.013317412696778774,
361
+ 0.0551181435585022,
362
+ 0.045344121754169464,
363
+ -0.019709965214133263,
364
+ 0.023972632363438606,
365
+ -0.07546709477901459,
366
+ -0.017601193860173225,
367
+ 0.024891924113035202,
368
+ -0.07839994877576828,
369
+ 0.1504017859697342,
370
+ 0.09807147085666656,
371
+ -0.08361978828907013,
372
+ -0.016263773664832115,
373
+ -0.055570755153894424,
374
+ -0.09492272883653641,
375
+ 0.07796304672956467,
376
+ -0.09073076397180557,
377
+ -0.012596683576703072,
378
+ 0.013228489086031914,
379
+ -0.10546976327896118,
380
+ 0.05545874312520027,
381
+ -0.04222075641155243,
382
+ -0.050683051347732544,
383
+ -0.12240958958864212,
384
+ -0.018347764387726784,
385
+ 0.15500836074352264,
386
+ 0.04130037873983383,
387
+ 0.0492885559797287,
388
+ -0.011226184666156769,
389
+ -0.07462459802627563,
390
+ -0.023724716156721115,
391
+ 0.0965256318449974,
392
+ -0.09009316563606262,
393
+ -0.18899451196193695,
394
+ -0.0357961468398571,
395
+ -0.08571311831474304,
396
+ -0.03372519835829735,
397
+ 0.05454622954130173,
398
+ 0.005792342126369476,
399
+ 0.11176207661628723,
400
+ -0.010873362421989441,
401
+ -0.006714796647429466,
402
+ -0.05863803252577782,
403
+ 0.08751683682203293,
404
+ 0.08173485845327377,
405
+ 0.039866771548986435,
406
+ -0.054378580302000046,
407
+ -0.0018829719629138708
408
+ ]
409
+ },
410
+ {
411
+ "file": "/Users/jun/Library/Mobile Documents/iCloud~md~obsidian/Documents/new/700_projects/smart-coding-mcp/test/milvus-cache-contract.test.js",
412
+ "startLine": 1,
413
+ "endLine": 172,
414
+ "content": "import fs from \"fs/promises\";\nimport os from \"os\";\nimport path from \"path\";\nimport { afterEach, describe, expect, it, vi } from \"vitest\";\nimport { MilvusCache } from \"../lib/milvus-cache.js\";\n\nconst tempDirs = [];\n\nasync function makeConfig(overrides = {}) {\n const cacheDirectory = await fs.mkdtemp(path.join(os.tmpdir(), \"smart-coding-milvus-cache-\"));\n tempDirs.push(cacheDirectory);\n return {\n enableCache: true,\n cacheDirectory,\n embeddingProvider: \"gemini\",\n geminiDimensions: 768,\n embeddingDimension: 128,\n milvusCollection: \"test_collection\",\n milvusDatabase: \"default\",\n milvusAddress: \"\",\n milvusToken: \"\",\n ...overrides\n };\n}\n\nafterEach(async () => {\n for (const dir of tempDirs.splice(0)) {\n await fs.rm(dir, { recursive: true, force: true });\n }\n});\n\ndescribe(\"Milvus Cache Contract\", () => {\n it(\"should fail load when milvus address is missing\", async () => {\n const config = await makeConfig({ milvusAddress: \"\" });\n const cache = new MilvusCache(config);\n\n await expect(cache.load()).rejects.toThrow(\"SMART_CODING_MILVUS_ADDRESS\");\n });\n\n it(\"should keep cache contract for in-memory operations before load\", async () => {\n const config = await makeConfig();\n const cache = new MilvusCache(config);\n\n cache.addBatchToStore([\n {\n file: \"a.js\",\n startLine: 1,\n endLine: 3,\n content: \"const a = 1;\",\n vector: new Array(768).fill(0.1)\n },\n {\n file: \"b.js\",\n startLine: 5,\n endLine: 9,\n content: \"const b = 2;\",\n vector: new Array(768).fill(0.2)\n }\n ]);\n\n expect(cache.getVectorStore()).toHaveLength(2);\n\n cache.removeFileFromStore(\"a.js\");\n expect(cache.getVectorStore()).toHaveLength(1);\n expect(cache.getVectorStore()[0].file).toBe(\"b.js\");\n });\n\n it(\"should manage file hashes with hash/mtime contract\", async () => {\n const config = await makeConfig();\n const cache = new MilvusCache(config);\n\n cache.setFileHash(\"x.ts\", \"hash-1\", 123);\n expect(cache.getFileHash(\"x.ts\")).toBe(\"hash-1\");\n expect(cache.getFileMtime(\"x.ts\")).toBe(123);\n\n cache.deleteFileHash(\"x.ts\");\n expect(cache.getFileHash(\"x.ts\")).toBeNull();\n expect(cache.getFileMtime(\"x.ts\")).toBeNull();\n });\n\n it(\"should search by vector using Milvus ANN params and map results\", async () => {\n const config = await makeConfig();\n const cache = new MilvusCache(config);\n\n const mockSearch = vi.fn().mockResolvedValue({\n results: [\n {\n file: \"src/auth.js\",\n start_line: \"10\",\n end_line: \"20\",\n content: \"export async function login() {}\",\n score: 0.9321\n }\n ]\n });\n\n cache.client = { search: mockSearch };\n\n const rows = await cache.searchByVector(\n new Array(768).fill(0.01),\n 7,\n 'file == \"src/auth.js\"'\n );\n\n expect(mockSearch).toHaveBeenCalledTimes(1);\n const request = mockSearch.mock.calls[0][0];\n expect(request.collection_name).toBe(\"test_collection\");\n expect(request.anns_field).toBe(\"vector\");\n expect(request.metric_type).toBe(\"COSINE\");\n expect(request.limit).toBe(7);\n expect(request.data).toHaveLength(768);\n expect(request.filter).toBe('file == \"src/auth.js\"');\n\n expect(rows).toEqual([\n {\n file: \"src/auth.js\",\n startLine: 10,\n endLine: 20,\n content: \"export async function login() {}\",\n score: 0.9321\n }\n ]);\n });\n\n it(\"should flatten nested result arrays from SDK response\", async () => {\n const config = await makeConfig();\n const cache = new MilvusCache(config);\n\n cache.client = {\n search: vi.fn().mockResolvedValue({\n results: [\n [\n {\n file: \"src/a.js\",\n start_line: 1,\n end_line: 2,\n content: \"const a = 1\",\n score: 0.8\n }\n ]\n ]\n })\n };\n\n const rows = await cache.searchByVector(new Array(768).fill(0.02), 3);\n expect(rows).toHaveLength(1);\n expect(rows[0].file).toBe(\"src/a.js\");\n expect(rows[0].score).toBe(0.8);\n });\n\n it(\"should throw when client is not initialized\", async () => {\n const config = await makeConfig();\n const cache = new MilvusCache(config);\n\n await expect(cache.searchByVector(new Array(768).fill(0.01), 5)).rejects.toThrow(\n \"Milvus client not initialized\"\n );\n });\n\n it(\"should validate query vector dimension before search call\", async () => {\n const config = await makeConfig();\n const cache = new MilvusCache(config);\n const mockSearch = vi.fn();\n cache.client = { search: mockSearch };\n\n await expect(cache.searchByVector([0.1, 0.2], 5)).rejects.toThrow(\n \"Query vector dimension mismatch\"\n );\n expect(mockSearch).not.toHaveBeenCalled();\n });\n});\n",
415
+ "vector": [
416
+ 0.06989334523677826,
417
+ 0.09279084950685501,
418
+ -0.28722330927848816,
419
+ -0.1374661922454834,
420
+ 0.09219080954790115,
421
+ -0.1258455514907837,
422
+ -0.04659014195203781,
423
+ 0.05231737717986107,
424
+ -0.028522541746497154,
425
+ -0.13490243256092072,
426
+ -0.11689536273479462,
427
+ 0.06298447400331497,
428
+ 0.250491201877594,
429
+ 0.03496721014380455,
430
+ 0.018588755279779434,
431
+ 0.09076961874961853,
432
+ 0.030188193544745445,
433
+ -0.05926463007926941,
434
+ 0.05549225956201553,
435
+ -0.06513846665620804,
436
+ -0.09826518595218658,
437
+ -0.09547222405672073,
438
+ 0.04555385559797287,
439
+ -0.0010778267169371247,
440
+ 0.2011719048023224,
441
+ 0.13620075583457947,
442
+ -0.010945703834295273,
443
+ 0.13344159722328186,
444
+ -0.054980285465717316,
445
+ 0.032426055520772934,
446
+ 0.15621595084667206,
447
+ 0.10168932378292084,
448
+ -0.05191462114453316,
449
+ -0.04809003695845604,
450
+ -0.024015206843614578,
451
+ -0.09504210948944092,
452
+ -0.06087885797023773,
453
+ -0.03866283595561981,
454
+ -0.052629586309194565,
455
+ -0.046629585325717926,
456
+ -0.1002141684293747,
457
+ 0.029035190120339394,
458
+ 0.004597329534590244,
459
+ 0.01048421673476696,
460
+ 0.104668527841568,
461
+ -0.024357883259654045,
462
+ 0.11698635667562485,
463
+ 0.029642783105373383,
464
+ 0.20687130093574524,
465
+ -0.19501923024654388,
466
+ 0.09100848436355591,
467
+ 0.00698690814897418,
468
+ -0.08593756705522537,
469
+ -0.0208261888474226,
470
+ 0.25734955072402954,
471
+ 0.05553777143359184,
472
+ -0.09140697866678238,
473
+ -0.002971593290567398,
474
+ 0.12042880058288574,
475
+ -0.02970997802913189,
476
+ 0.11344171315431595,
477
+ 0.11852532625198364,
478
+ -0.0777057409286499,
479
+ 0.12308990210294724,
480
+ 0.046813156455755234,
481
+ -0.055255789309740067,
482
+ -0.13012193143367767,
483
+ 0.06383141875267029,
484
+ -0.04456283524632454,
485
+ 0.04641799256205559,
486
+ 0.053823381662368774,
487
+ 0.05450029671192169,
488
+ 0.03684500604867935,
489
+ -0.1309269368648529,
490
+ -0.15175741910934448,
491
+ 0.028335213661193848,
492
+ -0.08363565802574158,
493
+ -0.0016543444944545627,
494
+ -0.0721648782491684,
495
+ 0.07907940447330475,
496
+ -0.06126425787806511,
497
+ 0.03195405378937721,
498
+ 0.10843384265899658,
499
+ -0.07893526554107666,
500
+ 0.038608402013778687,
501
+ -0.05660504475235939,
502
+ -0.05545036494731903,
503
+ 0.029744740575551987,
504
+ -0.0835375040769577,
505
+ 0.10201199352741241,
506
+ -0.09528036415576935,
507
+ -0.03447628393769264,
508
+ -0.020436933264136314,
509
+ 0.05245654657483101,
510
+ -0.07587239891290665,
511
+ 0.08720170706510544,
512
+ -0.045813728123903275,
513
+ -0.007716943509876728,
514
+ -0.06787552684545517,
515
+ -0.08941919356584549,
516
+ -0.01960206776857376,
517
+ -0.04948434978723526,
518
+ -0.024665385484695435,
519
+ -0.05700599029660225,
520
+ 0.0015979859745129943,
521
+ 0.06902966648340225,
522
+ 0.05229188874363899,
523
+ 0.0948450043797493,
524
+ 0.017788780853152275,
525
+ -0.07872626185417175,
526
+ -0.023440023884177208,
527
+ 0.10071363300085068,
528
+ -0.07748767733573914,
529
+ -0.1524248868227005,
530
+ -0.012889477424323559,
531
+ -0.02570568397641182,
532
+ 0.07312727719545364,
533
+ 0.023128272965550423,
534
+ -0.061961110681295395,
535
+ 0.02999204955995083,
536
+ -0.05075949430465698,
537
+ -0.09555822610855103,
538
+ -0.04222562164068222,
539
+ 0.050797976553440094,
540
+ 0.10729549825191498,
541
+ 0.03013460896909237,
542
+ -0.03585230931639671,
543
+ 0.00017862312961369753
544
+ ]
545
+ },
546
+ {
547
+ "file": "/Users/jun/Library/Mobile Documents/iCloud~md~obsidian/Documents/new/700_projects/smart-coding-mcp/features/index-codebase.js",
548
+ "startLine": 1,
549
+ "endLine": 394,
550
+ "content": "import { fdir } from \"fdir\";\nimport fs from \"fs/promises\";\nimport chokidar from \"chokidar\";\nimport path from \"path\";\nimport os from \"os\";\nimport { Worker } from \"worker_threads\";\nimport { fileURLToPath } from \"url\";\nimport { smartChunk, hashContent } from \"../lib/utils.js\";\nimport { ResourceThrottle } from \"../lib/resource-throttle.js\";\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nasync function resolveCacheStats(cache) {\n if (typeof cache?.getStats === \"function\") {\n try {\n const stats = await cache.getStats();\n return {\n totalChunks: Number(stats?.totalChunks || 0),\n totalFiles: Number(stats?.totalFiles || 0)\n };\n } catch {\n // Fall back to legacy vectorStore contract below.\n }\n }\n\n const vectorStore = cache?.getVectorStore?.() || [];\n return {\n totalChunks: vectorStore.length,\n totalFiles: new Set(vectorStore.map((v) => v.file)).size\n };\n}\n\nexport class CodebaseIndexer {\n constructor(embedder, cache, config, server = null) {\n this.embedder = embedder;\n this.cache = cache;\n this.config = config;\n this.server = server;\n this.watcher = null;\n this.workers = [];\n this.workerReady = [];\n this.isIndexing = false;\n \n // Initialize resource throttling\n this.throttle = new ResourceThrottle(config);\n \n // Track indexing status for progressive search\n this.indexingStatus = {\n inProgress: false,\n totalFiles: 0,\n processedFiles: 0,\n percentage: 0\n };\n }\n\n /**\n * Initialize worker thread pool for parallel embedding\n * Note: Workers are disabled for nomic models due to ONNX runtime thread-safety issues\n */\n async initializeWorkers() {\n // Workers don't work with nomic/transformers.js due to ONNX WASM thread-safety issues\n const isNomicModel = this.config.embeddingModel?.includes('nomic');\n if (isNomicModel) {\n console.error(\"[Indexer] Single-threaded mode (nomic model - ONNX workers incompatible)\");\n return;\n }\n\n const provider = (this.config.embeddingProvider || 'local').toLowerCase();\n if (provider === 'gemini') {\n console.error(\"[Indexer] Single-threaded mode (gemini provider - API rate-limit safe mode)\");\n return;\n }\n\n // Check if workers are explicitly disabled\n if (this.config.workerThreads === 0 || this.config.disableWorkers) {\n console.error(\"[Indexer] Single-threaded mode (workers disabled by config)\");\n return;\n }\n\n const numWorkers = this.config.workerThreads === \"auto\"\n ? this.throttle.maxWorkers // Use throttled worker count\n : this.throttle.getWorkerCount(this.config.workerThreads);\n\n // Only use workers if we have more than 1 CPU\n if (numWorkers <= 1) {\n console.error(\"[Indexer] Single-threaded mode (1 CPU detected)\");\n return;\n }\n\n if (this.config.verbose) {\n console.error(`[Indexer] Worker config: workerThreads=${this.config.workerThreads}, resolved to ${numWorkers}`);\n }\n\n console.error(`[Indexer] Initializing ${numWorkers} worker threads...`);\n \n const workerPath = path.join(__dirname, \"../lib/embedding-worker.js\");\n \n for (let i = 0; i < numWorkers; i++) {\n try {\n const worker = new Worker(workerPath, {\n workerData: { \n embeddingProvider: this.config.embeddingProvider,\n embeddingModel: this.config.embeddingModel,\n embeddingDimension: this.config.embeddingDimension,\n geminiApiKey: this.config.geminiApiKey,\n geminiModel: this.config.geminiModel,\n geminiBaseURL: this.config.geminiBaseURL,\n geminiDimensions: this.config.geminiDimensions,\n geminiBatchSize: this.config.geminiBatchSize,\n geminiBatchFlushMs: this.config.geminiBatchFlushMs,\n geminiMaxRetries: this.config.geminiMaxRetries,\n verbose: this.config.verbose\n }\n });\n\n const readyPromise = new Promise((resolve, reject) => {\n const timeout = setTimeout(() => reject(new Error(\"Worker init timeout\")), 120000);\n \n worker.once(\"message\", (msg) => {\n clearTimeout(timeout);\n if (msg.type === \"ready\") {\n resolve(worker);\n } else if (msg.type === \"error\") {\n reject(new Error(msg.error));\n }\n });\n \n worker.once(\"error\", (err) => {\n clearTimeout(timeout);\n reject(err);\n });\n });\n\n this.workers.push(worker);\n this.workerReady.push(readyPromise);\n } catch (err) {\n console.error(`[Indexer] Failed to create worker ${i}: ${err.message}`);\n }\n }\n\n // Wait for all workers to be ready\n try {\n await Promise.all(this.workerReady);\n console.error(`[Indexer] ${this.workers.length} workers ready`);\n if (this.config.verbose) {\n console.error(`[Indexer] Each worker loaded model: ${this.config.embeddingModel}`);\n }\n } catch (err) {\n console.error(`[Indexer] Worker initialization failed: ${err.message}, falling back to single-threaded`);\n this.terminateWorkers();\n }\n }\n\n /**\n * Terminate all worker threads\n */\n terminateWorkers() {\n for (const worker of this.workers) {\n worker.postMessage({ type: \"shutdown\" });\n }\n this.workers = [];\n this.workerReady = [];\n }\n\n /**\n * Send MCP progress notification to connected clients\n */\n sendProgress(progress, total, message) {\n if (this.server) {\n try {\n this.server.sendNotification(\"notifications/progress\", {\n progressToken: \"indexing\",\n progress,\n total,\n message\n });\n } catch (err) {\n // Silently ignore if client doesn't support progress notifications\n }\n }\n }\n\n /**\n * Process chunks using worker thread pool with timeout and error recovery\n */\n async processChunksWithWorkers(allChunks) {\n if (this.workers.length === 0) {\n // Fallback to single-threaded processing\n return this.processChunksSingleThreaded(allChunks);\n }\n\n const results = [];\n const chunkSize = Math.ceil(allChunks.length / this.workers.length);\n const workerPromises = [];\n const WORKER_TIMEOUT = 300000; // 5 minutes per batch\n\n if (this.config.verbose) {\n console.error(`[Indexer] Distributing ${allChunks.length} chunks across ${this.workers.length} workers (~${chunkSize} chunks each)`);\n }\n\n for (let i = 0; i < this.workers.length; i++) {\n const workerChunks = allChunks.slice(i * chunkSize, (i + 1) * chunkSize);\n if (workerChunks.length === 0) continue;\n\n if (this.config.verbose) {\n console.error(`[Indexer] Worker ${i}: processing ${workerChunks.length} chunks`);\n }\n\n const promise = new Promise((resolve, reject) => {\n const worker = this.workers[i];\n const batchId = `batch-${i}-${Date.now()}`;\n \n // Timeout handler\n const timeout = setTimeout(() => {\n worker.off(\"message\", handler);\n console.error(`[Indexer] Worker ${i} timed out, falling back to single-threaded for this batch`);\n // Return empty and let fallback handle it\n resolve([]);\n }, WORKER_TIMEOUT);\n\n const handler = (msg) => {\n if (msg.batchId === batchId) {\n clearTimeout(timeout);\n worker.off(\"message\", handler);\n if (msg.type === \"results\") {\n resolve(msg.results);\n } else if (msg.type === \"error\") {\n console.error(`[Indexer] Worker ${i} error: ${msg.error}`);\n resolve([]); // Return empty, don't reject - let fallback handle\n }\n }\n };\n\n // Handle worker crash\n const errorHandler = (err) => {\n clearTimeout(timeout);\n worker.off(\"message\", handler);\n console.error(`[Indexer] Worker ${i} crashed: ${err.message}`);\n resolve([]); // Return empty, don't reject\n };\n worker.once(\"error\", errorHandler);\n\n worker.on(\"message\", handler);\n worker.postMessage({ type: \"process\", chunks: workerChunks, batchId });\n });\n\n workerPromises.push({ promise, chunks: workerChunks });\n }\n\n // Wait for all workers with error recovery\n const workerResults = await Promise.all(workerPromises.map(p => p.promise));\n \n // Collect results and identify failed chunks that need retry\n const failedChunks = [];\n for (let i = 0; i < workerResults.length; i++) {\n if (workerResults[i].length > 0) {\n results.push(...workerResults[i]);\n } else if (workerPromises[i].chunks.length > 0) {\n // Worker failed or timed out, need to retry these chunks\n failedChunks.push(...workerPromises[i].chunks);\n }\n }\n\n // Retry failed chunks with single-threaded fallback\n if (failedChunks.length > 0) {\n console.error(`[Indexer] Retrying ${failedChunks.length} chunks with single-threaded fallback...`);\n const retryResults = await this.processChunksSingleThreaded(failedChunks);\n results.push(...retryResults);\n }\n\n return results;\n }\n\n /**\n * Single-threaded chunk processing (fallback)\n */\n async processChunksSingleThreaded(chunks) {\n const results = [];\n \n for (const chunk of chunks) {\n try {\n const output = await this.embedder(chunk.text, { pooling: \"mean\", normalize: true });\n results.push({\n file: chunk.file,\n startLine: chunk.startLine,\n endLine: chunk.endLine,\n content: chunk.text,\n vector: Array.from(output.data),\n success: true\n });\n } catch (error) {\n results.push({\n file: chunk.file,\n startLine: chunk.startLine,\n endLine: chunk.endLine,\n error: error.message,\n success: false\n });\n }\n }\n\n return results;\n }\n\n async indexFile(file) {\n const fileName = path.basename(file);\n if (this.config.verbose) {\n console.error(`[Indexer] Processing: ${fileName}...`);\n }\n\n try {\n // Check file size first\n const stats = await fs.stat(file);\n\n // Skip directories\n if (stats.isDirectory()) {\n return 0;\n }\n\n if (stats.size > this.config.maxFileSize) {\n if (this.config.verbose) {\n console.error(`[Indexer] Skipped ${fileName} (too large: ${(stats.size / 1024 / 1024).toFixed(2)}MB)`);\n }\n return 0;\n }\n\n // OPTIMIZATION: Check mtime first (fast) before reading file content\n const currentMtime = stats.mtimeMs;\n const cachedMtime = this.cache.getFileMtime(file);\n\n // If mtime unchanged, file definitely unchanged - skip without reading\n if (cachedMtime && currentMtime === cachedMtime) {\n if (this.config.verbose) {\n console.error(`[Indexer] Skipped ${fileName} (unchanged - mtime)`);\n }\n return 0;\n }\n\n const content = await fs.readFile(file, \"utf-8\");\n const hash = hashContent(content);\n\n // Skip if file hasn't changed (content check after mtime indicated change)\n if (this.cache.getFileHash(file) === hash) {\n // Content same but mtime different - update cached mtime\n this.cache.setFileHash(file, hash, currentMtime);\n if (this.config.verbose) {\n console.error(`[Indexer] Skipped ${fileName} (unchanged - hash)`);\n }\n return 0;\n }\n\n if (this.config.verbose) {\n console.error(`[Indexer] Indexing ${fileName}...`);\n }\n \n // Remove old chunks for this file\n this.cache.removeFileFromStore(file);\n \n const chunks = smartChunk(content, file, this.config);\n let addedChunks = 0;\n\n for (const chunk of chunks) {\n try {\n const output = await this.embedder(chunk.text, { pooling: \"mean\", normalize: true });\n \n this.cache.addToStore({\n file,\n startLine: chunk.startLine,\n endLine: chunk.endLine,\n content: chunk.text,\n vector: Array.from(output.data)\n });\n addedChunks++;\n } catch (embeddingError) {\n console.error(`[Indexer] Failed to embed chunk in ${fileName}:`, embeddingError.message);\n }\n }\n\n this.cache.setFileHash(file, hash, currentMtime);\n if (this.config.verbose) {\n console.error(`[Indexer] Completed ${fileName} (${addedChunks} chunks)`);\n }\n return addedChunks;\n } catch (error) {\n console.error(`[Indexer] Error indexing ${fileName}:`, error.message);\n return 0;\n }\n }\n\n /**\n * Discover files using fdir (3-5x faster than glob)\n * Uses config.excludePatterns which includes smart patterns from ignore-patterns.js\n */\n async discoverFiles() {",
551
+ "vector": [
552
+ 0.1581457257270813,
553
+ 0.09730719774961472,
554
+ -0.3029850125312805,
555
+ -0.0920015200972557,
556
+ 0.1111610010266304,
557
+ -0.08129741996526718,
558
+ 0.07933555543422699,
559
+ 0.06567011773586273,
560
+ 0.05503755435347557,
561
+ -0.12007122486829758,
562
+ -0.09795062988996506,
563
+ 0.0036514585372060537,
564
+ 0.21044915914535522,
565
+ 0.13158400356769562,
566
+ 0.13338004052639008,
567
+ -0.028420306742191315,
568
+ -0.028399521484971046,
569
+ 0.006674378179013729,
570
+ 0.10423435270786285,
571
+ 0.02588612213730812,
572
+ 0.08754349499940872,
573
+ -0.1967044621706009,
574
+ 0.00023717241128906608,
575
+ -0.006087819114327431,
576
+ 0.10185301303863525,
577
+ 0.08951200544834137,
578
+ -0.048756107687950134,
579
+ 0.008991514332592487,
580
+ -0.0750628411769867,
581
+ 0.01490585133433342,
582
+ -0.02149953506886959,
583
+ 0.0015851608477532864,
584
+ 0.0031420381274074316,
585
+ -0.01975736953318119,
586
+ -0.09090711176395416,
587
+ -0.05832710117101669,
588
+ 0.019585730507969856,
589
+ -0.06903216987848282,
590
+ 0.026971740648150444,
591
+ 0.021378114819526672,
592
+ 0.013534357771277428,
593
+ -0.009621960110962391,
594
+ 0.020762592554092407,
595
+ -0.09896891564130783,
596
+ 0.05619579926133156,
597
+ 0.0009851728100329638,
598
+ 0.06719415634870529,
599
+ 0.120758056640625,
600
+ -0.034059587866067886,
601
+ -0.07381908595561981,
602
+ 0.10196612775325775,
603
+ -0.059638962149620056,
604
+ -0.06823472678661346,
605
+ 0.009703384712338448,
606
+ 0.11015640199184418,
607
+ 0.09928888827562332,
608
+ -0.04564967751502991,
609
+ 0.032569508999586105,
610
+ 0.12816089391708374,
611
+ 0.07059252262115479,
612
+ 0.11648830026388168,
613
+ 0.03688926249742508,
614
+ -0.03861513361334801,
615
+ 0.07646742463111877,
616
+ 0.09887293726205826,
617
+ -0.05591810867190361,
618
+ 0.011368947103619576,
619
+ 0.16489580273628235,
620
+ 0.035311684012413025,
621
+ 0.07226822525262833,
622
+ 0.11262308806180954,
623
+ -0.02197161875665188,
624
+ 0.026616549119353294,
625
+ 0.0912456214427948,
626
+ -0.05040040984749794,
627
+ 0.17206838726997375,
628
+ -0.001167329610325396,
629
+ -0.14145590364933014,
630
+ -0.03256872668862343,
631
+ 0.0020463985856622458,
632
+ -0.04155460372567177,
633
+ -0.10168083757162094,
634
+ 0.04580698534846306,
635
+ -0.02633069083094597,
636
+ 0.01709134690463543,
637
+ -0.12554492056369781,
638
+ -0.055298492312431335,
639
+ -0.14122527837753296,
640
+ -0.05947331339120865,
641
+ 0.1057809591293335,
642
+ 0.1283518373966217,
643
+ -0.04555771127343178,
644
+ 0.04975105822086334,
645
+ 0.07905534654855728,
646
+ -0.07113630324602127,
647
+ -0.05268482491374016,
648
+ -0.03614216297864914,
649
+ -0.0028994218446314335,
650
+ -0.14961880445480347,
651
+ -0.0071167717687785625,
652
+ -0.014561337418854237,
653
+ -0.08701707422733307,
654
+ 0.12362214177846909,
655
+ -0.1566791981458664,
656
+ 0.16238804161548615,
657
+ 0.1335301548242569,
658
+ 0.037199944257736206,
659
+ -0.0007604017155244946,
660
+ 0.11358943581581116,
661
+ 0.039150938391685486,
662
+ -0.051211610436439514,
663
+ 0.07153531163930893,
664
+ -0.07599829882383347,
665
+ -0.17895187437534332,
666
+ -0.0277266763150692,
667
+ -0.03506512567400932,
668
+ 0.15224745869636536,
669
+ -0.09044156968593597,
670
+ -0.03551291674375534,
671
+ 0.05021766200661659,
672
+ -0.03844892978668213,
673
+ -0.2054094523191452,
674
+ -0.04287634417414665,
675
+ 0.05382128804922104,
676
+ 0.06213771179318428,
677
+ 0.0630461573600769,
678
+ -0.015376919880509377,
679
+ -0.028032727539539337
680
+ ]
681
+ },
682
+ {
683
+ "file": "/Users/jun/Library/Mobile Documents/iCloud~md~obsidian/Documents/new/700_projects/smart-coding-mcp/features/index-codebase.js",
684
+ "startLine": 267,
685
+ "endLine": 665,
686
+ "content": " const retryResults = await this.processChunksSingleThreaded(failedChunks);\n results.push(...retryResults);\n }\n\n return results;\n }\n\n /**\n * Single-threaded chunk processing (fallback)\n */\n async processChunksSingleThreaded(chunks) {\n const results = [];\n \n for (const chunk of chunks) {\n try {\n const output = await this.embedder(chunk.text, { pooling: \"mean\", normalize: true });\n results.push({\n file: chunk.file,\n startLine: chunk.startLine,\n endLine: chunk.endLine,\n content: chunk.text,\n vector: Array.from(output.data),\n success: true\n });\n } catch (error) {\n results.push({\n file: chunk.file,\n startLine: chunk.startLine,\n endLine: chunk.endLine,\n error: error.message,\n success: false\n });\n }\n }\n\n return results;\n }\n\n async indexFile(file) {\n const fileName = path.basename(file);\n if (this.config.verbose) {\n console.error(`[Indexer] Processing: ${fileName}...`);\n }\n\n try {\n // Check file size first\n const stats = await fs.stat(file);\n\n // Skip directories\n if (stats.isDirectory()) {\n return 0;\n }\n\n if (stats.size > this.config.maxFileSize) {\n if (this.config.verbose) {\n console.error(`[Indexer] Skipped ${fileName} (too large: ${(stats.size / 1024 / 1024).toFixed(2)}MB)`);\n }\n return 0;\n }\n\n // OPTIMIZATION: Check mtime first (fast) before reading file content\n const currentMtime = stats.mtimeMs;\n const cachedMtime = this.cache.getFileMtime(file);\n\n // If mtime unchanged, file definitely unchanged - skip without reading\n if (cachedMtime && currentMtime === cachedMtime) {\n if (this.config.verbose) {\n console.error(`[Indexer] Skipped ${fileName} (unchanged - mtime)`);\n }\n return 0;\n }\n\n const content = await fs.readFile(file, \"utf-8\");\n const hash = hashContent(content);\n\n // Skip if file hasn't changed (content check after mtime indicated change)\n if (this.cache.getFileHash(file) === hash) {\n // Content same but mtime different - update cached mtime\n this.cache.setFileHash(file, hash, currentMtime);\n if (this.config.verbose) {\n console.error(`[Indexer] Skipped ${fileName} (unchanged - hash)`);\n }\n return 0;\n }\n\n if (this.config.verbose) {\n console.error(`[Indexer] Indexing ${fileName}...`);\n }\n \n // Remove old chunks for this file\n this.cache.removeFileFromStore(file);\n \n const chunks = smartChunk(content, file, this.config);\n let addedChunks = 0;\n\n for (const chunk of chunks) {\n try {\n const output = await this.embedder(chunk.text, { pooling: \"mean\", normalize: true });\n \n this.cache.addToStore({\n file,\n startLine: chunk.startLine,\n endLine: chunk.endLine,\n content: chunk.text,\n vector: Array.from(output.data)\n });\n addedChunks++;\n } catch (embeddingError) {\n console.error(`[Indexer] Failed to embed chunk in ${fileName}:`, embeddingError.message);\n }\n }\n\n this.cache.setFileHash(file, hash, currentMtime);\n if (this.config.verbose) {\n console.error(`[Indexer] Completed ${fileName} (${addedChunks} chunks)`);\n }\n return addedChunks;\n } catch (error) {\n console.error(`[Indexer] Error indexing ${fileName}:`, error.message);\n return 0;\n }\n }\n\n /**\n * Discover files using fdir (3-5x faster than glob)\n * Uses config.excludePatterns which includes smart patterns from ignore-patterns.js\n */\n async discoverFiles() {\n const startTime = Date.now();\n \n // Build extension filter from config\n const extensions = new Set(this.config.fileExtensions.map(ext => `.${ext}`));\n \n // Extract directory names from glob patterns in config.excludePatterns\n // Patterns like \"**/node_modules/**\" -> \"node_modules\"\n const excludeDirs = new Set();\n for (const pattern of this.config.excludePatterns) {\n // Extract directory names from glob patterns\n const match = pattern.match(/\\*\\*\\/([^/*]+)\\/?\\*?\\*?$/);\n if (match) {\n excludeDirs.add(match[1]);\n }\n // Also handle patterns like \"**/dirname/**\"\n const match2 = pattern.match(/\\*\\*\\/([^/*]+)\\/\\*\\*$/);\n if (match2) {\n excludeDirs.add(match2[1]);\n }\n }\n \n // Always exclude cache directory\n excludeDirs.add(\".smart-coding-cache\");\n \n if (this.config.verbose) {\n console.error(`[Indexer] Using ${excludeDirs.size} exclude directories from config`);\n }\n\n const api = new fdir()\n .withFullPaths()\n .exclude((dirName) => excludeDirs.has(dirName))\n .filter((filePath) => extensions.has(path.extname(filePath)))\n .crawl(this.config.searchDirectory);\n\n const files = await api.withPromise();\n \n console.error(`[Indexer] File discovery: ${files.length} files in ${Date.now() - startTime}ms`);\n return files;\n }\n\n /**\n * Sort files by priority for progressive indexing\n * Priority: recently modified files first (users likely searching for recent work)\n */\n async sortFilesByPriority(files) {\n const startTime = Date.now();\n\n // Get mtime for all files in parallel\n const filesWithMtime = await Promise.all(\n files.map(async (file) => {\n try {\n const stats = await fs.stat(file);\n return { file, mtime: stats.mtimeMs };\n } catch {\n return { file, mtime: 0 };\n }\n })\n );\n\n // Sort by mtime descending (most recently modified first)\n filesWithMtime.sort((a, b) => b.mtime - a.mtime);\n\n if (this.config.verbose) {\n console.error(`[Indexer] Priority sort: ${files.length} files in ${Date.now() - startTime}ms`);\n }\n\n return filesWithMtime.map(f => f.file);\n }\n\n /**\n * Start background indexing (non-blocking)\n * Allows search to work immediately with partial results\n */\n startBackgroundIndexing(force = false) {\n if (this.isIndexing) {\n console.error(\"[Indexer] Background indexing already in progress\");\n return;\n }\n\n console.error(\"[Indexer] Starting background indexing...\");\n\n // Run indexAll in background (don't await)\n this.indexAll(force).then(result => {\n console.error(`[Indexer] Background indexing complete: ${result.message || 'done'}`);\n }).catch(err => {\n console.error(`[Indexer] Background indexing error: ${err.message}`);\n });\n }\n\n /**\n * Get current indexing status for progressive search\n */\n getIndexingStatus() {\n return {\n ...this.indexingStatus,\n isReady: !this.indexingStatus.inProgress || this.indexingStatus.processedFiles > 0\n };\n }\n\n /**\n * Pre-filter files by hash (skip unchanged files before processing)\n */\n async preFilterFiles(files) {\n const startTime = Date.now();\n const filesToProcess = [];\n const skippedCount = { unchanged: 0, tooLarge: 0, error: 0 };\n\n // Process in parallel batches for speed\n const BATCH_SIZE = 500;\n \n for (let i = 0; i < files.length; i += BATCH_SIZE) {\n const batch = files.slice(i, i + BATCH_SIZE);\n \n const results = await Promise.all(\n batch.map(async (file) => {\n try {\n const stats = await fs.stat(file);\n \n if (stats.isDirectory()) {\n return null;\n }\n \n if (stats.size > this.config.maxFileSize) {\n skippedCount.tooLarge++;\n return null;\n }\n \n const content = await fs.readFile(file, \"utf-8\");\n const hash = hashContent(content);\n \n if (this.cache.getFileHash(file) === hash) {\n skippedCount.unchanged++;\n return null;\n }\n \n return { file, content, hash };\n } catch (error) {\n skippedCount.error++;\n return null;\n }\n })\n );\n\n for (const result of results) {\n if (result) filesToProcess.push(result);\n }\n }\n\n console.error(`[Indexer] Pre-filter: ${filesToProcess.length} changed, ${skippedCount.unchanged} unchanged, ${skippedCount.tooLarge} too large, ${skippedCount.error} errors (${Date.now() - startTime}ms)`);\n return filesToProcess;\n }\n\n async indexAll(force = false) {\n if (this.isIndexing) {\n console.error(\"[Indexer] Indexing already in progress, skipping concurrent request\");\n return { skipped: true, reason: \"Indexing already in progress\" };\n }\n\n this.isIndexing = true;\n\n // Initialize indexing status for progressive search\n this.indexingStatus = {\n inProgress: true,\n totalFiles: 0,\n processedFiles: 0,\n percentage: 0\n };\n\n // Declare counters outside try block so they're accessible in finally\n let processedFiles = 0;\n let skippedFiles = 0;\n\n try {\n if (force) {\n console.error(\"[Indexer] Force reindex requested: clearing cache\");\n if (typeof this.cache.resetForFullReindex === \"function\") {\n await this.cache.resetForFullReindex();\n } else {\n this.cache.setVectorStore([]);\n this.cache.clearAllFileHashes();\n }\n }\n\n const totalStartTime = Date.now();\n console.error(`[Indexer] Starting optimized indexing in ${this.config.searchDirectory}...`);\n \n // Step 1: Fast file discovery with fdir\n let files = await this.discoverFiles();\n\n if (files.length === 0) {\n console.error(\"[Indexer] No files found to index\");\n this.sendProgress(100, 100, \"No files found to index\");\n return { skipped: false, filesProcessed: 0, chunksCreated: 0, message: \"No files found to index\" };\n }\n\n // Step 1.1: Sort files by priority (recently modified first) for progressive indexing\n // This ensures search results are useful even while indexing is in progress\n files = await this.sortFilesByPriority(files);\n console.error(`[Indexer] Progressive mode: recently modified files will be indexed first`);\n\n // Send progress: discovery complete\n this.sendProgress(5, 100, `Discovered ${files.length} files (sorted by priority)`);\n\n // Step 1.5: Prune deleted or excluded files from cache\n if (!force) {\n const currentFilesSet = new Set(files);\n const cachedFiles = Array.from(this.cache.getAllFileHashes().keys());\n let prunedCount = 0;\n\n for (const cachedFile of cachedFiles) {\n if (!currentFilesSet.has(cachedFile)) {\n this.cache.removeFileFromStore(cachedFile);\n this.cache.deleteFileHash(cachedFile);\n prunedCount++;\n }\n }\n \n if (prunedCount > 0) {\n if (this.config.verbose) {\n console.error(`[Indexer] Pruned ${prunedCount} deleted/excluded files from index`);\n }\n // If we pruned files, we should save these changes even if no other files changed\n }\n }\n\n // Step 2: Process files with progressive indexing\n // Use batch size of 1 for immediate search availability (progressive indexing)\n // Each file is processed, embedded, and saved immediately so search can find it\n const adaptiveBatchSize = this.config.progressiveIndexing !== false ? 1 :\n files.length > 10000 ? 500 :\n files.length > 1000 ? 200 :\n this.config.batchSize || 100;\n\n console.error(`[Indexer] Processing ${files.length} files (progressive mode: batch size ${adaptiveBatchSize})`);\n\n // Step 3: Initialize worker threads (always use when multi-core available)\n const useWorkers = os.cpus().length > 1;\n \n if (useWorkers) {\n await this.initializeWorkers();\n console.error(`[Indexer] Multi-threaded mode: ${this.workers.length} workers active`);\n } else {\n console.error(`[Indexer] Single-threaded mode (single-core system)`);\n }\n\n let totalChunks = 0;\n let batchCounter = 0; // Track batches for incremental saves\n \n // Update total file count for status tracking (estimated, will adjust as we filter)\n this.indexingStatus.totalFiles = files.length;\n\n // Step 4: Process files in adaptive batches with inline lazy filtering\n for (let i = 0; i < files.length; i += adaptiveBatchSize) {\n const batch = files.slice(i, i + adaptiveBatchSize);\n \n // Lazy filter and generate chunks for this batch\n const allChunks = [];\n const fileHashes = new Map();\n \n for (const file of batch) {\n try {\n const stats = await fs.stat(file);\n\n // Skip directories and oversized files\n if (stats.isDirectory()) continue;\n if (stats.size > this.config.maxFileSize) {\n skippedFiles++;\n continue;\n }\n\n // OPTIMIZATION: Check mtime first (fast) before reading file content",
687
+ "vector": [
688
+ 0.2004900723695755,
689
+ 0.08742590993642807,
690
+ -0.31946516036987305,
691
+ -0.07496917992830276,
692
+ 0.08812963962554932,
693
+ -0.049481626600027084,
694
+ 0.07166194915771484,
695
+ 0.0778622254729271,
696
+ 0.025894882157444954,
697
+ -0.13997361063957214,
698
+ -0.06350693106651306,
699
+ -0.012218390591442585,
700
+ 0.24647164344787598,
701
+ 0.15105034410953522,
702
+ 0.09983714669942856,
703
+ -0.05895882844924927,
704
+ -0.04254899546504021,
705
+ -0.0047841197811067104,
706
+ 0.07406415045261383,
707
+ 0.03338836878538132,
708
+ 0.08037767559289932,
709
+ -0.16201448440551758,
710
+ -0.024916179478168488,
711
+ -0.021709877997636795,
712
+ 0.06069096177816391,
713
+ 0.1334153115749359,
714
+ -0.07323963195085526,
715
+ 0.01629321277141571,
716
+ -0.1441895216703415,
717
+ 0.07248875498771667,
718
+ -0.003559352597221732,
719
+ -0.030797896906733513,
720
+ -0.057104796171188354,
721
+ -0.01649126224219799,
722
+ -0.08484768122434616,
723
+ -0.05955332890152931,
724
+ 0.08874189108610153,
725
+ -0.07790060341358185,
726
+ 0.025552013888955116,
727
+ -0.012111528776586056,
728
+ 0.02903941087424755,
729
+ 0.028872443363070488,
730
+ -0.00256912549957633,
731
+ -0.10991302877664566,
732
+ 0.04715258628129959,
733
+ 0.001459878054447472,
734
+ 0.07626042515039444,
735
+ 0.08310190588235855,
736
+ 0.016730209812521935,
737
+ -0.14389929175376892,
738
+ 0.07123272120952606,
739
+ 0.01354307122528553,
740
+ -0.05582740902900696,
741
+ -0.016913248226046562,
742
+ 0.11500194668769836,
743
+ 0.12534722685813904,
744
+ 0.02746613882482052,
745
+ 0.04026485234498978,
746
+ 0.1467626988887787,
747
+ 0.034109488129615784,
748
+ 0.15540309250354767,
749
+ 0.004103995859622955,
750
+ -0.04238760098814964,
751
+ 0.09997699409723282,
752
+ 0.09902447462081909,
753
+ -0.04401959851384163,
754
+ -0.015907008200883865,
755
+ 0.15126250684261322,
756
+ 0.01491245161741972,
757
+ 0.05753715708851814,
758
+ 0.09574049711227417,
759
+ 0.014417252503335476,
760
+ 0.03316980227828026,
761
+ 0.11039614677429199,
762
+ -0.07818535715341568,
763
+ 0.1355142891407013,
764
+ -0.036420758813619614,
765
+ -0.12393581122159958,
766
+ -0.05708072707056999,
767
+ 0.007042900193482637,
768
+ -0.014832097105681896,
769
+ -0.10861879587173462,
770
+ 0.07373612374067307,
771
+ -0.010846639052033424,
772
+ 0.004877697676420212,
773
+ -0.13283340632915497,
774
+ -0.08903644233942032,
775
+ -0.11092044413089752,
776
+ -0.013484036549925804,
777
+ 0.10321880877017975,
778
+ 0.08449871838092804,
779
+ -0.008889189921319485,
780
+ 0.06933161616325378,
781
+ 0.11338340491056442,
782
+ -0.1126001626253128,
783
+ -0.025866171345114708,
784
+ -0.03898569941520691,
785
+ 0.00857498962432146,
786
+ -0.17260225117206573,
787
+ -0.023072805255651474,
788
+ -0.022961756214499474,
789
+ -0.0680951401591301,
790
+ 0.11823565512895584,
791
+ -0.08444441854953766,
792
+ 0.1431797593832016,
793
+ 0.10837671905755997,
794
+ 0.03677082806825638,
795
+ -0.000356168020516634,
796
+ 0.06445174664258957,
797
+ 0.02095799706876278,
798
+ -0.04063499718904495,
799
+ 0.06420458108186722,
800
+ -0.07078398019075394,
801
+ -0.1807531714439392,
802
+ -0.07313914597034454,
803
+ -0.027090609073638916,
804
+ 0.16154420375823975,
805
+ -0.08997083455324173,
806
+ -0.02687673829495907,
807
+ 0.020467497408390045,
808
+ -0.03619936481118202,
809
+ -0.15813469886779785,
810
+ -0.006644389126449823,
811
+ 0.01596990041434765,
812
+ 0.05005795136094093,
813
+ 0.06600048393011093,
814
+ -0.023801350966095924,
815
+ -0.05026095733046532
816
+ ]
817
+ },
818
+ {
819
+ "file": "/Users/jun/Library/Mobile Documents/iCloud~md~obsidian/Documents/new/700_projects/smart-coding-mcp/features/index-codebase.js",
820
+ "startLine": 565,
821
+ "endLine": 916,
822
+ "content": " let skippedFiles = 0;\n\n try {\n if (force) {\n console.error(\"[Indexer] Force reindex requested: clearing cache\");\n if (typeof this.cache.resetForFullReindex === \"function\") {\n await this.cache.resetForFullReindex();\n } else {\n this.cache.setVectorStore([]);\n this.cache.clearAllFileHashes();\n }\n }\n\n const totalStartTime = Date.now();\n console.error(`[Indexer] Starting optimized indexing in ${this.config.searchDirectory}...`);\n \n // Step 1: Fast file discovery with fdir\n let files = await this.discoverFiles();\n\n if (files.length === 0) {\n console.error(\"[Indexer] No files found to index\");\n this.sendProgress(100, 100, \"No files found to index\");\n return { skipped: false, filesProcessed: 0, chunksCreated: 0, message: \"No files found to index\" };\n }\n\n // Step 1.1: Sort files by priority (recently modified first) for progressive indexing\n // This ensures search results are useful even while indexing is in progress\n files = await this.sortFilesByPriority(files);\n console.error(`[Indexer] Progressive mode: recently modified files will be indexed first`);\n\n // Send progress: discovery complete\n this.sendProgress(5, 100, `Discovered ${files.length} files (sorted by priority)`);\n\n // Step 1.5: Prune deleted or excluded files from cache\n if (!force) {\n const currentFilesSet = new Set(files);\n const cachedFiles = Array.from(this.cache.getAllFileHashes().keys());\n let prunedCount = 0;\n\n for (const cachedFile of cachedFiles) {\n if (!currentFilesSet.has(cachedFile)) {\n this.cache.removeFileFromStore(cachedFile);\n this.cache.deleteFileHash(cachedFile);\n prunedCount++;\n }\n }\n \n if (prunedCount > 0) {\n if (this.config.verbose) {\n console.error(`[Indexer] Pruned ${prunedCount} deleted/excluded files from index`);\n }\n // If we pruned files, we should save these changes even if no other files changed\n }\n }\n\n // Step 2: Process files with progressive indexing\n // Use batch size of 1 for immediate search availability (progressive indexing)\n // Each file is processed, embedded, and saved immediately so search can find it\n const adaptiveBatchSize = this.config.progressiveIndexing !== false ? 1 :\n files.length > 10000 ? 500 :\n files.length > 1000 ? 200 :\n this.config.batchSize || 100;\n\n console.error(`[Indexer] Processing ${files.length} files (progressive mode: batch size ${adaptiveBatchSize})`);\n\n // Step 3: Initialize worker threads (always use when multi-core available)\n const useWorkers = os.cpus().length > 1;\n \n if (useWorkers) {\n await this.initializeWorkers();\n console.error(`[Indexer] Multi-threaded mode: ${this.workers.length} workers active`);\n } else {\n console.error(`[Indexer] Single-threaded mode (single-core system)`);\n }\n\n let totalChunks = 0;\n let batchCounter = 0; // Track batches for incremental saves\n \n // Update total file count for status tracking (estimated, will adjust as we filter)\n this.indexingStatus.totalFiles = files.length;\n\n // Step 4: Process files in adaptive batches with inline lazy filtering\n for (let i = 0; i < files.length; i += adaptiveBatchSize) {\n const batch = files.slice(i, i + adaptiveBatchSize);\n \n // Lazy filter and generate chunks for this batch\n const allChunks = [];\n const fileHashes = new Map();\n \n for (const file of batch) {\n try {\n const stats = await fs.stat(file);\n\n // Skip directories and oversized files\n if (stats.isDirectory()) continue;\n if (stats.size > this.config.maxFileSize) {\n skippedFiles++;\n continue;\n }\n\n // OPTIMIZATION: Check mtime first (fast) before reading file content\n const currentMtime = stats.mtimeMs;\n const cachedMtime = this.cache.getFileMtime(file);\n\n // If mtime unchanged, file definitely unchanged - skip without reading\n if (cachedMtime && currentMtime === cachedMtime) {\n skippedFiles++;\n continue;\n }\n\n // mtime changed (or new file) - read content and verify with hash\n const content = await fs.readFile(file, \"utf-8\");\n const hash = hashContent(content);\n\n // Check if content actually changed (mtime can change without content change)\n if (this.cache.getFileHash(file) === hash) {\n // Content same but mtime different - update cached mtime\n this.cache.setFileHash(file, hash, currentMtime);\n skippedFiles++;\n continue;\n }\n \n // File changed - remove old chunks and prepare new ones\n this.cache.removeFileFromStore(file);\n const chunks = smartChunk(content, file, this.config);\n \n for (const chunk of chunks) {\n allChunks.push({\n file,\n text: chunk.text,\n startLine: chunk.startLine,\n endLine: chunk.endLine,\n hash,\n mtime: currentMtime\n });\n }\n\n fileHashes.set(file, { hash, mtime: currentMtime });\n } catch (error) {\n // Skip files with read errors\n skippedFiles++;\n if (this.config.verbose) {\n console.error(`[Indexer] Error reading ${path.basename(file)}: ${error.message}`);\n }\n }\n }\n \n // Skip this batch if no chunks to process\n if (allChunks.length === 0) {\n continue;\n }\n\n // Process chunks (with workers if available, otherwise single-threaded)\n let results;\n if (useWorkers && this.workers.length > 0) {\n results = await this.processChunksWithWorkers(allChunks);\n } else {\n results = await this.processChunksSingleThreaded(allChunks);\n }\n\n // Collect successful results for batch insert\n const chunksToInsert = [];\n const filesProcessedInBatch = new Set();\n \n for (const result of results) {\n if (result.success) {\n chunksToInsert.push({\n file: result.file,\n startLine: result.startLine,\n endLine: result.endLine,\n content: result.content,\n vector: result.vector\n });\n totalChunks++;\n filesProcessedInBatch.add(result.file);\n }\n }\n \n // Batch insert to SQLite (much faster than individual inserts)\n if (chunksToInsert.length > 0 && typeof this.cache.addBatchToStore === 'function') {\n this.cache.addBatchToStore(chunksToInsert);\n } else {\n // Fallback for old cache implementation\n for (const chunk of chunksToInsert) {\n this.cache.addToStore(chunk);\n }\n }\n\n // Update file hashes with mtime\n for (const [file, { hash, mtime }] of fileHashes) {\n this.cache.setFileHash(file, hash, mtime);\n }\n\n processedFiles += filesProcessedInBatch.size;\n batchCounter++;\n \n // Update indexing status for progressive search\n const estimatedTotal = files.length - skippedFiles;\n this.indexingStatus.processedFiles = processedFiles;\n this.indexingStatus.totalFiles = Math.max(estimatedTotal, processedFiles);\n this.indexingStatus.percentage = estimatedTotal > 0 ? Math.floor((processedFiles / estimatedTotal) * 100) : 100;\n\n // Progressive indexing: save after EVERY batch so search can find new results immediately\n // This is critical for background indexing - users can search while indexing continues\n if (chunksToInsert.length > 0) {\n if (typeof this.cache.saveIncremental === 'function') {\n await this.cache.saveIncremental();\n } else {\n // Fallback: full save (slower but ensures data is persisted)\n await this.cache.save();\n }\n }\n\n // Apply CPU throttling (delay between batches)\n await this.throttle.throttledBatch(null);\n\n // Progress indicator - show progress after each file in progressive mode\n const progressInterval = adaptiveBatchSize === 1 ? 1 : adaptiveBatchSize * 2;\n if (processedFiles > 0 && ((processedFiles + skippedFiles) % progressInterval === 0 || i + adaptiveBatchSize >= files.length)) {\n const elapsed = ((Date.now() - totalStartTime) / 1000).toFixed(1);\n const totalProcessed = processedFiles + skippedFiles;\n const rate = totalProcessed > 0 ? (totalProcessed / parseFloat(elapsed)).toFixed(1) : '0';\n console.error(`[Indexer] Progress: ${processedFiles} indexed, ${skippedFiles} skipped of ${files.length} (${rate} files/sec)`);\n\n // Send MCP progress notification (10-95% range for batch processing)\n const progressPercent = Math.min(95, Math.floor(10 + (totalProcessed / files.length) * 85));\n this.sendProgress(progressPercent, 100, `Indexed ${processedFiles} files, ${skippedFiles} skipped (${rate}/sec)`);\n }\n }\n\n // Cleanup workers\n if (useWorkers) {\n this.terminateWorkers();\n }\n\n const totalTime = ((Date.now() - totalStartTime) / 1000).toFixed(1);\n const changedFiles = processedFiles;\n console.error(`[Indexer] Complete: ${totalChunks} chunks from ${changedFiles} changed files (${skippedFiles} unchanged) in ${totalTime}s`);\n \n // Mark indexing as complete\n this.indexingStatus.inProgress = false;\n this.indexingStatus.percentage = 100;\n \n // Send completion progress\n const summaryMsg = changedFiles > 0 \n ? `Complete: ${totalChunks} chunks from ${changedFiles} changed files (${skippedFiles} unchanged) in ${totalTime}s`\n : `Complete: No files changed (${skippedFiles} files up to date)`;\n this.sendProgress(100, 100, summaryMsg);\n \n await this.cache.save();\n\n const stats = await resolveCacheStats(this.cache);\n const resolvedTotalChunks =\n stats.totalChunks === 0 && totalChunks > 0 ? totalChunks : stats.totalChunks;\n const resolvedTotalFiles =\n stats.totalFiles === 0 && changedFiles > 0 ? changedFiles : stats.totalFiles;\n return {\n skipped: false,\n filesProcessed: changedFiles,\n chunksCreated: totalChunks,\n totalFiles: resolvedTotalFiles,\n totalChunks: resolvedTotalChunks,\n duration: totalTime,\n message: changedFiles > 0 \n ? `Indexed ${changedFiles} files (${totalChunks} chunks, ${skippedFiles} unchanged) in ${totalTime}s`\n : `All ${skippedFiles} files up to date`\n };\n } finally {\n this.isIndexing = false;\n // Adjust estimated total after completion\n this.indexingStatus.totalFiles = processedFiles + skippedFiles;\n }\n }\n\n setupFileWatcher() {\n if (!this.config.watchFiles) return;\n\n const pattern = this.config.fileExtensions.map(ext => `**/*.${ext}`);\n \n this.watcher = chokidar.watch(pattern, {\n cwd: this.config.searchDirectory,\n ignored: this.config.excludePatterns,\n persistent: true,\n ignoreInitial: true\n });\n\n this.watcher\n .on(\"add\", async (filePath) => {\n const fullPath = path.join(this.config.searchDirectory, filePath);\n console.error(`[Indexer] New file detected: ${filePath}`);\n await this.indexFile(fullPath);\n await this.cache.save();\n })\n .on(\"change\", async (filePath) => {\n const fullPath = path.join(this.config.searchDirectory, filePath);\n console.error(`[Indexer] File changed: ${filePath}`);\n await this.indexFile(fullPath);\n await this.cache.save();\n })\n .on(\"unlink\", (filePath) => {\n const fullPath = path.join(this.config.searchDirectory, filePath);\n console.error(`[Indexer] File deleted: ${filePath}`);\n this.cache.removeFileFromStore(fullPath);\n this.cache.deleteFileHash(fullPath);\n this.cache.save();\n });\n\n console.error(\"[Indexer] File watcher enabled for incremental indexing\");\n }\n}\n\n// MCP Tool definition for this feature\nexport function getToolDefinition() {\n return {\n name: \"b_index_codebase\",\n description: \"Manually trigger a full reindex of the codebase. This will scan all files and update the embeddings cache. Useful after large code changes or if the index seems out of date.\",\n inputSchema: {\n type: \"object\",\n properties: {\n force: {\n type: \"boolean\",\n description: \"Force reindex even if files haven't changed\",\n default: false\n }\n }\n },\n annotations: {\n title: \"Reindex Codebase\",\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false\n }\n };\n}\n\n// Tool handler\nexport async function handleToolCall(request, indexer) {\n const force = request.params.arguments?.force || false;\n const result = await indexer.indexAll(force);\n \n // Handle case when indexing was skipped due to concurrent request\n if (result?.skipped) {\n return {\n content: [{\n type: \"text\",\n text: `Indexing skipped: ${result.reason}\\n\\nPlease wait for the current indexing operation to complete before requesting another reindex.`\n }]\n };\n }\n \n // Get current stats from cache",
823
+ "vector": [
824
+ 0.21296283602714539,
825
+ 0.06970507651567459,
826
+ -0.27411937713623047,
827
+ -0.07206238806247711,
828
+ 0.1048586368560791,
829
+ -0.09113089740276337,
830
+ 0.05917135626077652,
831
+ 0.14159263670444489,
832
+ 0.04946787282824516,
833
+ -0.14154276251792908,
834
+ -0.11192521452903748,
835
+ -0.0012752795591950417,
836
+ 0.2390749752521515,
837
+ 0.14436542987823486,
838
+ 0.10990599542856216,
839
+ -0.06353151798248291,
840
+ -0.02995608188211918,
841
+ -0.0035002122167497873,
842
+ 0.08589986711740494,
843
+ 0.045336414128541946,
844
+ 0.0832846537232399,
845
+ -0.16696211695671082,
846
+ -0.04497470334172249,
847
+ -0.021228235214948654,
848
+ 0.02615312486886978,
849
+ 0.1139010414481163,
850
+ -0.07230430841445923,
851
+ 0.05724888667464256,
852
+ -0.1112324520945549,
853
+ 0.06635451316833496,
854
+ -0.016103165224194527,
855
+ -0.01146382000297308,
856
+ -0.037814732640981674,
857
+ 0.012660345062613487,
858
+ -0.09729259461164474,
859
+ -0.07384864240884781,
860
+ 0.0783107802271843,
861
+ -0.10646484792232513,
862
+ 0.031788818538188934,
863
+ 0.014896051958203316,
864
+ 0.05588391050696373,
865
+ 0.030406199395656586,
866
+ -0.004747200291603804,
867
+ -0.1396324634552002,
868
+ 0.026269342750310898,
869
+ -0.029952645301818848,
870
+ 0.06577280908823013,
871
+ 0.07896578311920166,
872
+ 0.006481649354100227,
873
+ -0.11994511634111404,
874
+ 0.07927889376878738,
875
+ -0.0009603027137927711,
876
+ -0.08857662230730057,
877
+ -0.01771746575832367,
878
+ 0.13173258304595947,
879
+ 0.1196831464767456,
880
+ 0.0032359701581299305,
881
+ -0.0013527998235076666,
882
+ 0.1585170179605484,
883
+ 0.053464554250240326,
884
+ 0.1717112958431244,
885
+ -0.0022603918332606554,
886
+ -0.04234495759010315,
887
+ 0.06364049017429352,
888
+ 0.09501801431179047,
889
+ -0.04763609543442726,
890
+ 0.005532358773052692,
891
+ 0.15389728546142578,
892
+ 0.037763312458992004,
893
+ 0.04795394837856293,
894
+ 0.09376377612352371,
895
+ 0.04853798821568489,
896
+ -0.017781969159841537,
897
+ 0.10978776961565018,
898
+ -0.059433385729789734,
899
+ 0.13021761178970337,
900
+ -0.04068084433674812,
901
+ -0.09737341105937958,
902
+ -0.051584407687187195,
903
+ 0.004978674463927746,
904
+ -0.00124297512229532,
905
+ -0.10999264568090439,
906
+ 0.05976748466491699,
907
+ -0.013356665149331093,
908
+ -0.020037313923239708,
909
+ -0.13073432445526123,
910
+ -0.05498259514570236,
911
+ -0.10517313331365585,
912
+ 0.006567369680851698,
913
+ 0.11132080107927322,
914
+ 0.075832799077034,
915
+ -0.014782462269067764,
916
+ 0.054923396557569504,
917
+ 0.11408346146345139,
918
+ -0.11242004483938217,
919
+ -0.02907419577240944,
920
+ -0.01590387336909771,
921
+ 0.0186269860714674,
922
+ -0.15875235199928284,
923
+ -0.01718713901937008,
924
+ 0.0004678733239416033,
925
+ -0.061454061418771744,
926
+ 0.15106505155563354,
927
+ -0.10642614960670471,
928
+ 0.15369418263435364,
929
+ 0.08291187137365341,
930
+ 0.04548779875040054,
931
+ 0.007982353679835796,
932
+ 0.06330762803554535,
933
+ 0.033961664885282516,
934
+ -0.014757526107132435,
935
+ 0.023637447506189346,
936
+ -0.051955901086330414,
937
+ -0.15155473351478577,
938
+ -0.06317074596881866,
939
+ -0.014444715343415737,
940
+ 0.20402032136917114,
941
+ -0.07881775498390198,
942
+ -0.05122551694512367,
943
+ -0.009053944610059261,
944
+ -0.03693239763379097,
945
+ -0.17400044202804565,
946
+ -0.0394265353679657,
947
+ -0.0008639968582428992,
948
+ 0.06474142521619797,
949
+ 0.04816853255033493,
950
+ -0.015438792295753956,
951
+ -0.029206177219748497
952
+ ]
953
+ },
954
+ {
955
+ "file": "/Users/jun/Library/Mobile Documents/iCloud~md~obsidian/Documents/new/700_projects/smart-coding-mcp/features/index-codebase.js",
956
+ "startLine": 806,
957
+ "endLine": 942,
958
+ "content": " this.indexingStatus.percentage = 100;\n \n // Send completion progress\n const summaryMsg = changedFiles > 0 \n ? `Complete: ${totalChunks} chunks from ${changedFiles} changed files (${skippedFiles} unchanged) in ${totalTime}s`\n : `Complete: No files changed (${skippedFiles} files up to date)`;\n this.sendProgress(100, 100, summaryMsg);\n \n await this.cache.save();\n\n const stats = await resolveCacheStats(this.cache);\n const resolvedTotalChunks =\n stats.totalChunks === 0 && totalChunks > 0 ? totalChunks : stats.totalChunks;\n const resolvedTotalFiles =\n stats.totalFiles === 0 && changedFiles > 0 ? changedFiles : stats.totalFiles;\n return {\n skipped: false,\n filesProcessed: changedFiles,\n chunksCreated: totalChunks,\n totalFiles: resolvedTotalFiles,\n totalChunks: resolvedTotalChunks,\n duration: totalTime,\n message: changedFiles > 0 \n ? `Indexed ${changedFiles} files (${totalChunks} chunks, ${skippedFiles} unchanged) in ${totalTime}s`\n : `All ${skippedFiles} files up to date`\n };\n } finally {\n this.isIndexing = false;\n // Adjust estimated total after completion\n this.indexingStatus.totalFiles = processedFiles + skippedFiles;\n }\n }\n\n setupFileWatcher() {\n if (!this.config.watchFiles) return;\n\n const pattern = this.config.fileExtensions.map(ext => `**/*.${ext}`);\n \n this.watcher = chokidar.watch(pattern, {\n cwd: this.config.searchDirectory,\n ignored: this.config.excludePatterns,\n persistent: true,\n ignoreInitial: true\n });\n\n this.watcher\n .on(\"add\", async (filePath) => {\n const fullPath = path.join(this.config.searchDirectory, filePath);\n console.error(`[Indexer] New file detected: ${filePath}`);\n await this.indexFile(fullPath);\n await this.cache.save();\n })\n .on(\"change\", async (filePath) => {\n const fullPath = path.join(this.config.searchDirectory, filePath);\n console.error(`[Indexer] File changed: ${filePath}`);\n await this.indexFile(fullPath);\n await this.cache.save();\n })\n .on(\"unlink\", (filePath) => {\n const fullPath = path.join(this.config.searchDirectory, filePath);\n console.error(`[Indexer] File deleted: ${filePath}`);\n this.cache.removeFileFromStore(fullPath);\n this.cache.deleteFileHash(fullPath);\n this.cache.save();\n });\n\n console.error(\"[Indexer] File watcher enabled for incremental indexing\");\n }\n}\n\n// MCP Tool definition for this feature\nexport function getToolDefinition() {\n return {\n name: \"b_index_codebase\",\n description: \"Manually trigger a full reindex of the codebase. This will scan all files and update the embeddings cache. Useful after large code changes or if the index seems out of date.\",\n inputSchema: {\n type: \"object\",\n properties: {\n force: {\n type: \"boolean\",\n description: \"Force reindex even if files haven't changed\",\n default: false\n }\n }\n },\n annotations: {\n title: \"Reindex Codebase\",\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false\n }\n };\n}\n\n// Tool handler\nexport async function handleToolCall(request, indexer) {\n const force = request.params.arguments?.force || false;\n const result = await indexer.indexAll(force);\n \n // Handle case when indexing was skipped due to concurrent request\n if (result?.skipped) {\n return {\n content: [{\n type: \"text\",\n text: `Indexing skipped: ${result.reason}\\n\\nPlease wait for the current indexing operation to complete before requesting another reindex.`\n }]\n };\n }\n \n // Get current stats from cache\n const cacheStats = await resolveCacheStats(indexer.cache);\n const stats = {\n totalChunks: result?.totalChunks ?? cacheStats.totalChunks,\n totalFiles: result?.totalFiles ?? cacheStats.totalFiles,\n filesProcessed: result?.filesProcessed ?? 0,\n chunksCreated: result?.chunksCreated ?? 0\n };\n \n let message = result?.message \n ? `Codebase reindexed successfully.\\n\\n${result.message}`\n : `Codebase reindexed successfully.`;\n \n message += `\\n\\nStatistics:\\n- Total files in index: ${stats.totalFiles}\\n- Total code chunks: ${stats.totalChunks}`;\n \n if (stats.filesProcessed > 0) {\n message += `\\n- Files processed this run: ${stats.filesProcessed}\\n- Chunks created this run: ${stats.chunksCreated}`;\n }\n \n return {\n content: [{\n type: \"text\",\n text: message\n }]\n };\n}\n",
959
+ "vector": [
960
+ 0.23948170244693756,
961
+ 0.04286263883113861,
962
+ -0.284313827753067,
963
+ -0.16506464779376984,
964
+ 0.12502136826515198,
965
+ -0.1394774466753006,
966
+ 0.12979590892791748,
967
+ 0.08894723653793335,
968
+ 0.013148622587323189,
969
+ -0.07919842004776001,
970
+ -0.07884605973958969,
971
+ 0.06666618585586548,
972
+ 0.20726017653942108,
973
+ 0.045004718005657196,
974
+ -0.04326258972287178,
975
+ -0.004861490800976753,
976
+ 0.009672941640019417,
977
+ -0.05521945655345917,
978
+ -0.012036226689815521,
979
+ 0.1162031814455986,
980
+ 0.03884035348892212,
981
+ -0.10715600848197937,
982
+ 0.016793541610240936,
983
+ -0.06798620522022247,
984
+ 0.08106261491775513,
985
+ 0.16029970347881317,
986
+ -0.0647430270910263,
987
+ 0.025799568742513657,
988
+ -0.1294655054807663,
989
+ 0.11735974252223969,
990
+ 0.011007382534444332,
991
+ 0.08585093170404434,
992
+ -0.07773850858211517,
993
+ -0.029420392587780952,
994
+ -0.0860455110669136,
995
+ -0.009500413201749325,
996
+ 0.12107241898775101,
997
+ -0.16953547298908234,
998
+ 0.010318228043615818,
999
+ -0.014194882474839687,
1000
+ 0.06963729858398438,
1001
+ -0.048153672367334366,
1002
+ -0.04514563828706741,
1003
+ -0.10788867622613907,
1004
+ 0.0029431881848722696,
1005
+ -0.018326302990317345,
1006
+ 0.07824256271123886,
1007
+ 0.03726441413164139,
1008
+ 0.15687447786331177,
1009
+ -0.16433139145374298,
1010
+ -0.031569551676511765,
1011
+ 0.03381207212805748,
1012
+ -0.026945794001221657,
1013
+ 0.07068394869565964,
1014
+ 0.20021197199821472,
1015
+ 0.10260815918445587,
1016
+ -0.11806975305080414,
1017
+ -0.09016000479459763,
1018
+ 0.13758987188339233,
1019
+ -0.11739587783813477,
1020
+ 0.1100175678730011,
1021
+ -0.02561233937740326,
1022
+ 0.0030259026680141687,
1023
+ 0.12075459957122803,
1024
+ 0.08544037491083145,
1025
+ -0.11769631505012512,
1026
+ -0.09731180220842361,
1027
+ 0.12280730158090591,
1028
+ 0.019623935222625732,
1029
+ -0.028978781774640083,
1030
+ 0.10304403305053711,
1031
+ 0.1041877418756485,
1032
+ -0.02881542779505253,
1033
+ 0.011675221845507622,
1034
+ -0.10992570966482162,
1035
+ 0.0720379427075386,
1036
+ -0.14539392292499542,
1037
+ -0.055648721754550934,
1038
+ -0.11973642557859421,
1039
+ 0.05609261989593506,
1040
+ -0.07498329132795334,
1041
+ -0.07013565301895142,
1042
+ -0.0093824602663517,
1043
+ 0.040978897362947464,
1044
+ 0.013626805506646633,
1045
+ -0.036334652453660965,
1046
+ -0.0288072656840086,
1047
+ -0.03346323221921921,
1048
+ -0.09030415862798691,
1049
+ 0.11945045739412308,
1050
+ -0.0692397803068161,
1051
+ -0.020283525809645653,
1052
+ 0.07031993567943573,
1053
+ 0.08144325762987137,
1054
+ -0.061899080872535706,
1055
+ 0.036748532205820084,
1056
+ -0.008706125430762768,
1057
+ 0.03798976168036461,
1058
+ -0.06553841382265091,
1059
+ -0.030810272321105003,
1060
+ 0.00463100615888834,
1061
+ 0.004973101429641247,
1062
+ 0.029724154621362686,
1063
+ 0.0014544908190146089,
1064
+ 0.06575191766023636,
1065
+ 0.12247584760189056,
1066
+ 0.04362604767084122,
1067
+ 0.05273944139480591,
1068
+ 0.02680492214858532,
1069
+ 0.0010125348344445229,
1070
+ 0.03457527235150337,
1071
+ 0.055647850036621094,
1072
+ -0.07631631940603256,
1073
+ -0.10719797015190125,
1074
+ -0.018324704840779305,
1075
+ -0.03273926302790642,
1076
+ 0.16857311129570007,
1077
+ -0.04833541437983513,
1078
+ -0.12480825185775757,
1079
+ 0.04377125948667526,
1080
+ -0.07930049300193787,
1081
+ -0.07675141096115112,
1082
+ -0.026830287650227547,
1083
+ 0.05300493165850639,
1084
+ 0.022431030869483948,
1085
+ 0.027565695345401764,
1086
+ 0.0032527283765375614,
1087
+ 0.10416751354932785
1088
+ ]
1089
+ },
1090
+ {
1091
+ "file": "/Users/jun/Library/Mobile Documents/iCloud~md~obsidian/Documents/new/700_projects/smart-coding-mcp/features/get-status.js",
1092
+ "startLine": 1,
1093
+ "endLine": 194,
1094
+ "content": "/**\n * Get Status Feature\n * \n * MCP tool to return comprehensive status information about the server.\n * Useful for agents to understand current state and configuration.\n */\n\nimport fs from 'fs/promises';\nimport path from 'path';\nimport { createRequire } from 'module';\n\nconst require = createRequire(import.meta.url);\nconst packageJson = require('../package.json');\n\n/**\n * Get tool definition for MCP registration\n */\nexport function getToolDefinition(config) {\n return {\n name: \"f_get_status\",\n description: \"Get comprehensive status information about the Smart Coding MCP server. Returns version, workspace path, model configuration, indexing status, and cache information. Useful for understanding the current state of the semantic search system.\",\n inputSchema: {\n type: \"object\",\n properties: {},\n required: []\n }\n };\n}\n\n/**\n * Status Reporter class\n */\nexport class StatusReporter {\n constructor(config, cache, indexer, embedder) {\n this.config = config;\n this.cache = cache;\n this.indexer = indexer;\n this.embedder = embedder;\n this.startTime = Date.now();\n }\n\n /**\n * Get comprehensive status\n */\n async getStatus() {\n const vectorStoreProvider = (this.config.vectorStoreProvider || 'sqlite').toLowerCase();\n let totalChunks = 0;\n let totalFiles = 0;\n\n if (typeof this.cache?.getStats === 'function') {\n try {\n const stats = await this.cache.getStats();\n totalChunks = Number(stats?.totalChunks || 0);\n totalFiles = Number(stats?.totalFiles || 0);\n } catch (err) {\n // Fallback to legacy vectorStore contract if cache-specific stats fail.\n }\n }\n\n if (totalChunks === 0 && totalFiles === 0) {\n const vectorStore = this.cache?.getVectorStore?.() || [];\n totalChunks = vectorStore.length;\n totalFiles = new Set(vectorStore.map((v) => v.file)).size;\n }\n \n // Get cache size (check for SQLite database)\n let cacheSizeBytes = 0;\n let cacheType = 'none';\n if (vectorStoreProvider === 'milvus') {\n cacheType = 'milvus';\n } else {\n try {\n // Check for SQLite cache first\n const sqlitePath = path.join(this.config.cacheDirectory, 'embeddings.db');\n const stats = await fs.stat(sqlitePath);\n cacheSizeBytes = stats.size;\n cacheType = 'sqlite';\n } catch {\n // Try old JSON cache as fallback\n try {\n const jsonPath = path.join(this.config.cacheDirectory, 'embeddings.json');\n const stats = await fs.stat(jsonPath);\n cacheSizeBytes = stats.size;\n cacheType = 'json';\n } catch {\n // No cache file exists\n cacheType = 'none';\n }\n }\n }\n\n // Determine index status and progressive indexing info\n let indexStatus = 'empty';\n let progressiveIndexing = null;\n \n if (this.indexer?.isIndexing) {\n indexStatus = 'indexing';\n // Include progressive indexing status\n if (this.indexer.indexingStatus) {\n progressiveIndexing = {\n inProgress: this.indexer.indexingStatus.inProgress,\n totalFiles: this.indexer.indexingStatus.totalFiles,\n processedFiles: this.indexer.indexingStatus.processedFiles,\n percentage: this.indexer.indexingStatus.percentage\n };\n }\n } else if (totalChunks > 0) {\n indexStatus = 'ready';\n }\n\n return {\n version: packageJson.version,\n uptime: Math.floor((Date.now() - this.startTime) / 1000),\n \n workspace: {\n path: this.config.searchDirectory,\n cacheDirectory: this.config.cacheDirectory\n },\n\n model: {\n provider: this.config.embeddingProvider,\n name: this.embedder?.modelName || (\n this.config.embeddingProvider === 'gemini'\n ? this.config.geminiModel\n : this.config.embeddingModel\n ),\n dimension: this.embedder?.dimension || (\n this.config.embeddingProvider === 'gemini'\n ? this.config.geminiDimensions\n : this.config.embeddingDimension\n ),\n device: this.embedder?.device || this.config.device\n },\n\n index: {\n status: indexStatus,\n filesIndexed: totalFiles,\n chunksCount: totalChunks,\n chunkingMode: this.config.chunkingMode,\n ...(progressiveIndexing && { progressiveIndexing })\n },\n\n cache: {\n enabled: this.config.enableCache,\n type: cacheType,\n path: cacheType === 'milvus' ? this.config.milvusAddress : this.config.cacheDirectory,\n sizeBytes: cacheSizeBytes,\n sizeFormatted: formatBytes(cacheSizeBytes)\n },\n\n config: {\n maxResults: this.config.maxResults,\n chunkSize: this.config.chunkSize,\n semanticWeight: this.config.semanticWeight,\n exactMatchBoost: this.config.exactMatchBoost,\n workerThreads: this.config.workerThreads,\n embeddingProvider: this.config.embeddingProvider,\n vectorStoreProvider\n },\n \n resourceThrottling: {\n maxCpuPercent: this.config.maxCpuPercent,\n batchDelay: this.config.batchDelay,\n maxWorkers: this.config.maxWorkers\n }\n };\n }\n}\n\n/**\n * Format bytes to human readable\n */\nfunction formatBytes(bytes) {\n if (bytes === 0) return '0 B';\n const k = 1024;\n const sizes = ['B', 'KB', 'MB', 'GB'];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];\n}\n\n/**\n * Handle MCP tool call\n */\nexport async function handleToolCall(request, instance) {\n const status = await instance.getStatus();\n\n return {\n content: [{\n type: \"text\",\n text: JSON.stringify(status, null, 2)\n }]\n };\n}\n",
1095
+ "vector": [
1096
+ 0.07057134062051773,
1097
+ 0.07975049316883087,
1098
+ -0.28085657954216003,
1099
+ -0.16133390367031097,
1100
+ 0.13192430138587952,
1101
+ -0.193454310297966,
1102
+ 0.07149916142225266,
1103
+ 0.07327672839164734,
1104
+ 0.11953112483024597,
1105
+ -0.17789973318576813,
1106
+ -0.06347949057817459,
1107
+ 0.0026028682477772236,
1108
+ 0.21874985098838806,
1109
+ 0.11117780953645706,
1110
+ 0.10679641366004944,
1111
+ 0.011735680513083935,
1112
+ -0.05787436664104462,
1113
+ -0.07041323184967041,
1114
+ 0.03683336451649666,
1115
+ 0.05677330493927002,
1116
+ 0.04120809957385063,
1117
+ -0.08250969648361206,
1118
+ 0.03799358010292053,
1119
+ -0.026872748509049416,
1120
+ 0.15997600555419922,
1121
+ 0.1521548628807068,
1122
+ -0.04002800211310387,
1123
+ 0.04515771567821503,
1124
+ 0.01703343540430069,
1125
+ 0.11222914606332779,
1126
+ 0.0738486647605896,
1127
+ 0.026411380618810654,
1128
+ 0.014012142084538937,
1129
+ -0.056096505373716354,
1130
+ -0.018833540380001068,
1131
+ -0.01252163015305996,
1132
+ 0.0024008669424802065,
1133
+ -0.10509614646434784,
1134
+ -0.08253582566976547,
1135
+ -0.014716788195073605,
1136
+ 0.046310391277074814,
1137
+ -0.06366497278213501,
1138
+ -0.031537752598524094,
1139
+ -0.1625177562236786,
1140
+ 0.075807124376297,
1141
+ 0.05269966274499893,
1142
+ 0.13652341067790985,
1143
+ -0.0021229886915534735,
1144
+ 0.05677998438477516,
1145
+ -0.08062577247619629,
1146
+ 0.003292395733296871,
1147
+ -0.060112789273262024,
1148
+ 0.12968482077121735,
1149
+ 0.08327163755893707,
1150
+ 0.2338561862707138,
1151
+ 0.008730914443731308,
1152
+ -0.05286018177866936,
1153
+ -0.025395814329385757,
1154
+ 0.032928839325904846,
1155
+ -0.06716848164796829,
1156
+ 0.2210608273744583,
1157
+ 0.1542629599571228,
1158
+ -0.037004876881837845,
1159
+ 0.2010587751865387,
1160
+ 0.08159790933132172,
1161
+ -0.055133260786533356,
1162
+ -0.0707874447107315,
1163
+ 0.11406750977039337,
1164
+ -0.11926154047250748,
1165
+ 0.022425012663006783,
1166
+ -0.018791355192661285,
1167
+ 0.006005498114973307,
1168
+ -0.00579097680747509,
1169
+ 0.040023110806941986,
1170
+ -0.08182903379201889,
1171
+ 0.0627443939447403,
1172
+ -0.09738650918006897,
1173
+ -0.05425114557147026,
1174
+ -0.06759131699800491,
1175
+ 0.11094355583190918,
1176
+ -0.003977371379733086,
1177
+ -0.04718845710158348,
1178
+ 0.12378638982772827,
1179
+ -0.09827891737222672,
1180
+ -0.0010886465897783637,
1181
+ -0.11848212033510208,
1182
+ -0.02759416401386261,
1183
+ -0.05441422387957573,
1184
+ -0.06485196948051453,
1185
+ 0.09176939725875854,
1186
+ -0.027164287865161896,
1187
+ 0.03661518543958664,
1188
+ -0.004690262489020824,
1189
+ 0.023897338658571243,
1190
+ -0.0978141501545906,
1191
+ 0.11650973558425903,
1192
+ 0.019258227199316025,
1193
+ -0.05889353156089783,
1194
+ -0.08325810730457306,
1195
+ -0.0824921578168869,
1196
+ 0.02578507363796234,
1197
+ -0.05377570539712906,
1198
+ -0.02489115484058857,
1199
+ -0.030422650277614594,
1200
+ 0.07098051905632019,
1201
+ 0.14765895903110504,
1202
+ -0.03612038493156433,
1203
+ 0.08885776251554489,
1204
+ 0.006427676882594824,
1205
+ -0.015176921151578426,
1206
+ -0.030876707285642624,
1207
+ 0.07927709817886353,
1208
+ -0.03936394676566124,
1209
+ 0.026058975607156754,
1210
+ -0.027527671307325363,
1211
+ -0.07588574290275574,
1212
+ 0.11834855377674103,
1213
+ 0.05359714850783348,
1214
+ -0.06899028271436691,
1215
+ 0.013759649358689785,
1216
+ 0.04004018381237984,
1217
+ -0.1256563812494278,
1218
+ -0.08887828141450882,
1219
+ 0.0417821928858757,
1220
+ 0.0721406489610672,
1221
+ 0.07611875981092453,
1222
+ -0.026015425100922585,
1223
+ -0.017744600772857666
1224
+ ]
1225
+ },
1226
+ {
1227
+ "file": "/Users/jun/Library/Mobile Documents/iCloud~md~obsidian/Documents/new/700_projects/smart-coding-mcp/features/hybrid-search.js",
1228
+ "startLine": 1,
1229
+ "endLine": 190,
1230
+ "content": "import path from \"path\";\nimport { cosineSimilarity } from \"../lib/utils.js\";\n\nexport class HybridSearch {\n constructor(embedder, cache, config, indexer = null) {\n this.embedder = embedder;\n this.cache = cache;\n this.config = config;\n this.indexer = indexer; // Reference to indexer for status checking\n }\n\n async search(query, maxResults) {\n const hasAnnSearch = typeof this.cache?.searchByVector === \"function\";\n\n // Show warning if indexing is still in progress but we have some results\n let indexingWarning = null;\n if (this.indexer?.indexingStatus?.inProgress) {\n indexingWarning = `⚠️ Indexing in progress (${this.indexer.indexingStatus.percentage}% complete). Results shown are from partially indexed codebase.\\n\\n`;\n }\n\n // Generate query embedding\n const queryEmbed = await this.embedder(query, { pooling: \"mean\", normalize: true });\n const queryVector = Array.from(queryEmbed.data);\n\n if (hasAnnSearch) {\n const annTopK = Math.max(maxResults * 5, 20);\n const candidates = await this.cache.searchByVector(queryVector, annTopK);\n\n const scoredChunks = candidates.map((chunk) => {\n // Base semantic score from provider (Milvus or fallback cache) plus lexical boost.\n let score = Number(chunk.score || 0) * this.config.semanticWeight;\n\n const lowerQuery = query.toLowerCase();\n const lowerContent = String(chunk.content || \"\").toLowerCase();\n\n if (lowerContent.includes(lowerQuery)) {\n score += this.config.exactMatchBoost;\n } else {\n const queryWords = lowerQuery.split(/\\s+/).filter(Boolean);\n const matchedWords = queryWords.filter(\n (word) => word.length > 2 && lowerContent.includes(word)\n ).length;\n const lexicalBoost = queryWords.length > 0 ? (matchedWords / queryWords.length) * 0.3 : 0;\n score += lexicalBoost;\n }\n\n return { ...chunk, score };\n });\n\n const results = scoredChunks\n .sort((a, b) => b.score - a.score)\n .slice(0, maxResults);\n\n if (results.length === 0) {\n const stats = typeof this.cache?.getStats === \"function\"\n ? await this.cache.getStats().catch(() => null)\n : null;\n const totalChunks = Number(stats?.totalChunks || 0);\n\n if (totalChunks === 0) {\n if (this.indexer?.indexingStatus?.inProgress) {\n return {\n results: [],\n message: `Indexing in progress (${this.indexer.indexingStatus.percentage}% complete). Search available but results may be incomplete. Please wait for indexing to finish for full coverage.`\n };\n }\n return {\n results: [],\n message: \"No code has been indexed yet. Please wait for initial indexing to complete.\"\n };\n }\n }\n\n return { results, message: null, indexingWarning };\n }\n\n // Legacy fallback: in-memory vector scoring.\n const vectorStore = this.cache.getVectorStore();\n\n if (vectorStore.length === 0) {\n if (this.indexer?.indexingStatus?.inProgress) {\n return {\n results: [],\n message: `Indexing in progress (${this.indexer.indexingStatus.percentage}% complete). Search available but results may be incomplete. Please wait for indexing to finish for full coverage.`\n };\n }\n return {\n results: [],\n message: \"No code has been indexed yet. Please wait for initial indexing to complete.\"\n };\n }\n\n const scoredChunks = vectorStore.map((chunk) => {\n let score = cosineSimilarity(queryVector, chunk.vector) * this.config.semanticWeight;\n\n const lowerQuery = query.toLowerCase();\n const lowerContent = chunk.content.toLowerCase();\n\n if (lowerContent.includes(lowerQuery)) {\n score += this.config.exactMatchBoost;\n } else {\n const queryWords = lowerQuery.split(/\\s+/);\n const matchedWords = queryWords.filter((word) =>\n word.length > 2 && lowerContent.includes(word)\n ).length;\n score += (matchedWords / queryWords.length) * 0.3;\n }\n\n return { ...chunk, score };\n });\n\n const results = scoredChunks\n .sort((a, b) => b.score - a.score)\n .slice(0, maxResults);\n\n return { results, message: null, indexingWarning };\n }\n\n formatResults(results) {\n if (results.length === 0) {\n return \"No matching code found for your query.\";\n }\n\n return results.map((r, idx) => {\n const relPath = path.relative(this.config.searchDirectory, r.file);\n return `## Result ${idx + 1} (Relevance: ${(r.score * 100).toFixed(1)}%)\\n` +\n `**File:** \\`${relPath}\\`\\n` +\n `**Lines:** ${r.startLine}-${r.endLine}\\n\\n` +\n \"```\" + path.extname(r.file).slice(1) + \"\\n\" +\n r.content + \"\\n\" +\n \"```\\n\";\n }).join(\"\\n\");\n }\n}\n\n// MCP Tool definition for this feature\nexport function getToolDefinition(config) {\n return {\n name: \"a_semantic_search\",\n description: \"Performs intelligent hybrid code search combining semantic understanding with exact text matching. Ideal for finding code by meaning (e.g., 'authentication logic', 'database queries') even with typos or variations. Returns the most relevant code snippets with file locations and line numbers.\",\n inputSchema: {\n type: \"object\",\n properties: {\n query: { \n type: \"string\", \n description: \"Search query - can be natural language (e.g., 'where do we handle user login') or specific terms\" \n },\n maxResults: {\n type: \"number\",\n description: \"Maximum number of results to return (default: from config)\",\n default: config.maxResults\n }\n },\n required: [\"query\"]\n },\n annotations: {\n title: \"Semantic Code Search\",\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false\n }\n };\n}\n\n// Tool handler\nexport async function handleToolCall(request, hybridSearch) {\n const query = request.params.arguments.query;\n const maxResults = request.params.arguments.maxResults || hybridSearch.config.maxResults;\n \n const { results, message, indexingWarning } = await hybridSearch.search(query, maxResults);\n \n if (message) {\n return {\n content: [{ type: \"text\", text: message }]\n };\n }\n\n let formattedText = hybridSearch.formatResults(results);\n \n // Prepend indexing warning if present\n if (indexingWarning) {\n formattedText = indexingWarning + formattedText;\n }\n \n return {\n content: [{ type: \"text\", text: formattedText }]\n };\n}\n",
1231
+ "vector": [
1232
+ 0.12740129232406616,
1233
+ 0.15518121421337128,
1234
+ -0.21078798174858093,
1235
+ -0.1617874801158905,
1236
+ 0.09796125441789627,
1237
+ -0.08469109982252121,
1238
+ 0.004065915942192078,
1239
+ 0.07962168008089066,
1240
+ 0.0743013322353363,
1241
+ -0.09125421196222305,
1242
+ 0.004482813645154238,
1243
+ -0.014535536989569664,
1244
+ 0.2535611689090729,
1245
+ -0.006922576110810041,
1246
+ -0.024550603702664375,
1247
+ 0.014434193260967731,
1248
+ 0.025115404278039932,
1249
+ -0.08896371722221375,
1250
+ 0.06893981248140335,
1251
+ 0.09941767156124115,
1252
+ -0.03565429523587227,
1253
+ -0.06120755150914192,
1254
+ 0.12204122543334961,
1255
+ -0.01462928019464016,
1256
+ 0.1737065464258194,
1257
+ 0.07341615110635757,
1258
+ -0.08381202071905136,
1259
+ 0.023803947493433952,
1260
+ -0.08263792097568512,
1261
+ 0.09843198955059052,
1262
+ -0.004881568253040314,
1263
+ 0.09881187230348587,
1264
+ -0.057152073830366135,
1265
+ 0.001458719838410616,
1266
+ -0.11398345977067947,
1267
+ 0.05571068450808525,
1268
+ 0.03979508951306343,
1269
+ -0.06164532154798508,
1270
+ -0.027130812406539917,
1271
+ 0.09677587449550629,
1272
+ 0.047586701810359955,
1273
+ -0.005022235214710236,
1274
+ -0.01879909634590149,
1275
+ -0.10060114413499832,
1276
+ 0.03749711439013481,
1277
+ -0.0520540215075016,
1278
+ 0.14345088601112366,
1279
+ 0.014659328386187553,
1280
+ 0.2107417732477188,
1281
+ -0.15408197045326233,
1282
+ -0.10587600618600845,
1283
+ -0.07136231660842896,
1284
+ 0.0197284072637558,
1285
+ -0.049530502408742905,
1286
+ 0.22692681849002838,
1287
+ 0.11802695691585541,
1288
+ -0.155377596616745,
1289
+ -0.10578014701604843,
1290
+ 0.12910324335098267,
1291
+ -0.06785444915294647,
1292
+ 0.1183910071849823,
1293
+ 0.12445499747991562,
1294
+ -0.02341265231370926,
1295
+ 0.17693458497524261,
1296
+ 0.021237554028630257,
1297
+ -0.08250609040260315,
1298
+ -0.11043854802846909,
1299
+ 0.11984279006719589,
1300
+ -0.050937406718730927,
1301
+ -0.008535142987966537,
1302
+ 0.08321410417556763,
1303
+ -0.059367138892412186,
1304
+ 0.047683488577604294,
1305
+ -0.00565014174208045,
1306
+ -0.19091835618019104,
1307
+ 0.05729922652244568,
1308
+ -0.07905294001102448,
1309
+ 0.004023456014692783,
1310
+ -0.024473071098327637,
1311
+ 0.10127636045217514,
1312
+ -0.09744955599308014,
1313
+ 0.029921511188149452,
1314
+ -0.042635418474674225,
1315
+ -0.020941410213708878,
1316
+ 0.03464387729763985,
1317
+ -0.08861012011766434,
1318
+ -0.05885560065507889,
1319
+ -0.041618406772613525,
1320
+ -0.08023533225059509,
1321
+ 0.06750112771987915,
1322
+ 0.028942542150616646,
1323
+ -0.10820017755031586,
1324
+ 0.04572024941444397,
1325
+ -0.008157225325703621,
1326
+ -0.09873592108488083,
1327
+ 0.10617676377296448,
1328
+ -0.0024774326011538506,
1329
+ 0.009695076383650303,
1330
+ -0.005787276197224855,
1331
+ -0.115599624812603,
1332
+ 0.009291514754295349,
1333
+ -0.027600111439824104,
1334
+ -0.0031702371779829264,
1335
+ -0.04750872403383255,
1336
+ 0.040915895253419876,
1337
+ 0.11683639883995056,
1338
+ 0.04092840477824211,
1339
+ 0.016026191413402557,
1340
+ -0.04528998211026192,
1341
+ -0.06519939750432968,
1342
+ -0.009214947931468487,
1343
+ 0.04757414385676384,
1344
+ -0.15759478509426117,
1345
+ -0.158574178814888,
1346
+ 0.05039048567414284,
1347
+ 0.02694810926914215,
1348
+ 0.05461406707763672,
1349
+ 0.0051349615678191185,
1350
+ -0.00433738436549902,
1351
+ 0.07376096397638321,
1352
+ 0.021075638011097908,
1353
+ -0.07508666068315506,
1354
+ -0.07525905966758728,
1355
+ 0.10990860313177109,
1356
+ 0.12451997399330139,
1357
+ 0.06247367337346077,
1358
+ 0.0535169318318367,
1359
+ 0.02656020224094391
1360
+ ]
1361
+ },
1362
+ {
1363
+ "file": "/Users/jun/Library/Mobile Documents/iCloud~md~obsidian/Documents/new/700_projects/smart-coding-mcp/lib/cache.js",
1364
+ "startLine": 1,
1365
+ "endLine": 158,
1366
+ "content": "import fs from \"fs/promises\";\nimport path from \"path\";\nimport { cosineSimilarity } from \"./utils.js\";\n\nexport class EmbeddingsCache {\n constructor(config) {\n this.config = config;\n this.vectorStore = [];\n this.fileHashes = new Map();\n this.isSaving = false;\n }\n\n async load() {\n if (!this.config.enableCache) return;\n \n try {\n await fs.mkdir(this.config.cacheDirectory, { recursive: true });\n const cacheFile = path.join(this.config.cacheDirectory, \"embeddings.json\");\n const hashFile = path.join(this.config.cacheDirectory, \"file-hashes.json\");\n \n const [cacheData, hashData] = await Promise.all([\n fs.readFile(cacheFile, \"utf-8\").catch(() => null),\n fs.readFile(hashFile, \"utf-8\").catch(() => null)\n ]);\n\n if (cacheData && hashData) {\n const rawVectorStore = JSON.parse(cacheData);\n const rawHashes = new Map(Object.entries(JSON.parse(hashData)));\n \n // Filter cache to only include files matching current extensions\n const allowedExtensions = this.config.fileExtensions.map(ext => `.${ext}`);\n \n this.vectorStore = rawVectorStore.filter(chunk => {\n const ext = path.extname(chunk.file);\n return allowedExtensions.includes(ext);\n });\n \n // Only keep hashes for files matching current extensions\n for (const [file, hash] of rawHashes) {\n const ext = path.extname(file);\n if (allowedExtensions.includes(ext)) {\n this.fileHashes.set(file, hash);\n }\n }\n \n const filtered = rawVectorStore.length - this.vectorStore.length;\n if (filtered > 0) {\n console.error(`[Cache] Filtered ${filtered} outdated cache entries`);\n }\n console.error(`[Cache] Loaded ${this.vectorStore.length} cached embeddings`);\n }\n } catch (error) {\n console.error(\"[Cache] Failed to load cache:\", error.message);\n }\n }\n\n async save() {\n if (!this.config.enableCache) return;\n \n this.isSaving = true;\n \n try {\n await fs.mkdir(this.config.cacheDirectory, { recursive: true });\n const cacheFile = path.join(this.config.cacheDirectory, \"embeddings.json\");\n const hashFile = path.join(this.config.cacheDirectory, \"file-hashes.json\");\n \n await Promise.all([\n fs.writeFile(cacheFile, JSON.stringify(this.vectorStore, null, 2)),\n fs.writeFile(hashFile, JSON.stringify(Object.fromEntries(this.fileHashes), null, 2))\n ]);\n } catch (error) {\n console.error(\"[Cache] Failed to save cache:\", error.message);\n } finally {\n this.isSaving = false;\n }\n }\n\n getVectorStore() {\n return this.vectorStore;\n }\n\n searchByVector(queryVector, topK = 10) {\n const normalizedTopK = Number.isInteger(topK) && topK > 0 ? topK : 10;\n\n return this.vectorStore\n .map((chunk) => ({\n ...chunk,\n score: cosineSimilarity(queryVector, chunk.vector)\n }))\n .sort((a, b) => b.score - a.score)\n .slice(0, normalizedTopK);\n }\n\n getStats() {\n return {\n totalChunks: this.vectorStore.length,\n totalFiles: new Set(this.vectorStore.map((v) => v.file)).size\n };\n }\n\n setVectorStore(store) {\n this.vectorStore = store;\n }\n\n getFileHash(file) {\n const entry = this.fileHashes.get(file);\n // Support both old format (string) and new format ({ hash, mtime })\n if (typeof entry === 'string') {\n return entry;\n }\n return entry?.hash;\n }\n\n getFileMtime(file) {\n const entry = this.fileHashes.get(file);\n return entry?.mtime;\n }\n\n setFileHash(file, hash, mtime = null) {\n this.fileHashes.set(file, { hash, mtime });\n }\n\n deleteFileHash(file) {\n this.fileHashes.delete(file);\n }\n\n getAllFileHashes() {\n return this.fileHashes;\n }\n\n clearAllFileHashes() {\n this.fileHashes = new Map();\n }\n\n removeFileFromStore(file) {\n this.vectorStore = this.vectorStore.filter(chunk => chunk.file !== file);\n }\n\n\n addToStore(chunk) {\n this.vectorStore.push(chunk);\n }\n\n async clear() {\n if (!this.config.enableCache) return;\n \n try {\n await fs.rm(this.config.cacheDirectory, { recursive: true, force: true });\n this.vectorStore = [];\n this.fileHashes = new Map();\n console.error(`[Cache] Cache cleared successfully: ${this.config.cacheDirectory}`);\n } catch (error) {\n console.error(\"[Cache] Failed to clear cache:\", error.message);\n throw error;\n }\n }\n}\n",
1367
+ "vector": [
1368
+ 0.16595740616321564,
1369
+ 0.10454504936933517,
1370
+ -0.22398774325847626,
1371
+ -0.1350092887878418,
1372
+ 0.050904229283332825,
1373
+ -0.08924388885498047,
1374
+ -0.0661332905292511,
1375
+ 0.06810492277145386,
1376
+ -0.0014953304780647159,
1377
+ -0.08119437843561172,
1378
+ -0.013574246317148209,
1379
+ -0.014959864318370819,
1380
+ 0.2628854811191559,
1381
+ 0.08369147032499313,
1382
+ 0.03517252579331398,
1383
+ 0.14099162817001343,
1384
+ -0.07524291425943375,
1385
+ -0.06467299163341522,
1386
+ 0.0003173230797983706,
1387
+ 0.05953002721071243,
1388
+ -0.04255819320678711,
1389
+ -0.07436007261276245,
1390
+ 0.0452319011092186,
1391
+ 0.010065384209156036,
1392
+ 0.1350277066230774,
1393
+ 0.12188588827848434,
1394
+ -0.019381504505872726,
1395
+ 0.054564639925956726,
1396
+ -0.10160712897777557,
1397
+ 0.06566224247217178,
1398
+ 0.07409965246915817,
1399
+ 0.1310969591140747,
1400
+ -0.011461990885436535,
1401
+ -0.11751676350831985,
1402
+ -0.03916100785136223,
1403
+ 0.0008718477911315858,
1404
+ -0.07819074392318726,
1405
+ -0.06570448726415634,
1406
+ 0.06440527737140656,
1407
+ -0.1371002048254013,
1408
+ -0.010946937836706638,
1409
+ 0.010660053230822086,
1410
+ -0.07399876415729523,
1411
+ -0.0406130775809288,
1412
+ 0.06791641563177109,
1413
+ -0.015665551647543907,
1414
+ 0.18031153082847595,
1415
+ 0.06830530613660812,
1416
+ 0.20751991868019104,
1417
+ -0.12634173035621643,
1418
+ -0.02605307102203369,
1419
+ -0.022946691140532494,
1420
+ -0.0628606528043747,
1421
+ -0.0038798018358647823,
1422
+ 0.22924625873565674,
1423
+ 0.051772695034742355,
1424
+ -0.10262198746204376,
1425
+ -0.08592791110277176,
1426
+ 0.14286868274211884,
1427
+ -0.10877623409032822,
1428
+ 0.08739428967237473,
1429
+ 0.1251714676618576,
1430
+ 0.05641758441925049,
1431
+ 0.16901353001594543,
1432
+ 0.022509777918457985,
1433
+ -0.0667458176612854,
1434
+ -0.1273995190858841,
1435
+ 0.11553263664245605,
1436
+ -0.02373179793357849,
1437
+ -0.006410493049770594,
1438
+ 0.039889901876449585,
1439
+ 0.06181389465928078,
1440
+ 0.03942074254155159,
1441
+ -0.11699230968952179,
1442
+ -0.11126925051212311,
1443
+ 0.052126314491033554,
1444
+ -0.03360385447740555,
1445
+ -0.00505861546844244,
1446
+ -0.11397335678339005,
1447
+ 0.10593877732753754,
1448
+ -0.1899852305650711,
1449
+ -0.05128175765275955,
1450
+ 0.011601158417761326,
1451
+ -0.007252636831253767,
1452
+ 0.041124872863292694,
1453
+ -0.0657215416431427,
1454
+ -0.08952194452285767,
1455
+ -0.09239066392183304,
1456
+ -0.08257213979959488,
1457
+ 0.025224551558494568,
1458
+ -0.04575124755501747,
1459
+ -0.07483971863985062,
1460
+ 0.026660697534680367,
1461
+ 0.005754536017775536,
1462
+ -0.04968397691845894,
1463
+ 0.04767967760562897,
1464
+ -0.021406661719083786,
1465
+ 0.016721956431865692,
1466
+ -0.04791073501110077,
1467
+ -0.09808947145938873,
1468
+ -0.0018265699036419392,
1469
+ -0.08777353167533875,
1470
+ 0.0768047571182251,
1471
+ 0.009647988714277744,
1472
+ 0.13009925186634064,
1473
+ 0.03809253126382828,
1474
+ 0.08258683979511261,
1475
+ 0.09020508080720901,
1476
+ 0.07393025606870651,
1477
+ -0.08418470621109009,
1478
+ 0.007934866473078728,
1479
+ 0.11525744199752808,
1480
+ -0.08252908289432526,
1481
+ -0.10055242478847504,
1482
+ -0.004714840557426214,
1483
+ -0.033263642340898514,
1484
+ 0.08397563546895981,
1485
+ -0.03178725764155388,
1486
+ 0.011175778694450855,
1487
+ -0.0030163482297211885,
1488
+ 0.019418450072407722,
1489
+ -0.14418764412403107,
1490
+ -0.031112829223275185,
1491
+ 0.08298131823539734,
1492
+ 0.1455431878566742,
1493
+ 0.05146403983235359,
1494
+ -0.020356344059109688,
1495
+ -0.07960373908281326
1496
+ ]
1497
+ },
1498
+ {
1499
+ "file": "/Users/jun/Library/Mobile Documents/iCloud~md~obsidian/Documents/new/700_projects/smart-coding-mcp/lib/sqlite-cache.js",
1500
+ "startLine": 1,
1501
+ "endLine": 469,
1502
+ "content": "import Database from 'better-sqlite3';\nimport fs from 'fs/promises';\nimport path from 'path';\nimport { cosineSimilarity } from './utils.js';\n\n/**\n * SQLite-based embeddings cache for fast, efficient storage\n * Replaces JSON-based cache for better performance on large codebases\n */\nexport class SQLiteCache {\n constructor(config) {\n this.config = config;\n this.db = null;\n this.isSaving = false;\n this.dbPath = path.join(config.cacheDirectory, 'embeddings.db');\n \n // Track indexing status for progressive indexing\n this.indexingStatus = {\n inProgress: false,\n totalFiles: 0,\n processedFiles: 0,\n percentage: 0\n };\n }\n\n /**\n * Initialize SQLite database and create schema\n */\n async load() {\n if (!this.config.enableCache) return;\n \n try {\n // Ensure cache directory exists\n await fs.mkdir(this.config.cacheDirectory, { recursive: true });\n \n // Check if we need to migrate from JSON\n const jsonCacheExists = await this.checkJSONCache();\n \n // Open SQLite database\n this.db = new Database(this.dbPath);\n \n // Enable performance optimizations\n this.db.pragma('journal_mode = WAL'); // Write-Ahead Logging for better concurrency\n this.db.pragma('synchronous = NORMAL'); // Faster writes, still safe\n this.db.pragma('cache_size = 10000'); // 10MB cache\n this.db.pragma('temp_store = MEMORY'); // Temp tables in memory\n \n // Create schema if not exists\n this.createSchema();\n \n // Migrate from JSON if needed\n if (jsonCacheExists && this.getVectorCount() === 0) {\n console.error('[Cache] Migrating from JSON to SQLite...');\n await this.migrateFromJSON();\n }\n \n const count = this.getVectorCount();\n const fileCount = this.getFileCount();\n console.error(`[Cache] Loaded SQLite cache: ${count} embeddings from ${fileCount} files`);\n } catch (error) {\n console.error('[Cache] Failed to initialize SQLite cache:', error.message);\n throw error;\n }\n }\n\n /**\n * Create database schema\n */\n createSchema() {\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS embeddings (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n file TEXT NOT NULL,\n start_line INTEGER NOT NULL,\n end_line INTEGER NOT NULL,\n content TEXT NOT NULL,\n vector BLOB NOT NULL,\n indexed_at INTEGER NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS file_hashes (\n file TEXT PRIMARY KEY,\n hash TEXT NOT NULL,\n mtime REAL,\n indexed_at INTEGER NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS idx_file ON embeddings(file);\n CREATE INDEX IF NOT EXISTS idx_indexed_at ON embeddings(indexed_at);\n `);\n\n // Migration: Add mtime column if it doesn't exist (for existing databases)\n try {\n this.db.exec('ALTER TABLE file_hashes ADD COLUMN mtime REAL');\n } catch (e) {\n // Column already exists, ignore\n }\n }\n\n /**\n * Check if JSON cache exists\n */\n async checkJSONCache() {\n try {\n const jsonPath = path.join(this.config.cacheDirectory, 'embeddings.json');\n await fs.access(jsonPath);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Migrate from JSON cache to SQLite\n */\n async migrateFromJSON() {\n try {\n const jsonCachePath = path.join(this.config.cacheDirectory, 'embeddings.json');\n const jsonHashPath = path.join(this.config.cacheDirectory, 'file-hashes.json');\n \n const [cacheData, hashData] = await Promise.all([\n fs.readFile(jsonCachePath, 'utf-8').catch(() => null),\n fs.readFile(jsonHashPath, 'utf-8').catch(() => null)\n ]);\n\n if (!cacheData || !hashData) {\n console.error('[Cache] No JSON cache found to migrate');\n return;\n }\n\n const vectorStore = JSON.parse(cacheData);\n const fileHashes = new Map(Object.entries(JSON.parse(hashData)));\n\n console.error(`[Cache] Migrating ${vectorStore.length} embeddings...`);\n\n // Use transaction for fast batch insert\n const insertVector = this.db.prepare(`\n INSERT INTO embeddings (file, start_line, end_line, content, vector, indexed_at)\n VALUES (?, ?, ?, ?, ?, ?)\n `);\n\n const insertHash = this.db.prepare(`\n INSERT OR REPLACE INTO file_hashes (file, hash, indexed_at)\n VALUES (?, ?, ?)\n `);\n\n const transaction = this.db.transaction(() => {\n const now = Date.now();\n \n for (const chunk of vectorStore) {\n const vectorBuffer = this.vectorToBuffer(chunk.vector);\n insertVector.run(\n chunk.file,\n chunk.startLine,\n chunk.endLine,\n chunk.content,\n vectorBuffer,\n now\n );\n }\n\n for (const [file, hash] of fileHashes) {\n insertHash.run(file, hash, now);\n }\n });\n\n transaction();\n\n console.error('[Cache] Migration complete! Backing up JSON files...');\n \n // Backup old JSON files\n await fs.rename(jsonCachePath, jsonCachePath + '.backup');\n await fs.rename(jsonHashPath, jsonHashPath + '.backup');\n \n console.error('[Cache] JSON cache backed up (you can delete .backup files if everything works)');\n } catch (error) {\n console.error('[Cache] Migration failed:', error.message);\n throw error;\n }\n }\n\n /**\n * Convert Float32Array/Array to Buffer for SQLite storage\n */\n vectorToBuffer(vector) {\n const float32 = new Float32Array(vector);\n return Buffer.from(float32.buffer);\n }\n\n /**\n * Convert Buffer back to Array for compatibility\n */\n bufferToVector(buffer) {\n const float32 = new Float32Array(buffer.buffer, buffer.byteOffset, buffer.length / 4);\n return Array.from(float32);\n }\n\n /**\n * Get all vectors from store (lazy loaded)\n */\n getVectorStore() {\n if (!this.db) return [];\n \n const stmt = this.db.prepare(`\n SELECT file, start_line, end_line, content, vector\n FROM embeddings\n ORDER BY file, start_line\n `);\n\n const rows = stmt.all();\n return rows.map(row => ({\n file: row.file,\n startLine: row.start_line,\n endLine: row.end_line,\n content: row.content,\n vector: this.bufferToVector(row.vector)\n }));\n }\n\n /**\n * Vector search compatibility API used by ANN-capable search path.\n * SQLite fallback performs in-process cosine scoring.\n */\n searchByVector(queryVector, topK = 10) {\n const normalizedTopK = Number.isInteger(topK) && topK > 0 ? topK : 10;\n const vectorStore = this.getVectorStore();\n\n return vectorStore\n .map((chunk) => ({\n ...chunk,\n score: cosineSimilarity(queryVector, chunk.vector)\n }))\n .sort((a, b) => b.score - a.score)\n .slice(0, normalizedTopK);\n }\n\n /**\n * Get vector count\n */\n getVectorCount() {\n if (!this.db) return 0;\n const result = this.db.prepare('SELECT COUNT(*) as count FROM embeddings').get();\n return result.count;\n }\n\n /**\n * Get unique file count\n */\n getFileCount() {\n if (!this.db) return 0;\n const result = this.db.prepare('SELECT COUNT(DISTINCT file) as count FROM embeddings').get();\n return result.count;\n }\n\n getStats() {\n return {\n totalChunks: this.getVectorCount(),\n totalFiles: this.getFileCount()\n };\n }\n\n /**\n * Add chunk to store with batch optimization\n */\n addToStore(chunk) {\n if (!this.db) return;\n \n const vectorBuffer = this.vectorToBuffer(chunk.vector);\n const stmt = this.db.prepare(`\n INSERT INTO embeddings (file, start_line, end_line, content, vector, indexed_at)\n VALUES (?, ?, ?, ?, ?, ?)\n `);\n\n stmt.run(\n chunk.file,\n chunk.startLine,\n chunk.endLine,\n chunk.content,\n vectorBuffer,\n Date.now()\n );\n }\n\n /**\n * Add multiple chunks in a transaction (much faster)\n */\n addBatchToStore(chunks) {\n if (!this.db || chunks.length === 0) return;\n \n const stmt = this.db.prepare(`\n INSERT INTO embeddings (file, start_line, end_line, content, vector, indexed_at)\n VALUES (?, ?, ?, ?, ?, ?)\n `);\n\n const transaction = this.db.transaction(() => {\n const now = Date.now();\n for (const chunk of chunks) {\n const vectorBuffer = this.vectorToBuffer(chunk.vector);\n stmt.run(\n chunk.file,\n chunk.startLine,\n chunk.endLine,\n chunk.content,\n vectorBuffer,\n now\n );\n }\n });\n\n transaction();\n }\n\n /**\n * Remove all chunks for a specific file\n */\n removeFileFromStore(file) {\n if (!this.db) return;\n \n const stmt = this.db.prepare('DELETE FROM embeddings WHERE file = ?');\n stmt.run(file);\n }\n\n /**\n * Get file hash\n */\n getFileHash(file) {\n if (!this.db) return null;\n\n const stmt = this.db.prepare('SELECT hash FROM file_hashes WHERE file = ?');\n const row = stmt.get(file);\n return row ? row.hash : null;\n }\n\n /**\n * Get file mtime (modification time) for fast change detection\n */\n getFileMtime(file) {\n if (!this.db) return null;\n\n const stmt = this.db.prepare('SELECT mtime FROM file_hashes WHERE file = ?');\n const row = stmt.get(file);\n return row ? row.mtime : null;\n }\n\n /**\n * Set file hash with optional mtime\n */\n setFileHash(file, hash, mtime = null) {\n if (!this.db) return;\n\n const stmt = this.db.prepare(`\n INSERT OR REPLACE INTO file_hashes (file, hash, mtime, indexed_at)\n VALUES (?, ?, ?, ?)\n `);\n stmt.run(file, hash, mtime, Date.now());\n }\n\n /**\n * Delete file hash\n */\n deleteFileHash(file) {\n if (!this.db) return;\n \n const stmt = this.db.prepare('DELETE FROM file_hashes WHERE file = ?');\n stmt.run(file);\n }\n\n /**\n * Get all file hashes as Map\n */\n getAllFileHashes() {\n if (!this.db) return new Map();\n \n const stmt = this.db.prepare('SELECT file, hash FROM file_hashes');\n const rows = stmt.all();\n return new Map(rows.map(row => [row.file, row.hash]));\n }\n\n /**\n * Save (checkpoint WAL for durability)\n * With SQLite, writes are already persisted, this just checkpoints the WAL\n */\n async save() {\n if (!this.config.enableCache || !this.db) return;\n \n this.isSaving = true;\n \n try {\n // Checkpoint WAL to ensure durability\n this.db.pragma('wal_checkpoint(PASSIVE)');\n } catch (error) {\n console.error('[Cache] Failed to checkpoint WAL:', error.message);\n } finally {\n this.isSaving = false;\n }\n }\n\n /**\n * Incremental save during indexing (no-op for SQLite, already persisted)\n */\n async saveIncremental() {\n // SQLite writes are already persisted due to WAL mode\n // This is a no-op but kept for API compatibility\n return;\n }\n\n /**\n * Clear all cache data\n */\n async clear() {\n if (!this.config.enableCache) return;\n \n try {\n if (this.db) {\n this.db.close();\n this.db = null;\n }\n \n await fs.rm(this.config.cacheDirectory, { recursive: true, force: true });\n console.error(`[Cache] Cache cleared successfully: ${this.config.cacheDirectory}`);\n } catch (error) {\n console.error('[Cache] Failed to clear cache:', error.message);\n throw error;\n }\n }\n\n /**\n * Close database connection\n */\n close() {\n if (this.db) {\n this.db.close();\n this.db = null;\n }\n }\n\n /**\n * Clear all file hashes from the database\n */\n clearAllFileHashes() {\n if (!this.db) return;\n this.db.exec('DELETE FROM file_hashes');\n }\n\n /**\n * Reset vectors + file hashes for full reindex.\n */\n async resetForFullReindex() {\n this.setVectorStore([]);\n this.clearAllFileHashes();\n }\n\n /**\n * Set vector store (for compatibility with test code)\n * This is less efficient than batch operations but kept for compatibility\n */\n setVectorStore(store) {\n if (!this.db) return;\n \n // Clear existing data\n this.db.exec('DELETE FROM embeddings');\n \n // Insert new data\n if (store.length > 0) {\n this.addBatchToStore(store);\n }\n }\n}\n",
1503
+ "vector": [
1504
+ 0.17610858380794525,
1505
+ 0.08675367385149002,
1506
+ -0.29814496636390686,
1507
+ -0.11028185486793518,
1508
+ 0.14830884337425232,
1509
+ -0.10776294022798538,
1510
+ 0.021966109052300453,
1511
+ 0.09552248567342758,
1512
+ 0.027391066774725914,
1513
+ -0.10248623788356781,
1514
+ -0.11495758593082428,
1515
+ 0.0067843724973499775,
1516
+ 0.3021169900894165,
1517
+ 0.05543402209877968,
1518
+ 0.06311436742544174,
1519
+ -0.012146751396358013,
1520
+ -0.06133873388171196,
1521
+ 0.0018890094943344593,
1522
+ 0.1608002781867981,
1523
+ 0.11390095949172974,
1524
+ 0.07120368629693985,
1525
+ -0.18890145421028137,
1526
+ -0.012094917707145214,
1527
+ -0.03566117212176323,
1528
+ 0.11330465227365494,
1529
+ 0.07282066345214844,
1530
+ -0.13154762983322144,
1531
+ 0.022991767153143883,
1532
+ -0.08065801113843918,
1533
+ 0.054186053574085236,
1534
+ 0.0066047846339643,
1535
+ 0.06726238876581192,
1536
+ 0.043001867830753326,
1537
+ -0.04456934332847595,
1538
+ -0.11054425686597824,
1539
+ -0.025585133582353592,
1540
+ 0.011343184858560562,
1541
+ -0.06332966685295105,
1542
+ 0.007712243590503931,
1543
+ -0.051856640726327896,
1544
+ 0.00561432633548975,
1545
+ 0.035718489438295364,
1546
+ -0.0014126349706202745,
1547
+ -0.1918007880449295,
1548
+ 0.03793348744511604,
1549
+ 0.014814043417572975,
1550
+ 0.13686007261276245,
1551
+ 0.0290600024163723,
1552
+ 0.03151417896151543,
1553
+ -0.08784613013267517,
1554
+ -0.0034513939172029495,
1555
+ -0.037083808332681656,
1556
+ -0.023131633177399635,
1557
+ -0.05394306406378746,
1558
+ 0.12118890136480331,
1559
+ 0.11685580760240555,
1560
+ -0.024567201733589172,
1561
+ 0.07999919354915619,
1562
+ 0.07942410558462143,
1563
+ 0.001491001807153225,
1564
+ 0.1448618620634079,
1565
+ -0.017583545297384262,
1566
+ 0.00983371026813984,
1567
+ 0.13380281627178192,
1568
+ 0.09834379702806473,
1569
+ -0.044932831078767776,
1570
+ -0.04699728265404701,
1571
+ 0.20115654170513153,
1572
+ 0.016782015562057495,
1573
+ 0.08905328810214996,
1574
+ 0.1162048876285553,
1575
+ 0.03938957303762436,
1576
+ 0.012751343660056591,
1577
+ 0.09438503533601761,
1578
+ -0.06176295131444931,
1579
+ 0.12341069430112839,
1580
+ -0.029215877875685692,
1581
+ -0.10936563462018967,
1582
+ -0.0694982185959816,
1583
+ 0.03788724169135094,
1584
+ -0.013497307896614075,
1585
+ -0.05914925038814545,
1586
+ 0.06552232801914215,
1587
+ -0.03996057063341141,
1588
+ 0.01005631498992443,
1589
+ -0.10869783163070679,
1590
+ -0.07618915289640427,
1591
+ -0.08624150604009628,
1592
+ 0.051234375685453415,
1593
+ 0.06740886718034744,
1594
+ 0.09788616001605988,
1595
+ 0.010133369825780392,
1596
+ 0.034359805285930634,
1597
+ 0.10098715126514435,
1598
+ -0.06616494059562683,
1599
+ -0.014049929566681385,
1600
+ -0.05254490301012993,
1601
+ -0.022937176749110222,
1602
+ -0.18541133403778076,
1603
+ -0.04772138223052025,
1604
+ -0.025385215878486633,
1605
+ -0.06405352801084518,
1606
+ 0.11816075444221497,
1607
+ -0.04668682813644409,
1608
+ 0.12182901054620743,
1609
+ 0.04632604867219925,
1610
+ 0.07841562479734421,
1611
+ 0.05123784393072128,
1612
+ 0.02592640370130539,
1613
+ 0.03866131603717804,
1614
+ -0.044982198625802994,
1615
+ 0.04324060305953026,
1616
+ -0.0837235227227211,
1617
+ -0.15989771485328674,
1618
+ -0.056984707713127136,
1619
+ -0.03959043696522713,
1620
+ 0.12962022423744202,
1621
+ -0.08965958654880524,
1622
+ -0.026452546939253807,
1623
+ 0.010734747163951397,
1624
+ -0.028782030567526817,
1625
+ -0.13392356038093567,
1626
+ -0.07339756190776825,
1627
+ 0.05292747914791107,
1628
+ 0.019030924886465073,
1629
+ 0.002080140635371208,
1630
+ -0.05071667954325676,
1631
+ -0.06380479037761688
1632
+ ]
1633
+ },
1634
+ {
1635
+ "file": "/Users/jun/Library/Mobile Documents/iCloud~md~obsidian/Documents/new/700_projects/smart-coding-mcp/lib/milvus-cache.js",
1636
+ "startLine": 1,
1637
+ "endLine": 478,
1638
+ "content": "import fs from \"fs/promises\";\nimport path from \"path\";\nimport { DataType, MetricType, MilvusClient } from \"@zilliz/milvus2-sdk-node\";\n\nconst DEFAULT_COLLECTION = \"smart_coding_embeddings\";\nconst DEFAULT_MAX_CONTENT_LENGTH = 65535;\n\nfunction escapeFilterString(value) {\n return String(value).replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"');\n}\n\nfunction parseIntSafe(value, fallback = 0) {\n const parsed = Number.parseInt(String(value), 10);\n return Number.isNaN(parsed) ? fallback : parsed;\n}\n\nexport class MilvusCache {\n constructor(config) {\n this.config = config;\n this.client = null;\n this.vectorStore = [];\n this.fileHashes = new Map();\n this.isSaving = false;\n this.hashesDirty = false;\n this.lastWriteError = null;\n this.writeQueue = Promise.resolve();\n this.hashPath = path.join(config.cacheDirectory, \"file-hashes.json\");\n this.collectionName = config.milvusCollection || DEFAULT_COLLECTION;\n this.dimension = this.resolveDimension(config);\n }\n\n resolveDimension(config) {\n const provider = (config.embeddingProvider || \"local\").toLowerCase();\n const candidate =\n provider === \"gemini\" ? config.geminiDimensions : config.embeddingDimension;\n const dim = Number(candidate);\n if (Number.isInteger(dim) && dim > 0) return dim;\n return provider === \"gemini\" ? 768 : 128;\n }\n\n getRequestBase() {\n const base = {};\n if (this.config.milvusDatabase) {\n base.db_name = this.config.milvusDatabase;\n }\n return base;\n }\n\n enqueueWrite(task, label = \"write\") {\n this.writeQueue = this.writeQueue\n .then(async () => {\n await task();\n })\n .catch((error) => {\n this.lastWriteError = error;\n console.error(`[Cache] Milvus ${label} failed: ${error.message}`);\n });\n return this.writeQueue;\n }\n\n async waitForWrites() {\n await this.writeQueue;\n if (this.lastWriteError) {\n const error = this.lastWriteError;\n this.lastWriteError = null;\n throw error;\n }\n }\n\n validateConfig() {\n const address = this.config?.milvusAddress?.trim();\n if (!address) {\n throw new Error(\n \"[Cache] Milvus provider selected but SMART_CODING_MILVUS_ADDRESS is not set.\"\n );\n }\n return address;\n }\n\n async load() {\n if (!this.config.enableCache) return;\n\n this.validateConfig();\n this.dimension = this.resolveDimension(this.config);\n this.collectionName = this.config.milvusCollection || DEFAULT_COLLECTION;\n this.hashPath = path.join(this.config.cacheDirectory, \"file-hashes.json\");\n\n await fs.mkdir(this.config.cacheDirectory, { recursive: true });\n await this.loadFileHashes();\n\n this.client = new MilvusClient({\n address: this.config.milvusAddress,\n token: this.config.milvusToken || undefined,\n database: this.config.milvusDatabase || undefined\n });\n\n await this.ensureCollection();\n // ANN mode: keep local vector store empty and delegate retrieval to Milvus search API.\n this.vectorStore = [];\n const stats = await this.getStats();\n\n console.error(\n `[Cache] Loaded Milvus cache (ANN mode): ${stats.totalChunks} embeddings from ${stats.totalFiles} files`\n );\n }\n\n async ensureCollection() {\n const hasRes = await this.client.hasCollection({\n collection_name: this.collectionName,\n ...this.getRequestBase()\n });\n const exists = Boolean(hasRes?.value);\n\n if (!exists) {\n await this.client.createCollection({\n collection_name: this.collectionName,\n description: \"smart-coding-mcp embeddings cache\",\n fields: [\n {\n name: \"id\",\n data_type: DataType.Int64,\n is_primary_key: true,\n autoID: true\n },\n {\n name: \"file\",\n data_type: DataType.VarChar,\n max_length: 4096\n },\n {\n name: \"start_line\",\n data_type: DataType.Int64\n },\n {\n name: \"end_line\",\n data_type: DataType.Int64\n },\n {\n name: \"content\",\n data_type: DataType.VarChar,\n max_length: DEFAULT_MAX_CONTENT_LENGTH\n },\n {\n name: \"vector\",\n data_type: DataType.FloatVector,\n dim: this.dimension\n }\n ],\n enable_dynamic_field: false,\n ...this.getRequestBase()\n });\n\n try {\n await this.client.createIndex({\n collection_name: this.collectionName,\n field_name: \"vector\",\n index_type: \"AUTOINDEX\",\n metric_type: MetricType.COSINE,\n ...this.getRequestBase()\n });\n } catch (error) {\n console.error(`[Cache] Milvus index create warning: ${error.message}`);\n }\n }\n\n await this.client.loadCollection({\n collection_name: this.collectionName,\n ...this.getRequestBase()\n });\n }\n\n async searchByVector(queryVector, topK = 10, filter = null) {\n if (!this.client) {\n throw new Error(\"[Cache] Milvus client not initialized\");\n }\n\n const vector = Array.isArray(queryVector)\n ? queryVector\n : Array.from(queryVector || []);\n if (vector.length !== this.dimension) {\n throw new Error(\n `[Cache] Query vector dimension mismatch: expected ${this.dimension}, got ${vector.length}`\n );\n }\n\n const normalizedTopK = Number.isInteger(topK) && topK > 0 ? topK : 10;\n const searchParams = {\n collection_name: this.collectionName,\n anns_field: \"vector\",\n data: vector,\n output_fields: [\"file\", \"start_line\", \"end_line\", \"content\"],\n limit: normalizedTopK,\n metric_type: \"COSINE\",\n ...this.getRequestBase()\n };\n\n if (typeof filter === \"string\" && filter.trim()) {\n searchParams.filter = filter.trim();\n }\n\n const res = await this.client.search(searchParams);\n const rawResults = Array.isArray(res?.results) ? res.results : [];\n const rows = Array.isArray(rawResults[0]) ? rawResults[0] : rawResults;\n\n return rows\n .filter((row) => row && row.file)\n .map((row) => ({\n file: String(row.file),\n startLine: parseIntSafe(row.start_line, 0),\n endLine: parseIntSafe(row.end_line, 0),\n content: String(row.content || \"\"),\n score: Number(row.score || 0)\n }));\n }\n\n async getStats() {\n if (!this.client) {\n return { totalChunks: this.vectorStore.length, totalFiles: this.fileHashes.size };\n }\n\n let totalChunks = 0;\n\n // Prefer explicit count query when available (more reliable during ANN mode).\n try {\n const countRes = await this.client.count({\n collection_name: this.collectionName,\n expr: \"id >= 0\",\n ...this.getRequestBase()\n });\n totalChunks = Number(countRes?.data || 0);\n } catch {\n // Fallback to collection statistics below.\n }\n\n if (!Number.isFinite(totalChunks) || totalChunks < 0) {\n totalChunks = 0;\n }\n\n if (totalChunks === 0) {\n const stats = await this.client.getCollectionStatistics({\n collection_name: this.collectionName,\n ...this.getRequestBase()\n });\n const statsList = Array.isArray(stats?.stats) ? stats.stats : [];\n const rowCountFromList = statsList.find((item) => item?.key === \"row_count\")?.value;\n totalChunks = parseIntSafe(stats?.data?.row_count ?? rowCountFromList, 0);\n }\n\n return {\n totalChunks,\n totalFiles: this.fileHashes.size\n };\n }\n\n normalizeChunk(chunk) {\n if (!chunk || !chunk.file) return null;\n\n const rawVector = Array.isArray(chunk.vector)\n ? chunk.vector\n : Array.from(chunk.vector || []);\n if (rawVector.length !== this.dimension) {\n return null;\n }\n\n return {\n file: String(chunk.file),\n start_line: parseIntSafe(chunk.startLine, 0),\n end_line: parseIntSafe(chunk.endLine, 0),\n content: String(chunk.content || \"\").slice(0, DEFAULT_MAX_CONTENT_LENGTH),\n vector: rawVector.map((value) => Number(value))\n };\n }\n\n getVectorStore() {\n return this.vectorStore;\n }\n\n setVectorStore(store) {\n const normalizedStore = [];\n for (const chunk of Array.isArray(store) ? store : []) {\n const normalized = this.normalizeChunk(chunk);\n if (!normalized) continue;\n normalizedStore.push({\n file: normalized.file,\n startLine: normalized.start_line,\n endLine: normalized.end_line,\n content: normalized.content,\n vector: normalized.vector\n });\n }\n\n this.vectorStore = normalizedStore;\n\n this.enqueueWrite(async () => {\n if (!this.client) return;\n await this.client.delete({\n collection_name: this.collectionName,\n filter: \"id >= 0\",\n ...this.getRequestBase()\n });\n\n if (normalizedStore.length === 0) return;\n await this.client.insert({\n collection_name: this.collectionName,\n data: normalizedStore.map((row) => ({\n file: row.file,\n start_line: row.startLine,\n end_line: row.endLine,\n content: row.content,\n vector: row.vector\n })),\n ...this.getRequestBase()\n });\n }, \"setVectorStore\");\n }\n\n addToStore(chunk) {\n this.addBatchToStore([chunk]);\n }\n\n addBatchToStore(chunks) {\n if (!Array.isArray(chunks) || chunks.length === 0) return;\n\n const normalizedBatch = [];\n const localBatch = [];\n\n for (const chunk of chunks) {\n const normalized = this.normalizeChunk(chunk);\n if (!normalized) continue;\n normalizedBatch.push(normalized);\n localBatch.push({\n file: normalized.file,\n startLine: normalized.start_line,\n endLine: normalized.end_line,\n content: normalized.content,\n vector: normalized.vector\n });\n }\n\n if (normalizedBatch.length === 0) return;\n if (!this.client) {\n this.vectorStore.push(...localBatch);\n }\n\n this.enqueueWrite(async () => {\n if (!this.client) return;\n await this.client.insert({\n collection_name: this.collectionName,\n data: normalizedBatch,\n ...this.getRequestBase()\n });\n }, \"insert\");\n }\n\n removeFileFromStore(file) {\n this.vectorStore = this.vectorStore.filter((chunk) => chunk.file !== file);\n\n const escaped = escapeFilterString(file);\n this.enqueueWrite(async () => {\n if (!this.client) return;\n await this.client.delete({\n collection_name: this.collectionName,\n filter: `file == \"${escaped}\"`,\n ...this.getRequestBase()\n });\n }, \"deleteByFile\");\n }\n\n getFileHash(file) {\n const entry = this.fileHashes.get(file);\n if (typeof entry === \"string\") return entry;\n return entry?.hash || null;\n }\n\n getFileMtime(file) {\n const entry = this.fileHashes.get(file);\n return entry?.mtime ?? null;\n }\n\n setFileHash(file, hash, mtime = null) {\n this.fileHashes.set(file, { hash, mtime });\n this.hashesDirty = true;\n }\n\n deleteFileHash(file) {\n this.fileHashes.delete(file);\n this.hashesDirty = true;\n }\n\n getAllFileHashes() {\n return this.fileHashes;\n }\n\n clearAllFileHashes() {\n this.fileHashes = new Map();\n this.hashesDirty = true;\n }\n\n async loadFileHashes() {\n try {\n const raw = await fs.readFile(this.hashPath, \"utf-8\");\n const parsed = JSON.parse(raw);\n this.fileHashes = new Map(Object.entries(parsed || {}));\n this.hashesDirty = false;\n } catch {\n this.fileHashes = new Map();\n this.hashesDirty = false;\n }\n }\n\n async saveFileHashes() {\n if (!this.hashesDirty) return;\n await fs.mkdir(this.config.cacheDirectory, { recursive: true });\n await fs.writeFile(\n this.hashPath,\n JSON.stringify(Object.fromEntries(this.fileHashes), null, 2),\n \"utf-8\"\n );\n this.hashesDirty = false;\n }\n\n async save() {\n if (!this.config.enableCache) return;\n\n this.isSaving = true;\n try {\n await this.waitForWrites();\n if (this.client) {\n await this.client.flush({\n collection_names: [this.collectionName],\n ...this.getRequestBase()\n });\n }\n await this.saveFileHashes();\n } finally {\n this.isSaving = false;\n }\n }\n\n async saveIncremental() {\n if (!this.config.enableCache) return;\n await this.waitForWrites();\n await this.saveFileHashes();\n }\n\n async resetForFullReindex() {\n this.setVectorStore([]);\n this.clearAllFileHashes();\n await this.save();\n }\n\n async clear() {\n if (!this.config.enableCache) return;\n\n await this.waitForWrites();\n\n if (this.client) {\n try {\n await this.client.dropCollection({\n collection_name: this.collectionName,\n ...this.getRequestBase()\n });\n } catch (error) {\n console.error(`[Cache] Milvus drop collection warning: ${error.message}`);\n }\n\n await this.ensureCollection();\n }\n\n this.vectorStore = [];\n this.fileHashes = new Map();\n this.hashesDirty = true;\n await this.saveFileHashes();\n\n console.error(`[Cache] Milvus cache cleared successfully: ${this.collectionName}`);\n }\n}\n",
1639
+ "vector": [
1640
+ 0.13730067014694214,
1641
+ 0.0886315405368805,
1642
+ -0.2946660816669464,
1643
+ -0.15541239082813263,
1644
+ 0.09881986677646637,
1645
+ -0.08915340900421143,
1646
+ 0.07476003468036652,
1647
+ 0.06224866211414337,
1648
+ 0.06921052187681198,
1649
+ -0.12051547318696976,
1650
+ -0.08415821939706802,
1651
+ -0.005875017028301954,
1652
+ 0.32263797521591187,
1653
+ 0.045938387513160706,
1654
+ 0.10372753441333771,
1655
+ 0.024883966892957687,
1656
+ -0.1007300391793251,
1657
+ -0.060748081654310226,
1658
+ 0.1200689971446991,
1659
+ 0.04942591115832329,
1660
+ 0.04785097390413284,
1661
+ -0.14556853473186493,
1662
+ 0.012146000750362873,
1663
+ -0.06192529574036598,
1664
+ 0.13202759623527527,
1665
+ 0.09306637942790985,
1666
+ -0.014222914353013039,
1667
+ 0.056986939162015915,
1668
+ -0.14565542340278625,
1669
+ -0.027135826647281647,
1670
+ -0.008461633697152138,
1671
+ 0.032501813024282455,
1672
+ 0.032581690698862076,
1673
+ -0.019549226388335228,
1674
+ -0.11350204795598984,
1675
+ -0.09357208758592606,
1676
+ 0.047342509031295776,
1677
+ -0.044372908771038055,
1678
+ 0.0229358971118927,
1679
+ -0.008516100235283375,
1680
+ -0.007043263874948025,
1681
+ 0.013743036426603794,
1682
+ -0.00000825735605758382,
1683
+ -0.0929207131266594,
1684
+ 0.02137291245162487,
1685
+ 0.033891793340444565,
1686
+ 0.05832526460289955,
1687
+ 0.13524016737937927,
1688
+ -0.008499512448906898,
1689
+ -0.0766170397400856,
1690
+ 0.09153563529253006,
1691
+ -0.014937731437385082,
1692
+ -0.03540649265050888,
1693
+ -0.030856814235448837,
1694
+ 0.1323801726102829,
1695
+ 0.1020880714058876,
1696
+ -0.019435759633779526,
1697
+ 0.0952984020113945,
1698
+ 0.11818593740463257,
1699
+ 0.06669147312641144,
1700
+ 0.13083818554878235,
1701
+ 0.035766229033470154,
1702
+ -0.04056733846664429,
1703
+ 0.11579257994890213,
1704
+ 0.05650673806667328,
1705
+ -0.04523031786084175,
1706
+ -0.05001084506511688,
1707
+ 0.15003134310245514,
1708
+ 0.005568407475948334,
1709
+ 0.04851573333144188,
1710
+ 0.09552208334207535,
1711
+ 0.005469136405736208,
1712
+ 0.07085748016834259,
1713
+ 0.025132019072771072,
1714
+ -0.08381233364343643,
1715
+ 0.11367248743772507,
1716
+ 0.004667581059038639,
1717
+ -0.09449846297502518,
1718
+ -0.07671719789505005,
1719
+ 0.053895238786935806,
1720
+ -0.03966321051120758,
1721
+ -0.10247503966093063,
1722
+ 0.06246865168213844,
1723
+ -0.014244350604712963,
1724
+ 0.06421836465597153,
1725
+ -0.12740010023117065,
1726
+ -0.08810196816921234,
1727
+ -0.09704052656888962,
1728
+ 0.003204687964171171,
1729
+ 0.09522006660699844,
1730
+ 0.14317747950553894,
1731
+ -0.0016872065607458353,
1732
+ 0.06401775777339935,
1733
+ 0.09720179438591003,
1734
+ -0.11257115751504898,
1735
+ -0.03299164026975632,
1736
+ -0.05938786640763283,
1737
+ 0.01328890211880207,
1738
+ -0.181989848613739,
1739
+ 0.0009019780554808676,
1740
+ -0.01923346146941185,
1741
+ -0.06535537540912628,
1742
+ 0.1452878713607788,
1743
+ -0.10370767116546631,
1744
+ 0.128138929605484,
1745
+ 0.10258589684963226,
1746
+ 0.05736957862973213,
1747
+ 0.031863998621702194,
1748
+ 0.0478055402636528,
1749
+ -0.002627595793455839,
1750
+ -0.020494522526860237,
1751
+ 0.08770152926445007,
1752
+ -0.08293313533067703,
1753
+ -0.13855400681495667,
1754
+ -0.04442203789949417,
1755
+ -0.06460627168416977,
1756
+ 0.10637567937374115,
1757
+ -0.07216741144657135,
1758
+ -0.07834934443235397,
1759
+ 0.0190973449498415,
1760
+ -0.033708974719047546,
1761
+ -0.17721594870090485,
1762
+ -0.08825916796922684,
1763
+ 0.07684992253780365,
1764
+ 0.03547850623726845,
1765
+ 0.013023985549807549,
1766
+ 0.02427751012146473,
1767
+ 0.006025256123393774
1768
+ ]
1769
+ },
1770
+ {
1771
+ "file": "/Users/jun/Library/Mobile Documents/iCloud~md~obsidian/Documents/new/700_projects/smart-coding-mcp/config.json",
1772
+ "startLine": 1,
1773
+ "endLine": 85,
1774
+ "content": "{\n \"searchDirectory\": \".\",\n \"fileExtensions\": [\n \"js\",\n \"ts\",\n \"jsx\",\n \"tsx\",\n \"mjs\",\n \"cjs\",\n \"css\",\n \"scss\",\n \"sass\",\n \"less\",\n \"html\",\n \"htm\",\n \"xml\",\n \"svg\",\n \"py\",\n \"pyw\",\n \"java\",\n \"kt\",\n \"scala\",\n \"c\",\n \"cpp\",\n \"h\",\n \"hpp\",\n \"cs\",\n \"go\",\n \"rs\",\n \"rb\",\n \"php\",\n \"swift\",\n \"sh\",\n \"bash\",\n \"json\",\n \"yaml\",\n \"yml\",\n \"toml\",\n \"sql\"\n ],\n \"excludePatterns\": [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/build/**\",\n \"**/.git/**\",\n \"**/coverage/**\",\n \"**/.next/**\",\n \"**/target/**\",\n \"**/vendor/**\",\n \"**/.smart-coding-cache/**\",\n \"**/*.rdb\",\n \"**/.venv/**\",\n \"**/venv/**\",\n \"**/__pycache__/**\",\n \"**/_legacy/**\"\n ],\n \"smartIndexing\": true,\n \"chunkSize\": 15,\n \"chunkOverlap\": 3,\n \"batchSize\": 100,\n \"maxFileSize\": 1048576,\n \"maxResults\": 3,\n \"enableCache\": true,\n \"cacheDirectory\": \"./.smart-coding-cache\",\n \"watchFiles\": false,\n \"verbose\": false,\n \"embeddingProvider\": \"local\",\n \"embeddingModel\": \"nomic-ai/nomic-embed-text-v1.5\",\n \"embeddingDimension\": 128,\n \"device\": \"auto\",\n \"geminiModel\": \"gemini-embedding-001\",\n \"geminiBaseURL\": \"https://generativelanguage.googleapis.com/v1beta/openai\",\n \"geminiDimensions\": 768,\n \"geminiBatchSize\": 24,\n \"geminiBatchFlushMs\": 12,\n \"geminiMaxRetries\": 3,\n \"chunkingMode\": \"smart\",\n \"semanticWeight\": 0.7,\n \"exactMatchBoost\": 1.5,\n \"workerThreads\": \"auto\",\n \"maxCpuPercent\": 50,\n \"batchDelay\": 100,\n \"autoIndexDelay\": 5000\n}\n",
1775
+ "vector": [
1776
+ 0.0603330172598362,
1777
+ 0.1846204251050949,
1778
+ -0.2514520585536957,
1779
+ -0.11584358662366867,
1780
+ 0.08839605003595352,
1781
+ -0.1471397429704666,
1782
+ -0.028772257268428802,
1783
+ -0.012816404923796654,
1784
+ -0.10743117332458496,
1785
+ -0.2087891697883606,
1786
+ 0.014974342659115791,
1787
+ -0.003422962035983801,
1788
+ 0.21311689913272858,
1789
+ 0.045558806508779526,
1790
+ 0.02935049869120121,
1791
+ -0.05148829519748688,
1792
+ -0.025105876848101616,
1793
+ -0.054230064153671265,
1794
+ 0.03168415650725365,
1795
+ 0.05745445191860199,
1796
+ -0.08026353269815445,
1797
+ -0.21820741891860962,
1798
+ 0.0352071113884449,
1799
+ 0.01294973399490118,
1800
+ 0.11762282997369766,
1801
+ 0.0751737505197525,
1802
+ 0.011288456618785858,
1803
+ 0.0049444399774074554,
1804
+ -0.14640985429286957,
1805
+ 0.017393706366419792,
1806
+ 0.0041992333717644215,
1807
+ -0.029669860377907753,
1808
+ 0.018895253539085388,
1809
+ -0.050238244235515594,
1810
+ 0.00351736880838871,
1811
+ -0.04921988770365715,
1812
+ 0.13100449740886688,
1813
+ 0.031226016581058502,
1814
+ -0.040821805596351624,
1815
+ 0.13341876864433289,
1816
+ 0.04806447774171829,
1817
+ -0.076088085770607,
1818
+ 0.023452818393707275,
1819
+ -0.12387261539697647,
1820
+ -0.004748850595206022,
1821
+ -0.14466074109077454,
1822
+ 0.06267848610877991,
1823
+ -0.006985949352383614,
1824
+ 0.2379857450723648,
1825
+ -0.1984974443912506,
1826
+ -0.008539712056517601,
1827
+ 0.0019480790942907333,
1828
+ -0.0001929566205944866,
1829
+ 0.01902610994875431,
1830
+ 0.04497227817773819,
1831
+ -0.0209675133228302,
1832
+ -0.0672336220741272,
1833
+ 0.02947383187711239,
1834
+ 0.058193791657686234,
1835
+ -0.05984310060739517,
1836
+ 0.16839247941970825,
1837
+ 0.17515327036380768,
1838
+ -0.008634692057967186,
1839
+ 0.12084219604730606,
1840
+ 0.07414598017930984,
1841
+ -0.019761094823479652,
1842
+ -0.11083662509918213,
1843
+ 0.04899929091334343,
1844
+ 0.0002529654011595994,
1845
+ -0.08616728335618973,
1846
+ 0.07907871901988983,
1847
+ 0.018424158915877342,
1848
+ 0.05477144941687584,
1849
+ 0.09619136154651642,
1850
+ -0.1724243462085724,
1851
+ -0.05693015828728676,
1852
+ -0.021189961582422256,
1853
+ -0.07573829591274261,
1854
+ -0.06283469498157501,
1855
+ 0.12929977476596832,
1856
+ 0.05627327784895897,
1857
+ 0.03339430317282677,
1858
+ 0.1293465942144394,
1859
+ -0.11251930892467499,
1860
+ 0.07408392429351807,
1861
+ -0.02022681012749672,
1862
+ -0.08744575083255768,
1863
+ 0.054768022149801254,
1864
+ -0.0010127174900844693,
1865
+ 0.09935358166694641,
1866
+ -0.061266135424375534,
1867
+ -0.03910521790385246,
1868
+ 0.03878246992826462,
1869
+ 0.09091518074274063,
1870
+ -0.10156004130840302,
1871
+ 0.10237753391265869,
1872
+ 0.0907038226723671,
1873
+ 0.013779880478978157,
1874
+ -0.09509070962667465,
1875
+ -0.11496849358081818,
1876
+ -0.07420370727777481,
1877
+ 0.04315663129091263,
1878
+ 0.019069354981184006,
1879
+ 0.009062942117452621,
1880
+ 0.06866662204265594,
1881
+ 0.06299661099910736,
1882
+ 0.05823444575071335,
1883
+ 0.05318794399499893,
1884
+ 0.028024349361658096,
1885
+ -0.046276211738586426,
1886
+ -0.05895373225212097,
1887
+ 0.06562865525484085,
1888
+ -0.05201137810945511,
1889
+ -0.06559871137142181,
1890
+ 0.0026368459220975637,
1891
+ -0.07608383893966675,
1892
+ 0.21425719559192657,
1893
+ -0.03820258006453514,
1894
+ -0.005087343044579029,
1895
+ 0.13822084665298462,
1896
+ -0.011009148322045803,
1897
+ -0.006588975433260202,
1898
+ 0.024484509602189064,
1899
+ 0.03787181153893471,
1900
+ 0.08359185606241226,
1901
+ 0.035756804049015045,
1902
+ -0.08237508684396744,
1903
+ -0.05541232228279114
1904
+ ]
1905
+ },
1906
+ {
1907
+ "file": "/Users/jun/Library/Mobile Documents/iCloud~md~obsidian/Documents/new/700_projects/smart-coding-mcp/lib/cache-factory.js",
1908
+ "startLine": 1,
1909
+ "endLine": 14,
1910
+ "content": "import { SQLiteCache } from \"./sqlite-cache.js\";\nimport { MilvusCache } from \"./milvus-cache.js\";\n\nexport function createCache(config) {\n // Keep cache provider selection centralized so index/search paths stay consistent.\n const provider = (config?.vectorStoreProvider || \"sqlite\").toLowerCase();\n\n if (provider === \"milvus\") {\n return new MilvusCache(config);\n }\n\n return new SQLiteCache(config);\n}\n",
1911
+ "vector": [
1912
+ -0.01082774717360735,
1913
+ 0.06626889854669571,
1914
+ -0.25161662697792053,
1915
+ -0.14933012425899506,
1916
+ 0.02861740067601204,
1917
+ -0.1544455736875534,
1918
+ -0.007243073545396328,
1919
+ 0.008536187931895256,
1920
+ 0.025026414543390274,
1921
+ -0.009299647994339466,
1922
+ -0.19999082386493683,
1923
+ 0.06592264026403427,
1924
+ 0.3004385232925415,
1925
+ -0.08029153198003769,
1926
+ 0.0006805066950619221,
1927
+ 0.05567292869091034,
1928
+ -0.020779699087142944,
1929
+ -0.07084246724843979,
1930
+ 0.09110759943723679,
1931
+ 0.0916912779211998,
1932
+ -0.055001355707645416,
1933
+ -0.19250699877738953,
1934
+ 0.10785271227359772,
1935
+ -0.07833565026521683,
1936
+ 0.1703566163778305,
1937
+ 0.09782149642705917,
1938
+ -0.0017037119250744581,
1939
+ 0.0919932872056961,
1940
+ -0.04832227900624275,
1941
+ 0.1334947645664215,
1942
+ 0.08876121789216995,
1943
+ 0.10866264253854752,
1944
+ -0.02278382144868374,
1945
+ 0.009724610485136509,
1946
+ -0.0303071066737175,
1947
+ -0.02324957773089409,
1948
+ -0.09390660375356674,
1949
+ -0.06646591424942017,
1950
+ -0.14036761224269867,
1951
+ 0.012252203188836575,
1952
+ -0.0033730845898389816,
1953
+ 0.10017817467451096,
1954
+ -0.06169886887073517,
1955
+ -0.09166969358921051,
1956
+ 0.12208543717861176,
1957
+ 0.011162432841956615,
1958
+ 0.13410566747188568,
1959
+ -0.01501054409891367,
1960
+ 0.1743764579296112,
1961
+ -0.1087699756026268,
1962
+ 0.01388153713196516,
1963
+ -0.014463160187005997,
1964
+ 0.012904026545584202,
1965
+ -0.009706496261060238,
1966
+ 0.11620201170444489,
1967
+ 0.02519969642162323,
1968
+ -0.037128105759620667,
1969
+ 0.06247721239924431,
1970
+ 0.10469167679548264,
1971
+ -0.1951899230480194,
1972
+ 0.09338898211717606,
1973
+ 0.09480993449687958,
1974
+ 0.05138218030333519,
1975
+ 0.15334652364253998,
1976
+ 0.06573499739170074,
1977
+ -0.06077475845813751,
1978
+ -0.06784002482891083,
1979
+ 0.1608954668045044,
1980
+ 0.005502506624907255,
1981
+ 0.058712515980005264,
1982
+ 0.03642481192946434,
1983
+ 0.08874233067035675,
1984
+ 0.03946305811405182,
1985
+ 0.06853572279214859,
1986
+ -0.1556568294763565,
1987
+ 0.03366970643401146,
1988
+ -0.05804193764925003,
1989
+ 0.0002829217119142413,
1990
+ -0.08770260214805603,
1991
+ 0.029635179787874222,
1992
+ -0.009421763941645622,
1993
+ 0.057846181094646454,
1994
+ 0.04504675418138504,
1995
+ -0.1504567712545395,
1996
+ -0.0016108067939057946,
1997
+ 0.03380119800567627,
1998
+ -0.030443191528320312,
1999
+ -0.02301703952252865,
2000
+ -0.08597756922245026,
2001
+ 0.0950860008597374,
2002
+ -0.007588767446577549,
2003
+ 0.01054852269589901,
2004
+ 0.028994590044021606,
2005
+ 0.14494602382183075,
2006
+ -0.14615988731384277,
2007
+ 0.11469011753797531,
2008
+ 0.09009284526109695,
2009
+ 0.02348962612450123,
2010
+ -0.0903531163930893,
2011
+ -0.11819816380739212,
2012
+ 0.04314788430929184,
2013
+ -0.07442238181829453,
2014
+ 0.03791282698512077,
2015
+ 0.040610987693071365,
2016
+ 0.0014731530100107193,
2017
+ 0.05659887194633484,
2018
+ -0.05055712163448334,
2019
+ 0.013861597515642643,
2020
+ 0.005268569104373455,
2021
+ -0.027832472696900368,
2022
+ -0.022025156766176224,
2023
+ 0.05062246695160866,
2024
+ -0.10352011024951935,
2025
+ -0.06317321211099625,
2026
+ -0.02375372312963009,
2027
+ -0.05419987067580223,
2028
+ 0.07974203675985336,
2029
+ -0.013643726706504822,
2030
+ 0.05506013333797455,
2031
+ 0.08168993890285492,
2032
+ -0.051069870591163635,
2033
+ -0.06602615118026733,
2034
+ -0.08244229108095169,
2035
+ 0.0025310267228633165,
2036
+ 0.1428195983171463,
2037
+ 0.024636760354042053,
2038
+ -0.08813566714525223,
2039
+ 0.029948227107524872
2040
+ ]
2041
+ },
2042
+ {
2043
+ "file": "/Users/jun/Library/Mobile Documents/iCloud~md~obsidian/Documents/new/700_projects/smart-coding-mcp/test/gemini-embedder.test.js",
2044
+ "startLine": 1,
2045
+ "endLine": 274,
2046
+ "content": "/**\n * Tests for Gemini embedder (OpenAI-compatible endpoint wrapper).\n *\n * These tests are network-free and validate request shape, retry behavior,\n * and returned vector contract.\n */\n\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport { createGeminiEmbedder } from \"../lib/gemini-embedder.js\";\nimport { createEmbedder } from \"../lib/mrl-embedder.js\";\n\ndescribe(\"Gemini Embedder\", () => {\n const originalEnv = process.env;\n const originalFetch = global.fetch;\n\n beforeEach(() => {\n process.env = { ...originalEnv };\n vi.clearAllMocks();\n });\n\n afterEach(() => {\n process.env = { ...originalEnv };\n global.fetch = originalFetch;\n });\n\n it(\"should throw when API key is missing\", () => {\n delete process.env.SMART_CODING_GEMINI_API_KEY;\n delete process.env.GEMINI_API_KEY;\n\n expect(() =>\n createGeminiEmbedder({\n geminiApiKey: \"\"\n })\n ).toThrow(/Missing API key/);\n });\n\n it(\"should return Float32Array vector for a single text\", async () => {\n global.fetch = vi.fn().mockResolvedValue({\n ok: true,\n json: async () => ({\n data: [{ embedding: [0.1, 0.2, 0.3] }]\n })\n });\n\n const embedder = createGeminiEmbedder({\n geminiApiKey: \"test-key\",\n geminiModel: \"gemini-embedding-001\",\n geminiDimensions: 3,\n geminiBatchFlushMs: 0\n });\n\n const output = await embedder(\"hello world\");\n const vector = Array.from(output.data);\n expect(vector).toHaveLength(3);\n expect(vector[0]).toBeCloseTo(0.1, 5);\n expect(vector[1]).toBeCloseTo(0.2, 5);\n expect(vector[2]).toBeCloseTo(0.3, 5);\n expect(output.dims).toEqual([1, 3]);\n expect(embedder.modelName).toBe(\"gemini-embedding-001\");\n expect(embedder.dimension).toBe(3);\n expect(embedder.device).toBe(\"api\");\n });\n\n it(\"should call OpenAI-compatible embeddings endpoint with Bearer auth\", async () => {\n global.fetch = vi.fn().mockResolvedValue({\n ok: true,\n json: async () => ({\n data: [{ embedding: [0.5, 0.25] }]\n })\n });\n\n const embedder = createGeminiEmbedder({\n geminiApiKey: \"k-123\",\n geminiBaseURL: \"https://generativelanguage.googleapis.com/v1beta/openai/\"\n });\n\n await embedder(\"shape check\");\n\n expect(global.fetch).toHaveBeenCalledTimes(1);\n const [url, request] = global.fetch.mock.calls[0];\n expect(url).toBe(\"https://generativelanguage.googleapis.com/v1beta/openai/embeddings\");\n expect(request.method).toBe(\"POST\");\n expect(request.headers.Authorization).toBe(\"Bearer k-123\");\n expect(request.headers[\"Content-Type\"]).toBe(\"application/json\");\n const body = JSON.parse(request.body);\n expect(body.model).toBeDefined();\n expect(body.input).toEqual([\"shape check\"]);\n });\n\n it(\"should retry on 5xx and then succeed\", async () => {\n global.fetch = vi\n .fn()\n .mockResolvedValueOnce({\n ok: false,\n status: 503,\n text: async () => \"service unavailable\"\n })\n .mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n data: [{ embedding: [0.9, 0.8, 0.7] }]\n })\n });\n\n const embedder = createGeminiEmbedder({\n geminiApiKey: \"retry-key\",\n geminiMaxRetries: 1,\n geminiBatchFlushMs: 0\n });\n\n const result = await embedder(\"retry test\");\n const vector = Array.from(result.data);\n expect(vector).toHaveLength(3);\n expect(vector[0]).toBeCloseTo(0.9, 5);\n expect(vector[1]).toBeCloseTo(0.8, 5);\n expect(vector[2]).toBeCloseTo(0.7, 5);\n expect(global.fetch).toHaveBeenCalledTimes(2);\n });\n\n it(\"should retry on 429 and then succeed\", async () => {\n global.fetch = vi\n .fn()\n .mockResolvedValueOnce({\n ok: false,\n status: 429,\n text: async () => \"rate limit exceeded\"\n })\n .mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n data: [{ embedding: [0.31, 0.32, 0.33] }]\n })\n });\n\n const embedder = createGeminiEmbedder({\n geminiApiKey: \"retry-429-key\",\n geminiMaxRetries: 1,\n geminiBatchFlushMs: 0\n });\n\n const result = await embedder(\"retry 429 test\");\n const vector = Array.from(result.data);\n expect(vector).toHaveLength(3);\n expect(vector[0]).toBeCloseTo(0.31, 5);\n expect(vector[1]).toBeCloseTo(0.32, 5);\n expect(vector[2]).toBeCloseTo(0.33, 5);\n expect(global.fetch).toHaveBeenCalledTimes(2);\n });\n\n it(\"should retry on transient network error and then succeed\", async () => {\n global.fetch = vi\n .fn()\n .mockRejectedValueOnce(new TypeError(\"fetch failed\"))\n .mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n data: [{ embedding: [0.41, 0.42] }]\n })\n });\n\n const embedder = createGeminiEmbedder({\n geminiApiKey: \"retry-network-key\",\n geminiMaxRetries: 1,\n geminiBatchFlushMs: 0,\n geminiDimensions: 2\n });\n\n const result = await embedder(\"retry network test\");\n const vector = Array.from(result.data);\n expect(vector[0]).toBeCloseTo(0.41, 5);\n expect(vector[1]).toBeCloseTo(0.42, 5);\n expect(global.fetch).toHaveBeenCalledTimes(2);\n });\n\n it(\"should fail after max retries on repeated 429\", async () => {\n global.fetch = vi.fn().mockResolvedValue({\n ok: false,\n status: 429,\n text: async () => \"still rate limited\"\n });\n\n const embedder = createGeminiEmbedder({\n geminiApiKey: \"retry-limit-key\",\n geminiMaxRetries: 2,\n geminiBatchFlushMs: 0\n });\n\n await expect(embedder(\"retry limit test\")).rejects.toThrow(/429/);\n // initial + 2 retries\n expect(global.fetch).toHaveBeenCalledTimes(3);\n });\n\n it(\"should handle concurrent batch load with a transient 429\", async () => {\n let callCount = 0;\n global.fetch = vi.fn().mockImplementation(async (_url, request) => {\n callCount += 1;\n\n if (callCount === 1) {\n return {\n ok: false,\n status: 429,\n text: async () => \"burst rate limit\"\n };\n }\n\n const body = JSON.parse(request.body);\n const embeddings = body.input.map((_text, idx) => ({\n embedding: [idx + 0.1, idx + 0.2]\n }));\n\n return {\n ok: true,\n json: async () => ({ data: embeddings })\n };\n });\n\n const embedder = createGeminiEmbedder({\n geminiApiKey: \"batch-retry-key\",\n geminiDimensions: 2,\n geminiBatchSize: 64,\n geminiBatchFlushMs: 5,\n geminiMaxRetries: 1\n });\n\n const inputs = Array.from({ length: 20 }, (_, i) => `batch-${i}`);\n const results = await Promise.all(inputs.map((text) => embedder(text)));\n\n expect(results).toHaveLength(20);\n expect(results[0].dims).toEqual([1, 2]);\n expect(results[19].dims).toEqual([1, 2]);\n // one failed attempt + one retry for the same batch\n expect(global.fetch).toHaveBeenCalledTimes(2);\n });\n\n it(\"should not retry on 400 (non-retryable)\", async () => {\n global.fetch = vi.fn().mockResolvedValue({\n ok: false,\n status: 400,\n text: async () => \"bad request\"\n });\n\n const embedder = createGeminiEmbedder({\n geminiApiKey: \"bad-request-key\",\n geminiMaxRetries: 3,\n geminiBatchFlushMs: 0\n });\n\n await expect(embedder(\"non retryable test\")).rejects.toThrow(/400/);\n expect(global.fetch).toHaveBeenCalledTimes(1);\n });\n\n it(\"createEmbedder should route to gemini provider when configured\", async () => {\n global.fetch = vi.fn().mockResolvedValue({\n ok: true,\n json: async () => ({\n data: [{ embedding: [0.11, 0.22] }]\n })\n });\n\n const embedder = await createEmbedder({\n embeddingProvider: \"gemini\",\n geminiApiKey: \"provider-key\",\n geminiDimensions: 2\n });\n\n const result = await embedder(\"provider route\");\n const vector = Array.from(result.data);\n expect(vector).toHaveLength(2);\n expect(vector[0]).toBeCloseTo(0.11, 5);\n expect(vector[1]).toBeCloseTo(0.22, 5);\n expect(embedder.provider).toBe(\"gemini\");\n });\n});\n",
2047
+ "vector": [
2048
+ 0.07429467141628265,
2049
+ 0.08749568462371826,
2050
+ -0.2954220175743103,
2051
+ -0.061870694160461426,
2052
+ 0.1391019970178604,
2053
+ -0.10246008634567261,
2054
+ 0.13488991558551788,
2055
+ 0.030225295573472977,
2056
+ 0.058633241802453995,
2057
+ -0.017892932519316673,
2058
+ -0.08564099669456482,
2059
+ 0.05041772127151489,
2060
+ 0.23137547075748444,
2061
+ 0.03945732116699219,
2062
+ 0.05089656263589859,
2063
+ -0.05243123695254326,
2064
+ 0.03305608034133911,
2065
+ -0.06531672179698944,
2066
+ 0.07894738018512726,
2067
+ 0.10819889605045319,
2068
+ 0.002535534091293812,
2069
+ -0.22954970598220825,
2070
+ 0.07278713583946228,
2071
+ -0.0048547848127782345,
2072
+ 0.1265529990196228,
2073
+ 0.008766892366111279,
2074
+ 0.014455458149313927,
2075
+ 0.066266268491745,
2076
+ -0.11845991015434265,
2077
+ -0.10621864348649979,
2078
+ 0.046594418585300446,
2079
+ -0.035478319972753525,
2080
+ 0.062475644052028656,
2081
+ -0.06380968540906906,
2082
+ -0.0618915930390358,
2083
+ 0.018903566524386406,
2084
+ -0.012854848057031631,
2085
+ 0.03510170802474022,
2086
+ -0.12140484899282455,
2087
+ 0.05088192597031593,
2088
+ -0.0038806914817541838,
2089
+ 0.06648988276720047,
2090
+ -0.028463339433073997,
2091
+ -0.11369684338569641,
2092
+ 0.020572563633322716,
2093
+ 0.0795123428106308,
2094
+ 0.17362116277217865,
2095
+ 0.026002731174230576,
2096
+ -0.022788899019360542,
2097
+ -0.2222042679786682,
2098
+ 0.07248394936323166,
2099
+ 0.008579091168940067,
2100
+ -0.07201076298952103,
2101
+ -0.09847857803106308,
2102
+ 0.2490483820438385,
2103
+ 0.09040655940771103,
2104
+ -0.029461227357387543,
2105
+ -0.002615089761093259,
2106
+ 0.08667456358671188,
2107
+ 0.01818353869020939,
2108
+ 0.04434482753276825,
2109
+ 0.061591409146785736,
2110
+ -0.08369634300470352,
2111
+ 0.14579647779464722,
2112
+ 0.13947893679141998,
2113
+ -0.013956677168607712,
2114
+ -0.04042186215519905,
2115
+ 0.147396057844162,
2116
+ -0.06133352220058441,
2117
+ 0.03631182014942169,
2118
+ 0.05181899294257164,
2119
+ -0.09119614958763123,
2120
+ 0.0439314991235733,
2121
+ 0.02522202581167221,
2122
+ 0.012637197971343994,
2123
+ 0.06731968373060226,
2124
+ -0.03164246305823326,
2125
+ -0.03709614276885986,
2126
+ -0.03740047663450241,
2127
+ 0.08791538327932358,
2128
+ -0.015240681357681751,
2129
+ 0.04446370527148247,
2130
+ 0.04383549839258194,
2131
+ -0.0805320218205452,
2132
+ 0.03643820062279701,
2133
+ -0.1213131994009018,
2134
+ -0.0018667654367163777,
2135
+ -0.036806121468544006,
2136
+ -0.13397863507270813,
2137
+ 0.15733492374420166,
2138
+ 0.14244355261325836,
2139
+ -0.08248786628246307,
2140
+ -0.027001038193702698,
2141
+ 0.07168415933847427,
2142
+ -0.018978718668222427,
2143
+ -0.09821677207946777,
2144
+ -0.11966622620820999,
2145
+ -0.008267385885119438,
2146
+ -0.14164511859416962,
2147
+ 0.06961197406053543,
2148
+ 0.006974808406084776,
2149
+ 0.010943349450826645,
2150
+ 0.02799272909760475,
2151
+ -0.07752110809087753,
2152
+ 0.04638911038637161,
2153
+ 0.11095727980136871,
2154
+ 0.13161316514015198,
2155
+ 0.0321931391954422,
2156
+ 0.027063382789492607,
2157
+ -0.047113221138715744,
2158
+ -0.128585547208786,
2159
+ -0.013670013286173344,
2160
+ -0.10805260390043259,
2161
+ -0.10163422673940659,
2162
+ -0.017476022243499756,
2163
+ -0.05206144228577614,
2164
+ 0.09571206569671631,
2165
+ -0.05882852151989937,
2166
+ -0.01716124825179577,
2167
+ 0.0577368400990963,
2168
+ 0.036674026399850845,
2169
+ -0.08793500810861588,
2170
+ 0.03704546391963959,
2171
+ 0.009862380102276802,
2172
+ 0.09683586657047272,
2173
+ 0.09504247456789017,
2174
+ -0.10260891914367676,
2175
+ -0.03487544134259224
2176
+ ]
2177
+ }
2178
+ ]