opencode-codebase-index 0.2.5 → 0.3.2

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/index.cjs CHANGED
@@ -491,7 +491,7 @@ var require_ignore = __commonJS({
491
491
  // path matching.
492
492
  // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
493
493
  // @returns {TestResult} true if a file is ignored
494
- test(path8, checkUnignored, mode) {
494
+ test(path9, checkUnignored, mode) {
495
495
  let ignored = false;
496
496
  let unignored = false;
497
497
  let matchedRule;
@@ -500,7 +500,7 @@ var require_ignore = __commonJS({
500
500
  if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
501
501
  return;
502
502
  }
503
- const matched = rule[mode].test(path8);
503
+ const matched = rule[mode].test(path9);
504
504
  if (!matched) {
505
505
  return;
506
506
  }
@@ -521,17 +521,17 @@ var require_ignore = __commonJS({
521
521
  var throwError = (message, Ctor) => {
522
522
  throw new Ctor(message);
523
523
  };
524
- var checkPath = (path8, originalPath, doThrow) => {
525
- if (!isString(path8)) {
524
+ var checkPath = (path9, originalPath, doThrow) => {
525
+ if (!isString(path9)) {
526
526
  return doThrow(
527
527
  `path must be a string, but got \`${originalPath}\``,
528
528
  TypeError
529
529
  );
530
530
  }
531
- if (!path8) {
531
+ if (!path9) {
532
532
  return doThrow(`path must not be empty`, TypeError);
533
533
  }
534
- if (checkPath.isNotRelative(path8)) {
534
+ if (checkPath.isNotRelative(path9)) {
535
535
  const r = "`path.relative()`d";
536
536
  return doThrow(
537
537
  `path should be a ${r} string, but got "${originalPath}"`,
@@ -540,7 +540,7 @@ var require_ignore = __commonJS({
540
540
  }
541
541
  return true;
542
542
  };
543
- var isNotRelative = (path8) => REGEX_TEST_INVALID_PATH.test(path8);
543
+ var isNotRelative = (path9) => REGEX_TEST_INVALID_PATH.test(path9);
544
544
  checkPath.isNotRelative = isNotRelative;
545
545
  checkPath.convert = (p) => p;
546
546
  var Ignore2 = class {
@@ -570,19 +570,19 @@ var require_ignore = __commonJS({
570
570
  }
571
571
  // @returns {TestResult}
572
572
  _test(originalPath, cache, checkUnignored, slices) {
573
- const path8 = originalPath && checkPath.convert(originalPath);
573
+ const path9 = originalPath && checkPath.convert(originalPath);
574
574
  checkPath(
575
- path8,
575
+ path9,
576
576
  originalPath,
577
577
  this._strictPathCheck ? throwError : RETURN_FALSE
578
578
  );
579
- return this._t(path8, cache, checkUnignored, slices);
579
+ return this._t(path9, cache, checkUnignored, slices);
580
580
  }
581
- checkIgnore(path8) {
582
- if (!REGEX_TEST_TRAILING_SLASH.test(path8)) {
583
- return this.test(path8);
581
+ checkIgnore(path9) {
582
+ if (!REGEX_TEST_TRAILING_SLASH.test(path9)) {
583
+ return this.test(path9);
584
584
  }
585
- const slices = path8.split(SLASH2).filter(Boolean);
585
+ const slices = path9.split(SLASH2).filter(Boolean);
586
586
  slices.pop();
587
587
  if (slices.length) {
588
588
  const parent = this._t(
@@ -595,18 +595,18 @@ var require_ignore = __commonJS({
595
595
  return parent;
596
596
  }
597
597
  }
598
- return this._rules.test(path8, false, MODE_CHECK_IGNORE);
598
+ return this._rules.test(path9, false, MODE_CHECK_IGNORE);
599
599
  }
600
- _t(path8, cache, checkUnignored, slices) {
601
- if (path8 in cache) {
602
- return cache[path8];
600
+ _t(path9, cache, checkUnignored, slices) {
601
+ if (path9 in cache) {
602
+ return cache[path9];
603
603
  }
604
604
  if (!slices) {
605
- slices = path8.split(SLASH2).filter(Boolean);
605
+ slices = path9.split(SLASH2).filter(Boolean);
606
606
  }
607
607
  slices.pop();
608
608
  if (!slices.length) {
609
- return cache[path8] = this._rules.test(path8, checkUnignored, MODE_IGNORE);
609
+ return cache[path9] = this._rules.test(path9, checkUnignored, MODE_IGNORE);
610
610
  }
611
611
  const parent = this._t(
612
612
  slices.join(SLASH2) + SLASH2,
@@ -614,29 +614,29 @@ var require_ignore = __commonJS({
614
614
  checkUnignored,
615
615
  slices
616
616
  );
617
- return cache[path8] = parent.ignored ? parent : this._rules.test(path8, checkUnignored, MODE_IGNORE);
617
+ return cache[path9] = parent.ignored ? parent : this._rules.test(path9, checkUnignored, MODE_IGNORE);
618
618
  }
619
- ignores(path8) {
620
- return this._test(path8, this._ignoreCache, false).ignored;
619
+ ignores(path9) {
620
+ return this._test(path9, this._ignoreCache, false).ignored;
621
621
  }
622
622
  createFilter() {
623
- return (path8) => !this.ignores(path8);
623
+ return (path9) => !this.ignores(path9);
624
624
  }
625
625
  filter(paths) {
626
626
  return makeArray(paths).filter(this.createFilter());
627
627
  }
628
628
  // @returns {TestResult}
629
- test(path8) {
630
- return this._test(path8, this._testCache, true);
629
+ test(path9) {
630
+ return this._test(path9, this._testCache, true);
631
631
  }
632
632
  };
633
633
  var factory = (options) => new Ignore2(options);
634
- var isPathValid = (path8) => checkPath(path8 && checkPath.convert(path8), path8, RETURN_FALSE);
634
+ var isPathValid = (path9) => checkPath(path9 && checkPath.convert(path9), path9, RETURN_FALSE);
635
635
  var setupWindows = () => {
636
636
  const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
637
637
  checkPath.convert = makePosix;
638
638
  const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
639
- checkPath.isNotRelative = (path8) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path8) || isNotRelative(path8);
639
+ checkPath.isNotRelative = (path9) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path9) || isNotRelative(path9);
640
640
  };
641
641
  if (
642
642
  // Detect `process` so that it can run in browsers.
@@ -657,9 +657,10 @@ __export(index_exports, {
657
657
  default: () => index_default
658
658
  });
659
659
  module.exports = __toCommonJS(index_exports);
660
- var import_fs5 = require("fs");
661
- var path7 = __toESM(require("path"), 1);
660
+ var import_fs6 = require("fs");
661
+ var path8 = __toESM(require("path"), 1);
662
662
  var os3 = __toESM(require("os"), 1);
663
+ var import_url2 = require("url");
663
664
 
664
665
  // src/config/schema.ts
665
666
  var DEFAULT_INCLUDE = [
@@ -697,7 +698,10 @@ function getDefaultIndexingConfig() {
697
698
  maxChunksPerFile: 100,
698
699
  semanticOnly: false,
699
700
  retries: 3,
700
- retryDelayMs: 1e3
701
+ retryDelayMs: 1e3,
702
+ autoGc: true,
703
+ gcIntervalDays: 7,
704
+ gcOrphanThreshold: 100
701
705
  };
702
706
  }
703
707
  function getDefaultSearchConfig() {
@@ -709,8 +713,21 @@ function getDefaultSearchConfig() {
709
713
  contextLines: 0
710
714
  };
711
715
  }
716
+ function getDefaultDebugConfig() {
717
+ return {
718
+ enabled: false,
719
+ logLevel: "info",
720
+ logSearch: true,
721
+ logEmbedding: true,
722
+ logCache: true,
723
+ logGc: true,
724
+ logBranch: true,
725
+ metrics: true
726
+ };
727
+ }
712
728
  var VALID_PROVIDERS = ["auto", "github-copilot", "openai", "google", "ollama"];
713
729
  var VALID_SCOPES = ["project", "global"];
730
+ var VALID_LOG_LEVELS = ["error", "warn", "info", "debug"];
714
731
  function isValidProvider(value) {
715
732
  return typeof value === "string" && VALID_PROVIDERS.includes(value);
716
733
  }
@@ -720,10 +737,14 @@ function isValidScope(value) {
720
737
  function isStringArray(value) {
721
738
  return Array.isArray(value) && value.every((item) => typeof item === "string");
722
739
  }
740
+ function isValidLogLevel(value) {
741
+ return typeof value === "string" && VALID_LOG_LEVELS.includes(value);
742
+ }
723
743
  function parseConfig(raw) {
724
744
  const input = raw && typeof raw === "object" ? raw : {};
725
745
  const defaultIndexing = getDefaultIndexingConfig();
726
746
  const defaultSearch = getDefaultSearchConfig();
747
+ const defaultDebug = getDefaultDebugConfig();
727
748
  const rawIndexing = input.indexing && typeof input.indexing === "object" ? input.indexing : {};
728
749
  const indexing = {
729
750
  autoIndex: typeof rawIndexing.autoIndex === "boolean" ? rawIndexing.autoIndex : defaultIndexing.autoIndex,
@@ -732,7 +753,10 @@ function parseConfig(raw) {
732
753
  maxChunksPerFile: typeof rawIndexing.maxChunksPerFile === "number" ? Math.max(1, rawIndexing.maxChunksPerFile) : defaultIndexing.maxChunksPerFile,
733
754
  semanticOnly: typeof rawIndexing.semanticOnly === "boolean" ? rawIndexing.semanticOnly : defaultIndexing.semanticOnly,
734
755
  retries: typeof rawIndexing.retries === "number" ? rawIndexing.retries : defaultIndexing.retries,
735
- retryDelayMs: typeof rawIndexing.retryDelayMs === "number" ? rawIndexing.retryDelayMs : defaultIndexing.retryDelayMs
756
+ retryDelayMs: typeof rawIndexing.retryDelayMs === "number" ? rawIndexing.retryDelayMs : defaultIndexing.retryDelayMs,
757
+ autoGc: typeof rawIndexing.autoGc === "boolean" ? rawIndexing.autoGc : defaultIndexing.autoGc,
758
+ gcIntervalDays: typeof rawIndexing.gcIntervalDays === "number" ? Math.max(1, rawIndexing.gcIntervalDays) : defaultIndexing.gcIntervalDays,
759
+ gcOrphanThreshold: typeof rawIndexing.gcOrphanThreshold === "number" ? Math.max(0, rawIndexing.gcOrphanThreshold) : defaultIndexing.gcOrphanThreshold
736
760
  };
737
761
  const rawSearch = input.search && typeof input.search === "object" ? input.search : {};
738
762
  const search = {
@@ -742,6 +766,17 @@ function parseConfig(raw) {
742
766
  hybridWeight: typeof rawSearch.hybridWeight === "number" ? Math.min(1, Math.max(0, rawSearch.hybridWeight)) : defaultSearch.hybridWeight,
743
767
  contextLines: typeof rawSearch.contextLines === "number" ? Math.min(50, Math.max(0, rawSearch.contextLines)) : defaultSearch.contextLines
744
768
  };
769
+ const rawDebug = input.debug && typeof input.debug === "object" ? input.debug : {};
770
+ const debug = {
771
+ enabled: typeof rawDebug.enabled === "boolean" ? rawDebug.enabled : defaultDebug.enabled,
772
+ logLevel: isValidLogLevel(rawDebug.logLevel) ? rawDebug.logLevel : defaultDebug.logLevel,
773
+ logSearch: typeof rawDebug.logSearch === "boolean" ? rawDebug.logSearch : defaultDebug.logSearch,
774
+ logEmbedding: typeof rawDebug.logEmbedding === "boolean" ? rawDebug.logEmbedding : defaultDebug.logEmbedding,
775
+ logCache: typeof rawDebug.logCache === "boolean" ? rawDebug.logCache : defaultDebug.logCache,
776
+ logGc: typeof rawDebug.logGc === "boolean" ? rawDebug.logGc : defaultDebug.logGc,
777
+ logBranch: typeof rawDebug.logBranch === "boolean" ? rawDebug.logBranch : defaultDebug.logBranch,
778
+ metrics: typeof rawDebug.metrics === "boolean" ? rawDebug.metrics : defaultDebug.metrics
779
+ };
745
780
  return {
746
781
  embeddingProvider: isValidProvider(input.embeddingProvider) ? input.embeddingProvider : "auto",
747
782
  embeddingModel: typeof input.embeddingModel === "string" ? input.embeddingModel : "auto",
@@ -749,7 +784,8 @@ function parseConfig(raw) {
749
784
  include: isStringArray(input.include) ? input.include : DEFAULT_INCLUDE,
750
785
  exclude: isStringArray(input.exclude) ? input.exclude : DEFAULT_EXCLUDE,
751
786
  indexing,
752
- search
787
+ search,
788
+ debug
753
789
  };
754
790
  }
755
791
  var EMBEDDING_MODELS = {
@@ -814,6 +850,7 @@ function getDefaultModelForProvider(provider) {
814
850
  // src/indexer/index.ts
815
851
  var import_fs4 = require("fs");
816
852
  var path5 = __toESM(require("path"), 1);
853
+ var import_perf_hooks = require("perf_hooks");
817
854
 
818
855
  // node_modules/eventemitter3/index.mjs
819
856
  var import_index = __toESM(require_eventemitter3(), 1);
@@ -2071,34 +2108,36 @@ var GoogleEmbeddingProvider = class {
2071
2108
  };
2072
2109
  }
2073
2110
  async embedBatch(texts) {
2074
- const embeddings = [];
2075
- let totalTokens = 0;
2076
- for (const text of texts) {
2077
- const response = await fetch(
2078
- `${this.credentials.baseUrl}/models/${this.modelInfo.model}:embedContent?key=${this.credentials.apiKey}`,
2079
- {
2080
- method: "POST",
2081
- headers: {
2082
- "Content-Type": "application/json"
2083
- },
2084
- body: JSON.stringify({
2085
- content: {
2086
- parts: [{ text }]
2087
- }
2088
- })
2111
+ const results = await Promise.all(
2112
+ texts.map(async (text) => {
2113
+ const response = await fetch(
2114
+ `${this.credentials.baseUrl}/models/${this.modelInfo.model}:embedContent?key=${this.credentials.apiKey}`,
2115
+ {
2116
+ method: "POST",
2117
+ headers: {
2118
+ "Content-Type": "application/json"
2119
+ },
2120
+ body: JSON.stringify({
2121
+ content: {
2122
+ parts: [{ text }]
2123
+ }
2124
+ })
2125
+ }
2126
+ );
2127
+ if (!response.ok) {
2128
+ const error = await response.text();
2129
+ throw new Error(`Google embedding API error: ${response.status} - ${error}`);
2089
2130
  }
2090
- );
2091
- if (!response.ok) {
2092
- const error = await response.text();
2093
- throw new Error(`Google embedding API error: ${response.status} - ${error}`);
2094
- }
2095
- const data = await response.json();
2096
- embeddings.push(data.embedding.values);
2097
- totalTokens += Math.ceil(text.length / 4);
2098
- }
2131
+ const data = await response.json();
2132
+ return {
2133
+ embedding: data.embedding.values,
2134
+ tokensUsed: Math.ceil(text.length / 4)
2135
+ };
2136
+ })
2137
+ );
2099
2138
  return {
2100
- embeddings,
2101
- totalTokensUsed: totalTokens
2139
+ embeddings: results.map((r) => r.embedding),
2140
+ totalTokensUsed: results.reduce((sum, r) => sum + r.tokensUsed, 0)
2102
2141
  };
2103
2142
  }
2104
2143
  getModelInfo() {
@@ -2132,16 +2171,10 @@ var OllamaEmbeddingProvider = class {
2132
2171
  };
2133
2172
  }
2134
2173
  async embedBatch(texts) {
2135
- const embeddings = [];
2136
- let totalTokens = 0;
2137
- for (const text of texts) {
2138
- const result = await this.embed(text);
2139
- embeddings.push(result.embedding);
2140
- totalTokens += result.tokensUsed;
2141
- }
2174
+ const results = await Promise.all(texts.map((text) => this.embed(text)));
2142
2175
  return {
2143
- embeddings,
2144
- totalTokensUsed: totalTokens
2176
+ embeddings: results.map((r) => r.embedding),
2177
+ totalTokensUsed: results.reduce((sum, r) => sum + r.tokensUsed, 0)
2145
2178
  };
2146
2179
  }
2147
2180
  getModelInfo() {
@@ -2327,6 +2360,298 @@ function padRight(str, length) {
2327
2360
  return str.padEnd(length);
2328
2361
  }
2329
2362
 
2363
+ // src/utils/logger.ts
2364
+ var LOG_LEVEL_PRIORITY = {
2365
+ error: 0,
2366
+ warn: 1,
2367
+ info: 2,
2368
+ debug: 3
2369
+ };
2370
+ function createEmptyMetrics() {
2371
+ return {
2372
+ filesScanned: 0,
2373
+ filesParsed: 0,
2374
+ parseMs: 0,
2375
+ chunksProcessed: 0,
2376
+ chunksEmbedded: 0,
2377
+ chunksFromCache: 0,
2378
+ chunksRemoved: 0,
2379
+ embeddingApiCalls: 0,
2380
+ embeddingTokensUsed: 0,
2381
+ embeddingErrors: 0,
2382
+ searchCount: 0,
2383
+ searchTotalMs: 0,
2384
+ searchAvgMs: 0,
2385
+ searchLastMs: 0,
2386
+ embeddingCallMs: 0,
2387
+ vectorSearchMs: 0,
2388
+ keywordSearchMs: 0,
2389
+ fusionMs: 0,
2390
+ cacheHits: 0,
2391
+ cacheMisses: 0,
2392
+ queryCacheHits: 0,
2393
+ queryCacheSimilarHits: 0,
2394
+ queryCacheMisses: 0,
2395
+ gcRuns: 0,
2396
+ gcOrphansRemoved: 0,
2397
+ gcChunksRemoved: 0,
2398
+ gcEmbeddingsRemoved: 0
2399
+ };
2400
+ }
2401
+ var Logger = class {
2402
+ config;
2403
+ metrics;
2404
+ logs = [];
2405
+ maxLogs = 1e3;
2406
+ constructor(config) {
2407
+ this.config = config;
2408
+ this.metrics = createEmptyMetrics();
2409
+ }
2410
+ shouldLog(level) {
2411
+ if (!this.config.enabled) return false;
2412
+ return LOG_LEVEL_PRIORITY[level] <= LOG_LEVEL_PRIORITY[this.config.logLevel];
2413
+ }
2414
+ log(level, category, message, data) {
2415
+ if (!this.shouldLog(level)) return;
2416
+ const entry = {
2417
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2418
+ level,
2419
+ category,
2420
+ message,
2421
+ data
2422
+ };
2423
+ this.logs.push(entry);
2424
+ if (this.logs.length > this.maxLogs) {
2425
+ this.logs.shift();
2426
+ }
2427
+ }
2428
+ search(level, message, data) {
2429
+ if (this.config.logSearch) {
2430
+ this.log(level, "search", message, data);
2431
+ }
2432
+ }
2433
+ embedding(level, message, data) {
2434
+ if (this.config.logEmbedding) {
2435
+ this.log(level, "embedding", message, data);
2436
+ }
2437
+ }
2438
+ cache(level, message, data) {
2439
+ if (this.config.logCache) {
2440
+ this.log(level, "cache", message, data);
2441
+ }
2442
+ }
2443
+ gc(level, message, data) {
2444
+ if (this.config.logGc) {
2445
+ this.log(level, "gc", message, data);
2446
+ }
2447
+ }
2448
+ branch(level, message, data) {
2449
+ if (this.config.logBranch) {
2450
+ this.log(level, "branch", message, data);
2451
+ }
2452
+ }
2453
+ info(message, data) {
2454
+ this.log("info", "general", message, data);
2455
+ }
2456
+ warn(message, data) {
2457
+ this.log("warn", "general", message, data);
2458
+ }
2459
+ error(message, data) {
2460
+ this.log("error", "general", message, data);
2461
+ }
2462
+ debug(message, data) {
2463
+ this.log("debug", "general", message, data);
2464
+ }
2465
+ recordIndexingStart() {
2466
+ if (!this.config.metrics) return;
2467
+ this.metrics.indexingStartTime = Date.now();
2468
+ }
2469
+ recordIndexingEnd() {
2470
+ if (!this.config.metrics) return;
2471
+ this.metrics.indexingEndTime = Date.now();
2472
+ }
2473
+ recordFilesScanned(count) {
2474
+ if (!this.config.metrics) return;
2475
+ this.metrics.filesScanned = count;
2476
+ }
2477
+ recordFilesParsed(count) {
2478
+ if (!this.config.metrics) return;
2479
+ this.metrics.filesParsed = count;
2480
+ }
2481
+ recordParseDuration(durationMs) {
2482
+ if (!this.config.metrics) return;
2483
+ this.metrics.parseMs = durationMs;
2484
+ }
2485
+ recordChunksProcessed(count) {
2486
+ if (!this.config.metrics) return;
2487
+ this.metrics.chunksProcessed += count;
2488
+ }
2489
+ recordChunksEmbedded(count) {
2490
+ if (!this.config.metrics) return;
2491
+ this.metrics.chunksEmbedded += count;
2492
+ }
2493
+ recordChunksFromCache(count) {
2494
+ if (!this.config.metrics) return;
2495
+ this.metrics.chunksFromCache += count;
2496
+ }
2497
+ recordChunksRemoved(count) {
2498
+ if (!this.config.metrics) return;
2499
+ this.metrics.chunksRemoved += count;
2500
+ }
2501
+ recordEmbeddingApiCall(tokens) {
2502
+ if (!this.config.metrics) return;
2503
+ this.metrics.embeddingApiCalls++;
2504
+ this.metrics.embeddingTokensUsed += tokens;
2505
+ }
2506
+ recordEmbeddingError() {
2507
+ if (!this.config.metrics) return;
2508
+ this.metrics.embeddingErrors++;
2509
+ }
2510
+ recordSearch(durationMs, breakdown) {
2511
+ if (!this.config.metrics) return;
2512
+ this.metrics.searchCount++;
2513
+ this.metrics.searchTotalMs += durationMs;
2514
+ this.metrics.searchLastMs = durationMs;
2515
+ this.metrics.searchAvgMs = this.metrics.searchTotalMs / this.metrics.searchCount;
2516
+ if (breakdown) {
2517
+ this.metrics.embeddingCallMs = breakdown.embeddingMs;
2518
+ this.metrics.vectorSearchMs = breakdown.vectorMs;
2519
+ this.metrics.keywordSearchMs = breakdown.keywordMs;
2520
+ this.metrics.fusionMs = breakdown.fusionMs;
2521
+ }
2522
+ }
2523
+ recordCacheHit() {
2524
+ if (!this.config.metrics) return;
2525
+ this.metrics.cacheHits++;
2526
+ }
2527
+ recordCacheMiss() {
2528
+ if (!this.config.metrics) return;
2529
+ this.metrics.cacheMisses++;
2530
+ }
2531
+ recordQueryCacheHit() {
2532
+ if (!this.config.metrics) return;
2533
+ this.metrics.queryCacheHits++;
2534
+ }
2535
+ recordQueryCacheSimilarHit() {
2536
+ if (!this.config.metrics) return;
2537
+ this.metrics.queryCacheSimilarHits++;
2538
+ }
2539
+ recordQueryCacheMiss() {
2540
+ if (!this.config.metrics) return;
2541
+ this.metrics.queryCacheMisses++;
2542
+ }
2543
+ recordGc(orphans, chunks, embeddings) {
2544
+ if (!this.config.metrics) return;
2545
+ this.metrics.gcRuns++;
2546
+ this.metrics.gcOrphansRemoved += orphans;
2547
+ this.metrics.gcChunksRemoved += chunks;
2548
+ this.metrics.gcEmbeddingsRemoved += embeddings;
2549
+ }
2550
+ getMetrics() {
2551
+ return { ...this.metrics };
2552
+ }
2553
+ getLogs(limit) {
2554
+ const logs = [...this.logs];
2555
+ if (limit) {
2556
+ return logs.slice(-limit);
2557
+ }
2558
+ return logs;
2559
+ }
2560
+ getLogsByCategory(category, limit) {
2561
+ const filtered = this.logs.filter((l) => l.category === category);
2562
+ if (limit) {
2563
+ return filtered.slice(-limit);
2564
+ }
2565
+ return filtered;
2566
+ }
2567
+ getLogsByLevel(level, limit) {
2568
+ const filtered = this.logs.filter((l) => l.level === level);
2569
+ if (limit) {
2570
+ return filtered.slice(-limit);
2571
+ }
2572
+ return filtered;
2573
+ }
2574
+ resetMetrics() {
2575
+ this.metrics = createEmptyMetrics();
2576
+ }
2577
+ clearLogs() {
2578
+ this.logs = [];
2579
+ }
2580
+ formatMetrics() {
2581
+ const m = this.metrics;
2582
+ const lines = [];
2583
+ lines.push("=== Metrics ===");
2584
+ if (m.indexingStartTime && m.indexingEndTime) {
2585
+ const duration = m.indexingEndTime - m.indexingStartTime;
2586
+ lines.push(`Indexing duration: ${(duration / 1e3).toFixed(2)}s`);
2587
+ }
2588
+ lines.push("");
2589
+ lines.push("Indexing:");
2590
+ lines.push(` Files scanned: ${m.filesScanned}`);
2591
+ lines.push(` Files parsed: ${m.filesParsed}`);
2592
+ lines.push(` Chunks processed: ${m.chunksProcessed}`);
2593
+ lines.push(` Chunks embedded: ${m.chunksEmbedded}`);
2594
+ lines.push(` Chunks from cache: ${m.chunksFromCache}`);
2595
+ lines.push(` Chunks removed: ${m.chunksRemoved}`);
2596
+ lines.push("");
2597
+ lines.push("Embedding API:");
2598
+ lines.push(` API calls: ${m.embeddingApiCalls}`);
2599
+ lines.push(` Tokens used: ${m.embeddingTokensUsed.toLocaleString()}`);
2600
+ lines.push(` Errors: ${m.embeddingErrors}`);
2601
+ if (m.searchCount > 0) {
2602
+ lines.push("");
2603
+ lines.push("Search:");
2604
+ lines.push(` Total searches: ${m.searchCount}`);
2605
+ lines.push(` Average time: ${m.searchAvgMs.toFixed(2)}ms`);
2606
+ lines.push(` Last search: ${m.searchLastMs.toFixed(2)}ms`);
2607
+ if (m.embeddingCallMs > 0) {
2608
+ lines.push(` - Embedding: ${m.embeddingCallMs.toFixed(2)}ms`);
2609
+ lines.push(` - Vector search: ${m.vectorSearchMs.toFixed(2)}ms`);
2610
+ lines.push(` - Keyword search: ${m.keywordSearchMs.toFixed(2)}ms`);
2611
+ lines.push(` - Fusion: ${m.fusionMs.toFixed(2)}ms`);
2612
+ }
2613
+ }
2614
+ const totalCacheOps = m.cacheHits + m.cacheMisses;
2615
+ if (totalCacheOps > 0) {
2616
+ lines.push("");
2617
+ lines.push("Cache:");
2618
+ lines.push(` Hits: ${m.cacheHits}`);
2619
+ lines.push(` Misses: ${m.cacheMisses}`);
2620
+ lines.push(` Hit rate: ${(m.cacheHits / totalCacheOps * 100).toFixed(1)}%`);
2621
+ }
2622
+ if (m.gcRuns > 0) {
2623
+ lines.push("");
2624
+ lines.push("Garbage Collection:");
2625
+ lines.push(` GC runs: ${m.gcRuns}`);
2626
+ lines.push(` Orphans removed: ${m.gcOrphansRemoved}`);
2627
+ lines.push(` Chunks removed: ${m.gcChunksRemoved}`);
2628
+ lines.push(` Embeddings removed: ${m.gcEmbeddingsRemoved}`);
2629
+ }
2630
+ return lines.join("\n");
2631
+ }
2632
+ formatRecentLogs(limit = 20) {
2633
+ const logs = this.getLogs(limit);
2634
+ if (logs.length === 0) {
2635
+ return "No logs recorded.";
2636
+ }
2637
+ return logs.map((l) => {
2638
+ const dataStr = l.data ? ` ${JSON.stringify(l.data)}` : "";
2639
+ return `[${l.timestamp}] [${l.level.toUpperCase()}] [${l.category}] ${l.message}${dataStr}`;
2640
+ }).join("\n");
2641
+ }
2642
+ isEnabled() {
2643
+ return this.config.enabled;
2644
+ }
2645
+ isMetricsEnabled() {
2646
+ return this.config.enabled && this.config.metrics;
2647
+ }
2648
+ };
2649
+ var globalLogger = null;
2650
+ function initializeLogger(config) {
2651
+ globalLogger = new Logger(config);
2652
+ return globalLogger;
2653
+ }
2654
+
2330
2655
  // src/native/index.ts
2331
2656
  var path3 = __toESM(require("path"), 1);
2332
2657
  var os2 = __toESM(require("os"), 1);
@@ -2456,6 +2781,21 @@ var VectorStore = class {
2456
2781
  metadata: JSON.parse(r.metadata)
2457
2782
  }));
2458
2783
  }
2784
+ getMetadata(id) {
2785
+ const result = this.inner.getMetadata(id);
2786
+ if (result === null || result === void 0) {
2787
+ return void 0;
2788
+ }
2789
+ return JSON.parse(result);
2790
+ }
2791
+ getMetadataBatch(ids) {
2792
+ const results = this.inner.getMetadataBatch(ids);
2793
+ const map = /* @__PURE__ */ new Map();
2794
+ for (const { key, metadata } of results) {
2795
+ map.set(key, JSON.parse(metadata));
2796
+ }
2797
+ return map;
2798
+ }
2459
2799
  };
2460
2800
  var CHARS_PER_TOKEN = 4;
2461
2801
  var MAX_BATCH_TOKENS = 7500;
@@ -2683,12 +3023,20 @@ var Database = class {
2683
3023
  upsertEmbedding(contentHash, embedding, chunkText, model) {
2684
3024
  this.inner.upsertEmbedding(contentHash, embedding, chunkText, model);
2685
3025
  }
3026
+ upsertEmbeddingsBatch(items) {
3027
+ if (items.length === 0) return;
3028
+ this.inner.upsertEmbeddingsBatch(items);
3029
+ }
2686
3030
  getMissingEmbeddings(contentHashes) {
2687
3031
  return this.inner.getMissingEmbeddings(contentHashes);
2688
3032
  }
2689
3033
  upsertChunk(chunk) {
2690
3034
  this.inner.upsertChunk(chunk);
2691
3035
  }
3036
+ upsertChunksBatch(chunks) {
3037
+ if (chunks.length === 0) return;
3038
+ this.inner.upsertChunksBatch(chunks);
3039
+ }
2692
3040
  getChunk(chunkId) {
2693
3041
  return this.inner.getChunk(chunkId) ?? null;
2694
3042
  }
@@ -2701,6 +3049,10 @@ var Database = class {
2701
3049
  addChunksToBranch(branch, chunkIds) {
2702
3050
  this.inner.addChunksToBranch(branch, chunkIds);
2703
3051
  }
3052
+ addChunksToBranchBatch(branch, chunkIds) {
3053
+ if (chunkIds.length === 0) return;
3054
+ this.inner.addChunksToBranchBatch(branch, chunkIds);
3055
+ }
2704
3056
  clearBranch(branch) {
2705
3057
  return this.inner.clearBranch(branch);
2706
3058
  }
@@ -2842,12 +3194,18 @@ var Indexer = class {
2842
3194
  failedBatchesPath = "";
2843
3195
  currentBranch = "default";
2844
3196
  baseBranch = "main";
3197
+ logger;
3198
+ queryEmbeddingCache = /* @__PURE__ */ new Map();
3199
+ maxQueryCacheSize = 100;
3200
+ queryCacheTtlMs = 5 * 60 * 1e3;
3201
+ querySimilarityThreshold = 0.85;
2845
3202
  constructor(projectRoot, config) {
2846
3203
  this.projectRoot = projectRoot;
2847
3204
  this.config = config;
2848
3205
  this.indexPath = this.getIndexPath();
2849
3206
  this.fileHashCachePath = path5.join(this.indexPath, "file-hashes.json");
2850
3207
  this.failedBatchesPath = path5.join(this.indexPath, "failed-batches.json");
3208
+ this.logger = initializeLogger(config.debug);
2851
3209
  }
2852
3210
  getIndexPath() {
2853
3211
  if (this.config.scope === "global") {
@@ -2926,6 +3284,11 @@ var Indexer = class {
2926
3284
  "No embedding provider available. Configure GitHub, OpenAI, Google, or Ollama."
2927
3285
  );
2928
3286
  }
3287
+ this.logger.info("Initializing indexer", {
3288
+ provider: this.detectedProvider.provider,
3289
+ model: this.detectedProvider.modelInfo.model,
3290
+ scope: this.config.scope
3291
+ });
2929
3292
  this.provider = createEmbeddingProvider(
2930
3293
  this.detectedProvider.credentials,
2931
3294
  this.detectedProvider.modelInfo
@@ -2957,15 +3320,54 @@ var Indexer = class {
2957
3320
  if (isGitRepo(this.projectRoot)) {
2958
3321
  this.currentBranch = getBranchOrDefault(this.projectRoot);
2959
3322
  this.baseBranch = getBaseBranch(this.projectRoot);
3323
+ this.logger.branch("info", "Detected git repository", {
3324
+ currentBranch: this.currentBranch,
3325
+ baseBranch: this.baseBranch
3326
+ });
2960
3327
  } else {
2961
3328
  this.currentBranch = "default";
2962
3329
  this.baseBranch = "default";
3330
+ this.logger.branch("debug", "Not a git repository, using default branch");
3331
+ }
3332
+ if (this.config.indexing.autoGc) {
3333
+ await this.maybeRunAutoGc();
3334
+ }
3335
+ }
3336
+ async maybeRunAutoGc() {
3337
+ if (!this.database) return;
3338
+ const lastGcTimestamp = this.database.getMetadata("lastGcTimestamp");
3339
+ const now = Date.now();
3340
+ const intervalMs = this.config.indexing.gcIntervalDays * 24 * 60 * 60 * 1e3;
3341
+ let shouldRunGc = false;
3342
+ if (!lastGcTimestamp) {
3343
+ shouldRunGc = true;
3344
+ } else {
3345
+ const lastGcTime = parseInt(lastGcTimestamp, 10);
3346
+ if (!isNaN(lastGcTime) && now - lastGcTime > intervalMs) {
3347
+ shouldRunGc = true;
3348
+ }
3349
+ }
3350
+ if (shouldRunGc) {
3351
+ await this.healthCheck();
3352
+ this.database.setMetadata("lastGcTimestamp", now.toString());
3353
+ }
3354
+ }
3355
+ async maybeRunOrphanGc() {
3356
+ if (!this.database) return;
3357
+ const stats = this.database.getStats();
3358
+ if (!stats) return;
3359
+ const orphanCount = stats.embeddingCount - stats.chunkCount;
3360
+ if (orphanCount > this.config.indexing.gcOrphanThreshold) {
3361
+ this.database.gcOrphanEmbeddings();
3362
+ this.database.gcOrphanChunks();
3363
+ this.database.setMetadata("lastGcTimestamp", Date.now().toString());
2963
3364
  }
2964
3365
  }
2965
3366
  migrateFromLegacyIndex() {
2966
3367
  if (!this.store || !this.database) return;
2967
3368
  const allMetadata = this.store.getAllMetadata();
2968
3369
  const chunkIds = [];
3370
+ const chunkDataBatch = [];
2969
3371
  for (const { key, metadata } of allMetadata) {
2970
3372
  const chunkData = {
2971
3373
  chunkId: key,
@@ -2977,10 +3379,13 @@ var Indexer = class {
2977
3379
  name: metadata.name,
2978
3380
  language: metadata.language
2979
3381
  };
2980
- this.database.upsertChunk(chunkData);
3382
+ chunkDataBatch.push(chunkData);
2981
3383
  chunkIds.push(key);
2982
3384
  }
2983
- this.database.addChunksToBranch(this.currentBranch || "default", chunkIds);
3385
+ if (chunkDataBatch.length > 0) {
3386
+ this.database.upsertChunksBatch(chunkDataBatch);
3387
+ }
3388
+ this.database.addChunksToBranchBatch(this.currentBranch || "default", chunkIds);
2984
3389
  }
2985
3390
  async ensureInitialized() {
2986
3391
  if (!this.store || !this.provider || !this.invertedIndex || !this.detectedProvider || !this.database) {
@@ -3006,6 +3411,8 @@ var Indexer = class {
3006
3411
  }
3007
3412
  async index(onProgress) {
3008
3413
  const { store, provider, invertedIndex, database, detectedProvider } = await this.ensureInitialized();
3414
+ this.logger.recordIndexingStart();
3415
+ this.logger.info("Starting indexing", { projectRoot: this.projectRoot });
3009
3416
  const startTime = Date.now();
3010
3417
  const stats = {
3011
3418
  totalFiles: 0,
@@ -3035,6 +3442,11 @@ var Indexer = class {
3035
3442
  );
3036
3443
  stats.totalFiles = files.length;
3037
3444
  stats.skippedFiles = skipped;
3445
+ this.logger.recordFilesScanned(files.length);
3446
+ this.logger.cache("debug", "Scanning files for changes", {
3447
+ totalFiles: files.length,
3448
+ skippedFiles: skipped.length
3449
+ });
3038
3450
  const changedFiles = [];
3039
3451
  const unchangedFilePaths = /* @__PURE__ */ new Set();
3040
3452
  const currentFileHashes = /* @__PURE__ */ new Map();
@@ -3043,11 +3455,17 @@ var Indexer = class {
3043
3455
  currentFileHashes.set(f.path, currentHash);
3044
3456
  if (this.fileHashCache.get(f.path) === currentHash) {
3045
3457
  unchangedFilePaths.add(f.path);
3458
+ this.logger.recordCacheHit();
3046
3459
  } else {
3047
3460
  const content = await import_fs4.promises.readFile(f.path, "utf-8");
3048
3461
  changedFiles.push({ path: f.path, content, hash: currentHash });
3462
+ this.logger.recordCacheMiss();
3049
3463
  }
3050
3464
  }
3465
+ this.logger.cache("info", "File hash cache results", {
3466
+ unchanged: unchangedFilePaths.size,
3467
+ changed: changedFiles.length
3468
+ });
3051
3469
  onProgress?.({
3052
3470
  phase: "parsing",
3053
3471
  filesProcessed: 0,
@@ -3055,7 +3473,12 @@ var Indexer = class {
3055
3473
  chunksProcessed: 0,
3056
3474
  totalChunks: 0
3057
3475
  });
3476
+ const parseStartTime = import_perf_hooks.performance.now();
3058
3477
  const parsedFiles = parseFiles(changedFiles);
3478
+ const parseMs = import_perf_hooks.performance.now() - parseStartTime;
3479
+ this.logger.recordFilesParsed(parsedFiles.length);
3480
+ this.logger.recordParseDuration(parseMs);
3481
+ this.logger.debug("Parsed changed files", { parsedCount: parsedFiles.length, parseMs: parseMs.toFixed(2) });
3059
3482
  const existingChunks = /* @__PURE__ */ new Map();
3060
3483
  const existingChunksByFile = /* @__PURE__ */ new Map();
3061
3484
  for (const { key, metadata } of store.getAllMetadata()) {
@@ -3076,6 +3499,7 @@ var Indexer = class {
3076
3499
  }
3077
3500
  }
3078
3501
  }
3502
+ const chunkDataBatch = [];
3079
3503
  for (const parsed of parsedFiles) {
3080
3504
  currentFilePaths.add(parsed.path);
3081
3505
  if (parsed.chunks.length === 0) {
@@ -3103,7 +3527,7 @@ var Indexer = class {
3103
3527
  name: chunk.name,
3104
3528
  language: chunk.language
3105
3529
  };
3106
- database.upsertChunk(chunkData);
3530
+ chunkDataBatch.push(chunkData);
3107
3531
  if (existingChunks.get(id) === contentHash) {
3108
3532
  fileChunkCount++;
3109
3533
  continue;
@@ -3122,6 +3546,9 @@ var Indexer = class {
3122
3546
  fileChunkCount++;
3123
3547
  }
3124
3548
  }
3549
+ if (chunkDataBatch.length > 0) {
3550
+ database.upsertChunksBatch(chunkDataBatch);
3551
+ }
3125
3552
  let removedCount = 0;
3126
3553
  for (const [chunkId] of existingChunks) {
3127
3554
  if (!currentChunkIds.has(chunkId)) {
@@ -3133,9 +3560,16 @@ var Indexer = class {
3133
3560
  stats.totalChunks = pendingChunks.length;
3134
3561
  stats.existingChunks = currentChunkIds.size - pendingChunks.length;
3135
3562
  stats.removedChunks = removedCount;
3563
+ this.logger.recordChunksProcessed(currentChunkIds.size);
3564
+ this.logger.recordChunksRemoved(removedCount);
3565
+ this.logger.info("Chunk analysis complete", {
3566
+ pending: pendingChunks.length,
3567
+ existing: stats.existingChunks,
3568
+ removed: removedCount
3569
+ });
3136
3570
  if (pendingChunks.length === 0 && removedCount === 0) {
3137
3571
  database.clearBranch(this.currentBranch);
3138
- database.addChunksToBranch(this.currentBranch, Array.from(currentChunkIds));
3572
+ database.addChunksToBranchBatch(this.currentBranch, Array.from(currentChunkIds));
3139
3573
  this.fileHashCache = currentFileHashes;
3140
3574
  this.saveFileHashCache();
3141
3575
  stats.durationMs = Date.now() - startTime;
@@ -3150,7 +3584,7 @@ var Indexer = class {
3150
3584
  }
3151
3585
  if (pendingChunks.length === 0) {
3152
3586
  database.clearBranch(this.currentBranch);
3153
- database.addChunksToBranch(this.currentBranch, Array.from(currentChunkIds));
3587
+ database.addChunksToBranchBatch(this.currentBranch, Array.from(currentChunkIds));
3154
3588
  store.save();
3155
3589
  invertedIndex.save();
3156
3590
  this.fileHashCache = currentFileHashes;
@@ -3176,6 +3610,11 @@ var Indexer = class {
3176
3610
  const missingHashes = new Set(database.getMissingEmbeddings(allContentHashes));
3177
3611
  const chunksNeedingEmbedding = pendingChunks.filter((c) => missingHashes.has(c.contentHash));
3178
3612
  const chunksWithExistingEmbedding = pendingChunks.filter((c) => !missingHashes.has(c.contentHash));
3613
+ this.logger.cache("info", "Embedding cache lookup", {
3614
+ needsEmbedding: chunksNeedingEmbedding.length,
3615
+ fromCache: chunksWithExistingEmbedding.length
3616
+ });
3617
+ this.logger.recordChunksFromCache(chunksWithExistingEmbedding.length);
3179
3618
  for (const chunk of chunksWithExistingEmbedding) {
3180
3619
  const embeddingBuffer = database.getEmbedding(chunk.contentHash);
3181
3620
  if (embeddingBuffer) {
@@ -3214,13 +3653,16 @@ var Indexer = class {
3214
3653
  const message = getErrorMessage(error);
3215
3654
  if (isRateLimitError(error)) {
3216
3655
  rateLimitBackoffMs = Math.min(providerRateLimits.maxRetryMs, (rateLimitBackoffMs || providerRateLimits.minRetryMs) * 2);
3217
- console.error(
3218
- `Rate limited (attempt ${error.attemptNumber}/${error.retriesLeft + error.attemptNumber}): waiting ${rateLimitBackoffMs / 1e3}s before retry...`
3219
- );
3656
+ this.logger.embedding("warn", `Rate limited, backing off`, {
3657
+ attempt: error.attemptNumber,
3658
+ retriesLeft: error.retriesLeft,
3659
+ backoffMs: rateLimitBackoffMs
3660
+ });
3220
3661
  } else {
3221
- console.error(
3222
- `Embedding batch failed (attempt ${error.attemptNumber}): ${message}`
3223
- );
3662
+ this.logger.embedding("error", `Embedding batch failed`, {
3663
+ attempt: error.attemptNumber,
3664
+ error: message
3665
+ });
3224
3666
  }
3225
3667
  }
3226
3668
  }
@@ -3234,20 +3676,25 @@ var Indexer = class {
3234
3676
  metadata: chunk.metadata
3235
3677
  }));
3236
3678
  store.addBatch(items);
3237
- for (let i = 0; i < batch.length; i++) {
3238
- const chunk = batch[i];
3239
- const embedding = result.embeddings[i];
3240
- database.upsertEmbedding(
3241
- chunk.contentHash,
3242
- float32ArrayToBuffer(embedding),
3243
- chunk.text,
3244
- detectedProvider.modelInfo.model
3245
- );
3679
+ const embeddingBatchItems = batch.map((chunk, i) => ({
3680
+ contentHash: chunk.contentHash,
3681
+ embedding: float32ArrayToBuffer(result.embeddings[i]),
3682
+ chunkText: chunk.text,
3683
+ model: detectedProvider.modelInfo.model
3684
+ }));
3685
+ database.upsertEmbeddingsBatch(embeddingBatchItems);
3686
+ for (const chunk of batch) {
3246
3687
  invertedIndex.removeChunk(chunk.id);
3247
3688
  invertedIndex.addChunk(chunk.id, chunk.content);
3248
3689
  }
3249
3690
  stats.indexedChunks += batch.length;
3250
3691
  stats.tokensUsed += result.totalTokensUsed;
3692
+ this.logger.recordChunksEmbedded(batch.length);
3693
+ this.logger.recordEmbeddingApiCall(result.totalTokensUsed);
3694
+ this.logger.embedding("debug", `Embedded batch`, {
3695
+ batchSize: batch.length,
3696
+ tokens: result.totalTokensUsed
3697
+ });
3251
3698
  onProgress?.({
3252
3699
  phase: "embedding",
3253
3700
  filesProcessed: files.length,
@@ -3258,7 +3705,11 @@ var Indexer = class {
3258
3705
  } catch (error) {
3259
3706
  stats.failedChunks += batch.length;
3260
3707
  this.addFailedBatch(batch, getErrorMessage(error));
3261
- console.error(`Failed to embed batch after retries: ${getErrorMessage(error)}`);
3708
+ this.logger.recordEmbeddingError();
3709
+ this.logger.embedding("error", `Failed to embed batch after retries`, {
3710
+ batchSize: batch.length,
3711
+ error: getErrorMessage(error)
3712
+ });
3262
3713
  }
3263
3714
  });
3264
3715
  }
@@ -3271,12 +3722,25 @@ var Indexer = class {
3271
3722
  totalChunks: pendingChunks.length
3272
3723
  });
3273
3724
  database.clearBranch(this.currentBranch);
3274
- database.addChunksToBranch(this.currentBranch, Array.from(currentChunkIds));
3725
+ database.addChunksToBranchBatch(this.currentBranch, Array.from(currentChunkIds));
3275
3726
  store.save();
3276
3727
  invertedIndex.save();
3277
3728
  this.fileHashCache = currentFileHashes;
3278
3729
  this.saveFileHashCache();
3730
+ if (this.config.indexing.autoGc && stats.removedChunks > 0) {
3731
+ await this.maybeRunOrphanGc();
3732
+ }
3279
3733
  stats.durationMs = Date.now() - startTime;
3734
+ this.logger.recordIndexingEnd();
3735
+ this.logger.info("Indexing complete", {
3736
+ files: stats.totalFiles,
3737
+ indexed: stats.indexedChunks,
3738
+ existing: stats.existingChunks,
3739
+ removed: stats.removedChunks,
3740
+ failed: stats.failedChunks,
3741
+ tokens: stats.tokensUsed,
3742
+ durationMs: stats.durationMs
3743
+ });
3280
3744
  if (stats.failedChunks > 0) {
3281
3745
  stats.failedBatchesPath = this.failedBatchesPath;
3282
3746
  }
@@ -3289,18 +3753,96 @@ var Indexer = class {
3289
3753
  });
3290
3754
  return stats;
3291
3755
  }
3756
+ async getQueryEmbedding(query, provider) {
3757
+ const now = Date.now();
3758
+ const cached = this.queryEmbeddingCache.get(query);
3759
+ if (cached && now - cached.timestamp < this.queryCacheTtlMs) {
3760
+ this.logger.cache("debug", "Query embedding cache hit (exact)", { query: query.slice(0, 50) });
3761
+ this.logger.recordQueryCacheHit();
3762
+ return cached.embedding;
3763
+ }
3764
+ const similarMatch = this.findSimilarCachedQuery(query, now);
3765
+ if (similarMatch) {
3766
+ this.logger.cache("debug", "Query embedding cache hit (similar)", {
3767
+ query: query.slice(0, 50),
3768
+ similarTo: similarMatch.key.slice(0, 50),
3769
+ similarity: similarMatch.similarity.toFixed(3)
3770
+ });
3771
+ this.logger.recordQueryCacheSimilarHit();
3772
+ return similarMatch.embedding;
3773
+ }
3774
+ this.logger.cache("debug", "Query embedding cache miss", { query: query.slice(0, 50) });
3775
+ this.logger.recordQueryCacheMiss();
3776
+ const { embedding, tokensUsed } = await provider.embed(query);
3777
+ this.logger.recordEmbeddingApiCall(tokensUsed);
3778
+ if (this.queryEmbeddingCache.size >= this.maxQueryCacheSize) {
3779
+ const oldestKey = this.queryEmbeddingCache.keys().next().value;
3780
+ if (oldestKey) {
3781
+ this.queryEmbeddingCache.delete(oldestKey);
3782
+ }
3783
+ }
3784
+ this.queryEmbeddingCache.set(query, { embedding, timestamp: now });
3785
+ return embedding;
3786
+ }
3787
+ findSimilarCachedQuery(query, now) {
3788
+ const queryTokens = this.tokenize(query);
3789
+ if (queryTokens.size === 0) return null;
3790
+ let bestMatch = null;
3791
+ for (const [cachedQuery, { embedding, timestamp }] of this.queryEmbeddingCache) {
3792
+ if (now - timestamp >= this.queryCacheTtlMs) continue;
3793
+ const cachedTokens = this.tokenize(cachedQuery);
3794
+ const similarity = this.jaccardSimilarity(queryTokens, cachedTokens);
3795
+ if (similarity >= this.querySimilarityThreshold) {
3796
+ if (!bestMatch || similarity > bestMatch.similarity) {
3797
+ bestMatch = { key: cachedQuery, embedding, similarity };
3798
+ }
3799
+ }
3800
+ }
3801
+ return bestMatch;
3802
+ }
3803
+ tokenize(text) {
3804
+ return new Set(
3805
+ text.toLowerCase().replace(/[^\w\s]/g, " ").split(/\s+/).filter((t) => t.length > 1)
3806
+ );
3807
+ }
3808
+ jaccardSimilarity(a, b) {
3809
+ if (a.size === 0 && b.size === 0) return 1;
3810
+ if (a.size === 0 || b.size === 0) return 0;
3811
+ let intersection = 0;
3812
+ for (const token of a) {
3813
+ if (b.has(token)) intersection++;
3814
+ }
3815
+ const union = a.size + b.size - intersection;
3816
+ return intersection / union;
3817
+ }
3292
3818
  async search(query, limit, options) {
3819
+ const searchStartTime = import_perf_hooks.performance.now();
3293
3820
  const { store, provider, database } = await this.ensureInitialized();
3294
3821
  if (store.count() === 0) {
3822
+ this.logger.search("debug", "Search on empty index", { query });
3295
3823
  return [];
3296
3824
  }
3297
3825
  const maxResults = limit ?? this.config.search.maxResults;
3298
3826
  const hybridWeight = options?.hybridWeight ?? this.config.search.hybridWeight;
3299
3827
  const filterByBranch = options?.filterByBranch ?? true;
3300
- const { embedding } = await provider.embed(query);
3828
+ this.logger.search("debug", "Starting search", {
3829
+ query,
3830
+ maxResults,
3831
+ hybridWeight,
3832
+ filterByBranch
3833
+ });
3834
+ const embeddingStartTime = import_perf_hooks.performance.now();
3835
+ const embedding = await this.getQueryEmbedding(query, provider);
3836
+ const embeddingMs = import_perf_hooks.performance.now() - embeddingStartTime;
3837
+ const vectorStartTime = import_perf_hooks.performance.now();
3301
3838
  const semanticResults = store.search(embedding, maxResults * 4);
3839
+ const vectorMs = import_perf_hooks.performance.now() - vectorStartTime;
3840
+ const keywordStartTime = import_perf_hooks.performance.now();
3302
3841
  const keywordResults = await this.keywordSearch(query, maxResults * 4);
3842
+ const keywordMs = import_perf_hooks.performance.now() - keywordStartTime;
3843
+ const fusionStartTime = import_perf_hooks.performance.now();
3303
3844
  const combined = this.fuseResults(semanticResults, keywordResults, hybridWeight, maxResults * 4);
3845
+ const fusionMs = import_perf_hooks.performance.now() - fusionStartTime;
3304
3846
  let branchChunkIds = null;
3305
3847
  if (filterByBranch && this.currentBranch !== "default") {
3306
3848
  branchChunkIds = new Set(database.getBranchChunkIds(this.currentBranch));
@@ -3321,6 +3863,22 @@ var Indexer = class {
3321
3863
  }
3322
3864
  return true;
3323
3865
  }).slice(0, maxResults);
3866
+ const totalSearchMs = import_perf_hooks.performance.now() - searchStartTime;
3867
+ this.logger.recordSearch(totalSearchMs, {
3868
+ embeddingMs,
3869
+ vectorMs,
3870
+ keywordMs,
3871
+ fusionMs
3872
+ });
3873
+ this.logger.search("info", "Search complete", {
3874
+ query,
3875
+ results: filtered.length,
3876
+ totalMs: Math.round(totalSearchMs * 100) / 100,
3877
+ embeddingMs: Math.round(embeddingMs * 100) / 100,
3878
+ vectorMs: Math.round(vectorMs * 100) / 100,
3879
+ keywordMs: Math.round(keywordMs * 100) / 100,
3880
+ fusionMs: Math.round(fusionMs * 100) / 100
3881
+ });
3324
3882
  return Promise.all(
3325
3883
  filtered.map(async (r) => {
3326
3884
  let content = "";
@@ -3359,11 +3917,8 @@ var Indexer = class {
3359
3917
  if (scores.size === 0) {
3360
3918
  return [];
3361
3919
  }
3362
- const allMetadata = store.getAllMetadata();
3363
- const metadataMap = /* @__PURE__ */ new Map();
3364
- for (const { key, metadata } of allMetadata) {
3365
- metadataMap.set(key, metadata);
3366
- }
3920
+ const chunkIds = Array.from(scores.keys());
3921
+ const metadataMap = store.getMetadataBatch(chunkIds);
3367
3922
  const results = [];
3368
3923
  for (const [chunkId, score] of scores) {
3369
3924
  const metadata = metadataMap.get(chunkId);
@@ -3415,14 +3970,18 @@ var Indexer = class {
3415
3970
  };
3416
3971
  }
3417
3972
  async clearIndex() {
3418
- const { store, invertedIndex } = await this.ensureInitialized();
3973
+ const { store, invertedIndex, database } = await this.ensureInitialized();
3419
3974
  store.clear();
3420
3975
  store.save();
3421
3976
  invertedIndex.clear();
3422
3977
  invertedIndex.save();
3978
+ this.fileHashCache.clear();
3979
+ this.saveFileHashCache();
3980
+ database.clearBranch(this.currentBranch);
3423
3981
  }
3424
3982
  async healthCheck() {
3425
3983
  const { store, invertedIndex, database } = await this.ensureInitialized();
3984
+ this.logger.gc("info", "Starting health check");
3426
3985
  const allMetadata = store.getAllMetadata();
3427
3986
  const filePathsToChunkKeys = /* @__PURE__ */ new Map();
3428
3987
  for (const { key, metadata } of allMetadata) {
@@ -3449,6 +4008,13 @@ var Indexer = class {
3449
4008
  }
3450
4009
  const gcOrphanEmbeddings = database.gcOrphanEmbeddings();
3451
4010
  const gcOrphanChunks = database.gcOrphanChunks();
4011
+ this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
4012
+ this.logger.gc("info", "Health check complete", {
4013
+ removedStale: removedCount,
4014
+ orphanEmbeddings: gcOrphanEmbeddings,
4015
+ orphanChunks: gcOrphanChunks,
4016
+ removedFiles: removedFilePaths.length
4017
+ });
3452
4018
  return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks };
3453
4019
  }
3454
4020
  async retryFailedBatches() {
@@ -3482,9 +4048,12 @@ var Indexer = class {
3482
4048
  invertedIndex.removeChunk(chunk.id);
3483
4049
  invertedIndex.addChunk(chunk.id, chunk.content);
3484
4050
  }
4051
+ this.logger.recordChunksEmbedded(batch.chunks.length);
4052
+ this.logger.recordEmbeddingApiCall(result.totalTokensUsed);
3485
4053
  succeeded += batch.chunks.length;
3486
4054
  } catch (error) {
3487
4055
  failed += batch.chunks.length;
4056
+ this.logger.recordEmbeddingError();
3488
4057
  stillFailing.push({
3489
4058
  ...batch,
3490
4059
  attemptCount: batch.attemptCount + 1,
@@ -3519,6 +4088,9 @@ var Indexer = class {
3519
4088
  const { database } = await this.ensureInitialized();
3520
4089
  return database.getStats();
3521
4090
  }
4091
+ getLogger() {
4092
+ return this.logger;
4093
+ }
3522
4094
  };
3523
4095
 
3524
4096
  // node_modules/chokidar/index.js
@@ -3611,7 +4183,7 @@ var ReaddirpStream = class extends import_node_stream.Readable {
3611
4183
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
3612
4184
  const statMethod = opts.lstat ? import_promises.lstat : import_promises.stat;
3613
4185
  if (wantBigintFsStats) {
3614
- this._stat = (path8) => statMethod(path8, { bigint: true });
4186
+ this._stat = (path9) => statMethod(path9, { bigint: true });
3615
4187
  } else {
3616
4188
  this._stat = statMethod;
3617
4189
  }
@@ -3636,8 +4208,8 @@ var ReaddirpStream = class extends import_node_stream.Readable {
3636
4208
  const par = this.parent;
3637
4209
  const fil = par && par.files;
3638
4210
  if (fil && fil.length > 0) {
3639
- const { path: path8, depth } = par;
3640
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path8));
4211
+ const { path: path9, depth } = par;
4212
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path9));
3641
4213
  const awaited = await Promise.all(slice);
3642
4214
  for (const entry of awaited) {
3643
4215
  if (!entry)
@@ -3677,21 +4249,21 @@ var ReaddirpStream = class extends import_node_stream.Readable {
3677
4249
  this.reading = false;
3678
4250
  }
3679
4251
  }
3680
- async _exploreDir(path8, depth) {
4252
+ async _exploreDir(path9, depth) {
3681
4253
  let files;
3682
4254
  try {
3683
- files = await (0, import_promises.readdir)(path8, this._rdOptions);
4255
+ files = await (0, import_promises.readdir)(path9, this._rdOptions);
3684
4256
  } catch (error) {
3685
4257
  this._onError(error);
3686
4258
  }
3687
- return { files, depth, path: path8 };
4259
+ return { files, depth, path: path9 };
3688
4260
  }
3689
- async _formatEntry(dirent, path8) {
4261
+ async _formatEntry(dirent, path9) {
3690
4262
  let entry;
3691
- const basename3 = this._isDirent ? dirent.name : dirent;
4263
+ const basename4 = this._isDirent ? dirent.name : dirent;
3692
4264
  try {
3693
- const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(path8, basename3));
3694
- entry = { path: (0, import_node_path.relative)(this._root, fullPath), fullPath, basename: basename3 };
4265
+ const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(path9, basename4));
4266
+ entry = { path: (0, import_node_path.relative)(this._root, fullPath), fullPath, basename: basename4 };
3695
4267
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
3696
4268
  } catch (err) {
3697
4269
  this._onError(err);
@@ -4090,16 +4662,16 @@ var delFromSet = (main, prop, item) => {
4090
4662
  };
4091
4663
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
4092
4664
  var FsWatchInstances = /* @__PURE__ */ new Map();
4093
- function createFsWatchInstance(path8, options, listener, errHandler, emitRaw) {
4665
+ function createFsWatchInstance(path9, options, listener, errHandler, emitRaw) {
4094
4666
  const handleEvent = (rawEvent, evPath) => {
4095
- listener(path8);
4096
- emitRaw(rawEvent, evPath, { watchedPath: path8 });
4097
- if (evPath && path8 !== evPath) {
4098
- fsWatchBroadcast(sp.resolve(path8, evPath), KEY_LISTENERS, sp.join(path8, evPath));
4667
+ listener(path9);
4668
+ emitRaw(rawEvent, evPath, { watchedPath: path9 });
4669
+ if (evPath && path9 !== evPath) {
4670
+ fsWatchBroadcast(sp.resolve(path9, evPath), KEY_LISTENERS, sp.join(path9, evPath));
4099
4671
  }
4100
4672
  };
4101
4673
  try {
4102
- return (0, import_node_fs.watch)(path8, {
4674
+ return (0, import_node_fs.watch)(path9, {
4103
4675
  persistent: options.persistent
4104
4676
  }, handleEvent);
4105
4677
  } catch (error) {
@@ -4115,12 +4687,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
4115
4687
  listener(val1, val2, val3);
4116
4688
  });
4117
4689
  };
4118
- var setFsWatchListener = (path8, fullPath, options, handlers) => {
4690
+ var setFsWatchListener = (path9, fullPath, options, handlers) => {
4119
4691
  const { listener, errHandler, rawEmitter } = handlers;
4120
4692
  let cont = FsWatchInstances.get(fullPath);
4121
4693
  let watcher;
4122
4694
  if (!options.persistent) {
4123
- watcher = createFsWatchInstance(path8, options, listener, errHandler, rawEmitter);
4695
+ watcher = createFsWatchInstance(path9, options, listener, errHandler, rawEmitter);
4124
4696
  if (!watcher)
4125
4697
  return;
4126
4698
  return watcher.close.bind(watcher);
@@ -4131,7 +4703,7 @@ var setFsWatchListener = (path8, fullPath, options, handlers) => {
4131
4703
  addAndConvert(cont, KEY_RAW, rawEmitter);
4132
4704
  } else {
4133
4705
  watcher = createFsWatchInstance(
4134
- path8,
4706
+ path9,
4135
4707
  options,
4136
4708
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
4137
4709
  errHandler,
@@ -4146,7 +4718,7 @@ var setFsWatchListener = (path8, fullPath, options, handlers) => {
4146
4718
  cont.watcherUnusable = true;
4147
4719
  if (isWindows && error.code === "EPERM") {
4148
4720
  try {
4149
- const fd = await (0, import_promises2.open)(path8, "r");
4721
+ const fd = await (0, import_promises2.open)(path9, "r");
4150
4722
  await fd.close();
4151
4723
  broadcastErr(error);
4152
4724
  } catch (err) {
@@ -4177,7 +4749,7 @@ var setFsWatchListener = (path8, fullPath, options, handlers) => {
4177
4749
  };
4178
4750
  };
4179
4751
  var FsWatchFileInstances = /* @__PURE__ */ new Map();
4180
- var setFsWatchFileListener = (path8, fullPath, options, handlers) => {
4752
+ var setFsWatchFileListener = (path9, fullPath, options, handlers) => {
4181
4753
  const { listener, rawEmitter } = handlers;
4182
4754
  let cont = FsWatchFileInstances.get(fullPath);
4183
4755
  const copts = cont && cont.options;
@@ -4199,7 +4771,7 @@ var setFsWatchFileListener = (path8, fullPath, options, handlers) => {
4199
4771
  });
4200
4772
  const currmtime = curr.mtimeMs;
4201
4773
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
4202
- foreach(cont.listeners, (listener2) => listener2(path8, curr));
4774
+ foreach(cont.listeners, (listener2) => listener2(path9, curr));
4203
4775
  }
4204
4776
  })
4205
4777
  };
@@ -4229,13 +4801,13 @@ var NodeFsHandler = class {
4229
4801
  * @param listener on fs change
4230
4802
  * @returns closer for the watcher instance
4231
4803
  */
4232
- _watchWithNodeFs(path8, listener) {
4804
+ _watchWithNodeFs(path9, listener) {
4233
4805
  const opts = this.fsw.options;
4234
- const directory = sp.dirname(path8);
4235
- const basename3 = sp.basename(path8);
4806
+ const directory = sp.dirname(path9);
4807
+ const basename4 = sp.basename(path9);
4236
4808
  const parent = this.fsw._getWatchedDir(directory);
4237
- parent.add(basename3);
4238
- const absolutePath = sp.resolve(path8);
4809
+ parent.add(basename4);
4810
+ const absolutePath = sp.resolve(path9);
4239
4811
  const options = {
4240
4812
  persistent: opts.persistent
4241
4813
  };
@@ -4244,13 +4816,13 @@ var NodeFsHandler = class {
4244
4816
  let closer;
4245
4817
  if (opts.usePolling) {
4246
4818
  const enableBin = opts.interval !== opts.binaryInterval;
4247
- options.interval = enableBin && isBinaryPath(basename3) ? opts.binaryInterval : opts.interval;
4248
- closer = setFsWatchFileListener(path8, absolutePath, options, {
4819
+ options.interval = enableBin && isBinaryPath(basename4) ? opts.binaryInterval : opts.interval;
4820
+ closer = setFsWatchFileListener(path9, absolutePath, options, {
4249
4821
  listener,
4250
4822
  rawEmitter: this.fsw._emitRaw
4251
4823
  });
4252
4824
  } else {
4253
- closer = setFsWatchListener(path8, absolutePath, options, {
4825
+ closer = setFsWatchListener(path9, absolutePath, options, {
4254
4826
  listener,
4255
4827
  errHandler: this._boundHandleError,
4256
4828
  rawEmitter: this.fsw._emitRaw
@@ -4266,13 +4838,13 @@ var NodeFsHandler = class {
4266
4838
  if (this.fsw.closed) {
4267
4839
  return;
4268
4840
  }
4269
- const dirname4 = sp.dirname(file);
4270
- const basename3 = sp.basename(file);
4271
- const parent = this.fsw._getWatchedDir(dirname4);
4841
+ const dirname5 = sp.dirname(file);
4842
+ const basename4 = sp.basename(file);
4843
+ const parent = this.fsw._getWatchedDir(dirname5);
4272
4844
  let prevStats = stats;
4273
- if (parent.has(basename3))
4845
+ if (parent.has(basename4))
4274
4846
  return;
4275
- const listener = async (path8, newStats) => {
4847
+ const listener = async (path9, newStats) => {
4276
4848
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
4277
4849
  return;
4278
4850
  if (!newStats || newStats.mtimeMs === 0) {
@@ -4286,18 +4858,18 @@ var NodeFsHandler = class {
4286
4858
  this.fsw._emit(EV.CHANGE, file, newStats2);
4287
4859
  }
4288
4860
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
4289
- this.fsw._closeFile(path8);
4861
+ this.fsw._closeFile(path9);
4290
4862
  prevStats = newStats2;
4291
4863
  const closer2 = this._watchWithNodeFs(file, listener);
4292
4864
  if (closer2)
4293
- this.fsw._addPathCloser(path8, closer2);
4865
+ this.fsw._addPathCloser(path9, closer2);
4294
4866
  } else {
4295
4867
  prevStats = newStats2;
4296
4868
  }
4297
4869
  } catch (error) {
4298
- this.fsw._remove(dirname4, basename3);
4870
+ this.fsw._remove(dirname5, basename4);
4299
4871
  }
4300
- } else if (parent.has(basename3)) {
4872
+ } else if (parent.has(basename4)) {
4301
4873
  const at = newStats.atimeMs;
4302
4874
  const mt = newStats.mtimeMs;
4303
4875
  if (!at || at <= mt || mt !== prevStats.mtimeMs) {
@@ -4322,7 +4894,7 @@ var NodeFsHandler = class {
4322
4894
  * @param item basename of this item
4323
4895
  * @returns true if no more processing is needed for this entry.
4324
4896
  */
4325
- async _handleSymlink(entry, directory, path8, item) {
4897
+ async _handleSymlink(entry, directory, path9, item) {
4326
4898
  if (this.fsw.closed) {
4327
4899
  return;
4328
4900
  }
@@ -4332,7 +4904,7 @@ var NodeFsHandler = class {
4332
4904
  this.fsw._incrReadyCount();
4333
4905
  let linkPath;
4334
4906
  try {
4335
- linkPath = await (0, import_promises2.realpath)(path8);
4907
+ linkPath = await (0, import_promises2.realpath)(path9);
4336
4908
  } catch (e) {
4337
4909
  this.fsw._emitReady();
4338
4910
  return true;
@@ -4342,12 +4914,12 @@ var NodeFsHandler = class {
4342
4914
  if (dir.has(item)) {
4343
4915
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
4344
4916
  this.fsw._symlinkPaths.set(full, linkPath);
4345
- this.fsw._emit(EV.CHANGE, path8, entry.stats);
4917
+ this.fsw._emit(EV.CHANGE, path9, entry.stats);
4346
4918
  }
4347
4919
  } else {
4348
4920
  dir.add(item);
4349
4921
  this.fsw._symlinkPaths.set(full, linkPath);
4350
- this.fsw._emit(EV.ADD, path8, entry.stats);
4922
+ this.fsw._emit(EV.ADD, path9, entry.stats);
4351
4923
  }
4352
4924
  this.fsw._emitReady();
4353
4925
  return true;
@@ -4377,9 +4949,9 @@ var NodeFsHandler = class {
4377
4949
  return;
4378
4950
  }
4379
4951
  const item = entry.path;
4380
- let path8 = sp.join(directory, item);
4952
+ let path9 = sp.join(directory, item);
4381
4953
  current.add(item);
4382
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path8, item)) {
4954
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path9, item)) {
4383
4955
  return;
4384
4956
  }
4385
4957
  if (this.fsw.closed) {
@@ -4388,8 +4960,8 @@ var NodeFsHandler = class {
4388
4960
  }
4389
4961
  if (item === target || !target && !previous.has(item)) {
4390
4962
  this.fsw._incrReadyCount();
4391
- path8 = sp.join(dir, sp.relative(dir, path8));
4392
- this._addToNodeFs(path8, initialAdd, wh, depth + 1);
4963
+ path9 = sp.join(dir, sp.relative(dir, path9));
4964
+ this._addToNodeFs(path9, initialAdd, wh, depth + 1);
4393
4965
  }
4394
4966
  }).on(EV.ERROR, this._boundHandleError);
4395
4967
  return new Promise((resolve4, reject) => {
@@ -4458,13 +5030,13 @@ var NodeFsHandler = class {
4458
5030
  * @param depth Child path actually targeted for watch
4459
5031
  * @param target Child path actually targeted for watch
4460
5032
  */
4461
- async _addToNodeFs(path8, initialAdd, priorWh, depth, target) {
5033
+ async _addToNodeFs(path9, initialAdd, priorWh, depth, target) {
4462
5034
  const ready = this.fsw._emitReady;
4463
- if (this.fsw._isIgnored(path8) || this.fsw.closed) {
5035
+ if (this.fsw._isIgnored(path9) || this.fsw.closed) {
4464
5036
  ready();
4465
5037
  return false;
4466
5038
  }
4467
- const wh = this.fsw._getWatchHelpers(path8);
5039
+ const wh = this.fsw._getWatchHelpers(path9);
4468
5040
  if (priorWh) {
4469
5041
  wh.filterPath = (entry) => priorWh.filterPath(entry);
4470
5042
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -4480,8 +5052,8 @@ var NodeFsHandler = class {
4480
5052
  const follow = this.fsw.options.followSymlinks;
4481
5053
  let closer;
4482
5054
  if (stats.isDirectory()) {
4483
- const absPath = sp.resolve(path8);
4484
- const targetPath = follow ? await (0, import_promises2.realpath)(path8) : path8;
5055
+ const absPath = sp.resolve(path9);
5056
+ const targetPath = follow ? await (0, import_promises2.realpath)(path9) : path9;
4485
5057
  if (this.fsw.closed)
4486
5058
  return;
4487
5059
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -4491,29 +5063,29 @@ var NodeFsHandler = class {
4491
5063
  this.fsw._symlinkPaths.set(absPath, targetPath);
4492
5064
  }
4493
5065
  } else if (stats.isSymbolicLink()) {
4494
- const targetPath = follow ? await (0, import_promises2.realpath)(path8) : path8;
5066
+ const targetPath = follow ? await (0, import_promises2.realpath)(path9) : path9;
4495
5067
  if (this.fsw.closed)
4496
5068
  return;
4497
5069
  const parent = sp.dirname(wh.watchPath);
4498
5070
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
4499
5071
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
4500
- closer = await this._handleDir(parent, stats, initialAdd, depth, path8, wh, targetPath);
5072
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path9, wh, targetPath);
4501
5073
  if (this.fsw.closed)
4502
5074
  return;
4503
5075
  if (targetPath !== void 0) {
4504
- this.fsw._symlinkPaths.set(sp.resolve(path8), targetPath);
5076
+ this.fsw._symlinkPaths.set(sp.resolve(path9), targetPath);
4505
5077
  }
4506
5078
  } else {
4507
5079
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
4508
5080
  }
4509
5081
  ready();
4510
5082
  if (closer)
4511
- this.fsw._addPathCloser(path8, closer);
5083
+ this.fsw._addPathCloser(path9, closer);
4512
5084
  return false;
4513
5085
  } catch (error) {
4514
5086
  if (this.fsw._handleError(error)) {
4515
5087
  ready();
4516
- return path8;
5088
+ return path9;
4517
5089
  }
4518
5090
  }
4519
5091
  }
@@ -4556,24 +5128,24 @@ function createPattern(matcher) {
4556
5128
  }
4557
5129
  return () => false;
4558
5130
  }
4559
- function normalizePath(path8) {
4560
- if (typeof path8 !== "string")
5131
+ function normalizePath(path9) {
5132
+ if (typeof path9 !== "string")
4561
5133
  throw new Error("string expected");
4562
- path8 = sp2.normalize(path8);
4563
- path8 = path8.replace(/\\/g, "/");
5134
+ path9 = sp2.normalize(path9);
5135
+ path9 = path9.replace(/\\/g, "/");
4564
5136
  let prepend = false;
4565
- if (path8.startsWith("//"))
5137
+ if (path9.startsWith("//"))
4566
5138
  prepend = true;
4567
- path8 = path8.replace(DOUBLE_SLASH_RE, "/");
5139
+ path9 = path9.replace(DOUBLE_SLASH_RE, "/");
4568
5140
  if (prepend)
4569
- path8 = "/" + path8;
4570
- return path8;
5141
+ path9 = "/" + path9;
5142
+ return path9;
4571
5143
  }
4572
5144
  function matchPatterns(patterns, testString, stats) {
4573
- const path8 = normalizePath(testString);
5145
+ const path9 = normalizePath(testString);
4574
5146
  for (let index = 0; index < patterns.length; index++) {
4575
5147
  const pattern = patterns[index];
4576
- if (pattern(path8, stats)) {
5148
+ if (pattern(path9, stats)) {
4577
5149
  return true;
4578
5150
  }
4579
5151
  }
@@ -4611,19 +5183,19 @@ var toUnix = (string) => {
4611
5183
  }
4612
5184
  return str;
4613
5185
  };
4614
- var normalizePathToUnix = (path8) => toUnix(sp2.normalize(toUnix(path8)));
4615
- var normalizeIgnored = (cwd = "") => (path8) => {
4616
- if (typeof path8 === "string") {
4617
- return normalizePathToUnix(sp2.isAbsolute(path8) ? path8 : sp2.join(cwd, path8));
5186
+ var normalizePathToUnix = (path9) => toUnix(sp2.normalize(toUnix(path9)));
5187
+ var normalizeIgnored = (cwd = "") => (path9) => {
5188
+ if (typeof path9 === "string") {
5189
+ return normalizePathToUnix(sp2.isAbsolute(path9) ? path9 : sp2.join(cwd, path9));
4618
5190
  } else {
4619
- return path8;
5191
+ return path9;
4620
5192
  }
4621
5193
  };
4622
- var getAbsolutePath = (path8, cwd) => {
4623
- if (sp2.isAbsolute(path8)) {
4624
- return path8;
5194
+ var getAbsolutePath = (path9, cwd) => {
5195
+ if (sp2.isAbsolute(path9)) {
5196
+ return path9;
4625
5197
  }
4626
- return sp2.join(cwd, path8);
5198
+ return sp2.join(cwd, path9);
4627
5199
  };
4628
5200
  var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
4629
5201
  var DirEntry = class {
@@ -4688,10 +5260,10 @@ var WatchHelper = class {
4688
5260
  dirParts;
4689
5261
  followSymlinks;
4690
5262
  statMethod;
4691
- constructor(path8, follow, fsw) {
5263
+ constructor(path9, follow, fsw) {
4692
5264
  this.fsw = fsw;
4693
- const watchPath = path8;
4694
- this.path = path8 = path8.replace(REPLACER_RE, "");
5265
+ const watchPath = path9;
5266
+ this.path = path9 = path9.replace(REPLACER_RE, "");
4695
5267
  this.watchPath = watchPath;
4696
5268
  this.fullWatchPath = sp2.resolve(watchPath);
4697
5269
  this.dirParts = [];
@@ -4831,20 +5403,20 @@ var FSWatcher = class extends import_node_events.EventEmitter {
4831
5403
  this._closePromise = void 0;
4832
5404
  let paths = unifyPaths(paths_);
4833
5405
  if (cwd) {
4834
- paths = paths.map((path8) => {
4835
- const absPath = getAbsolutePath(path8, cwd);
5406
+ paths = paths.map((path9) => {
5407
+ const absPath = getAbsolutePath(path9, cwd);
4836
5408
  return absPath;
4837
5409
  });
4838
5410
  }
4839
- paths.forEach((path8) => {
4840
- this._removeIgnoredPath(path8);
5411
+ paths.forEach((path9) => {
5412
+ this._removeIgnoredPath(path9);
4841
5413
  });
4842
5414
  this._userIgnored = void 0;
4843
5415
  if (!this._readyCount)
4844
5416
  this._readyCount = 0;
4845
5417
  this._readyCount += paths.length;
4846
- Promise.all(paths.map(async (path8) => {
4847
- const res = await this._nodeFsHandler._addToNodeFs(path8, !_internal, void 0, 0, _origAdd);
5418
+ Promise.all(paths.map(async (path9) => {
5419
+ const res = await this._nodeFsHandler._addToNodeFs(path9, !_internal, void 0, 0, _origAdd);
4848
5420
  if (res)
4849
5421
  this._emitReady();
4850
5422
  return res;
@@ -4866,17 +5438,17 @@ var FSWatcher = class extends import_node_events.EventEmitter {
4866
5438
  return this;
4867
5439
  const paths = unifyPaths(paths_);
4868
5440
  const { cwd } = this.options;
4869
- paths.forEach((path8) => {
4870
- if (!sp2.isAbsolute(path8) && !this._closers.has(path8)) {
5441
+ paths.forEach((path9) => {
5442
+ if (!sp2.isAbsolute(path9) && !this._closers.has(path9)) {
4871
5443
  if (cwd)
4872
- path8 = sp2.join(cwd, path8);
4873
- path8 = sp2.resolve(path8);
5444
+ path9 = sp2.join(cwd, path9);
5445
+ path9 = sp2.resolve(path9);
4874
5446
  }
4875
- this._closePath(path8);
4876
- this._addIgnoredPath(path8);
4877
- if (this._watched.has(path8)) {
5447
+ this._closePath(path9);
5448
+ this._addIgnoredPath(path9);
5449
+ if (this._watched.has(path9)) {
4878
5450
  this._addIgnoredPath({
4879
- path: path8,
5451
+ path: path9,
4880
5452
  recursive: true
4881
5453
  });
4882
5454
  }
@@ -4940,38 +5512,38 @@ var FSWatcher = class extends import_node_events.EventEmitter {
4940
5512
  * @param stats arguments to be passed with event
4941
5513
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
4942
5514
  */
4943
- async _emit(event, path8, stats) {
5515
+ async _emit(event, path9, stats) {
4944
5516
  if (this.closed)
4945
5517
  return;
4946
5518
  const opts = this.options;
4947
5519
  if (isWindows)
4948
- path8 = sp2.normalize(path8);
5520
+ path9 = sp2.normalize(path9);
4949
5521
  if (opts.cwd)
4950
- path8 = sp2.relative(opts.cwd, path8);
4951
- const args = [path8];
5522
+ path9 = sp2.relative(opts.cwd, path9);
5523
+ const args = [path9];
4952
5524
  if (stats != null)
4953
5525
  args.push(stats);
4954
5526
  const awf = opts.awaitWriteFinish;
4955
5527
  let pw;
4956
- if (awf && (pw = this._pendingWrites.get(path8))) {
5528
+ if (awf && (pw = this._pendingWrites.get(path9))) {
4957
5529
  pw.lastChange = /* @__PURE__ */ new Date();
4958
5530
  return this;
4959
5531
  }
4960
5532
  if (opts.atomic) {
4961
5533
  if (event === EVENTS.UNLINK) {
4962
- this._pendingUnlinks.set(path8, [event, ...args]);
5534
+ this._pendingUnlinks.set(path9, [event, ...args]);
4963
5535
  setTimeout(() => {
4964
- this._pendingUnlinks.forEach((entry, path9) => {
5536
+ this._pendingUnlinks.forEach((entry, path10) => {
4965
5537
  this.emit(...entry);
4966
5538
  this.emit(EVENTS.ALL, ...entry);
4967
- this._pendingUnlinks.delete(path9);
5539
+ this._pendingUnlinks.delete(path10);
4968
5540
  });
4969
5541
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
4970
5542
  return this;
4971
5543
  }
4972
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path8)) {
5544
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path9)) {
4973
5545
  event = EVENTS.CHANGE;
4974
- this._pendingUnlinks.delete(path8);
5546
+ this._pendingUnlinks.delete(path9);
4975
5547
  }
4976
5548
  }
4977
5549
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -4989,16 +5561,16 @@ var FSWatcher = class extends import_node_events.EventEmitter {
4989
5561
  this.emitWithAll(event, args);
4990
5562
  }
4991
5563
  };
4992
- this._awaitWriteFinish(path8, awf.stabilityThreshold, event, awfEmit);
5564
+ this._awaitWriteFinish(path9, awf.stabilityThreshold, event, awfEmit);
4993
5565
  return this;
4994
5566
  }
4995
5567
  if (event === EVENTS.CHANGE) {
4996
- const isThrottled = !this._throttle(EVENTS.CHANGE, path8, 50);
5568
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path9, 50);
4997
5569
  if (isThrottled)
4998
5570
  return this;
4999
5571
  }
5000
5572
  if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
5001
- const fullPath = opts.cwd ? sp2.join(opts.cwd, path8) : path8;
5573
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path9) : path9;
5002
5574
  let stats2;
5003
5575
  try {
5004
5576
  stats2 = await (0, import_promises3.stat)(fullPath);
@@ -5029,23 +5601,23 @@ var FSWatcher = class extends import_node_events.EventEmitter {
5029
5601
  * @param timeout duration of time to suppress duplicate actions
5030
5602
  * @returns tracking object or false if action should be suppressed
5031
5603
  */
5032
- _throttle(actionType, path8, timeout) {
5604
+ _throttle(actionType, path9, timeout) {
5033
5605
  if (!this._throttled.has(actionType)) {
5034
5606
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
5035
5607
  }
5036
5608
  const action = this._throttled.get(actionType);
5037
5609
  if (!action)
5038
5610
  throw new Error("invalid throttle");
5039
- const actionPath = action.get(path8);
5611
+ const actionPath = action.get(path9);
5040
5612
  if (actionPath) {
5041
5613
  actionPath.count++;
5042
5614
  return false;
5043
5615
  }
5044
5616
  let timeoutObject;
5045
5617
  const clear = () => {
5046
- const item = action.get(path8);
5618
+ const item = action.get(path9);
5047
5619
  const count = item ? item.count : 0;
5048
- action.delete(path8);
5620
+ action.delete(path9);
5049
5621
  clearTimeout(timeoutObject);
5050
5622
  if (item)
5051
5623
  clearTimeout(item.timeoutObject);
@@ -5053,7 +5625,7 @@ var FSWatcher = class extends import_node_events.EventEmitter {
5053
5625
  };
5054
5626
  timeoutObject = setTimeout(clear, timeout);
5055
5627
  const thr = { timeoutObject, clear, count: 0 };
5056
- action.set(path8, thr);
5628
+ action.set(path9, thr);
5057
5629
  return thr;
5058
5630
  }
5059
5631
  _incrReadyCount() {
@@ -5067,44 +5639,44 @@ var FSWatcher = class extends import_node_events.EventEmitter {
5067
5639
  * @param event
5068
5640
  * @param awfEmit Callback to be called when ready for event to be emitted.
5069
5641
  */
5070
- _awaitWriteFinish(path8, threshold, event, awfEmit) {
5642
+ _awaitWriteFinish(path9, threshold, event, awfEmit) {
5071
5643
  const awf = this.options.awaitWriteFinish;
5072
5644
  if (typeof awf !== "object")
5073
5645
  return;
5074
5646
  const pollInterval = awf.pollInterval;
5075
5647
  let timeoutHandler;
5076
- let fullPath = path8;
5077
- if (this.options.cwd && !sp2.isAbsolute(path8)) {
5078
- fullPath = sp2.join(this.options.cwd, path8);
5648
+ let fullPath = path9;
5649
+ if (this.options.cwd && !sp2.isAbsolute(path9)) {
5650
+ fullPath = sp2.join(this.options.cwd, path9);
5079
5651
  }
5080
5652
  const now = /* @__PURE__ */ new Date();
5081
5653
  const writes = this._pendingWrites;
5082
5654
  function awaitWriteFinishFn(prevStat) {
5083
5655
  (0, import_node_fs2.stat)(fullPath, (err, curStat) => {
5084
- if (err || !writes.has(path8)) {
5656
+ if (err || !writes.has(path9)) {
5085
5657
  if (err && err.code !== "ENOENT")
5086
5658
  awfEmit(err);
5087
5659
  return;
5088
5660
  }
5089
5661
  const now2 = Number(/* @__PURE__ */ new Date());
5090
5662
  if (prevStat && curStat.size !== prevStat.size) {
5091
- writes.get(path8).lastChange = now2;
5663
+ writes.get(path9).lastChange = now2;
5092
5664
  }
5093
- const pw = writes.get(path8);
5665
+ const pw = writes.get(path9);
5094
5666
  const df = now2 - pw.lastChange;
5095
5667
  if (df >= threshold) {
5096
- writes.delete(path8);
5668
+ writes.delete(path9);
5097
5669
  awfEmit(void 0, curStat);
5098
5670
  } else {
5099
5671
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
5100
5672
  }
5101
5673
  });
5102
5674
  }
5103
- if (!writes.has(path8)) {
5104
- writes.set(path8, {
5675
+ if (!writes.has(path9)) {
5676
+ writes.set(path9, {
5105
5677
  lastChange: now,
5106
5678
  cancelWait: () => {
5107
- writes.delete(path8);
5679
+ writes.delete(path9);
5108
5680
  clearTimeout(timeoutHandler);
5109
5681
  return event;
5110
5682
  }
@@ -5115,8 +5687,8 @@ var FSWatcher = class extends import_node_events.EventEmitter {
5115
5687
  /**
5116
5688
  * Determines whether user has asked to ignore this path.
5117
5689
  */
5118
- _isIgnored(path8, stats) {
5119
- if (this.options.atomic && DOT_RE.test(path8))
5690
+ _isIgnored(path9, stats) {
5691
+ if (this.options.atomic && DOT_RE.test(path9))
5120
5692
  return true;
5121
5693
  if (!this._userIgnored) {
5122
5694
  const { cwd } = this.options;
@@ -5126,17 +5698,17 @@ var FSWatcher = class extends import_node_events.EventEmitter {
5126
5698
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
5127
5699
  this._userIgnored = anymatch(list, void 0);
5128
5700
  }
5129
- return this._userIgnored(path8, stats);
5701
+ return this._userIgnored(path9, stats);
5130
5702
  }
5131
- _isntIgnored(path8, stat4) {
5132
- return !this._isIgnored(path8, stat4);
5703
+ _isntIgnored(path9, stat4) {
5704
+ return !this._isIgnored(path9, stat4);
5133
5705
  }
5134
5706
  /**
5135
5707
  * Provides a set of common helpers and properties relating to symlink handling.
5136
5708
  * @param path file or directory pattern being watched
5137
5709
  */
5138
- _getWatchHelpers(path8) {
5139
- return new WatchHelper(path8, this.options.followSymlinks, this);
5710
+ _getWatchHelpers(path9) {
5711
+ return new WatchHelper(path9, this.options.followSymlinks, this);
5140
5712
  }
5141
5713
  // Directory helpers
5142
5714
  // -----------------
@@ -5168,63 +5740,63 @@ var FSWatcher = class extends import_node_events.EventEmitter {
5168
5740
  * @param item base path of item/directory
5169
5741
  */
5170
5742
  _remove(directory, item, isDirectory) {
5171
- const path8 = sp2.join(directory, item);
5172
- const fullPath = sp2.resolve(path8);
5173
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path8) || this._watched.has(fullPath);
5174
- if (!this._throttle("remove", path8, 100))
5743
+ const path9 = sp2.join(directory, item);
5744
+ const fullPath = sp2.resolve(path9);
5745
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path9) || this._watched.has(fullPath);
5746
+ if (!this._throttle("remove", path9, 100))
5175
5747
  return;
5176
5748
  if (!isDirectory && this._watched.size === 1) {
5177
5749
  this.add(directory, item, true);
5178
5750
  }
5179
- const wp = this._getWatchedDir(path8);
5751
+ const wp = this._getWatchedDir(path9);
5180
5752
  const nestedDirectoryChildren = wp.getChildren();
5181
- nestedDirectoryChildren.forEach((nested) => this._remove(path8, nested));
5753
+ nestedDirectoryChildren.forEach((nested) => this._remove(path9, nested));
5182
5754
  const parent = this._getWatchedDir(directory);
5183
5755
  const wasTracked = parent.has(item);
5184
5756
  parent.remove(item);
5185
5757
  if (this._symlinkPaths.has(fullPath)) {
5186
5758
  this._symlinkPaths.delete(fullPath);
5187
5759
  }
5188
- let relPath = path8;
5760
+ let relPath = path9;
5189
5761
  if (this.options.cwd)
5190
- relPath = sp2.relative(this.options.cwd, path8);
5762
+ relPath = sp2.relative(this.options.cwd, path9);
5191
5763
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
5192
5764
  const event = this._pendingWrites.get(relPath).cancelWait();
5193
5765
  if (event === EVENTS.ADD)
5194
5766
  return;
5195
5767
  }
5196
- this._watched.delete(path8);
5768
+ this._watched.delete(path9);
5197
5769
  this._watched.delete(fullPath);
5198
5770
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
5199
- if (wasTracked && !this._isIgnored(path8))
5200
- this._emit(eventName, path8);
5201
- this._closePath(path8);
5771
+ if (wasTracked && !this._isIgnored(path9))
5772
+ this._emit(eventName, path9);
5773
+ this._closePath(path9);
5202
5774
  }
5203
5775
  /**
5204
5776
  * Closes all watchers for a path
5205
5777
  */
5206
- _closePath(path8) {
5207
- this._closeFile(path8);
5208
- const dir = sp2.dirname(path8);
5209
- this._getWatchedDir(dir).remove(sp2.basename(path8));
5778
+ _closePath(path9) {
5779
+ this._closeFile(path9);
5780
+ const dir = sp2.dirname(path9);
5781
+ this._getWatchedDir(dir).remove(sp2.basename(path9));
5210
5782
  }
5211
5783
  /**
5212
5784
  * Closes only file-specific watchers
5213
5785
  */
5214
- _closeFile(path8) {
5215
- const closers = this._closers.get(path8);
5786
+ _closeFile(path9) {
5787
+ const closers = this._closers.get(path9);
5216
5788
  if (!closers)
5217
5789
  return;
5218
5790
  closers.forEach((closer) => closer());
5219
- this._closers.delete(path8);
5791
+ this._closers.delete(path9);
5220
5792
  }
5221
- _addPathCloser(path8, closer) {
5793
+ _addPathCloser(path9, closer) {
5222
5794
  if (!closer)
5223
5795
  return;
5224
- let list = this._closers.get(path8);
5796
+ let list = this._closers.get(path9);
5225
5797
  if (!list) {
5226
5798
  list = [];
5227
- this._closers.set(path8, list);
5799
+ this._closers.set(path9, list);
5228
5800
  }
5229
5801
  list.push(closer);
5230
5802
  }
@@ -5319,7 +5891,7 @@ var FileWatcher = class {
5319
5891
  return;
5320
5892
  }
5321
5893
  const changes = Array.from(this.pendingChanges.entries()).map(
5322
- ([path8, type]) => ({ path: path8, type })
5894
+ ([path9, type]) => ({ path: path9, type })
5323
5895
  );
5324
5896
  this.pendingChanges.clear();
5325
5897
  try {
@@ -5499,7 +6071,7 @@ var index_codebase = (0, import_plugin.tool)({
5499
6071
  estimateOnly: z.boolean().optional().default(false).describe("Only show cost estimate without indexing"),
5500
6072
  verbose: z.boolean().optional().default(false).describe("Show detailed info about skipped files and parsing failures")
5501
6073
  },
5502
- async execute(args) {
6074
+ async execute(args, context) {
5503
6075
  const indexer = getIndexer();
5504
6076
  if (args.estimateOnly) {
5505
6077
  const estimate = await indexer.estimateCost();
@@ -5508,7 +6080,19 @@ var index_codebase = (0, import_plugin.tool)({
5508
6080
  if (args.force) {
5509
6081
  await indexer.clearIndex();
5510
6082
  }
5511
- const stats = await indexer.index();
6083
+ const stats = await indexer.index((progress) => {
6084
+ context.metadata({
6085
+ title: formatProgressTitle(progress),
6086
+ metadata: {
6087
+ phase: progress.phase,
6088
+ filesProcessed: progress.filesProcessed,
6089
+ totalFiles: progress.totalFiles,
6090
+ chunksProcessed: progress.chunksProcessed,
6091
+ totalChunks: progress.totalChunks,
6092
+ percentage: calculatePercentage(progress)
6093
+ }
6094
+ });
6095
+ });
5512
6096
  return formatIndexStats(stats, args.verbose ?? false);
5513
6097
  }
5514
6098
  });
@@ -5546,6 +6130,51 @@ var index_health_check = (0, import_plugin.tool)({
5546
6130
  return lines.join("\n");
5547
6131
  }
5548
6132
  });
6133
+ var index_metrics = (0, import_plugin.tool)({
6134
+ description: "Get metrics and performance statistics for the codebase index. Shows indexing stats, search timings, cache hit rates, and API usage. Requires debug.enabled=true and debug.metrics=true in config.",
6135
+ args: {},
6136
+ async execute() {
6137
+ const indexer = getIndexer();
6138
+ const logger = indexer.getLogger();
6139
+ if (!logger.isEnabled()) {
6140
+ return 'Debug mode is disabled. Enable it in your config:\n\n```json\n{\n "debug": {\n "enabled": true,\n "metrics": true\n }\n}\n```';
6141
+ }
6142
+ if (!logger.isMetricsEnabled()) {
6143
+ return 'Metrics collection is disabled. Enable it in your config:\n\n```json\n{\n "debug": {\n "enabled": true,\n "metrics": true\n }\n}\n```';
6144
+ }
6145
+ return logger.formatMetrics();
6146
+ }
6147
+ });
6148
+ var index_logs = (0, import_plugin.tool)({
6149
+ description: "Get recent debug logs from the codebase indexer. Shows timestamped log entries with level and category. Requires debug.enabled=true in config.",
6150
+ args: {
6151
+ limit: z.number().optional().default(20).describe("Maximum number of log entries to return"),
6152
+ category: z.enum(["search", "embedding", "cache", "gc", "branch", "general"]).optional().describe("Filter by log category"),
6153
+ level: z.enum(["error", "warn", "info", "debug"]).optional().describe("Filter by minimum log level")
6154
+ },
6155
+ async execute(args) {
6156
+ const indexer = getIndexer();
6157
+ const logger = indexer.getLogger();
6158
+ if (!logger.isEnabled()) {
6159
+ return 'Debug mode is disabled. Enable it in your config:\n\n```json\n{\n "debug": {\n "enabled": true\n }\n}\n```';
6160
+ }
6161
+ let logs;
6162
+ if (args.category) {
6163
+ logs = logger.getLogsByCategory(args.category, args.limit);
6164
+ } else if (args.level) {
6165
+ logs = logger.getLogsByLevel(args.level, args.limit);
6166
+ } else {
6167
+ logs = logger.getLogs(args.limit);
6168
+ }
6169
+ if (logs.length === 0) {
6170
+ return "No logs recorded yet. Logs are captured during indexing and search operations.";
6171
+ }
6172
+ return logs.map((l) => {
6173
+ const dataStr = l.data ? ` ${JSON.stringify(l.data)}` : "";
6174
+ return `[${l.timestamp}] [${l.level.toUpperCase()}] [${l.category}] ${l.message}${dataStr}`;
6175
+ }).join("\n");
6176
+ }
6177
+ });
5549
6178
  function formatIndexStats(stats, verbose = false) {
5550
6179
  const lines = [];
5551
6180
  if (stats.indexedChunks === 0 && stats.removedChunks === 0) {
@@ -5607,12 +6236,91 @@ function formatStatus(status) {
5607
6236
  }
5608
6237
  return lines.join("\n");
5609
6238
  }
6239
+ function formatProgressTitle(progress) {
6240
+ switch (progress.phase) {
6241
+ case "scanning":
6242
+ return "Scanning files...";
6243
+ case "parsing":
6244
+ return `Parsing: ${progress.filesProcessed}/${progress.totalFiles} files`;
6245
+ case "embedding":
6246
+ return `Embedding: ${progress.chunksProcessed}/${progress.totalChunks} chunks`;
6247
+ case "storing":
6248
+ return "Storing index...";
6249
+ case "complete":
6250
+ return "Indexing complete";
6251
+ default:
6252
+ return "Indexing...";
6253
+ }
6254
+ }
6255
+ function calculatePercentage(progress) {
6256
+ if (progress.phase === "scanning") return 0;
6257
+ if (progress.phase === "complete") return 100;
6258
+ if (progress.phase === "parsing") {
6259
+ if (progress.totalFiles === 0) return 5;
6260
+ return Math.round(5 + progress.filesProcessed / progress.totalFiles * 15);
6261
+ }
6262
+ if (progress.phase === "embedding") {
6263
+ if (progress.totalChunks === 0) return 20;
6264
+ return Math.round(20 + progress.chunksProcessed / progress.totalChunks * 70);
6265
+ }
6266
+ if (progress.phase === "storing") return 95;
6267
+ return 0;
6268
+ }
6269
+
6270
+ // src/commands/loader.ts
6271
+ var import_fs5 = require("fs");
6272
+ var path7 = __toESM(require("path"), 1);
6273
+ function parseFrontmatter(content) {
6274
+ const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
6275
+ const match = content.match(frontmatterRegex);
6276
+ if (!match) {
6277
+ return { frontmatter: {}, body: content.trim() };
6278
+ }
6279
+ const frontmatterLines = match[1].split("\n");
6280
+ const frontmatter = {};
6281
+ for (const line of frontmatterLines) {
6282
+ const colonIndex = line.indexOf(":");
6283
+ if (colonIndex > 0) {
6284
+ const key = line.slice(0, colonIndex).trim();
6285
+ const value = line.slice(colonIndex + 1).trim();
6286
+ frontmatter[key] = value;
6287
+ }
6288
+ }
6289
+ return { frontmatter, body: match[2].trim() };
6290
+ }
6291
+ function loadCommandsFromDirectory(commandsDir) {
6292
+ const commands = /* @__PURE__ */ new Map();
6293
+ if (!(0, import_fs5.existsSync)(commandsDir)) {
6294
+ return commands;
6295
+ }
6296
+ const files = (0, import_fs5.readdirSync)(commandsDir).filter((f) => f.endsWith(".md"));
6297
+ for (const file of files) {
6298
+ const filePath = path7.join(commandsDir, file);
6299
+ const content = (0, import_fs5.readFileSync)(filePath, "utf-8");
6300
+ const { frontmatter, body } = parseFrontmatter(content);
6301
+ const name = path7.basename(file, ".md");
6302
+ const description = frontmatter.description || `Run the ${name} command`;
6303
+ commands.set(name, {
6304
+ description,
6305
+ template: body
6306
+ });
6307
+ }
6308
+ return commands;
6309
+ }
5610
6310
 
5611
6311
  // src/index.ts
6312
+ var import_meta2 = {};
6313
+ function getCommandsDir() {
6314
+ let currentDir = process.cwd();
6315
+ if (typeof import_meta2 !== "undefined" && import_meta2.url) {
6316
+ currentDir = path8.dirname((0, import_url2.fileURLToPath)(import_meta2.url));
6317
+ }
6318
+ return path8.join(currentDir, "..", "commands");
6319
+ }
5612
6320
  function loadJsonFile(filePath) {
5613
6321
  try {
5614
- if ((0, import_fs5.existsSync)(filePath)) {
5615
- const content = (0, import_fs5.readFileSync)(filePath, "utf-8");
6322
+ if ((0, import_fs6.existsSync)(filePath)) {
6323
+ const content = (0, import_fs6.readFileSync)(filePath, "utf-8");
5616
6324
  return JSON.parse(content);
5617
6325
  }
5618
6326
  } catch {
@@ -5620,11 +6328,11 @@ function loadJsonFile(filePath) {
5620
6328
  return null;
5621
6329
  }
5622
6330
  function loadPluginConfig(projectRoot) {
5623
- const projectConfig = loadJsonFile(path7.join(projectRoot, ".opencode", "codebase-index.json"));
6331
+ const projectConfig = loadJsonFile(path8.join(projectRoot, ".opencode", "codebase-index.json"));
5624
6332
  if (projectConfig) {
5625
6333
  return projectConfig;
5626
6334
  }
5627
- const globalConfigPath = path7.join(os3.homedir(), ".config", "opencode", "codebase-index.json");
6335
+ const globalConfigPath = path8.join(os3.homedir(), ".config", "opencode", "codebase-index.json");
5628
6336
  const globalConfig = loadJsonFile(globalConfigPath);
5629
6337
  if (globalConfig) {
5630
6338
  return globalConfig;
@@ -5652,40 +6360,17 @@ var plugin = async ({ directory }) => {
5652
6360
  codebase_search,
5653
6361
  index_codebase,
5654
6362
  index_status,
5655
- index_health_check
6363
+ index_health_check,
6364
+ index_metrics,
6365
+ index_logs
5656
6366
  },
5657
6367
  async config(cfg) {
5658
6368
  cfg.command = cfg.command ?? {};
5659
- cfg.command["search"] = {
5660
- description: "Search codebase by meaning using semantic search",
5661
- template: `Use the \`codebase_search\` tool to find code related to: $ARGUMENTS
5662
-
5663
- If the index doesn't exist yet, run \`index_codebase\` first.
5664
-
5665
- Return the most relevant results with file paths and line numbers.`
5666
- };
5667
- cfg.command["find"] = {
5668
- description: "Find code using hybrid approach (semantic + grep)",
5669
- template: `Find code related to: $ARGUMENTS
5670
-
5671
- Strategy:
5672
- 1. First use \`codebase_search\` to find semantically related code
5673
- 2. From the results, identify specific function/class names
5674
- 3. Use grep to find all occurrences of those identifiers
5675
- 4. Combine findings into a comprehensive answer
5676
-
5677
- If the semantic index doesn't exist, run \`index_codebase\` first.`
5678
- };
5679
- cfg.command["index"] = {
5680
- description: "Index the codebase for semantic search",
5681
- template: `Run the \`index_codebase\` tool to create or update the semantic search index.
5682
-
5683
- Show progress and final statistics including:
5684
- - Number of files processed
5685
- - Number of chunks indexed
5686
- - Tokens used
5687
- - Duration`
5688
- };
6369
+ const commandsDir = getCommandsDir();
6370
+ const commands = loadCommandsFromDirectory(commandsDir);
6371
+ for (const [name, definition] of commands) {
6372
+ cfg.command[name] = definition;
6373
+ }
5689
6374
  }
5690
6375
  };
5691
6376
  };