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