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