opencode-codebase-index 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js ADDED
@@ -0,0 +1,4867 @@
1
+ #!/usr/bin/env node
2
+ // opencode-codebase-index - Semantic codebase search for OpenCode
3
+ import { createRequire } from 'module'; const require = createRequire(import.meta.url);
4
+ var __create = Object.create;
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getProtoOf = Object.getPrototypeOf;
9
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
10
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
11
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
12
+ }) : x)(function(x) {
13
+ if (typeof require !== "undefined") return require.apply(this, arguments);
14
+ throw Error('Dynamic require of "' + x + '" is not supported');
15
+ });
16
+ var __commonJS = (cb, mod) => function __require2() {
17
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
18
+ };
19
+ var __copyProps = (to, from, except, desc) => {
20
+ if (from && typeof from === "object" || typeof from === "function") {
21
+ for (let key of __getOwnPropNames(from))
22
+ if (!__hasOwnProp.call(to, key) && key !== except)
23
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
24
+ }
25
+ return to;
26
+ };
27
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
28
+ // If the importer is in node compatibility mode or this is not an ESM
29
+ // file that has been converted to a CommonJS file using a Babel-
30
+ // compatible transform (i.e. "__esModule" has not been set), then set
31
+ // "default" to the CommonJS "module.exports" for node compatibility.
32
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
33
+ mod
34
+ ));
35
+
36
+ // node_modules/eventemitter3/index.js
37
+ var require_eventemitter3 = __commonJS({
38
+ "node_modules/eventemitter3/index.js"(exports, module) {
39
+ "use strict";
40
+ var has = Object.prototype.hasOwnProperty;
41
+ var prefix = "~";
42
+ function Events() {
43
+ }
44
+ if (Object.create) {
45
+ Events.prototype = /* @__PURE__ */ Object.create(null);
46
+ if (!new Events().__proto__) prefix = false;
47
+ }
48
+ function EE(fn, context, once) {
49
+ this.fn = fn;
50
+ this.context = context;
51
+ this.once = once || false;
52
+ }
53
+ function addListener(emitter, event, fn, context, once) {
54
+ if (typeof fn !== "function") {
55
+ throw new TypeError("The listener must be a function");
56
+ }
57
+ var listener = new EE(fn, context || emitter, once), evt = prefix ? prefix + event : event;
58
+ if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++;
59
+ else if (!emitter._events[evt].fn) emitter._events[evt].push(listener);
60
+ else emitter._events[evt] = [emitter._events[evt], listener];
61
+ return emitter;
62
+ }
63
+ function clearEvent(emitter, evt) {
64
+ if (--emitter._eventsCount === 0) emitter._events = new Events();
65
+ else delete emitter._events[evt];
66
+ }
67
+ function EventEmitter2() {
68
+ this._events = new Events();
69
+ this._eventsCount = 0;
70
+ }
71
+ EventEmitter2.prototype.eventNames = function eventNames() {
72
+ var names = [], events, name;
73
+ if (this._eventsCount === 0) return names;
74
+ for (name in events = this._events) {
75
+ if (has.call(events, name)) names.push(prefix ? name.slice(1) : name);
76
+ }
77
+ if (Object.getOwnPropertySymbols) {
78
+ return names.concat(Object.getOwnPropertySymbols(events));
79
+ }
80
+ return names;
81
+ };
82
+ EventEmitter2.prototype.listeners = function listeners(event) {
83
+ var evt = prefix ? prefix + event : event, handlers = this._events[evt];
84
+ if (!handlers) return [];
85
+ if (handlers.fn) return [handlers.fn];
86
+ for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) {
87
+ ee[i] = handlers[i].fn;
88
+ }
89
+ return ee;
90
+ };
91
+ EventEmitter2.prototype.listenerCount = function listenerCount(event) {
92
+ var evt = prefix ? prefix + event : event, listeners = this._events[evt];
93
+ if (!listeners) return 0;
94
+ if (listeners.fn) return 1;
95
+ return listeners.length;
96
+ };
97
+ EventEmitter2.prototype.emit = function emit(event, a1, a2, a3, a4, a5) {
98
+ var evt = prefix ? prefix + event : event;
99
+ if (!this._events[evt]) return false;
100
+ var listeners = this._events[evt], len = arguments.length, args, i;
101
+ if (listeners.fn) {
102
+ if (listeners.once) this.removeListener(event, listeners.fn, void 0, true);
103
+ switch (len) {
104
+ case 1:
105
+ return listeners.fn.call(listeners.context), true;
106
+ case 2:
107
+ return listeners.fn.call(listeners.context, a1), true;
108
+ case 3:
109
+ return listeners.fn.call(listeners.context, a1, a2), true;
110
+ case 4:
111
+ return listeners.fn.call(listeners.context, a1, a2, a3), true;
112
+ case 5:
113
+ return listeners.fn.call(listeners.context, a1, a2, a3, a4), true;
114
+ case 6:
115
+ return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true;
116
+ }
117
+ for (i = 1, args = new Array(len - 1); i < len; i++) {
118
+ args[i - 1] = arguments[i];
119
+ }
120
+ listeners.fn.apply(listeners.context, args);
121
+ } else {
122
+ var length = listeners.length, j;
123
+ for (i = 0; i < length; i++) {
124
+ if (listeners[i].once) this.removeListener(event, listeners[i].fn, void 0, true);
125
+ switch (len) {
126
+ case 1:
127
+ listeners[i].fn.call(listeners[i].context);
128
+ break;
129
+ case 2:
130
+ listeners[i].fn.call(listeners[i].context, a1);
131
+ break;
132
+ case 3:
133
+ listeners[i].fn.call(listeners[i].context, a1, a2);
134
+ break;
135
+ case 4:
136
+ listeners[i].fn.call(listeners[i].context, a1, a2, a3);
137
+ break;
138
+ default:
139
+ if (!args) for (j = 1, args = new Array(len - 1); j < len; j++) {
140
+ args[j - 1] = arguments[j];
141
+ }
142
+ listeners[i].fn.apply(listeners[i].context, args);
143
+ }
144
+ }
145
+ }
146
+ return true;
147
+ };
148
+ EventEmitter2.prototype.on = function on(event, fn, context) {
149
+ return addListener(this, event, fn, context, false);
150
+ };
151
+ EventEmitter2.prototype.once = function once(event, fn, context) {
152
+ return addListener(this, event, fn, context, true);
153
+ };
154
+ EventEmitter2.prototype.removeListener = function removeListener(event, fn, context, once) {
155
+ var evt = prefix ? prefix + event : event;
156
+ if (!this._events[evt]) return this;
157
+ if (!fn) {
158
+ clearEvent(this, evt);
159
+ return this;
160
+ }
161
+ var listeners = this._events[evt];
162
+ if (listeners.fn) {
163
+ if (listeners.fn === fn && (!once || listeners.once) && (!context || listeners.context === context)) {
164
+ clearEvent(this, evt);
165
+ }
166
+ } else {
167
+ for (var i = 0, events = [], length = listeners.length; i < length; i++) {
168
+ if (listeners[i].fn !== fn || once && !listeners[i].once || context && listeners[i].context !== context) {
169
+ events.push(listeners[i]);
170
+ }
171
+ }
172
+ if (events.length) this._events[evt] = events.length === 1 ? events[0] : events;
173
+ else clearEvent(this, evt);
174
+ }
175
+ return this;
176
+ };
177
+ EventEmitter2.prototype.removeAllListeners = function removeAllListeners(event) {
178
+ var evt;
179
+ if (event) {
180
+ evt = prefix ? prefix + event : event;
181
+ if (this._events[evt]) clearEvent(this, evt);
182
+ } else {
183
+ this._events = new Events();
184
+ this._eventsCount = 0;
185
+ }
186
+ return this;
187
+ };
188
+ EventEmitter2.prototype.off = EventEmitter2.prototype.removeListener;
189
+ EventEmitter2.prototype.addListener = EventEmitter2.prototype.on;
190
+ EventEmitter2.prefixed = prefix;
191
+ EventEmitter2.EventEmitter = EventEmitter2;
192
+ if ("undefined" !== typeof module) {
193
+ module.exports = EventEmitter2;
194
+ }
195
+ }
196
+ });
197
+
198
+ // node_modules/ignore/index.js
199
+ var require_ignore = __commonJS({
200
+ "node_modules/ignore/index.js"(exports, module) {
201
+ "use strict";
202
+ function makeArray(subject) {
203
+ return Array.isArray(subject) ? subject : [subject];
204
+ }
205
+ var UNDEFINED = void 0;
206
+ var EMPTY = "";
207
+ var SPACE = " ";
208
+ var ESCAPE = "\\";
209
+ var REGEX_TEST_BLANK_LINE = /^\s+$/;
210
+ var REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\]|^)\\$/;
211
+ var REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/;
212
+ var REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/;
213
+ var REGEX_SPLITALL_CRLF = /\r?\n/g;
214
+ var REGEX_TEST_INVALID_PATH = /^\.{0,2}\/|^\.{1,2}$/;
215
+ var REGEX_TEST_TRAILING_SLASH = /\/$/;
216
+ var SLASH = "/";
217
+ var TMP_KEY_IGNORE = "node-ignore";
218
+ if (typeof Symbol !== "undefined") {
219
+ TMP_KEY_IGNORE = /* @__PURE__ */ Symbol.for("node-ignore");
220
+ }
221
+ var KEY_IGNORE = TMP_KEY_IGNORE;
222
+ var define = (object, key, value) => {
223
+ Object.defineProperty(object, key, { value });
224
+ return value;
225
+ };
226
+ var REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g;
227
+ var RETURN_FALSE = () => false;
228
+ var sanitizeRange = (range) => range.replace(
229
+ REGEX_REGEXP_RANGE,
230
+ (match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0) ? match : EMPTY
231
+ );
232
+ var cleanRangeBackSlash = (slashes) => {
233
+ const { length } = slashes;
234
+ return slashes.slice(0, length - length % 2);
235
+ };
236
+ var REPLACERS = [
237
+ [
238
+ // Remove BOM
239
+ // TODO:
240
+ // Other similar zero-width characters?
241
+ /^\uFEFF/,
242
+ () => EMPTY
243
+ ],
244
+ // > Trailing spaces are ignored unless they are quoted with backslash ("\")
245
+ [
246
+ // (a\ ) -> (a )
247
+ // (a ) -> (a)
248
+ // (a ) -> (a)
249
+ // (a \ ) -> (a )
250
+ /((?:\\\\)*?)(\\?\s+)$/,
251
+ (_, m1, m2) => m1 + (m2.indexOf("\\") === 0 ? SPACE : EMPTY)
252
+ ],
253
+ // Replace (\ ) with ' '
254
+ // (\ ) -> ' '
255
+ // (\\ ) -> '\\ '
256
+ // (\\\ ) -> '\\ '
257
+ [
258
+ /(\\+?)\s/g,
259
+ (_, m1) => {
260
+ const { length } = m1;
261
+ return m1.slice(0, length - length % 2) + SPACE;
262
+ }
263
+ ],
264
+ // Escape metacharacters
265
+ // which is written down by users but means special for regular expressions.
266
+ // > There are 12 characters with special meanings:
267
+ // > - the backslash \,
268
+ // > - the caret ^,
269
+ // > - the dollar sign $,
270
+ // > - the period or dot .,
271
+ // > - the vertical bar or pipe symbol |,
272
+ // > - the question mark ?,
273
+ // > - the asterisk or star *,
274
+ // > - the plus sign +,
275
+ // > - the opening parenthesis (,
276
+ // > - the closing parenthesis ),
277
+ // > - and the opening square bracket [,
278
+ // > - the opening curly brace {,
279
+ // > These special characters are often called "metacharacters".
280
+ [
281
+ /[\\$.|*+(){^]/g,
282
+ (match) => `\\${match}`
283
+ ],
284
+ [
285
+ // > a question mark (?) matches a single character
286
+ /(?!\\)\?/g,
287
+ () => "[^/]"
288
+ ],
289
+ // leading slash
290
+ [
291
+ // > A leading slash matches the beginning of the pathname.
292
+ // > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c".
293
+ // A leading slash matches the beginning of the pathname
294
+ /^\//,
295
+ () => "^"
296
+ ],
297
+ // replace special metacharacter slash after the leading slash
298
+ [
299
+ /\//g,
300
+ () => "\\/"
301
+ ],
302
+ [
303
+ // > A leading "**" followed by a slash means match in all directories.
304
+ // > For example, "**/foo" matches file or directory "foo" anywhere,
305
+ // > the same as pattern "foo".
306
+ // > "**/foo/bar" matches file or directory "bar" anywhere that is directly
307
+ // > under directory "foo".
308
+ // Notice that the '*'s have been replaced as '\\*'
309
+ /^\^*\\\*\\\*\\\//,
310
+ // '**/foo' <-> 'foo'
311
+ () => "^(?:.*\\/)?"
312
+ ],
313
+ // starting
314
+ [
315
+ // there will be no leading '/'
316
+ // (which has been replaced by section "leading slash")
317
+ // If starts with '**', adding a '^' to the regular expression also works
318
+ /^(?=[^^])/,
319
+ function startingReplacer() {
320
+ return !/\/(?!$)/.test(this) ? "(?:^|\\/)" : "^";
321
+ }
322
+ ],
323
+ // two globstars
324
+ [
325
+ // Use lookahead assertions so that we could match more than one `'/**'`
326
+ /\\\/\\\*\\\*(?=\\\/|$)/g,
327
+ // Zero, one or several directories
328
+ // should not use '*', or it will be replaced by the next replacer
329
+ // Check if it is not the last `'/**'`
330
+ (_, index, str) => index + 6 < str.length ? "(?:\\/[^\\/]+)*" : "\\/.+"
331
+ ],
332
+ // normal intermediate wildcards
333
+ [
334
+ // Never replace escaped '*'
335
+ // ignore rule '\*' will match the path '*'
336
+ // 'abc.*/' -> go
337
+ // 'abc.*' -> skip this rule,
338
+ // coz trailing single wildcard will be handed by [trailing wildcard]
339
+ /(^|[^\\]+)(\\\*)+(?=.+)/g,
340
+ // '*.js' matches '.js'
341
+ // '*.js' doesn't match 'abc'
342
+ (_, p1, p2) => {
343
+ const unescaped = p2.replace(/\\\*/g, "[^\\/]*");
344
+ return p1 + unescaped;
345
+ }
346
+ ],
347
+ [
348
+ // unescape, revert step 3 except for back slash
349
+ // For example, if a user escape a '\\*',
350
+ // after step 3, the result will be '\\\\\\*'
351
+ /\\\\\\(?=[$.|*+(){^])/g,
352
+ () => ESCAPE
353
+ ],
354
+ [
355
+ // '\\\\' -> '\\'
356
+ /\\\\/g,
357
+ () => ESCAPE
358
+ ],
359
+ [
360
+ // > The range notation, e.g. [a-zA-Z],
361
+ // > can be used to match one of the characters in a range.
362
+ // `\` is escaped by step 3
363
+ /(\\)?\[([^\]/]*?)(\\*)($|\])/g,
364
+ (match, leadEscape, range, endEscape, close) => leadEscape === ESCAPE ? `\\[${range}${cleanRangeBackSlash(endEscape)}${close}` : close === "]" ? endEscape.length % 2 === 0 ? `[${sanitizeRange(range)}${endEscape}]` : "[]" : "[]"
365
+ ],
366
+ // ending
367
+ [
368
+ // 'js' will not match 'js.'
369
+ // 'ab' will not match 'abc'
370
+ /(?:[^*])$/,
371
+ // WTF!
372
+ // https://git-scm.com/docs/gitignore
373
+ // changes in [2.22.1](https://git-scm.com/docs/gitignore/2.22.1)
374
+ // which re-fixes #24, #38
375
+ // > If there is a separator at the end of the pattern then the pattern
376
+ // > will only match directories, otherwise the pattern can match both
377
+ // > files and directories.
378
+ // 'js*' will not match 'a.js'
379
+ // 'js/' will not match 'a.js'
380
+ // 'js' will match 'a.js' and 'a.js/'
381
+ (match) => /\/$/.test(match) ? `${match}$` : `${match}(?=$|\\/$)`
382
+ ]
383
+ ];
384
+ var REGEX_REPLACE_TRAILING_WILDCARD = /(^|\\\/)?\\\*$/;
385
+ var MODE_IGNORE = "regex";
386
+ var MODE_CHECK_IGNORE = "checkRegex";
387
+ var UNDERSCORE = "_";
388
+ var TRAILING_WILD_CARD_REPLACERS = {
389
+ [MODE_IGNORE](_, p1) {
390
+ const prefix = p1 ? `${p1}[^/]+` : "[^/]*";
391
+ return `${prefix}(?=$|\\/$)`;
392
+ },
393
+ [MODE_CHECK_IGNORE](_, p1) {
394
+ const prefix = p1 ? `${p1}[^/]*` : "[^/]*";
395
+ return `${prefix}(?=$|\\/$)`;
396
+ }
397
+ };
398
+ var makeRegexPrefix = (pattern) => REPLACERS.reduce(
399
+ (prev, [matcher, replacer]) => prev.replace(matcher, replacer.bind(pattern)),
400
+ pattern
401
+ );
402
+ var isString = (subject) => typeof subject === "string";
403
+ var checkPattern = (pattern) => pattern && isString(pattern) && !REGEX_TEST_BLANK_LINE.test(pattern) && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern) && pattern.indexOf("#") !== 0;
404
+ var splitPattern = (pattern) => pattern.split(REGEX_SPLITALL_CRLF).filter(Boolean);
405
+ var IgnoreRule = class {
406
+ constructor(pattern, mark, body, ignoreCase, negative, prefix) {
407
+ this.pattern = pattern;
408
+ this.mark = mark;
409
+ this.negative = negative;
410
+ define(this, "body", body);
411
+ define(this, "ignoreCase", ignoreCase);
412
+ define(this, "regexPrefix", prefix);
413
+ }
414
+ get regex() {
415
+ const key = UNDERSCORE + MODE_IGNORE;
416
+ if (this[key]) {
417
+ return this[key];
418
+ }
419
+ return this._make(MODE_IGNORE, key);
420
+ }
421
+ get checkRegex() {
422
+ const key = UNDERSCORE + MODE_CHECK_IGNORE;
423
+ if (this[key]) {
424
+ return this[key];
425
+ }
426
+ return this._make(MODE_CHECK_IGNORE, key);
427
+ }
428
+ _make(mode, key) {
429
+ const str = this.regexPrefix.replace(
430
+ REGEX_REPLACE_TRAILING_WILDCARD,
431
+ // It does not need to bind pattern
432
+ TRAILING_WILD_CARD_REPLACERS[mode]
433
+ );
434
+ const regex = this.ignoreCase ? new RegExp(str, "i") : new RegExp(str);
435
+ return define(this, key, regex);
436
+ }
437
+ };
438
+ var createRule = ({
439
+ pattern,
440
+ mark
441
+ }, ignoreCase) => {
442
+ let negative = false;
443
+ let body = pattern;
444
+ if (body.indexOf("!") === 0) {
445
+ negative = true;
446
+ body = body.substr(1);
447
+ }
448
+ body = body.replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, "!").replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, "#");
449
+ const regexPrefix = makeRegexPrefix(body);
450
+ return new IgnoreRule(
451
+ pattern,
452
+ mark,
453
+ body,
454
+ ignoreCase,
455
+ negative,
456
+ regexPrefix
457
+ );
458
+ };
459
+ var RuleManager = class {
460
+ constructor(ignoreCase) {
461
+ this._ignoreCase = ignoreCase;
462
+ this._rules = [];
463
+ }
464
+ _add(pattern) {
465
+ if (pattern && pattern[KEY_IGNORE]) {
466
+ this._rules = this._rules.concat(pattern._rules._rules);
467
+ this._added = true;
468
+ return;
469
+ }
470
+ if (isString(pattern)) {
471
+ pattern = {
472
+ pattern
473
+ };
474
+ }
475
+ if (checkPattern(pattern.pattern)) {
476
+ const rule = createRule(pattern, this._ignoreCase);
477
+ this._added = true;
478
+ this._rules.push(rule);
479
+ }
480
+ }
481
+ // @param {Array<string> | string | Ignore} pattern
482
+ add(pattern) {
483
+ this._added = false;
484
+ makeArray(
485
+ isString(pattern) ? splitPattern(pattern) : pattern
486
+ ).forEach(this._add, this);
487
+ return this._added;
488
+ }
489
+ // Test one single path without recursively checking parent directories
490
+ //
491
+ // - checkUnignored `boolean` whether should check if the path is unignored,
492
+ // setting `checkUnignored` to `false` could reduce additional
493
+ // path matching.
494
+ // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
495
+ // @returns {TestResult} true if a file is ignored
496
+ test(path7, checkUnignored, mode) {
497
+ let ignored = false;
498
+ let unignored = false;
499
+ let matchedRule;
500
+ this._rules.forEach((rule) => {
501
+ const { negative } = rule;
502
+ if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
503
+ return;
504
+ }
505
+ const matched = rule[mode].test(path7);
506
+ if (!matched) {
507
+ return;
508
+ }
509
+ ignored = !negative;
510
+ unignored = negative;
511
+ matchedRule = negative ? UNDEFINED : rule;
512
+ });
513
+ const ret = {
514
+ ignored,
515
+ unignored
516
+ };
517
+ if (matchedRule) {
518
+ ret.rule = matchedRule;
519
+ }
520
+ return ret;
521
+ }
522
+ };
523
+ var throwError = (message, Ctor) => {
524
+ throw new Ctor(message);
525
+ };
526
+ var checkPath = (path7, originalPath, doThrow) => {
527
+ if (!isString(path7)) {
528
+ return doThrow(
529
+ `path must be a string, but got \`${originalPath}\``,
530
+ TypeError
531
+ );
532
+ }
533
+ if (!path7) {
534
+ return doThrow(`path must not be empty`, TypeError);
535
+ }
536
+ if (checkPath.isNotRelative(path7)) {
537
+ const r = "`path.relative()`d";
538
+ return doThrow(
539
+ `path should be a ${r} string, but got "${originalPath}"`,
540
+ RangeError
541
+ );
542
+ }
543
+ return true;
544
+ };
545
+ var isNotRelative = (path7) => REGEX_TEST_INVALID_PATH.test(path7);
546
+ checkPath.isNotRelative = isNotRelative;
547
+ checkPath.convert = (p) => p;
548
+ var Ignore2 = class {
549
+ constructor({
550
+ ignorecase = true,
551
+ ignoreCase = ignorecase,
552
+ allowRelativePaths = false
553
+ } = {}) {
554
+ define(this, KEY_IGNORE, true);
555
+ this._rules = new RuleManager(ignoreCase);
556
+ this._strictPathCheck = !allowRelativePaths;
557
+ this._initCache();
558
+ }
559
+ _initCache() {
560
+ this._ignoreCache = /* @__PURE__ */ Object.create(null);
561
+ this._testCache = /* @__PURE__ */ Object.create(null);
562
+ }
563
+ add(pattern) {
564
+ if (this._rules.add(pattern)) {
565
+ this._initCache();
566
+ }
567
+ return this;
568
+ }
569
+ // legacy
570
+ addPattern(pattern) {
571
+ return this.add(pattern);
572
+ }
573
+ // @returns {TestResult}
574
+ _test(originalPath, cache, checkUnignored, slices) {
575
+ const path7 = originalPath && checkPath.convert(originalPath);
576
+ checkPath(
577
+ path7,
578
+ originalPath,
579
+ this._strictPathCheck ? throwError : RETURN_FALSE
580
+ );
581
+ return this._t(path7, cache, checkUnignored, slices);
582
+ }
583
+ checkIgnore(path7) {
584
+ if (!REGEX_TEST_TRAILING_SLASH.test(path7)) {
585
+ return this.test(path7);
586
+ }
587
+ const slices = path7.split(SLASH).filter(Boolean);
588
+ slices.pop();
589
+ if (slices.length) {
590
+ const parent = this._t(
591
+ slices.join(SLASH) + SLASH,
592
+ this._testCache,
593
+ true,
594
+ slices
595
+ );
596
+ if (parent.ignored) {
597
+ return parent;
598
+ }
599
+ }
600
+ return this._rules.test(path7, false, MODE_CHECK_IGNORE);
601
+ }
602
+ _t(path7, cache, checkUnignored, slices) {
603
+ if (path7 in cache) {
604
+ return cache[path7];
605
+ }
606
+ if (!slices) {
607
+ slices = path7.split(SLASH).filter(Boolean);
608
+ }
609
+ slices.pop();
610
+ if (!slices.length) {
611
+ return cache[path7] = this._rules.test(path7, checkUnignored, MODE_IGNORE);
612
+ }
613
+ const parent = this._t(
614
+ slices.join(SLASH) + SLASH,
615
+ cache,
616
+ checkUnignored,
617
+ slices
618
+ );
619
+ return cache[path7] = parent.ignored ? parent : this._rules.test(path7, checkUnignored, MODE_IGNORE);
620
+ }
621
+ ignores(path7) {
622
+ return this._test(path7, this._ignoreCache, false).ignored;
623
+ }
624
+ createFilter() {
625
+ return (path7) => !this.ignores(path7);
626
+ }
627
+ filter(paths) {
628
+ return makeArray(paths).filter(this.createFilter());
629
+ }
630
+ // @returns {TestResult}
631
+ test(path7) {
632
+ return this._test(path7, this._testCache, true);
633
+ }
634
+ };
635
+ var factory = (options) => new Ignore2(options);
636
+ var isPathValid = (path7) => checkPath(path7 && checkPath.convert(path7), path7, RETURN_FALSE);
637
+ var setupWindows = () => {
638
+ const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
639
+ checkPath.convert = makePosix;
640
+ const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
641
+ checkPath.isNotRelative = (path7) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path7) || isNotRelative(path7);
642
+ };
643
+ if (
644
+ // Detect `process` so that it can run in browsers.
645
+ typeof process !== "undefined" && process.platform === "win32"
646
+ ) {
647
+ setupWindows();
648
+ }
649
+ module.exports = factory;
650
+ factory.default = factory;
651
+ module.exports.isPathValid = isPathValid;
652
+ define(module.exports, /* @__PURE__ */ Symbol.for("setupWindows"), setupWindows);
653
+ }
654
+ });
655
+
656
+ // src/cli.ts
657
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
658
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
659
+ import * as path6 from "path";
660
+ import * as os3 from "os";
661
+
662
+ // src/config/constants.ts
663
+ var DEFAULT_INCLUDE = [
664
+ "**/*.{ts,tsx,js,jsx,mjs,cjs}",
665
+ "**/*.{py,pyi}",
666
+ "**/*.{go,rs,java,kt,scala}",
667
+ "**/*.{c,cpp,cc,h,hpp}",
668
+ "**/*.{rb,php,swift}",
669
+ "**/*.{vue,svelte,astro}",
670
+ "**/*.{sql,graphql,proto}",
671
+ "**/*.{yaml,yml,toml}",
672
+ "**/*.{md,mdx}",
673
+ "**/*.{sh,bash,zsh}"
674
+ ];
675
+ var DEFAULT_EXCLUDE = [
676
+ "**/node_modules/**",
677
+ "**/.git/**",
678
+ "**/dist/**",
679
+ "**/build/**",
680
+ "**/*.min.js",
681
+ "**/*.bundle.js",
682
+ "**/vendor/**",
683
+ "**/__pycache__/**",
684
+ "**/target/**",
685
+ "**/coverage/**",
686
+ "**/.next/**",
687
+ "**/.nuxt/**",
688
+ "**/.opencode/**"
689
+ ];
690
+ var EMBEDDING_MODELS = {
691
+ "google": {
692
+ // `text-embedding-004` is DEPRECATED - https://ai.google.dev/gemini-api/docs/deprecations
693
+ "text-embedding-005": {
694
+ provider: "google",
695
+ model: "text-embedding-005",
696
+ dimensions: 768,
697
+ maxTokens: 2048,
698
+ costPer1MTokens: 0.025,
699
+ taskAble: false
700
+ // Note: on reality, this model allows for task-specific embeddings. See: https://docs.cloud.google.com/vertex-ai/generative-ai/docs/embeddings/task-types
701
+ },
702
+ "gemini-embedding-001": {
703
+ provider: "google",
704
+ model: "gemini-embedding-001",
705
+ // Native output is 3072D, but we use Matryoshka truncation via outputDimensionality
706
+ // to reduce to 1536D for better storage/search efficiency with minimal quality loss.
707
+ // Google recommends 768, 1536, or 3072. See: https://ai.google.dev/gemini-api/docs/embeddings
708
+ dimensions: 1536,
709
+ maxTokens: 2048,
710
+ costPer1MTokens: 0.15,
711
+ taskAble: true
712
+ }
713
+ },
714
+ "openai": {
715
+ "text-embedding-3-small": {
716
+ provider: "openai",
717
+ model: "text-embedding-3-small",
718
+ dimensions: 1536,
719
+ maxTokens: 8191,
720
+ costPer1MTokens: 0.02
721
+ },
722
+ "text-embedding-3-large": {
723
+ provider: "openai",
724
+ model: "text-embedding-3-large",
725
+ dimensions: 3072,
726
+ maxTokens: 8191,
727
+ costPer1MTokens: 0.13
728
+ }
729
+ },
730
+ "ollama": {
731
+ "nomic-embed-text": {
732
+ provider: "ollama",
733
+ model: "nomic-embed-text",
734
+ dimensions: 768,
735
+ maxTokens: 8192,
736
+ costPer1MTokens: 0
737
+ },
738
+ "mxbai-embed-large": {
739
+ provider: "ollama",
740
+ model: "mxbai-embed-large",
741
+ dimensions: 1024,
742
+ maxTokens: 512,
743
+ costPer1MTokens: 0
744
+ }
745
+ },
746
+ "github-copilot": {
747
+ "text-embedding-3-small": {
748
+ provider: "github-copilot",
749
+ model: "text-embedding-3-small",
750
+ dimensions: 1536,
751
+ maxTokens: 8191,
752
+ costPer1MTokens: 0
753
+ }
754
+ }
755
+ };
756
+ var DEFAULT_PROVIDER_MODELS = {
757
+ "github-copilot": "text-embedding-3-small",
758
+ "openai": "text-embedding-3-small",
759
+ "google": "text-embedding-005",
760
+ "ollama": "nomic-embed-text"
761
+ };
762
+
763
+ // src/config/schema.ts
764
+ function getDefaultIndexingConfig() {
765
+ return {
766
+ autoIndex: false,
767
+ watchFiles: true,
768
+ maxFileSize: 1048576,
769
+ maxChunksPerFile: 100,
770
+ semanticOnly: false,
771
+ retries: 3,
772
+ retryDelayMs: 1e3,
773
+ autoGc: true,
774
+ gcIntervalDays: 7,
775
+ gcOrphanThreshold: 100,
776
+ requireProjectMarker: true
777
+ };
778
+ }
779
+ function getDefaultSearchConfig() {
780
+ return {
781
+ maxResults: 20,
782
+ minScore: 0.1,
783
+ includeContext: true,
784
+ hybridWeight: 0.5,
785
+ contextLines: 0
786
+ };
787
+ }
788
+ function getDefaultDebugConfig() {
789
+ return {
790
+ enabled: false,
791
+ logLevel: "info",
792
+ logSearch: true,
793
+ logEmbedding: true,
794
+ logCache: true,
795
+ logGc: true,
796
+ logBranch: true,
797
+ metrics: true
798
+ };
799
+ }
800
+ var VALID_SCOPES = ["project", "global"];
801
+ var VALID_LOG_LEVELS = ["error", "warn", "info", "debug"];
802
+ function isValidProvider(value) {
803
+ return typeof value === "string" && Object.keys(EMBEDDING_MODELS).includes(value);
804
+ }
805
+ function isValidModel(value, provider) {
806
+ return typeof value === "string" && Object.keys(EMBEDDING_MODELS[provider]).includes(value);
807
+ }
808
+ function isValidScope(value) {
809
+ return typeof value === "string" && VALID_SCOPES.includes(value);
810
+ }
811
+ function isStringArray(value) {
812
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
813
+ }
814
+ function isValidLogLevel(value) {
815
+ return typeof value === "string" && VALID_LOG_LEVELS.includes(value);
816
+ }
817
+ function parseConfig(raw) {
818
+ const input = raw && typeof raw === "object" ? raw : {};
819
+ const defaultIndexing = getDefaultIndexingConfig();
820
+ const defaultSearch = getDefaultSearchConfig();
821
+ const defaultDebug = getDefaultDebugConfig();
822
+ const rawIndexing = input.indexing && typeof input.indexing === "object" ? input.indexing : {};
823
+ const indexing = {
824
+ autoIndex: typeof rawIndexing.autoIndex === "boolean" ? rawIndexing.autoIndex : defaultIndexing.autoIndex,
825
+ watchFiles: typeof rawIndexing.watchFiles === "boolean" ? rawIndexing.watchFiles : defaultIndexing.watchFiles,
826
+ maxFileSize: typeof rawIndexing.maxFileSize === "number" ? rawIndexing.maxFileSize : defaultIndexing.maxFileSize,
827
+ maxChunksPerFile: typeof rawIndexing.maxChunksPerFile === "number" ? Math.max(1, rawIndexing.maxChunksPerFile) : defaultIndexing.maxChunksPerFile,
828
+ semanticOnly: typeof rawIndexing.semanticOnly === "boolean" ? rawIndexing.semanticOnly : defaultIndexing.semanticOnly,
829
+ retries: typeof rawIndexing.retries === "number" ? rawIndexing.retries : defaultIndexing.retries,
830
+ retryDelayMs: typeof rawIndexing.retryDelayMs === "number" ? rawIndexing.retryDelayMs : defaultIndexing.retryDelayMs,
831
+ autoGc: typeof rawIndexing.autoGc === "boolean" ? rawIndexing.autoGc : defaultIndexing.autoGc,
832
+ gcIntervalDays: typeof rawIndexing.gcIntervalDays === "number" ? Math.max(1, rawIndexing.gcIntervalDays) : defaultIndexing.gcIntervalDays,
833
+ gcOrphanThreshold: typeof rawIndexing.gcOrphanThreshold === "number" ? Math.max(0, rawIndexing.gcOrphanThreshold) : defaultIndexing.gcOrphanThreshold,
834
+ requireProjectMarker: typeof rawIndexing.requireProjectMarker === "boolean" ? rawIndexing.requireProjectMarker : defaultIndexing.requireProjectMarker
835
+ };
836
+ const rawSearch = input.search && typeof input.search === "object" ? input.search : {};
837
+ const search = {
838
+ maxResults: typeof rawSearch.maxResults === "number" ? rawSearch.maxResults : defaultSearch.maxResults,
839
+ minScore: typeof rawSearch.minScore === "number" ? rawSearch.minScore : defaultSearch.minScore,
840
+ includeContext: typeof rawSearch.includeContext === "boolean" ? rawSearch.includeContext : defaultSearch.includeContext,
841
+ hybridWeight: typeof rawSearch.hybridWeight === "number" ? Math.min(1, Math.max(0, rawSearch.hybridWeight)) : defaultSearch.hybridWeight,
842
+ contextLines: typeof rawSearch.contextLines === "number" ? Math.min(50, Math.max(0, rawSearch.contextLines)) : defaultSearch.contextLines
843
+ };
844
+ const rawDebug = input.debug && typeof input.debug === "object" ? input.debug : {};
845
+ const debug = {
846
+ enabled: typeof rawDebug.enabled === "boolean" ? rawDebug.enabled : defaultDebug.enabled,
847
+ logLevel: isValidLogLevel(rawDebug.logLevel) ? rawDebug.logLevel : defaultDebug.logLevel,
848
+ logSearch: typeof rawDebug.logSearch === "boolean" ? rawDebug.logSearch : defaultDebug.logSearch,
849
+ logEmbedding: typeof rawDebug.logEmbedding === "boolean" ? rawDebug.logEmbedding : defaultDebug.logEmbedding,
850
+ logCache: typeof rawDebug.logCache === "boolean" ? rawDebug.logCache : defaultDebug.logCache,
851
+ logGc: typeof rawDebug.logGc === "boolean" ? rawDebug.logGc : defaultDebug.logGc,
852
+ logBranch: typeof rawDebug.logBranch === "boolean" ? rawDebug.logBranch : defaultDebug.logBranch,
853
+ metrics: typeof rawDebug.metrics === "boolean" ? rawDebug.metrics : defaultDebug.metrics
854
+ };
855
+ let embeddingProvider;
856
+ let embeddingModel = void 0;
857
+ if (isValidProvider(input.embeddingProvider)) {
858
+ embeddingProvider = input.embeddingProvider;
859
+ if (input.embeddingModel) {
860
+ embeddingModel = isValidModel(input.embeddingModel, embeddingProvider) ? input.embeddingModel : DEFAULT_PROVIDER_MODELS[embeddingProvider];
861
+ }
862
+ } else {
863
+ embeddingProvider = "auto";
864
+ }
865
+ return {
866
+ embeddingProvider,
867
+ embeddingModel,
868
+ scope: isValidScope(input.scope) ? input.scope : "project",
869
+ include: isStringArray(input.include) ? input.include : DEFAULT_INCLUDE,
870
+ exclude: isStringArray(input.exclude) ? input.exclude : DEFAULT_EXCLUDE,
871
+ indexing,
872
+ search,
873
+ debug
874
+ };
875
+ }
876
+ function getDefaultModelForProvider(provider) {
877
+ const models = EMBEDDING_MODELS[provider];
878
+ const providerDefault = DEFAULT_PROVIDER_MODELS[provider];
879
+ return models[providerDefault];
880
+ }
881
+ var availableProviders = Object.keys(EMBEDDING_MODELS);
882
+
883
+ // src/mcp-server.ts
884
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
885
+ import { z } from "zod";
886
+
887
+ // src/indexer/index.ts
888
+ import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync, renameSync, unlinkSync, promises as fsPromises2 } from "fs";
889
+ import * as path5 from "path";
890
+ import { performance as performance2 } from "perf_hooks";
891
+
892
+ // node_modules/eventemitter3/index.mjs
893
+ var import_index = __toESM(require_eventemitter3(), 1);
894
+
895
+ // node_modules/p-timeout/index.js
896
+ var TimeoutError = class _TimeoutError extends Error {
897
+ name = "TimeoutError";
898
+ constructor(message, options) {
899
+ super(message, options);
900
+ Error.captureStackTrace?.(this, _TimeoutError);
901
+ }
902
+ };
903
+ var getAbortedReason = (signal) => signal.reason ?? new DOMException("This operation was aborted.", "AbortError");
904
+ function pTimeout(promise, options) {
905
+ const {
906
+ milliseconds,
907
+ fallback,
908
+ message,
909
+ customTimers = { setTimeout, clearTimeout },
910
+ signal
911
+ } = options;
912
+ let timer;
913
+ let abortHandler;
914
+ const wrappedPromise = new Promise((resolve4, reject) => {
915
+ if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
916
+ throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
917
+ }
918
+ if (signal?.aborted) {
919
+ reject(getAbortedReason(signal));
920
+ return;
921
+ }
922
+ if (signal) {
923
+ abortHandler = () => {
924
+ reject(getAbortedReason(signal));
925
+ };
926
+ signal.addEventListener("abort", abortHandler, { once: true });
927
+ }
928
+ promise.then(resolve4, reject);
929
+ if (milliseconds === Number.POSITIVE_INFINITY) {
930
+ return;
931
+ }
932
+ const timeoutError = new TimeoutError();
933
+ timer = customTimers.setTimeout.call(void 0, () => {
934
+ if (fallback) {
935
+ try {
936
+ resolve4(fallback());
937
+ } catch (error) {
938
+ reject(error);
939
+ }
940
+ return;
941
+ }
942
+ if (typeof promise.cancel === "function") {
943
+ promise.cancel();
944
+ }
945
+ if (message === false) {
946
+ resolve4();
947
+ } else if (message instanceof Error) {
948
+ reject(message);
949
+ } else {
950
+ timeoutError.message = message ?? `Promise timed out after ${milliseconds} milliseconds`;
951
+ reject(timeoutError);
952
+ }
953
+ }, milliseconds);
954
+ });
955
+ const cancelablePromise = wrappedPromise.finally(() => {
956
+ cancelablePromise.clear();
957
+ if (abortHandler && signal) {
958
+ signal.removeEventListener("abort", abortHandler);
959
+ }
960
+ });
961
+ cancelablePromise.clear = () => {
962
+ customTimers.clearTimeout.call(void 0, timer);
963
+ timer = void 0;
964
+ };
965
+ return cancelablePromise;
966
+ }
967
+
968
+ // node_modules/p-queue/dist/lower-bound.js
969
+ function lowerBound(array, value, comparator) {
970
+ let first = 0;
971
+ let count = array.length;
972
+ while (count > 0) {
973
+ const step = Math.trunc(count / 2);
974
+ let it = first + step;
975
+ if (comparator(array[it], value) <= 0) {
976
+ first = ++it;
977
+ count -= step + 1;
978
+ } else {
979
+ count = step;
980
+ }
981
+ }
982
+ return first;
983
+ }
984
+
985
+ // node_modules/p-queue/dist/priority-queue.js
986
+ var PriorityQueue = class {
987
+ #queue = [];
988
+ enqueue(run, options) {
989
+ const { priority = 0, id } = options ?? {};
990
+ const element = {
991
+ priority,
992
+ id,
993
+ run
994
+ };
995
+ if (this.size === 0 || this.#queue[this.size - 1].priority >= priority) {
996
+ this.#queue.push(element);
997
+ return;
998
+ }
999
+ const index = lowerBound(this.#queue, element, (a, b) => b.priority - a.priority);
1000
+ this.#queue.splice(index, 0, element);
1001
+ }
1002
+ setPriority(id, priority) {
1003
+ const index = this.#queue.findIndex((element) => element.id === id);
1004
+ if (index === -1) {
1005
+ throw new ReferenceError(`No promise function with the id "${id}" exists in the queue.`);
1006
+ }
1007
+ const [item] = this.#queue.splice(index, 1);
1008
+ this.enqueue(item.run, { priority, id });
1009
+ }
1010
+ dequeue() {
1011
+ const item = this.#queue.shift();
1012
+ return item?.run;
1013
+ }
1014
+ filter(options) {
1015
+ return this.#queue.filter((element) => element.priority === options.priority).map((element) => element.run);
1016
+ }
1017
+ get size() {
1018
+ return this.#queue.length;
1019
+ }
1020
+ };
1021
+
1022
+ // node_modules/p-queue/dist/index.js
1023
+ var PQueue = class extends import_index.default {
1024
+ #carryoverIntervalCount;
1025
+ #isIntervalIgnored;
1026
+ #intervalCount = 0;
1027
+ #intervalCap;
1028
+ #rateLimitedInInterval = false;
1029
+ #rateLimitFlushScheduled = false;
1030
+ #interval;
1031
+ #intervalEnd = 0;
1032
+ #lastExecutionTime = 0;
1033
+ #intervalId;
1034
+ #timeoutId;
1035
+ #strict;
1036
+ // Circular buffer implementation for better performance
1037
+ #strictTicks = [];
1038
+ #strictTicksStartIndex = 0;
1039
+ #queue;
1040
+ #queueClass;
1041
+ #pending = 0;
1042
+ // The `!` is needed because of https://github.com/microsoft/TypeScript/issues/32194
1043
+ #concurrency;
1044
+ #isPaused;
1045
+ // Use to assign a unique identifier to a promise function, if not explicitly specified
1046
+ #idAssigner = 1n;
1047
+ // Track currently running tasks for debugging
1048
+ #runningTasks = /* @__PURE__ */ new Map();
1049
+ /**
1050
+ Get or set the default timeout for all tasks. Can be changed at runtime.
1051
+
1052
+ Operations will throw a `TimeoutError` if they don't complete within the specified time.
1053
+
1054
+ The timeout begins when the operation is dequeued and starts execution, not while it's waiting in the queue.
1055
+
1056
+ @example
1057
+ ```
1058
+ const queue = new PQueue({timeout: 5000});
1059
+
1060
+ // Change timeout for all future tasks
1061
+ queue.timeout = 10000;
1062
+ ```
1063
+ */
1064
+ timeout;
1065
+ constructor(options) {
1066
+ super();
1067
+ options = {
1068
+ carryoverIntervalCount: false,
1069
+ intervalCap: Number.POSITIVE_INFINITY,
1070
+ interval: 0,
1071
+ concurrency: Number.POSITIVE_INFINITY,
1072
+ autoStart: true,
1073
+ queueClass: PriorityQueue,
1074
+ strict: false,
1075
+ ...options
1076
+ };
1077
+ if (!(typeof options.intervalCap === "number" && options.intervalCap >= 1)) {
1078
+ throw new TypeError(`Expected \`intervalCap\` to be a number from 1 and up, got \`${options.intervalCap?.toString() ?? ""}\` (${typeof options.intervalCap})`);
1079
+ }
1080
+ if (options.interval === void 0 || !(Number.isFinite(options.interval) && options.interval >= 0)) {
1081
+ throw new TypeError(`Expected \`interval\` to be a finite number >= 0, got \`${options.interval?.toString() ?? ""}\` (${typeof options.interval})`);
1082
+ }
1083
+ if (options.strict && options.interval === 0) {
1084
+ throw new TypeError("The `strict` option requires a non-zero `interval`");
1085
+ }
1086
+ if (options.strict && options.intervalCap === Number.POSITIVE_INFINITY) {
1087
+ throw new TypeError("The `strict` option requires a finite `intervalCap`");
1088
+ }
1089
+ this.#carryoverIntervalCount = options.carryoverIntervalCount ?? options.carryoverConcurrencyCount ?? false;
1090
+ this.#isIntervalIgnored = options.intervalCap === Number.POSITIVE_INFINITY || options.interval === 0;
1091
+ this.#intervalCap = options.intervalCap;
1092
+ this.#interval = options.interval;
1093
+ this.#strict = options.strict;
1094
+ this.#queue = new options.queueClass();
1095
+ this.#queueClass = options.queueClass;
1096
+ this.concurrency = options.concurrency;
1097
+ if (options.timeout !== void 0 && !(Number.isFinite(options.timeout) && options.timeout > 0)) {
1098
+ throw new TypeError(`Expected \`timeout\` to be a positive finite number, got \`${options.timeout}\` (${typeof options.timeout})`);
1099
+ }
1100
+ this.timeout = options.timeout;
1101
+ this.#isPaused = options.autoStart === false;
1102
+ this.#setupRateLimitTracking();
1103
+ }
1104
+ #cleanupStrictTicks(now) {
1105
+ while (this.#strictTicksStartIndex < this.#strictTicks.length) {
1106
+ const oldestTick = this.#strictTicks[this.#strictTicksStartIndex];
1107
+ if (oldestTick !== void 0 && now - oldestTick >= this.#interval) {
1108
+ this.#strictTicksStartIndex++;
1109
+ } else {
1110
+ break;
1111
+ }
1112
+ }
1113
+ const shouldCompact = this.#strictTicksStartIndex > 100 && this.#strictTicksStartIndex > this.#strictTicks.length / 2 || this.#strictTicksStartIndex === this.#strictTicks.length;
1114
+ if (shouldCompact) {
1115
+ this.#strictTicks = this.#strictTicks.slice(this.#strictTicksStartIndex);
1116
+ this.#strictTicksStartIndex = 0;
1117
+ }
1118
+ }
1119
+ // Helper methods for interval consumption
1120
+ #consumeIntervalSlot(now) {
1121
+ if (this.#strict) {
1122
+ this.#strictTicks.push(now);
1123
+ } else {
1124
+ this.#intervalCount++;
1125
+ }
1126
+ }
1127
+ #rollbackIntervalSlot() {
1128
+ if (this.#strict) {
1129
+ if (this.#strictTicks.length > this.#strictTicksStartIndex) {
1130
+ this.#strictTicks.pop();
1131
+ }
1132
+ } else if (this.#intervalCount > 0) {
1133
+ this.#intervalCount--;
1134
+ }
1135
+ }
1136
+ #getActiveTicksCount() {
1137
+ return this.#strictTicks.length - this.#strictTicksStartIndex;
1138
+ }
1139
+ get #doesIntervalAllowAnother() {
1140
+ if (this.#isIntervalIgnored) {
1141
+ return true;
1142
+ }
1143
+ if (this.#strict) {
1144
+ return this.#getActiveTicksCount() < this.#intervalCap;
1145
+ }
1146
+ return this.#intervalCount < this.#intervalCap;
1147
+ }
1148
+ get #doesConcurrentAllowAnother() {
1149
+ return this.#pending < this.#concurrency;
1150
+ }
1151
+ #next() {
1152
+ this.#pending--;
1153
+ if (this.#pending === 0) {
1154
+ this.emit("pendingZero");
1155
+ }
1156
+ this.#tryToStartAnother();
1157
+ this.emit("next");
1158
+ }
1159
+ #onResumeInterval() {
1160
+ this.#timeoutId = void 0;
1161
+ this.#onInterval();
1162
+ this.#initializeIntervalIfNeeded();
1163
+ }
1164
+ #isIntervalPausedAt(now) {
1165
+ if (this.#strict) {
1166
+ this.#cleanupStrictTicks(now);
1167
+ const activeTicksCount = this.#getActiveTicksCount();
1168
+ if (activeTicksCount >= this.#intervalCap) {
1169
+ const oldestTick = this.#strictTicks[this.#strictTicksStartIndex];
1170
+ const delay = this.#interval - (now - oldestTick);
1171
+ this.#createIntervalTimeout(delay);
1172
+ return true;
1173
+ }
1174
+ return false;
1175
+ }
1176
+ if (this.#intervalId === void 0) {
1177
+ const delay = this.#intervalEnd - now;
1178
+ if (delay < 0) {
1179
+ if (this.#lastExecutionTime > 0) {
1180
+ const timeSinceLastExecution = now - this.#lastExecutionTime;
1181
+ if (timeSinceLastExecution < this.#interval) {
1182
+ this.#createIntervalTimeout(this.#interval - timeSinceLastExecution);
1183
+ return true;
1184
+ }
1185
+ }
1186
+ this.#intervalCount = this.#carryoverIntervalCount ? this.#pending : 0;
1187
+ } else {
1188
+ this.#createIntervalTimeout(delay);
1189
+ return true;
1190
+ }
1191
+ }
1192
+ return false;
1193
+ }
1194
+ #createIntervalTimeout(delay) {
1195
+ if (this.#timeoutId !== void 0) {
1196
+ return;
1197
+ }
1198
+ this.#timeoutId = setTimeout(() => {
1199
+ this.#onResumeInterval();
1200
+ }, delay);
1201
+ }
1202
+ #clearIntervalTimer() {
1203
+ if (this.#intervalId) {
1204
+ clearInterval(this.#intervalId);
1205
+ this.#intervalId = void 0;
1206
+ }
1207
+ }
1208
+ #clearTimeoutTimer() {
1209
+ if (this.#timeoutId) {
1210
+ clearTimeout(this.#timeoutId);
1211
+ this.#timeoutId = void 0;
1212
+ }
1213
+ }
1214
+ #tryToStartAnother() {
1215
+ if (this.#queue.size === 0) {
1216
+ this.#clearIntervalTimer();
1217
+ this.emit("empty");
1218
+ if (this.#pending === 0) {
1219
+ this.#clearTimeoutTimer();
1220
+ if (this.#strict && this.#strictTicksStartIndex > 0) {
1221
+ const now = Date.now();
1222
+ this.#cleanupStrictTicks(now);
1223
+ }
1224
+ this.emit("idle");
1225
+ }
1226
+ return false;
1227
+ }
1228
+ let taskStarted = false;
1229
+ if (!this.#isPaused) {
1230
+ const now = Date.now();
1231
+ const canInitializeInterval = !this.#isIntervalPausedAt(now);
1232
+ if (this.#doesIntervalAllowAnother && this.#doesConcurrentAllowAnother) {
1233
+ const job = this.#queue.dequeue();
1234
+ if (!this.#isIntervalIgnored) {
1235
+ this.#consumeIntervalSlot(now);
1236
+ this.#scheduleRateLimitUpdate();
1237
+ }
1238
+ this.emit("active");
1239
+ job();
1240
+ if (canInitializeInterval) {
1241
+ this.#initializeIntervalIfNeeded();
1242
+ }
1243
+ taskStarted = true;
1244
+ }
1245
+ }
1246
+ return taskStarted;
1247
+ }
1248
+ #initializeIntervalIfNeeded() {
1249
+ if (this.#isIntervalIgnored || this.#intervalId !== void 0) {
1250
+ return;
1251
+ }
1252
+ if (this.#strict) {
1253
+ return;
1254
+ }
1255
+ this.#intervalId = setInterval(() => {
1256
+ this.#onInterval();
1257
+ }, this.#interval);
1258
+ this.#intervalEnd = Date.now() + this.#interval;
1259
+ }
1260
+ #onInterval() {
1261
+ if (!this.#strict) {
1262
+ if (this.#intervalCount === 0 && this.#pending === 0 && this.#intervalId) {
1263
+ this.#clearIntervalTimer();
1264
+ }
1265
+ this.#intervalCount = this.#carryoverIntervalCount ? this.#pending : 0;
1266
+ }
1267
+ this.#processQueue();
1268
+ this.#scheduleRateLimitUpdate();
1269
+ }
1270
+ /**
1271
+ Executes all queued functions until it reaches the limit.
1272
+ */
1273
+ #processQueue() {
1274
+ while (this.#tryToStartAnother()) {
1275
+ }
1276
+ }
1277
+ get concurrency() {
1278
+ return this.#concurrency;
1279
+ }
1280
+ set concurrency(newConcurrency) {
1281
+ if (!(typeof newConcurrency === "number" && newConcurrency >= 1)) {
1282
+ throw new TypeError(`Expected \`concurrency\` to be a number from 1 and up, got \`${newConcurrency}\` (${typeof newConcurrency})`);
1283
+ }
1284
+ this.#concurrency = newConcurrency;
1285
+ this.#processQueue();
1286
+ }
1287
+ /**
1288
+ Updates the priority of a promise function by its id, affecting its execution order. Requires a defined concurrency limit to take effect.
1289
+
1290
+ For example, this can be used to prioritize a promise function to run earlier.
1291
+
1292
+ ```js
1293
+ import PQueue from 'p-queue';
1294
+
1295
+ const queue = new PQueue({concurrency: 1});
1296
+
1297
+ queue.add(async () => '🦄', {priority: 1});
1298
+ queue.add(async () => '🦀', {priority: 0, id: '🦀'});
1299
+ queue.add(async () => '🦄', {priority: 1});
1300
+ queue.add(async () => '🦄', {priority: 1});
1301
+
1302
+ queue.setPriority('🦀', 2);
1303
+ ```
1304
+
1305
+ In this case, the promise function with `id: '🦀'` runs second.
1306
+
1307
+ You can also deprioritize a promise function to delay its execution:
1308
+
1309
+ ```js
1310
+ import PQueue from 'p-queue';
1311
+
1312
+ const queue = new PQueue({concurrency: 1});
1313
+
1314
+ queue.add(async () => '🦄', {priority: 1});
1315
+ queue.add(async () => '🦀', {priority: 1, id: '🦀'});
1316
+ queue.add(async () => '🦄');
1317
+ queue.add(async () => '🦄', {priority: 0});
1318
+
1319
+ queue.setPriority('🦀', -1);
1320
+ ```
1321
+ Here, the promise function with `id: '🦀'` executes last.
1322
+ */
1323
+ setPriority(id, priority) {
1324
+ if (typeof priority !== "number" || !Number.isFinite(priority)) {
1325
+ throw new TypeError(`Expected \`priority\` to be a finite number, got \`${priority}\` (${typeof priority})`);
1326
+ }
1327
+ this.#queue.setPriority(id, priority);
1328
+ }
1329
+ async add(function_, options = {}) {
1330
+ options = {
1331
+ timeout: this.timeout,
1332
+ ...options,
1333
+ // Assign unique ID if not provided
1334
+ id: options.id ?? (this.#idAssigner++).toString()
1335
+ };
1336
+ return new Promise((resolve4, reject) => {
1337
+ const taskSymbol = /* @__PURE__ */ Symbol(`task-${options.id}`);
1338
+ this.#queue.enqueue(async () => {
1339
+ this.#pending++;
1340
+ this.#runningTasks.set(taskSymbol, {
1341
+ id: options.id,
1342
+ priority: options.priority ?? 0,
1343
+ // Match priority-queue default
1344
+ startTime: Date.now(),
1345
+ timeout: options.timeout
1346
+ });
1347
+ let eventListener;
1348
+ try {
1349
+ try {
1350
+ options.signal?.throwIfAborted();
1351
+ } catch (error) {
1352
+ this.#rollbackIntervalConsumption();
1353
+ this.#runningTasks.delete(taskSymbol);
1354
+ throw error;
1355
+ }
1356
+ this.#lastExecutionTime = Date.now();
1357
+ let operation = function_({ signal: options.signal });
1358
+ if (options.timeout) {
1359
+ operation = pTimeout(Promise.resolve(operation), {
1360
+ milliseconds: options.timeout,
1361
+ message: `Task timed out after ${options.timeout}ms (queue has ${this.#pending} running, ${this.#queue.size} waiting)`
1362
+ });
1363
+ }
1364
+ if (options.signal) {
1365
+ const { signal } = options;
1366
+ operation = Promise.race([operation, new Promise((_resolve, reject2) => {
1367
+ eventListener = () => {
1368
+ reject2(signal.reason);
1369
+ };
1370
+ signal.addEventListener("abort", eventListener, { once: true });
1371
+ })]);
1372
+ }
1373
+ const result = await operation;
1374
+ resolve4(result);
1375
+ this.emit("completed", result);
1376
+ } catch (error) {
1377
+ reject(error);
1378
+ this.emit("error", error);
1379
+ } finally {
1380
+ if (eventListener) {
1381
+ options.signal?.removeEventListener("abort", eventListener);
1382
+ }
1383
+ this.#runningTasks.delete(taskSymbol);
1384
+ queueMicrotask(() => {
1385
+ this.#next();
1386
+ });
1387
+ }
1388
+ }, options);
1389
+ this.emit("add");
1390
+ this.#tryToStartAnother();
1391
+ });
1392
+ }
1393
+ async addAll(functions, options) {
1394
+ return Promise.all(functions.map(async (function_) => this.add(function_, options)));
1395
+ }
1396
+ /**
1397
+ Start (or resume) executing enqueued tasks within concurrency limit. No need to call this if queue is not paused (via `options.autoStart = false` or by `.pause()` method.)
1398
+ */
1399
+ start() {
1400
+ if (!this.#isPaused) {
1401
+ return this;
1402
+ }
1403
+ this.#isPaused = false;
1404
+ this.#processQueue();
1405
+ return this;
1406
+ }
1407
+ /**
1408
+ Put queue execution on hold.
1409
+ */
1410
+ pause() {
1411
+ this.#isPaused = true;
1412
+ }
1413
+ /**
1414
+ Clear the queue.
1415
+ */
1416
+ clear() {
1417
+ this.#queue = new this.#queueClass();
1418
+ this.#clearIntervalTimer();
1419
+ this.#updateRateLimitState();
1420
+ this.emit("empty");
1421
+ if (this.#pending === 0) {
1422
+ this.#clearTimeoutTimer();
1423
+ this.emit("idle");
1424
+ }
1425
+ this.emit("next");
1426
+ }
1427
+ /**
1428
+ Can be called multiple times. Useful if you for example add additional items at a later time.
1429
+
1430
+ @returns A promise that settles when the queue becomes empty.
1431
+ */
1432
+ async onEmpty() {
1433
+ if (this.#queue.size === 0) {
1434
+ return;
1435
+ }
1436
+ await this.#onEvent("empty");
1437
+ }
1438
+ /**
1439
+ @returns A promise that settles when the queue size is less than the given limit: `queue.size < limit`.
1440
+
1441
+ If you want to avoid having the queue grow beyond a certain size you can `await queue.onSizeLessThan()` before adding a new item.
1442
+
1443
+ Note that this only limits the number of items waiting to start. There could still be up to `concurrency` jobs already running that this call does not include in its calculation.
1444
+ */
1445
+ async onSizeLessThan(limit) {
1446
+ if (this.#queue.size < limit) {
1447
+ return;
1448
+ }
1449
+ await this.#onEvent("next", () => this.#queue.size < limit);
1450
+ }
1451
+ /**
1452
+ The difference with `.onEmpty` is that `.onIdle` guarantees that all work from the queue has finished. `.onEmpty` merely signals that the queue is empty, but it could mean that some promises haven't completed yet.
1453
+
1454
+ @returns A promise that settles when the queue becomes empty, and all promises have completed; `queue.size === 0 && queue.pending === 0`.
1455
+ */
1456
+ async onIdle() {
1457
+ if (this.#pending === 0 && this.#queue.size === 0) {
1458
+ return;
1459
+ }
1460
+ await this.#onEvent("idle");
1461
+ }
1462
+ /**
1463
+ The difference with `.onIdle` is that `.onPendingZero` only waits for currently running tasks to finish, ignoring queued tasks.
1464
+
1465
+ @returns A promise that settles when all currently running tasks have completed; `queue.pending === 0`.
1466
+ */
1467
+ async onPendingZero() {
1468
+ if (this.#pending === 0) {
1469
+ return;
1470
+ }
1471
+ await this.#onEvent("pendingZero");
1472
+ }
1473
+ /**
1474
+ @returns A promise that settles when the queue becomes rate-limited due to intervalCap.
1475
+ */
1476
+ async onRateLimit() {
1477
+ if (this.isRateLimited) {
1478
+ return;
1479
+ }
1480
+ await this.#onEvent("rateLimit");
1481
+ }
1482
+ /**
1483
+ @returns A promise that settles when the queue is no longer rate-limited.
1484
+ */
1485
+ async onRateLimitCleared() {
1486
+ if (!this.isRateLimited) {
1487
+ return;
1488
+ }
1489
+ await this.#onEvent("rateLimitCleared");
1490
+ }
1491
+ /**
1492
+ @returns A promise that rejects when any task in the queue errors.
1493
+
1494
+ Use with `Promise.race([queue.onError(), queue.onIdle()])` to fail fast on the first error while still resolving normally when the queue goes idle.
1495
+
1496
+ Important: The promise returned by `add()` still rejects. You must handle each `add()` promise (for example, `.catch(() => {})`) to avoid unhandled rejections.
1497
+
1498
+ @example
1499
+ ```
1500
+ import PQueue from 'p-queue';
1501
+
1502
+ const queue = new PQueue({concurrency: 2});
1503
+
1504
+ queue.add(() => fetchData(1)).catch(() => {});
1505
+ queue.add(() => fetchData(2)).catch(() => {});
1506
+ queue.add(() => fetchData(3)).catch(() => {});
1507
+
1508
+ // Stop processing on first error
1509
+ try {
1510
+ await Promise.race([
1511
+ queue.onError(),
1512
+ queue.onIdle()
1513
+ ]);
1514
+ } catch (error) {
1515
+ queue.pause(); // Stop processing remaining tasks
1516
+ console.error('Queue failed:', error);
1517
+ }
1518
+ ```
1519
+ */
1520
+ // eslint-disable-next-line @typescript-eslint/promise-function-async
1521
+ onError() {
1522
+ return new Promise((_resolve, reject) => {
1523
+ const handleError = (error) => {
1524
+ this.off("error", handleError);
1525
+ reject(error);
1526
+ };
1527
+ this.on("error", handleError);
1528
+ });
1529
+ }
1530
+ async #onEvent(event, filter) {
1531
+ return new Promise((resolve4) => {
1532
+ const listener = () => {
1533
+ if (filter && !filter()) {
1534
+ return;
1535
+ }
1536
+ this.off(event, listener);
1537
+ resolve4();
1538
+ };
1539
+ this.on(event, listener);
1540
+ });
1541
+ }
1542
+ /**
1543
+ Size of the queue, the number of queued items waiting to run.
1544
+ */
1545
+ get size() {
1546
+ return this.#queue.size;
1547
+ }
1548
+ /**
1549
+ Size of the queue, filtered by the given options.
1550
+
1551
+ For example, this can be used to find the number of items remaining in the queue with a specific priority level.
1552
+ */
1553
+ sizeBy(options) {
1554
+ return this.#queue.filter(options).length;
1555
+ }
1556
+ /**
1557
+ Number of running items (no longer in the queue).
1558
+ */
1559
+ get pending() {
1560
+ return this.#pending;
1561
+ }
1562
+ /**
1563
+ Whether the queue is currently paused.
1564
+ */
1565
+ get isPaused() {
1566
+ return this.#isPaused;
1567
+ }
1568
+ #setupRateLimitTracking() {
1569
+ if (this.#isIntervalIgnored) {
1570
+ return;
1571
+ }
1572
+ this.on("add", () => {
1573
+ if (this.#queue.size > 0) {
1574
+ this.#scheduleRateLimitUpdate();
1575
+ }
1576
+ });
1577
+ this.on("next", () => {
1578
+ this.#scheduleRateLimitUpdate();
1579
+ });
1580
+ }
1581
+ #scheduleRateLimitUpdate() {
1582
+ if (this.#isIntervalIgnored || this.#rateLimitFlushScheduled) {
1583
+ return;
1584
+ }
1585
+ this.#rateLimitFlushScheduled = true;
1586
+ queueMicrotask(() => {
1587
+ this.#rateLimitFlushScheduled = false;
1588
+ this.#updateRateLimitState();
1589
+ });
1590
+ }
1591
+ #rollbackIntervalConsumption() {
1592
+ if (this.#isIntervalIgnored) {
1593
+ return;
1594
+ }
1595
+ this.#rollbackIntervalSlot();
1596
+ this.#scheduleRateLimitUpdate();
1597
+ }
1598
+ #updateRateLimitState() {
1599
+ const previous = this.#rateLimitedInInterval;
1600
+ if (this.#isIntervalIgnored || this.#queue.size === 0) {
1601
+ if (previous) {
1602
+ this.#rateLimitedInInterval = false;
1603
+ this.emit("rateLimitCleared");
1604
+ }
1605
+ return;
1606
+ }
1607
+ let count;
1608
+ if (this.#strict) {
1609
+ const now = Date.now();
1610
+ this.#cleanupStrictTicks(now);
1611
+ count = this.#getActiveTicksCount();
1612
+ } else {
1613
+ count = this.#intervalCount;
1614
+ }
1615
+ const shouldBeRateLimited = count >= this.#intervalCap;
1616
+ if (shouldBeRateLimited !== previous) {
1617
+ this.#rateLimitedInInterval = shouldBeRateLimited;
1618
+ this.emit(shouldBeRateLimited ? "rateLimit" : "rateLimitCleared");
1619
+ }
1620
+ }
1621
+ /**
1622
+ Whether the queue is currently rate-limited due to intervalCap.
1623
+ */
1624
+ get isRateLimited() {
1625
+ return this.#rateLimitedInInterval;
1626
+ }
1627
+ /**
1628
+ Whether the queue is saturated. Returns `true` when:
1629
+ - All concurrency slots are occupied and tasks are waiting, OR
1630
+ - The queue is rate-limited and tasks are waiting
1631
+
1632
+ Useful for detecting backpressure and potential hanging tasks.
1633
+
1634
+ ```js
1635
+ import PQueue from 'p-queue';
1636
+
1637
+ const queue = new PQueue({concurrency: 2});
1638
+
1639
+ // Backpressure handling
1640
+ if (queue.isSaturated) {
1641
+ console.log('Queue is saturated, waiting for capacity...');
1642
+ await queue.onSizeLessThan(queue.concurrency);
1643
+ }
1644
+
1645
+ // Monitoring for stuck tasks
1646
+ setInterval(() => {
1647
+ if (queue.isSaturated) {
1648
+ console.warn(`Queue saturated: ${queue.pending} running, ${queue.size} waiting`);
1649
+ }
1650
+ }, 60000);
1651
+ ```
1652
+ */
1653
+ get isSaturated() {
1654
+ return this.#pending === this.#concurrency && this.#queue.size > 0 || this.isRateLimited && this.#queue.size > 0;
1655
+ }
1656
+ /**
1657
+ The tasks currently being executed. Each task includes its `id`, `priority`, `startTime`, and `timeout` (if set).
1658
+
1659
+ Returns an array of task info objects.
1660
+
1661
+ ```js
1662
+ import PQueue from 'p-queue';
1663
+
1664
+ const queue = new PQueue({concurrency: 2});
1665
+
1666
+ // Add tasks with IDs for better debugging
1667
+ queue.add(() => fetchUser(123), {id: 'user-123'});
1668
+ queue.add(() => fetchPosts(456), {id: 'posts-456', priority: 1});
1669
+
1670
+ // Check what's running
1671
+ console.log(queue.runningTasks);
1672
+ // => [{
1673
+ // id: 'user-123',
1674
+ // priority: 0,
1675
+ // startTime: 1759253001716,
1676
+ // timeout: undefined
1677
+ // }, {
1678
+ // id: 'posts-456',
1679
+ // priority: 1,
1680
+ // startTime: 1759253001916,
1681
+ // timeout: undefined
1682
+ // }]
1683
+ ```
1684
+ */
1685
+ get runningTasks() {
1686
+ return [...this.#runningTasks.values()].map((task) => ({ ...task }));
1687
+ }
1688
+ };
1689
+
1690
+ // node_modules/is-network-error/index.js
1691
+ var objectToString = Object.prototype.toString;
1692
+ var isError = (value) => objectToString.call(value) === "[object Error]";
1693
+ var errorMessages = /* @__PURE__ */ new Set([
1694
+ "network error",
1695
+ // Chrome
1696
+ "Failed to fetch",
1697
+ // Chrome
1698
+ "NetworkError when attempting to fetch resource.",
1699
+ // Firefox
1700
+ "The Internet connection appears to be offline.",
1701
+ // Safari 16
1702
+ "Network request failed",
1703
+ // `cross-fetch`
1704
+ "fetch failed",
1705
+ // Undici (Node.js)
1706
+ "terminated",
1707
+ // Undici (Node.js)
1708
+ " A network error occurred.",
1709
+ // Bun (WebKit)
1710
+ "Network connection lost"
1711
+ // Cloudflare Workers (fetch)
1712
+ ]);
1713
+ function isNetworkError(error) {
1714
+ const isValid = error && isError(error) && error.name === "TypeError" && typeof error.message === "string";
1715
+ if (!isValid) {
1716
+ return false;
1717
+ }
1718
+ const { message, stack } = error;
1719
+ if (message === "Load failed") {
1720
+ return stack === void 0 || "__sentry_captured__" in error;
1721
+ }
1722
+ if (message.startsWith("error sending request for url")) {
1723
+ return true;
1724
+ }
1725
+ return errorMessages.has(message);
1726
+ }
1727
+
1728
+ // node_modules/p-retry/index.js
1729
+ function validateRetries(retries) {
1730
+ if (typeof retries === "number") {
1731
+ if (retries < 0) {
1732
+ throw new TypeError("Expected `retries` to be a non-negative number.");
1733
+ }
1734
+ if (Number.isNaN(retries)) {
1735
+ throw new TypeError("Expected `retries` to be a valid number or Infinity, got NaN.");
1736
+ }
1737
+ } else if (retries !== void 0) {
1738
+ throw new TypeError("Expected `retries` to be a number or Infinity.");
1739
+ }
1740
+ }
1741
+ function validateNumberOption(name, value, { min = 0, allowInfinity = false } = {}) {
1742
+ if (value === void 0) {
1743
+ return;
1744
+ }
1745
+ if (typeof value !== "number" || Number.isNaN(value)) {
1746
+ throw new TypeError(`Expected \`${name}\` to be a number${allowInfinity ? " or Infinity" : ""}.`);
1747
+ }
1748
+ if (!allowInfinity && !Number.isFinite(value)) {
1749
+ throw new TypeError(`Expected \`${name}\` to be a finite number.`);
1750
+ }
1751
+ if (value < min) {
1752
+ throw new TypeError(`Expected \`${name}\` to be \u2265 ${min}.`);
1753
+ }
1754
+ }
1755
+ var AbortError = class extends Error {
1756
+ constructor(message) {
1757
+ super();
1758
+ if (message instanceof Error) {
1759
+ this.originalError = message;
1760
+ ({ message } = message);
1761
+ } else {
1762
+ this.originalError = new Error(message);
1763
+ this.originalError.stack = this.stack;
1764
+ }
1765
+ this.name = "AbortError";
1766
+ this.message = message;
1767
+ }
1768
+ };
1769
+ function calculateDelay(retriesConsumed, options) {
1770
+ const attempt = Math.max(1, retriesConsumed + 1);
1771
+ const random = options.randomize ? Math.random() + 1 : 1;
1772
+ let timeout = Math.round(random * options.minTimeout * options.factor ** (attempt - 1));
1773
+ timeout = Math.min(timeout, options.maxTimeout);
1774
+ return timeout;
1775
+ }
1776
+ function calculateRemainingTime(start, max) {
1777
+ if (!Number.isFinite(max)) {
1778
+ return max;
1779
+ }
1780
+ return max - (performance.now() - start);
1781
+ }
1782
+ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTime, options }) {
1783
+ const normalizedError = error instanceof Error ? error : new TypeError(`Non-error was thrown: "${error}". You should only throw errors.`);
1784
+ if (normalizedError instanceof AbortError) {
1785
+ throw normalizedError.originalError;
1786
+ }
1787
+ const retriesLeft = Number.isFinite(options.retries) ? Math.max(0, options.retries - retriesConsumed) : options.retries;
1788
+ const maxRetryTime = options.maxRetryTime ?? Number.POSITIVE_INFINITY;
1789
+ const context = Object.freeze({
1790
+ error: normalizedError,
1791
+ attemptNumber,
1792
+ retriesLeft,
1793
+ retriesConsumed
1794
+ });
1795
+ await options.onFailedAttempt(context);
1796
+ if (calculateRemainingTime(startTime, maxRetryTime) <= 0) {
1797
+ throw normalizedError;
1798
+ }
1799
+ const consumeRetry = await options.shouldConsumeRetry(context);
1800
+ const remainingTime = calculateRemainingTime(startTime, maxRetryTime);
1801
+ if (remainingTime <= 0 || retriesLeft <= 0) {
1802
+ throw normalizedError;
1803
+ }
1804
+ if (normalizedError instanceof TypeError && !isNetworkError(normalizedError)) {
1805
+ if (consumeRetry) {
1806
+ throw normalizedError;
1807
+ }
1808
+ options.signal?.throwIfAborted();
1809
+ return false;
1810
+ }
1811
+ if (!await options.shouldRetry(context)) {
1812
+ throw normalizedError;
1813
+ }
1814
+ if (!consumeRetry) {
1815
+ options.signal?.throwIfAborted();
1816
+ return false;
1817
+ }
1818
+ const delayTime = calculateDelay(retriesConsumed, options);
1819
+ const finalDelay = Math.min(delayTime, remainingTime);
1820
+ options.signal?.throwIfAborted();
1821
+ if (finalDelay > 0) {
1822
+ await new Promise((resolve4, reject) => {
1823
+ const onAbort = () => {
1824
+ clearTimeout(timeoutToken);
1825
+ options.signal?.removeEventListener("abort", onAbort);
1826
+ reject(options.signal.reason);
1827
+ };
1828
+ const timeoutToken = setTimeout(() => {
1829
+ options.signal?.removeEventListener("abort", onAbort);
1830
+ resolve4();
1831
+ }, finalDelay);
1832
+ if (options.unref) {
1833
+ timeoutToken.unref?.();
1834
+ }
1835
+ options.signal?.addEventListener("abort", onAbort, { once: true });
1836
+ });
1837
+ }
1838
+ options.signal?.throwIfAborted();
1839
+ return true;
1840
+ }
1841
+ async function pRetry(input, options = {}) {
1842
+ options = { ...options };
1843
+ validateRetries(options.retries);
1844
+ if (Object.hasOwn(options, "forever")) {
1845
+ throw new Error("The `forever` option is no longer supported. For many use-cases, you can set `retries: Infinity` instead.");
1846
+ }
1847
+ options.retries ??= 10;
1848
+ options.factor ??= 2;
1849
+ options.minTimeout ??= 1e3;
1850
+ options.maxTimeout ??= Number.POSITIVE_INFINITY;
1851
+ options.maxRetryTime ??= Number.POSITIVE_INFINITY;
1852
+ options.randomize ??= false;
1853
+ options.onFailedAttempt ??= () => {
1854
+ };
1855
+ options.shouldRetry ??= () => true;
1856
+ options.shouldConsumeRetry ??= () => true;
1857
+ validateNumberOption("factor", options.factor, { min: 0, allowInfinity: false });
1858
+ validateNumberOption("minTimeout", options.minTimeout, { min: 0, allowInfinity: false });
1859
+ validateNumberOption("maxTimeout", options.maxTimeout, { min: 0, allowInfinity: true });
1860
+ validateNumberOption("maxRetryTime", options.maxRetryTime, { min: 0, allowInfinity: true });
1861
+ if (!(options.factor > 0)) {
1862
+ options.factor = 1;
1863
+ }
1864
+ options.signal?.throwIfAborted();
1865
+ let attemptNumber = 0;
1866
+ let retriesConsumed = 0;
1867
+ const startTime = performance.now();
1868
+ while (Number.isFinite(options.retries) ? retriesConsumed <= options.retries : true) {
1869
+ attemptNumber++;
1870
+ try {
1871
+ options.signal?.throwIfAborted();
1872
+ const result = await input(attemptNumber);
1873
+ options.signal?.throwIfAborted();
1874
+ return result;
1875
+ } catch (error) {
1876
+ if (await onAttemptFailure({
1877
+ error,
1878
+ attemptNumber,
1879
+ retriesConsumed,
1880
+ startTime,
1881
+ options
1882
+ })) {
1883
+ retriesConsumed++;
1884
+ }
1885
+ }
1886
+ }
1887
+ throw new Error("Retry attempts exhausted without throwing an error.");
1888
+ }
1889
+
1890
+ // src/embeddings/detector.ts
1891
+ import { existsSync, readFileSync } from "fs";
1892
+ import * as path from "path";
1893
+ import * as os from "os";
1894
+ function getOpenCodeAuthPath() {
1895
+ return path.join(os.homedir(), ".local", "share", "opencode", "auth.json");
1896
+ }
1897
+ function loadOpenCodeAuth() {
1898
+ const authPath = getOpenCodeAuthPath();
1899
+ try {
1900
+ if (existsSync(authPath)) {
1901
+ return JSON.parse(readFileSync(authPath, "utf-8"));
1902
+ }
1903
+ } catch {
1904
+ }
1905
+ return {};
1906
+ }
1907
+ async function detectEmbeddingProvider(preferredProvider, model) {
1908
+ const credentials = await getProviderCredentials(preferredProvider);
1909
+ if (credentials) {
1910
+ if (!model) {
1911
+ return {
1912
+ provider: preferredProvider,
1913
+ credentials,
1914
+ modelInfo: getDefaultModelForProvider(preferredProvider)
1915
+ };
1916
+ }
1917
+ if (!isValidModel(model, preferredProvider)) {
1918
+ throw new Error(
1919
+ `Model '${model}' is not supported by provider '${preferredProvider}'`
1920
+ );
1921
+ }
1922
+ const providerModels = EMBEDDING_MODELS[preferredProvider];
1923
+ return {
1924
+ provider: preferredProvider,
1925
+ credentials,
1926
+ modelInfo: providerModels[model]
1927
+ };
1928
+ }
1929
+ throw new Error(
1930
+ `Preferred provider '${preferredProvider}' is not configured or authenticated`
1931
+ );
1932
+ }
1933
+ async function tryDetectProvider() {
1934
+ for (const provider of availableProviders) {
1935
+ const credentials = await getProviderCredentials(provider);
1936
+ if (credentials) {
1937
+ return {
1938
+ provider,
1939
+ credentials,
1940
+ modelInfo: getDefaultModelForProvider(provider)
1941
+ };
1942
+ }
1943
+ }
1944
+ throw new Error(
1945
+ `No embedding-capable provider found. Please authenticate with OpenCode using one of: ${availableProviders.join(", ")}.`
1946
+ );
1947
+ }
1948
+ async function getProviderCredentials(provider) {
1949
+ switch (provider) {
1950
+ case "github-copilot":
1951
+ return getGitHubCopilotCredentials();
1952
+ case "openai":
1953
+ return getOpenAICredentials();
1954
+ case "google":
1955
+ return getGoogleCredentials();
1956
+ case "ollama":
1957
+ return getOllamaCredentials();
1958
+ default:
1959
+ return null;
1960
+ }
1961
+ }
1962
+ function getGitHubCopilotCredentials() {
1963
+ const authData = loadOpenCodeAuth();
1964
+ const copilotAuth = authData["github-copilot"] || authData["github-copilot-enterprise"];
1965
+ if (!copilotAuth || copilotAuth.type !== "oauth") {
1966
+ return null;
1967
+ }
1968
+ const baseUrl = copilotAuth.enterpriseUrl ? `https://copilot-api.${copilotAuth.enterpriseUrl.replace(/^https?:\/\//, "").replace(/\/$/, "")}` : "https://models.github.ai";
1969
+ return {
1970
+ provider: "github-copilot",
1971
+ baseUrl,
1972
+ refreshToken: copilotAuth.refresh,
1973
+ accessToken: copilotAuth.access,
1974
+ tokenExpires: copilotAuth.expires
1975
+ };
1976
+ }
1977
+ function getOpenAICredentials() {
1978
+ const authData = loadOpenCodeAuth();
1979
+ const openaiAuth = authData["openai"];
1980
+ if (openaiAuth?.type === "api") {
1981
+ return {
1982
+ provider: "openai",
1983
+ apiKey: openaiAuth.key,
1984
+ baseUrl: "https://api.openai.com/v1"
1985
+ };
1986
+ }
1987
+ return null;
1988
+ }
1989
+ function getGoogleCredentials() {
1990
+ const authData = loadOpenCodeAuth();
1991
+ const googleAuth = authData["google"] || authData["google-generative-ai"];
1992
+ if (googleAuth?.type === "api") {
1993
+ return {
1994
+ provider: "google",
1995
+ apiKey: googleAuth.key,
1996
+ baseUrl: "https://generativelanguage.googleapis.com/v1beta"
1997
+ };
1998
+ }
1999
+ return null;
2000
+ }
2001
+ async function getOllamaCredentials() {
2002
+ const baseUrl = process.env.OLLAMA_HOST || "http://localhost:11434";
2003
+ try {
2004
+ const controller = new AbortController();
2005
+ const timeoutId = setTimeout(() => controller.abort(), 2e3);
2006
+ const response = await fetch(`${baseUrl}/api/tags`, {
2007
+ signal: controller.signal
2008
+ });
2009
+ clearTimeout(timeoutId);
2010
+ if (response.ok) {
2011
+ const data = await response.json();
2012
+ const hasEmbeddingModel = data.models?.some(
2013
+ (m) => m.name.includes("nomic-embed") || m.name.includes("mxbai-embed") || m.name.includes("all-minilm")
2014
+ );
2015
+ if (hasEmbeddingModel) {
2016
+ return {
2017
+ provider: "ollama",
2018
+ baseUrl
2019
+ };
2020
+ }
2021
+ }
2022
+ } catch {
2023
+ return null;
2024
+ }
2025
+ return null;
2026
+ }
2027
+ function getProviderDisplayName(provider) {
2028
+ switch (provider) {
2029
+ case "github-copilot":
2030
+ return "GitHub Copilot";
2031
+ case "openai":
2032
+ return "OpenAI";
2033
+ case "google":
2034
+ return "Google (Gemini)";
2035
+ case "ollama":
2036
+ return "Ollama (Local)";
2037
+ default:
2038
+ return provider;
2039
+ }
2040
+ }
2041
+
2042
+ // src/embeddings/provider.ts
2043
+ function createEmbeddingProvider(configuredProviderInfo) {
2044
+ switch (configuredProviderInfo.provider) {
2045
+ case "github-copilot":
2046
+ return new GitHubCopilotEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
2047
+ case "openai":
2048
+ return new OpenAIEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
2049
+ case "google":
2050
+ return new GoogleEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
2051
+ case "ollama":
2052
+ return new OllamaEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
2053
+ default: {
2054
+ const _exhaustive = configuredProviderInfo;
2055
+ throw new Error(`Unsupported embedding provider: ${_exhaustive.provider}`);
2056
+ }
2057
+ }
2058
+ }
2059
+ var GitHubCopilotEmbeddingProvider = class {
2060
+ constructor(credentials, modelInfo) {
2061
+ this.credentials = credentials;
2062
+ this.modelInfo = modelInfo;
2063
+ }
2064
+ getToken() {
2065
+ if (!this.credentials.refreshToken) {
2066
+ throw new Error("No OAuth token available for GitHub");
2067
+ }
2068
+ return this.credentials.refreshToken;
2069
+ }
2070
+ async embedQuery(query) {
2071
+ const result = await this.embedBatch([query]);
2072
+ return {
2073
+ embedding: result.embeddings[0],
2074
+ tokensUsed: result.totalTokensUsed
2075
+ };
2076
+ }
2077
+ async embedDocument(document) {
2078
+ const result = await this.embedBatch([document]);
2079
+ return {
2080
+ embedding: result.embeddings[0],
2081
+ tokensUsed: result.totalTokensUsed
2082
+ };
2083
+ }
2084
+ async embedBatch(texts) {
2085
+ const token = this.getToken();
2086
+ const response = await fetch(`${this.credentials.baseUrl}/inference/embeddings`, {
2087
+ method: "POST",
2088
+ headers: {
2089
+ Authorization: `Bearer ${token}`,
2090
+ "Content-Type": "application/json",
2091
+ Accept: "application/vnd.github+json",
2092
+ "X-GitHub-Api-Version": "2022-11-28"
2093
+ },
2094
+ body: JSON.stringify({
2095
+ model: `openai/${this.modelInfo.model}`,
2096
+ input: texts
2097
+ })
2098
+ });
2099
+ if (!response.ok) {
2100
+ const error = await response.text();
2101
+ throw new Error(`GitHub Copilot embedding API error: ${response.status} - ${error}`);
2102
+ }
2103
+ const data = await response.json();
2104
+ return {
2105
+ embeddings: data.data.map((d) => d.embedding),
2106
+ totalTokensUsed: data.usage.total_tokens
2107
+ };
2108
+ }
2109
+ getModelInfo() {
2110
+ return this.modelInfo;
2111
+ }
2112
+ };
2113
+ var OpenAIEmbeddingProvider = class {
2114
+ constructor(credentials, modelInfo) {
2115
+ this.credentials = credentials;
2116
+ this.modelInfo = modelInfo;
2117
+ }
2118
+ async embedQuery(query) {
2119
+ const result = await this.embedBatch([query]);
2120
+ return {
2121
+ embedding: result.embeddings[0],
2122
+ tokensUsed: result.totalTokensUsed
2123
+ };
2124
+ }
2125
+ async embedDocument(document) {
2126
+ const result = await this.embedBatch([document]);
2127
+ return {
2128
+ embedding: result.embeddings[0],
2129
+ tokensUsed: result.totalTokensUsed
2130
+ };
2131
+ }
2132
+ async embedBatch(texts) {
2133
+ const response = await fetch(`${this.credentials.baseUrl}/embeddings`, {
2134
+ method: "POST",
2135
+ headers: {
2136
+ Authorization: `Bearer ${this.credentials.apiKey}`,
2137
+ "Content-Type": "application/json"
2138
+ },
2139
+ body: JSON.stringify({
2140
+ model: this.modelInfo.model,
2141
+ input: texts
2142
+ })
2143
+ });
2144
+ if (!response.ok) {
2145
+ const error = await response.text();
2146
+ throw new Error(`OpenAI embedding API error: ${response.status} - ${error}`);
2147
+ }
2148
+ const data = await response.json();
2149
+ return {
2150
+ embeddings: data.data.map((d) => d.embedding),
2151
+ totalTokensUsed: data.usage.total_tokens
2152
+ };
2153
+ }
2154
+ getModelInfo() {
2155
+ return this.modelInfo;
2156
+ }
2157
+ };
2158
+ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider {
2159
+ constructor(credentials, modelInfo) {
2160
+ this.credentials = credentials;
2161
+ this.modelInfo = modelInfo;
2162
+ }
2163
+ static BATCH_SIZE = 20;
2164
+ async embedQuery(query) {
2165
+ const taskType = this.modelInfo.taskAble ? "CODE_RETRIEVAL_QUERY" : void 0;
2166
+ const result = await this.embedWithTaskType([query], taskType);
2167
+ return {
2168
+ embedding: result.embeddings[0],
2169
+ tokensUsed: result.totalTokensUsed
2170
+ };
2171
+ }
2172
+ async embedDocument(document) {
2173
+ const taskType = this.modelInfo.taskAble ? "RETRIEVAL_DOCUMENT" : void 0;
2174
+ const result = await this.embedWithTaskType([document], taskType);
2175
+ return {
2176
+ embedding: result.embeddings[0],
2177
+ tokensUsed: result.totalTokensUsed
2178
+ };
2179
+ }
2180
+ async embedBatch(texts) {
2181
+ const taskType = this.modelInfo.taskAble ? "RETRIEVAL_DOCUMENT" : void 0;
2182
+ return this.embedWithTaskType(texts, taskType);
2183
+ }
2184
+ /**
2185
+ * Embeds texts using the Google embedContent API.
2186
+ * Sends multiple texts as parts in batched requests (up to BATCH_SIZE per call).
2187
+ * When taskType is provided (gemini-embedding-001), includes it in the request
2188
+ * for task-specific embedding optimization.
2189
+ */
2190
+ async embedWithTaskType(texts, taskType) {
2191
+ const batches = [];
2192
+ for (let i = 0; i < texts.length; i += _GoogleEmbeddingProvider.BATCH_SIZE) {
2193
+ batches.push(texts.slice(i, i + _GoogleEmbeddingProvider.BATCH_SIZE));
2194
+ }
2195
+ const batchResults = await Promise.all(
2196
+ batches.map(async (batch) => {
2197
+ const requests = batch.map((text) => ({
2198
+ model: `models/${this.modelInfo.model}`,
2199
+ content: {
2200
+ parts: [{ text }]
2201
+ },
2202
+ taskType,
2203
+ outputDimensionality: this.modelInfo.dimensions
2204
+ }));
2205
+ const response = await fetch(
2206
+ `${this.credentials.baseUrl}/models/${this.modelInfo.model}:batchEmbedContents?key=${this.credentials.apiKey}`,
2207
+ {
2208
+ method: "POST",
2209
+ headers: {
2210
+ "Content-Type": "application/json"
2211
+ },
2212
+ body: JSON.stringify({ requests })
2213
+ }
2214
+ );
2215
+ if (!response.ok) {
2216
+ const error = await response.text();
2217
+ throw new Error(`Google embedding API error: ${response.status} - ${error}`);
2218
+ }
2219
+ const data = await response.json();
2220
+ return {
2221
+ embeddings: data.embeddings.map((e) => e.values),
2222
+ tokensUsed: batch.reduce((sum, text) => sum + Math.ceil(text.length / 4), 0)
2223
+ };
2224
+ })
2225
+ );
2226
+ return {
2227
+ embeddings: batchResults.flatMap((r) => r.embeddings),
2228
+ totalTokensUsed: batchResults.reduce((sum, r) => sum + r.tokensUsed, 0)
2229
+ };
2230
+ }
2231
+ getModelInfo() {
2232
+ return this.modelInfo;
2233
+ }
2234
+ };
2235
+ var OllamaEmbeddingProvider = class {
2236
+ constructor(credentials, modelInfo) {
2237
+ this.credentials = credentials;
2238
+ this.modelInfo = modelInfo;
2239
+ }
2240
+ async embedQuery(query) {
2241
+ const result = await this.embedBatch([query]);
2242
+ return {
2243
+ embedding: result.embeddings[0],
2244
+ tokensUsed: result.totalTokensUsed
2245
+ };
2246
+ }
2247
+ async embedDocument(document) {
2248
+ const result = await this.embedBatch([document]);
2249
+ return {
2250
+ embedding: result.embeddings[0],
2251
+ tokensUsed: result.totalTokensUsed
2252
+ };
2253
+ }
2254
+ async embedBatch(texts) {
2255
+ const results = await Promise.all(
2256
+ texts.map(async (text) => {
2257
+ const response = await fetch(`${this.credentials.baseUrl}/api/embeddings`, {
2258
+ method: "POST",
2259
+ headers: {
2260
+ "Content-Type": "application/json"
2261
+ },
2262
+ body: JSON.stringify({
2263
+ model: this.modelInfo.model,
2264
+ prompt: text
2265
+ })
2266
+ });
2267
+ if (!response.ok) {
2268
+ const error = await response.text();
2269
+ throw new Error(`Ollama embedding API error: ${response.status} - ${error}`);
2270
+ }
2271
+ const data = await response.json();
2272
+ return {
2273
+ embedding: data.embedding,
2274
+ tokensUsed: Math.ceil(text.length / 4)
2275
+ };
2276
+ })
2277
+ );
2278
+ return {
2279
+ embeddings: results.map((r) => r.embedding),
2280
+ totalTokensUsed: results.reduce((sum, r) => sum + r.tokensUsed, 0)
2281
+ };
2282
+ }
2283
+ getModelInfo() {
2284
+ return this.modelInfo;
2285
+ }
2286
+ };
2287
+
2288
+ // src/utils/files.ts
2289
+ var import_ignore = __toESM(require_ignore(), 1);
2290
+ import { existsSync as existsSync2, readFileSync as readFileSync2, promises as fsPromises } from "fs";
2291
+ import * as path2 from "path";
2292
+ function createIgnoreFilter(projectRoot) {
2293
+ const ig = (0, import_ignore.default)();
2294
+ const defaultIgnores = [
2295
+ "node_modules",
2296
+ ".git",
2297
+ "dist",
2298
+ "build",
2299
+ ".next",
2300
+ ".nuxt",
2301
+ "coverage",
2302
+ "__pycache__",
2303
+ "target",
2304
+ "vendor",
2305
+ ".opencode"
2306
+ ];
2307
+ ig.add(defaultIgnores);
2308
+ const gitignorePath = path2.join(projectRoot, ".gitignore");
2309
+ if (existsSync2(gitignorePath)) {
2310
+ const gitignoreContent = readFileSync2(gitignorePath, "utf-8");
2311
+ ig.add(gitignoreContent);
2312
+ }
2313
+ return ig;
2314
+ }
2315
+ function matchGlob(filePath, pattern) {
2316
+ let regexPattern = pattern.replace(/\*\*/g, "<<<DOUBLESTAR>>>").replace(/\*/g, "[^/]*").replace(/<<<DOUBLESTAR>>>/g, ".*").replace(/\?/g, ".").replace(/\{([^}]+)\}/g, (_, p1) => `(${p1.split(",").join("|")})`);
2317
+ if (regexPattern.startsWith(".*/")) {
2318
+ regexPattern = `(.*\\/)?${regexPattern.slice(3)}`;
2319
+ }
2320
+ const regex = new RegExp(`^${regexPattern}$`);
2321
+ return regex.test(filePath);
2322
+ }
2323
+ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns, ignoreFilter, maxFileSize, skipped) {
2324
+ const entries = await fsPromises.readdir(dir, { withFileTypes: true });
2325
+ for (const entry of entries) {
2326
+ const fullPath = path2.join(dir, entry.name);
2327
+ const relativePath = path2.relative(projectRoot, fullPath);
2328
+ if (ignoreFilter.ignores(relativePath)) {
2329
+ if (entry.isFile()) {
2330
+ skipped.push({ path: relativePath, reason: "gitignore" });
2331
+ }
2332
+ continue;
2333
+ }
2334
+ if (entry.isDirectory()) {
2335
+ yield* walkDirectory(
2336
+ fullPath,
2337
+ projectRoot,
2338
+ includePatterns,
2339
+ excludePatterns,
2340
+ ignoreFilter,
2341
+ maxFileSize,
2342
+ skipped
2343
+ );
2344
+ } else if (entry.isFile()) {
2345
+ const stat = await fsPromises.stat(fullPath);
2346
+ if (stat.size > maxFileSize) {
2347
+ skipped.push({ path: relativePath, reason: "too_large" });
2348
+ continue;
2349
+ }
2350
+ for (const pattern of excludePatterns) {
2351
+ if (matchGlob(relativePath, pattern)) {
2352
+ skipped.push({ path: relativePath, reason: "excluded" });
2353
+ continue;
2354
+ }
2355
+ }
2356
+ let matched = false;
2357
+ for (const pattern of includePatterns) {
2358
+ if (matchGlob(relativePath, pattern)) {
2359
+ matched = true;
2360
+ break;
2361
+ }
2362
+ }
2363
+ if (matched) {
2364
+ yield { path: fullPath, size: stat.size };
2365
+ }
2366
+ }
2367
+ }
2368
+ }
2369
+ async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFileSize) {
2370
+ const ignoreFilter = createIgnoreFilter(projectRoot);
2371
+ const files = [];
2372
+ const skipped = [];
2373
+ for await (const file of walkDirectory(
2374
+ projectRoot,
2375
+ projectRoot,
2376
+ includePatterns,
2377
+ excludePatterns,
2378
+ ignoreFilter,
2379
+ maxFileSize,
2380
+ skipped
2381
+ )) {
2382
+ files.push(file);
2383
+ }
2384
+ return { files, skipped };
2385
+ }
2386
+
2387
+ // src/utils/cost.ts
2388
+ function estimateChunksFromFiles(files) {
2389
+ let totalChunks = 0;
2390
+ for (const file of files) {
2391
+ const avgChunkSize = 400;
2392
+ const chunksPerFile = Math.max(1, Math.ceil(file.size / avgChunkSize));
2393
+ totalChunks += chunksPerFile;
2394
+ }
2395
+ return totalChunks;
2396
+ }
2397
+ function estimateCost(estimatedTokens, modelInfo) {
2398
+ return estimatedTokens / 1e6 * modelInfo.costPer1MTokens;
2399
+ }
2400
+ function createCostEstimate(files, provider) {
2401
+ const filesCount = files.length;
2402
+ const totalSizeBytes = files.reduce((sum, f) => sum + f.size, 0);
2403
+ const estimatedChunks = estimateChunksFromFiles(files);
2404
+ const avgTokensPerChunk = 150;
2405
+ const estimatedTokens = estimatedChunks * avgTokensPerChunk;
2406
+ const estimatedCost = estimateCost(estimatedTokens, provider.modelInfo);
2407
+ return {
2408
+ filesCount,
2409
+ totalSizeBytes,
2410
+ estimatedChunks,
2411
+ estimatedTokens,
2412
+ estimatedCost,
2413
+ provider: getProviderDisplayName(provider.provider),
2414
+ model: provider.modelInfo.model,
2415
+ isFree: provider.modelInfo.costPer1MTokens === 0
2416
+ };
2417
+ }
2418
+ function formatCostEstimate(estimate) {
2419
+ const sizeFormatted = formatBytes(estimate.totalSizeBytes);
2420
+ const costFormatted = estimate.isFree ? "Free" : `~$${estimate.estimatedCost.toFixed(4)}`;
2421
+ return `
2422
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
2423
+ \u2502 \u{1F4CA} Indexing Estimate \u2502
2424
+ \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
2425
+ \u2502 \u2502
2426
+ \u2502 Files to index: ${padRight(estimate.filesCount.toLocaleString() + " files", 40)}\u2502
2427
+ \u2502 Total size: ${padRight(sizeFormatted, 40)}\u2502
2428
+ \u2502 Estimated chunks: ${padRight("~" + estimate.estimatedChunks.toLocaleString() + " chunks", 40)}\u2502
2429
+ \u2502 Estimated tokens: ${padRight("~" + estimate.estimatedTokens.toLocaleString() + " tokens", 40)}\u2502
2430
+ \u2502 \u2502
2431
+ \u2502 Provider: ${padRight(estimate.provider, 52)}\u2502
2432
+ \u2502 Model: ${padRight(estimate.model, 52)}\u2502
2433
+ \u2502 Cost: ${padRight(costFormatted, 52)}\u2502
2434
+ \u2502 \u2502
2435
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
2436
+ `;
2437
+ }
2438
+ function formatBytes(bytes) {
2439
+ if (bytes === 0) return "0 B";
2440
+ const k = 1024;
2441
+ const sizes = ["B", "KB", "MB", "GB"];
2442
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
2443
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
2444
+ }
2445
+ function padRight(str, length) {
2446
+ return str.padEnd(length);
2447
+ }
2448
+
2449
+ // src/utils/logger.ts
2450
+ var LOG_LEVEL_PRIORITY = {
2451
+ error: 0,
2452
+ warn: 1,
2453
+ info: 2,
2454
+ debug: 3
2455
+ };
2456
+ function createEmptyMetrics() {
2457
+ return {
2458
+ filesScanned: 0,
2459
+ filesParsed: 0,
2460
+ parseMs: 0,
2461
+ chunksProcessed: 0,
2462
+ chunksEmbedded: 0,
2463
+ chunksFromCache: 0,
2464
+ chunksRemoved: 0,
2465
+ embeddingApiCalls: 0,
2466
+ embeddingTokensUsed: 0,
2467
+ embeddingErrors: 0,
2468
+ searchCount: 0,
2469
+ searchTotalMs: 0,
2470
+ searchAvgMs: 0,
2471
+ searchLastMs: 0,
2472
+ embeddingCallMs: 0,
2473
+ vectorSearchMs: 0,
2474
+ keywordSearchMs: 0,
2475
+ fusionMs: 0,
2476
+ cacheHits: 0,
2477
+ cacheMisses: 0,
2478
+ queryCacheHits: 0,
2479
+ queryCacheSimilarHits: 0,
2480
+ queryCacheMisses: 0,
2481
+ gcRuns: 0,
2482
+ gcOrphansRemoved: 0,
2483
+ gcChunksRemoved: 0,
2484
+ gcEmbeddingsRemoved: 0
2485
+ };
2486
+ }
2487
+ var Logger = class {
2488
+ config;
2489
+ metrics;
2490
+ logs = [];
2491
+ maxLogs = 1e3;
2492
+ constructor(config) {
2493
+ this.config = config;
2494
+ this.metrics = createEmptyMetrics();
2495
+ }
2496
+ shouldLog(level) {
2497
+ if (!this.config.enabled) return false;
2498
+ return LOG_LEVEL_PRIORITY[level] <= LOG_LEVEL_PRIORITY[this.config.logLevel];
2499
+ }
2500
+ log(level, category, message, data) {
2501
+ if (!this.shouldLog(level)) return;
2502
+ const entry = {
2503
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2504
+ level,
2505
+ category,
2506
+ message,
2507
+ data
2508
+ };
2509
+ this.logs.push(entry);
2510
+ if (this.logs.length > this.maxLogs) {
2511
+ this.logs.shift();
2512
+ }
2513
+ }
2514
+ search(level, message, data) {
2515
+ if (this.config.logSearch) {
2516
+ this.log(level, "search", message, data);
2517
+ }
2518
+ }
2519
+ embedding(level, message, data) {
2520
+ if (this.config.logEmbedding) {
2521
+ this.log(level, "embedding", message, data);
2522
+ }
2523
+ }
2524
+ cache(level, message, data) {
2525
+ if (this.config.logCache) {
2526
+ this.log(level, "cache", message, data);
2527
+ }
2528
+ }
2529
+ gc(level, message, data) {
2530
+ if (this.config.logGc) {
2531
+ this.log(level, "gc", message, data);
2532
+ }
2533
+ }
2534
+ branch(level, message, data) {
2535
+ if (this.config.logBranch) {
2536
+ this.log(level, "branch", message, data);
2537
+ }
2538
+ }
2539
+ info(message, data) {
2540
+ this.log("info", "general", message, data);
2541
+ }
2542
+ warn(message, data) {
2543
+ this.log("warn", "general", message, data);
2544
+ }
2545
+ error(message, data) {
2546
+ this.log("error", "general", message, data);
2547
+ }
2548
+ debug(message, data) {
2549
+ this.log("debug", "general", message, data);
2550
+ }
2551
+ recordIndexingStart() {
2552
+ if (!this.config.metrics) return;
2553
+ this.metrics.indexingStartTime = Date.now();
2554
+ }
2555
+ recordIndexingEnd() {
2556
+ if (!this.config.metrics) return;
2557
+ this.metrics.indexingEndTime = Date.now();
2558
+ }
2559
+ recordFilesScanned(count) {
2560
+ if (!this.config.metrics) return;
2561
+ this.metrics.filesScanned = count;
2562
+ }
2563
+ recordFilesParsed(count) {
2564
+ if (!this.config.metrics) return;
2565
+ this.metrics.filesParsed = count;
2566
+ }
2567
+ recordParseDuration(durationMs) {
2568
+ if (!this.config.metrics) return;
2569
+ this.metrics.parseMs = durationMs;
2570
+ }
2571
+ recordChunksProcessed(count) {
2572
+ if (!this.config.metrics) return;
2573
+ this.metrics.chunksProcessed += count;
2574
+ }
2575
+ recordChunksEmbedded(count) {
2576
+ if (!this.config.metrics) return;
2577
+ this.metrics.chunksEmbedded += count;
2578
+ }
2579
+ recordChunksFromCache(count) {
2580
+ if (!this.config.metrics) return;
2581
+ this.metrics.chunksFromCache += count;
2582
+ }
2583
+ recordChunksRemoved(count) {
2584
+ if (!this.config.metrics) return;
2585
+ this.metrics.chunksRemoved += count;
2586
+ }
2587
+ recordEmbeddingApiCall(tokens) {
2588
+ if (!this.config.metrics) return;
2589
+ this.metrics.embeddingApiCalls++;
2590
+ this.metrics.embeddingTokensUsed += tokens;
2591
+ }
2592
+ recordEmbeddingError() {
2593
+ if (!this.config.metrics) return;
2594
+ this.metrics.embeddingErrors++;
2595
+ }
2596
+ recordSearch(durationMs, breakdown) {
2597
+ if (!this.config.metrics) return;
2598
+ this.metrics.searchCount++;
2599
+ this.metrics.searchTotalMs += durationMs;
2600
+ this.metrics.searchLastMs = durationMs;
2601
+ this.metrics.searchAvgMs = this.metrics.searchTotalMs / this.metrics.searchCount;
2602
+ if (breakdown) {
2603
+ this.metrics.embeddingCallMs = breakdown.embeddingMs;
2604
+ this.metrics.vectorSearchMs = breakdown.vectorMs;
2605
+ this.metrics.keywordSearchMs = breakdown.keywordMs;
2606
+ this.metrics.fusionMs = breakdown.fusionMs;
2607
+ }
2608
+ }
2609
+ recordCacheHit() {
2610
+ if (!this.config.metrics) return;
2611
+ this.metrics.cacheHits++;
2612
+ }
2613
+ recordCacheMiss() {
2614
+ if (!this.config.metrics) return;
2615
+ this.metrics.cacheMisses++;
2616
+ }
2617
+ recordQueryCacheHit() {
2618
+ if (!this.config.metrics) return;
2619
+ this.metrics.queryCacheHits++;
2620
+ }
2621
+ recordQueryCacheSimilarHit() {
2622
+ if (!this.config.metrics) return;
2623
+ this.metrics.queryCacheSimilarHits++;
2624
+ }
2625
+ recordQueryCacheMiss() {
2626
+ if (!this.config.metrics) return;
2627
+ this.metrics.queryCacheMisses++;
2628
+ }
2629
+ recordGc(orphans, chunks, embeddings) {
2630
+ if (!this.config.metrics) return;
2631
+ this.metrics.gcRuns++;
2632
+ this.metrics.gcOrphansRemoved += orphans;
2633
+ this.metrics.gcChunksRemoved += chunks;
2634
+ this.metrics.gcEmbeddingsRemoved += embeddings;
2635
+ }
2636
+ getMetrics() {
2637
+ return { ...this.metrics };
2638
+ }
2639
+ getLogs(limit) {
2640
+ const logs = [...this.logs];
2641
+ if (limit) {
2642
+ return logs.slice(-limit);
2643
+ }
2644
+ return logs;
2645
+ }
2646
+ getLogsByCategory(category, limit) {
2647
+ const filtered = this.logs.filter((l) => l.category === category);
2648
+ if (limit) {
2649
+ return filtered.slice(-limit);
2650
+ }
2651
+ return filtered;
2652
+ }
2653
+ getLogsByLevel(level, limit) {
2654
+ const filtered = this.logs.filter((l) => l.level === level);
2655
+ if (limit) {
2656
+ return filtered.slice(-limit);
2657
+ }
2658
+ return filtered;
2659
+ }
2660
+ resetMetrics() {
2661
+ this.metrics = createEmptyMetrics();
2662
+ }
2663
+ clearLogs() {
2664
+ this.logs = [];
2665
+ }
2666
+ formatMetrics() {
2667
+ const m = this.metrics;
2668
+ const lines = [];
2669
+ lines.push("=== Metrics ===");
2670
+ if (m.indexingStartTime && m.indexingEndTime) {
2671
+ const duration = m.indexingEndTime - m.indexingStartTime;
2672
+ lines.push(`Indexing duration: ${(duration / 1e3).toFixed(2)}s`);
2673
+ }
2674
+ lines.push("");
2675
+ lines.push("Indexing:");
2676
+ lines.push(` Files scanned: ${m.filesScanned}`);
2677
+ lines.push(` Files parsed: ${m.filesParsed}`);
2678
+ lines.push(` Chunks processed: ${m.chunksProcessed}`);
2679
+ lines.push(` Chunks embedded: ${m.chunksEmbedded}`);
2680
+ lines.push(` Chunks from cache: ${m.chunksFromCache}`);
2681
+ lines.push(` Chunks removed: ${m.chunksRemoved}`);
2682
+ lines.push("");
2683
+ lines.push("Embedding API:");
2684
+ lines.push(` API calls: ${m.embeddingApiCalls}`);
2685
+ lines.push(` Tokens used: ${m.embeddingTokensUsed.toLocaleString()}`);
2686
+ lines.push(` Errors: ${m.embeddingErrors}`);
2687
+ if (m.searchCount > 0) {
2688
+ lines.push("");
2689
+ lines.push("Search:");
2690
+ lines.push(` Total searches: ${m.searchCount}`);
2691
+ lines.push(` Average time: ${m.searchAvgMs.toFixed(2)}ms`);
2692
+ lines.push(` Last search: ${m.searchLastMs.toFixed(2)}ms`);
2693
+ if (m.embeddingCallMs > 0) {
2694
+ lines.push(` - Embedding: ${m.embeddingCallMs.toFixed(2)}ms`);
2695
+ lines.push(` - Vector search: ${m.vectorSearchMs.toFixed(2)}ms`);
2696
+ lines.push(` - Keyword search: ${m.keywordSearchMs.toFixed(2)}ms`);
2697
+ lines.push(` - Fusion: ${m.fusionMs.toFixed(2)}ms`);
2698
+ }
2699
+ }
2700
+ const totalCacheOps = m.cacheHits + m.cacheMisses;
2701
+ if (totalCacheOps > 0) {
2702
+ lines.push("");
2703
+ lines.push("Cache:");
2704
+ lines.push(` Hits: ${m.cacheHits}`);
2705
+ lines.push(` Misses: ${m.cacheMisses}`);
2706
+ lines.push(` Hit rate: ${(m.cacheHits / totalCacheOps * 100).toFixed(1)}%`);
2707
+ }
2708
+ if (m.gcRuns > 0) {
2709
+ lines.push("");
2710
+ lines.push("Garbage Collection:");
2711
+ lines.push(` GC runs: ${m.gcRuns}`);
2712
+ lines.push(` Orphans removed: ${m.gcOrphansRemoved}`);
2713
+ lines.push(` Chunks removed: ${m.gcChunksRemoved}`);
2714
+ lines.push(` Embeddings removed: ${m.gcEmbeddingsRemoved}`);
2715
+ }
2716
+ return lines.join("\n");
2717
+ }
2718
+ formatRecentLogs(limit = 20) {
2719
+ const logs = this.getLogs(limit);
2720
+ if (logs.length === 0) {
2721
+ return "No logs recorded.";
2722
+ }
2723
+ return logs.map((l) => {
2724
+ const dataStr = l.data ? ` ${JSON.stringify(l.data)}` : "";
2725
+ return `[${l.timestamp}] [${l.level.toUpperCase()}] [${l.category}] ${l.message}${dataStr}`;
2726
+ }).join("\n");
2727
+ }
2728
+ isEnabled() {
2729
+ return this.config.enabled;
2730
+ }
2731
+ isMetricsEnabled() {
2732
+ return this.config.enabled && this.config.metrics;
2733
+ }
2734
+ };
2735
+ var globalLogger = null;
2736
+ function initializeLogger(config) {
2737
+ globalLogger = new Logger(config);
2738
+ return globalLogger;
2739
+ }
2740
+
2741
+ // src/native/index.ts
2742
+ import * as path3 from "path";
2743
+ import * as os2 from "os";
2744
+ import { fileURLToPath } from "url";
2745
+ function getNativeBinding() {
2746
+ const platform2 = os2.platform();
2747
+ const arch2 = os2.arch();
2748
+ let bindingName;
2749
+ if (platform2 === "darwin" && arch2 === "arm64") {
2750
+ bindingName = "codebase-index-native.darwin-arm64.node";
2751
+ } else if (platform2 === "darwin" && arch2 === "x64") {
2752
+ bindingName = "codebase-index-native.darwin-x64.node";
2753
+ } else if (platform2 === "linux" && arch2 === "x64") {
2754
+ bindingName = "codebase-index-native.linux-x64-gnu.node";
2755
+ } else if (platform2 === "linux" && arch2 === "arm64") {
2756
+ bindingName = "codebase-index-native.linux-arm64-gnu.node";
2757
+ } else if (platform2 === "win32" && arch2 === "x64") {
2758
+ bindingName = "codebase-index-native.win32-x64-msvc.node";
2759
+ } else {
2760
+ throw new Error(`Unsupported platform: ${platform2}-${arch2}`);
2761
+ }
2762
+ let currentDir;
2763
+ if (typeof import.meta !== "undefined" && import.meta.url) {
2764
+ currentDir = path3.dirname(fileURLToPath(import.meta.url));
2765
+ } else if (typeof __dirname !== "undefined") {
2766
+ currentDir = __dirname;
2767
+ } else {
2768
+ currentDir = process.cwd();
2769
+ }
2770
+ const isDevMode = currentDir.includes("/src/native");
2771
+ const packageRoot = isDevMode ? path3.resolve(currentDir, "../..") : path3.resolve(currentDir, "..");
2772
+ const nativePath = path3.join(packageRoot, "native", bindingName);
2773
+ return __require(nativePath);
2774
+ }
2775
+ var native = getNativeBinding();
2776
+ function parseFiles(files) {
2777
+ const result = native.parseFiles(files);
2778
+ return result.map((f) => ({
2779
+ path: f.path,
2780
+ chunks: f.chunks.map(mapChunk),
2781
+ hash: f.hash
2782
+ }));
2783
+ }
2784
+ function mapChunk(c) {
2785
+ return {
2786
+ content: c.content,
2787
+ startLine: c.startLine ?? c.start_line,
2788
+ endLine: c.endLine ?? c.end_line,
2789
+ chunkType: c.chunkType ?? c.chunk_type,
2790
+ name: c.name ?? void 0,
2791
+ language: c.language
2792
+ };
2793
+ }
2794
+ function hashContent(content) {
2795
+ return native.hashContent(content);
2796
+ }
2797
+ function hashFile(filePath) {
2798
+ return native.hashFile(filePath);
2799
+ }
2800
+ var VectorStore = class {
2801
+ inner;
2802
+ dimensions;
2803
+ constructor(indexPath, dimensions) {
2804
+ this.inner = new native.VectorStore(indexPath, dimensions);
2805
+ this.dimensions = dimensions;
2806
+ }
2807
+ add(id, vector, metadata) {
2808
+ if (vector.length !== this.dimensions) {
2809
+ throw new Error(
2810
+ `Vector dimension mismatch: expected ${this.dimensions}, got ${vector.length}`
2811
+ );
2812
+ }
2813
+ this.inner.add(id, vector, JSON.stringify(metadata));
2814
+ }
2815
+ addBatch(items) {
2816
+ const ids = items.map((i) => i.id);
2817
+ const vectors = items.map((i) => {
2818
+ if (i.vector.length !== this.dimensions) {
2819
+ throw new Error(
2820
+ `Vector dimension mismatch for ${i.id}: expected ${this.dimensions}, got ${i.vector.length}`
2821
+ );
2822
+ }
2823
+ return i.vector;
2824
+ });
2825
+ const metadata = items.map((i) => JSON.stringify(i.metadata));
2826
+ this.inner.addBatch(ids, vectors, metadata);
2827
+ }
2828
+ search(queryVector, limit = 10) {
2829
+ if (queryVector.length !== this.dimensions) {
2830
+ throw new Error(
2831
+ `Query vector dimension mismatch: expected ${this.dimensions}, got ${queryVector.length}`
2832
+ );
2833
+ }
2834
+ const results = this.inner.search(queryVector, limit);
2835
+ return results.map((r) => ({
2836
+ id: r.id,
2837
+ score: r.score,
2838
+ metadata: JSON.parse(r.metadata)
2839
+ }));
2840
+ }
2841
+ remove(id) {
2842
+ return this.inner.remove(id);
2843
+ }
2844
+ save() {
2845
+ this.inner.save();
2846
+ }
2847
+ load() {
2848
+ this.inner.load();
2849
+ }
2850
+ count() {
2851
+ return this.inner.count();
2852
+ }
2853
+ clear() {
2854
+ this.inner.clear();
2855
+ }
2856
+ getDimensions() {
2857
+ return this.dimensions;
2858
+ }
2859
+ getAllKeys() {
2860
+ return this.inner.getAllKeys();
2861
+ }
2862
+ getAllMetadata() {
2863
+ const results = this.inner.getAllMetadata();
2864
+ return results.map((r) => ({
2865
+ key: r.key,
2866
+ metadata: JSON.parse(r.metadata)
2867
+ }));
2868
+ }
2869
+ getMetadata(id) {
2870
+ const result = this.inner.getMetadata(id);
2871
+ if (result === null || result === void 0) {
2872
+ return void 0;
2873
+ }
2874
+ return JSON.parse(result);
2875
+ }
2876
+ getMetadataBatch(ids) {
2877
+ const results = this.inner.getMetadataBatch(ids);
2878
+ const map = /* @__PURE__ */ new Map();
2879
+ for (const { key, metadata } of results) {
2880
+ map.set(key, JSON.parse(metadata));
2881
+ }
2882
+ return map;
2883
+ }
2884
+ };
2885
+ var CHARS_PER_TOKEN = 4;
2886
+ var MAX_BATCH_TOKENS = 7500;
2887
+ var MAX_SINGLE_CHUNK_TOKENS = 2e3;
2888
+ function estimateTokens(text) {
2889
+ return Math.ceil(text.length / CHARS_PER_TOKEN);
2890
+ }
2891
+ function createEmbeddingText(chunk, filePath) {
2892
+ const parts = [];
2893
+ const fileName = filePath.split("/").pop() || filePath;
2894
+ const dirPath = filePath.split("/").slice(-3, -1).join("/");
2895
+ const langDescriptors = {
2896
+ typescript: "TypeScript",
2897
+ javascript: "JavaScript",
2898
+ python: "Python",
2899
+ rust: "Rust",
2900
+ go: "Go",
2901
+ java: "Java"
2902
+ };
2903
+ const typeDescriptors = {
2904
+ function_declaration: "function",
2905
+ function: "function",
2906
+ arrow_function: "arrow function",
2907
+ method_definition: "method",
2908
+ class_declaration: "class",
2909
+ interface_declaration: "interface",
2910
+ type_alias_declaration: "type alias",
2911
+ enum_declaration: "enum",
2912
+ export_statement: "export",
2913
+ lexical_declaration: "variable declaration",
2914
+ function_definition: "function",
2915
+ class_definition: "class",
2916
+ function_item: "function",
2917
+ impl_item: "implementation",
2918
+ struct_item: "struct",
2919
+ enum_item: "enum",
2920
+ trait_item: "trait"
2921
+ };
2922
+ const lang = langDescriptors[chunk.language] || chunk.language;
2923
+ const typeDesc = typeDescriptors[chunk.chunkType] || chunk.chunkType;
2924
+ if (chunk.name) {
2925
+ parts.push(`${lang} ${typeDesc} "${chunk.name}"`);
2926
+ } else {
2927
+ parts.push(`${lang} ${typeDesc}`);
2928
+ }
2929
+ if (dirPath) {
2930
+ parts.push(`in ${dirPath}/${fileName}`);
2931
+ } else {
2932
+ parts.push(`in ${fileName}`);
2933
+ }
2934
+ const semanticHints = extractSemanticHints(chunk.name || "", chunk.content);
2935
+ if (semanticHints.length > 0) {
2936
+ parts.push(`Purpose: ${semanticHints.join(", ")}`);
2937
+ }
2938
+ parts.push("");
2939
+ let content = chunk.content;
2940
+ const headerLength = parts.join("\n").length;
2941
+ const maxContentChars = MAX_SINGLE_CHUNK_TOKENS * CHARS_PER_TOKEN - headerLength;
2942
+ if (content.length > maxContentChars) {
2943
+ content = content.slice(0, maxContentChars) + "\n... [truncated]";
2944
+ }
2945
+ parts.push(content);
2946
+ return parts.join("\n");
2947
+ }
2948
+ function createDynamicBatches(chunks) {
2949
+ const batches = [];
2950
+ let currentBatch = [];
2951
+ let currentTokens = 0;
2952
+ for (const chunk of chunks) {
2953
+ const chunkTokens = estimateTokens(chunk.text);
2954
+ if (currentBatch.length > 0 && currentTokens + chunkTokens > MAX_BATCH_TOKENS) {
2955
+ batches.push(currentBatch);
2956
+ currentBatch = [];
2957
+ currentTokens = 0;
2958
+ }
2959
+ currentBatch.push(chunk);
2960
+ currentTokens += chunkTokens;
2961
+ }
2962
+ if (currentBatch.length > 0) {
2963
+ batches.push(currentBatch);
2964
+ }
2965
+ return batches;
2966
+ }
2967
+ function extractSemanticHints(name, content) {
2968
+ const hints = [];
2969
+ const combined = `${name} ${content}`.toLowerCase();
2970
+ const signature = extractFunctionSignature(content);
2971
+ if (signature) {
2972
+ hints.push(signature);
2973
+ }
2974
+ const patterns = [
2975
+ [/auth|login|logout|signin|signout|credential/i, "authentication"],
2976
+ [/password|hash|bcrypt|argon/i, "password handling"],
2977
+ [/token|jwt|bearer|oauth/i, "token management"],
2978
+ [/user|account|profile|member/i, "user management"],
2979
+ [/permission|role|access|authorize/i, "authorization"],
2980
+ [/validate|verify|check|assert/i, "validation"],
2981
+ [/error|exception|throw|catch/i, "error handling"],
2982
+ [/log|debug|trace|info|warn/i, "logging"],
2983
+ [/cache|memoize|store/i, "caching"],
2984
+ [/fetch|request|response|api|http/i, "HTTP/API"],
2985
+ [/database|db|query|sql|mongo/i, "database"],
2986
+ [/file|read|write|stream|path/i, "file operations"],
2987
+ [/parse|serialize|json|xml/i, "data parsing"],
2988
+ [/encrypt|decrypt|crypto|secret|cipher|cryptographic/i, "encryption/cryptography"],
2989
+ [/test|spec|mock|stub|expect/i, "testing"],
2990
+ [/config|setting|option|env/i, "configuration"],
2991
+ [/route|endpoint|handler|controller|middleware/i, "routing/middleware"],
2992
+ [/render|component|view|template/i, "UI rendering"],
2993
+ [/state|redux|store|dispatch/i, "state management"],
2994
+ [/hook|effect|memo|callback/i, "React hooks"]
2995
+ ];
2996
+ for (const [pattern, hint] of patterns) {
2997
+ if (pattern.test(combined) && !hints.includes(hint)) {
2998
+ hints.push(hint);
2999
+ }
3000
+ }
3001
+ return hints.slice(0, 6);
3002
+ }
3003
+ function extractFunctionSignature(content) {
3004
+ const tsJsPatterns = [
3005
+ /(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*(?:<[^>]+>)?\s*\(([^)]*)\)\s*(?::\s*([^{]+))?/,
3006
+ /(?:export\s+)?const\s+(\w+)\s*(?::\s*[^=]+)?\s*=\s*(?:async\s+)?\(([^)]*)\)\s*(?::\s*([^=>{]+))?\s*=>/,
3007
+ /(?:export\s+)?const\s+(\w+)\s*(?::\s*[^=]+)?\s*=\s*(?:async\s+)?function\s*\(([^)]*)\)/
3008
+ ];
3009
+ const pyPatterns = [
3010
+ /def\s+(\w+)\s*\(([^)]*)\)\s*(?:->\s*([^:]+))?:/,
3011
+ /async\s+def\s+(\w+)\s*\(([^)]*)\)\s*(?:->\s*([^:]+))?:/
3012
+ ];
3013
+ const goPatterns = [
3014
+ /func\s+(?:\([^)]+\)\s+)?(\w+)\s*\(([^)]*)\)\s*(?:\(([^)]+)\)|([^{\n]+))?/
3015
+ ];
3016
+ const rustPatterns = [
3017
+ /(?:pub\s+)?(?:async\s+)?fn\s+(\w+)\s*(?:<[^>]+>)?\s*\(([^)]*)\)\s*(?:->\s*([^{]+))?/
3018
+ ];
3019
+ for (const pattern of [...tsJsPatterns, ...pyPatterns, ...goPatterns, ...rustPatterns]) {
3020
+ const match = content.match(pattern);
3021
+ if (match) {
3022
+ const funcName = match[1];
3023
+ const params = match[2]?.trim() || "";
3024
+ const returnType = (match[3] || match[4])?.trim();
3025
+ const paramNames = extractParamNames(params);
3026
+ let sig = `${funcName}(${paramNames.join(", ")})`;
3027
+ if (returnType && returnType.length < 50) {
3028
+ sig += ` -> ${returnType.replace(/\s+/g, " ").trim()}`;
3029
+ }
3030
+ if (sig.length < 100) {
3031
+ return sig;
3032
+ }
3033
+ }
3034
+ }
3035
+ return null;
3036
+ }
3037
+ function extractParamNames(params) {
3038
+ if (!params.trim()) return [];
3039
+ const names = [];
3040
+ const parts = params.split(",");
3041
+ for (const part of parts) {
3042
+ const trimmed = part.trim();
3043
+ if (!trimmed) continue;
3044
+ const tsMatch = trimmed.match(/^(\w+)\s*[?:]?/);
3045
+ const pyMatch = trimmed.match(/^(\w+)\s*(?::|=)/);
3046
+ const goMatch = trimmed.match(/^(\w+)\s+\w/);
3047
+ const rustMatch = trimmed.match(/^(\w+)\s*:/);
3048
+ const match = tsMatch || pyMatch || goMatch || rustMatch;
3049
+ if (match && match[1] !== "self" && match[1] !== "this") {
3050
+ names.push(match[1]);
3051
+ }
3052
+ }
3053
+ return names.slice(0, 5);
3054
+ }
3055
+ function generateChunkId(filePath, chunk) {
3056
+ const hash = hashContent(`${filePath}:${chunk.startLine}:${chunk.endLine}:${chunk.content}`);
3057
+ return `chunk_${hash.slice(0, 16)}`;
3058
+ }
3059
+ function generateChunkHash(chunk) {
3060
+ return hashContent(chunk.content);
3061
+ }
3062
+ var InvertedIndex = class {
3063
+ inner;
3064
+ constructor(indexPath) {
3065
+ this.inner = new native.InvertedIndex(indexPath);
3066
+ }
3067
+ load() {
3068
+ this.inner.load();
3069
+ }
3070
+ save() {
3071
+ this.inner.save();
3072
+ }
3073
+ addChunk(chunkId, content) {
3074
+ this.inner.addChunk(chunkId, content);
3075
+ }
3076
+ removeChunk(chunkId) {
3077
+ return this.inner.removeChunk(chunkId);
3078
+ }
3079
+ search(query, limit) {
3080
+ const results = this.inner.search(query, limit ?? 100);
3081
+ const map = /* @__PURE__ */ new Map();
3082
+ for (const r of results) {
3083
+ map.set(r.chunkId, r.score);
3084
+ }
3085
+ return map;
3086
+ }
3087
+ hasChunk(chunkId) {
3088
+ return this.inner.hasChunk(chunkId);
3089
+ }
3090
+ clear() {
3091
+ this.inner.clear();
3092
+ }
3093
+ getDocumentCount() {
3094
+ return this.inner.documentCount();
3095
+ }
3096
+ };
3097
+ var Database = class {
3098
+ inner;
3099
+ constructor(dbPath) {
3100
+ this.inner = new native.Database(dbPath);
3101
+ }
3102
+ embeddingExists(contentHash) {
3103
+ return this.inner.embeddingExists(contentHash);
3104
+ }
3105
+ getEmbedding(contentHash) {
3106
+ return this.inner.getEmbedding(contentHash) ?? null;
3107
+ }
3108
+ upsertEmbedding(contentHash, embedding, chunkText, model) {
3109
+ this.inner.upsertEmbedding(contentHash, embedding, chunkText, model);
3110
+ }
3111
+ upsertEmbeddingsBatch(items) {
3112
+ if (items.length === 0) return;
3113
+ this.inner.upsertEmbeddingsBatch(items);
3114
+ }
3115
+ getMissingEmbeddings(contentHashes) {
3116
+ return this.inner.getMissingEmbeddings(contentHashes);
3117
+ }
3118
+ upsertChunk(chunk) {
3119
+ this.inner.upsertChunk(chunk);
3120
+ }
3121
+ upsertChunksBatch(chunks) {
3122
+ if (chunks.length === 0) return;
3123
+ this.inner.upsertChunksBatch(chunks);
3124
+ }
3125
+ getChunk(chunkId) {
3126
+ return this.inner.getChunk(chunkId) ?? null;
3127
+ }
3128
+ getChunksByFile(filePath) {
3129
+ return this.inner.getChunksByFile(filePath);
3130
+ }
3131
+ deleteChunksByFile(filePath) {
3132
+ return this.inner.deleteChunksByFile(filePath);
3133
+ }
3134
+ addChunksToBranch(branch, chunkIds) {
3135
+ this.inner.addChunksToBranch(branch, chunkIds);
3136
+ }
3137
+ addChunksToBranchBatch(branch, chunkIds) {
3138
+ if (chunkIds.length === 0) return;
3139
+ this.inner.addChunksToBranchBatch(branch, chunkIds);
3140
+ }
3141
+ clearBranch(branch) {
3142
+ return this.inner.clearBranch(branch);
3143
+ }
3144
+ getBranchChunkIds(branch) {
3145
+ return this.inner.getBranchChunkIds(branch);
3146
+ }
3147
+ getBranchDelta(branch, baseBranch) {
3148
+ return this.inner.getBranchDelta(branch, baseBranch);
3149
+ }
3150
+ chunkExistsOnBranch(branch, chunkId) {
3151
+ return this.inner.chunkExistsOnBranch(branch, chunkId);
3152
+ }
3153
+ getAllBranches() {
3154
+ return this.inner.getAllBranches();
3155
+ }
3156
+ getMetadata(key) {
3157
+ return this.inner.getMetadata(key) ?? null;
3158
+ }
3159
+ setMetadata(key, value) {
3160
+ this.inner.setMetadata(key, value);
3161
+ }
3162
+ deleteMetadata(key) {
3163
+ return this.inner.deleteMetadata(key);
3164
+ }
3165
+ gcOrphanEmbeddings() {
3166
+ return this.inner.gcOrphanEmbeddings();
3167
+ }
3168
+ gcOrphanChunks() {
3169
+ return this.inner.gcOrphanChunks();
3170
+ }
3171
+ getStats() {
3172
+ return this.inner.getStats();
3173
+ }
3174
+ };
3175
+
3176
+ // src/git/index.ts
3177
+ import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync, statSync } from "fs";
3178
+ import * as path4 from "path";
3179
+ import { execSync } from "child_process";
3180
+ function resolveGitDir(repoRoot) {
3181
+ const gitPath = path4.join(repoRoot, ".git");
3182
+ if (!existsSync3(gitPath)) {
3183
+ return null;
3184
+ }
3185
+ try {
3186
+ const stat = statSync(gitPath);
3187
+ if (stat.isDirectory()) {
3188
+ return gitPath;
3189
+ }
3190
+ if (stat.isFile()) {
3191
+ const content = readFileSync3(gitPath, "utf-8").trim();
3192
+ const match = content.match(/^gitdir:\s*(.+)$/);
3193
+ if (match) {
3194
+ const gitdir = match[1];
3195
+ const resolvedPath = path4.isAbsolute(gitdir) ? gitdir : path4.resolve(repoRoot, gitdir);
3196
+ if (existsSync3(resolvedPath)) {
3197
+ return resolvedPath;
3198
+ }
3199
+ }
3200
+ }
3201
+ } catch {
3202
+ }
3203
+ return null;
3204
+ }
3205
+ function isGitRepo(dir) {
3206
+ return resolveGitDir(dir) !== null;
3207
+ }
3208
+ function getCurrentBranch(repoRoot) {
3209
+ const gitDir = resolveGitDir(repoRoot);
3210
+ if (!gitDir) {
3211
+ return null;
3212
+ }
3213
+ const headPath = path4.join(gitDir, "HEAD");
3214
+ if (!existsSync3(headPath)) {
3215
+ return null;
3216
+ }
3217
+ try {
3218
+ const headContent = readFileSync3(headPath, "utf-8").trim();
3219
+ const match = headContent.match(/^ref: refs\/heads\/(.+)$/);
3220
+ if (match) {
3221
+ return match[1];
3222
+ }
3223
+ if (/^[0-9a-f]{40}$/i.test(headContent)) {
3224
+ return headContent.slice(0, 7);
3225
+ }
3226
+ return null;
3227
+ } catch {
3228
+ return null;
3229
+ }
3230
+ }
3231
+ function getBaseBranch(repoRoot) {
3232
+ const gitDir = resolveGitDir(repoRoot);
3233
+ const candidates = ["main", "master", "develop", "trunk"];
3234
+ if (gitDir) {
3235
+ for (const candidate of candidates) {
3236
+ const refPath = path4.join(gitDir, "refs", "heads", candidate);
3237
+ if (existsSync3(refPath)) {
3238
+ return candidate;
3239
+ }
3240
+ const packedRefsPath = path4.join(gitDir, "packed-refs");
3241
+ if (existsSync3(packedRefsPath)) {
3242
+ try {
3243
+ const content = readFileSync3(packedRefsPath, "utf-8");
3244
+ if (content.includes(`refs/heads/${candidate}`)) {
3245
+ return candidate;
3246
+ }
3247
+ } catch {
3248
+ }
3249
+ }
3250
+ }
3251
+ }
3252
+ try {
3253
+ const result = execSync("git remote show origin", {
3254
+ cwd: repoRoot,
3255
+ encoding: "utf-8",
3256
+ stdio: ["pipe", "pipe", "pipe"]
3257
+ });
3258
+ const match = result.match(/HEAD branch: (.+)/);
3259
+ if (match) {
3260
+ return match[1].trim();
3261
+ }
3262
+ } catch {
3263
+ }
3264
+ return getCurrentBranch(repoRoot) ?? "main";
3265
+ }
3266
+ function getBranchOrDefault(repoRoot) {
3267
+ if (!isGitRepo(repoRoot)) {
3268
+ return "default";
3269
+ }
3270
+ return getCurrentBranch(repoRoot) ?? "default";
3271
+ }
3272
+
3273
+ // src/indexer/index.ts
3274
+ function float32ArrayToBuffer(arr) {
3275
+ const float32 = new Float32Array(arr);
3276
+ return Buffer.from(float32.buffer);
3277
+ }
3278
+ function bufferToFloat32Array(buf) {
3279
+ return new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4);
3280
+ }
3281
+ function getErrorMessage(error) {
3282
+ if (error instanceof Error) {
3283
+ return error.message;
3284
+ }
3285
+ if (typeof error === "string") {
3286
+ return error;
3287
+ }
3288
+ if (error && typeof error === "object" && "message" in error) {
3289
+ return String(error.message);
3290
+ }
3291
+ return String(error);
3292
+ }
3293
+ function isRateLimitError(error) {
3294
+ const message = getErrorMessage(error);
3295
+ return message.includes("429") || message.toLowerCase().includes("rate limit") || message.toLowerCase().includes("too many requests");
3296
+ }
3297
+ var INDEX_METADATA_VERSION = "1";
3298
+ var Indexer = class {
3299
+ config;
3300
+ projectRoot;
3301
+ indexPath;
3302
+ store = null;
3303
+ invertedIndex = null;
3304
+ database = null;
3305
+ provider = null;
3306
+ configuredProviderInfo = null;
3307
+ fileHashCache = /* @__PURE__ */ new Map();
3308
+ fileHashCachePath = "";
3309
+ failedBatchesPath = "";
3310
+ currentBranch = "default";
3311
+ baseBranch = "main";
3312
+ logger;
3313
+ queryEmbeddingCache = /* @__PURE__ */ new Map();
3314
+ maxQueryCacheSize = 100;
3315
+ queryCacheTtlMs = 5 * 60 * 1e3;
3316
+ querySimilarityThreshold = 0.85;
3317
+ indexCompatibility = null;
3318
+ indexingLockPath = "";
3319
+ constructor(projectRoot, config) {
3320
+ this.projectRoot = projectRoot;
3321
+ this.config = config;
3322
+ this.indexPath = this.getIndexPath();
3323
+ this.fileHashCachePath = path5.join(this.indexPath, "file-hashes.json");
3324
+ this.failedBatchesPath = path5.join(this.indexPath, "failed-batches.json");
3325
+ this.indexingLockPath = path5.join(this.indexPath, "indexing.lock");
3326
+ this.logger = initializeLogger(config.debug);
3327
+ }
3328
+ getIndexPath() {
3329
+ if (this.config.scope === "global") {
3330
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "";
3331
+ return path5.join(homeDir, ".opencode", "global-index");
3332
+ }
3333
+ return path5.join(this.projectRoot, ".opencode", "index");
3334
+ }
3335
+ loadFileHashCache() {
3336
+ try {
3337
+ if (existsSync4(this.fileHashCachePath)) {
3338
+ const data = readFileSync4(this.fileHashCachePath, "utf-8");
3339
+ const parsed = JSON.parse(data);
3340
+ this.fileHashCache = new Map(Object.entries(parsed));
3341
+ }
3342
+ } catch {
3343
+ this.fileHashCache = /* @__PURE__ */ new Map();
3344
+ }
3345
+ }
3346
+ saveFileHashCache() {
3347
+ const obj = {};
3348
+ for (const [k, v] of this.fileHashCache) {
3349
+ obj[k] = v;
3350
+ }
3351
+ this.atomicWriteSync(this.fileHashCachePath, JSON.stringify(obj));
3352
+ }
3353
+ atomicWriteSync(targetPath, data) {
3354
+ const tempPath = `${targetPath}.tmp`;
3355
+ writeFileSync(tempPath, data);
3356
+ renameSync(tempPath, targetPath);
3357
+ }
3358
+ checkForInterruptedIndexing() {
3359
+ return existsSync4(this.indexingLockPath);
3360
+ }
3361
+ acquireIndexingLock() {
3362
+ const lockData = {
3363
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
3364
+ pid: process.pid
3365
+ };
3366
+ writeFileSync(this.indexingLockPath, JSON.stringify(lockData));
3367
+ }
3368
+ releaseIndexingLock() {
3369
+ if (existsSync4(this.indexingLockPath)) {
3370
+ unlinkSync(this.indexingLockPath);
3371
+ }
3372
+ }
3373
+ async recoverFromInterruptedIndexing() {
3374
+ this.logger.warn("Detected interrupted indexing session, recovering...");
3375
+ if (existsSync4(this.fileHashCachePath)) {
3376
+ unlinkSync(this.fileHashCachePath);
3377
+ }
3378
+ await this.healthCheck();
3379
+ this.releaseIndexingLock();
3380
+ this.logger.info("Recovery complete, next index will re-process all files");
3381
+ }
3382
+ loadFailedBatches() {
3383
+ try {
3384
+ if (existsSync4(this.failedBatchesPath)) {
3385
+ const data = readFileSync4(this.failedBatchesPath, "utf-8");
3386
+ return JSON.parse(data);
3387
+ }
3388
+ } catch {
3389
+ return [];
3390
+ }
3391
+ return [];
3392
+ }
3393
+ saveFailedBatches(batches) {
3394
+ if (batches.length === 0) {
3395
+ if (existsSync4(this.failedBatchesPath)) {
3396
+ fsPromises2.unlink(this.failedBatchesPath).catch(() => {
3397
+ });
3398
+ }
3399
+ return;
3400
+ }
3401
+ writeFileSync(this.failedBatchesPath, JSON.stringify(batches, null, 2));
3402
+ }
3403
+ addFailedBatch(batch, error) {
3404
+ const existing = this.loadFailedBatches();
3405
+ existing.push({
3406
+ chunks: batch,
3407
+ error,
3408
+ attemptCount: 1,
3409
+ lastAttempt: (/* @__PURE__ */ new Date()).toISOString()
3410
+ });
3411
+ this.saveFailedBatches(existing);
3412
+ }
3413
+ getProviderRateLimits(provider) {
3414
+ switch (provider) {
3415
+ case "github-copilot":
3416
+ return { concurrency: 1, intervalMs: 4e3, minRetryMs: 5e3, maxRetryMs: 6e4 };
3417
+ case "openai":
3418
+ return { concurrency: 3, intervalMs: 500, minRetryMs: 1e3, maxRetryMs: 3e4 };
3419
+ case "google":
3420
+ return { concurrency: 5, intervalMs: 200, minRetryMs: 1e3, maxRetryMs: 3e4 };
3421
+ case "ollama":
3422
+ return { concurrency: 5, intervalMs: 0, minRetryMs: 500, maxRetryMs: 5e3 };
3423
+ default:
3424
+ return { concurrency: 3, intervalMs: 1e3, minRetryMs: 1e3, maxRetryMs: 3e4 };
3425
+ }
3426
+ }
3427
+ async initialize() {
3428
+ if (this.config.embeddingProvider === "auto") {
3429
+ this.configuredProviderInfo = await tryDetectProvider();
3430
+ } else {
3431
+ this.configuredProviderInfo = await detectEmbeddingProvider(this.config.embeddingProvider, this.config.embeddingModel);
3432
+ }
3433
+ if (!this.configuredProviderInfo) {
3434
+ throw new Error(
3435
+ "No embedding provider available. Configure GitHub, OpenAI, Google, or Ollama."
3436
+ );
3437
+ }
3438
+ this.logger.info("Initializing indexer", {
3439
+ provider: this.configuredProviderInfo.provider,
3440
+ model: this.configuredProviderInfo.modelInfo.model,
3441
+ scope: this.config.scope
3442
+ });
3443
+ this.provider = createEmbeddingProvider(this.configuredProviderInfo);
3444
+ await fsPromises2.mkdir(this.indexPath, { recursive: true });
3445
+ if (this.checkForInterruptedIndexing()) {
3446
+ await this.recoverFromInterruptedIndexing();
3447
+ }
3448
+ const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
3449
+ const storePath = path5.join(this.indexPath, "vectors");
3450
+ this.store = new VectorStore(storePath, dimensions);
3451
+ const indexFilePath = path5.join(this.indexPath, "vectors.usearch");
3452
+ if (existsSync4(indexFilePath)) {
3453
+ this.store.load();
3454
+ }
3455
+ const invertedIndexPath = path5.join(this.indexPath, "inverted-index.json");
3456
+ this.invertedIndex = new InvertedIndex(invertedIndexPath);
3457
+ try {
3458
+ this.invertedIndex.load();
3459
+ } catch {
3460
+ if (existsSync4(invertedIndexPath)) {
3461
+ await fsPromises2.unlink(invertedIndexPath);
3462
+ }
3463
+ this.invertedIndex = new InvertedIndex(invertedIndexPath);
3464
+ }
3465
+ const dbPath = path5.join(this.indexPath, "codebase.db");
3466
+ const dbIsNew = !existsSync4(dbPath);
3467
+ this.database = new Database(dbPath);
3468
+ if (dbIsNew && this.store.count() > 0) {
3469
+ this.migrateFromLegacyIndex();
3470
+ }
3471
+ this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
3472
+ if (!this.indexCompatibility.compatible) {
3473
+ this.logger.warn("Index compatibility issue detected", {
3474
+ reason: this.indexCompatibility.reason,
3475
+ storedMetadata: this.indexCompatibility.storedMetadata,
3476
+ configuredProviderInfo: this.configuredProviderInfo
3477
+ });
3478
+ }
3479
+ if (isGitRepo(this.projectRoot)) {
3480
+ this.currentBranch = getBranchOrDefault(this.projectRoot);
3481
+ this.baseBranch = getBaseBranch(this.projectRoot);
3482
+ this.logger.branch("info", "Detected git repository", {
3483
+ currentBranch: this.currentBranch,
3484
+ baseBranch: this.baseBranch
3485
+ });
3486
+ } else {
3487
+ this.currentBranch = "default";
3488
+ this.baseBranch = "default";
3489
+ this.logger.branch("debug", "Not a git repository, using default branch");
3490
+ }
3491
+ if (this.config.indexing.autoGc) {
3492
+ await this.maybeRunAutoGc();
3493
+ }
3494
+ }
3495
+ async maybeRunAutoGc() {
3496
+ if (!this.database) return;
3497
+ const lastGcTimestamp = this.database.getMetadata("lastGcTimestamp");
3498
+ const now = Date.now();
3499
+ const intervalMs = this.config.indexing.gcIntervalDays * 24 * 60 * 60 * 1e3;
3500
+ let shouldRunGc = false;
3501
+ if (!lastGcTimestamp) {
3502
+ shouldRunGc = true;
3503
+ } else {
3504
+ const lastGcTime = parseInt(lastGcTimestamp, 10);
3505
+ if (!isNaN(lastGcTime) && now - lastGcTime > intervalMs) {
3506
+ shouldRunGc = true;
3507
+ }
3508
+ }
3509
+ if (shouldRunGc) {
3510
+ await this.healthCheck();
3511
+ this.database.setMetadata("lastGcTimestamp", now.toString());
3512
+ }
3513
+ }
3514
+ async maybeRunOrphanGc() {
3515
+ if (!this.database) return;
3516
+ const stats = this.database.getStats();
3517
+ if (!stats) return;
3518
+ const orphanCount = stats.embeddingCount - stats.chunkCount;
3519
+ if (orphanCount > this.config.indexing.gcOrphanThreshold) {
3520
+ this.database.gcOrphanEmbeddings();
3521
+ this.database.gcOrphanChunks();
3522
+ this.database.setMetadata("lastGcTimestamp", Date.now().toString());
3523
+ }
3524
+ }
3525
+ migrateFromLegacyIndex() {
3526
+ if (!this.store || !this.database) return;
3527
+ const allMetadata = this.store.getAllMetadata();
3528
+ const chunkIds = [];
3529
+ const chunkDataBatch = [];
3530
+ for (const { key, metadata } of allMetadata) {
3531
+ const chunkData = {
3532
+ chunkId: key,
3533
+ contentHash: metadata.hash,
3534
+ filePath: metadata.filePath,
3535
+ startLine: metadata.startLine,
3536
+ endLine: metadata.endLine,
3537
+ nodeType: metadata.chunkType,
3538
+ name: metadata.name,
3539
+ language: metadata.language
3540
+ };
3541
+ chunkDataBatch.push(chunkData);
3542
+ chunkIds.push(key);
3543
+ }
3544
+ if (chunkDataBatch.length > 0) {
3545
+ this.database.upsertChunksBatch(chunkDataBatch);
3546
+ }
3547
+ this.database.addChunksToBranchBatch(this.currentBranch || "default", chunkIds);
3548
+ }
3549
+ loadIndexMetadata() {
3550
+ if (!this.database) return null;
3551
+ const version = this.database.getMetadata("index.version");
3552
+ if (!version) return null;
3553
+ return {
3554
+ indexVersion: version,
3555
+ embeddingProvider: this.database.getMetadata("index.embeddingProvider") ?? "",
3556
+ embeddingModel: this.database.getMetadata("index.embeddingModel") ?? "",
3557
+ embeddingDimensions: parseInt(this.database.getMetadata("index.embeddingDimensions") ?? "0", 10),
3558
+ createdAt: this.database.getMetadata("index.createdAt") ?? "",
3559
+ updatedAt: this.database.getMetadata("index.updatedAt") ?? ""
3560
+ };
3561
+ }
3562
+ saveIndexMetadata(provider) {
3563
+ if (!this.database) return;
3564
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3565
+ const existingCreatedAt = this.database.getMetadata("index.createdAt");
3566
+ this.database.setMetadata("index.version", INDEX_METADATA_VERSION);
3567
+ this.database.setMetadata("index.embeddingProvider", provider.provider);
3568
+ this.database.setMetadata("index.embeddingModel", provider.modelInfo.model);
3569
+ this.database.setMetadata("index.embeddingDimensions", provider.modelInfo.dimensions.toString());
3570
+ this.database.setMetadata("index.updatedAt", now);
3571
+ if (!existingCreatedAt) {
3572
+ this.database.setMetadata("index.createdAt", now);
3573
+ }
3574
+ }
3575
+ validateIndexCompatibility(provider) {
3576
+ const storedMetadata = this.loadIndexMetadata();
3577
+ if (!storedMetadata) {
3578
+ return { compatible: true };
3579
+ }
3580
+ const currentProvider = provider.provider;
3581
+ const currentModel = provider.modelInfo.model;
3582
+ const currentDimensions = provider.modelInfo.dimensions;
3583
+ if (storedMetadata.embeddingDimensions !== currentDimensions) {
3584
+ return {
3585
+ compatible: false,
3586
+ code: "DIMENSION_MISMATCH" /* DIMENSION_MISMATCH */,
3587
+ reason: `Dimension mismatch: index has ${storedMetadata.embeddingDimensions}D vectors (${storedMetadata.embeddingProvider}/${storedMetadata.embeddingModel}), but current provider uses ${currentDimensions}D (${currentProvider}/${currentModel}). Run index_codebase with force=true to rebuild.`,
3588
+ storedMetadata
3589
+ };
3590
+ }
3591
+ if (storedMetadata.embeddingModel !== currentModel) {
3592
+ return {
3593
+ compatible: false,
3594
+ code: "MODEL_MISMATCH" /* MODEL_MISMATCH */,
3595
+ reason: `Model mismatch: index was built with "${storedMetadata.embeddingModel}", but current model is "${currentModel}". Embeddings are incompatible. Run index_codebase with force=true to rebuild.`,
3596
+ storedMetadata
3597
+ };
3598
+ }
3599
+ if (storedMetadata.embeddingProvider !== currentProvider) {
3600
+ this.logger.warn("Provider changed", {
3601
+ storedProvider: storedMetadata.embeddingProvider,
3602
+ currentProvider
3603
+ });
3604
+ }
3605
+ return {
3606
+ compatible: true,
3607
+ storedMetadata
3608
+ };
3609
+ }
3610
+ checkCompatibility() {
3611
+ if (!this.indexCompatibility) {
3612
+ if (!this.configuredProviderInfo) {
3613
+ throw new Error("No embedding provider info, you must initialize the indexer first.");
3614
+ }
3615
+ this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
3616
+ }
3617
+ return this.indexCompatibility;
3618
+ }
3619
+ async ensureInitialized() {
3620
+ if (!this.store || !this.provider || !this.invertedIndex || !this.configuredProviderInfo || !this.database) {
3621
+ await this.initialize();
3622
+ }
3623
+ return {
3624
+ store: this.store,
3625
+ provider: this.provider,
3626
+ invertedIndex: this.invertedIndex,
3627
+ configuredProviderInfo: this.configuredProviderInfo,
3628
+ database: this.database
3629
+ };
3630
+ }
3631
+ async estimateCost() {
3632
+ const { configuredProviderInfo } = await this.ensureInitialized();
3633
+ const { files } = await collectFiles(
3634
+ this.projectRoot,
3635
+ this.config.include,
3636
+ this.config.exclude,
3637
+ this.config.indexing.maxFileSize
3638
+ );
3639
+ return createCostEstimate(files, configuredProviderInfo);
3640
+ }
3641
+ async index(onProgress) {
3642
+ const { store, provider, invertedIndex, database, configuredProviderInfo } = await this.ensureInitialized();
3643
+ if (!this.indexCompatibility?.compatible) {
3644
+ throw new Error(
3645
+ `${this.indexCompatibility?.reason} Run index_codebase with force=true to rebuild the index.`
3646
+ );
3647
+ }
3648
+ this.acquireIndexingLock();
3649
+ this.logger.recordIndexingStart();
3650
+ this.logger.info("Starting indexing", { projectRoot: this.projectRoot });
3651
+ const startTime = Date.now();
3652
+ const stats = {
3653
+ totalFiles: 0,
3654
+ totalChunks: 0,
3655
+ indexedChunks: 0,
3656
+ failedChunks: 0,
3657
+ tokensUsed: 0,
3658
+ durationMs: 0,
3659
+ existingChunks: 0,
3660
+ removedChunks: 0,
3661
+ skippedFiles: [],
3662
+ parseFailures: []
3663
+ };
3664
+ onProgress?.({
3665
+ phase: "scanning",
3666
+ filesProcessed: 0,
3667
+ totalFiles: 0,
3668
+ chunksProcessed: 0,
3669
+ totalChunks: 0
3670
+ });
3671
+ this.loadFileHashCache();
3672
+ const { files, skipped } = await collectFiles(
3673
+ this.projectRoot,
3674
+ this.config.include,
3675
+ this.config.exclude,
3676
+ this.config.indexing.maxFileSize
3677
+ );
3678
+ stats.totalFiles = files.length;
3679
+ stats.skippedFiles = skipped;
3680
+ this.logger.recordFilesScanned(files.length);
3681
+ this.logger.cache("debug", "Scanning files for changes", {
3682
+ totalFiles: files.length,
3683
+ skippedFiles: skipped.length
3684
+ });
3685
+ const changedFiles = [];
3686
+ const unchangedFilePaths = /* @__PURE__ */ new Set();
3687
+ const currentFileHashes = /* @__PURE__ */ new Map();
3688
+ for (const f of files) {
3689
+ const currentHash = hashFile(f.path);
3690
+ currentFileHashes.set(f.path, currentHash);
3691
+ if (this.fileHashCache.get(f.path) === currentHash) {
3692
+ unchangedFilePaths.add(f.path);
3693
+ this.logger.recordCacheHit();
3694
+ } else {
3695
+ const content = await fsPromises2.readFile(f.path, "utf-8");
3696
+ changedFiles.push({ path: f.path, content, hash: currentHash });
3697
+ this.logger.recordCacheMiss();
3698
+ }
3699
+ }
3700
+ this.logger.cache("info", "File hash cache results", {
3701
+ unchanged: unchangedFilePaths.size,
3702
+ changed: changedFiles.length
3703
+ });
3704
+ onProgress?.({
3705
+ phase: "parsing",
3706
+ filesProcessed: 0,
3707
+ totalFiles: files.length,
3708
+ chunksProcessed: 0,
3709
+ totalChunks: 0
3710
+ });
3711
+ const parseStartTime = performance2.now();
3712
+ const parsedFiles = parseFiles(changedFiles);
3713
+ const parseMs = performance2.now() - parseStartTime;
3714
+ this.logger.recordFilesParsed(parsedFiles.length);
3715
+ this.logger.recordParseDuration(parseMs);
3716
+ this.logger.debug("Parsed changed files", { parsedCount: parsedFiles.length, parseMs: parseMs.toFixed(2) });
3717
+ const existingChunks = /* @__PURE__ */ new Map();
3718
+ const existingChunksByFile = /* @__PURE__ */ new Map();
3719
+ for (const { key, metadata } of store.getAllMetadata()) {
3720
+ existingChunks.set(key, metadata.hash);
3721
+ const fileChunks = existingChunksByFile.get(metadata.filePath) || /* @__PURE__ */ new Set();
3722
+ fileChunks.add(key);
3723
+ existingChunksByFile.set(metadata.filePath, fileChunks);
3724
+ }
3725
+ const currentChunkIds = /* @__PURE__ */ new Set();
3726
+ const currentFilePaths = /* @__PURE__ */ new Set();
3727
+ const pendingChunks = [];
3728
+ for (const filePath of unchangedFilePaths) {
3729
+ currentFilePaths.add(filePath);
3730
+ const fileChunks = existingChunksByFile.get(filePath);
3731
+ if (fileChunks) {
3732
+ for (const chunkId of fileChunks) {
3733
+ currentChunkIds.add(chunkId);
3734
+ }
3735
+ }
3736
+ }
3737
+ const chunkDataBatch = [];
3738
+ for (const parsed of parsedFiles) {
3739
+ currentFilePaths.add(parsed.path);
3740
+ if (parsed.chunks.length === 0) {
3741
+ const relativePath = path5.relative(this.projectRoot, parsed.path);
3742
+ stats.parseFailures.push(relativePath);
3743
+ }
3744
+ let fileChunkCount = 0;
3745
+ for (const chunk of parsed.chunks) {
3746
+ if (fileChunkCount >= this.config.indexing.maxChunksPerFile) {
3747
+ break;
3748
+ }
3749
+ if (this.config.indexing.semanticOnly && chunk.chunkType === "other") {
3750
+ continue;
3751
+ }
3752
+ const id = generateChunkId(parsed.path, chunk);
3753
+ const contentHash = generateChunkHash(chunk);
3754
+ currentChunkIds.add(id);
3755
+ chunkDataBatch.push({
3756
+ chunkId: id,
3757
+ contentHash,
3758
+ filePath: parsed.path,
3759
+ startLine: chunk.startLine,
3760
+ endLine: chunk.endLine,
3761
+ nodeType: chunk.chunkType,
3762
+ name: chunk.name,
3763
+ language: chunk.language
3764
+ });
3765
+ if (existingChunks.get(id) === contentHash) {
3766
+ fileChunkCount++;
3767
+ continue;
3768
+ }
3769
+ const text = createEmbeddingText(chunk, parsed.path);
3770
+ const metadata = {
3771
+ filePath: parsed.path,
3772
+ startLine: chunk.startLine,
3773
+ endLine: chunk.endLine,
3774
+ chunkType: chunk.chunkType,
3775
+ name: chunk.name,
3776
+ language: chunk.language,
3777
+ hash: contentHash
3778
+ };
3779
+ pendingChunks.push({ id, text, content: chunk.content, contentHash, metadata });
3780
+ fileChunkCount++;
3781
+ }
3782
+ }
3783
+ if (chunkDataBatch.length > 0) {
3784
+ database.upsertChunksBatch(chunkDataBatch);
3785
+ }
3786
+ let removedCount = 0;
3787
+ for (const [chunkId] of existingChunks) {
3788
+ if (!currentChunkIds.has(chunkId)) {
3789
+ store.remove(chunkId);
3790
+ invertedIndex.removeChunk(chunkId);
3791
+ removedCount++;
3792
+ }
3793
+ }
3794
+ stats.totalChunks = pendingChunks.length;
3795
+ stats.existingChunks = currentChunkIds.size - pendingChunks.length;
3796
+ stats.removedChunks = removedCount;
3797
+ this.logger.recordChunksProcessed(currentChunkIds.size);
3798
+ this.logger.recordChunksRemoved(removedCount);
3799
+ this.logger.info("Chunk analysis complete", {
3800
+ pending: pendingChunks.length,
3801
+ existing: stats.existingChunks,
3802
+ removed: removedCount
3803
+ });
3804
+ if (pendingChunks.length === 0 && removedCount === 0) {
3805
+ database.clearBranch(this.currentBranch);
3806
+ database.addChunksToBranchBatch(this.currentBranch, Array.from(currentChunkIds));
3807
+ this.fileHashCache = currentFileHashes;
3808
+ this.saveFileHashCache();
3809
+ stats.durationMs = Date.now() - startTime;
3810
+ onProgress?.({
3811
+ phase: "complete",
3812
+ filesProcessed: files.length,
3813
+ totalFiles: files.length,
3814
+ chunksProcessed: 0,
3815
+ totalChunks: 0
3816
+ });
3817
+ this.releaseIndexingLock();
3818
+ return stats;
3819
+ }
3820
+ if (pendingChunks.length === 0) {
3821
+ database.clearBranch(this.currentBranch);
3822
+ database.addChunksToBranchBatch(this.currentBranch, Array.from(currentChunkIds));
3823
+ store.save();
3824
+ invertedIndex.save();
3825
+ this.fileHashCache = currentFileHashes;
3826
+ this.saveFileHashCache();
3827
+ stats.durationMs = Date.now() - startTime;
3828
+ onProgress?.({
3829
+ phase: "complete",
3830
+ filesProcessed: files.length,
3831
+ totalFiles: files.length,
3832
+ chunksProcessed: 0,
3833
+ totalChunks: 0
3834
+ });
3835
+ this.releaseIndexingLock();
3836
+ return stats;
3837
+ }
3838
+ onProgress?.({
3839
+ phase: "embedding",
3840
+ filesProcessed: files.length,
3841
+ totalFiles: files.length,
3842
+ chunksProcessed: 0,
3843
+ totalChunks: pendingChunks.length
3844
+ });
3845
+ const allContentHashes = pendingChunks.map((c) => c.contentHash);
3846
+ const missingHashes = new Set(database.getMissingEmbeddings(allContentHashes));
3847
+ const chunksNeedingEmbedding = pendingChunks.filter((c) => missingHashes.has(c.contentHash));
3848
+ const chunksWithExistingEmbedding = pendingChunks.filter((c) => !missingHashes.has(c.contentHash));
3849
+ this.logger.cache("info", "Embedding cache lookup", {
3850
+ needsEmbedding: chunksNeedingEmbedding.length,
3851
+ fromCache: chunksWithExistingEmbedding.length
3852
+ });
3853
+ this.logger.recordChunksFromCache(chunksWithExistingEmbedding.length);
3854
+ for (const chunk of chunksWithExistingEmbedding) {
3855
+ const embeddingBuffer = database.getEmbedding(chunk.contentHash);
3856
+ if (embeddingBuffer) {
3857
+ const vector = bufferToFloat32Array(embeddingBuffer);
3858
+ store.add(chunk.id, Array.from(vector), chunk.metadata);
3859
+ invertedIndex.removeChunk(chunk.id);
3860
+ invertedIndex.addChunk(chunk.id, chunk.content);
3861
+ stats.indexedChunks++;
3862
+ }
3863
+ }
3864
+ const providerRateLimits = this.getProviderRateLimits(configuredProviderInfo.provider);
3865
+ const queue = new PQueue({
3866
+ concurrency: providerRateLimits.concurrency,
3867
+ interval: providerRateLimits.intervalMs,
3868
+ intervalCap: providerRateLimits.concurrency
3869
+ });
3870
+ const dynamicBatches = createDynamicBatches(chunksNeedingEmbedding);
3871
+ let rateLimitBackoffMs = 0;
3872
+ for (const batch of dynamicBatches) {
3873
+ queue.add(async () => {
3874
+ if (rateLimitBackoffMs > 0) {
3875
+ await new Promise((resolve4) => setTimeout(resolve4, rateLimitBackoffMs));
3876
+ }
3877
+ try {
3878
+ const result = await pRetry(
3879
+ async () => {
3880
+ const texts = batch.map((c) => c.text);
3881
+ return provider.embedBatch(texts);
3882
+ },
3883
+ {
3884
+ retries: this.config.indexing.retries,
3885
+ minTimeout: Math.max(this.config.indexing.retryDelayMs, providerRateLimits.minRetryMs),
3886
+ maxTimeout: providerRateLimits.maxRetryMs,
3887
+ factor: 2,
3888
+ onFailedAttempt: (error) => {
3889
+ const message = getErrorMessage(error);
3890
+ if (isRateLimitError(error)) {
3891
+ rateLimitBackoffMs = Math.min(providerRateLimits.maxRetryMs, (rateLimitBackoffMs || providerRateLimits.minRetryMs) * 2);
3892
+ this.logger.embedding("warn", `Rate limited, backing off`, {
3893
+ attempt: error.attemptNumber,
3894
+ retriesLeft: error.retriesLeft,
3895
+ backoffMs: rateLimitBackoffMs
3896
+ });
3897
+ } else {
3898
+ this.logger.embedding("error", `Embedding batch failed`, {
3899
+ attempt: error.attemptNumber,
3900
+ error: message
3901
+ });
3902
+ }
3903
+ }
3904
+ }
3905
+ );
3906
+ if (rateLimitBackoffMs > 0) {
3907
+ rateLimitBackoffMs = Math.max(0, rateLimitBackoffMs - 2e3);
3908
+ }
3909
+ const items = batch.map((chunk, idx) => ({
3910
+ id: chunk.id,
3911
+ vector: result.embeddings[idx],
3912
+ metadata: chunk.metadata
3913
+ }));
3914
+ store.addBatch(items);
3915
+ const embeddingBatchItems = batch.map((chunk, i) => ({
3916
+ contentHash: chunk.contentHash,
3917
+ embedding: float32ArrayToBuffer(result.embeddings[i]),
3918
+ chunkText: chunk.text,
3919
+ model: configuredProviderInfo.modelInfo.model
3920
+ }));
3921
+ database.upsertEmbeddingsBatch(embeddingBatchItems);
3922
+ for (const chunk of batch) {
3923
+ invertedIndex.removeChunk(chunk.id);
3924
+ invertedIndex.addChunk(chunk.id, chunk.content);
3925
+ }
3926
+ stats.indexedChunks += batch.length;
3927
+ stats.tokensUsed += result.totalTokensUsed;
3928
+ this.logger.recordChunksEmbedded(batch.length);
3929
+ this.logger.recordEmbeddingApiCall(result.totalTokensUsed);
3930
+ this.logger.embedding("debug", `Embedded batch`, {
3931
+ batchSize: batch.length,
3932
+ tokens: result.totalTokensUsed
3933
+ });
3934
+ onProgress?.({
3935
+ phase: "embedding",
3936
+ filesProcessed: files.length,
3937
+ totalFiles: files.length,
3938
+ chunksProcessed: stats.indexedChunks,
3939
+ totalChunks: pendingChunks.length
3940
+ });
3941
+ } catch (error) {
3942
+ stats.failedChunks += batch.length;
3943
+ this.addFailedBatch(batch, getErrorMessage(error));
3944
+ this.logger.recordEmbeddingError();
3945
+ this.logger.embedding("error", `Failed to embed batch after retries`, {
3946
+ batchSize: batch.length,
3947
+ error: getErrorMessage(error)
3948
+ });
3949
+ }
3950
+ });
3951
+ }
3952
+ await queue.onIdle();
3953
+ onProgress?.({
3954
+ phase: "storing",
3955
+ filesProcessed: files.length,
3956
+ totalFiles: files.length,
3957
+ chunksProcessed: stats.indexedChunks,
3958
+ totalChunks: pendingChunks.length
3959
+ });
3960
+ database.clearBranch(this.currentBranch);
3961
+ database.addChunksToBranchBatch(this.currentBranch, Array.from(currentChunkIds));
3962
+ store.save();
3963
+ invertedIndex.save();
3964
+ this.fileHashCache = currentFileHashes;
3965
+ this.saveFileHashCache();
3966
+ if (this.config.indexing.autoGc && stats.removedChunks > 0) {
3967
+ await this.maybeRunOrphanGc();
3968
+ }
3969
+ stats.durationMs = Date.now() - startTime;
3970
+ this.saveIndexMetadata(configuredProviderInfo);
3971
+ this.indexCompatibility = { compatible: true };
3972
+ this.logger.recordIndexingEnd();
3973
+ this.logger.info("Indexing complete", {
3974
+ files: stats.totalFiles,
3975
+ indexed: stats.indexedChunks,
3976
+ existing: stats.existingChunks,
3977
+ removed: stats.removedChunks,
3978
+ failed: stats.failedChunks,
3979
+ tokens: stats.tokensUsed,
3980
+ durationMs: stats.durationMs
3981
+ });
3982
+ if (stats.failedChunks > 0) {
3983
+ stats.failedBatchesPath = this.failedBatchesPath;
3984
+ }
3985
+ onProgress?.({
3986
+ phase: "complete",
3987
+ filesProcessed: files.length,
3988
+ totalFiles: files.length,
3989
+ chunksProcessed: stats.indexedChunks,
3990
+ totalChunks: pendingChunks.length
3991
+ });
3992
+ this.releaseIndexingLock();
3993
+ return stats;
3994
+ }
3995
+ async getQueryEmbedding(query, provider) {
3996
+ const now = Date.now();
3997
+ const cached = this.queryEmbeddingCache.get(query);
3998
+ if (cached && now - cached.timestamp < this.queryCacheTtlMs) {
3999
+ this.logger.cache("debug", "Query embedding cache hit (exact)", { query: query.slice(0, 50) });
4000
+ this.logger.recordQueryCacheHit();
4001
+ return cached.embedding;
4002
+ }
4003
+ const similarMatch = this.findSimilarCachedQuery(query, now);
4004
+ if (similarMatch) {
4005
+ this.logger.cache("debug", "Query embedding cache hit (similar)", {
4006
+ query: query.slice(0, 50),
4007
+ similarTo: similarMatch.key.slice(0, 50),
4008
+ similarity: similarMatch.similarity.toFixed(3)
4009
+ });
4010
+ this.logger.recordQueryCacheSimilarHit();
4011
+ return similarMatch.embedding;
4012
+ }
4013
+ this.logger.cache("debug", "Query embedding cache miss", { query: query.slice(0, 50) });
4014
+ this.logger.recordQueryCacheMiss();
4015
+ const { embedding, tokensUsed } = await provider.embedQuery(query);
4016
+ this.logger.recordEmbeddingApiCall(tokensUsed);
4017
+ if (this.queryEmbeddingCache.size >= this.maxQueryCacheSize) {
4018
+ const oldestKey = this.queryEmbeddingCache.keys().next().value;
4019
+ if (oldestKey) {
4020
+ this.queryEmbeddingCache.delete(oldestKey);
4021
+ }
4022
+ }
4023
+ this.queryEmbeddingCache.set(query, { embedding, timestamp: now });
4024
+ return embedding;
4025
+ }
4026
+ findSimilarCachedQuery(query, now) {
4027
+ const queryTokens = this.tokenize(query);
4028
+ if (queryTokens.size === 0) return null;
4029
+ let bestMatch = null;
4030
+ for (const [cachedQuery, { embedding, timestamp }] of this.queryEmbeddingCache) {
4031
+ if (now - timestamp >= this.queryCacheTtlMs) continue;
4032
+ const cachedTokens = this.tokenize(cachedQuery);
4033
+ const similarity = this.jaccardSimilarity(queryTokens, cachedTokens);
4034
+ if (similarity >= this.querySimilarityThreshold) {
4035
+ if (!bestMatch || similarity > bestMatch.similarity) {
4036
+ bestMatch = { key: cachedQuery, embedding, similarity };
4037
+ }
4038
+ }
4039
+ }
4040
+ return bestMatch;
4041
+ }
4042
+ tokenize(text) {
4043
+ return new Set(
4044
+ text.toLowerCase().replace(/[^\w\s]/g, " ").split(/\s+/).filter((t) => t.length > 1)
4045
+ );
4046
+ }
4047
+ jaccardSimilarity(a, b) {
4048
+ if (a.size === 0 && b.size === 0) return 1;
4049
+ if (a.size === 0 || b.size === 0) return 0;
4050
+ let intersection = 0;
4051
+ for (const token of a) {
4052
+ if (b.has(token)) intersection++;
4053
+ }
4054
+ const union = a.size + b.size - intersection;
4055
+ return intersection / union;
4056
+ }
4057
+ async search(query, limit, options) {
4058
+ const { store, provider, database } = await this.ensureInitialized();
4059
+ const compatibility = this.checkCompatibility();
4060
+ if (!compatibility.compatible) {
4061
+ throw new Error(
4062
+ `${compatibility.reason ?? "Index is incompatible with current embedding provider."} A possible solution is to run index_codebase with force=true to rebuild the index.`
4063
+ );
4064
+ }
4065
+ const searchStartTime = performance2.now();
4066
+ if (store.count() === 0) {
4067
+ this.logger.search("debug", "Search on empty index", { query });
4068
+ return [];
4069
+ }
4070
+ const maxResults = limit ?? this.config.search.maxResults;
4071
+ const hybridWeight = options?.hybridWeight ?? this.config.search.hybridWeight;
4072
+ const filterByBranch = options?.filterByBranch ?? true;
4073
+ this.logger.search("debug", "Starting search", {
4074
+ query,
4075
+ maxResults,
4076
+ hybridWeight,
4077
+ filterByBranch
4078
+ });
4079
+ const embeddingStartTime = performance2.now();
4080
+ const embedding = await this.getQueryEmbedding(query, provider);
4081
+ const embeddingMs = performance2.now() - embeddingStartTime;
4082
+ const vectorStartTime = performance2.now();
4083
+ const semanticResults = store.search(embedding, maxResults * 4);
4084
+ const vectorMs = performance2.now() - vectorStartTime;
4085
+ const keywordStartTime = performance2.now();
4086
+ const keywordResults = await this.keywordSearch(query, maxResults * 4);
4087
+ const keywordMs = performance2.now() - keywordStartTime;
4088
+ const fusionStartTime = performance2.now();
4089
+ const combined = this.fuseResults(semanticResults, keywordResults, hybridWeight, maxResults * 4);
4090
+ const fusionMs = performance2.now() - fusionStartTime;
4091
+ let branchChunkIds = null;
4092
+ if (filterByBranch && this.currentBranch !== "default") {
4093
+ branchChunkIds = new Set(database.getBranchChunkIds(this.currentBranch));
4094
+ }
4095
+ const filtered = combined.filter((r) => {
4096
+ if (r.score < this.config.search.minScore) return false;
4097
+ if (branchChunkIds && !branchChunkIds.has(r.id)) return false;
4098
+ if (options?.fileType) {
4099
+ const ext = r.metadata.filePath.split(".").pop()?.toLowerCase();
4100
+ if (ext !== options.fileType.toLowerCase().replace(/^\./, "")) return false;
4101
+ }
4102
+ if (options?.directory) {
4103
+ const normalizedDir = options.directory.replace(/^\/|\/$/g, "");
4104
+ if (!r.metadata.filePath.includes(`/${normalizedDir}/`) && !r.metadata.filePath.includes(`${normalizedDir}/`)) return false;
4105
+ }
4106
+ if (options?.chunkType) {
4107
+ if (r.metadata.chunkType !== options.chunkType) return false;
4108
+ }
4109
+ return true;
4110
+ }).slice(0, maxResults);
4111
+ const totalSearchMs = performance2.now() - searchStartTime;
4112
+ this.logger.recordSearch(totalSearchMs, {
4113
+ embeddingMs,
4114
+ vectorMs,
4115
+ keywordMs,
4116
+ fusionMs
4117
+ });
4118
+ this.logger.search("info", "Search complete", {
4119
+ query,
4120
+ results: filtered.length,
4121
+ totalMs: Math.round(totalSearchMs * 100) / 100,
4122
+ embeddingMs: Math.round(embeddingMs * 100) / 100,
4123
+ vectorMs: Math.round(vectorMs * 100) / 100,
4124
+ keywordMs: Math.round(keywordMs * 100) / 100,
4125
+ fusionMs: Math.round(fusionMs * 100) / 100
4126
+ });
4127
+ const metadataOnly = options?.metadataOnly ?? false;
4128
+ return Promise.all(
4129
+ filtered.map(async (r) => {
4130
+ let content = "";
4131
+ let contextStartLine = r.metadata.startLine;
4132
+ let contextEndLine = r.metadata.endLine;
4133
+ if (!metadataOnly && this.config.search.includeContext) {
4134
+ try {
4135
+ const fileContent = await fsPromises2.readFile(
4136
+ r.metadata.filePath,
4137
+ "utf-8"
4138
+ );
4139
+ const lines = fileContent.split("\n");
4140
+ const contextLines = options?.contextLines ?? this.config.search.contextLines;
4141
+ contextStartLine = Math.max(1, r.metadata.startLine - contextLines);
4142
+ contextEndLine = Math.min(lines.length, r.metadata.endLine + contextLines);
4143
+ content = lines.slice(contextStartLine - 1, contextEndLine).join("\n");
4144
+ } catch {
4145
+ content = "[File not accessible]";
4146
+ }
4147
+ }
4148
+ return {
4149
+ filePath: r.metadata.filePath,
4150
+ startLine: contextStartLine,
4151
+ endLine: contextEndLine,
4152
+ content,
4153
+ score: r.score,
4154
+ chunkType: r.metadata.chunkType,
4155
+ name: r.metadata.name
4156
+ };
4157
+ })
4158
+ );
4159
+ }
4160
+ async keywordSearch(query, limit) {
4161
+ const { store, invertedIndex } = await this.ensureInitialized();
4162
+ const scores = invertedIndex.search(query);
4163
+ if (scores.size === 0) {
4164
+ return [];
4165
+ }
4166
+ const chunkIds = Array.from(scores.keys());
4167
+ const metadataMap = store.getMetadataBatch(chunkIds);
4168
+ const results = [];
4169
+ for (const [chunkId, score] of scores) {
4170
+ const metadata = metadataMap.get(chunkId);
4171
+ if (metadata && score > 0) {
4172
+ results.push({ id: chunkId, score, metadata });
4173
+ }
4174
+ }
4175
+ results.sort((a, b) => b.score - a.score);
4176
+ return results.slice(0, limit);
4177
+ }
4178
+ fuseResults(semanticResults, keywordResults, keywordWeight, limit) {
4179
+ const semanticWeight = 1 - keywordWeight;
4180
+ const fusedScores = /* @__PURE__ */ new Map();
4181
+ for (const r of semanticResults) {
4182
+ fusedScores.set(r.id, {
4183
+ score: r.score * semanticWeight,
4184
+ metadata: r.metadata
4185
+ });
4186
+ }
4187
+ for (const r of keywordResults) {
4188
+ const existing = fusedScores.get(r.id);
4189
+ if (existing) {
4190
+ existing.score += r.score * keywordWeight;
4191
+ } else {
4192
+ fusedScores.set(r.id, {
4193
+ score: r.score * keywordWeight,
4194
+ metadata: r.metadata
4195
+ });
4196
+ }
4197
+ }
4198
+ const results = Array.from(fusedScores.entries()).map(([id, data]) => ({
4199
+ id,
4200
+ score: data.score,
4201
+ metadata: data.metadata
4202
+ }));
4203
+ results.sort((a, b) => b.score - a.score);
4204
+ return results.slice(0, limit);
4205
+ }
4206
+ async getStatus() {
4207
+ const { store, configuredProviderInfo } = await this.ensureInitialized();
4208
+ return {
4209
+ indexed: store.count() > 0,
4210
+ vectorCount: store.count(),
4211
+ provider: configuredProviderInfo.provider,
4212
+ model: configuredProviderInfo.modelInfo.model,
4213
+ indexPath: this.indexPath,
4214
+ currentBranch: this.currentBranch,
4215
+ baseBranch: this.baseBranch,
4216
+ compatibility: this.indexCompatibility
4217
+ };
4218
+ }
4219
+ async clearIndex() {
4220
+ const { store, invertedIndex, database } = await this.ensureInitialized();
4221
+ store.clear();
4222
+ store.save();
4223
+ invertedIndex.clear();
4224
+ invertedIndex.save();
4225
+ this.fileHashCache.clear();
4226
+ this.saveFileHashCache();
4227
+ database.clearBranch(this.currentBranch);
4228
+ database.deleteMetadata("index.version");
4229
+ database.deleteMetadata("index.embeddingProvider");
4230
+ database.deleteMetadata("index.embeddingModel");
4231
+ database.deleteMetadata("index.embeddingDimensions");
4232
+ database.deleteMetadata("index.createdAt");
4233
+ database.deleteMetadata("index.updatedAt");
4234
+ this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
4235
+ }
4236
+ async healthCheck() {
4237
+ const { store, invertedIndex, database } = await this.ensureInitialized();
4238
+ this.logger.gc("info", "Starting health check");
4239
+ const allMetadata = store.getAllMetadata();
4240
+ const filePathsToChunkKeys = /* @__PURE__ */ new Map();
4241
+ for (const { key, metadata } of allMetadata) {
4242
+ const existing = filePathsToChunkKeys.get(metadata.filePath) || [];
4243
+ existing.push(key);
4244
+ filePathsToChunkKeys.set(metadata.filePath, existing);
4245
+ }
4246
+ const removedFilePaths = [];
4247
+ let removedCount = 0;
4248
+ for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
4249
+ if (!existsSync4(filePath)) {
4250
+ for (const key of chunkKeys) {
4251
+ store.remove(key);
4252
+ invertedIndex.removeChunk(key);
4253
+ removedCount++;
4254
+ }
4255
+ database.deleteChunksByFile(filePath);
4256
+ removedFilePaths.push(filePath);
4257
+ }
4258
+ }
4259
+ if (removedCount > 0) {
4260
+ store.save();
4261
+ invertedIndex.save();
4262
+ }
4263
+ const gcOrphanEmbeddings = database.gcOrphanEmbeddings();
4264
+ const gcOrphanChunks = database.gcOrphanChunks();
4265
+ this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
4266
+ this.logger.gc("info", "Health check complete", {
4267
+ removedStale: removedCount,
4268
+ orphanEmbeddings: gcOrphanEmbeddings,
4269
+ orphanChunks: gcOrphanChunks,
4270
+ removedFiles: removedFilePaths.length
4271
+ });
4272
+ return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks };
4273
+ }
4274
+ async retryFailedBatches() {
4275
+ const { store, provider, invertedIndex } = await this.ensureInitialized();
4276
+ const failedBatches = this.loadFailedBatches();
4277
+ if (failedBatches.length === 0) {
4278
+ return { succeeded: 0, failed: 0, remaining: 0 };
4279
+ }
4280
+ let succeeded = 0;
4281
+ let failed = 0;
4282
+ const stillFailing = [];
4283
+ for (const batch of failedBatches) {
4284
+ try {
4285
+ const result = await pRetry(
4286
+ async () => {
4287
+ const texts = batch.chunks.map((c) => c.text);
4288
+ return provider.embedBatch(texts);
4289
+ },
4290
+ {
4291
+ retries: this.config.indexing.retries,
4292
+ minTimeout: this.config.indexing.retryDelayMs
4293
+ }
4294
+ );
4295
+ const items = batch.chunks.map((chunk, idx) => ({
4296
+ id: chunk.id,
4297
+ vector: result.embeddings[idx],
4298
+ metadata: chunk.metadata
4299
+ }));
4300
+ store.addBatch(items);
4301
+ for (const chunk of batch.chunks) {
4302
+ invertedIndex.removeChunk(chunk.id);
4303
+ invertedIndex.addChunk(chunk.id, chunk.content);
4304
+ }
4305
+ this.logger.recordChunksEmbedded(batch.chunks.length);
4306
+ this.logger.recordEmbeddingApiCall(result.totalTokensUsed);
4307
+ succeeded += batch.chunks.length;
4308
+ } catch (error) {
4309
+ failed += batch.chunks.length;
4310
+ this.logger.recordEmbeddingError();
4311
+ stillFailing.push({
4312
+ ...batch,
4313
+ attemptCount: batch.attemptCount + 1,
4314
+ lastAttempt: (/* @__PURE__ */ new Date()).toISOString(),
4315
+ error: String(error)
4316
+ });
4317
+ }
4318
+ }
4319
+ this.saveFailedBatches(stillFailing);
4320
+ if (succeeded > 0) {
4321
+ store.save();
4322
+ invertedIndex.save();
4323
+ }
4324
+ return { succeeded, failed, remaining: stillFailing.length };
4325
+ }
4326
+ getFailedBatchesCount() {
4327
+ return this.loadFailedBatches().length;
4328
+ }
4329
+ getCurrentBranch() {
4330
+ return this.currentBranch;
4331
+ }
4332
+ getBaseBranch() {
4333
+ return this.baseBranch;
4334
+ }
4335
+ refreshBranchInfo() {
4336
+ if (isGitRepo(this.projectRoot)) {
4337
+ this.currentBranch = getBranchOrDefault(this.projectRoot);
4338
+ this.baseBranch = getBaseBranch(this.projectRoot);
4339
+ }
4340
+ }
4341
+ async getDatabaseStats() {
4342
+ const { database } = await this.ensureInitialized();
4343
+ return database.getStats();
4344
+ }
4345
+ getLogger() {
4346
+ return this.logger;
4347
+ }
4348
+ async findSimilar(code, limit = this.config.search.maxResults, options) {
4349
+ const { store, provider, database } = await this.ensureInitialized();
4350
+ const compatibility = this.checkCompatibility();
4351
+ if (!compatibility.compatible) {
4352
+ throw new Error(
4353
+ `${compatibility.reason ?? "Index is incompatible with current embedding provider."} Run index_codebase with force=true to rebuild the index.`
4354
+ );
4355
+ }
4356
+ const searchStartTime = performance2.now();
4357
+ if (store.count() === 0) {
4358
+ this.logger.search("debug", "Find similar on empty index");
4359
+ return [];
4360
+ }
4361
+ const filterByBranch = options?.filterByBranch ?? true;
4362
+ this.logger.search("debug", "Starting find similar", {
4363
+ codeLength: code.length,
4364
+ limit,
4365
+ filterByBranch
4366
+ });
4367
+ const embeddingStartTime = performance2.now();
4368
+ const { embedding, tokensUsed } = await provider.embedDocument(code);
4369
+ const embeddingMs = performance2.now() - embeddingStartTime;
4370
+ this.logger.recordEmbeddingApiCall(tokensUsed);
4371
+ const vectorStartTime = performance2.now();
4372
+ const semanticResults = store.search(embedding, limit * 2);
4373
+ const vectorMs = performance2.now() - vectorStartTime;
4374
+ let branchChunkIds = null;
4375
+ if (filterByBranch && this.currentBranch !== "default") {
4376
+ branchChunkIds = new Set(database.getBranchChunkIds(this.currentBranch));
4377
+ }
4378
+ const filtered = semanticResults.filter((r) => {
4379
+ if (r.score < this.config.search.minScore) return false;
4380
+ if (branchChunkIds && !branchChunkIds.has(r.id)) return false;
4381
+ if (options?.excludeFile) {
4382
+ if (r.metadata.filePath === options.excludeFile) return false;
4383
+ }
4384
+ if (options?.fileType) {
4385
+ const ext = r.metadata.filePath.split(".").pop()?.toLowerCase();
4386
+ if (ext !== options.fileType.toLowerCase().replace(/^\./, "")) return false;
4387
+ }
4388
+ if (options?.directory) {
4389
+ const normalizedDir = options.directory.replace(/^\/|\/$/g, "");
4390
+ if (!r.metadata.filePath.includes(`/${normalizedDir}/`) && !r.metadata.filePath.includes(`${normalizedDir}/`)) return false;
4391
+ }
4392
+ if (options?.chunkType) {
4393
+ if (r.metadata.chunkType !== options.chunkType) return false;
4394
+ }
4395
+ return true;
4396
+ }).slice(0, limit);
4397
+ const totalSearchMs = performance2.now() - searchStartTime;
4398
+ this.logger.recordSearch(totalSearchMs, {
4399
+ embeddingMs,
4400
+ vectorMs,
4401
+ keywordMs: 0,
4402
+ fusionMs: 0
4403
+ });
4404
+ this.logger.search("info", "Find similar complete", {
4405
+ codeLength: code.length,
4406
+ results: filtered.length,
4407
+ totalMs: Math.round(totalSearchMs * 100) / 100,
4408
+ embeddingMs: Math.round(embeddingMs * 100) / 100,
4409
+ vectorMs: Math.round(vectorMs * 100) / 100
4410
+ });
4411
+ return Promise.all(
4412
+ filtered.map(async (r) => {
4413
+ let content = "";
4414
+ if (this.config.search.includeContext) {
4415
+ try {
4416
+ const fileContent = await fsPromises2.readFile(
4417
+ r.metadata.filePath,
4418
+ "utf-8"
4419
+ );
4420
+ const lines = fileContent.split("\n");
4421
+ content = lines.slice(r.metadata.startLine - 1, r.metadata.endLine).join("\n");
4422
+ } catch {
4423
+ content = "[File not accessible]";
4424
+ }
4425
+ }
4426
+ return {
4427
+ filePath: r.metadata.filePath,
4428
+ startLine: r.metadata.startLine,
4429
+ endLine: r.metadata.endLine,
4430
+ content,
4431
+ score: r.score,
4432
+ chunkType: r.metadata.chunkType,
4433
+ name: r.metadata.name
4434
+ };
4435
+ })
4436
+ );
4437
+ }
4438
+ };
4439
+
4440
+ // src/mcp-server.ts
4441
+ var MAX_CONTENT_LINES = 30;
4442
+ function truncateContent(content) {
4443
+ const lines = content.split("\n");
4444
+ if (lines.length <= MAX_CONTENT_LINES) return content;
4445
+ return lines.slice(0, MAX_CONTENT_LINES).join("\n") + `
4446
+ // ... (${lines.length - MAX_CONTENT_LINES} more lines)`;
4447
+ }
4448
+ function formatIndexStats(stats, verbose = false) {
4449
+ const lines = [];
4450
+ if (stats.indexedChunks === 0 && stats.removedChunks === 0) {
4451
+ lines.push(`Indexed. ${stats.totalFiles} files processed, ${stats.existingChunks} code chunks already up to date.`);
4452
+ } else if (stats.indexedChunks === 0) {
4453
+ lines.push(`Indexed. ${stats.totalFiles} files, removed ${stats.removedChunks} stale chunks, ${stats.existingChunks} chunks remain.`);
4454
+ } else {
4455
+ let main2 = `Indexed. ${stats.totalFiles} files processed, ${stats.indexedChunks} new chunks embedded.`;
4456
+ if (stats.existingChunks > 0) {
4457
+ main2 += ` ${stats.existingChunks} unchanged chunks skipped.`;
4458
+ }
4459
+ lines.push(main2);
4460
+ if (stats.removedChunks > 0) {
4461
+ lines.push(`Removed ${stats.removedChunks} stale chunks.`);
4462
+ }
4463
+ if (stats.failedChunks > 0) {
4464
+ lines.push(`Failed: ${stats.failedChunks} chunks.`);
4465
+ }
4466
+ lines.push(`Tokens: ${stats.tokensUsed.toLocaleString()}, Duration: ${(stats.durationMs / 1e3).toFixed(1)}s`);
4467
+ }
4468
+ if (verbose) {
4469
+ if (stats.skippedFiles.length > 0) {
4470
+ const tooLarge = stats.skippedFiles.filter((f) => f.reason === "too_large");
4471
+ const excluded = stats.skippedFiles.filter((f) => f.reason === "excluded");
4472
+ const gitignored = stats.skippedFiles.filter((f) => f.reason === "gitignore");
4473
+ lines.push("");
4474
+ lines.push(`Skipped files: ${stats.skippedFiles.length}`);
4475
+ if (tooLarge.length > 0) {
4476
+ lines.push(` Too large (${tooLarge.length}): ${tooLarge.slice(0, 5).map((f) => f.path).join(", ")}${tooLarge.length > 5 ? "..." : ""}`);
4477
+ }
4478
+ if (excluded.length > 0) {
4479
+ lines.push(` Excluded (${excluded.length}): ${excluded.slice(0, 5).map((f) => f.path).join(", ")}${excluded.length > 5 ? "..." : ""}`);
4480
+ }
4481
+ if (gitignored.length > 0) {
4482
+ lines.push(` Gitignored (${gitignored.length}): ${gitignored.slice(0, 5).map((f) => f.path).join(", ")}${gitignored.length > 5 ? "..." : ""}`);
4483
+ }
4484
+ }
4485
+ if (stats.parseFailures.length > 0) {
4486
+ lines.push("");
4487
+ lines.push(`Files with no extractable chunks (${stats.parseFailures.length}): ${stats.parseFailures.slice(0, 10).join(", ")}${stats.parseFailures.length > 10 ? "..." : ""}`);
4488
+ }
4489
+ }
4490
+ return lines.join("\n");
4491
+ }
4492
+ function formatStatus(status) {
4493
+ if (!status.indexed) {
4494
+ return "Codebase is not indexed. Run index_codebase to create an index.";
4495
+ }
4496
+ const lines = [
4497
+ `Index status:`,
4498
+ ` Indexed chunks: ${status.vectorCount.toLocaleString()}`,
4499
+ ` Provider: ${status.provider}`,
4500
+ ` Model: ${status.model}`,
4501
+ ` Location: ${status.indexPath}`
4502
+ ];
4503
+ if (status.currentBranch !== "default") {
4504
+ lines.push(` Current branch: ${status.currentBranch}`);
4505
+ lines.push(` Base branch: ${status.baseBranch}`);
4506
+ }
4507
+ return lines.join("\n");
4508
+ }
4509
+ var CHUNK_TYPE_ENUM = [
4510
+ "function",
4511
+ "class",
4512
+ "method",
4513
+ "interface",
4514
+ "type",
4515
+ "enum",
4516
+ "struct",
4517
+ "impl",
4518
+ "trait",
4519
+ "module",
4520
+ "other"
4521
+ ];
4522
+ function createMcpServer(projectRoot, config) {
4523
+ const server = new McpServer({
4524
+ name: "opencode-codebase-index",
4525
+ version: "0.5.0"
4526
+ });
4527
+ const indexer = new Indexer(projectRoot, config);
4528
+ let initialized = false;
4529
+ async function ensureInitialized() {
4530
+ if (!initialized) {
4531
+ await indexer.initialize();
4532
+ initialized = true;
4533
+ }
4534
+ }
4535
+ server.tool(
4536
+ "codebase_search",
4537
+ "Search codebase by MEANING, not keywords. Returns full code content. For just finding WHERE code is (saves ~90% tokens), use codebase_peek instead.",
4538
+ {
4539
+ query: z.string().describe("Natural language description of what code you're looking for. Describe behavior, not syntax."),
4540
+ limit: z.number().optional().default(5).describe("Maximum number of results to return"),
4541
+ fileType: z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
4542
+ directory: z.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
4543
+ chunkType: z.enum(CHUNK_TYPE_ENUM).optional().describe("Filter by code chunk type"),
4544
+ contextLines: z.number().optional().describe("Number of extra lines to include before/after each match (default: 0)")
4545
+ },
4546
+ async (args) => {
4547
+ await ensureInitialized();
4548
+ const results = await indexer.search(args.query, args.limit ?? 5, {
4549
+ fileType: args.fileType,
4550
+ directory: args.directory,
4551
+ chunkType: args.chunkType,
4552
+ contextLines: args.contextLines
4553
+ });
4554
+ if (results.length === 0) {
4555
+ return { content: [{ type: "text", text: "No matching code found. Try a different query or run index_codebase first." }] };
4556
+ }
4557
+ const formatted = results.map((r, idx) => {
4558
+ const header = r.name ? `[${idx + 1}] ${r.chunkType} "${r.name}" in ${r.filePath}:${r.startLine}-${r.endLine}` : `[${idx + 1}] ${r.chunkType} in ${r.filePath}:${r.startLine}-${r.endLine}`;
4559
+ return `${header} (score: ${r.score.toFixed(2)})
4560
+ \`\`\`
4561
+ ${truncateContent(r.content)}
4562
+ \`\`\``;
4563
+ });
4564
+ return { content: [{ type: "text", text: `Found ${results.length} results for "${args.query}":
4565
+
4566
+ ${formatted.join("\n\n")}` }] };
4567
+ }
4568
+ );
4569
+ server.tool(
4570
+ "codebase_peek",
4571
+ "Quick lookup of code locations by meaning. Returns only metadata (file, line, name, type) WITHOUT code content. Saves ~90% tokens vs codebase_search.",
4572
+ {
4573
+ query: z.string().describe("Natural language description of what code you're looking for."),
4574
+ limit: z.number().optional().default(10).describe("Maximum number of results to return"),
4575
+ fileType: z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
4576
+ directory: z.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
4577
+ chunkType: z.enum(CHUNK_TYPE_ENUM).optional().describe("Filter by code chunk type")
4578
+ },
4579
+ async (args) => {
4580
+ await ensureInitialized();
4581
+ const results = await indexer.search(args.query, args.limit ?? 10, {
4582
+ fileType: args.fileType,
4583
+ directory: args.directory,
4584
+ chunkType: args.chunkType,
4585
+ metadataOnly: true
4586
+ });
4587
+ if (results.length === 0) {
4588
+ return { content: [{ type: "text", text: "No matching code found. Try a different query or run index_codebase first." }] };
4589
+ }
4590
+ const formatted = results.map((r, idx) => {
4591
+ const location = `${r.filePath}:${r.startLine}-${r.endLine}`;
4592
+ const name = r.name ? `"${r.name}"` : "(anonymous)";
4593
+ return `[${idx + 1}] ${r.chunkType} ${name} at ${location} (score: ${r.score.toFixed(2)})`;
4594
+ });
4595
+ return { content: [{ type: "text", text: `Found ${results.length} locations for "${args.query}":
4596
+
4597
+ ${formatted.join("\n")}
4598
+
4599
+ Use Read tool to examine specific files.` }] };
4600
+ }
4601
+ );
4602
+ server.tool(
4603
+ "index_codebase",
4604
+ "Index the codebase for semantic search. Creates vector embeddings of code chunks. Incremental - only re-indexes changed files. Run before first codebase_search.",
4605
+ {
4606
+ force: z.boolean().optional().default(false).describe("Force reindex even if already indexed"),
4607
+ estimateOnly: z.boolean().optional().default(false).describe("Only show cost estimate without indexing"),
4608
+ verbose: z.boolean().optional().default(false).describe("Show detailed info about skipped files and parsing failures")
4609
+ },
4610
+ async (args) => {
4611
+ await ensureInitialized();
4612
+ if (args.estimateOnly) {
4613
+ const estimate = await indexer.estimateCost();
4614
+ return { content: [{ type: "text", text: formatCostEstimate(estimate) }] };
4615
+ }
4616
+ if (args.force) {
4617
+ await indexer.clearIndex();
4618
+ }
4619
+ const stats = await indexer.index();
4620
+ return { content: [{ type: "text", text: formatIndexStats(stats, args.verbose ?? false) }] };
4621
+ }
4622
+ );
4623
+ server.tool(
4624
+ "index_status",
4625
+ "Check the status of the codebase index. Shows whether the codebase is indexed, how many chunks are stored, and the embedding provider being used.",
4626
+ {},
4627
+ async () => {
4628
+ await ensureInitialized();
4629
+ const status = await indexer.getStatus();
4630
+ return { content: [{ type: "text", text: formatStatus(status) }] };
4631
+ }
4632
+ );
4633
+ server.tool(
4634
+ "index_health_check",
4635
+ "Check index health and remove stale entries from deleted files. Run this to clean up the index after files have been deleted.",
4636
+ {},
4637
+ async () => {
4638
+ await ensureInitialized();
4639
+ const result = await indexer.healthCheck();
4640
+ if (result.removed === 0 && result.gcOrphanEmbeddings === 0 && result.gcOrphanChunks === 0) {
4641
+ return { content: [{ type: "text", text: "Index is healthy. No stale entries found." }] };
4642
+ }
4643
+ const lines = [`Health check complete:`];
4644
+ if (result.removed > 0) {
4645
+ lines.push(` Removed stale entries: ${result.removed}`);
4646
+ }
4647
+ if (result.gcOrphanEmbeddings > 0) {
4648
+ lines.push(` Garbage collected orphan embeddings: ${result.gcOrphanEmbeddings}`);
4649
+ }
4650
+ if (result.gcOrphanChunks > 0) {
4651
+ lines.push(` Garbage collected orphan chunks: ${result.gcOrphanChunks}`);
4652
+ }
4653
+ if (result.filePaths.length > 0) {
4654
+ lines.push(` Cleaned paths: ${result.filePaths.join(", ")}`);
4655
+ }
4656
+ return { content: [{ type: "text", text: lines.join("\n") }] };
4657
+ }
4658
+ );
4659
+ server.tool(
4660
+ "index_metrics",
4661
+ "Get metrics and performance statistics for the codebase index. Requires debug.enabled=true and debug.metrics=true in config.",
4662
+ {},
4663
+ async () => {
4664
+ await ensureInitialized();
4665
+ const logger = indexer.getLogger();
4666
+ if (!logger.isEnabled()) {
4667
+ return { content: [{ type: "text", text: 'Debug mode is disabled. Enable it in your config:\n\n```json\n{\n "debug": {\n "enabled": true,\n "metrics": true\n }\n}\n```' }] };
4668
+ }
4669
+ if (!logger.isMetricsEnabled()) {
4670
+ return { content: [{ type: "text", text: 'Metrics collection is disabled. Enable it in your config:\n\n```json\n{\n "debug": {\n "enabled": true,\n "metrics": true\n }\n}\n```' }] };
4671
+ }
4672
+ return { content: [{ type: "text", text: logger.formatMetrics() }] };
4673
+ }
4674
+ );
4675
+ server.tool(
4676
+ "index_logs",
4677
+ "Get recent debug logs from the codebase indexer. Requires debug.enabled=true in config.",
4678
+ {
4679
+ limit: z.number().optional().default(20).describe("Maximum number of log entries to return"),
4680
+ category: z.enum(["search", "embedding", "cache", "gc", "branch", "general"]).optional().describe("Filter by log category"),
4681
+ level: z.enum(["error", "warn", "info", "debug"]).optional().describe("Filter by minimum log level")
4682
+ },
4683
+ async (args) => {
4684
+ await ensureInitialized();
4685
+ const logger = indexer.getLogger();
4686
+ if (!logger.isEnabled()) {
4687
+ return { content: [{ type: "text", text: 'Debug mode is disabled. Enable it in your config:\n\n```json\n{\n "debug": {\n "enabled": true\n }\n}\n```' }] };
4688
+ }
4689
+ let logs;
4690
+ if (args.category) {
4691
+ logs = logger.getLogsByCategory(args.category, args.limit);
4692
+ } else if (args.level) {
4693
+ logs = logger.getLogsByLevel(args.level, args.limit);
4694
+ } else {
4695
+ logs = logger.getLogs(args.limit);
4696
+ }
4697
+ if (logs.length === 0) {
4698
+ return { content: [{ type: "text", text: "No logs recorded yet. Logs are captured during indexing and search operations." }] };
4699
+ }
4700
+ const text = logs.map((l) => {
4701
+ const dataStr = l.data ? ` ${JSON.stringify(l.data)}` : "";
4702
+ return `[${l.timestamp}] [${l.level.toUpperCase()}] [${l.category}] ${l.message}${dataStr}`;
4703
+ }).join("\n");
4704
+ return { content: [{ type: "text", text }] };
4705
+ }
4706
+ );
4707
+ server.tool(
4708
+ "find_similar",
4709
+ "Find code similar to a given snippet. Use for duplicate detection, pattern discovery, or refactoring prep.",
4710
+ {
4711
+ code: z.string().describe("The code snippet to find similar code for"),
4712
+ limit: z.number().optional().default(10).describe("Maximum number of results to return"),
4713
+ fileType: z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
4714
+ directory: z.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
4715
+ chunkType: z.enum(CHUNK_TYPE_ENUM).optional().describe("Filter by code chunk type"),
4716
+ excludeFile: z.string().optional().describe("Exclude results from this file path")
4717
+ },
4718
+ async (args) => {
4719
+ await ensureInitialized();
4720
+ const results = await indexer.findSimilar(args.code, args.limit ?? 10, {
4721
+ fileType: args.fileType,
4722
+ directory: args.directory,
4723
+ chunkType: args.chunkType,
4724
+ excludeFile: args.excludeFile
4725
+ });
4726
+ if (results.length === 0) {
4727
+ return { content: [{ type: "text", text: "No similar code found. Try a different snippet or run index_codebase first." }] };
4728
+ }
4729
+ const formatted = results.map((r, idx) => {
4730
+ const header = r.name ? `[${idx + 1}] ${r.chunkType} "${r.name}" in ${r.filePath}:${r.startLine}-${r.endLine}` : `[${idx + 1}] ${r.chunkType} in ${r.filePath}:${r.startLine}-${r.endLine}`;
4731
+ return `${header} (similarity: ${(r.score * 100).toFixed(1)}%)
4732
+ \`\`\`
4733
+ ${truncateContent(r.content)}
4734
+ \`\`\``;
4735
+ });
4736
+ return { content: [{ type: "text", text: `Found ${results.length} similar code blocks:
4737
+
4738
+ ${formatted.join("\n\n")}` }] };
4739
+ }
4740
+ );
4741
+ server.prompt(
4742
+ "search",
4743
+ "Search codebase by meaning using semantic search",
4744
+ { query: z.string().describe("What to search for in the codebase") },
4745
+ (args) => ({
4746
+ messages: [{
4747
+ role: "user",
4748
+ content: {
4749
+ type: "text",
4750
+ text: `Search the codebase for: "${args.query}"
4751
+
4752
+ Use the codebase_search tool with this query. If you need just locations first, use codebase_peek instead to save tokens.`
4753
+ }
4754
+ }]
4755
+ })
4756
+ );
4757
+ server.prompt(
4758
+ "find",
4759
+ "Find code using hybrid approach (semantic + grep)",
4760
+ { query: z.string().describe("What to find in the codebase") },
4761
+ (args) => ({
4762
+ messages: [{
4763
+ role: "user",
4764
+ content: {
4765
+ type: "text",
4766
+ text: `Find code related to: "${args.query}"
4767
+
4768
+ Use a hybrid approach:
4769
+ 1. First use codebase_peek to find semantic matches by meaning
4770
+ 2. Then use grep for exact identifier matches
4771
+ 3. Combine results for comprehensive coverage`
4772
+ }
4773
+ }]
4774
+ })
4775
+ );
4776
+ server.prompt(
4777
+ "index",
4778
+ "Index the codebase for semantic search",
4779
+ { options: z.string().optional().describe("Options: 'force' to rebuild, 'estimate' to check costs") },
4780
+ (args) => {
4781
+ const opts = args.options?.toLowerCase() ?? "";
4782
+ let instruction = "Use the index_codebase tool to index the codebase for semantic search.";
4783
+ if (opts.includes("force")) {
4784
+ instruction = "Use the index_codebase tool with force=true to rebuild the entire index from scratch.";
4785
+ } else if (opts.includes("estimate")) {
4786
+ instruction = "Use the index_codebase tool with estimateOnly=true to check the cost estimate before indexing.";
4787
+ }
4788
+ return {
4789
+ messages: [{
4790
+ role: "user",
4791
+ content: { type: "text", text: instruction }
4792
+ }]
4793
+ };
4794
+ }
4795
+ );
4796
+ server.prompt(
4797
+ "status",
4798
+ "Check if the codebase is indexed and ready",
4799
+ {},
4800
+ () => ({
4801
+ messages: [{
4802
+ role: "user",
4803
+ content: {
4804
+ type: "text",
4805
+ text: "Use the index_status tool to check if the codebase index is ready and show its current state."
4806
+ }
4807
+ }]
4808
+ })
4809
+ );
4810
+ return server;
4811
+ }
4812
+
4813
+ // src/cli.ts
4814
+ function loadJsonFile(filePath) {
4815
+ try {
4816
+ if (existsSync5(filePath)) {
4817
+ const content = readFileSync5(filePath, "utf-8");
4818
+ return JSON.parse(content);
4819
+ }
4820
+ } catch {
4821
+ }
4822
+ return null;
4823
+ }
4824
+ function loadPluginConfig(projectRoot, configPath) {
4825
+ if (configPath) {
4826
+ const config = loadJsonFile(configPath);
4827
+ if (config) return config;
4828
+ }
4829
+ const projectConfig = loadJsonFile(path6.join(projectRoot, ".opencode", "codebase-index.json"));
4830
+ if (projectConfig) return projectConfig;
4831
+ const globalConfig = loadJsonFile(path6.join(os3.homedir(), ".config", "opencode", "codebase-index.json"));
4832
+ if (globalConfig) return globalConfig;
4833
+ return {};
4834
+ }
4835
+ function parseArgs(argv) {
4836
+ let project = process.cwd();
4837
+ let config;
4838
+ for (let i = 2; i < argv.length; i++) {
4839
+ if (argv[i] === "--project" && argv[i + 1]) {
4840
+ project = path6.resolve(argv[++i]);
4841
+ } else if (argv[i] === "--config" && argv[i + 1]) {
4842
+ config = path6.resolve(argv[++i]);
4843
+ }
4844
+ }
4845
+ return { project, config };
4846
+ }
4847
+ async function main() {
4848
+ const args = parseArgs(process.argv);
4849
+ const rawConfig = loadPluginConfig(args.project, args.config);
4850
+ const config = parseConfig(rawConfig);
4851
+ const server = createMcpServer(args.project, config);
4852
+ const transport = new StdioServerTransport();
4853
+ await server.connect(transport);
4854
+ const shutdown = () => {
4855
+ server.close().catch(() => {
4856
+ });
4857
+ process.exit(0);
4858
+ };
4859
+ process.on("SIGINT", shutdown);
4860
+ process.on("SIGTERM", shutdown);
4861
+ }
4862
+ main().catch((error) => {
4863
+ const message = error instanceof Error ? error.message : String(error);
4864
+ console.error(`Fatal: ${message}`);
4865
+ process.exit(1);
4866
+ });
4867
+ //# sourceMappingURL=cli.js.map