opencode-codebase-index 0.12.0 → 0.13.1

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.cjs CHANGED
@@ -14,6 +14,10 @@ var __commonJS = (cb, mod) => function __require() {
14
14
  throw mod = 0, e;
15
15
  }
16
16
  };
17
+ var __export = (target, all) => {
18
+ for (var name in all)
19
+ __defProp(target, name, { get: all[name], enumerable: true });
20
+ };
17
21
  var __copyProps = (to, from, except, desc) => {
18
22
  if (from && typeof from === "object" || typeof from === "function") {
19
23
  for (let key of __getOwnPropNames(from))
@@ -30,6 +34,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
34
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
31
35
  mod
32
36
  ));
37
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
33
38
 
34
39
  // node_modules/eventemitter3/index.js
35
40
  var require_eventemitter3 = __commonJS({
@@ -62,11 +67,11 @@ var require_eventemitter3 = __commonJS({
62
67
  if (--emitter._eventsCount === 0) emitter._events = new Events();
63
68
  else delete emitter._events[evt];
64
69
  }
65
- function EventEmitter2() {
70
+ function EventEmitter3() {
66
71
  this._events = new Events();
67
72
  this._eventsCount = 0;
68
73
  }
69
- EventEmitter2.prototype.eventNames = function eventNames() {
74
+ EventEmitter3.prototype.eventNames = function eventNames() {
70
75
  var names = [], events, name;
71
76
  if (this._eventsCount === 0) return names;
72
77
  for (name in events = this._events) {
@@ -77,7 +82,7 @@ var require_eventemitter3 = __commonJS({
77
82
  }
78
83
  return names;
79
84
  };
80
- EventEmitter2.prototype.listeners = function listeners(event) {
85
+ EventEmitter3.prototype.listeners = function listeners(event) {
81
86
  var evt = prefix ? prefix + event : event, handlers = this._events[evt];
82
87
  if (!handlers) return [];
83
88
  if (handlers.fn) return [handlers.fn];
@@ -86,13 +91,13 @@ var require_eventemitter3 = __commonJS({
86
91
  }
87
92
  return ee;
88
93
  };
89
- EventEmitter2.prototype.listenerCount = function listenerCount(event) {
94
+ EventEmitter3.prototype.listenerCount = function listenerCount(event) {
90
95
  var evt = prefix ? prefix + event : event, listeners = this._events[evt];
91
96
  if (!listeners) return 0;
92
97
  if (listeners.fn) return 1;
93
98
  return listeners.length;
94
99
  };
95
- EventEmitter2.prototype.emit = function emit(event, a1, a2, a3, a4, a5) {
100
+ EventEmitter3.prototype.emit = function emit(event, a1, a2, a3, a4, a5) {
96
101
  var evt = prefix ? prefix + event : event;
97
102
  if (!this._events[evt]) return false;
98
103
  var listeners = this._events[evt], len = arguments.length, args, i;
@@ -143,13 +148,13 @@ var require_eventemitter3 = __commonJS({
143
148
  }
144
149
  return true;
145
150
  };
146
- EventEmitter2.prototype.on = function on(event, fn, context) {
151
+ EventEmitter3.prototype.on = function on(event, fn, context) {
147
152
  return addListener(this, event, fn, context, false);
148
153
  };
149
- EventEmitter2.prototype.once = function once(event, fn, context) {
154
+ EventEmitter3.prototype.once = function once(event, fn, context) {
150
155
  return addListener(this, event, fn, context, true);
151
156
  };
152
- EventEmitter2.prototype.removeListener = function removeListener(event, fn, context, once) {
157
+ EventEmitter3.prototype.removeListener = function removeListener(event, fn, context, once) {
153
158
  var evt = prefix ? prefix + event : event;
154
159
  if (!this._events[evt]) return this;
155
160
  if (!fn) {
@@ -172,7 +177,7 @@ var require_eventemitter3 = __commonJS({
172
177
  }
173
178
  return this;
174
179
  };
175
- EventEmitter2.prototype.removeAllListeners = function removeAllListeners(event) {
180
+ EventEmitter3.prototype.removeAllListeners = function removeAllListeners(event) {
176
181
  var evt;
177
182
  if (event) {
178
183
  evt = prefix ? prefix + event : event;
@@ -183,12 +188,12 @@ var require_eventemitter3 = __commonJS({
183
188
  }
184
189
  return this;
185
190
  };
186
- EventEmitter2.prototype.off = EventEmitter2.prototype.removeListener;
187
- EventEmitter2.prototype.addListener = EventEmitter2.prototype.on;
188
- EventEmitter2.prefixed = prefix;
189
- EventEmitter2.EventEmitter = EventEmitter2;
191
+ EventEmitter3.prototype.off = EventEmitter3.prototype.removeListener;
192
+ EventEmitter3.prototype.addListener = EventEmitter3.prototype.on;
193
+ EventEmitter3.prefixed = prefix;
194
+ EventEmitter3.EventEmitter = EventEmitter3;
190
195
  if ("undefined" !== typeof module3) {
191
- module3.exports = EventEmitter2;
196
+ module3.exports = EventEmitter3;
192
197
  }
193
198
  }
194
199
  });
@@ -211,7 +216,7 @@ var require_ignore = __commonJS({
211
216
  var REGEX_SPLITALL_CRLF = /\r?\n/g;
212
217
  var REGEX_TEST_INVALID_PATH = /^\.{0,2}\/|^\.{1,2}$/;
213
218
  var REGEX_TEST_TRAILING_SLASH = /\/$/;
214
- var SLASH = "/";
219
+ var SLASH2 = "/";
215
220
  var TMP_KEY_IGNORE = "node-ignore";
216
221
  if (typeof Symbol !== "undefined") {
217
222
  TMP_KEY_IGNORE = /* @__PURE__ */ Symbol.for("node-ignore");
@@ -491,7 +496,7 @@ var require_ignore = __commonJS({
491
496
  // path matching.
492
497
  // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
493
498
  // @returns {TestResult} true if a file is ignored
494
- test(path19, checkUnignored, mode) {
499
+ test(path25, checkUnignored, mode) {
495
500
  let ignored = false;
496
501
  let unignored = false;
497
502
  let matchedRule;
@@ -500,7 +505,7 @@ var require_ignore = __commonJS({
500
505
  if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
501
506
  return;
502
507
  }
503
- const matched = rule[mode].test(path19);
508
+ const matched = rule[mode].test(path25);
504
509
  if (!matched) {
505
510
  return;
506
511
  }
@@ -521,17 +526,17 @@ var require_ignore = __commonJS({
521
526
  var throwError = (message, Ctor) => {
522
527
  throw new Ctor(message);
523
528
  };
524
- var checkPath = (path19, originalPath, doThrow) => {
525
- if (!isString(path19)) {
529
+ var checkPath = (path25, originalPath, doThrow) => {
530
+ if (!isString(path25)) {
526
531
  return doThrow(
527
532
  `path must be a string, but got \`${originalPath}\``,
528
533
  TypeError
529
534
  );
530
535
  }
531
- if (!path19) {
536
+ if (!path25) {
532
537
  return doThrow(`path must not be empty`, TypeError);
533
538
  }
534
- if (checkPath.isNotRelative(path19)) {
539
+ if (checkPath.isNotRelative(path25)) {
535
540
  const r = "`path.relative()`d";
536
541
  return doThrow(
537
542
  `path should be a ${r} string, but got "${originalPath}"`,
@@ -540,7 +545,7 @@ var require_ignore = __commonJS({
540
545
  }
541
546
  return true;
542
547
  };
543
- var isNotRelative = (path19) => REGEX_TEST_INVALID_PATH.test(path19);
548
+ var isNotRelative = (path25) => REGEX_TEST_INVALID_PATH.test(path25);
544
549
  checkPath.isNotRelative = isNotRelative;
545
550
  checkPath.convert = (p) => p;
546
551
  var Ignore2 = class {
@@ -570,23 +575,23 @@ var require_ignore = __commonJS({
570
575
  }
571
576
  // @returns {TestResult}
572
577
  _test(originalPath, cache, checkUnignored, slices) {
573
- const path19 = originalPath && checkPath.convert(originalPath);
578
+ const path25 = originalPath && checkPath.convert(originalPath);
574
579
  checkPath(
575
- path19,
580
+ path25,
576
581
  originalPath,
577
582
  this._strictPathCheck ? throwError : RETURN_FALSE
578
583
  );
579
- return this._t(path19, cache, checkUnignored, slices);
584
+ return this._t(path25, cache, checkUnignored, slices);
580
585
  }
581
- checkIgnore(path19) {
582
- if (!REGEX_TEST_TRAILING_SLASH.test(path19)) {
583
- return this.test(path19);
586
+ checkIgnore(path25) {
587
+ if (!REGEX_TEST_TRAILING_SLASH.test(path25)) {
588
+ return this.test(path25);
584
589
  }
585
- const slices = path19.split(SLASH).filter(Boolean);
590
+ const slices = path25.split(SLASH2).filter(Boolean);
586
591
  slices.pop();
587
592
  if (slices.length) {
588
593
  const parent = this._t(
589
- slices.join(SLASH) + SLASH,
594
+ slices.join(SLASH2) + SLASH2,
590
595
  this._testCache,
591
596
  true,
592
597
  slices
@@ -595,48 +600,48 @@ var require_ignore = __commonJS({
595
600
  return parent;
596
601
  }
597
602
  }
598
- return this._rules.test(path19, false, MODE_CHECK_IGNORE);
603
+ return this._rules.test(path25, false, MODE_CHECK_IGNORE);
599
604
  }
600
- _t(path19, cache, checkUnignored, slices) {
601
- if (path19 in cache) {
602
- return cache[path19];
605
+ _t(path25, cache, checkUnignored, slices) {
606
+ if (path25 in cache) {
607
+ return cache[path25];
603
608
  }
604
609
  if (!slices) {
605
- slices = path19.split(SLASH).filter(Boolean);
610
+ slices = path25.split(SLASH2).filter(Boolean);
606
611
  }
607
612
  slices.pop();
608
613
  if (!slices.length) {
609
- return cache[path19] = this._rules.test(path19, checkUnignored, MODE_IGNORE);
614
+ return cache[path25] = this._rules.test(path25, checkUnignored, MODE_IGNORE);
610
615
  }
611
616
  const parent = this._t(
612
- slices.join(SLASH) + SLASH,
617
+ slices.join(SLASH2) + SLASH2,
613
618
  cache,
614
619
  checkUnignored,
615
620
  slices
616
621
  );
617
- return cache[path19] = parent.ignored ? parent : this._rules.test(path19, checkUnignored, MODE_IGNORE);
622
+ return cache[path25] = parent.ignored ? parent : this._rules.test(path25, checkUnignored, MODE_IGNORE);
618
623
  }
619
- ignores(path19) {
620
- return this._test(path19, this._ignoreCache, false).ignored;
624
+ ignores(path25) {
625
+ return this._test(path25, this._ignoreCache, false).ignored;
621
626
  }
622
627
  createFilter() {
623
- return (path19) => !this.ignores(path19);
628
+ return (path25) => !this.ignores(path25);
624
629
  }
625
630
  filter(paths) {
626
631
  return makeArray(paths).filter(this.createFilter());
627
632
  }
628
633
  // @returns {TestResult}
629
- test(path19) {
630
- return this._test(path19, this._testCache, true);
634
+ test(path25) {
635
+ return this._test(path25, this._testCache, true);
631
636
  }
632
637
  };
633
638
  var factory = (options) => new Ignore2(options);
634
- var isPathValid = (path19) => checkPath(path19 && checkPath.convert(path19), path19, RETURN_FALSE);
639
+ var isPathValid = (path25) => checkPath(path25 && checkPath.convert(path25), path25, RETURN_FALSE);
635
640
  var setupWindows = () => {
636
641
  const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
637
642
  checkPath.convert = makePosix;
638
643
  const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
639
- checkPath.isNotRelative = (path19) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path19) || isNotRelative(path19);
644
+ checkPath.isNotRelative = (path25) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path25) || isNotRelative(path25);
640
645
  };
641
646
  if (
642
647
  // Detect `process` so that it can run in browsers.
@@ -652,8 +657,18 @@ var require_ignore = __commonJS({
652
657
  });
653
658
 
654
659
  // src/cli.ts
660
+ var cli_exports = {};
661
+ __export(cli_exports, {
662
+ isCliEntrypoint: () => isCliEntrypoint,
663
+ loadCliRawConfig: () => loadCliRawConfig,
664
+ parseArgs: () => parseArgs
665
+ });
666
+ module.exports = __toCommonJS(cli_exports);
655
667
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
656
- var path18 = __toESM(require("path"), 1);
668
+ var import_fs15 = require("fs");
669
+ var os5 = __toESM(require("os"), 1);
670
+ var path24 = __toESM(require("path"), 1);
671
+ var import_url2 = require("url");
657
672
 
658
673
  // src/config/constants.ts
659
674
  var DEFAULT_INCLUDE = [
@@ -1038,6 +1053,19 @@ var autoDetectProviders = AUTO_DETECT_PROVIDER_ORDER.filter(
1038
1053
  (provider) => provider in EMBEDDING_MODELS
1039
1054
  );
1040
1055
 
1056
+ // src/config/host.ts
1057
+ var HOST_MODES = ["opencode", "codex", "claude", "pi"];
1058
+ function isSupportedHostMode(value) {
1059
+ return HOST_MODES.includes(value);
1060
+ }
1061
+ function parseHostMode(value) {
1062
+ const normalized = (value ?? "").toLowerCase();
1063
+ if (isSupportedHostMode(normalized)) {
1064
+ return normalized;
1065
+ }
1066
+ throw new Error(`Invalid host mode: ${value ?? "(none)"}. Allowed values: ${HOST_MODES.join(", ")}.`);
1067
+ }
1068
+
1041
1069
  // src/eval/compare.ts
1042
1070
  function metricDelta(current, baseline) {
1043
1071
  const absolute = current - baseline;
@@ -1081,9 +1109,9 @@ var import_fs = require("fs");
1081
1109
  var path = __toESM(require("path"), 1);
1082
1110
 
1083
1111
  // src/eval/report-formatters.ts
1084
- function assertFiniteNumber(value, path19) {
1112
+ function assertFiniteNumber(value, path25) {
1085
1113
  if (typeof value !== "number" || Number.isNaN(value) || !Number.isFinite(value)) {
1086
- throw new Error(`${path19} must be a finite number`);
1114
+ throw new Error(`${path25} must be a finite number`);
1087
1115
  }
1088
1116
  return value;
1089
1117
  }
@@ -1304,7 +1332,7 @@ function pTimeout(promise, options) {
1304
1332
  } = options;
1305
1333
  let timer;
1306
1334
  let abortHandler;
1307
- const wrappedPromise = new Promise((resolve11, reject) => {
1335
+ const wrappedPromise = new Promise((resolve15, reject) => {
1308
1336
  if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
1309
1337
  throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
1310
1338
  }
@@ -1318,7 +1346,7 @@ function pTimeout(promise, options) {
1318
1346
  };
1319
1347
  signal.addEventListener("abort", abortHandler, { once: true });
1320
1348
  }
1321
- promise.then(resolve11, reject);
1349
+ promise.then(resolve15, reject);
1322
1350
  if (milliseconds === Number.POSITIVE_INFINITY) {
1323
1351
  return;
1324
1352
  }
@@ -1326,7 +1354,7 @@ function pTimeout(promise, options) {
1326
1354
  timer = customTimers.setTimeout.call(void 0, () => {
1327
1355
  if (fallback) {
1328
1356
  try {
1329
- resolve11(fallback());
1357
+ resolve15(fallback());
1330
1358
  } catch (error) {
1331
1359
  reject(error);
1332
1360
  }
@@ -1336,7 +1364,7 @@ function pTimeout(promise, options) {
1336
1364
  promise.cancel();
1337
1365
  }
1338
1366
  if (message === false) {
1339
- resolve11();
1367
+ resolve15();
1340
1368
  } else if (message instanceof Error) {
1341
1369
  reject(message);
1342
1370
  } else {
@@ -1738,7 +1766,7 @@ var PQueue = class extends import_index.default {
1738
1766
  // Assign unique ID if not provided
1739
1767
  id: options.id ?? (this.#idAssigner++).toString()
1740
1768
  };
1741
- return new Promise((resolve11, reject) => {
1769
+ return new Promise((resolve15, reject) => {
1742
1770
  const taskSymbol = /* @__PURE__ */ Symbol(`task-${options.id}`);
1743
1771
  let cleanupQueueAbortHandler = () => void 0;
1744
1772
  const run = async () => {
@@ -1778,7 +1806,7 @@ var PQueue = class extends import_index.default {
1778
1806
  })]);
1779
1807
  }
1780
1808
  const result = await operation;
1781
- resolve11(result);
1809
+ resolve15(result);
1782
1810
  this.emit("completed", result);
1783
1811
  } catch (error) {
1784
1812
  reject(error);
@@ -1966,13 +1994,13 @@ var PQueue = class extends import_index.default {
1966
1994
  });
1967
1995
  }
1968
1996
  async #onEvent(event, filter) {
1969
- return new Promise((resolve11) => {
1997
+ return new Promise((resolve15) => {
1970
1998
  const listener = () => {
1971
1999
  if (filter && !filter()) {
1972
2000
  return;
1973
2001
  }
1974
2002
  this.off(event, listener);
1975
- resolve11();
2003
+ resolve15();
1976
2004
  };
1977
2005
  this.on(event, listener);
1978
2006
  });
@@ -2258,7 +2286,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
2258
2286
  const finalDelay = Math.min(delayTime, remainingTime);
2259
2287
  options.signal?.throwIfAborted();
2260
2288
  if (finalDelay > 0) {
2261
- await new Promise((resolve11, reject) => {
2289
+ await new Promise((resolve15, reject) => {
2262
2290
  const onAbort = () => {
2263
2291
  clearTimeout(timeoutToken);
2264
2292
  options.signal?.removeEventListener("abort", onAbort);
@@ -2266,7 +2294,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
2266
2294
  };
2267
2295
  const timeoutToken = setTimeout(() => {
2268
2296
  options.signal?.removeEventListener("abort", onAbort);
2269
- resolve11();
2297
+ resolve15();
2270
2298
  }, finalDelay);
2271
2299
  if (options.unref) {
2272
2300
  timeoutToken.unref?.();
@@ -3054,8 +3082,64 @@ function isHiddenPathSegment(part) {
3054
3082
  function isBuildPathSegment(part) {
3055
3083
  return part.toLowerCase().includes("build");
3056
3084
  }
3085
+ function hasFilteredPathSegment(relativePath, separator = path3.sep) {
3086
+ return relativePath.split(separator).some(
3087
+ (part) => isHiddenPathSegment(part) || isBuildPathSegment(part)
3088
+ );
3089
+ }
3090
+ var RESTRICTED_DIRECTORIES = /* @__PURE__ */ new Set([
3091
+ // macOS
3092
+ "library",
3093
+ "applications",
3094
+ "system",
3095
+ "volumes",
3096
+ "private",
3097
+ "cores",
3098
+ // Linux
3099
+ "proc",
3100
+ "sys",
3101
+ "dev",
3102
+ "run",
3103
+ "snap",
3104
+ // Windows
3105
+ "windows",
3106
+ "programdata",
3107
+ "program files",
3108
+ "program files (x86)",
3109
+ "$recycle.bin"
3110
+ ]);
3111
+ function isRestrictedDirectory(relativePath, separator = path3.sep) {
3112
+ const firstSegment = relativePath.split(separator)[0];
3113
+ if (!firstSegment) return false;
3114
+ return RESTRICTED_DIRECTORIES.has(firstSegment.toLowerCase());
3115
+ }
3057
3116
 
3058
3117
  // src/utils/files.ts
3118
+ var PROJECT_MARKERS = [
3119
+ ".git",
3120
+ "package.json",
3121
+ "Cargo.toml",
3122
+ "go.mod",
3123
+ "pyproject.toml",
3124
+ "setup.py",
3125
+ "requirements.txt",
3126
+ "Gemfile",
3127
+ "composer.json",
3128
+ "pom.xml",
3129
+ "build.gradle",
3130
+ "CMakeLists.txt",
3131
+ "Makefile",
3132
+ ".opencode",
3133
+ ".codebase-index"
3134
+ ];
3135
+ function hasProjectMarker(projectRoot) {
3136
+ for (const marker of PROJECT_MARKERS) {
3137
+ if ((0, import_fs3.existsSync)(path4.join(projectRoot, marker))) {
3138
+ return true;
3139
+ }
3140
+ }
3141
+ return false;
3142
+ }
3059
3143
  function createIgnoreFilter(projectRoot) {
3060
3144
  const ig = (0, import_ignore.default)();
3061
3145
  const defaultIgnores = [
@@ -3070,6 +3154,7 @@ function createIgnoreFilter(projectRoot) {
3070
3154
  "target",
3071
3155
  "vendor",
3072
3156
  ".opencode",
3157
+ ".codebase-index",
3073
3158
  ".*",
3074
3159
  "**/.*",
3075
3160
  "**/.*/**",
@@ -3083,6 +3168,26 @@ function createIgnoreFilter(projectRoot) {
3083
3168
  }
3084
3169
  return ig;
3085
3170
  }
3171
+ function shouldIncludeFile(filePath, projectRoot, includePatterns, excludePatterns, ignoreFilter) {
3172
+ const relativePath = path4.relative(projectRoot, filePath);
3173
+ if (hasFilteredPathSegment(relativePath, path4.sep)) {
3174
+ return false;
3175
+ }
3176
+ if (ignoreFilter.ignores(relativePath)) {
3177
+ return false;
3178
+ }
3179
+ for (const pattern of excludePatterns) {
3180
+ if (matchGlob(relativePath, pattern)) {
3181
+ return false;
3182
+ }
3183
+ }
3184
+ for (const pattern of includePatterns) {
3185
+ if (matchGlob(relativePath, pattern)) {
3186
+ return true;
3187
+ }
3188
+ }
3189
+ return false;
3190
+ }
3086
3191
  function matchGlob(filePath, pattern) {
3087
3192
  if (pattern.startsWith("**/")) {
3088
3193
  const withoutPrefix = pattern.slice(3);
@@ -3124,8 +3229,8 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3124
3229
  if (entry.isDirectory()) {
3125
3230
  subdirs.push({ fullPath, relativePath });
3126
3231
  } else if (entry.isFile()) {
3127
- const stat = await import_fs3.promises.stat(fullPath);
3128
- if (stat.size > maxFileSize) {
3232
+ const stat4 = await import_fs3.promises.stat(fullPath);
3233
+ if (stat4.size > maxFileSize) {
3129
3234
  skipped.push({ path: relativePath, reason: "too_large" });
3130
3235
  continue;
3131
3236
  }
@@ -3143,7 +3248,7 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3143
3248
  }
3144
3249
  }
3145
3250
  if (matched) {
3146
- filesInDir.push({ path: fullPath, size: stat.size });
3251
+ filesInDir.push({ path: fullPath, size: stat4.size });
3147
3252
  }
3148
3253
  }
3149
3254
  }
@@ -3200,8 +3305,8 @@ async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFi
3200
3305
  }
3201
3306
  for (const resolvedKbRoot of normalizedRoots) {
3202
3307
  try {
3203
- const stat = await import_fs3.promises.stat(resolvedKbRoot);
3204
- if (!stat.isDirectory()) {
3308
+ const stat4 = await import_fs3.promises.stat(resolvedKbRoot);
3309
+ if (!stat4.isDirectory()) {
3205
3310
  skipped.push({ path: resolvedKbRoot, reason: "excluded" });
3206
3311
  continue;
3207
3312
  }
@@ -4409,11 +4514,11 @@ function resolveGitDir(repoRoot) {
4409
4514
  return null;
4410
4515
  }
4411
4516
  try {
4412
- const stat = (0, import_fs5.statSync)(gitPath);
4413
- if (stat.isDirectory()) {
4517
+ const stat4 = (0, import_fs5.statSync)(gitPath);
4518
+ if (stat4.isDirectory()) {
4414
4519
  return gitPath;
4415
4520
  }
4416
- if (stat.isFile()) {
4521
+ if (stat4.isFile()) {
4417
4522
  const content = (0, import_fs5.readFileSync)(gitPath, "utf-8").trim();
4418
4523
  const match = content.match(/^gitdir:\s*(.+)$/);
4419
4524
  if (match) {
@@ -4478,13 +4583,46 @@ function getBranchOrDefault(repoRoot) {
4478
4583
  }
4479
4584
  return getCurrentBranch(repoRoot) ?? "default";
4480
4585
  }
4586
+ function getHeadPath(repoRoot) {
4587
+ const gitDir = resolveGitDir(repoRoot);
4588
+ if (gitDir) {
4589
+ return path7.join(gitDir, "HEAD");
4590
+ }
4591
+ return path7.join(repoRoot, ".git", "HEAD");
4592
+ }
4481
4593
 
4482
4594
  // src/config/paths.ts
4483
4595
  var import_fs6 = require("fs");
4484
4596
  var os3 = __toESM(require("os"), 1);
4485
4597
  var path8 = __toESM(require("path"), 1);
4486
- var PROJECT_CONFIG_RELATIVE_PATH = path8.join(".opencode", "codebase-index.json");
4487
- var PROJECT_INDEX_RELATIVE_PATH = path8.join(".opencode", "index");
4598
+ var OPENCODE_PROJECT_CONFIG_RELATIVE_PATH = path8.join(".opencode", "codebase-index.json");
4599
+ var OPENCODE_PROJECT_INDEX_RELATIVE_PATH = path8.join(".opencode", "index");
4600
+ var CODEBASE_INDEX_DIR = ".codebase-index";
4601
+ var CODEBASE_PROJECT_CONFIG_RELATIVE_PATH = path8.join(CODEBASE_INDEX_DIR, "config.json");
4602
+ var CODEBASE_PROJECT_INDEX_RELATIVE_PATH = path8.join(CODEBASE_INDEX_DIR, "index");
4603
+ var CLAUDE_DIR = ".claude";
4604
+ var CLAUDE_PROJECT_CONFIG_RELATIVE_PATH = path8.join(CLAUDE_DIR, "codebase-index.json");
4605
+ var CLAUDE_PROJECT_INDEX_RELATIVE_PATH = path8.join(CLAUDE_DIR, "index");
4606
+ function getProjectConfigRelativePath(host) {
4607
+ switch (host) {
4608
+ case "opencode":
4609
+ return OPENCODE_PROJECT_CONFIG_RELATIVE_PATH;
4610
+ case "claude":
4611
+ return CLAUDE_PROJECT_CONFIG_RELATIVE_PATH;
4612
+ default:
4613
+ return CODEBASE_PROJECT_CONFIG_RELATIVE_PATH;
4614
+ }
4615
+ }
4616
+ function getProjectIndexRelativePath(host) {
4617
+ switch (host) {
4618
+ case "opencode":
4619
+ return OPENCODE_PROJECT_INDEX_RELATIVE_PATH;
4620
+ case "claude":
4621
+ return CLAUDE_PROJECT_INDEX_RELATIVE_PATH;
4622
+ default:
4623
+ return CODEBASE_PROJECT_INDEX_RELATIVE_PATH;
4624
+ }
4625
+ }
4488
4626
  function resolveWorktreeFallbackPath(projectRoot, relativePath) {
4489
4627
  const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot);
4490
4628
  if (!mainRepoRoot) {
@@ -4493,31 +4631,122 @@ function resolveWorktreeFallbackPath(projectRoot, relativePath) {
4493
4631
  const fallbackPath = path8.join(mainRepoRoot, relativePath);
4494
4632
  return (0, import_fs6.existsSync)(fallbackPath) ? fallbackPath : null;
4495
4633
  }
4496
- function hasProjectConfig(projectRoot) {
4497
- return (0, import_fs6.existsSync)(path8.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH));
4634
+ function resolveWorktreeFallbackProjectIndexPath(projectRoot, host) {
4635
+ const inheritedHostPath = resolveWorktreeFallbackPath(projectRoot, getProjectIndexRelativePath(host));
4636
+ if (inheritedHostPath) {
4637
+ return inheritedHostPath;
4638
+ }
4639
+ if (host !== "opencode") {
4640
+ return resolveWorktreeFallbackPath(projectRoot, OPENCODE_PROJECT_INDEX_RELATIVE_PATH);
4641
+ }
4642
+ return null;
4643
+ }
4644
+ function getHostProjectIndexRelativePath(host) {
4645
+ return getProjectIndexRelativePath(host);
4498
4646
  }
4499
- function getGlobalIndexPath() {
4500
- return path8.join(os3.homedir(), ".opencode", "global-index");
4647
+ function hasHostProjectConfig(projectRoot, host) {
4648
+ return (0, import_fs6.existsSync)(path8.join(projectRoot, getProjectConfigRelativePath(host)));
4501
4649
  }
4502
- function resolveProjectConfigPath(projectRoot) {
4503
- const localConfigPath = path8.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH);
4504
- if ((0, import_fs6.existsSync)(localConfigPath)) {
4505
- return localConfigPath;
4650
+ function getGlobalIndexPath(host = "opencode") {
4651
+ switch (host) {
4652
+ case "opencode":
4653
+ return path8.join(os3.homedir(), ".opencode", "global-index");
4654
+ case "claude":
4655
+ return path8.join(os3.homedir(), ".claude", "global-index");
4656
+ default:
4657
+ return path8.join(os3.homedir(), ".codebase-index", "global-index");
4658
+ }
4659
+ }
4660
+ function getGlobalConfigPath(host = "opencode") {
4661
+ switch (host) {
4662
+ case "opencode":
4663
+ return path8.join(os3.homedir(), ".config", "opencode", "codebase-index.json");
4664
+ case "claude":
4665
+ return path8.join(os3.homedir(), ".claude", "codebase-index.json");
4666
+ default:
4667
+ return path8.join(os3.homedir(), ".config", "codebase-index", "config.json");
4668
+ }
4669
+ }
4670
+ function resolveGlobalConfigPath(host = "opencode") {
4671
+ const hostConfigPath = getGlobalConfigPath(host);
4672
+ if ((0, import_fs6.existsSync)(hostConfigPath)) {
4673
+ return hostConfigPath;
4674
+ }
4675
+ if (host !== "opencode") {
4676
+ const legacyConfigPath = getGlobalConfigPath("opencode");
4677
+ if ((0, import_fs6.existsSync)(legacyConfigPath)) {
4678
+ return legacyConfigPath;
4679
+ }
4680
+ }
4681
+ return hostConfigPath;
4682
+ }
4683
+ function resolveGlobalIndexPath(host = "opencode") {
4684
+ const hostIndexPath = getGlobalIndexPath(host);
4685
+ if ((0, import_fs6.existsSync)(hostIndexPath)) {
4686
+ return hostIndexPath;
4687
+ }
4688
+ if (host !== "opencode") {
4689
+ const legacyIndexPath = getGlobalIndexPath("opencode");
4690
+ if ((0, import_fs6.existsSync)(legacyIndexPath)) {
4691
+ return legacyIndexPath;
4692
+ }
4693
+ }
4694
+ return hostIndexPath;
4695
+ }
4696
+ function resolveProjectConfigPath(projectRoot, host = "opencode") {
4697
+ const hostConfigPath = path8.join(projectRoot, getProjectConfigRelativePath(host));
4698
+ if ((0, import_fs6.existsSync)(hostConfigPath)) {
4699
+ return hostConfigPath;
4700
+ }
4701
+ if (host !== "opencode") {
4702
+ const legacyConfigPath = path8.join(projectRoot, OPENCODE_PROJECT_CONFIG_RELATIVE_PATH);
4703
+ if ((0, import_fs6.existsSync)(legacyConfigPath)) {
4704
+ return legacyConfigPath;
4705
+ }
4706
+ }
4707
+ const hostFallback = resolveWorktreeFallbackPath(projectRoot, getProjectConfigRelativePath(host));
4708
+ if (hostFallback) {
4709
+ return hostFallback;
4506
4710
  }
4507
- return resolveWorktreeFallbackPath(projectRoot, PROJECT_CONFIG_RELATIVE_PATH) ?? localConfigPath;
4711
+ if (host !== "opencode") {
4712
+ const legacyFallback = resolveWorktreeFallbackPath(projectRoot, OPENCODE_PROJECT_CONFIG_RELATIVE_PATH);
4713
+ if (legacyFallback) {
4714
+ return legacyFallback;
4715
+ }
4716
+ }
4717
+ return hostConfigPath;
4508
4718
  }
4509
- function resolveProjectIndexPath(projectRoot, scope) {
4719
+ function resolveWritableProjectConfigPath(projectRoot, host = "opencode") {
4720
+ return path8.join(projectRoot, getProjectConfigRelativePath(host));
4721
+ }
4722
+ function resolveProjectIndexPath(projectRoot, scope, host = "opencode") {
4510
4723
  if (scope === "global") {
4511
- return getGlobalIndexPath();
4724
+ return resolveGlobalIndexPath(host);
4512
4725
  }
4513
- const localIndexPath = path8.join(projectRoot, PROJECT_INDEX_RELATIVE_PATH);
4726
+ const localIndexPath = path8.join(projectRoot, getProjectIndexRelativePath(host));
4514
4727
  if ((0, import_fs6.existsSync)(localIndexPath)) {
4515
4728
  return localIndexPath;
4516
4729
  }
4517
- if (hasProjectConfig(projectRoot)) {
4730
+ if (host !== "opencode") {
4731
+ const legacyIndexPath = path8.join(projectRoot, OPENCODE_PROJECT_INDEX_RELATIVE_PATH);
4732
+ if ((0, import_fs6.existsSync)(legacyIndexPath) && !hasHostProjectConfig(projectRoot, host)) {
4733
+ return legacyIndexPath;
4734
+ }
4735
+ }
4736
+ if (hasHostProjectConfig(projectRoot, host)) {
4518
4737
  return localIndexPath;
4519
4738
  }
4520
- return resolveWorktreeFallbackPath(projectRoot, PROJECT_INDEX_RELATIVE_PATH) ?? localIndexPath;
4739
+ const hostFallback = resolveWorktreeFallbackPath(projectRoot, getProjectIndexRelativePath(host));
4740
+ if (hostFallback) {
4741
+ return hostFallback;
4742
+ }
4743
+ if (host !== "opencode") {
4744
+ const legacyFallback = resolveWorktreeFallbackPath(projectRoot, OPENCODE_PROJECT_INDEX_RELATIVE_PATH);
4745
+ if (legacyFallback) {
4746
+ return legacyFallback;
4747
+ }
4748
+ }
4749
+ return localIndexPath;
4521
4750
  }
4522
4751
 
4523
4752
  // src/tools/changed-files.ts
@@ -4610,8 +4839,8 @@ function normalizeFiles(rawFiles, projectRoot) {
4610
4839
  const trimmed = raw.trim();
4611
4840
  if (!trimmed) continue;
4612
4841
  const absolute = path9.resolve(projectRoot, trimmed);
4613
- const relative5 = path9.relative(projectRoot, absolute);
4614
- const cleaned = relative5.startsWith("./") ? relative5.slice(2) : relative5;
4842
+ const relative10 = path9.relative(projectRoot, absolute);
4843
+ const cleaned = relative10.startsWith("./") ? relative10.slice(2) : relative10;
4615
4844
  if (!seen.has(cleaned)) {
4616
4845
  seen.add(cleaned);
4617
4846
  result.push(cleaned);
@@ -5889,6 +6118,7 @@ function unionCandidates(semanticCandidates, keywordCandidates) {
5889
6118
  return Array.from(byId.values());
5890
6119
  }
5891
6120
  var Indexer = class {
6121
+ host;
5892
6122
  config;
5893
6123
  projectRoot;
5894
6124
  indexPath;
@@ -5910,9 +6140,10 @@ var Indexer = class {
5910
6140
  querySimilarityThreshold = 0.85;
5911
6141
  indexCompatibility = null;
5912
6142
  indexingLockPath = "";
5913
- constructor(projectRoot, config) {
6143
+ constructor(projectRoot, config, host = "opencode") {
5914
6144
  this.projectRoot = projectRoot;
5915
6145
  this.config = config;
6146
+ this.host = host;
5916
6147
  this.indexPath = this.getIndexPath();
5917
6148
  this.fileHashCachePath = path10.join(this.indexPath, "file-hashes.json");
5918
6149
  this.failedBatchesPath = path10.join(this.indexPath, "failed-batches.json");
@@ -5920,7 +6151,7 @@ var Indexer = class {
5920
6151
  this.logger = initializeLogger(config.debug);
5921
6152
  }
5922
6153
  getIndexPath() {
5923
- return resolveProjectIndexPath(this.projectRoot, this.config.scope);
6154
+ return resolveProjectIndexPath(this.projectRoot, this.config.scope, this.host);
5924
6155
  }
5925
6156
  loadFileHashCache() {
5926
6157
  if (!(0, import_fs7.existsSync)(this.fileHashCachePath)) {
@@ -7358,7 +7589,7 @@ var Indexer = class {
7358
7589
  for (const requestBatch of requestBatches) {
7359
7590
  queue.add(async () => {
7360
7591
  if (rateLimitBackoffMs > 0) {
7361
- await new Promise((resolve11) => setTimeout(resolve11, rateLimitBackoffMs));
7592
+ await new Promise((resolve15) => setTimeout(resolve15, rateLimitBackoffMs));
7362
7593
  }
7363
7594
  try {
7364
7595
  const result = await pRetry(
@@ -7919,8 +8150,14 @@ var Indexer = class {
7919
8150
  this.indexCompatibility = compatibility;
7920
8151
  return;
7921
8152
  }
7922
- const localProjectIndexPath = path10.join(this.projectRoot, ".opencode", "index");
7923
- if (path10.resolve(this.indexPath) !== path10.resolve(localProjectIndexPath)) {
8153
+ const localProjectIndexPaths = [path10.join(this.projectRoot, getHostProjectIndexRelativePath(this.host))];
8154
+ if (this.host !== "opencode") {
8155
+ localProjectIndexPaths.push(path10.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
8156
+ }
8157
+ const isLocalProjectIndex = localProjectIndexPaths.some(
8158
+ (localPath) => path10.resolve(this.indexPath) === path10.resolve(localPath)
8159
+ );
8160
+ if (!isLocalProjectIndex) {
7924
8161
  throw new Error(
7925
8162
  "Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
7926
8163
  );
@@ -8365,9 +8602,9 @@ var Indexer = class {
8365
8602
  const { database } = await this.ensureInitialized();
8366
8603
  let shortest = [];
8367
8604
  for (const branchKey of this.getBranchCatalogKeys()) {
8368
- const path19 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
8369
- if (path19.length > 0 && (shortest.length === 0 || path19.length < shortest.length)) {
8370
- shortest = path19;
8605
+ const path25 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
8606
+ if (path25.length > 0 && (shortest.length === 0 || path25.length < shortest.length)) {
8607
+ shortest = path25;
8371
8608
  }
8372
8609
  }
8373
8610
  return shortest;
@@ -8553,6 +8790,46 @@ var Indexer = class {
8553
8790
  conflictingPRs
8554
8791
  };
8555
8792
  }
8793
+ async getVisualizationData(options) {
8794
+ const { database, store } = await this.ensureInitialized();
8795
+ const seenSymbols = /* @__PURE__ */ new Map();
8796
+ const seenEdges = /* @__PURE__ */ new Map();
8797
+ for (const branchKey of this.getBranchCatalogKeys()) {
8798
+ const symbolIds = database.getBranchSymbolIds(branchKey);
8799
+ const symbolIdSet = new Set(symbolIds);
8800
+ const chunkIds = database.getBranchChunkIds(branchKey);
8801
+ const metadataMap = chunkIds.length > 0 ? store.getMetadataBatch(chunkIds) : /* @__PURE__ */ new Map();
8802
+ const filePaths = /* @__PURE__ */ new Set();
8803
+ for (const [, meta] of metadataMap) {
8804
+ if (meta.filePath) filePaths.add(meta.filePath);
8805
+ }
8806
+ const directory = options?.directory?.replace(/\/$/, "");
8807
+ const absoluteDirectoryFilter = directory ? path10.resolve(this.projectRoot, directory) : void 0;
8808
+ for (const filePath of filePaths) {
8809
+ if (directory) {
8810
+ const absoluteFilePath = path10.resolve(filePath);
8811
+ const matchesRelative = filePath === directory || filePath.startsWith(directory + "/");
8812
+ const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path10.sep));
8813
+ if (!matchesRelative && !matchesProjectRelative) {
8814
+ continue;
8815
+ }
8816
+ }
8817
+ for (const sym of database.getSymbolsByFile(filePath)) {
8818
+ if (symbolIdSet.has(sym.id) && !seenSymbols.has(sym.id)) {
8819
+ seenSymbols.set(sym.id, sym);
8820
+ }
8821
+ }
8822
+ }
8823
+ for (const symbolId of seenSymbols.keys()) {
8824
+ for (const edge of database.getCallees(symbolId, branchKey)) {
8825
+ if (!seenEdges.has(edge.id)) {
8826
+ seenEdges.set(edge.id, edge);
8827
+ }
8828
+ }
8829
+ }
8830
+ }
8831
+ return { symbols: [...seenSymbols.values()], edges: [...seenEdges.values()] };
8832
+ }
8556
8833
  async close() {
8557
8834
  await this.database?.close();
8558
8835
  this.database = null;
@@ -9025,23 +9302,23 @@ function isRecord2(value) {
9025
9302
  function isStringArray3(value) {
9026
9303
  return Array.isArray(value) && value.every((item) => typeof item === "string");
9027
9304
  }
9028
- function asPositiveNumber(value, path19) {
9305
+ function asPositiveNumber(value, path25) {
9029
9306
  if (typeof value !== "number" || Number.isNaN(value) || value < 0) {
9030
- throw new Error(`${path19} must be a non-negative number`);
9307
+ throw new Error(`${path25} must be a non-negative number`);
9031
9308
  }
9032
9309
  return value;
9033
9310
  }
9034
- function parseQueryType(value, path19) {
9311
+ function parseQueryType(value, path25) {
9035
9312
  if (value === "definition" || value === "implementation-intent" || value === "similarity" || value === "keyword-heavy") {
9036
9313
  return value;
9037
9314
  }
9038
9315
  throw new Error(
9039
- `${path19} must be one of: definition, implementation-intent, similarity, keyword-heavy`
9316
+ `${path25} must be one of: definition, implementation-intent, similarity, keyword-heavy`
9040
9317
  );
9041
9318
  }
9042
- function parseExpected(input, path19) {
9319
+ function parseExpected(input, path25) {
9043
9320
  if (!isRecord2(input)) {
9044
- throw new Error(`${path19} must be an object`);
9321
+ throw new Error(`${path25} must be an object`);
9045
9322
  }
9046
9323
  const filePathRaw = input.filePath;
9047
9324
  const acceptableFilesRaw = input.acceptableFiles;
@@ -9050,16 +9327,16 @@ function parseExpected(input, path19) {
9050
9327
  const filePath = typeof filePathRaw === "string" ? filePathRaw : void 0;
9051
9328
  const acceptableFiles = isStringArray3(acceptableFilesRaw) ? acceptableFilesRaw : void 0;
9052
9329
  if (!filePath && (!acceptableFiles || acceptableFiles.length === 0)) {
9053
- throw new Error(`${path19} must include either expected.filePath or expected.acceptableFiles`);
9330
+ throw new Error(`${path25} must include either expected.filePath or expected.acceptableFiles`);
9054
9331
  }
9055
9332
  if (acceptableFilesRaw !== void 0 && !isStringArray3(acceptableFilesRaw)) {
9056
- throw new Error(`${path19}.acceptableFiles must be an array of strings`);
9333
+ throw new Error(`${path25}.acceptableFiles must be an array of strings`);
9057
9334
  }
9058
9335
  if (symbolRaw !== void 0 && typeof symbolRaw !== "string") {
9059
- throw new Error(`${path19}.symbol must be a string when provided`);
9336
+ throw new Error(`${path25}.symbol must be a string when provided`);
9060
9337
  }
9061
9338
  if (branchRaw !== void 0 && typeof branchRaw !== "string") {
9062
- throw new Error(`${path19}.branch must be a string when provided`);
9339
+ throw new Error(`${path25}.branch must be a string when provided`);
9063
9340
  }
9064
9341
  return {
9065
9342
  filePath,
@@ -9069,25 +9346,25 @@ function parseExpected(input, path19) {
9069
9346
  };
9070
9347
  }
9071
9348
  function parseQuery(input, index) {
9072
- const path19 = `queries[${index}]`;
9349
+ const path25 = `queries[${index}]`;
9073
9350
  if (!isRecord2(input)) {
9074
- throw new Error(`${path19} must be an object`);
9351
+ throw new Error(`${path25} must be an object`);
9075
9352
  }
9076
9353
  const id = input.id;
9077
9354
  const query = input.query;
9078
9355
  const queryType = input.queryType;
9079
9356
  const expected = input.expected;
9080
9357
  if (typeof id !== "string" || id.trim().length === 0) {
9081
- throw new Error(`${path19}.id must be a non-empty string`);
9358
+ throw new Error(`${path25}.id must be a non-empty string`);
9082
9359
  }
9083
9360
  if (typeof query !== "string" || query.trim().length === 0) {
9084
- throw new Error(`${path19}.query must be a non-empty string`);
9361
+ throw new Error(`${path25}.query must be a non-empty string`);
9085
9362
  }
9086
9363
  return {
9087
9364
  id,
9088
9365
  query,
9089
- queryType: parseQueryType(queryType, `${path19}.queryType`),
9090
- expected: parseExpected(expected, `${path19}.expected`)
9366
+ queryType: parseQueryType(queryType, `${path25}.queryType`),
9367
+ expected: parseExpected(expected, `${path25}.expected`)
9091
9368
  };
9092
9369
  }
9093
9370
  function parseGoldenDataset(raw, sourceLabel) {
@@ -9670,8 +9947,7 @@ async function handleEvalCommand(args, cwd) {
9670
9947
 
9671
9948
  // src/mcp-server.ts
9672
9949
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
9673
- var path17 = __toESM(require("path"), 1);
9674
- var import_fs12 = require("fs");
9950
+ var import_fs14 = require("fs");
9675
9951
 
9676
9952
  // src/mcp-server/register-prompts.ts
9677
9953
  var import_zod = require("zod");
@@ -9766,168 +10042,6 @@ Use the implementation_lookup tool to find where this symbol is defined. This pr
9766
10042
  // src/mcp-server/register-tools.ts
9767
10043
  var import_zod2 = require("zod");
9768
10044
 
9769
- // src/config/merger.ts
9770
- var import_fs11 = require("fs");
9771
- var os5 = __toESM(require("os"), 1);
9772
- var path16 = __toESM(require("path"), 1);
9773
- var PROJECT_OVERRIDE_KEYS = [
9774
- "embeddingProvider",
9775
- "customProvider",
9776
- "embeddingModel",
9777
- "reranker",
9778
- "include",
9779
- "exclude",
9780
- "indexing",
9781
- "search",
9782
- "debug",
9783
- "scope"
9784
- ];
9785
- var MERGE_ARRAY_KEYS = ["knowledgeBases", "additionalInclude"];
9786
- function isRecord3(value) {
9787
- return typeof value === "object" && value !== null && !Array.isArray(value);
9788
- }
9789
- function isStringArray4(value) {
9790
- return Array.isArray(value) && value.every((item) => typeof item === "string");
9791
- }
9792
- function applyProjectOverride(merged, normalizedProjectConfig, globalConfig, key) {
9793
- if (key in normalizedProjectConfig) {
9794
- merged[key] = normalizedProjectConfig[key];
9795
- return;
9796
- }
9797
- if (key in globalConfig) {
9798
- merged[key] = globalConfig[key];
9799
- }
9800
- }
9801
- function mergeUniqueStringArray(values) {
9802
- return [...new Set(values.map((value) => String(value).trim()))];
9803
- }
9804
- function normalizeKnowledgeBasePath(value) {
9805
- let normalized = path16.normalize(String(value).trim());
9806
- const root = path16.parse(normalized).root;
9807
- while (normalized.length > root.length && /[\\/]$/.test(normalized)) {
9808
- normalized = normalized.slice(0, -1);
9809
- }
9810
- return normalized;
9811
- }
9812
- function mergeKnowledgeBasePaths(values) {
9813
- return [...new Set(values.map((value) => normalizeKnowledgeBasePath(value)).filter((value) => value.length > 0))];
9814
- }
9815
- function validateConfigLayerShape(rawConfig, filePath) {
9816
- if (!isRecord3(rawConfig)) {
9817
- throw new Error(`Config file ${filePath} must contain a JSON object at the root.`);
9818
- }
9819
- if (rawConfig.knowledgeBases !== void 0 && !isStringArray4(rawConfig.knowledgeBases)) {
9820
- throw new Error(`Config file ${filePath} field 'knowledgeBases' must be an array of strings.`);
9821
- }
9822
- if (rawConfig.additionalInclude !== void 0 && !isStringArray4(rawConfig.additionalInclude)) {
9823
- throw new Error(`Config file ${filePath} field 'additionalInclude' must be an array of strings.`);
9824
- }
9825
- if (rawConfig.include !== void 0 && !isStringArray4(rawConfig.include)) {
9826
- throw new Error(`Config file ${filePath} field 'include' must be an array of strings.`);
9827
- }
9828
- if (rawConfig.exclude !== void 0 && !isStringArray4(rawConfig.exclude)) {
9829
- throw new Error(`Config file ${filePath} field 'exclude' must be an array of strings.`);
9830
- }
9831
- for (const section of ["customProvider", "indexing", "search", "debug", "reranker"]) {
9832
- const value = rawConfig[section];
9833
- if (value !== void 0 && !isRecord3(value)) {
9834
- throw new Error(`Config file ${filePath} field '${section}' must be an object.`);
9835
- }
9836
- }
9837
- return rawConfig;
9838
- }
9839
- function loadJsonFile(filePath) {
9840
- if (!(0, import_fs11.existsSync)(filePath)) {
9841
- return null;
9842
- }
9843
- try {
9844
- const content = (0, import_fs11.readFileSync)(filePath, "utf-8");
9845
- return validateConfigLayerShape(JSON.parse(content), filePath);
9846
- } catch (error) {
9847
- if (error instanceof Error && error.message.startsWith("Config file ")) {
9848
- throw error;
9849
- }
9850
- const message = error instanceof Error ? error.message : String(error);
9851
- throw new Error(`Failed to load config file ${filePath}: ${message}`);
9852
- }
9853
- return null;
9854
- }
9855
- function materializeLocalProjectConfig(projectRoot, config) {
9856
- const localConfigPath = path16.join(projectRoot, ".opencode", "codebase-index.json");
9857
- (0, import_fs11.mkdirSync)(path16.dirname(localConfigPath), { recursive: true });
9858
- (0, import_fs11.writeFileSync)(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
9859
- return localConfigPath;
9860
- }
9861
- function loadProjectConfigLayer(projectRoot) {
9862
- const projectConfigPath = resolveProjectConfigPath(projectRoot);
9863
- const projectConfig = loadJsonFile(projectConfigPath);
9864
- if (!projectConfig) {
9865
- return {};
9866
- }
9867
- const normalizedConfig = { ...projectConfig };
9868
- const projectConfigBaseDir = path16.dirname(path16.dirname(projectConfigPath));
9869
- if (Array.isArray(normalizedConfig.knowledgeBases)) {
9870
- normalizedConfig.knowledgeBases = resolveInheritedKnowledgeBaseEntries(
9871
- normalizedConfig.knowledgeBases,
9872
- projectConfigBaseDir,
9873
- projectRoot
9874
- );
9875
- }
9876
- return normalizedConfig;
9877
- }
9878
- function loadMergedConfig(projectRoot) {
9879
- const globalConfigPath = os5.homedir() + "/.config/opencode/codebase-index.json";
9880
- const projectConfigPath = resolveProjectConfigPath(projectRoot);
9881
- let globalConfig = null;
9882
- let globalConfigError = null;
9883
- try {
9884
- globalConfig = loadJsonFile(globalConfigPath);
9885
- } catch (error) {
9886
- globalConfigError = error instanceof Error ? error : new Error(String(error));
9887
- }
9888
- const projectConfig = loadJsonFile(projectConfigPath);
9889
- const normalizedProjectConfig = loadProjectConfigLayer(projectRoot);
9890
- if (globalConfigError) {
9891
- if (!projectConfig) {
9892
- throw globalConfigError;
9893
- }
9894
- globalConfig = null;
9895
- }
9896
- if (!globalConfig && !projectConfig) {
9897
- return {};
9898
- }
9899
- if (!projectConfig && globalConfig) {
9900
- return globalConfig;
9901
- }
9902
- if (!globalConfig && projectConfig) {
9903
- return normalizedProjectConfig;
9904
- }
9905
- if (!globalConfig || !projectConfig) {
9906
- return globalConfig ?? normalizedProjectConfig;
9907
- }
9908
- const merged = { ...globalConfig };
9909
- for (const key of PROJECT_OVERRIDE_KEYS) {
9910
- applyProjectOverride(merged, normalizedProjectConfig, globalConfig, key);
9911
- }
9912
- if (projectConfig) {
9913
- for (const key of Object.keys(projectConfig)) {
9914
- if (PROJECT_OVERRIDE_KEYS.includes(key) || MERGE_ARRAY_KEYS.includes(key)) {
9915
- continue;
9916
- }
9917
- merged[key] = normalizedProjectConfig[key];
9918
- }
9919
- }
9920
- const globalKbs = globalConfig && Array.isArray(globalConfig.knowledgeBases) ? globalConfig.knowledgeBases : [];
9921
- const projectKbs = projectConfig ? Array.isArray(normalizedProjectConfig.knowledgeBases) ? normalizedProjectConfig.knowledgeBases : [] : [];
9922
- const allKbs = [...globalKbs, ...projectKbs];
9923
- merged.knowledgeBases = mergeKnowledgeBasePaths(allKbs);
9924
- const globalAdditional = globalConfig && Array.isArray(globalConfig.additionalInclude) ? globalConfig.additionalInclude : [];
9925
- const projectAdditional = projectConfig && Array.isArray(projectConfig.additionalInclude) ? projectConfig.additionalInclude : [];
9926
- const allAdditional = [...globalAdditional, ...projectAdditional];
9927
- merged.additionalInclude = mergeUniqueStringArray(allAdditional);
9928
- return merged;
9929
- }
9930
-
9931
10045
  // src/tools/utils.ts
9932
10046
  var MAX_CONTENT_LINES = 30;
9933
10047
  function truncateContent(content) {
@@ -10039,6 +10153,36 @@ function formatStatus(status) {
10039
10153
  }
10040
10154
  return lines.join("\n");
10041
10155
  }
10156
+ function formatProgressTitle(progress) {
10157
+ switch (progress.phase) {
10158
+ case "scanning":
10159
+ return "Scanning files...";
10160
+ case "parsing":
10161
+ return `Parsing: ${progress.filesProcessed}/${progress.totalFiles} files`;
10162
+ case "embedding":
10163
+ return `Embedding: ${progress.chunksProcessed}/${progress.totalChunks} chunks`;
10164
+ case "storing":
10165
+ return "Storing index...";
10166
+ case "complete":
10167
+ return "Indexing complete";
10168
+ default:
10169
+ return "Indexing...";
10170
+ }
10171
+ }
10172
+ function calculatePercentage(progress) {
10173
+ if (progress.phase === "scanning") return 0;
10174
+ if (progress.phase === "complete") return 100;
10175
+ if (progress.phase === "parsing") {
10176
+ if (progress.totalFiles === 0) return 5;
10177
+ return Math.round(5 + progress.filesProcessed / progress.totalFiles * 15);
10178
+ }
10179
+ if (progress.phase === "embedding") {
10180
+ if (progress.totalChunks === 0) return 20;
10181
+ return Math.round(20 + progress.chunksProcessed / progress.totalChunks * 70);
10182
+ }
10183
+ if (progress.phase === "storing") return 95;
10184
+ return 0;
10185
+ }
10042
10186
  function formatHealthCheck(result) {
10043
10187
  if (result.resetCorruptedIndex) {
10044
10188
  return result.warning ?? "Detected a corrupted local index and reset it. Run index_codebase to rebuild search data.";
@@ -10135,17 +10279,421 @@ function formatPrImpact(result) {
10135
10279
  return lines.join("\n");
10136
10280
  }
10137
10281
 
10138
- // src/mcp-server/shared.ts
10139
- var MAX_CONTENT_LINES2 = 30;
10140
- function truncateContent2(content) {
10141
- const lines = content.split("\n");
10142
- if (lines.length <= MAX_CONTENT_LINES2) return content;
10143
- return lines.slice(0, MAX_CONTENT_LINES2).join("\n") + `
10144
- // ... (${lines.length - MAX_CONTENT_LINES2} more lines)`;
10145
- }
10146
- var CHUNK_TYPE_ENUM = [
10147
- "function",
10148
- "class",
10282
+ // src/tools/operations.ts
10283
+ var import_fs13 = require("fs");
10284
+ var path19 = __toESM(require("path"), 1);
10285
+
10286
+ // src/config/merger.ts
10287
+ var import_fs11 = require("fs");
10288
+ var path16 = __toESM(require("path"), 1);
10289
+ var PROJECT_OVERRIDE_KEYS = [
10290
+ "embeddingProvider",
10291
+ "customProvider",
10292
+ "embeddingModel",
10293
+ "reranker",
10294
+ "include",
10295
+ "exclude",
10296
+ "indexing",
10297
+ "search",
10298
+ "debug",
10299
+ "scope"
10300
+ ];
10301
+ var MERGE_ARRAY_KEYS = ["knowledgeBases", "additionalInclude"];
10302
+ function isRecord3(value) {
10303
+ return typeof value === "object" && value !== null && !Array.isArray(value);
10304
+ }
10305
+ function isStringArray4(value) {
10306
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
10307
+ }
10308
+ function applyProjectOverride(merged, normalizedProjectConfig, globalConfig, key) {
10309
+ if (key in normalizedProjectConfig) {
10310
+ merged[key] = normalizedProjectConfig[key];
10311
+ return;
10312
+ }
10313
+ if (key in globalConfig) {
10314
+ merged[key] = globalConfig[key];
10315
+ }
10316
+ }
10317
+ function mergeUniqueStringArray(values) {
10318
+ return [...new Set(values.map((value) => String(value).trim()))];
10319
+ }
10320
+ function normalizeKnowledgeBasePath(value) {
10321
+ let normalized = path16.normalize(String(value).trim());
10322
+ const root = path16.parse(normalized).root;
10323
+ while (normalized.length > root.length && /[\\/]$/.test(normalized)) {
10324
+ normalized = normalized.slice(0, -1);
10325
+ }
10326
+ return normalized;
10327
+ }
10328
+ function mergeKnowledgeBasePaths(values) {
10329
+ return [...new Set(values.map((value) => normalizeKnowledgeBasePath(value)).filter((value) => value.length > 0))];
10330
+ }
10331
+ function validateConfigLayerShape(rawConfig, filePath) {
10332
+ if (!isRecord3(rawConfig)) {
10333
+ throw new Error(`Config file ${filePath} must contain a JSON object at the root.`);
10334
+ }
10335
+ if (rawConfig.knowledgeBases !== void 0 && !isStringArray4(rawConfig.knowledgeBases)) {
10336
+ throw new Error(`Config file ${filePath} field 'knowledgeBases' must be an array of strings.`);
10337
+ }
10338
+ if (rawConfig.additionalInclude !== void 0 && !isStringArray4(rawConfig.additionalInclude)) {
10339
+ throw new Error(`Config file ${filePath} field 'additionalInclude' must be an array of strings.`);
10340
+ }
10341
+ if (rawConfig.include !== void 0 && !isStringArray4(rawConfig.include)) {
10342
+ throw new Error(`Config file ${filePath} field 'include' must be an array of strings.`);
10343
+ }
10344
+ if (rawConfig.exclude !== void 0 && !isStringArray4(rawConfig.exclude)) {
10345
+ throw new Error(`Config file ${filePath} field 'exclude' must be an array of strings.`);
10346
+ }
10347
+ for (const section of ["customProvider", "indexing", "search", "debug", "reranker"]) {
10348
+ const value = rawConfig[section];
10349
+ if (value !== void 0 && !isRecord3(value)) {
10350
+ throw new Error(`Config file ${filePath} field '${section}' must be an object.`);
10351
+ }
10352
+ }
10353
+ return rawConfig;
10354
+ }
10355
+ function loadJsonFile(filePath) {
10356
+ if (!(0, import_fs11.existsSync)(filePath)) {
10357
+ return null;
10358
+ }
10359
+ try {
10360
+ const content = (0, import_fs11.readFileSync)(filePath, "utf-8");
10361
+ return validateConfigLayerShape(JSON.parse(content), filePath);
10362
+ } catch (error) {
10363
+ if (error instanceof Error && error.message.startsWith("Config file ")) {
10364
+ throw error;
10365
+ }
10366
+ const message = error instanceof Error ? error.message : String(error);
10367
+ throw new Error(`Failed to load config file ${filePath}: ${message}`);
10368
+ }
10369
+ }
10370
+ function loadConfigFile(filePath) {
10371
+ return loadJsonFile(filePath);
10372
+ }
10373
+ function materializeLocalProjectConfig(projectRoot, config, host = "opencode") {
10374
+ const localConfigPath = resolveWritableProjectConfigPath(projectRoot, host);
10375
+ (0, import_fs11.mkdirSync)(path16.dirname(localConfigPath), { recursive: true });
10376
+ (0, import_fs11.writeFileSync)(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
10377
+ return localConfigPath;
10378
+ }
10379
+ function loadProjectConfigLayer(projectRoot, host = "opencode") {
10380
+ const projectConfigPath = resolveProjectConfigPath(projectRoot, host);
10381
+ const projectConfig = loadJsonFile(projectConfigPath);
10382
+ if (!projectConfig) {
10383
+ return {};
10384
+ }
10385
+ const normalizedConfig = { ...projectConfig };
10386
+ const projectConfigBaseDir = path16.dirname(path16.dirname(projectConfigPath));
10387
+ if (Array.isArray(normalizedConfig.knowledgeBases)) {
10388
+ normalizedConfig.knowledgeBases = resolveInheritedKnowledgeBaseEntries(
10389
+ normalizedConfig.knowledgeBases,
10390
+ projectConfigBaseDir,
10391
+ projectRoot
10392
+ );
10393
+ }
10394
+ return normalizedConfig;
10395
+ }
10396
+ function loadMergedConfig(projectRoot, host = "opencode") {
10397
+ const globalConfigPath = resolveGlobalConfigPath(host);
10398
+ const projectConfigPath = resolveProjectConfigPath(projectRoot, host);
10399
+ let globalConfig = null;
10400
+ let globalConfigError = null;
10401
+ try {
10402
+ globalConfig = loadJsonFile(globalConfigPath);
10403
+ } catch (error) {
10404
+ globalConfigError = error instanceof Error ? error : new Error(String(error));
10405
+ }
10406
+ const projectConfig = loadJsonFile(projectConfigPath);
10407
+ const normalizedProjectConfig = loadProjectConfigLayer(projectRoot, host);
10408
+ if (globalConfigError) {
10409
+ if (!projectConfig) {
10410
+ throw globalConfigError;
10411
+ }
10412
+ globalConfig = null;
10413
+ }
10414
+ if (!globalConfig && !projectConfig) {
10415
+ return {};
10416
+ }
10417
+ if (!projectConfig && globalConfig) {
10418
+ return globalConfig;
10419
+ }
10420
+ if (!globalConfig && projectConfig) {
10421
+ return normalizedProjectConfig;
10422
+ }
10423
+ if (!globalConfig || !projectConfig) {
10424
+ return globalConfig ?? normalizedProjectConfig;
10425
+ }
10426
+ const merged = { ...globalConfig };
10427
+ for (const key of PROJECT_OVERRIDE_KEYS) {
10428
+ applyProjectOverride(merged, normalizedProjectConfig, globalConfig, key);
10429
+ }
10430
+ if (projectConfig) {
10431
+ for (const key of Object.keys(projectConfig)) {
10432
+ if (PROJECT_OVERRIDE_KEYS.includes(key) || MERGE_ARRAY_KEYS.includes(key)) {
10433
+ continue;
10434
+ }
10435
+ merged[key] = normalizedProjectConfig[key];
10436
+ }
10437
+ }
10438
+ const globalKbs = globalConfig && Array.isArray(globalConfig.knowledgeBases) ? globalConfig.knowledgeBases : [];
10439
+ const projectKbs = projectConfig ? Array.isArray(normalizedProjectConfig.knowledgeBases) ? normalizedProjectConfig.knowledgeBases : [] : [];
10440
+ const allKbs = [...globalKbs, ...projectKbs];
10441
+ merged.knowledgeBases = mergeKnowledgeBasePaths(allKbs);
10442
+ const globalAdditional = globalConfig && Array.isArray(globalConfig.additionalInclude) ? globalConfig.additionalInclude : [];
10443
+ const projectAdditional = projectConfig && Array.isArray(projectConfig.additionalInclude) ? projectConfig.additionalInclude : [];
10444
+ const allAdditional = [...globalAdditional, ...projectAdditional];
10445
+ merged.additionalInclude = mergeUniqueStringArray(allAdditional);
10446
+ return merged;
10447
+ }
10448
+
10449
+ // src/tools/knowledge-base-paths.ts
10450
+ var path17 = __toESM(require("path"), 1);
10451
+ function resolveConfigPathValue(value, baseDir) {
10452
+ const trimmed = value.trim();
10453
+ if (!trimmed) {
10454
+ return trimmed;
10455
+ }
10456
+ const absolutePath = path17.isAbsolute(trimmed) ? trimmed : path17.resolve(baseDir, trimmed);
10457
+ return path17.normalize(absolutePath);
10458
+ }
10459
+
10460
+ // src/tools/config-state.ts
10461
+ var import_fs12 = require("fs");
10462
+ var path18 = __toESM(require("path"), 1);
10463
+ function normalizeKnowledgeBasePaths(config, projectRoot) {
10464
+ const normalized = { ...config };
10465
+ if (Array.isArray(normalized.knowledgeBases)) {
10466
+ normalized.knowledgeBases = normalized.knowledgeBases.map(
10467
+ (kb) => resolveConfigPathValue(kb, projectRoot)
10468
+ );
10469
+ }
10470
+ return normalized;
10471
+ }
10472
+ function toConfigRecord(rawConfig) {
10473
+ if (!rawConfig || typeof rawConfig !== "object") {
10474
+ return {};
10475
+ }
10476
+ return { ...rawConfig };
10477
+ }
10478
+ function loadRuntimeConfig(projectRoot, host = "opencode") {
10479
+ return normalizeKnowledgeBasePaths(toConfigRecord(loadMergedConfig(projectRoot, host)), projectRoot);
10480
+ }
10481
+
10482
+ // src/tools/operations.ts
10483
+ var indexerCache = /* @__PURE__ */ new Map();
10484
+ var configCache = /* @__PURE__ */ new Map();
10485
+ var defaultProjectRoots = /* @__PURE__ */ new Map();
10486
+ function getProjectRoot(projectRoot, host) {
10487
+ if (projectRoot) {
10488
+ return projectRoot;
10489
+ }
10490
+ const root = defaultProjectRoots.get(host);
10491
+ if (!root) {
10492
+ throw new Error("Codebase index tools not initialized. Plugin may not be loaded correctly.");
10493
+ }
10494
+ return root;
10495
+ }
10496
+ function getIndexerCacheKey(projectRoot, host) {
10497
+ return `${host}::${projectRoot}`;
10498
+ }
10499
+ function getOrCreateIndexer(projectRoot, host) {
10500
+ const key = getIndexerCacheKey(projectRoot, host);
10501
+ const cached = indexerCache.get(key);
10502
+ if (cached) {
10503
+ return cached;
10504
+ }
10505
+ const config = parseConfig(loadRuntimeConfig(projectRoot, host));
10506
+ const indexer = new Indexer(projectRoot, config, host);
10507
+ indexerCache.set(key, indexer);
10508
+ configCache.set(key, config);
10509
+ return indexer;
10510
+ }
10511
+ function initializeTools(projectRoot, config, host = "opencode") {
10512
+ defaultProjectRoots.set(host, projectRoot);
10513
+ const key = getIndexerCacheKey(projectRoot, host);
10514
+ const indexer = new Indexer(projectRoot, config, host);
10515
+ indexerCache.set(key, indexer);
10516
+ configCache.set(key, config);
10517
+ }
10518
+ function getIndexerForProject(projectRoot, host = "opencode") {
10519
+ const root = getProjectRoot(projectRoot, host);
10520
+ return getOrCreateIndexer(root, host);
10521
+ }
10522
+ function refreshIndexerForDirectory(projectRoot, host = "opencode", config = parseConfig(loadRuntimeConfig(projectRoot, host))) {
10523
+ const key = getIndexerCacheKey(projectRoot, host);
10524
+ const indexer = new Indexer(projectRoot, config, host);
10525
+ indexerCache.set(key, indexer);
10526
+ configCache.set(key, config);
10527
+ }
10528
+ function shouldForceLocalizeProjectIndex(projectRoot, host = "opencode") {
10529
+ const root = getProjectRoot(projectRoot, host);
10530
+ const localIndexPath = path19.join(root, getHostProjectIndexRelativePath(host));
10531
+ if ((0, import_fs13.existsSync)(localIndexPath)) {
10532
+ return false;
10533
+ }
10534
+ const inheritedIndexPath = resolveWorktreeFallbackProjectIndexPath(root, host);
10535
+ return inheritedIndexPath !== null;
10536
+ }
10537
+ async function searchCodebase(projectRoot, host, query, options = {}) {
10538
+ const indexer = getIndexerForProject(projectRoot, host);
10539
+ return indexer.search(query, options.limit, {
10540
+ fileType: options.fileType,
10541
+ directory: options.directory,
10542
+ chunkType: options.chunkType,
10543
+ contextLines: options.contextLines,
10544
+ metadataOnly: options.metadataOnly,
10545
+ definitionIntent: options.definitionIntent
10546
+ });
10547
+ }
10548
+ async function findSimilarCode(projectRoot, host, code, options = {}) {
10549
+ const indexer = getIndexerForProject(projectRoot, host);
10550
+ return indexer.findSimilar(code, options.limit, {
10551
+ fileType: options.fileType,
10552
+ directory: options.directory,
10553
+ chunkType: options.chunkType,
10554
+ excludeFile: options.excludeFile
10555
+ });
10556
+ }
10557
+ async function implementationLookup(projectRoot, host, query, options = {}) {
10558
+ const indexer = getIndexerForProject(projectRoot, host);
10559
+ return indexer.search(query, options.limit, {
10560
+ fileType: options.fileType,
10561
+ directory: options.directory,
10562
+ definitionIntent: true
10563
+ });
10564
+ }
10565
+ async function getCallGraphData(projectRoot, host, params) {
10566
+ const indexer = getIndexerForProject(projectRoot, host);
10567
+ if (params.direction === "callees") {
10568
+ if (!params.symbolId) {
10569
+ return { direction: "callees", callees: [], callers: [] };
10570
+ }
10571
+ const callees = await indexer.getCallees(params.symbolId, params.relationshipType);
10572
+ return { direction: "callees", callees, callers: [] };
10573
+ }
10574
+ const callers = await indexer.getCallers(params.name, params.relationshipType);
10575
+ return { direction: "callers", callers, callees: [] };
10576
+ }
10577
+ async function getCallGraphPath(projectRoot, host, from, to, maxDepth) {
10578
+ const indexer = getIndexerForProject(projectRoot, host);
10579
+ return indexer.findCallPath(from, to, maxDepth);
10580
+ }
10581
+ async function runIndexCodebase(projectRoot, host, args, onProgress) {
10582
+ const root = getProjectRoot(projectRoot, host);
10583
+ const key = getIndexerCacheKey(root, host);
10584
+ const cachedConfig = configCache.get(key);
10585
+ let indexer = getIndexerForProject(root, host);
10586
+ if (args.estimateOnly) {
10587
+ return { kind: "estimate", estimate: await indexer.estimateCost() };
10588
+ }
10589
+ if (args.force) {
10590
+ if (shouldForceLocalizeProjectIndex(root, host)) {
10591
+ materializeLocalProjectConfig(root, loadProjectConfigLayer(root, host), host);
10592
+ refreshIndexerForDirectory(root, host, cachedConfig);
10593
+ indexer = getIndexerForProject(root, host);
10594
+ }
10595
+ await indexer.clearIndex();
10596
+ refreshIndexerForDirectory(root, host, cachedConfig);
10597
+ indexer = getIndexerForProject(root, host);
10598
+ }
10599
+ const stats = await indexer.index((progress) => {
10600
+ if (onProgress) {
10601
+ return onProgress(formatProgressTitle(progress), {
10602
+ phase: progress.phase,
10603
+ filesProcessed: progress.filesProcessed,
10604
+ totalFiles: progress.totalFiles,
10605
+ chunksProcessed: progress.chunksProcessed,
10606
+ totalChunks: progress.totalChunks,
10607
+ percentage: calculatePercentage(progress)
10608
+ });
10609
+ }
10610
+ return Promise.resolve();
10611
+ });
10612
+ return { kind: "stats", stats };
10613
+ }
10614
+ async function getIndexStatus(projectRoot, host) {
10615
+ const indexer = getIndexerForProject(projectRoot, host);
10616
+ return indexer.getStatus();
10617
+ }
10618
+ async function getIndexHealthCheck(projectRoot, host) {
10619
+ const indexer = getIndexerForProject(projectRoot, host);
10620
+ return indexer.healthCheck();
10621
+ }
10622
+ async function getPrImpact(projectRoot, host, params) {
10623
+ const indexer = getIndexerForProject(projectRoot, host);
10624
+ return indexer.getPrImpact({
10625
+ pr: params.pr,
10626
+ branch: params.branch,
10627
+ maxDepth: params.maxDepth,
10628
+ hubThreshold: params.hubThreshold,
10629
+ checkConflicts: params.checkConflicts,
10630
+ direction: params.direction
10631
+ });
10632
+ }
10633
+ async function getIndexMetrics(projectRoot, host) {
10634
+ const indexer = getIndexerForProject(projectRoot, host);
10635
+ const logger = indexer.getLogger();
10636
+ if (!logger.isEnabled()) {
10637
+ return {
10638
+ enabled: false,
10639
+ metricsEnabled: false,
10640
+ 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```'
10641
+ };
10642
+ }
10643
+ if (!logger.isMetricsEnabled()) {
10644
+ return {
10645
+ enabled: true,
10646
+ metricsEnabled: false,
10647
+ 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```'
10648
+ };
10649
+ }
10650
+ return {
10651
+ enabled: true,
10652
+ metricsEnabled: true,
10653
+ text: logger.formatMetrics()
10654
+ };
10655
+ }
10656
+ async function getIndexLogs(projectRoot, host, args) {
10657
+ const indexer = getIndexerForProject(projectRoot, host);
10658
+ const logger = indexer.getLogger();
10659
+ if (!logger.isEnabled()) {
10660
+ return {
10661
+ kind: "disabled",
10662
+ text: 'Debug mode is disabled. Enable it in your config:\n\n```json\n{\n "debug": {\n "enabled": true\n }\n}\n```'
10663
+ };
10664
+ }
10665
+ let logs;
10666
+ if (args.category) {
10667
+ logs = logger.getLogsByCategory(args.category, args.limit);
10668
+ } else if (args.level) {
10669
+ logs = logger.getLogsByLevel(args.level, args.limit);
10670
+ } else {
10671
+ logs = logger.getLogs(args.limit);
10672
+ }
10673
+ if (logs.length === 0) {
10674
+ return {
10675
+ kind: "entries",
10676
+ text: "No logs recorded yet. Logs are captured during indexing and search operations."
10677
+ };
10678
+ }
10679
+ const text = logs.map((entry) => {
10680
+ const dataStr = entry.data ? ` ${JSON.stringify(entry.data)}` : "";
10681
+ return `[${entry.timestamp}] [${entry.level.toUpperCase()}] [${entry.category}] ${entry.message}${dataStr}`;
10682
+ }).join("\n");
10683
+ return { kind: "entries", text };
10684
+ }
10685
+
10686
+ // src/mcp-server/shared.ts
10687
+ var MAX_CONTENT_LINES2 = 30;
10688
+ function truncateContent2(content) {
10689
+ const lines = content.split("\n");
10690
+ if (lines.length <= MAX_CONTENT_LINES2) return content;
10691
+ return lines.slice(0, MAX_CONTENT_LINES2).join("\n") + `
10692
+ // ... (${lines.length - MAX_CONTENT_LINES2} more lines)`;
10693
+ }
10694
+ var CHUNK_TYPE_ENUM = [
10695
+ "function",
10696
+ "class",
10149
10697
  "method",
10150
10698
  "interface",
10151
10699
  "type",
@@ -10171,8 +10719,8 @@ function registerMcpTools(server, runtime) {
10171
10719
  contextLines: import_zod2.z.number().optional().describe("Number of extra lines to include before/after each match (default: 0)")
10172
10720
  },
10173
10721
  async (args) => {
10174
- await runtime.ensureInitialized();
10175
- const results = await runtime.getIndexer().search(args.query, args.limit ?? 5, {
10722
+ const results = await searchCodebase(runtime.projectRoot, runtime.host, args.query, {
10723
+ limit: args.limit ?? 5,
10176
10724
  fileType: args.fileType,
10177
10725
  directory: args.directory,
10178
10726
  chunkType: args.chunkType,
@@ -10204,8 +10752,8 @@ ${formatted.join("\n\n")}` }] };
10204
10752
  chunkType: import_zod2.z.enum(CHUNK_TYPE_ENUM).optional().describe("Filter by code chunk type")
10205
10753
  },
10206
10754
  async (args) => {
10207
- await runtime.ensureInitialized();
10208
- const results = await runtime.getIndexer().search(args.query, args.limit ?? 10, {
10755
+ const results = await searchCodebase(runtime.projectRoot, runtime.host, args.query, {
10756
+ limit: args.limit ?? 10,
10209
10757
  fileType: args.fileType,
10210
10758
  directory: args.directory,
10211
10759
  chunkType: args.chunkType,
@@ -10235,25 +10783,9 @@ Use Read tool to examine specific files.` }] };
10235
10783
  verbose: import_zod2.z.boolean().optional().default(false).describe("Show detailed info about skipped files and parsing failures")
10236
10784
  },
10237
10785
  async (args) => {
10238
- if (args.estimateOnly) {
10239
- await runtime.ensureInitialized();
10240
- const estimate = await runtime.getIndexer().estimateCost();
10241
- return { content: [{ type: "text", text: formatCostEstimate(estimate) }] };
10242
- }
10243
- if (args.force) {
10244
- if (runtime.shouldForceLocalizeProjectIndex()) {
10245
- materializeLocalProjectConfig(runtime.projectRoot, loadProjectConfigLayer(runtime.projectRoot));
10246
- runtime.refreshIndexerFromConfig();
10247
- }
10248
- await runtime.ensureInitialized();
10249
- await runtime.getIndexer().clearIndex();
10250
- runtime.refreshIndexerFromConfig();
10251
- await runtime.ensureInitialized();
10252
- } else {
10253
- await runtime.ensureInitialized();
10254
- }
10255
- const stats = await runtime.getIndexer().index();
10256
- return { content: [{ type: "text", text: formatIndexStats(stats, args.verbose ?? false) }] };
10786
+ const result = await runIndexCodebase(runtime.projectRoot, runtime.host, args);
10787
+ const text = result.kind === "estimate" ? formatCostEstimate(result.estimate) : formatIndexStats(result.stats, args.verbose ?? false);
10788
+ return { content: [{ type: "text", text }] };
10257
10789
  }
10258
10790
  );
10259
10791
  server.tool(
@@ -10261,8 +10793,7 @@ Use Read tool to examine specific files.` }] };
10261
10793
  "Check the status of the codebase index. Shows whether the codebase is indexed, how many chunks are stored, and the embedding provider being used.",
10262
10794
  {},
10263
10795
  async () => {
10264
- await runtime.ensureInitialized();
10265
- const status = await runtime.getIndexer().getStatus();
10796
+ const status = await getIndexStatus(runtime.projectRoot, runtime.host);
10266
10797
  return { content: [{ type: "text", text: formatStatus(status) }] };
10267
10798
  }
10268
10799
  );
@@ -10271,8 +10802,7 @@ Use Read tool to examine specific files.` }] };
10271
10802
  "Check index health and remove stale entries from deleted files. Run this to clean up the index after files have been deleted.",
10272
10803
  {},
10273
10804
  async () => {
10274
- await runtime.ensureInitialized();
10275
- const result = await runtime.getIndexer().healthCheck();
10805
+ const result = await getIndexHealthCheck(runtime.projectRoot, runtime.host);
10276
10806
  return { content: [{ type: "text", text: formatHealthCheck(result) }] };
10277
10807
  }
10278
10808
  );
@@ -10281,15 +10811,8 @@ Use Read tool to examine specific files.` }] };
10281
10811
  "Get metrics and performance statistics for the codebase index. Requires debug.enabled=true and debug.metrics=true in config.",
10282
10812
  {},
10283
10813
  async () => {
10284
- await runtime.ensureInitialized();
10285
- const logger = runtime.getIndexer().getLogger();
10286
- if (!logger.isEnabled()) {
10287
- 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```' }] };
10288
- }
10289
- if (!logger.isMetricsEnabled()) {
10290
- 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```' }] };
10291
- }
10292
- return { content: [{ type: "text", text: logger.formatMetrics() }] };
10814
+ const result = await getIndexMetrics(runtime.projectRoot, runtime.host);
10815
+ return { content: [{ type: "text", text: result.text }] };
10293
10816
  }
10294
10817
  );
10295
10818
  server.tool(
@@ -10301,27 +10824,8 @@ Use Read tool to examine specific files.` }] };
10301
10824
  level: import_zod2.z.enum(["error", "warn", "info", "debug"]).optional().describe("Filter by minimum log level")
10302
10825
  },
10303
10826
  async (args) => {
10304
- await runtime.ensureInitialized();
10305
- const logger = runtime.getIndexer().getLogger();
10306
- if (!logger.isEnabled()) {
10307
- 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```' }] };
10308
- }
10309
- let logs;
10310
- if (args.category) {
10311
- logs = logger.getLogsByCategory(args.category, args.limit);
10312
- } else if (args.level) {
10313
- logs = logger.getLogsByLevel(args.level, args.limit);
10314
- } else {
10315
- logs = logger.getLogs(args.limit);
10316
- }
10317
- if (logs.length === 0) {
10318
- return { content: [{ type: "text", text: "No logs recorded yet. Logs are captured during indexing and search operations." }] };
10319
- }
10320
- const text = logs.map((l) => {
10321
- const dataStr = l.data ? ` ${JSON.stringify(l.data)}` : "";
10322
- return `[${l.timestamp}] [${l.level.toUpperCase()}] [${l.category}] ${l.message}${dataStr}`;
10323
- }).join("\n");
10324
- return { content: [{ type: "text", text }] };
10827
+ const result = await getIndexLogs(runtime.projectRoot, runtime.host, args);
10828
+ return { content: [{ type: "text", text: result.text }] };
10325
10829
  }
10326
10830
  );
10327
10831
  server.tool(
@@ -10336,8 +10840,8 @@ Use Read tool to examine specific files.` }] };
10336
10840
  excludeFile: import_zod2.z.string().optional().describe("Exclude results from this file path")
10337
10841
  },
10338
10842
  async (args) => {
10339
- await runtime.ensureInitialized();
10340
- const results = await runtime.getIndexer().findSimilar(args.code, args.limit ?? 10, {
10843
+ const results = await findSimilarCode(runtime.projectRoot, runtime.host, args.code, {
10844
+ limit: args.limit ?? 10,
10341
10845
  fileType: args.fileType,
10342
10846
  directory: args.directory,
10343
10847
  chunkType: args.chunkType,
@@ -10368,11 +10872,10 @@ ${formatted.join("\n\n")}` }] };
10368
10872
  directory: import_zod2.z.string().optional().describe("Filter by directory path (e.g., 'src/utils')")
10369
10873
  },
10370
10874
  async (args) => {
10371
- await runtime.ensureInitialized();
10372
- const results = await runtime.getIndexer().search(args.query, args.limit ?? 5, {
10875
+ const results = await implementationLookup(runtime.projectRoot, runtime.host, args.query, {
10876
+ limit: args.limit ?? 5,
10373
10877
  fileType: args.fileType,
10374
- directory: args.directory,
10375
- definitionIntent: true
10878
+ directory: args.directory
10376
10879
  });
10377
10880
  return { content: [{ type: "text", text: formatDefinitionLookup(results, args.query) }] };
10378
10881
  }
@@ -10387,13 +10890,11 @@ ${formatted.join("\n\n")}` }] };
10387
10890
  relationshipType: import_zod2.z.enum(["Call", "MethodCall", "Constructor", "Import", "Inherits", "Implements"]).optional().describe("Filter by relationship type. Omit to show all.")
10388
10891
  },
10389
10892
  async (args) => {
10390
- await runtime.ensureInitialized();
10391
- const indexer = runtime.getIndexer();
10392
10893
  if (args.direction === "callees") {
10393
10894
  if (!args.symbolId) {
10394
10895
  return { content: [{ type: "text", text: "Error: 'symbolId' is required when direction is 'callees'." }] };
10395
10896
  }
10396
- const callees = await indexer.getCallees(args.symbolId, args.relationshipType);
10897
+ const { callees } = await getCallGraphData(runtime.projectRoot, runtime.host, args);
10397
10898
  if (callees.length === 0) {
10398
10899
  return { content: [{ type: "text", text: `No callees found for symbol ${args.symbolId}${args.relationshipType ? ` with type ${args.relationshipType}` : ""}.` }] };
10399
10900
  }
@@ -10405,7 +10906,7 @@ ${formatted.join("\n\n")}` }] };
10405
10906
 
10406
10907
  ${formatted2.join("\n")}` }] };
10407
10908
  }
10408
- const callers = await indexer.getCallers(args.name, args.relationshipType);
10909
+ const { callers } = await getCallGraphData(runtime.projectRoot, runtime.host, args);
10409
10910
  if (callers.length === 0) {
10410
10911
  return { content: [{ type: "text", text: `No callers found for "${args.name}"${args.relationshipType ? ` with type ${args.relationshipType}` : ""}.` }] };
10411
10912
  }
@@ -10427,18 +10928,16 @@ ${formatted.join("\n")}` }] };
10427
10928
  maxDepth: import_zod2.z.number().optional().default(10).describe("Maximum traversal depth (default: 10)")
10428
10929
  },
10429
10930
  async (args) => {
10430
- await runtime.ensureInitialized();
10431
- const indexer = runtime.getIndexer();
10432
- const path19 = await indexer.findCallPath(args.from, args.to, args.maxDepth);
10433
- if (path19.length === 0) {
10931
+ const path25 = await getCallGraphPath(runtime.projectRoot, runtime.host, args.from, args.to, args.maxDepth);
10932
+ if (path25.length === 0) {
10434
10933
  return { content: [{ type: "text", text: `No path found between "${args.from}" and "${args.to}". They may be in disconnected components, or the call graph index needs updating.` }] };
10435
10934
  }
10436
- const formatted = path19.map((hop, i) => {
10935
+ const formatted = path25.map((hop, i) => {
10437
10936
  const prefix = i === 0 ? "[start]" : `--${hop.callType}-->`;
10438
10937
  const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
10439
10938
  return `${prefix} ${hop.symbolName}${location}`;
10440
10939
  });
10441
- return { content: [{ type: "text", text: `Path (${path19.length} hops):
10940
+ return { content: [{ type: "text", text: `Path (${path25.length} hops):
10442
10941
  ${formatted.join("\n")}` }] };
10443
10942
  }
10444
10943
  );
@@ -10454,10 +10953,8 @@ ${formatted.join("\n")}` }] };
10454
10953
  direction: import_zod2.z.enum(["callers", "callees", "both"]).optional().default("both").describe("Call-graph traversal direction: 'callers' for upstream, 'callees' for downstream, 'both' for union (default: both)")
10455
10954
  },
10456
10955
  async (args) => {
10457
- await runtime.ensureInitialized();
10458
- const indexer = runtime.getIndexer();
10459
10956
  try {
10460
- const result = await indexer.getPrImpact({
10957
+ const result = await getPrImpact(runtime.projectRoot, runtime.host, {
10461
10958
  pr: args.pr,
10462
10959
  branch: args.branch,
10463
10960
  maxDepth: args.maxDepth,
@@ -10475,80 +10972,2929 @@ ${formatted.join("\n")}` }] };
10475
10972
  }
10476
10973
 
10477
10974
  // src/mcp-server.ts
10478
- function createMcpServer(projectRoot, config) {
10975
+ var import_meta2 = {};
10976
+ function getPackageVersion() {
10977
+ const raw = JSON.parse((0, import_fs14.readFileSync)(new URL("../package.json", import_meta2.url), "utf-8"));
10978
+ if (raw && typeof raw === "object" && "version" in raw && typeof raw.version === "string") {
10979
+ return raw.version;
10980
+ }
10981
+ return "0.0.0";
10982
+ }
10983
+ function createMcpServer(projectRoot, config, host = "opencode") {
10479
10984
  const server = new import_mcp.McpServer({
10480
10985
  name: "opencode-codebase-index",
10481
- version: "0.5.1"
10986
+ version: getPackageVersion()
10482
10987
  });
10483
- let indexer = new Indexer(projectRoot, config);
10484
- let initialized = false;
10485
- function refreshIndexerFromConfig() {
10486
- indexer = new Indexer(projectRoot, config);
10487
- initialized = false;
10488
- }
10489
- function shouldForceLocalizeProjectIndex() {
10490
- if (config.scope !== "project") {
10491
- return false;
10492
- }
10493
- const localIndexPath = path17.join(projectRoot, ".opencode", "index");
10494
- const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot);
10495
- if (!mainRepoRoot) {
10496
- return false;
10497
- }
10498
- const inheritedIndexPath = path17.join(mainRepoRoot, ".opencode", "index");
10499
- return !(0, import_fs12.existsSync)(localIndexPath) && (0, import_fs12.existsSync)(inheritedIndexPath);
10500
- }
10501
- async function ensureInitialized() {
10502
- if (!initialized) {
10503
- await indexer.initialize();
10504
- initialized = true;
10505
- }
10506
- }
10988
+ initializeTools(projectRoot, config, host);
10507
10989
  registerMcpTools(server, {
10508
10990
  projectRoot,
10509
- ensureInitialized,
10510
- getIndexer: () => indexer,
10511
- refreshIndexerFromConfig,
10512
- shouldForceLocalizeProjectIndex
10991
+ host
10513
10992
  });
10514
10993
  registerMcpPrompts(server);
10515
10994
  return server;
10516
10995
  }
10517
10996
 
10518
- // src/cli.ts
10519
- function parseArgs(argv) {
10520
- let project = process.cwd();
10521
- let config;
10522
- for (let i = 2; i < argv.length; i++) {
10523
- if (argv[i] === "--project" && argv[i + 1]) {
10524
- project = path18.resolve(argv[++i]);
10525
- } else if (argv[i] === "--config" && argv[i + 1]) {
10526
- config = path18.resolve(argv[++i]);
10527
- }
10528
- }
10529
- return { project, config };
10530
- }
10531
- async function main() {
10532
- if (process.argv[2] === "eval") {
10533
- const exitCode = await handleEvalCommand(process.argv.slice(3), process.cwd());
10534
- process.exit(exitCode);
10535
- }
10536
- const args = parseArgs(process.argv);
10537
- const rawConfig = loadMergedConfig(args.project);
10538
- const config = parseConfig(rawConfig);
10539
- const server = createMcpServer(args.project, config);
10540
- const transport = new import_stdio.StdioServerTransport();
10541
- await server.connect(transport);
10542
- const shutdown = () => {
10543
- server.close().catch(() => {
10997
+ // src/utils/auto-index.ts
10998
+ function getErrorMessage3(error) {
10999
+ return error instanceof Error ? error.message : String(error);
11000
+ }
11001
+ function startAutoIndex(indexer, projectRoot) {
11002
+ indexer.initialize().then(() => {
11003
+ indexer.index().catch((error) => {
11004
+ console.error(`[codebase-index] Auto-index failed for "${projectRoot}": ${getErrorMessage3(error)}`);
10544
11005
  });
10545
- process.exit(0);
10546
- };
11006
+ }).catch((error) => {
11007
+ console.error(`[codebase-index] Auto-index initialization failed for "${projectRoot}": ${getErrorMessage3(error)}`);
11008
+ });
11009
+ }
11010
+
11011
+ // node_modules/chokidar/index.js
11012
+ var import_node_events = require("events");
11013
+ var import_node_fs2 = require("fs");
11014
+ var import_promises3 = require("fs/promises");
11015
+ var sp2 = __toESM(require("path"), 1);
11016
+
11017
+ // node_modules/readdirp/index.js
11018
+ var import_promises = require("fs/promises");
11019
+ var import_node_path = require("path");
11020
+ var import_node_stream = require("stream");
11021
+ var EntryTypes = {
11022
+ FILE_TYPE: "files",
11023
+ DIR_TYPE: "directories",
11024
+ FILE_DIR_TYPE: "files_directories",
11025
+ EVERYTHING_TYPE: "all"
11026
+ };
11027
+ var defaultOptions = {
11028
+ root: ".",
11029
+ fileFilter: (_entryInfo) => true,
11030
+ directoryFilter: (_entryInfo) => true,
11031
+ type: EntryTypes.FILE_TYPE,
11032
+ lstat: false,
11033
+ depth: 2147483648,
11034
+ alwaysStat: false,
11035
+ highWaterMark: 4096
11036
+ };
11037
+ Object.freeze(defaultOptions);
11038
+ var RECURSIVE_ERROR_CODE = "READDIRP_RECURSIVE_ERROR";
11039
+ var NORMAL_FLOW_ERRORS = /* @__PURE__ */ new Set(["ENOENT", "EPERM", "EACCES", "ELOOP", RECURSIVE_ERROR_CODE]);
11040
+ var ALL_TYPES = [
11041
+ EntryTypes.DIR_TYPE,
11042
+ EntryTypes.EVERYTHING_TYPE,
11043
+ EntryTypes.FILE_DIR_TYPE,
11044
+ EntryTypes.FILE_TYPE
11045
+ ];
11046
+ var DIR_TYPES = /* @__PURE__ */ new Set([
11047
+ EntryTypes.DIR_TYPE,
11048
+ EntryTypes.EVERYTHING_TYPE,
11049
+ EntryTypes.FILE_DIR_TYPE
11050
+ ]);
11051
+ var FILE_TYPES = /* @__PURE__ */ new Set([
11052
+ EntryTypes.EVERYTHING_TYPE,
11053
+ EntryTypes.FILE_DIR_TYPE,
11054
+ EntryTypes.FILE_TYPE
11055
+ ]);
11056
+ var isNormalFlowError = (error) => NORMAL_FLOW_ERRORS.has(error.code);
11057
+ var wantBigintFsStats = process.platform === "win32";
11058
+ var emptyFn = (_entryInfo) => true;
11059
+ var normalizeFilter = (filter) => {
11060
+ if (filter === void 0)
11061
+ return emptyFn;
11062
+ if (typeof filter === "function")
11063
+ return filter;
11064
+ if (typeof filter === "string") {
11065
+ const fl = filter.trim();
11066
+ return (entry) => entry.basename === fl;
11067
+ }
11068
+ if (Array.isArray(filter)) {
11069
+ const trItems = filter.map((item) => item.trim());
11070
+ return (entry) => trItems.some((f) => entry.basename === f);
11071
+ }
11072
+ return emptyFn;
11073
+ };
11074
+ var ReaddirpStream = class extends import_node_stream.Readable {
11075
+ parents;
11076
+ reading;
11077
+ parent;
11078
+ _stat;
11079
+ _maxDepth;
11080
+ _wantsDir;
11081
+ _wantsFile;
11082
+ _wantsEverything;
11083
+ _root;
11084
+ _isDirent;
11085
+ _statsProp;
11086
+ _rdOptions;
11087
+ _fileFilter;
11088
+ _directoryFilter;
11089
+ constructor(options = {}) {
11090
+ super({
11091
+ objectMode: true,
11092
+ autoDestroy: true,
11093
+ highWaterMark: options.highWaterMark
11094
+ });
11095
+ const opts = { ...defaultOptions, ...options };
11096
+ const { root, type } = opts;
11097
+ this._fileFilter = normalizeFilter(opts.fileFilter);
11098
+ this._directoryFilter = normalizeFilter(opts.directoryFilter);
11099
+ const statMethod = opts.lstat ? import_promises.lstat : import_promises.stat;
11100
+ if (wantBigintFsStats) {
11101
+ this._stat = (path25) => statMethod(path25, { bigint: true });
11102
+ } else {
11103
+ this._stat = statMethod;
11104
+ }
11105
+ this._maxDepth = opts.depth != null && Number.isSafeInteger(opts.depth) ? opts.depth : defaultOptions.depth;
11106
+ this._wantsDir = type ? DIR_TYPES.has(type) : false;
11107
+ this._wantsFile = type ? FILE_TYPES.has(type) : false;
11108
+ this._wantsEverything = type === EntryTypes.EVERYTHING_TYPE;
11109
+ this._root = (0, import_node_path.resolve)(root);
11110
+ this._isDirent = !opts.alwaysStat;
11111
+ this._statsProp = this._isDirent ? "dirent" : "stats";
11112
+ this._rdOptions = { encoding: "utf8", withFileTypes: this._isDirent };
11113
+ this.parents = [this._exploreDir(root, 1)];
11114
+ this.reading = false;
11115
+ this.parent = void 0;
11116
+ }
11117
+ async _read(batch) {
11118
+ if (this.reading)
11119
+ return;
11120
+ this.reading = true;
11121
+ try {
11122
+ while (!this.destroyed && batch > 0) {
11123
+ const par = this.parent;
11124
+ const fil = par && par.files;
11125
+ if (fil && fil.length > 0) {
11126
+ const { path: path25, depth } = par;
11127
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path25));
11128
+ const awaited = await Promise.all(slice);
11129
+ for (const entry of awaited) {
11130
+ if (!entry)
11131
+ continue;
11132
+ if (this.destroyed)
11133
+ return;
11134
+ const entryType = await this._getEntryType(entry);
11135
+ if (entryType === "directory" && this._directoryFilter(entry)) {
11136
+ if (depth <= this._maxDepth) {
11137
+ this.parents.push(this._exploreDir(entry.fullPath, depth + 1));
11138
+ }
11139
+ if (this._wantsDir) {
11140
+ this.push(entry);
11141
+ batch--;
11142
+ }
11143
+ } else if ((entryType === "file" || this._includeAsFile(entry)) && this._fileFilter(entry)) {
11144
+ if (this._wantsFile) {
11145
+ this.push(entry);
11146
+ batch--;
11147
+ }
11148
+ }
11149
+ }
11150
+ } else {
11151
+ const parent = this.parents.pop();
11152
+ if (!parent) {
11153
+ this.push(null);
11154
+ break;
11155
+ }
11156
+ this.parent = await parent;
11157
+ if (this.destroyed)
11158
+ return;
11159
+ }
11160
+ }
11161
+ } catch (error) {
11162
+ this.destroy(error);
11163
+ } finally {
11164
+ this.reading = false;
11165
+ }
11166
+ }
11167
+ async _exploreDir(path25, depth) {
11168
+ let files;
11169
+ try {
11170
+ files = await (0, import_promises.readdir)(path25, this._rdOptions);
11171
+ } catch (error) {
11172
+ this._onError(error);
11173
+ }
11174
+ return { files, depth, path: path25 };
11175
+ }
11176
+ async _formatEntry(dirent, path25) {
11177
+ let entry;
11178
+ const basename5 = this._isDirent ? dirent.name : dirent;
11179
+ try {
11180
+ const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(path25, basename5));
11181
+ entry = { path: (0, import_node_path.relative)(this._root, fullPath), fullPath, basename: basename5 };
11182
+ entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
11183
+ } catch (err) {
11184
+ this._onError(err);
11185
+ return;
11186
+ }
11187
+ return entry;
11188
+ }
11189
+ _onError(err) {
11190
+ if (isNormalFlowError(err) && !this.destroyed) {
11191
+ this.emit("warn", err);
11192
+ } else {
11193
+ this.destroy(err);
11194
+ }
11195
+ }
11196
+ async _getEntryType(entry) {
11197
+ if (!entry && this._statsProp in entry) {
11198
+ return "";
11199
+ }
11200
+ const stats = entry[this._statsProp];
11201
+ if (stats.isFile())
11202
+ return "file";
11203
+ if (stats.isDirectory())
11204
+ return "directory";
11205
+ if (stats && stats.isSymbolicLink()) {
11206
+ const full = entry.fullPath;
11207
+ try {
11208
+ const entryRealPath = await (0, import_promises.realpath)(full);
11209
+ const entryRealPathStats = await (0, import_promises.lstat)(entryRealPath);
11210
+ if (entryRealPathStats.isFile()) {
11211
+ return "file";
11212
+ }
11213
+ if (entryRealPathStats.isDirectory()) {
11214
+ const len = entryRealPath.length;
11215
+ if (full.startsWith(entryRealPath) && full.substr(len, 1) === import_node_path.sep) {
11216
+ const recursiveError = new Error(`Circular symlink detected: "${full}" points to "${entryRealPath}"`);
11217
+ recursiveError.code = RECURSIVE_ERROR_CODE;
11218
+ return this._onError(recursiveError);
11219
+ }
11220
+ return "directory";
11221
+ }
11222
+ } catch (error) {
11223
+ this._onError(error);
11224
+ return "";
11225
+ }
11226
+ }
11227
+ }
11228
+ _includeAsFile(entry) {
11229
+ const stats = entry && entry[this._statsProp];
11230
+ return stats && this._wantsEverything && !stats.isDirectory();
11231
+ }
11232
+ };
11233
+ function readdirp(root, options = {}) {
11234
+ let type = options.entryType || options.type;
11235
+ if (type === "both")
11236
+ type = EntryTypes.FILE_DIR_TYPE;
11237
+ if (type)
11238
+ options.type = type;
11239
+ if (!root) {
11240
+ throw new Error("readdirp: root argument is required. Usage: readdirp(root, options)");
11241
+ } else if (typeof root !== "string") {
11242
+ throw new TypeError("readdirp: root argument must be a string. Usage: readdirp(root, options)");
11243
+ } else if (type && !ALL_TYPES.includes(type)) {
11244
+ throw new Error(`readdirp: Invalid type passed. Use one of ${ALL_TYPES.join(", ")}`);
11245
+ }
11246
+ options.root = root;
11247
+ return new ReaddirpStream(options);
11248
+ }
11249
+
11250
+ // node_modules/chokidar/handler.js
11251
+ var import_node_fs = require("fs");
11252
+ var import_promises2 = require("fs/promises");
11253
+ var import_node_os = require("os");
11254
+ var sp = __toESM(require("path"), 1);
11255
+ var STR_DATA = "data";
11256
+ var STR_END = "end";
11257
+ var STR_CLOSE = "close";
11258
+ var EMPTY_FN = () => {
11259
+ };
11260
+ var pl = process.platform;
11261
+ var isWindows = pl === "win32";
11262
+ var isMacos = pl === "darwin";
11263
+ var isLinux = pl === "linux";
11264
+ var isFreeBSD = pl === "freebsd";
11265
+ var isIBMi = (0, import_node_os.type)() === "OS400";
11266
+ var EVENTS = {
11267
+ ALL: "all",
11268
+ READY: "ready",
11269
+ ADD: "add",
11270
+ CHANGE: "change",
11271
+ ADD_DIR: "addDir",
11272
+ UNLINK: "unlink",
11273
+ UNLINK_DIR: "unlinkDir",
11274
+ RAW: "raw",
11275
+ ERROR: "error"
11276
+ };
11277
+ var EV = EVENTS;
11278
+ var THROTTLE_MODE_WATCH = "watch";
11279
+ var statMethods = { lstat: import_promises2.lstat, stat: import_promises2.stat };
11280
+ var KEY_LISTENERS = "listeners";
11281
+ var KEY_ERR = "errHandlers";
11282
+ var KEY_RAW = "rawEmitters";
11283
+ var HANDLER_KEYS = [KEY_LISTENERS, KEY_ERR, KEY_RAW];
11284
+ var binaryExtensions = /* @__PURE__ */ new Set([
11285
+ "3dm",
11286
+ "3ds",
11287
+ "3g2",
11288
+ "3gp",
11289
+ "7z",
11290
+ "a",
11291
+ "aac",
11292
+ "adp",
11293
+ "afdesign",
11294
+ "afphoto",
11295
+ "afpub",
11296
+ "ai",
11297
+ "aif",
11298
+ "aiff",
11299
+ "alz",
11300
+ "ape",
11301
+ "apk",
11302
+ "appimage",
11303
+ "ar",
11304
+ "arj",
11305
+ "asf",
11306
+ "au",
11307
+ "avi",
11308
+ "bak",
11309
+ "baml",
11310
+ "bh",
11311
+ "bin",
11312
+ "bk",
11313
+ "bmp",
11314
+ "btif",
11315
+ "bz2",
11316
+ "bzip2",
11317
+ "cab",
11318
+ "caf",
11319
+ "cgm",
11320
+ "class",
11321
+ "cmx",
11322
+ "cpio",
11323
+ "cr2",
11324
+ "cur",
11325
+ "dat",
11326
+ "dcm",
11327
+ "deb",
11328
+ "dex",
11329
+ "djvu",
11330
+ "dll",
11331
+ "dmg",
11332
+ "dng",
11333
+ "doc",
11334
+ "docm",
11335
+ "docx",
11336
+ "dot",
11337
+ "dotm",
11338
+ "dra",
11339
+ "DS_Store",
11340
+ "dsk",
11341
+ "dts",
11342
+ "dtshd",
11343
+ "dvb",
11344
+ "dwg",
11345
+ "dxf",
11346
+ "ecelp4800",
11347
+ "ecelp7470",
11348
+ "ecelp9600",
11349
+ "egg",
11350
+ "eol",
11351
+ "eot",
11352
+ "epub",
11353
+ "exe",
11354
+ "f4v",
11355
+ "fbs",
11356
+ "fh",
11357
+ "fla",
11358
+ "flac",
11359
+ "flatpak",
11360
+ "fli",
11361
+ "flv",
11362
+ "fpx",
11363
+ "fst",
11364
+ "fvt",
11365
+ "g3",
11366
+ "gh",
11367
+ "gif",
11368
+ "graffle",
11369
+ "gz",
11370
+ "gzip",
11371
+ "h261",
11372
+ "h263",
11373
+ "h264",
11374
+ "icns",
11375
+ "ico",
11376
+ "ief",
11377
+ "img",
11378
+ "ipa",
11379
+ "iso",
11380
+ "jar",
11381
+ "jpeg",
11382
+ "jpg",
11383
+ "jpgv",
11384
+ "jpm",
11385
+ "jxr",
11386
+ "key",
11387
+ "ktx",
11388
+ "lha",
11389
+ "lib",
11390
+ "lvp",
11391
+ "lz",
11392
+ "lzh",
11393
+ "lzma",
11394
+ "lzo",
11395
+ "m3u",
11396
+ "m4a",
11397
+ "m4v",
11398
+ "mar",
11399
+ "mdi",
11400
+ "mht",
11401
+ "mid",
11402
+ "midi",
11403
+ "mj2",
11404
+ "mka",
11405
+ "mkv",
11406
+ "mmr",
11407
+ "mng",
11408
+ "mobi",
11409
+ "mov",
11410
+ "movie",
11411
+ "mp3",
11412
+ "mp4",
11413
+ "mp4a",
11414
+ "mpeg",
11415
+ "mpg",
11416
+ "mpga",
11417
+ "mxu",
11418
+ "nef",
11419
+ "npx",
11420
+ "numbers",
11421
+ "nupkg",
11422
+ "o",
11423
+ "odp",
11424
+ "ods",
11425
+ "odt",
11426
+ "oga",
11427
+ "ogg",
11428
+ "ogv",
11429
+ "otf",
11430
+ "ott",
11431
+ "pages",
11432
+ "pbm",
11433
+ "pcx",
11434
+ "pdb",
11435
+ "pdf",
11436
+ "pea",
11437
+ "pgm",
11438
+ "pic",
11439
+ "png",
11440
+ "pnm",
11441
+ "pot",
11442
+ "potm",
11443
+ "potx",
11444
+ "ppa",
11445
+ "ppam",
11446
+ "ppm",
11447
+ "pps",
11448
+ "ppsm",
11449
+ "ppsx",
11450
+ "ppt",
11451
+ "pptm",
11452
+ "pptx",
11453
+ "psd",
11454
+ "pya",
11455
+ "pyc",
11456
+ "pyo",
11457
+ "pyv",
11458
+ "qt",
11459
+ "rar",
11460
+ "ras",
11461
+ "raw",
11462
+ "resources",
11463
+ "rgb",
11464
+ "rip",
11465
+ "rlc",
11466
+ "rmf",
11467
+ "rmvb",
11468
+ "rpm",
11469
+ "rtf",
11470
+ "rz",
11471
+ "s3m",
11472
+ "s7z",
11473
+ "scpt",
11474
+ "sgi",
11475
+ "shar",
11476
+ "snap",
11477
+ "sil",
11478
+ "sketch",
11479
+ "slk",
11480
+ "smv",
11481
+ "snk",
11482
+ "so",
11483
+ "stl",
11484
+ "suo",
11485
+ "sub",
11486
+ "swf",
11487
+ "tar",
11488
+ "tbz",
11489
+ "tbz2",
11490
+ "tga",
11491
+ "tgz",
11492
+ "thmx",
11493
+ "tif",
11494
+ "tiff",
11495
+ "tlz",
11496
+ "ttc",
11497
+ "ttf",
11498
+ "txz",
11499
+ "udf",
11500
+ "uvh",
11501
+ "uvi",
11502
+ "uvm",
11503
+ "uvp",
11504
+ "uvs",
11505
+ "uvu",
11506
+ "viv",
11507
+ "vob",
11508
+ "war",
11509
+ "wav",
11510
+ "wax",
11511
+ "wbmp",
11512
+ "wdp",
11513
+ "weba",
11514
+ "webm",
11515
+ "webp",
11516
+ "whl",
11517
+ "wim",
11518
+ "wm",
11519
+ "wma",
11520
+ "wmv",
11521
+ "wmx",
11522
+ "woff",
11523
+ "woff2",
11524
+ "wrm",
11525
+ "wvx",
11526
+ "xbm",
11527
+ "xif",
11528
+ "xla",
11529
+ "xlam",
11530
+ "xls",
11531
+ "xlsb",
11532
+ "xlsm",
11533
+ "xlsx",
11534
+ "xlt",
11535
+ "xltm",
11536
+ "xltx",
11537
+ "xm",
11538
+ "xmind",
11539
+ "xpi",
11540
+ "xpm",
11541
+ "xwd",
11542
+ "xz",
11543
+ "z",
11544
+ "zip",
11545
+ "zipx"
11546
+ ]);
11547
+ var isBinaryPath = (filePath) => binaryExtensions.has(sp.extname(filePath).slice(1).toLowerCase());
11548
+ var foreach = (val, fn) => {
11549
+ if (val instanceof Set) {
11550
+ val.forEach(fn);
11551
+ } else {
11552
+ fn(val);
11553
+ }
11554
+ };
11555
+ var addAndConvert = (main2, prop, item) => {
11556
+ let container = main2[prop];
11557
+ if (!(container instanceof Set)) {
11558
+ main2[prop] = container = /* @__PURE__ */ new Set([container]);
11559
+ }
11560
+ container.add(item);
11561
+ };
11562
+ var clearItem = (cont) => (key) => {
11563
+ const set = cont[key];
11564
+ if (set instanceof Set) {
11565
+ set.clear();
11566
+ } else {
11567
+ delete cont[key];
11568
+ }
11569
+ };
11570
+ var delFromSet = (main2, prop, item) => {
11571
+ const container = main2[prop];
11572
+ if (container instanceof Set) {
11573
+ container.delete(item);
11574
+ } else if (container === item) {
11575
+ delete main2[prop];
11576
+ }
11577
+ };
11578
+ var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
11579
+ var FsWatchInstances = /* @__PURE__ */ new Map();
11580
+ function createFsWatchInstance(path25, options, listener, errHandler, emitRaw) {
11581
+ const handleEvent = (rawEvent, evPath) => {
11582
+ listener(path25);
11583
+ emitRaw(rawEvent, evPath, { watchedPath: path25 });
11584
+ if (evPath && path25 !== evPath) {
11585
+ fsWatchBroadcast(sp.resolve(path25, evPath), KEY_LISTENERS, sp.join(path25, evPath));
11586
+ }
11587
+ };
11588
+ try {
11589
+ return (0, import_node_fs.watch)(path25, {
11590
+ persistent: options.persistent
11591
+ }, handleEvent);
11592
+ } catch (error) {
11593
+ errHandler(error);
11594
+ return void 0;
11595
+ }
11596
+ }
11597
+ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
11598
+ const cont = FsWatchInstances.get(fullPath);
11599
+ if (!cont)
11600
+ return;
11601
+ foreach(cont[listenerType], (listener) => {
11602
+ listener(val1, val2, val3);
11603
+ });
11604
+ };
11605
+ var setFsWatchListener = (path25, fullPath, options, handlers) => {
11606
+ const { listener, errHandler, rawEmitter } = handlers;
11607
+ let cont = FsWatchInstances.get(fullPath);
11608
+ let watcher;
11609
+ if (!options.persistent) {
11610
+ watcher = createFsWatchInstance(path25, options, listener, errHandler, rawEmitter);
11611
+ if (!watcher)
11612
+ return;
11613
+ return watcher.close.bind(watcher);
11614
+ }
11615
+ if (cont) {
11616
+ addAndConvert(cont, KEY_LISTENERS, listener);
11617
+ addAndConvert(cont, KEY_ERR, errHandler);
11618
+ addAndConvert(cont, KEY_RAW, rawEmitter);
11619
+ } else {
11620
+ watcher = createFsWatchInstance(
11621
+ path25,
11622
+ options,
11623
+ fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
11624
+ errHandler,
11625
+ // no need to use broadcast here
11626
+ fsWatchBroadcast.bind(null, fullPath, KEY_RAW)
11627
+ );
11628
+ if (!watcher)
11629
+ return;
11630
+ watcher.on(EV.ERROR, async (error) => {
11631
+ const broadcastErr = fsWatchBroadcast.bind(null, fullPath, KEY_ERR);
11632
+ if (cont)
11633
+ cont.watcherUnusable = true;
11634
+ if (isWindows && error.code === "EPERM") {
11635
+ try {
11636
+ const fd = await (0, import_promises2.open)(path25, "r");
11637
+ await fd.close();
11638
+ broadcastErr(error);
11639
+ } catch (err) {
11640
+ }
11641
+ } else {
11642
+ broadcastErr(error);
11643
+ }
11644
+ });
11645
+ cont = {
11646
+ listeners: listener,
11647
+ errHandlers: errHandler,
11648
+ rawEmitters: rawEmitter,
11649
+ watcher
11650
+ };
11651
+ FsWatchInstances.set(fullPath, cont);
11652
+ }
11653
+ return () => {
11654
+ delFromSet(cont, KEY_LISTENERS, listener);
11655
+ delFromSet(cont, KEY_ERR, errHandler);
11656
+ delFromSet(cont, KEY_RAW, rawEmitter);
11657
+ if (isEmptySet(cont.listeners)) {
11658
+ cont.watcher.close();
11659
+ FsWatchInstances.delete(fullPath);
11660
+ HANDLER_KEYS.forEach(clearItem(cont));
11661
+ cont.watcher = void 0;
11662
+ Object.freeze(cont);
11663
+ }
11664
+ };
11665
+ };
11666
+ var FsWatchFileInstances = /* @__PURE__ */ new Map();
11667
+ var setFsWatchFileListener = (path25, fullPath, options, handlers) => {
11668
+ const { listener, rawEmitter } = handlers;
11669
+ let cont = FsWatchFileInstances.get(fullPath);
11670
+ const copts = cont && cont.options;
11671
+ if (copts && (copts.persistent < options.persistent || copts.interval > options.interval)) {
11672
+ (0, import_node_fs.unwatchFile)(fullPath);
11673
+ cont = void 0;
11674
+ }
11675
+ if (cont) {
11676
+ addAndConvert(cont, KEY_LISTENERS, listener);
11677
+ addAndConvert(cont, KEY_RAW, rawEmitter);
11678
+ } else {
11679
+ cont = {
11680
+ listeners: listener,
11681
+ rawEmitters: rawEmitter,
11682
+ options,
11683
+ watcher: (0, import_node_fs.watchFile)(fullPath, options, (curr, prev) => {
11684
+ foreach(cont.rawEmitters, (rawEmitter2) => {
11685
+ rawEmitter2(EV.CHANGE, fullPath, { curr, prev });
11686
+ });
11687
+ const currmtime = curr.mtimeMs;
11688
+ if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
11689
+ foreach(cont.listeners, (listener2) => listener2(path25, curr));
11690
+ }
11691
+ })
11692
+ };
11693
+ FsWatchFileInstances.set(fullPath, cont);
11694
+ }
11695
+ return () => {
11696
+ delFromSet(cont, KEY_LISTENERS, listener);
11697
+ delFromSet(cont, KEY_RAW, rawEmitter);
11698
+ if (isEmptySet(cont.listeners)) {
11699
+ FsWatchFileInstances.delete(fullPath);
11700
+ (0, import_node_fs.unwatchFile)(fullPath);
11701
+ cont.options = cont.watcher = void 0;
11702
+ Object.freeze(cont);
11703
+ }
11704
+ };
11705
+ };
11706
+ var NodeFsHandler = class {
11707
+ fsw;
11708
+ _boundHandleError;
11709
+ constructor(fsW) {
11710
+ this.fsw = fsW;
11711
+ this._boundHandleError = (error) => fsW._handleError(error);
11712
+ }
11713
+ /**
11714
+ * Watch file for changes with fs_watchFile or fs_watch.
11715
+ * @param path to file or dir
11716
+ * @param listener on fs change
11717
+ * @returns closer for the watcher instance
11718
+ */
11719
+ _watchWithNodeFs(path25, listener) {
11720
+ const opts = this.fsw.options;
11721
+ const directory = sp.dirname(path25);
11722
+ const basename5 = sp.basename(path25);
11723
+ const parent = this.fsw._getWatchedDir(directory);
11724
+ parent.add(basename5);
11725
+ const absolutePath = sp.resolve(path25);
11726
+ const options = {
11727
+ persistent: opts.persistent
11728
+ };
11729
+ if (!listener)
11730
+ listener = EMPTY_FN;
11731
+ let closer;
11732
+ if (opts.usePolling) {
11733
+ const enableBin = opts.interval !== opts.binaryInterval;
11734
+ options.interval = enableBin && isBinaryPath(basename5) ? opts.binaryInterval : opts.interval;
11735
+ closer = setFsWatchFileListener(path25, absolutePath, options, {
11736
+ listener,
11737
+ rawEmitter: this.fsw._emitRaw
11738
+ });
11739
+ } else {
11740
+ closer = setFsWatchListener(path25, absolutePath, options, {
11741
+ listener,
11742
+ errHandler: this._boundHandleError,
11743
+ rawEmitter: this.fsw._emitRaw
11744
+ });
11745
+ }
11746
+ return closer;
11747
+ }
11748
+ /**
11749
+ * Watch a file and emit add event if warranted.
11750
+ * @returns closer for the watcher instance
11751
+ */
11752
+ _handleFile(file, stats, initialAdd) {
11753
+ if (this.fsw.closed) {
11754
+ return;
11755
+ }
11756
+ const dirname10 = sp.dirname(file);
11757
+ const basename5 = sp.basename(file);
11758
+ const parent = this.fsw._getWatchedDir(dirname10);
11759
+ let prevStats = stats;
11760
+ if (parent.has(basename5))
11761
+ return;
11762
+ const listener = async (path25, newStats) => {
11763
+ if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
11764
+ return;
11765
+ if (!newStats || newStats.mtimeMs === 0) {
11766
+ try {
11767
+ const newStats2 = await (0, import_promises2.stat)(file);
11768
+ if (this.fsw.closed)
11769
+ return;
11770
+ const at = newStats2.atimeMs;
11771
+ const mt = newStats2.mtimeMs;
11772
+ if (!at || at <= mt || mt !== prevStats.mtimeMs) {
11773
+ this.fsw._emit(EV.CHANGE, file, newStats2);
11774
+ }
11775
+ if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
11776
+ this.fsw._closeFile(path25);
11777
+ prevStats = newStats2;
11778
+ const closer2 = this._watchWithNodeFs(file, listener);
11779
+ if (closer2)
11780
+ this.fsw._addPathCloser(path25, closer2);
11781
+ } else {
11782
+ prevStats = newStats2;
11783
+ }
11784
+ } catch (error) {
11785
+ this.fsw._remove(dirname10, basename5);
11786
+ }
11787
+ } else if (parent.has(basename5)) {
11788
+ const at = newStats.atimeMs;
11789
+ const mt = newStats.mtimeMs;
11790
+ if (!at || at <= mt || mt !== prevStats.mtimeMs) {
11791
+ this.fsw._emit(EV.CHANGE, file, newStats);
11792
+ }
11793
+ prevStats = newStats;
11794
+ }
11795
+ };
11796
+ const closer = this._watchWithNodeFs(file, listener);
11797
+ if (!(initialAdd && this.fsw.options.ignoreInitial) && this.fsw._isntIgnored(file)) {
11798
+ if (!this.fsw._throttle(EV.ADD, file, 0))
11799
+ return;
11800
+ this.fsw._emit(EV.ADD, file, stats);
11801
+ }
11802
+ return closer;
11803
+ }
11804
+ /**
11805
+ * Handle symlinks encountered while reading a dir.
11806
+ * @param entry returned by readdirp
11807
+ * @param directory path of dir being read
11808
+ * @param path of this item
11809
+ * @param item basename of this item
11810
+ * @returns true if no more processing is needed for this entry.
11811
+ */
11812
+ async _handleSymlink(entry, directory, path25, item) {
11813
+ if (this.fsw.closed) {
11814
+ return;
11815
+ }
11816
+ const full = entry.fullPath;
11817
+ const dir = this.fsw._getWatchedDir(directory);
11818
+ if (!this.fsw.options.followSymlinks) {
11819
+ this.fsw._incrReadyCount();
11820
+ let linkPath;
11821
+ try {
11822
+ linkPath = await (0, import_promises2.realpath)(path25);
11823
+ } catch (e) {
11824
+ this.fsw._emitReady();
11825
+ return true;
11826
+ }
11827
+ if (this.fsw.closed)
11828
+ return;
11829
+ if (dir.has(item)) {
11830
+ if (this.fsw._symlinkPaths.get(full) !== linkPath) {
11831
+ this.fsw._symlinkPaths.set(full, linkPath);
11832
+ this.fsw._emit(EV.CHANGE, path25, entry.stats);
11833
+ }
11834
+ } else {
11835
+ dir.add(item);
11836
+ this.fsw._symlinkPaths.set(full, linkPath);
11837
+ this.fsw._emit(EV.ADD, path25, entry.stats);
11838
+ }
11839
+ this.fsw._emitReady();
11840
+ return true;
11841
+ }
11842
+ if (this.fsw._symlinkPaths.has(full)) {
11843
+ return true;
11844
+ }
11845
+ this.fsw._symlinkPaths.set(full, true);
11846
+ }
11847
+ _handleRead(directory, initialAdd, wh, target, dir, depth, throttler) {
11848
+ directory = sp.join(directory, "");
11849
+ const throttleKey = target ? `${directory}:${target}` : directory;
11850
+ throttler = this.fsw._throttle("readdir", throttleKey, 1e3);
11851
+ if (!throttler)
11852
+ return;
11853
+ const previous = this.fsw._getWatchedDir(wh.path);
11854
+ const current = /* @__PURE__ */ new Set();
11855
+ let stream = this.fsw._readdirp(directory, {
11856
+ fileFilter: (entry) => wh.filterPath(entry),
11857
+ directoryFilter: (entry) => wh.filterDir(entry)
11858
+ });
11859
+ if (!stream)
11860
+ return;
11861
+ stream.on(STR_DATA, async (entry) => {
11862
+ if (this.fsw.closed) {
11863
+ stream = void 0;
11864
+ return;
11865
+ }
11866
+ const item = entry.path;
11867
+ let path25 = sp.join(directory, item);
11868
+ current.add(item);
11869
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path25, item)) {
11870
+ return;
11871
+ }
11872
+ if (this.fsw.closed) {
11873
+ stream = void 0;
11874
+ return;
11875
+ }
11876
+ if (item === target || !target && !previous.has(item)) {
11877
+ this.fsw._incrReadyCount();
11878
+ path25 = sp.join(dir, sp.relative(dir, path25));
11879
+ this._addToNodeFs(path25, initialAdd, wh, depth + 1);
11880
+ }
11881
+ }).on(EV.ERROR, this._boundHandleError);
11882
+ return new Promise((resolve15, reject) => {
11883
+ if (!stream)
11884
+ return reject();
11885
+ stream.once(STR_END, () => {
11886
+ if (this.fsw.closed) {
11887
+ stream = void 0;
11888
+ return;
11889
+ }
11890
+ const wasThrottled = throttler ? throttler.clear() : false;
11891
+ resolve15(void 0);
11892
+ previous.getChildren().filter((item) => {
11893
+ return item !== directory && !current.has(item);
11894
+ }).forEach((item) => {
11895
+ this.fsw._remove(directory, item);
11896
+ });
11897
+ stream = void 0;
11898
+ if (wasThrottled)
11899
+ this._handleRead(directory, false, wh, target, dir, depth, throttler);
11900
+ });
11901
+ });
11902
+ }
11903
+ /**
11904
+ * Read directory to add / remove files from `@watched` list and re-read it on change.
11905
+ * @param dir fs path
11906
+ * @param stats
11907
+ * @param initialAdd
11908
+ * @param depth relative to user-supplied path
11909
+ * @param target child path targeted for watch
11910
+ * @param wh Common watch helpers for this path
11911
+ * @param realpath
11912
+ * @returns closer for the watcher instance.
11913
+ */
11914
+ async _handleDir(dir, stats, initialAdd, depth, target, wh, realpath2) {
11915
+ const parentDir = this.fsw._getWatchedDir(sp.dirname(dir));
11916
+ const tracked = parentDir.has(sp.basename(dir));
11917
+ if (!(initialAdd && this.fsw.options.ignoreInitial) && !target && !tracked) {
11918
+ this.fsw._emit(EV.ADD_DIR, dir, stats);
11919
+ }
11920
+ parentDir.add(sp.basename(dir));
11921
+ this.fsw._getWatchedDir(dir);
11922
+ let throttler;
11923
+ let closer;
11924
+ const oDepth = this.fsw.options.depth;
11925
+ if ((oDepth == null || depth <= oDepth) && !this.fsw._symlinkPaths.has(realpath2)) {
11926
+ if (!target) {
11927
+ await this._handleRead(dir, initialAdd, wh, target, dir, depth, throttler);
11928
+ if (this.fsw.closed)
11929
+ return;
11930
+ }
11931
+ closer = this._watchWithNodeFs(dir, (dirPath, stats2) => {
11932
+ if (stats2 && stats2.mtimeMs === 0)
11933
+ return;
11934
+ this._handleRead(dirPath, false, wh, target, dir, depth, throttler);
11935
+ });
11936
+ }
11937
+ return closer;
11938
+ }
11939
+ /**
11940
+ * Handle added file, directory, or glob pattern.
11941
+ * Delegates call to _handleFile / _handleDir after checks.
11942
+ * @param path to file or ir
11943
+ * @param initialAdd was the file added at watch instantiation?
11944
+ * @param priorWh depth relative to user-supplied path
11945
+ * @param depth Child path actually targeted for watch
11946
+ * @param target Child path actually targeted for watch
11947
+ */
11948
+ async _addToNodeFs(path25, initialAdd, priorWh, depth, target) {
11949
+ const ready = this.fsw._emitReady;
11950
+ if (this.fsw._isIgnored(path25) || this.fsw.closed) {
11951
+ ready();
11952
+ return false;
11953
+ }
11954
+ const wh = this.fsw._getWatchHelpers(path25);
11955
+ if (priorWh) {
11956
+ wh.filterPath = (entry) => priorWh.filterPath(entry);
11957
+ wh.filterDir = (entry) => priorWh.filterDir(entry);
11958
+ }
11959
+ try {
11960
+ const stats = await statMethods[wh.statMethod](wh.watchPath);
11961
+ if (this.fsw.closed)
11962
+ return;
11963
+ if (this.fsw._isIgnored(wh.watchPath, stats)) {
11964
+ ready();
11965
+ return false;
11966
+ }
11967
+ const follow = this.fsw.options.followSymlinks;
11968
+ let closer;
11969
+ if (stats.isDirectory()) {
11970
+ const absPath = sp.resolve(path25);
11971
+ const targetPath = follow ? await (0, import_promises2.realpath)(path25) : path25;
11972
+ if (this.fsw.closed)
11973
+ return;
11974
+ closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
11975
+ if (this.fsw.closed)
11976
+ return;
11977
+ if (absPath !== targetPath && targetPath !== void 0) {
11978
+ this.fsw._symlinkPaths.set(absPath, targetPath);
11979
+ }
11980
+ } else if (stats.isSymbolicLink()) {
11981
+ const targetPath = follow ? await (0, import_promises2.realpath)(path25) : path25;
11982
+ if (this.fsw.closed)
11983
+ return;
11984
+ const parent = sp.dirname(wh.watchPath);
11985
+ this.fsw._getWatchedDir(parent).add(wh.watchPath);
11986
+ this.fsw._emit(EV.ADD, wh.watchPath, stats);
11987
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path25, wh, targetPath);
11988
+ if (this.fsw.closed)
11989
+ return;
11990
+ if (targetPath !== void 0) {
11991
+ this.fsw._symlinkPaths.set(sp.resolve(path25), targetPath);
11992
+ }
11993
+ } else {
11994
+ closer = this._handleFile(wh.watchPath, stats, initialAdd);
11995
+ }
11996
+ ready();
11997
+ if (closer)
11998
+ this.fsw._addPathCloser(path25, closer);
11999
+ return false;
12000
+ } catch (error) {
12001
+ if (this.fsw._handleError(error)) {
12002
+ ready();
12003
+ return path25;
12004
+ }
12005
+ }
12006
+ }
12007
+ };
12008
+
12009
+ // node_modules/chokidar/index.js
12010
+ var SLASH = "/";
12011
+ var SLASH_SLASH = "//";
12012
+ var ONE_DOT = ".";
12013
+ var TWO_DOTS = "..";
12014
+ var STRING_TYPE = "string";
12015
+ var BACK_SLASH_RE = /\\/g;
12016
+ var DOUBLE_SLASH_RE = /\/\//g;
12017
+ var DOT_RE = /\..*\.(sw[px])$|~$|\.subl.*\.tmp/;
12018
+ var REPLACER_RE = /^\.[/\\]/;
12019
+ function arrify(item) {
12020
+ return Array.isArray(item) ? item : [item];
12021
+ }
12022
+ var isMatcherObject = (matcher) => typeof matcher === "object" && matcher !== null && !(matcher instanceof RegExp);
12023
+ function createPattern(matcher) {
12024
+ if (typeof matcher === "function")
12025
+ return matcher;
12026
+ if (typeof matcher === "string")
12027
+ return (string) => matcher === string;
12028
+ if (matcher instanceof RegExp)
12029
+ return (string) => matcher.test(string);
12030
+ if (typeof matcher === "object" && matcher !== null) {
12031
+ return (string) => {
12032
+ if (matcher.path === string)
12033
+ return true;
12034
+ if (matcher.recursive) {
12035
+ const relative10 = sp2.relative(matcher.path, string);
12036
+ if (!relative10) {
12037
+ return false;
12038
+ }
12039
+ return !relative10.startsWith("..") && !sp2.isAbsolute(relative10);
12040
+ }
12041
+ return false;
12042
+ };
12043
+ }
12044
+ return () => false;
12045
+ }
12046
+ function normalizePath2(path25) {
12047
+ if (typeof path25 !== "string")
12048
+ throw new Error("string expected");
12049
+ path25 = sp2.normalize(path25);
12050
+ path25 = path25.replace(/\\/g, "/");
12051
+ let prepend = false;
12052
+ if (path25.startsWith("//"))
12053
+ prepend = true;
12054
+ path25 = path25.replace(DOUBLE_SLASH_RE, "/");
12055
+ if (prepend)
12056
+ path25 = "/" + path25;
12057
+ return path25;
12058
+ }
12059
+ function matchPatterns(patterns, testString, stats) {
12060
+ const path25 = normalizePath2(testString);
12061
+ for (let index = 0; index < patterns.length; index++) {
12062
+ const pattern = patterns[index];
12063
+ if (pattern(path25, stats)) {
12064
+ return true;
12065
+ }
12066
+ }
12067
+ return false;
12068
+ }
12069
+ function anymatch(matchers, testString) {
12070
+ if (matchers == null) {
12071
+ throw new TypeError("anymatch: specify first argument");
12072
+ }
12073
+ const matchersArray = arrify(matchers);
12074
+ const patterns = matchersArray.map((matcher) => createPattern(matcher));
12075
+ if (testString == null) {
12076
+ return (testString2, stats) => {
12077
+ return matchPatterns(patterns, testString2, stats);
12078
+ };
12079
+ }
12080
+ return matchPatterns(patterns, testString);
12081
+ }
12082
+ var unifyPaths = (paths_) => {
12083
+ const paths = arrify(paths_).flat();
12084
+ if (!paths.every((p) => typeof p === STRING_TYPE)) {
12085
+ throw new TypeError(`Non-string provided as watch path: ${paths}`);
12086
+ }
12087
+ return paths.map(normalizePathToUnix);
12088
+ };
12089
+ var toUnix = (string) => {
12090
+ let str = string.replace(BACK_SLASH_RE, SLASH);
12091
+ let prepend = false;
12092
+ if (str.startsWith(SLASH_SLASH)) {
12093
+ prepend = true;
12094
+ }
12095
+ str = str.replace(DOUBLE_SLASH_RE, SLASH);
12096
+ if (prepend) {
12097
+ str = SLASH + str;
12098
+ }
12099
+ return str;
12100
+ };
12101
+ var normalizePathToUnix = (path25) => toUnix(sp2.normalize(toUnix(path25)));
12102
+ var normalizeIgnored = (cwd = "") => (path25) => {
12103
+ if (typeof path25 === "string") {
12104
+ return normalizePathToUnix(sp2.isAbsolute(path25) ? path25 : sp2.join(cwd, path25));
12105
+ } else {
12106
+ return path25;
12107
+ }
12108
+ };
12109
+ var getAbsolutePath = (path25, cwd) => {
12110
+ if (sp2.isAbsolute(path25)) {
12111
+ return path25;
12112
+ }
12113
+ return sp2.join(cwd, path25);
12114
+ };
12115
+ var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
12116
+ var DirEntry = class {
12117
+ path;
12118
+ _removeWatcher;
12119
+ items;
12120
+ constructor(dir, removeWatcher) {
12121
+ this.path = dir;
12122
+ this._removeWatcher = removeWatcher;
12123
+ this.items = /* @__PURE__ */ new Set();
12124
+ }
12125
+ add(item) {
12126
+ const { items } = this;
12127
+ if (!items)
12128
+ return;
12129
+ if (item !== ONE_DOT && item !== TWO_DOTS)
12130
+ items.add(item);
12131
+ }
12132
+ async remove(item) {
12133
+ const { items } = this;
12134
+ if (!items)
12135
+ return;
12136
+ items.delete(item);
12137
+ if (items.size > 0)
12138
+ return;
12139
+ const dir = this.path;
12140
+ try {
12141
+ await (0, import_promises3.readdir)(dir);
12142
+ } catch (err) {
12143
+ if (this._removeWatcher) {
12144
+ this._removeWatcher(sp2.dirname(dir), sp2.basename(dir));
12145
+ }
12146
+ }
12147
+ }
12148
+ has(item) {
12149
+ const { items } = this;
12150
+ if (!items)
12151
+ return;
12152
+ return items.has(item);
12153
+ }
12154
+ getChildren() {
12155
+ const { items } = this;
12156
+ if (!items)
12157
+ return [];
12158
+ return [...items.values()];
12159
+ }
12160
+ dispose() {
12161
+ this.items.clear();
12162
+ this.path = "";
12163
+ this._removeWatcher = EMPTY_FN;
12164
+ this.items = EMPTY_SET;
12165
+ Object.freeze(this);
12166
+ }
12167
+ };
12168
+ var STAT_METHOD_F = "stat";
12169
+ var STAT_METHOD_L = "lstat";
12170
+ var WatchHelper = class {
12171
+ fsw;
12172
+ path;
12173
+ watchPath;
12174
+ fullWatchPath;
12175
+ dirParts;
12176
+ followSymlinks;
12177
+ statMethod;
12178
+ constructor(path25, follow, fsw) {
12179
+ this.fsw = fsw;
12180
+ const watchPath = path25;
12181
+ this.path = path25 = path25.replace(REPLACER_RE, "");
12182
+ this.watchPath = watchPath;
12183
+ this.fullWatchPath = sp2.resolve(watchPath);
12184
+ this.dirParts = [];
12185
+ this.dirParts.forEach((parts) => {
12186
+ if (parts.length > 1)
12187
+ parts.pop();
12188
+ });
12189
+ this.followSymlinks = follow;
12190
+ this.statMethod = follow ? STAT_METHOD_F : STAT_METHOD_L;
12191
+ }
12192
+ entryPath(entry) {
12193
+ return sp2.join(this.watchPath, sp2.relative(this.watchPath, entry.fullPath));
12194
+ }
12195
+ filterPath(entry) {
12196
+ const { stats } = entry;
12197
+ if (stats && stats.isSymbolicLink())
12198
+ return this.filterDir(entry);
12199
+ const resolvedPath = this.entryPath(entry);
12200
+ return this.fsw._isntIgnored(resolvedPath, stats) && this.fsw._hasReadPermissions(stats);
12201
+ }
12202
+ filterDir(entry) {
12203
+ return this.fsw._isntIgnored(this.entryPath(entry), entry.stats);
12204
+ }
12205
+ };
12206
+ var FSWatcher = class extends import_node_events.EventEmitter {
12207
+ closed;
12208
+ options;
12209
+ _closers;
12210
+ _ignoredPaths;
12211
+ _throttled;
12212
+ _streams;
12213
+ _symlinkPaths;
12214
+ _watched;
12215
+ _pendingWrites;
12216
+ _pendingUnlinks;
12217
+ _readyCount;
12218
+ _emitReady;
12219
+ _closePromise;
12220
+ _userIgnored;
12221
+ _readyEmitted;
12222
+ _emitRaw;
12223
+ _boundRemove;
12224
+ _nodeFsHandler;
12225
+ // Not indenting methods for history sake; for now.
12226
+ constructor(_opts = {}) {
12227
+ super();
12228
+ this.closed = false;
12229
+ this._closers = /* @__PURE__ */ new Map();
12230
+ this._ignoredPaths = /* @__PURE__ */ new Set();
12231
+ this._throttled = /* @__PURE__ */ new Map();
12232
+ this._streams = /* @__PURE__ */ new Set();
12233
+ this._symlinkPaths = /* @__PURE__ */ new Map();
12234
+ this._watched = /* @__PURE__ */ new Map();
12235
+ this._pendingWrites = /* @__PURE__ */ new Map();
12236
+ this._pendingUnlinks = /* @__PURE__ */ new Map();
12237
+ this._readyCount = 0;
12238
+ this._readyEmitted = false;
12239
+ const awf = _opts.awaitWriteFinish;
12240
+ const DEF_AWF = { stabilityThreshold: 2e3, pollInterval: 100 };
12241
+ const opts = {
12242
+ // Defaults
12243
+ persistent: true,
12244
+ ignoreInitial: false,
12245
+ ignorePermissionErrors: false,
12246
+ interval: 100,
12247
+ binaryInterval: 300,
12248
+ followSymlinks: true,
12249
+ usePolling: false,
12250
+ // useAsync: false,
12251
+ atomic: true,
12252
+ // NOTE: overwritten later (depends on usePolling)
12253
+ ..._opts,
12254
+ // Change format
12255
+ ignored: _opts.ignored ? arrify(_opts.ignored) : arrify([]),
12256
+ awaitWriteFinish: awf === true ? DEF_AWF : typeof awf === "object" ? { ...DEF_AWF, ...awf } : false
12257
+ };
12258
+ if (isIBMi)
12259
+ opts.usePolling = true;
12260
+ if (opts.atomic === void 0)
12261
+ opts.atomic = !opts.usePolling;
12262
+ const envPoll = process.env.CHOKIDAR_USEPOLLING;
12263
+ if (envPoll !== void 0) {
12264
+ const envLower = envPoll.toLowerCase();
12265
+ if (envLower === "false" || envLower === "0")
12266
+ opts.usePolling = false;
12267
+ else if (envLower === "true" || envLower === "1")
12268
+ opts.usePolling = true;
12269
+ else
12270
+ opts.usePolling = !!envLower;
12271
+ }
12272
+ const envInterval = process.env.CHOKIDAR_INTERVAL;
12273
+ if (envInterval)
12274
+ opts.interval = Number.parseInt(envInterval, 10);
12275
+ let readyCalls = 0;
12276
+ this._emitReady = () => {
12277
+ readyCalls++;
12278
+ if (readyCalls >= this._readyCount) {
12279
+ this._emitReady = EMPTY_FN;
12280
+ this._readyEmitted = true;
12281
+ process.nextTick(() => this.emit(EVENTS.READY));
12282
+ }
12283
+ };
12284
+ this._emitRaw = (...args) => this.emit(EVENTS.RAW, ...args);
12285
+ this._boundRemove = this._remove.bind(this);
12286
+ this.options = opts;
12287
+ this._nodeFsHandler = new NodeFsHandler(this);
12288
+ Object.freeze(opts);
12289
+ }
12290
+ _addIgnoredPath(matcher) {
12291
+ if (isMatcherObject(matcher)) {
12292
+ for (const ignored of this._ignoredPaths) {
12293
+ if (isMatcherObject(ignored) && ignored.path === matcher.path && ignored.recursive === matcher.recursive) {
12294
+ return;
12295
+ }
12296
+ }
12297
+ }
12298
+ this._ignoredPaths.add(matcher);
12299
+ }
12300
+ _removeIgnoredPath(matcher) {
12301
+ this._ignoredPaths.delete(matcher);
12302
+ if (typeof matcher === "string") {
12303
+ for (const ignored of this._ignoredPaths) {
12304
+ if (isMatcherObject(ignored) && ignored.path === matcher) {
12305
+ this._ignoredPaths.delete(ignored);
12306
+ }
12307
+ }
12308
+ }
12309
+ }
12310
+ // Public methods
12311
+ /**
12312
+ * Adds paths to be watched on an existing FSWatcher instance.
12313
+ * @param paths_ file or file list. Other arguments are unused
12314
+ */
12315
+ add(paths_, _origAdd, _internal) {
12316
+ const { cwd } = this.options;
12317
+ this.closed = false;
12318
+ this._closePromise = void 0;
12319
+ let paths = unifyPaths(paths_);
12320
+ if (cwd) {
12321
+ paths = paths.map((path25) => {
12322
+ const absPath = getAbsolutePath(path25, cwd);
12323
+ return absPath;
12324
+ });
12325
+ }
12326
+ paths.forEach((path25) => {
12327
+ this._removeIgnoredPath(path25);
12328
+ });
12329
+ this._userIgnored = void 0;
12330
+ if (!this._readyCount)
12331
+ this._readyCount = 0;
12332
+ this._readyCount += paths.length;
12333
+ Promise.all(paths.map(async (path25) => {
12334
+ const res = await this._nodeFsHandler._addToNodeFs(path25, !_internal, void 0, 0, _origAdd);
12335
+ if (res)
12336
+ this._emitReady();
12337
+ return res;
12338
+ })).then((results) => {
12339
+ if (this.closed)
12340
+ return;
12341
+ results.forEach((item) => {
12342
+ if (item)
12343
+ this.add(sp2.dirname(item), sp2.basename(_origAdd || item));
12344
+ });
12345
+ });
12346
+ return this;
12347
+ }
12348
+ /**
12349
+ * Close watchers or start ignoring events from specified paths.
12350
+ */
12351
+ unwatch(paths_) {
12352
+ if (this.closed)
12353
+ return this;
12354
+ const paths = unifyPaths(paths_);
12355
+ const { cwd } = this.options;
12356
+ paths.forEach((path25) => {
12357
+ if (!sp2.isAbsolute(path25) && !this._closers.has(path25)) {
12358
+ if (cwd)
12359
+ path25 = sp2.join(cwd, path25);
12360
+ path25 = sp2.resolve(path25);
12361
+ }
12362
+ this._closePath(path25);
12363
+ this._addIgnoredPath(path25);
12364
+ if (this._watched.has(path25)) {
12365
+ this._addIgnoredPath({
12366
+ path: path25,
12367
+ recursive: true
12368
+ });
12369
+ }
12370
+ this._userIgnored = void 0;
12371
+ });
12372
+ return this;
12373
+ }
12374
+ /**
12375
+ * Close watchers and remove all listeners from watched paths.
12376
+ */
12377
+ close() {
12378
+ if (this._closePromise) {
12379
+ return this._closePromise;
12380
+ }
12381
+ this.closed = true;
12382
+ this.removeAllListeners();
12383
+ const closers = [];
12384
+ this._closers.forEach((closerList) => closerList.forEach((closer) => {
12385
+ const promise = closer();
12386
+ if (promise instanceof Promise)
12387
+ closers.push(promise);
12388
+ }));
12389
+ this._streams.forEach((stream) => stream.destroy());
12390
+ this._userIgnored = void 0;
12391
+ this._readyCount = 0;
12392
+ this._readyEmitted = false;
12393
+ this._watched.forEach((dirent) => dirent.dispose());
12394
+ this._closers.clear();
12395
+ this._watched.clear();
12396
+ this._streams.clear();
12397
+ this._symlinkPaths.clear();
12398
+ this._throttled.clear();
12399
+ this._closePromise = closers.length ? Promise.all(closers).then(() => void 0) : Promise.resolve();
12400
+ return this._closePromise;
12401
+ }
12402
+ /**
12403
+ * Expose list of watched paths
12404
+ * @returns for chaining
12405
+ */
12406
+ getWatched() {
12407
+ const watchList = {};
12408
+ this._watched.forEach((entry, dir) => {
12409
+ const key = this.options.cwd ? sp2.relative(this.options.cwd, dir) : dir;
12410
+ const index = key || ONE_DOT;
12411
+ watchList[index] = entry.getChildren().sort();
12412
+ });
12413
+ return watchList;
12414
+ }
12415
+ emitWithAll(event, args) {
12416
+ this.emit(event, ...args);
12417
+ if (event !== EVENTS.ERROR)
12418
+ this.emit(EVENTS.ALL, event, ...args);
12419
+ }
12420
+ // Common helpers
12421
+ // --------------
12422
+ /**
12423
+ * Normalize and emit events.
12424
+ * Calling _emit DOES NOT MEAN emit() would be called!
12425
+ * @param event Type of event
12426
+ * @param path File or directory path
12427
+ * @param stats arguments to be passed with event
12428
+ * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
12429
+ */
12430
+ async _emit(event, path25, stats) {
12431
+ if (this.closed)
12432
+ return;
12433
+ const opts = this.options;
12434
+ if (isWindows)
12435
+ path25 = sp2.normalize(path25);
12436
+ if (opts.cwd)
12437
+ path25 = sp2.relative(opts.cwd, path25);
12438
+ const args = [path25];
12439
+ if (stats != null)
12440
+ args.push(stats);
12441
+ const awf = opts.awaitWriteFinish;
12442
+ let pw;
12443
+ if (awf && (pw = this._pendingWrites.get(path25))) {
12444
+ pw.lastChange = /* @__PURE__ */ new Date();
12445
+ return this;
12446
+ }
12447
+ if (opts.atomic) {
12448
+ if (event === EVENTS.UNLINK) {
12449
+ this._pendingUnlinks.set(path25, [event, ...args]);
12450
+ setTimeout(() => {
12451
+ this._pendingUnlinks.forEach((entry, path26) => {
12452
+ this.emit(...entry);
12453
+ this.emit(EVENTS.ALL, ...entry);
12454
+ this._pendingUnlinks.delete(path26);
12455
+ });
12456
+ }, typeof opts.atomic === "number" ? opts.atomic : 100);
12457
+ return this;
12458
+ }
12459
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path25)) {
12460
+ event = EVENTS.CHANGE;
12461
+ this._pendingUnlinks.delete(path25);
12462
+ }
12463
+ }
12464
+ if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
12465
+ const awfEmit = (err, stats2) => {
12466
+ if (err) {
12467
+ event = EVENTS.ERROR;
12468
+ args[0] = err;
12469
+ this.emitWithAll(event, args);
12470
+ } else if (stats2) {
12471
+ if (args.length > 1) {
12472
+ args[1] = stats2;
12473
+ } else {
12474
+ args.push(stats2);
12475
+ }
12476
+ this.emitWithAll(event, args);
12477
+ }
12478
+ };
12479
+ this._awaitWriteFinish(path25, awf.stabilityThreshold, event, awfEmit);
12480
+ return this;
12481
+ }
12482
+ if (event === EVENTS.CHANGE) {
12483
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path25, 50);
12484
+ if (isThrottled)
12485
+ return this;
12486
+ }
12487
+ if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
12488
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path25) : path25;
12489
+ let stats2;
12490
+ try {
12491
+ stats2 = await (0, import_promises3.stat)(fullPath);
12492
+ } catch (err) {
12493
+ }
12494
+ if (!stats2 || this.closed)
12495
+ return;
12496
+ args.push(stats2);
12497
+ }
12498
+ this.emitWithAll(event, args);
12499
+ return this;
12500
+ }
12501
+ /**
12502
+ * Common handler for errors
12503
+ * @returns The error if defined, otherwise the value of the FSWatcher instance's `closed` flag
12504
+ */
12505
+ _handleError(error) {
12506
+ const code = error && error.code;
12507
+ if (error && code !== "ENOENT" && code !== "ENOTDIR" && (!this.options.ignorePermissionErrors || code !== "EPERM" && code !== "EACCES")) {
12508
+ this.emit(EVENTS.ERROR, error);
12509
+ }
12510
+ return error || this.closed;
12511
+ }
12512
+ /**
12513
+ * Helper utility for throttling
12514
+ * @param actionType type being throttled
12515
+ * @param path being acted upon
12516
+ * @param timeout duration of time to suppress duplicate actions
12517
+ * @returns tracking object or false if action should be suppressed
12518
+ */
12519
+ _throttle(actionType, path25, timeout) {
12520
+ if (!this._throttled.has(actionType)) {
12521
+ this._throttled.set(actionType, /* @__PURE__ */ new Map());
12522
+ }
12523
+ const action = this._throttled.get(actionType);
12524
+ if (!action)
12525
+ throw new Error("invalid throttle");
12526
+ const actionPath = action.get(path25);
12527
+ if (actionPath) {
12528
+ actionPath.count++;
12529
+ return false;
12530
+ }
12531
+ let timeoutObject;
12532
+ const clear = () => {
12533
+ const item = action.get(path25);
12534
+ const count = item ? item.count : 0;
12535
+ action.delete(path25);
12536
+ clearTimeout(timeoutObject);
12537
+ if (item)
12538
+ clearTimeout(item.timeoutObject);
12539
+ return count;
12540
+ };
12541
+ timeoutObject = setTimeout(clear, timeout);
12542
+ const thr = { timeoutObject, clear, count: 0 };
12543
+ action.set(path25, thr);
12544
+ return thr;
12545
+ }
12546
+ _incrReadyCount() {
12547
+ return this._readyCount++;
12548
+ }
12549
+ /**
12550
+ * Awaits write operation to finish.
12551
+ * Polls a newly created file for size variations. When files size does not change for 'threshold' milliseconds calls callback.
12552
+ * @param path being acted upon
12553
+ * @param threshold Time in milliseconds a file size must be fixed before acknowledging write OP is finished
12554
+ * @param event
12555
+ * @param awfEmit Callback to be called when ready for event to be emitted.
12556
+ */
12557
+ _awaitWriteFinish(path25, threshold, event, awfEmit) {
12558
+ const awf = this.options.awaitWriteFinish;
12559
+ if (typeof awf !== "object")
12560
+ return;
12561
+ const pollInterval = awf.pollInterval;
12562
+ let timeoutHandler;
12563
+ let fullPath = path25;
12564
+ if (this.options.cwd && !sp2.isAbsolute(path25)) {
12565
+ fullPath = sp2.join(this.options.cwd, path25);
12566
+ }
12567
+ const now = /* @__PURE__ */ new Date();
12568
+ const writes = this._pendingWrites;
12569
+ function awaitWriteFinishFn(prevStat) {
12570
+ (0, import_node_fs2.stat)(fullPath, (err, curStat) => {
12571
+ if (err || !writes.has(path25)) {
12572
+ if (err && err.code !== "ENOENT")
12573
+ awfEmit(err);
12574
+ return;
12575
+ }
12576
+ const now2 = Number(/* @__PURE__ */ new Date());
12577
+ if (prevStat && curStat.size !== prevStat.size) {
12578
+ writes.get(path25).lastChange = now2;
12579
+ }
12580
+ const pw = writes.get(path25);
12581
+ const df = now2 - pw.lastChange;
12582
+ if (df >= threshold) {
12583
+ writes.delete(path25);
12584
+ awfEmit(void 0, curStat);
12585
+ } else {
12586
+ timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
12587
+ }
12588
+ });
12589
+ }
12590
+ if (!writes.has(path25)) {
12591
+ writes.set(path25, {
12592
+ lastChange: now,
12593
+ cancelWait: () => {
12594
+ writes.delete(path25);
12595
+ clearTimeout(timeoutHandler);
12596
+ return event;
12597
+ }
12598
+ });
12599
+ timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval);
12600
+ }
12601
+ }
12602
+ /**
12603
+ * Determines whether user has asked to ignore this path.
12604
+ */
12605
+ _isIgnored(path25, stats) {
12606
+ if (this.options.atomic && DOT_RE.test(path25))
12607
+ return true;
12608
+ if (!this._userIgnored) {
12609
+ const { cwd } = this.options;
12610
+ const ign = this.options.ignored;
12611
+ const ignored = (ign || []).map(normalizeIgnored(cwd));
12612
+ const ignoredPaths = [...this._ignoredPaths];
12613
+ const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
12614
+ this._userIgnored = anymatch(list, void 0);
12615
+ }
12616
+ return this._userIgnored(path25, stats);
12617
+ }
12618
+ _isntIgnored(path25, stat4) {
12619
+ return !this._isIgnored(path25, stat4);
12620
+ }
12621
+ /**
12622
+ * Provides a set of common helpers and properties relating to symlink handling.
12623
+ * @param path file or directory pattern being watched
12624
+ */
12625
+ _getWatchHelpers(path25) {
12626
+ return new WatchHelper(path25, this.options.followSymlinks, this);
12627
+ }
12628
+ // Directory helpers
12629
+ // -----------------
12630
+ /**
12631
+ * Provides directory tracking objects
12632
+ * @param directory path of the directory
12633
+ */
12634
+ _getWatchedDir(directory) {
12635
+ const dir = sp2.resolve(directory);
12636
+ if (!this._watched.has(dir))
12637
+ this._watched.set(dir, new DirEntry(dir, this._boundRemove));
12638
+ return this._watched.get(dir);
12639
+ }
12640
+ // File helpers
12641
+ // ------------
12642
+ /**
12643
+ * Check for read permissions: https://stackoverflow.com/a/11781404/1358405
12644
+ */
12645
+ _hasReadPermissions(stats) {
12646
+ if (this.options.ignorePermissionErrors)
12647
+ return true;
12648
+ return Boolean(Number(stats.mode) & 256);
12649
+ }
12650
+ /**
12651
+ * Handles emitting unlink events for
12652
+ * files and directories, and via recursion, for
12653
+ * files and directories within directories that are unlinked
12654
+ * @param directory within which the following item is located
12655
+ * @param item base path of item/directory
12656
+ */
12657
+ _remove(directory, item, isDirectory) {
12658
+ const path25 = sp2.join(directory, item);
12659
+ const fullPath = sp2.resolve(path25);
12660
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path25) || this._watched.has(fullPath);
12661
+ if (!this._throttle("remove", path25, 100))
12662
+ return;
12663
+ if (!isDirectory && this._watched.size === 1) {
12664
+ this.add(directory, item, true);
12665
+ }
12666
+ const wp = this._getWatchedDir(path25);
12667
+ const nestedDirectoryChildren = wp.getChildren();
12668
+ nestedDirectoryChildren.forEach((nested) => this._remove(path25, nested));
12669
+ const parent = this._getWatchedDir(directory);
12670
+ const wasTracked = parent.has(item);
12671
+ parent.remove(item);
12672
+ if (this._symlinkPaths.has(fullPath)) {
12673
+ this._symlinkPaths.delete(fullPath);
12674
+ }
12675
+ let relPath = path25;
12676
+ if (this.options.cwd)
12677
+ relPath = sp2.relative(this.options.cwd, path25);
12678
+ if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
12679
+ const event = this._pendingWrites.get(relPath).cancelWait();
12680
+ if (event === EVENTS.ADD)
12681
+ return;
12682
+ }
12683
+ this._watched.delete(path25);
12684
+ this._watched.delete(fullPath);
12685
+ const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
12686
+ if (wasTracked && !this._isIgnored(path25))
12687
+ this._emit(eventName, path25);
12688
+ this._closePath(path25);
12689
+ }
12690
+ /**
12691
+ * Closes all watchers for a path
12692
+ */
12693
+ _closePath(path25) {
12694
+ this._closeFile(path25);
12695
+ const dir = sp2.dirname(path25);
12696
+ this._getWatchedDir(dir).remove(sp2.basename(path25));
12697
+ }
12698
+ /**
12699
+ * Closes only file-specific watchers
12700
+ */
12701
+ _closeFile(path25) {
12702
+ const closers = this._closers.get(path25);
12703
+ if (!closers)
12704
+ return;
12705
+ closers.forEach((closer) => closer());
12706
+ this._closers.delete(path25);
12707
+ }
12708
+ _addPathCloser(path25, closer) {
12709
+ if (!closer)
12710
+ return;
12711
+ let list = this._closers.get(path25);
12712
+ if (!list) {
12713
+ list = [];
12714
+ this._closers.set(path25, list);
12715
+ }
12716
+ list.push(closer);
12717
+ }
12718
+ _readdirp(root, opts) {
12719
+ if (this.closed)
12720
+ return;
12721
+ const options = { type: EVENTS.ALL, alwaysStat: true, lstat: true, ...opts, depth: 0 };
12722
+ let stream = readdirp(root, options);
12723
+ this._streams.add(stream);
12724
+ stream.once(STR_CLOSE, () => {
12725
+ stream = void 0;
12726
+ });
12727
+ stream.once(STR_END, () => {
12728
+ if (stream) {
12729
+ this._streams.delete(stream);
12730
+ stream = void 0;
12731
+ }
12732
+ });
12733
+ return stream;
12734
+ }
12735
+ };
12736
+ function watch(paths, options = {}) {
12737
+ const watcher = new FSWatcher(options);
12738
+ watcher.add(paths);
12739
+ return watcher;
12740
+ }
12741
+ var chokidar_default = { watch, FSWatcher };
12742
+
12743
+ // src/watcher/file-watcher.ts
12744
+ var path20 = __toESM(require("path"), 1);
12745
+ var FileWatcher = class {
12746
+ watcher = null;
12747
+ projectRoot;
12748
+ config;
12749
+ host;
12750
+ configPath;
12751
+ pendingChanges = /* @__PURE__ */ new Map();
12752
+ debounceTimer = null;
12753
+ debounceMs = 1e3;
12754
+ onChanges = null;
12755
+ constructor(projectRoot, config, host = "opencode", options = {}) {
12756
+ this.projectRoot = projectRoot;
12757
+ this.config = config;
12758
+ this.host = host;
12759
+ this.configPath = options.configPath;
12760
+ }
12761
+ start(handler) {
12762
+ if (this.watcher) {
12763
+ return;
12764
+ }
12765
+ this.onChanges = handler;
12766
+ const ignoreFilter = createIgnoreFilter(this.projectRoot);
12767
+ const watchTargets = this.configPath ? [this.projectRoot, this.configPath] : this.projectRoot;
12768
+ this.watcher = chokidar_default.watch(watchTargets, {
12769
+ ignored: (filePath) => {
12770
+ const relativePath = path20.relative(this.projectRoot, filePath);
12771
+ if (!relativePath) return false;
12772
+ if (this.isProjectConfigPathOrAncestor(relativePath)) {
12773
+ return false;
12774
+ }
12775
+ if (hasFilteredPathSegment(relativePath, path20.sep)) {
12776
+ return true;
12777
+ }
12778
+ if (isRestrictedDirectory(relativePath, path20.sep)) {
12779
+ return true;
12780
+ }
12781
+ if (ignoreFilter.ignores(relativePath)) {
12782
+ return true;
12783
+ }
12784
+ return false;
12785
+ },
12786
+ persistent: true,
12787
+ ignoreInitial: true,
12788
+ awaitWriteFinish: {
12789
+ stabilityThreshold: 300,
12790
+ pollInterval: 100
12791
+ }
12792
+ });
12793
+ this.watcher.on("error", (error) => {
12794
+ const err = error instanceof Error ? error : null;
12795
+ if (err?.code === "EPERM" || err?.code === "EACCES") {
12796
+ return;
12797
+ }
12798
+ console.error("[codebase-index] Watcher error:", err?.message ?? error);
12799
+ });
12800
+ this.watcher.on("add", (filePath) => this.handleChange("add", filePath));
12801
+ this.watcher.on("change", (filePath) => this.handleChange("change", filePath));
12802
+ this.watcher.on("unlink", (filePath) => this.handleChange("unlink", filePath));
12803
+ }
12804
+ handleChange(type, filePath) {
12805
+ if (this.isProjectConfigPath(filePath)) {
12806
+ this.pendingChanges.set(filePath, type);
12807
+ this.scheduleFlush();
12808
+ return;
12809
+ }
12810
+ const includePatterns = [...this.config.include, ...this.config.additionalInclude ?? []];
12811
+ if (!shouldIncludeFile(
12812
+ filePath,
12813
+ this.projectRoot,
12814
+ includePatterns,
12815
+ this.config.exclude,
12816
+ createIgnoreFilter(this.projectRoot)
12817
+ )) {
12818
+ return;
12819
+ }
12820
+ this.pendingChanges.set(filePath, type);
12821
+ this.scheduleFlush();
12822
+ }
12823
+ isProjectConfigPath(filePath) {
12824
+ const relativePath = path20.relative(this.projectRoot, filePath);
12825
+ const normalizedRelativePath = path20.normalize(relativePath);
12826
+ return this.getProjectConfigRelativePaths().some((configPath) => configPath === normalizedRelativePath);
12827
+ }
12828
+ isProjectConfigPathOrAncestor(relativePath) {
12829
+ const normalizedRelativePath = path20.normalize(relativePath);
12830
+ return this.getProjectConfigRelativePaths().some(
12831
+ (configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path20.sep}`)
12832
+ );
12833
+ }
12834
+ getProjectConfigRelativePaths() {
12835
+ if (this.configPath) {
12836
+ return [path20.normalize(path20.relative(this.projectRoot, this.configPath))];
12837
+ }
12838
+ return [
12839
+ resolveProjectConfigPath(this.projectRoot, this.host),
12840
+ resolveWritableProjectConfigPath(this.projectRoot, this.host)
12841
+ ].map((configPath) => path20.normalize(path20.relative(this.projectRoot, configPath)));
12842
+ }
12843
+ scheduleFlush() {
12844
+ if (this.debounceTimer) {
12845
+ clearTimeout(this.debounceTimer);
12846
+ }
12847
+ this.debounceTimer = setTimeout(() => {
12848
+ this.flush();
12849
+ }, this.debounceMs);
12850
+ }
12851
+ async flush() {
12852
+ if (this.pendingChanges.size === 0 || !this.onChanges) {
12853
+ return;
12854
+ }
12855
+ const changes = Array.from(this.pendingChanges.entries()).map(
12856
+ ([path25, type]) => ({ path: path25, type })
12857
+ );
12858
+ this.pendingChanges.clear();
12859
+ try {
12860
+ await this.onChanges(changes);
12861
+ } catch (error) {
12862
+ console.error("Error handling file changes:", error);
12863
+ }
12864
+ }
12865
+ stop() {
12866
+ if (this.debounceTimer) {
12867
+ clearTimeout(this.debounceTimer);
12868
+ this.debounceTimer = null;
12869
+ }
12870
+ if (this.watcher) {
12871
+ this.watcher.close();
12872
+ this.watcher = null;
12873
+ }
12874
+ this.pendingChanges.clear();
12875
+ this.onChanges = null;
12876
+ }
12877
+ isRunning() {
12878
+ return this.watcher !== null;
12879
+ }
12880
+ };
12881
+
12882
+ // src/watcher/git-head-watcher.ts
12883
+ var path21 = __toESM(require("path"), 1);
12884
+ var GitHeadWatcher = class {
12885
+ watcher = null;
12886
+ projectRoot;
12887
+ currentBranch = null;
12888
+ onBranchChange = null;
12889
+ debounceTimer = null;
12890
+ debounceMs = 100;
12891
+ // Short debounce for git operations
12892
+ constructor(projectRoot) {
12893
+ this.projectRoot = projectRoot;
12894
+ }
12895
+ start(handler) {
12896
+ if (this.watcher) {
12897
+ return;
12898
+ }
12899
+ if (!isGitRepo(this.projectRoot)) {
12900
+ return;
12901
+ }
12902
+ this.onBranchChange = handler;
12903
+ this.currentBranch = getCurrentBranch(this.projectRoot);
12904
+ const headPath = getHeadPath(this.projectRoot);
12905
+ const refsPath = path21.join(this.projectRoot, ".git", "refs", "heads");
12906
+ this.watcher = chokidar_default.watch([headPath, refsPath], {
12907
+ persistent: true,
12908
+ ignoreInitial: true,
12909
+ awaitWriteFinish: {
12910
+ stabilityThreshold: 50,
12911
+ pollInterval: 10
12912
+ }
12913
+ });
12914
+ this.watcher.on("change", () => this.handleHeadChange());
12915
+ this.watcher.on("add", () => this.handleHeadChange());
12916
+ }
12917
+ handleHeadChange() {
12918
+ if (this.debounceTimer) {
12919
+ clearTimeout(this.debounceTimer);
12920
+ }
12921
+ this.debounceTimer = setTimeout(() => {
12922
+ this.checkBranchChange();
12923
+ }, this.debounceMs);
12924
+ }
12925
+ async checkBranchChange() {
12926
+ const newBranch = getCurrentBranch(this.projectRoot);
12927
+ if (newBranch && newBranch !== this.currentBranch && this.onBranchChange) {
12928
+ const oldBranch = this.currentBranch;
12929
+ this.currentBranch = newBranch;
12930
+ try {
12931
+ await this.onBranchChange(oldBranch, newBranch);
12932
+ } catch (error) {
12933
+ console.error("Error handling branch change:", error);
12934
+ }
12935
+ } else if (newBranch) {
12936
+ this.currentBranch = newBranch;
12937
+ }
12938
+ }
12939
+ getCurrentBranch() {
12940
+ return this.currentBranch;
12941
+ }
12942
+ stop() {
12943
+ if (this.debounceTimer) {
12944
+ clearTimeout(this.debounceTimer);
12945
+ this.debounceTimer = null;
12946
+ }
12947
+ if (this.watcher) {
12948
+ this.watcher.close();
12949
+ this.watcher = null;
12950
+ }
12951
+ this.onBranchChange = null;
12952
+ }
12953
+ isRunning() {
12954
+ return this.watcher !== null;
12955
+ }
12956
+ };
12957
+
12958
+ // src/watcher/index.ts
12959
+ var BackgroundReindexer = class {
12960
+ constructor(runIndex) {
12961
+ this.runIndex = runIndex;
12962
+ }
12963
+ runIndex;
12964
+ running = false;
12965
+ pending = false;
12966
+ stopped = false;
12967
+ request() {
12968
+ if (this.stopped) {
12969
+ return;
12970
+ }
12971
+ this.pending = true;
12972
+ this.drain();
12973
+ }
12974
+ stop() {
12975
+ this.stopped = true;
12976
+ this.pending = false;
12977
+ }
12978
+ drain() {
12979
+ if (this.stopped || this.running || !this.pending) {
12980
+ return;
12981
+ }
12982
+ this.pending = false;
12983
+ this.running = true;
12984
+ void this.run();
12985
+ }
12986
+ async run() {
12987
+ try {
12988
+ await this.runIndex();
12989
+ } catch (error) {
12990
+ console.error("[codebase-index] Background reindex failed:", error);
12991
+ } finally {
12992
+ this.running = false;
12993
+ this.drain();
12994
+ }
12995
+ }
12996
+ };
12997
+ function createWatcherWithIndexer(getIndexer, projectRoot, config, host = "opencode", options = {}) {
12998
+ const fileWatcher = new FileWatcher(projectRoot, config, host, options);
12999
+ const backgroundReindexer = new BackgroundReindexer(async () => {
13000
+ await getIndexer().index();
13001
+ });
13002
+ fileWatcher.start(async (changes) => {
13003
+ const hasAddOrChange = changes.some(
13004
+ (c) => c.type === "add" || c.type === "change"
13005
+ );
13006
+ const hasDelete = changes.some((c) => c.type === "unlink");
13007
+ if (hasAddOrChange || hasDelete) {
13008
+ const configPaths = getConfigPaths(projectRoot, host, options);
13009
+ if (changes.some((change) => configPaths.includes(pathNormalize(change.path)))) {
13010
+ const parsedConfig = options.configPath ? parseConfig(loadConfigFile(options.configPath)) : void 0;
13011
+ refreshIndexerForDirectory(projectRoot, host, parsedConfig);
13012
+ }
13013
+ backgroundReindexer.request();
13014
+ }
13015
+ });
13016
+ let gitWatcher = null;
13017
+ if (isGitRepo(projectRoot)) {
13018
+ gitWatcher = new GitHeadWatcher(projectRoot);
13019
+ gitWatcher.start(async (oldBranch, newBranch) => {
13020
+ console.log(`Branch changed: ${oldBranch ?? "(none)"} -> ${newBranch}`);
13021
+ backgroundReindexer.request();
13022
+ });
13023
+ }
13024
+ return {
13025
+ fileWatcher,
13026
+ gitWatcher,
13027
+ stop() {
13028
+ backgroundReindexer.stop();
13029
+ fileWatcher.stop();
13030
+ gitWatcher?.stop();
13031
+ }
13032
+ };
13033
+ }
13034
+ function pathNormalize(value) {
13035
+ return value.split("\\").join("/");
13036
+ }
13037
+ function getConfigPaths(projectRoot, host, options) {
13038
+ if (options.configPath) {
13039
+ return [pathNormalize(options.configPath)];
13040
+ }
13041
+ return [
13042
+ resolveProjectConfigPath(projectRoot, host),
13043
+ resolveWritableProjectConfigPath(projectRoot, host)
13044
+ ].map((configPath) => pathNormalize(configPath));
13045
+ }
13046
+
13047
+ // src/tools/visualize/activity.ts
13048
+ var import_child_process3 = require("child_process");
13049
+ var path22 = __toESM(require("path"), 1);
13050
+ function attachRecentActivity(data, projectRoot) {
13051
+ const activity = readGitActivity(projectRoot);
13052
+ const changes = activity.size > 0 ? buildGitChanges(data, activity, projectRoot) : buildGraphChanges(data);
13053
+ return {
13054
+ ...data,
13055
+ changes
13056
+ };
13057
+ }
13058
+ function readGitActivity(projectRoot) {
13059
+ try {
13060
+ const output = (0, import_child_process3.execFileSync)(
13061
+ "git",
13062
+ ["-C", projectRoot, "log", "--since=90.days", "--numstat", "--date=short", "--pretty=format:__COMMIT__%x09%h%x09%ad%x09%s"],
13063
+ { encoding: "utf8", maxBuffer: 8 * 1024 * 1024, stdio: ["ignore", "pipe", "ignore"] }
13064
+ );
13065
+ return parseGitActivity(output);
13066
+ } catch {
13067
+ return /* @__PURE__ */ new Map();
13068
+ }
13069
+ }
13070
+ function parseGitActivity(output) {
13071
+ const activity = /* @__PURE__ */ new Map();
13072
+ let latestHash = "";
13073
+ let latestDate = "";
13074
+ let latestSubject = "";
13075
+ for (const line of output.split(/\r?\n/)) {
13076
+ if (!line) continue;
13077
+ if (line.startsWith("__COMMIT__ ")) {
13078
+ const [, hash = "", date = "", subject = ""] = line.split(" ");
13079
+ latestHash = hash;
13080
+ latestDate = date;
13081
+ latestSubject = subject;
13082
+ continue;
13083
+ }
13084
+ const [addedRaw, deletedRaw, filePath] = line.split(" ");
13085
+ if (!filePath || addedRaw === "-" || deletedRaw === "-") continue;
13086
+ const churn = Number(addedRaw) + Number(deletedRaw);
13087
+ if (!Number.isFinite(churn) || churn <= 0) continue;
13088
+ const normalizedPath = normalizePath3(filePath);
13089
+ const previous = activity.get(normalizedPath);
13090
+ activity.set(normalizedPath, {
13091
+ churn: (previous?.churn ?? 0) + churn,
13092
+ commits: (previous?.commits ?? 0) + 1,
13093
+ latestDate: previous?.latestDate ?? latestDate,
13094
+ latestHash: previous?.latestHash ?? latestHash,
13095
+ latestSubject: previous?.latestSubject ?? latestSubject
13096
+ });
13097
+ }
13098
+ return activity;
13099
+ }
13100
+ function buildGitChanges(data, activity, projectRoot) {
13101
+ const byModule = /* @__PURE__ */ new Map();
13102
+ for (const node of data.nodes) {
13103
+ const fileActivity = activity.get(toGitRelativePath(projectRoot, node.filePath));
13104
+ if (!fileActivity) continue;
13105
+ const current = byModule.get(node.moduleId) ?? {
13106
+ moduleId: node.moduleId,
13107
+ filePaths: /* @__PURE__ */ new Set(),
13108
+ churn: 0,
13109
+ commits: 0,
13110
+ latestDate: fileActivity.latestDate,
13111
+ latestHash: fileActivity.latestHash,
13112
+ latestSubject: fileActivity.latestSubject
13113
+ };
13114
+ if (!current.filePaths.has(node.filePath)) {
13115
+ current.filePaths.add(node.filePath);
13116
+ current.churn += fileActivity.churn;
13117
+ current.commits += fileActivity.commits;
13118
+ }
13119
+ if (fileActivity.latestDate > current.latestDate) {
13120
+ current.latestDate = fileActivity.latestDate;
13121
+ current.latestHash = fileActivity.latestHash;
13122
+ current.latestSubject = fileActivity.latestSubject;
13123
+ }
13124
+ byModule.set(node.moduleId, current);
13125
+ }
13126
+ return [...byModule.values()].sort((a, b) => scoreModule(data, b.moduleId, b.churn) - scoreModule(data, a.moduleId, a.churn)).slice(0, 12).map((item, index) => toGitChange(data, item, index));
13127
+ }
13128
+ function buildGraphChanges(data) {
13129
+ return data.modules.map((module3) => {
13130
+ const calls = moduleCallCount(data, module3.id);
13131
+ const focusNode = strongestNode(data, module3.id);
13132
+ const risk = riskFor(calls, module3.symbolCount);
13133
+ return {
13134
+ id: `graph-${module3.id}`,
13135
+ title: `${module3.label} is structurally important`,
13136
+ kind: risk === "high" ? "risk" : "hot",
13137
+ when: "graph-derived",
13138
+ source: "call graph",
13139
+ intent: "load-bearing path",
13140
+ summary: `${module3.symbolCount} symbols with ${calls} cross-module calls in this slice.`,
13141
+ why: "No recent Git history was available, so this highlights modules whose call relationships make them expensive to misunderstand during onboarding.",
13142
+ calls,
13143
+ churn: 0,
13144
+ risk,
13145
+ moduleId: module3.id,
13146
+ focusNodeId: focusNode?.id,
13147
+ filePaths: [...new Set(data.nodes.filter((node) => node.moduleId === module3.id).map((node) => node.filePath))].slice(0, 6)
13148
+ };
13149
+ }).sort((a, b) => b.calls - a.calls).slice(0, 8);
13150
+ }
13151
+ function toGitChange(data, item, index) {
13152
+ const module3 = data.modules.find((candidate) => candidate.id === item.moduleId);
13153
+ const label = module3?.label ?? item.moduleId;
13154
+ const calls = moduleCallCount(data, item.moduleId);
13155
+ const focusNode = strongestNode(data, item.moduleId);
13156
+ const risk = riskFor(calls, item.churn);
13157
+ const intent = inferIntent(item.latestSubject);
13158
+ return {
13159
+ id: `git-${item.moduleId}-${index}`,
13160
+ title: `${label} moved recently`,
13161
+ kind: risk === "high" ? "risk" : "hot",
13162
+ when: item.latestDate || "recently",
13163
+ source: item.latestHash ? `commit ${item.latestHash}` : "git history",
13164
+ intent,
13165
+ summary: `${item.churn} changed lines across ${item.filePaths.size} indexed files. Latest: ${item.latestSubject || "no commit subject"}.`,
13166
+ why: `${label} is both changing and connected to ${calls} call edges. Start here to understand whether the current graph is new work, load-bearing behavior, or legacy surface area.`,
13167
+ calls,
13168
+ churn: item.churn,
13169
+ risk,
13170
+ moduleId: item.moduleId,
13171
+ focusNodeId: focusNode?.id,
13172
+ filePaths: [...item.filePaths].slice(0, 8)
13173
+ };
13174
+ }
13175
+ function scoreModule(data, moduleId, churn) {
13176
+ return churn + moduleCallCount(data, moduleId) * 4;
13177
+ }
13178
+ function moduleCallCount(data, moduleId) {
13179
+ const moduleEdgeCount = data.moduleEdges.filter((edge) => edge.source === moduleId || edge.target === moduleId).reduce((total, edge) => total + edge.weight, 0);
13180
+ if (moduleEdgeCount > 0) return moduleEdgeCount;
13181
+ const nodeModules = new Map(data.nodes.map((node) => [node.id, node.moduleId]));
13182
+ return data.edges.filter((edge) => nodeModules.get(edge.source) === moduleId || nodeModules.get(edge.target) === moduleId).length;
13183
+ }
13184
+ function strongestNode(data, moduleId) {
13185
+ const degree = /* @__PURE__ */ new Map();
13186
+ for (const edge of data.edges) {
13187
+ degree.set(edge.source, (degree.get(edge.source) ?? 0) + 1);
13188
+ degree.set(edge.target, (degree.get(edge.target) ?? 0) + 1);
13189
+ }
13190
+ return data.nodes.filter((node) => node.moduleId === moduleId).sort((a, b) => (degree.get(b.id) ?? 0) - (degree.get(a.id) ?? 0))[0];
13191
+ }
13192
+ function riskFor(calls, churnOrSymbols) {
13193
+ if (calls >= 20 || churnOrSymbols >= 250) return "high";
13194
+ if (calls >= 8 || churnOrSymbols >= 80) return "medium";
13195
+ return "low";
13196
+ }
13197
+ function inferIntent(subject) {
13198
+ const normalized = subject.toLowerCase();
13199
+ if (normalized.includes("fix")) return "stability";
13200
+ if (normalized.includes("test")) return "verification";
13201
+ if (normalized.includes("refactor")) return "refactor";
13202
+ if (normalized.includes("visual")) return "visualization";
13203
+ if (normalized.includes("call") || normalized.includes("graph")) return "call graph";
13204
+ if (normalized.includes("config")) return "configuration";
13205
+ return "recent work";
13206
+ }
13207
+ function normalizePath3(filePath) {
13208
+ return filePath.replace(/\\/g, "/");
13209
+ }
13210
+ function toGitRelativePath(projectRoot, filePath) {
13211
+ const relativePath = path22.isAbsolute(filePath) ? path22.relative(projectRoot, filePath) : filePath;
13212
+ return normalizePath3(relativePath);
13213
+ }
13214
+
13215
+ // src/tools/visualize/template.ts
13216
+ function generateVisualizationHtml(data) {
13217
+ const jsonData = JSON.stringify(data).replace(/</g, "\\u003c").replace(/>/g, "\\u003e");
13218
+ return `<!DOCTYPE html>
13219
+ <html lang="en">
13220
+ <head>
13221
+ <meta charset="UTF-8">
13222
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
13223
+ <meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline'; script-src 'unsafe-inline'">
13224
+ <title>Call Graph Visualization</title>
13225
+ <style>
13226
+ *{box-sizing:border-box}body{margin:0;background:#0d1118;color:#c4cfe6;font-family:Inter,-apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif;overflow:hidden}
13227
+ #app{height:100vh;display:grid;grid-template-columns:300px minmax(0,1fr) 320px;grid-template-rows:auto 1fr;gap:12px;padding:14px}
13228
+ .top{grid-column:1/-1;display:flex;align-items:center;gap:10px;min-width:0}.search{width:300px;max-width:40vw;padding:8px 11px;border:1px solid #203149;border-radius:6px;background:#141e2e;color:#c4cfe6;outline:none}.search:focus{border-color:#5577d8}
13229
+ .tabs{display:flex;gap:6px;min-width:0;overflow:auto}.tab{border:1px solid #203149;background:#141e2e;color:#6f83a4;border-radius:6px;padding:7px 10px;cursor:pointer;white-space:nowrap}.tab.active{border-color:#5577d8;color:#fff;background:#111c30}
13230
+ .stats{margin-left:auto;color:#334966;font-size:12px;white-space:nowrap;border:1px solid #203149;background:#141e2e;border-radius:6px;padding:7px 11px}.stats b{color:#7f94b8}
13231
+ .panel{background:rgba(13,17,24,.86);border:1px solid #203149;border-radius:8px;min-height:0}.left,.right{padding:14px;overflow:auto;overflow-x:hidden}.title{font-size:10px;text-transform:uppercase;letter-spacing:.1em;color:#334966;margin:0 0 10px}
13232
+ .results,.list{display:grid;gap:7px;min-width:0}.result,.row,.node,.change{min-width:0;border:1px solid #203149;background:#101722;border-radius:7px;padding:9px 10px;cursor:pointer;overflow:hidden}.result:hover,.row:hover,.node:hover,.change:hover{border-color:#375171;background:#121c2a}.result.active,.node.active,.change.active{border-color:#8b6cf6;background:#15172a}
13233
+ .name{font-family:"SF Mono","Cascadia Code",Consolas,monospace;font-size:12px;color:#e0e6f7;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.meta{min-width:0;max-width:100%;font-size:11px;color:#6f83a4;margin-top:4px;display:flex;gap:8px;align-items:center;overflow:hidden}.meta span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.badge{font-family:"SF Mono",Consolas,monospace;font-size:10px;border-radius:4px;padding:2px 5px;background:#1b2940;color:#7890ac;flex:0 0 auto}.badge.function,.badge.fn{background:#18264a;color:#7896ff}.badge.class{background:#271f4b;color:#a48cff}.badge.type,.badge.interface{background:#172d36;color:#5fc4d4}.badge.enum{background:#382812;color:#e3a348}
13234
+ .main{position:relative;overflow:hidden;padding:14px}.flow{position:relative;height:100%;display:grid;grid-template-columns:minmax(220px,1fr) minmax(300px,1.15fr) minmax(220px,1fr);gap:24px;align-items:center}.lane{height:min(640px,calc(100vh - 150px));min-height:360px;border:1px solid #203149;border-radius:8px;padding:14px;display:flex;flex-direction:column;gap:10px;overflow:auto;background:rgba(16,23,34,.5)}.lane .node,.lane .row{flex:0 0 auto}.lane h3,.module-symbols h3{margin:0 0 4px;font-size:10px;text-transform:uppercase;letter-spacing:.1em;color:#334966;text-align:center}.center{align-self:center;min-width:0}.center .node{cursor:default;border-color:#8b6cf6;background:#15172a;box-shadow:0 0 0 1px rgba(139,108,246,.2)}.module-board{height:100%;display:grid;grid-template-rows:auto 260px minmax(260px,1fr);gap:12px;overflow:hidden}.module-summary .node{cursor:default;border-color:#8b6cf6;background:#15172a}.module-lanes{min-height:0;display:grid;grid-template-columns:1fr 1fr;gap:12px}.module-board .lane{height:auto;min-height:0}.module-symbols{min-height:0;border:1px solid #203149;border-radius:8px;padding:14px;background:rgba(16,23,34,.5);display:flex;flex-direction:column;gap:10px;overflow:hidden}.module-symbols .list{display:block;overflow:auto;min-height:0}.module-symbols .row{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;margin:0 0 6px;padding:8px 10px}.module-symbols .row .meta{margin-top:0;justify-content:flex-end}.weight{margin-left:auto;color:#334966;font-variant-numeric:tabular-nums}.edges{position:absolute;inset:0;pointer-events:none;z-index:1}.node{position:relative;z-index:2}
13235
+ .details{display:grid;gap:8px}.kv{display:flex;justify-content:space-between;gap:12px;border-bottom:1px solid #17243a;padding-bottom:7px;color:#6f83a4;font-size:12px}.kv span:first-child{color:#334966;text-transform:uppercase;font-size:10px;letter-spacing:.08em}.mini{display:flex;align-items:center;gap:8px;color:#6f83a4;font-size:12px}.dot{width:8px;height:8px;border-radius:50%;flex:0 0 auto}.empty{height:100%;display:grid;place-items:center;color:#334966;font-size:12px;text-align:center;line-height:1.5}.guide{border:1px solid #203149;border-radius:7px;background:#101722;padding:10px;color:#6f83a4;font-size:12px;line-height:1.45}.guide b{display:block;color:#e0e6f7;margin-bottom:4px}.guide span{display:block}
13236
+ .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:10px;align-content:start}.rank{font-size:20px;color:#8b6cf6;font-variant-numeric:tabular-nums}.change-grid{display:grid;grid-template-columns:minmax(280px,.95fr) minmax(320px,1.2fr);gap:12px;height:100%;min-height:0}.change-list,.why{display:flex;flex-direction:column;gap:9px;min-height:0;overflow:auto;overflow-x:hidden}.change-top{display:flex;align-items:center;gap:8px;margin-bottom:7px;min-width:0}.pill{font-size:10px;text-transform:uppercase;letter-spacing:.08em;border:1px solid #203149;border-radius:999px;padding:3px 7px;color:#6f83a4;flex:0 0 auto}.pill.hot{color:#d5c9ff;border-color:#634bc2}.pill.risk{color:#ffbab7;border-color:#8d3d3a}.pill.legacy{color:#c5d1df;border-color:#41536c}.why-card{min-width:0;border:1px solid #203149;background:rgba(16,23,34,.65);border-radius:8px;padding:14px;overflow:hidden}.why-card h3{margin:0 0 8px;font-size:13px}.why-card p{margin:0;color:#6f83a4;font-size:13px;line-height:1.55}.impact{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-top:10px}.impact div{border:1px solid #17243a;border-radius:7px;padding:8px}.impact b{display:block;font-size:16px;color:#e0e6f7}.impact span{font-size:10px;color:#334966;text-transform:uppercase;letter-spacing:.08em}
13237
+ .hint{position:absolute;left:50%;bottom:12px;transform:translateX(-50%);font-size:11px;color:#334966;border:1px solid #203149;background:rgba(13,17,24,.9);border-radius:6px;padding:6px 12px;white-space:nowrap}
13238
+ @media(max-width:1500px){body{overflow:auto}#app{height:auto;min-height:100vh;grid-template-columns:1fr;grid-template-rows:auto auto auto auto}.top{flex-wrap:wrap}.tabs{flex-wrap:wrap;overflow:visible}.search{width:100%;max-width:none}.stats{margin-left:0;width:100%}.main{order:1;min-height:820px}.right{order:2}.left{order:3}.flow{grid-template-columns:minmax(220px,1fr) minmax(300px,1.15fr) minmax(220px,1fr);gap:16px;align-items:center}.change-grid{grid-template-columns:1fr;gap:12px;align-items:stretch}.why{order:1}.change-list{order:2}.lane{height:min(560px,calc(100vh - 180px));min-height:260px}.impact{grid-template-columns:1fr}.edges{display:none}.hint{position:static;transform:none;margin-top:10px;text-align:center}}
13239
+ @media(max-width:900px){.flow,.module-lanes{grid-template-columns:1fr;gap:12px;align-items:stretch}.lane{height:auto;max-height:360px;min-height:160px}.module-board{grid-template-rows:auto auto minmax(260px,1fr);overflow:visible}.module-symbols{max-height:420px}.module-symbols .row{grid-template-columns:1fr}.module-symbols .row .meta{justify-content:flex-start;margin-top:4px}}
13240
+ </style>
13241
+ </head>
13242
+ <body>
13243
+ <div id="app">
13244
+ <div class="top">
13245
+ <input id="search" class="search" placeholder="Search changes, symbols, modules, files..." autocomplete="off">
13246
+ <div class="tabs" id="tabs"></div>
13247
+ <div class="stats"><b id="modeName">Changes</b> | <b id="nodeCount">0</b> nodes | <b id="edgeCount">0</b> edges | <b id="changeCount">0</b> change lenses</div>
13248
+ </div>
13249
+ <aside class="panel left"><h2 class="title">Search / jump</h2><div id="results" class="results"></div></aside>
13250
+ <main class="panel main"><svg id="edges" class="edges"></svg><div id="stage" class="flow"></div><div class="hint" id="hint">Scroll to pan vertically inside focus mode</div></main>
13251
+ <aside class="panel right"><h2 class="title">Details</h2><div id="details" class="details"></div></aside>
13252
+ </div>
13253
+ <script>
13254
+ const graphData = ${jsonData};
13255
+ const nodes = graphData.nodes || [];
13256
+ const edges = graphData.edges || [];
13257
+ const modules = graphData.modules || [];
13258
+ const moduleEdges = graphData.moduleEdges || [];
13259
+ const changes = graphData.changes || [];
13260
+ const modes = ["Changes", "Module Overview", "Explore Symbols", "Hotspots", "Cycles"];
13261
+ let mode = changes.length > 0 ? "Changes" : "Module Overview";
13262
+ let selected = nodes[0] ? { type: "symbol", id: nodes[0].id } : { type: "module", id: modules[0] && modules[0].id };
13263
+ let selectedChange = changes[0] && changes[0].id;
13264
+ let query = "";
13265
+
13266
+ function esc(value){return String(value == null ? "" : value).replace(/[&<>"']/g,function(c){return {"&":"&amp;","<":"&lt;",">":"&gt;","\\"":"&quot;","'":"&#39;"}[c];});}
13267
+ function nodeById(id){return nodes.find(function(node){return node.id === id;});}
13268
+ function moduleById(id){return modules.find(function(item){return item.id === id;});}
13269
+ function colorFor(id){const moduleId = nodeById(id) ? nodeById(id).moduleId : id; const index = Math.max(0, modules.findIndex(function(item){return item.id === moduleId;})); return ["#8b6cf6","#5577d8","#4aa46c","#ce6764","#d0933a","#41a6bd","#c667a4","#7890ac"][index % 8];}
13270
+ function shortPath(filePath){const parts = String(filePath || "").split(/[\\\\/]/).filter(Boolean); if(parts.length <= 3)return String(filePath || ""); return ".../" + parts.slice(-3).join("/");}
13271
+ function badge(kind){return '<span class="badge ' + esc(kind) + '">' + esc(kind) + '</span>';}
13272
+ function edgeWeight(edge){return edge.weight || 1;}
13273
+ function incomingSymbol(id){return edges.filter(function(edge){return edge.target === id;}).sort(function(a,b){return edgeWeight(b)-edgeWeight(a);});}
13274
+ function outgoingSymbol(id){return edges.filter(function(edge){return edge.source === id;}).sort(function(a,b){return edgeWeight(b)-edgeWeight(a);});}
13275
+ function incomingModule(id){return moduleEdges.filter(function(edge){return edge.target === id;}).sort(function(a,b){return b.weight-a.weight;});}
13276
+ function outgoingModule(id){return moduleEdges.filter(function(edge){return edge.source === id;}).sort(function(a,b){return b.weight-a.weight;});}
13277
+ function labelOf(id){const node = nodeById(id); if(node)return node.name; const mod = moduleById(id); return mod ? mod.label : id;}
13278
+ function nodeCard(title, meta, id, type, color, active){return '<div class="node ' + (active ? 'active' : '') + '" data-id="' + esc(id || '') + '" data-type="' + esc(type || 'symbol') + '" style="border-color:' + esc(color || "#203149") + '"><div class="name">' + esc(title) + '</div><div class="meta">' + meta + '</div></div>';}
13279
+ function symbolCard(id, weight){const node = nodeById(id); if(!node)return ""; return nodeCard(node.name, badge(node.kind) + '<span>' + esc(shortPath(node.filePath)) + '</span><span class="weight">' + weight + '</span>', node.id, "symbol", colorFor(node.id), false);}
13280
+ function moduleCard(id, weight){const item = moduleById(id); if(!item)return ""; return nodeCard(item.label, '<span>' + item.symbolCount + ' symbols</span><span class="weight">' + weight + '</span>', item.id, "module", colorFor(item.id), false);}
13281
+ function empty(a,b){return '<div class="empty"><div>' + esc(a) + '<br>' + esc(b) + '</div></div>';}
13282
+ function emptyCycleState(){return '<div class="empty"><div><b>No cycles found</b><br>This graph slice has no resolved module loops or symbol recursion.<br><br>A cycle means A calls B, B calls C, and C calls A.</div></div>';}
13283
+ function guideForMode(){
13284
+ if(mode === "Module Overview")return '<div class="guide"><b>How to read</b><span>Module = code area. Symbol = named code item. Counts on caller/callee cards are call edges. Grouped symbol rows collapse repeated indexed ranges.</span></div>';
13285
+ if(mode === "Explore Symbols")return '<div class="guide"><b>How to read</b><span>Focused symbol is in the middle. Left calls it. Right is what it calls.</span></div>';
13286
+ if(mode === "Hotspots")return '<div class="guide"><b>How to read</b><span>Higher score means more incoming plus outgoing call edges, often more load-bearing.</span></div>';
13287
+ if(mode === "Cycles")return '<div class="guide"><b>How to read</b><span>Cycles include module dependency loops and direct or short symbol recursion.</span></div>';
13288
+ return '<div class="guide"><b>How to read</b><span>Recent Git movement is shown first; open a change to see affected module, churn, and call context.</span></div>';
13289
+ }
13290
+ function groupedSymbolCards(edgeList, pickId, limit){
13291
+ const groups = new Map();
13292
+ edgeList.forEach(function(edge){const id = pickId(edge); const node = nodeById(id); if(!node)return; const key = node.name + "|" + node.kind + "|" + node.filePath; const group = groups.get(key) || { id:id, count:0 }; group.count += edgeWeight(edge); groups.set(key, group);});
13293
+ return [...groups.values()].sort(function(a,b){return b.count-a.count;}).slice(0, limit || groups.size).map(function(group){return symbolCard(group.id, group.count);}).join("");
13294
+ }
13295
+ function groupedModuleSymbolRows(symbols){
13296
+ const groups = new Map();
13297
+ symbols.forEach(function(node){
13298
+ const key = node.name + "|" + node.kind + "|" + node.filePath;
13299
+ const group = groups.get(key) || { id:node.id, node:node, count:0, minLine:node.line, maxLine:node.line };
13300
+ group.count += 1;
13301
+ group.minLine = Math.min(group.minLine, node.line);
13302
+ group.maxLine = Math.max(group.maxLine, node.line);
13303
+ groups.set(key, group);
13304
+ });
13305
+ return [...groups.values()].map(function(group){
13306
+ const lineText = group.minLine === group.maxLine ? "line " + group.minLine : "lines " + group.minLine + "-" + group.maxLine;
13307
+ const countText = group.count > 1 ? '<span>x' + group.count + '</span>' : "";
13308
+ return '<div class="row" data-id="' + esc(group.id) + '" data-type="symbol"><div class="name">' + esc(group.node.name) + '</div><div class="meta">' + badge(group.node.kind) + '<span>' + esc(shortPath(group.node.filePath)) + '</span><span>' + esc(lineText) + '</span>' + countText + '</div></div>';
13309
+ }).join("");
13310
+ }
13311
+ function findSymbolCycles(limit){
13312
+ const outgoing = new Map();
13313
+ edges.forEach(function(edge){if(edge.source === edge.target)return; const list = outgoing.get(edge.source) || []; list.push(edge.target); outgoing.set(edge.source, list);});
13314
+ const cycles = [];
13315
+ const seen = new Set();
13316
+ edges.filter(function(edge){return edge.source === edge.target;}).forEach(function(edge){
13317
+ if(!nodeById(edge.source))return;
13318
+ seen.add(edge.source + ">" + edge.source);
13319
+ cycles.push({ ids:[edge.source], kind:"direct recursion" });
13320
+ });
13321
+ nodes.some(function(start){
13322
+ const stack = [{ id:start.id, path:[start.id] }];
13323
+ while(stack.length > 0 && cycles.length < limit){
13324
+ const current = stack.pop();
13325
+ (outgoing.get(current.id) || []).forEach(function(next){
13326
+ if(cycles.length >= limit)return;
13327
+ if(next === start.id && current.path.length > 1){
13328
+ const key = current.path.slice().sort().join(">");
13329
+ if(!seen.has(key)){seen.add(key); cycles.push({ ids:current.path.slice(), kind:"symbol loop" });}
13330
+ } else if(current.path.length < 5 && current.path.indexOf(next) === -1) {
13331
+ stack.push({ id:next, path:current.path.concat(next) });
13332
+ }
13333
+ });
13334
+ }
13335
+ return cycles.length >= limit;
13336
+ });
13337
+ return cycles.slice(0, limit);
13338
+ }
13339
+
13340
+ function renderTabs(){
13341
+ document.getElementById("tabs").innerHTML = modes.map(function(item){return '<button class="tab ' + (item === mode ? 'active' : '') + '" data-mode="' + esc(item) + '">' + esc(item) + '</button>';}).join("");
13342
+ document.querySelectorAll("[data-mode]").forEach(function(button){button.onclick = function(){mode = button.dataset.mode || "Changes"; render();};});
13343
+ document.getElementById("modeName").textContent = mode;
13344
+ document.getElementById("nodeCount").textContent = String(nodes.length);
13345
+ document.getElementById("edgeCount").textContent = String(edges.length);
13346
+ document.getElementById("changeCount").textContent = String(changes.length);
13347
+ }
13348
+ function renderResults(){
13349
+ const q = query.toLowerCase();
13350
+ const items = changes.map(function(change){return {type:"change",id:change.id,title:change.title,meta:change.source + " | " + change.when + " | " + change.intent,color:change.kind === "risk" ? "#ce6764" : "#8b6cf6"};})
13351
+ .concat(modules.map(function(item){return {type:"module",id:item.id,title:item.label,meta:item.symbolCount + " symbols | " + item.category,color:colorFor(item.id)};}))
13352
+ .concat(nodes.map(function(node){return {type:"symbol",id:node.id,title:node.name,meta:node.kind + " | " + shortPath(node.filePath),color:colorFor(node.id)};}))
13353
+ .filter(function(item){return !q || (item.title + " " + item.meta + " " + item.id).toLowerCase().includes(q);})
13354
+ .slice(0, 24);
13355
+ document.getElementById("results").innerHTML = items.map(function(item){return '<div class="result" data-id="' + esc(item.id) + '" data-type="' + esc(item.type) + '" style="border-color:' + esc(item.color) + '"><div class="name">' + esc(item.title) + '</div><div class="meta">' + esc(item.meta) + '</div></div>';}).join("") || empty("No matching changes, symbols, or modules", "Try a file, symbol, intent, or risk term");
13356
+ }
13357
+ function renderChanges(){
13358
+ const active = changes.find(function(change){return change.id === selectedChange;}) || changes[0];
13359
+ if(!active){renderHotspots(); return;}
13360
+ selectedChange = active.id;
13361
+ const focus = active.focusNodeId ? nodeById(active.focusNodeId) : undefined;
13362
+ document.getElementById("stage").className = "change-grid";
13363
+ document.getElementById("stage").innerHTML = '<section class="change-list"><h2 class="title">Moving lately</h2>' + changes.map(function(change){
13364
+ return '<div class="change ' + (change.id === active.id ? 'active' : '') + '" data-change="' + esc(change.id) + '"><div class="change-top"><span class="pill ' + esc(change.kind) + '">' + esc(change.kind) + '</span><span class="meta">' + esc(change.source) + ' | ' + esc(change.when) + '</span></div><div class="name">' + esc(change.title) + '</div><div class="meta">' + esc(change.summary) + '</div></div>';
13365
+ }).join("") + '</section><section class="why"><div class="why-card"><div class="change-top"><span class="pill ' + esc(active.kind) + '">' + esc(active.intent) + '</span><span class="meta">' + esc(active.source) + ' | ' + esc(active.when) + '</span></div><h3>' + esc(active.title) + '</h3><p>' + esc(active.why) + '</p><div class="impact"><div><b>' + active.calls + '</b><span>call edges</span></div><div><b>' + active.churn + '</b><span>churn</span></div><div><b>' + esc(active.risk) + '</b><span>risk</span></div></div></div><div class="why-card"><h3>Current touchpoint</h3>' + (focus ? nodeCard(focus.name, badge(focus.kind) + '<span>' + esc(shortPath(focus.filePath)) + '</span>', focus.id, "symbol", colorFor(focus.id), false) : moduleCard(active.moduleId, active.calls)) + '</div><div class="why-card"><h3>Nearest call context</h3><div class="list">' + (focus ? (groupedSymbolCards(incomingSymbol(focus.id), function(edge){return edge.source;}, 3) + groupedSymbolCards(outgoingSymbol(focus.id), function(edge){return edge.target;}, 3)) : empty("No focused symbol", "Open Module Overview")) + '</div></div></section>';
13366
+ document.getElementById("hint").textContent = "Temporal default: what changed, why, and which call path it affects.";
13367
+ document.getElementById("edges").innerHTML = "";
13368
+ document.querySelectorAll("[data-change]").forEach(function(el){el.onclick = function(){selectedChange = el.dataset.change; render();};});
13369
+ }
13370
+ function renderSymbol(){
13371
+ const symbol = selected.type === "symbol" ? nodeById(selected.id) : nodes.find(function(node){return node.moduleId === selected.id;}) || nodes[0];
13372
+ if(!symbol){document.getElementById("stage").innerHTML = empty("No symbols", "Run index_codebase first"); return;}
13373
+ selected = {type:"symbol", id:symbol.id};
13374
+ const callers = incomingSymbol(symbol.id);
13375
+ const callees = outgoingSymbol(symbol.id);
13376
+ document.getElementById("stage").className = "flow";
13377
+ document.getElementById("stage").innerHTML = '<section class="lane" id="callers"><h3>Callers</h3>' + (groupedSymbolCards(callers, function(edge){return edge.source;}) || empty("No callers", "Entry-level symbol")) + '</section><section class="center">' + nodeCard(symbol.name, badge(symbol.kind) + '<span>' + esc(shortPath(symbol.filePath)) + '</span>', symbol.id, "symbol", colorFor(symbol.id), true) + '</section><section class="lane" id="callees"><h3>Callees</h3>' + (groupedSymbolCards(callees, function(edge){return edge.target;}) || empty("No callees", "Leaf-level symbol")) + '</section>';
13378
+ document.getElementById("hint").textContent = "Explore mode: clustered symbol relationships. Focused module view uses the same one-hop navigation.";
13379
+ requestAnimationFrame(drawEdges);
13380
+ }
13381
+ function renderModule(){
13382
+ const item = selected.type === "module" ? moduleById(selected.id) : moduleById((nodeById(selected.id) || {}).moduleId) || modules[0];
13383
+ if(!item){document.getElementById("stage").innerHTML = empty("No modules", "Run index_codebase first"); return;}
13384
+ selected = {type:"module", id:item.id};
13385
+ const callers = incomingModule(item.id);
13386
+ const callees = outgoingModule(item.id);
13387
+ const internalEdges = edges.filter(function(edge){return nodeById(edge.source)?.moduleId === item.id && nodeById(edge.target)?.moduleId === item.id;});
13388
+ const moduleSymbols = nodes.filter(function(node){return node.moduleId === item.id;});
13389
+ const leftTitle = callers.length > 0 ? "Incoming modules" : "Internal callers";
13390
+ const rightTitle = callees.length > 0 ? "Outgoing modules" : "Internal callees";
13391
+ const groupedModuleSymbols = groupedModuleSymbolRows(moduleSymbols);
13392
+ const uniqueModuleSymbolCount = (groupedModuleSymbols.match(/class="row"/g) || []).length;
13393
+ const leftCards = callers.length > 0 ? callers.map(function(edge){return moduleCard(edge.source, edge.weight);}).join("") : groupedSymbolCards(internalEdges, function(edge){return edge.source;}, 12);
13394
+ const rightCards = callees.length > 0 ? callees.map(function(edge){return moduleCard(edge.target, edge.weight);}).join("") : groupedSymbolCards(internalEdges, function(edge){return edge.target;}, 12);
13395
+ document.getElementById("stage").className = "module-board";
13396
+ document.getElementById("stage").innerHTML = '<section class="module-summary">' + nodeCard(item.label, '<span>' + item.symbolCount + ' indexed symbols</span><span>' + uniqueModuleSymbolCount + ' unique rows</span><span>' + esc(item.category) + '</span>', item.id, "module", colorFor(item.id), true) + '</section><section class="module-lanes"><div class="lane" id="callers"><h3>' + leftTitle + '</h3>' + (leftCards || empty("This slice only has intra-module calls.", "Selected from module list")) + '</div><div class="lane" id="callees"><h3>' + rightTitle + '</h3>' + (rightCards || empty("No calls in this module.", "Selected from module list")) + '</div></section><section class="module-symbols"><h3>Symbols in module (' + uniqueModuleSymbolCount + ' unique, ' + moduleSymbols.length + ' indexed)</h3><div class="list">' + groupedModuleSymbols + '</div></section>';
13397
+ document.getElementById("hint").textContent = callers.length + callees.length > 0 ? "Module Overview. Focused module view shows strongest incoming and outgoing module edges." : "Module Overview. This repo slice has no resolved cross-module edges, so this view shows intra-module hotspots.";
13398
+ requestAnimationFrame(drawEdges);
13399
+ }
13400
+ function renderHotspots(){
13401
+ const ranked = nodes.map(function(node){return {node:node, score:incomingSymbol(node.id).length + outgoingSymbol(node.id).length};}).sort(function(a,b){return b.score-a.score;}).slice(0, 20);
13402
+ document.getElementById("stage").className = "grid";
13403
+ document.getElementById("stage").innerHTML = ranked.map(function(item,index){return '<div class="row" data-id="' + esc(item.node.id) + '" data-type="symbol"><div class="meta"><span class="rank">' + (index + 1) + '</span><span class="weight">' + item.score + ' call edges</span></div><div class="name">' + esc(item.node.name) + '</div><div class="meta">' + badge(item.node.kind) + '<span>' + esc(shortPath(item.node.filePath)) + '</span></div></div>';}).join("") || empty("No hotspots", "No call edges in this slice");
13404
+ document.getElementById("hint").textContent = "Hotspots rank symbols by incoming plus outgoing call edges.";
13405
+ document.getElementById("edges").innerHTML = "";
13406
+ }
13407
+ function renderCycles(){
13408
+ const cycles = moduleEdges.filter(function(edge){return moduleEdges.some(function(other){return other.source === edge.target && other.target === edge.source;});});
13409
+ const symbolCycles = findSymbolCycles(20);
13410
+ document.getElementById("stage").className = "grid";
13411
+ const moduleHtml = cycles.map(function(edge){return '<div class="row" data-id="' + esc(edge.source) + '" data-type="module"><div class="name">' + esc(labelOf(edge.source)) + ' -> ' + esc(labelOf(edge.target)) + '</div><div class="meta"><span>module dependency loop candidate</span><span class="weight">' + edge.weight + ' calls</span></div></div>';}).join("");
13412
+ const symbolHtml = symbolCycles.map(function(cycle){const start = cycle.ids[0]; const node = nodeById(start); const path = cycle.ids.map(function(id){return labelOf(id);}).join(" -> ") + " -> " + labelOf(start); const countLabel = cycle.ids.length === 1 ? "1 symbol" : cycle.ids.length + " symbols"; return '<div class="row" data-id="' + esc(start) + '" data-type="symbol"><div class="name">' + esc(path) + '</div><div class="meta"><span>' + esc(cycle.kind) + '</span>' + (node ? '<span>' + esc(shortPath(node.filePath)) + '</span>' : '') + '<span class="weight">' + countLabel + '</span></div></div>';}).join("");
13413
+ document.getElementById("stage").innerHTML = moduleHtml + symbolHtml || emptyCycleState();
13414
+ document.getElementById("hint").textContent = "Cycle mode surfaces module loops plus direct or short symbol recursion.";
13415
+ document.getElementById("edges").innerHTML = "";
13416
+ }
13417
+ function renderDetails(){
13418
+ if(mode === "Changes"){
13419
+ const active = changes.find(function(change){return change.id === selectedChange;});
13420
+ if(active){document.getElementById("details").innerHTML = guideForMode() + '<div class="name">' + esc(active.title) + '</div><div class="kv"><span>Source</span><b>' + esc(active.source) + '</b></div><div class="kv"><span>Changed</span><b>' + esc(active.when) + '</b></div><div class="kv"><span>Intent</span><b>' + esc(active.intent) + '</b></div><div class="kv"><span>Risk</span><b>' + esc(active.risk) + '</b></div><h2 class="title">Onboarding read</h2><p style="margin:0;color:#6f83a4;font-size:13px;line-height:1.6">' + esc(active.summary) + '</p>'; return;}
13421
+ }
13422
+ const isSymbol = selected.type === "symbol";
13423
+ const item = isSymbol ? nodeById(selected.id) : moduleById(selected.id);
13424
+ if(!item){document.getElementById("details").innerHTML = guideForMode(); return;}
13425
+ const incoming = isSymbol ? incomingSymbol(selected.id) : incomingModule(selected.id);
13426
+ const outgoing = isSymbol ? outgoingSymbol(selected.id) : outgoingModule(selected.id);
13427
+ if(!isSymbol){
13428
+ const internal = edges.filter(function(edge){const source = nodeById(edge.source); const target = nodeById(edge.target); return source && target && source.moduleId === selected.id && target.moduleId === selected.id;});
13429
+ const hasCrossModuleEdges = incoming.length + outgoing.length > 0;
13430
+ const edgeCards = hasCrossModuleEdges ? incoming.concat(outgoing).map(function(edge){return moduleCard(edge.source === selected.id ? edge.target : edge.source, edge.weight);}).join("") : groupedSymbolCards(internal, function(edge){return edge.source;}, 4) + groupedSymbolCards(internal, function(edge){return edge.target;}, 4);
13431
+ document.getElementById("details").innerHTML = guideForMode() + '<div class="name">' + esc(item.label) + '</div><div class="kv"><span>Kind</span><b>' + esc(item.category) + '</b></div><div class="kv"><span>File/module</span><b>' + esc(item.label) + '</b></div><div class="kv"><span>Symbols</span><b>' + item.symbolCount + '</b></div><div class="kv"><span>' + (hasCrossModuleEdges ? 'Cross-module edges' : 'Internal call edges') + '</span><b>' + (incoming.length + outgoing.length || internal.length) + '</b></div><h2 class="title">Strongest edges</h2><div class="list">' + (edgeCards || empty("No calls in this module.", "Selected from module list")) + '</div>';
13432
+ return;
13433
+ }
13434
+ document.getElementById("details").innerHTML = guideForMode() + '<div class="name">' + esc(item.name) + '</div><div class="kv"><span>Kind</span><b>' + esc(item.kind) + '</b></div><div class="kv"><span>File/module</span><b>' + esc(shortPath(item.filePath)) + '</b></div><div class="kv"><span>Callers</span><b>' + incoming.length + '</b></div><div class="kv"><span>Callees</span><b>' + outgoing.length + '</b></div><h2 class="title">Strongest edges</h2><div class="list">' + groupedSymbolCards(incoming.concat(outgoing), function(edge){return edge.source === selected.id ? edge.target : edge.source;}, 8) + '</div>';
13435
+ }
13436
+ function drawEdges(){
13437
+ const svg = document.getElementById("edges");
13438
+ const stage = document.getElementById("stage");
13439
+ const left = document.getElementById("callers");
13440
+ const right = document.getElementById("callees");
13441
+ const center = document.querySelector(".center .node");
13442
+ svg.innerHTML = "";
13443
+ if(!left || !right || !center || innerWidth < 901)return;
13444
+ const box = stage.getBoundingClientRect();
13445
+ svg.setAttribute("viewBox", "0 0 " + box.width + " " + box.height);
13446
+ function line(from,to){const a=from.getBoundingClientRect();const b=to.getBoundingClientRect();const x1=a.right-box.left;const y1=a.top+a.height/2-box.top;const x2=b.left-box.left;const y2=b.top+b.height/2-box.top;const mid=(x1+x2)/2;const path=document.createElementNS("http://www.w3.org/2000/svg","path");path.setAttribute("d","M"+x1+","+y1+" C"+mid+","+y1+" "+mid+","+y2+" "+x2+","+y2);path.setAttribute("fill","none");path.setAttribute("stroke","#334966");path.setAttribute("stroke-width","1.5");path.setAttribute("opacity",".65");svg.appendChild(path);}
13447
+ left.querySelectorAll(".node").forEach(function(node){line(node, center);});
13448
+ right.querySelectorAll(".node").forEach(function(node){line(center, node);});
13449
+ }
13450
+ function wireClicks(){
13451
+ document.querySelectorAll("[data-id]").forEach(function(el){el.onclick = function(){selected = {type:el.dataset.type, id:el.dataset.id}; if(selected.type === "change"){selectedChange = selected.id; mode = "Changes";} else {mode = selected.type === "module" ? "Module Overview" : "Explore Symbols";} render();};});
13452
+ }
13453
+ function render(){
13454
+ renderTabs();
13455
+ renderResults();
13456
+ if(mode === "Changes")renderChanges(); else if(mode === "Module Overview")renderModule(); else if(mode === "Hotspots")renderHotspots(); else if(mode === "Cycles")renderCycles(); else renderSymbol();
13457
+ renderDetails();
13458
+ wireClicks();
13459
+ }
13460
+ document.getElementById("search").addEventListener("input", function(event){query = event.target.value; renderResults(); wireClicks();});
13461
+ addEventListener("resize", function(){requestAnimationFrame(drawEdges);});
13462
+ render();
13463
+ </script>
13464
+ </body>
13465
+ </html>`;
13466
+ }
13467
+
13468
+ // src/tools/visualize/transform.ts
13469
+ var path23 = __toESM(require("path"), 1);
13470
+
13471
+ // src/tools/visualize/modules.ts
13472
+ var MAX_MODULES = 18;
13473
+ var KNOWN_ROOTS = ["src", "native", "tests", "commands", "scripts", "docs", "benchmarks"];
13474
+ function normalizeSlashes(input) {
13475
+ return input.replace(/\\/g, "/");
13476
+ }
13477
+ function stripToProjectRelative(filePath) {
13478
+ const normalized = normalizeSlashes(filePath);
13479
+ let bestIndex = Number.POSITIVE_INFINITY;
13480
+ let bestRoot = null;
13481
+ for (const root of KNOWN_ROOTS) {
13482
+ if (normalized === root || normalized.startsWith(`${root}/`)) {
13483
+ return normalized;
13484
+ }
13485
+ const marker = `/${root}/`;
13486
+ const rootIndex = normalized.indexOf(marker);
13487
+ if (rootIndex !== -1 && rootIndex < bestIndex) {
13488
+ bestIndex = rootIndex;
13489
+ bestRoot = root;
13490
+ }
13491
+ if (normalized.endsWith(`/${root}`)) {
13492
+ return root;
13493
+ }
13494
+ }
13495
+ if (bestRoot !== null && Number.isFinite(bestIndex)) {
13496
+ return normalized.slice(bestIndex + 1);
13497
+ }
13498
+ const parts = normalized.split("/").filter(Boolean);
13499
+ return parts.slice(-3).join("/") || normalized;
13500
+ }
13501
+ function modulePrefixFromRelativePath(relativeFilePath) {
13502
+ const parts = relativeFilePath.split("/").filter(Boolean);
13503
+ if (parts.length === 0) return ".";
13504
+ const [root, second, third] = parts;
13505
+ if (root === "src") {
13506
+ if (!second) return "src";
13507
+ const broadBuckets = /* @__PURE__ */ new Set([
13508
+ "config",
13509
+ "embeddings",
13510
+ "eval",
13511
+ "git",
13512
+ "indexer",
13513
+ "native",
13514
+ "rerank",
13515
+ "tools",
13516
+ "utils",
13517
+ "watcher"
13518
+ ]);
13519
+ if (broadBuckets.has(second)) return `src/${second}`;
13520
+ return second ? `src/${second}` : "src";
13521
+ }
13522
+ if (root === "tests") {
13523
+ if (second === "fixtures" && third) return `tests/fixtures/${third}`;
13524
+ return second ? `tests/${second}` : "tests";
13525
+ }
13526
+ if (root === "native") {
13527
+ if (second === "src") return "native";
13528
+ return second ? `native/${second}` : "native";
13529
+ }
13530
+ if (root === "commands") return "commands";
13531
+ if (root === "scripts") return "scripts";
13532
+ if (root === "docs") return second ? `docs/${second}` : "docs";
13533
+ if (root === "benchmarks") return second ? `benchmarks/${second}` : "benchmarks";
13534
+ return root;
13535
+ }
13536
+ function parentModulePrefix(prefix) {
13537
+ const parts = prefix.split("/");
13538
+ if (parts.length <= 1) return null;
13539
+ return parts.slice(0, -1).join("/");
13540
+ }
13541
+ function shortLabel(prefix, allPrefixes) {
13542
+ const parts = prefix.split("/");
13543
+ for (let i = 1; i <= parts.length; i++) {
13544
+ const suffix = parts.slice(parts.length - i).join("/");
13545
+ const ambiguous = [...allPrefixes].some((candidate) => {
13546
+ if (candidate === prefix) return false;
13547
+ return candidate.endsWith(`/${suffix}`) || candidate === suffix;
13548
+ });
13549
+ if (!ambiguous) return suffix;
13550
+ }
13551
+ return prefix;
13552
+ }
13553
+ function classifyModulePrefix(prefix) {
13554
+ if (prefix.startsWith("tests/fixtures")) return "fixture";
13555
+ if (prefix.startsWith("tests")) return "test";
13556
+ if (prefix === "native" || prefix.startsWith("native/")) return "native";
13557
+ if (prefix === "commands" || prefix.startsWith("commands/")) return "command";
13558
+ if (prefix === "scripts" || prefix.startsWith("scripts/")) return "script";
13559
+ if (prefix === "docs" || prefix.startsWith("docs/")) return "doc";
13560
+ if (prefix === "benchmarks" || prefix.startsWith("benchmarks/")) return "benchmark";
13561
+ if (prefix === "src" || prefix.startsWith("src/")) return "source";
13562
+ return "other";
13563
+ }
13564
+ function displayLabelForPrefix(prefix, allPrefixes) {
13565
+ const short = shortLabel(prefix, allPrefixes);
13566
+ if (prefix.startsWith("tests/fixtures/")) {
13567
+ const fixtureName = prefix.slice("tests/fixtures/".length);
13568
+ return `fixture: ${fixtureName}`;
13569
+ }
13570
+ if (prefix === "tests/fixtures") {
13571
+ return "fixtures";
13572
+ }
13573
+ if (prefix.startsWith("tests/")) {
13574
+ const testArea = prefix.slice("tests/".length);
13575
+ return `tests: ${testArea}`;
13576
+ }
13577
+ if (prefix === "tests") {
13578
+ return "tests";
13579
+ }
13580
+ if (prefix === "native") {
13581
+ return "native";
13582
+ }
13583
+ return short;
13584
+ }
13585
+ function compactModules(prefixToNodes) {
13586
+ const grouped = new Map(prefixToNodes);
13587
+ while (grouped.size > MAX_MODULES) {
13588
+ const smallest = [...grouped.entries()].sort((a, b) => a[1].length - b[1].length)[0];
13589
+ if (!smallest) break;
13590
+ const [prefix, members] = smallest;
13591
+ const parent = parentModulePrefix(prefix);
13592
+ if (!parent) break;
13593
+ grouped.delete(prefix);
13594
+ if (!grouped.has(parent)) grouped.set(parent, []);
13595
+ grouped.get(parent)?.push(...members);
13596
+ }
13597
+ return grouped;
13598
+ }
13599
+ function deriveModules(nodes) {
13600
+ const initial = /* @__PURE__ */ new Map();
13601
+ for (const node of nodes) {
13602
+ const relative10 = stripToProjectRelative(node.filePath);
13603
+ const prefix = modulePrefixFromRelativePath(relative10);
13604
+ if (!initial.has(prefix)) initial.set(prefix, []);
13605
+ initial.get(prefix)?.push(node);
13606
+ }
13607
+ const nonFixturePrefixes = [...initial.keys()].filter((prefix) => !prefix.startsWith("tests/fixtures/"));
13608
+ if (nonFixturePrefixes.length > 0) {
13609
+ const fixtureEntries = [...initial.entries()].filter(([prefix]) => prefix.startsWith("tests/fixtures/"));
13610
+ if (fixtureEntries.length > 1) {
13611
+ const mergedFixtureNodes = fixtureEntries.flatMap(([, members]) => members);
13612
+ for (const [prefix] of fixtureEntries) {
13613
+ initial.delete(prefix);
13614
+ }
13615
+ initial.set("tests/fixtures", mergedFixtureNodes);
13616
+ }
13617
+ }
13618
+ const grouped = compactModules(initial);
13619
+ const allPrefixes = new Set(grouped.keys());
13620
+ const modules = [...grouped.entries()].map(([prefix, members]) => {
13621
+ const kinds = {};
13622
+ for (const node of members) {
13623
+ kinds[node.kind] = (kinds[node.kind] ?? 0) + 1;
13624
+ }
13625
+ const label = displayLabelForPrefix(prefix, allPrefixes);
13626
+ const category = classifyModulePrefix(prefix);
13627
+ const id = `module-${prefix.replace(/[^a-zA-Z0-9]+/g, "-")}`;
13628
+ for (const node of members) {
13629
+ node.moduleId = id;
13630
+ node.moduleLabel = label;
13631
+ }
13632
+ return {
13633
+ id,
13634
+ label,
13635
+ pathPrefix: prefix,
13636
+ category,
13637
+ symbolCount: members.length,
13638
+ symbols: members.map((node) => node.id),
13639
+ kinds
13640
+ };
13641
+ }).sort((a, b) => b.symbolCount - a.symbolCount || a.label.localeCompare(b.label));
13642
+ return modules;
13643
+ }
13644
+ function deriveModuleEdges(nodes, edges) {
13645
+ const nodeMap = new Map(nodes.map((node) => [node.id, node]));
13646
+ const aggregate = /* @__PURE__ */ new Map();
13647
+ for (const edge of edges) {
13648
+ const source = nodeMap.get(edge.source);
13649
+ if (!source || !source.moduleId) continue;
13650
+ const target = nodeMap.get(edge.target);
13651
+ const targetModuleId = target?.moduleId;
13652
+ if (!targetModuleId) continue;
13653
+ if (source.moduleId === targetModuleId) continue;
13654
+ const key = `${source.moduleId}__${targetModuleId}`;
13655
+ if (!aggregate.has(key)) {
13656
+ aggregate.set(key, {
13657
+ source: source.moduleId,
13658
+ target: targetModuleId,
13659
+ weight: 0,
13660
+ callTypes: {}
13661
+ });
13662
+ }
13663
+ const moduleEdge = aggregate.get(key);
13664
+ moduleEdge.weight += 1;
13665
+ moduleEdge.callTypes[edge.callType] = (moduleEdge.callTypes[edge.callType] ?? 0) + 1;
13666
+ }
13667
+ return [...aggregate.values()].sort((a, b) => b.weight - a.weight);
13668
+ }
13669
+
13670
+ // src/tools/visualize/transform.ts
13671
+ function transformForVisualization(symbols, edges, options = {}) {
13672
+ const { includeOrphans = false, directory, maxNodes = 5e3 } = options;
13673
+ let filteredSymbols = symbols;
13674
+ if (directory) {
13675
+ const normalizedDir = directory.replace(/\/$/, "");
13676
+ const normalizedDirWithSlash = `${normalizedDir}/`;
13677
+ const normalizedAbsoluteSuffix = `/${normalizedDirWithSlash}`;
13678
+ filteredSymbols = symbols.filter(
13679
+ (s) => {
13680
+ const normalizedPath = s.filePath.replace(/\\/g, "/");
13681
+ return normalizedPath === normalizedDir || normalizedPath.startsWith(normalizedDirWithSlash) || normalizedPath.endsWith(`/${normalizedDir}`) || normalizedPath.includes(normalizedAbsoluteSuffix);
13682
+ }
13683
+ );
13684
+ }
13685
+ const symbolIdSet = new Set(filteredSymbols.map((s) => s.id));
13686
+ const filteredEdges = [];
13687
+ for (const edge of edges) {
13688
+ if (!edge.isResolved || !edge.toSymbolId) continue;
13689
+ if (!symbolIdSet.has(edge.fromSymbolId)) continue;
13690
+ if (!symbolIdSet.has(edge.toSymbolId)) continue;
13691
+ filteredEdges.push({
13692
+ source: edge.fromSymbolId,
13693
+ target: edge.toSymbolId,
13694
+ callType: edge.callType,
13695
+ confidence: edge.confidence,
13696
+ line: edge.line
13697
+ });
13698
+ }
13699
+ let finalSymbols = filteredSymbols;
13700
+ if (!includeOrphans) {
13701
+ const connectedIds = /* @__PURE__ */ new Set();
13702
+ for (const edge of filteredEdges) {
13703
+ connectedIds.add(edge.source);
13704
+ connectedIds.add(edge.target);
13705
+ }
13706
+ finalSymbols = filteredSymbols.filter((s) => connectedIds.has(s.id));
13707
+ }
13708
+ const truncated = finalSymbols.length > maxNodes;
13709
+ if (truncated) {
13710
+ const connectionCount = /* @__PURE__ */ new Map();
13711
+ for (const edge of filteredEdges) {
13712
+ connectionCount.set(edge.source, (connectionCount.get(edge.source) ?? 0) + 1);
13713
+ connectionCount.set(edge.target, (connectionCount.get(edge.target) ?? 0) + 1);
13714
+ }
13715
+ finalSymbols = finalSymbols.sort((a, b) => (connectionCount.get(b.id) ?? 0) - (connectionCount.get(a.id) ?? 0)).slice(0, maxNodes);
13716
+ const remainingIds = new Set(finalSymbols.map((s) => s.id));
13717
+ filteredEdges.splice(
13718
+ 0,
13719
+ filteredEdges.length,
13720
+ ...filteredEdges.filter((e) => remainingIds.has(e.source) && remainingIds.has(e.target))
13721
+ );
13722
+ }
13723
+ const nodes = finalSymbols.map((s) => ({
13724
+ id: s.id,
13725
+ name: s.name,
13726
+ filePath: s.filePath,
13727
+ kind: s.kind,
13728
+ line: s.startLine,
13729
+ directory: path23.dirname(s.filePath),
13730
+ moduleId: "",
13731
+ moduleLabel: ""
13732
+ }));
13733
+ const modules = deriveModules(nodes);
13734
+ const moduleEdges = deriveModuleEdges(nodes, filteredEdges);
13735
+ return {
13736
+ nodes,
13737
+ edges: filteredEdges,
13738
+ modules,
13739
+ moduleEdges,
13740
+ metadata: {
13741
+ totalSymbols: symbols.length,
13742
+ totalEdges: edges.length,
13743
+ truncated,
13744
+ directory,
13745
+ moduleCount: modules.length
13746
+ }
13747
+ };
13748
+ }
13749
+
13750
+ // src/cli.ts
13751
+ var import_meta3 = {};
13752
+ function parseArgs(argv) {
13753
+ let project = process.cwd();
13754
+ let config;
13755
+ let host = "opencode";
13756
+ for (let i = 2; i < argv.length; i++) {
13757
+ if (argv[i] === "--project" && argv[i + 1]) {
13758
+ project = path24.resolve(argv[++i]);
13759
+ } else if (argv[i] === "--config" && argv[i + 1]) {
13760
+ config = path24.resolve(argv[++i]);
13761
+ } else if (argv[i] === "--host" && argv[i + 1]) {
13762
+ host = parseHostMode(argv[++i]);
13763
+ } else if (argv[i] === "--host") {
13764
+ host = parseHostMode(void 0);
13765
+ }
13766
+ }
13767
+ return { project, config, host };
13768
+ }
13769
+ function loadCliRawConfig(args) {
13770
+ return args.config ? loadConfigFile(args.config) : loadMergedConfig(args.project, args.host);
13771
+ }
13772
+ function isCliEntrypoint(moduleUrl, argvPath) {
13773
+ return argvPath !== void 0 && (0, import_fs15.realpathSync)((0, import_url2.fileURLToPath)(moduleUrl)) === (0, import_fs15.realpathSync)(argvPath);
13774
+ }
13775
+ function parseVisualizeArgs(argv, cwd) {
13776
+ let project = cwd;
13777
+ let directory;
13778
+ let includeOrphans = false;
13779
+ let maxNodes = 5e3;
13780
+ for (let i = 0; i < argv.length; i++) {
13781
+ const arg = argv[i];
13782
+ if (arg === "--project" && argv[i + 1]) {
13783
+ project = path24.resolve(argv[++i]);
13784
+ } else if (arg === "--max" && argv[i + 1]) {
13785
+ maxNodes = Number(argv[++i]);
13786
+ } else if (arg.startsWith("--max=") || arg.startsWith("max=")) {
13787
+ maxNodes = Number(arg.split("=")[1]);
13788
+ } else if (arg === "--orphans" || arg === "orphans" || arg === "--include-orphans" || arg === "include-orphans") {
13789
+ includeOrphans = true;
13790
+ } else if (!arg.startsWith("-") && directory === void 0) {
13791
+ directory = arg;
13792
+ }
13793
+ }
13794
+ if (!Number.isFinite(maxNodes) || maxNodes < 1) {
13795
+ throw new Error("max must be a positive number");
13796
+ }
13797
+ return { directory, includeOrphans, maxNodes, project };
13798
+ }
13799
+ async function handleVisualizeCommand(argv, cwd) {
13800
+ try {
13801
+ const args = parseVisualizeArgs(argv, cwd);
13802
+ const config = parseConfig(loadMergedConfig(args.project));
13803
+ const indexer = new Indexer(args.project, config);
13804
+ const rawData = await indexer.getVisualizationData({
13805
+ directory: args.directory
13806
+ });
13807
+ if (rawData.symbols.length === 0) {
13808
+ console.error("No call graph data found. Run /index in OpenCode first, then retry npm run visualize.");
13809
+ return 1;
13810
+ }
13811
+ const vizData = attachRecentActivity(transformForVisualization(rawData.symbols, rawData.edges, {
13812
+ includeOrphans: args.includeOrphans,
13813
+ directory: args.directory,
13814
+ maxNodes: args.maxNodes
13815
+ }), args.project);
13816
+ if (vizData.nodes.length === 0) {
13817
+ console.error("No connected symbols found. Retry with: npm run visualize -- orphans");
13818
+ return 1;
13819
+ }
13820
+ const outputPath = path24.join(os5.tmpdir(), `call-graph-${Date.now()}.html`);
13821
+ (0, import_fs15.writeFileSync)(outputPath, generateVisualizationHtml(vizData), "utf-8");
13822
+ console.log(`Temporal call graph visualization generated: ${outputPath}`);
13823
+ console.log(`Nodes: ${vizData.nodes.length} | Edges: ${vizData.edges.length}`);
13824
+ console.log(`Recent change lenses: ${vizData.changes?.length ?? 0}`);
13825
+ if (vizData.metadata.truncated) {
13826
+ console.log(`Graph truncated to ${args.maxNodes} most-connected nodes.`);
13827
+ }
13828
+ return 0;
13829
+ } catch {
13830
+ console.error("Failed to generate visualization. Check the project, config, and arguments, then retry.");
13831
+ return 1;
13832
+ }
13833
+ }
13834
+ async function main() {
13835
+ if (process.argv[2] === "eval") {
13836
+ const exitCode = await handleEvalCommand(process.argv.slice(3), process.cwd());
13837
+ process.exit(exitCode);
13838
+ }
13839
+ if (process.argv[2] === "visualize") {
13840
+ const exitCode = await handleVisualizeCommand(process.argv.slice(3), process.cwd());
13841
+ process.exit(exitCode);
13842
+ }
13843
+ const args = parseArgs(process.argv);
13844
+ const rawConfig = loadCliRawConfig(args);
13845
+ const config = parseConfig(rawConfig);
13846
+ const server = createMcpServer(args.project, config, args.host);
13847
+ const transport = new import_stdio.StdioServerTransport();
13848
+ await server.connect(transport);
13849
+ let watcher = null;
13850
+ const isHomeDir = path24.resolve(args.project) === path24.resolve(os5.homedir());
13851
+ const isValidProject = !isHomeDir && (!config.indexing.requireProjectMarker || hasProjectMarker(args.project));
13852
+ if (config.indexing.autoIndex && isValidProject) {
13853
+ const indexer = getIndexerForProject(args.project, args.host);
13854
+ startAutoIndex(indexer, args.project);
13855
+ }
13856
+ if (config.indexing.watchFiles && isValidProject) {
13857
+ watcher = createWatcherWithIndexer(
13858
+ () => getIndexerForProject(args.project, args.host),
13859
+ args.project,
13860
+ config,
13861
+ args.host,
13862
+ args.config ? { configPath: args.config } : {}
13863
+ );
13864
+ }
13865
+ const shutdown = () => {
13866
+ watcher?.stop();
13867
+ server.close().catch(() => {
13868
+ });
13869
+ process.exit(0);
13870
+ };
10547
13871
  process.on("SIGINT", shutdown);
10548
13872
  process.on("SIGTERM", shutdown);
10549
13873
  }
10550
- main().catch((_error) => {
10551
- console.error("Fatal: failed to start MCP server (check config and network)");
13874
+ function handleMainError(error) {
13875
+ if (error instanceof Error && error.message.startsWith("Invalid host mode")) {
13876
+ console.error(`Invalid host mode. Allowed values: ${HOST_MODES.join(", ")}.`);
13877
+ process.exit(1);
13878
+ }
13879
+ if (error instanceof Error) {
13880
+ console.error("Failed to start MCP server. Check config and network.");
13881
+ process.exit(1);
13882
+ }
13883
+ console.error("Fatal: failed to start MCP server");
10552
13884
  process.exit(1);
13885
+ }
13886
+ if (isCliEntrypoint(import_meta3.url, process.argv[1])) {
13887
+ main().catch(handleMainError);
13888
+ }
13889
+ // Annotate the CommonJS export names for ESM import in node:
13890
+ 0 && (module.exports = {
13891
+ isCliEntrypoint,
13892
+ loadCliRawConfig,
13893
+ parseArgs
10553
13894
  });
13895
+ /*! Bundled license information:
13896
+
13897
+ chokidar/index.js:
13898
+ (*! chokidar - MIT License (c) 2012 Paul Miller (paulmillr.com) *)
13899
+ */
10554
13900
  //# sourceMappingURL=cli.cjs.map