@wrongstack/tools 0.313.0 → 0.316.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_edit-match.d.ts +3 -2
- package/dist/_output-spool.d.ts +4 -3
- package/dist/_shell-pick.d.ts +2 -1
- package/dist/_spawn-stream.d.ts +2 -1
- package/dist/_syntax-check.d.ts +2 -1
- package/dist/audit.js +10 -4055
- package/dist/bash-kill-guard.d.ts +3 -6
- package/dist/bash.js +9 -2308
- package/dist/batch-tool-use.js +2 -187
- package/dist/browser/index.js +25 -1339
- package/dist/browser/network-guard-proxy.d.ts +2 -1
- package/dist/builtin.js +69 -33949
- package/dist/chunk-22B7GE5T.js +260 -0
- package/dist/chunk-2VSZC6QM.js +113 -0
- package/dist/chunk-3PVYQFRQ.js +472 -0
- package/dist/chunk-42BHI7TN.js +337 -0
- package/dist/chunk-4BHUSMNL.js +104 -0
- package/dist/chunk-4I4LWFOO.js +431 -0
- package/dist/chunk-4UESMQDP.js +122 -0
- package/dist/chunk-553IQUMO.js +478 -0
- package/dist/chunk-5H4GSJAI.js +223 -0
- package/dist/chunk-6RNIFLG4.js +305 -0
- package/dist/chunk-7EY5DCRO.js +434 -0
- package/dist/chunk-A6BRWIXN.js +317 -0
- package/dist/chunk-AHHDWNXC.js +200 -0
- package/dist/chunk-BSLVR56S.js +199 -0
- package/dist/chunk-BTSFTGIL.js +7 -0
- package/dist/chunk-D33S4RQG.js +222 -0
- package/dist/chunk-DA4KNE2H.js +4412 -0
- package/dist/chunk-DR7JOQTC.js +520 -0
- package/dist/chunk-EEOFQDT6.js +898 -0
- package/dist/chunk-EJWSCQ6D.js +137 -0
- package/dist/chunk-EMRGN4KM.js +170 -0
- package/dist/chunk-FQZOHLG3.js +89 -0
- package/dist/chunk-GOIZNNOA.js +221 -0
- package/dist/chunk-HFXSNKCV.js +196 -0
- package/dist/chunk-HYX2TZ7O.js +1272 -0
- package/dist/chunk-I64BS3VC.js +15 -0
- package/dist/chunk-IVWV2UM6.js +955 -0
- package/dist/chunk-IXMNTNW5.js +238 -0
- package/dist/chunk-IYK7XGDZ.js +892 -0
- package/dist/chunk-J5SKD6LN.js +235 -0
- package/dist/chunk-JNNVRHMV.js +2111 -0
- package/dist/chunk-JOHXDF3D.js +295 -0
- package/dist/chunk-JRJJX3L5.js +424 -0
- package/dist/chunk-K3WNSHUF.js +1371 -0
- package/dist/chunk-L25CVUVR.js +2830 -0
- package/dist/chunk-LIJQMYYX.js +59 -0
- package/dist/chunk-LWKUTMD5.js +197 -0
- package/dist/chunk-MQX2KXVW.js +54 -0
- package/dist/chunk-MZZN7VCD.js +359 -0
- package/dist/chunk-NBDZMMG2.js +169 -0
- package/dist/chunk-NKJW5GFO.js +366 -0
- package/dist/chunk-NKNOBUBU.js +522 -0
- package/dist/chunk-O4MBV4AX.js +134 -0
- package/dist/chunk-OUBDL3WH.js +3515 -0
- package/dist/chunk-PQKK5JH7.js +101 -0
- package/dist/chunk-PY2IOS7C.js +1000 -0
- package/dist/chunk-Q5JPPCZC.js +12 -0
- package/dist/chunk-QBV725YG.js +128 -0
- package/dist/chunk-QZQSPUBF.js +315 -0
- package/dist/chunk-RGISBZBD.js +231 -0
- package/dist/chunk-RJZQWEMS.js +84 -0
- package/dist/chunk-RXISBFR5.js +315 -0
- package/dist/chunk-S5CCLZL3.js +99 -0
- package/dist/chunk-SDLVH5RZ.js +178 -0
- package/dist/chunk-T2F4S7U4.js +1723 -0
- package/dist/chunk-TEBQ4VPZ.js +163 -0
- package/dist/chunk-U4D5S3VQ.js +21 -0
- package/dist/chunk-U4VAATKP.js +193 -0
- package/dist/chunk-UCYQTZ7F.js +113 -0
- package/dist/chunk-UKU2ZX4C.js +203 -0
- package/dist/chunk-UNSKG6DC.js +159 -0
- package/dist/chunk-V43EQ6VR.js +68 -0
- package/dist/chunk-VB3MJGCV.js +610 -0
- package/dist/chunk-VFKPWSOX.js +154 -0
- package/dist/chunk-VJNHFZ7J.js +190 -0
- package/dist/chunk-VXECAJJ7.js +1091 -0
- package/dist/chunk-WHIIIREL.js +223 -0
- package/dist/chunk-Z6DAKQ3U.js +1 -0
- package/dist/chunk-ZJQ4IGXK.js +474 -0
- package/dist/chunk-ZLTFLC25.js +385 -0
- package/dist/circuit-breaker.js +3 -191
- package/dist/codebase-index/ast-invariant-engine.d.ts +5 -4
- package/dist/codebase-index/binary-frame.d.ts +0 -2
- package/dist/codebase-index/bm25.d.ts +1 -1
- package/dist/codebase-index/circuit-breaker.d.ts +2 -1
- package/dist/codebase-index/codebase-ast-replace-tool.d.ts +3 -2
- package/dist/codebase-index/codebase-impact-analysis-tool.d.ts +4 -3
- package/dist/codebase-index/codebase-invariant-check-tool.d.ts +3 -2
- package/dist/codebase-index/codebase-repo-map-tool.d.ts +3 -2
- package/dist/codebase-index/codebase-skeleton-tool.d.ts +3 -2
- package/dist/codebase-index/codebase-targeted-test-tool.d.ts +3 -2
- package/dist/codebase-index/content-hash.d.ts +0 -2
- package/dist/codebase-index/import-extractor.d.ts +0 -13
- package/dist/codebase-index/index-service.d.ts +1 -1
- package/dist/codebase-index/index.js +84 -13688
- package/dist/codebase-index/languages.d.ts +2 -1
- package/dist/codebase-index/module-roots.d.ts +3 -2
- package/dist/codebase-index/parser-batch.d.ts +0 -5
- package/dist/codebase-index/parser-dispatch.d.ts +2 -1
- package/dist/codebase-index/parser-output.d.ts +4 -3
- package/dist/codebase-index/parser-worker-pool.d.ts +0 -2
- package/dist/codebase-index/parser-worker-script.d.ts +1 -16
- package/dist/codebase-index/parser-worker-script.js +8 -3995
- package/dist/codebase-index/project-server-client-state.d.ts +2 -2
- package/dist/codebase-index/project-server-endpoint.d.ts +0 -15
- package/dist/codebase-index/project-server.js +60 -8548
- package/dist/codebase-index/tree-sitter/visitor.d.ts +2 -1
- package/dist/codebase-index/tree-sitter-parser.d.ts +0 -2
- package/dist/codebase-index/ts-parser.d.ts +1 -1
- package/dist/codebase-index/vector-search.d.ts +2 -1
- package/dist/codebase-index/wal-maintenance.d.ts +3 -2
- package/dist/codebase-index/worker.js +20 -8416
- package/dist/codebase-index/writer-admin.d.ts +0 -1
- package/dist/codebase-index/writer-search.d.ts +0 -9
- package/dist/design.js +2 -299
- package/dist/diff.js +4 -256
- package/dist/document.js +4 -222
- package/dist/e2e.js +5 -476
- package/dist/edit.js +14 -10200
- package/dist/exec.js +16 -3131
- package/dist/fetch.js +8 -398
- package/dist/format.js +10 -3750
- package/dist/generic-parser-VUXDIAOZ.js +13 -0
- package/dist/git.js +4 -380
- package/dist/glob.js +6 -280
- package/dist/go-parser-JXYKJHRC.js +469 -0
- package/dist/grep.js +7 -604
- package/dist/index.js +318 -35387
- package/dist/install.js +10 -4091
- package/dist/json-parser-IBHZ4BZC.js +213 -0
- package/dist/json.js +4 -665
- package/dist/kanban-contract-actions.d.ts +1 -1
- package/dist/kanban-detail-actions.d.ts +2 -1
- package/dist/kanban-presence.d.ts +2 -1
- package/dist/kanban-serializer.d.ts +0 -8
- package/dist/kanban-split-task-handler.d.ts +2 -2
- package/dist/kanban-tool-types.d.ts +2 -1
- package/dist/kanban.js +3 -2096
- package/dist/languages/diagnostics.d.ts +2 -1
- package/dist/languages/index.js +23 -4664
- package/dist/languages/legacy-bridge.d.ts +3 -2
- package/dist/languages/types.d.ts +2 -1
- package/dist/legacy-bridge-7M6QLUQN.js +18 -0
- package/dist/lint.js +10 -3706
- package/dist/logs.js +4 -287
- package/dist/memory.js +6 -215
- package/dist/mode.js +3 -99
- package/dist/outdated.js +4 -5170
- package/dist/pack.js +65 -33897
- package/dist/parser-dispatch-QH4XPGG6.js +14 -0
- package/dist/patch.js +13 -9935
- package/dist/plan.js +5 -3286
- package/dist/process-registry.js +8 -651
- package/dist/ps-slash.js +12 -1439
- package/dist/pwsh.d.ts +0 -1
- package/dist/py-parser-IS4HKYMF.js +16 -0
- package/dist/read.js +13 -9816
- package/dist/replace.js +15 -10014
- package/dist/rs-parser-XHUUO2KZ.js +81 -0
- package/dist/scaffold.js +4 -253
- package/dist/search.js +5 -585
- package/dist/session-kanban-sync.d.ts +0 -6
- package/dist/session-kanban.js +30 -972
- package/dist/skill.js +2 -215
- package/dist/spawn-background.d.ts +1 -1
- package/dist/task.js +5 -3410
- package/dist/test.js +10 -3773
- package/dist/todo.js +4 -2891
- package/dist/tool-help.js +3 -123
- package/dist/tool-search.js +3 -79
- package/dist/tool-tier.js +68 -33984
- package/dist/tool-use.js +2 -93
- package/dist/tree-sitter-parser-AIERW3YI.js +15 -0
- package/dist/tree.js +4 -288
- package/dist/ts-parser-RCTYKSVI.js +232 -0
- package/dist/typecheck.js +10 -3753
- package/dist/win32.js +6 -60
- package/dist/write.js +14 -9882
- package/dist/yaml-parser-4SAXV3PL.js +157 -0
- package/package.json +5 -5
|
@@ -0,0 +1,1272 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MAX_BINARY_FRAME_BYTES,
|
|
3
|
+
PROJECT_INDEX_SERVER_MAX_FRAME_CHARS,
|
|
4
|
+
PROJECT_INDEX_SERVER_PROTOCOL_VERSION,
|
|
5
|
+
decodeBinaryFrame,
|
|
6
|
+
encodeBinaryFrame,
|
|
7
|
+
encodeProjectServerMessage,
|
|
8
|
+
isBinaryFrame,
|
|
9
|
+
projectIndexServerBuildId,
|
|
10
|
+
projectIndexServerEndpoint,
|
|
11
|
+
projectIndexServerMetadataPath
|
|
12
|
+
} from "./chunk-4UESMQDP.js";
|
|
13
|
+
import {
|
|
14
|
+
CircuitOpenError,
|
|
15
|
+
IndexTimeoutError,
|
|
16
|
+
LockError,
|
|
17
|
+
fileGraphService,
|
|
18
|
+
incomingCallsService,
|
|
19
|
+
indexCircuitBreaker,
|
|
20
|
+
indexService,
|
|
21
|
+
indexStorePool,
|
|
22
|
+
outgoingCallsService,
|
|
23
|
+
packageGraphService,
|
|
24
|
+
searchService,
|
|
25
|
+
statsService,
|
|
26
|
+
symbolGraphService
|
|
27
|
+
} from "./chunk-DA4KNE2H.js";
|
|
28
|
+
import {
|
|
29
|
+
isIndexablePath
|
|
30
|
+
} from "./chunk-TEBQ4VPZ.js";
|
|
31
|
+
|
|
32
|
+
// src/codebase-index/project-server-client-state.ts
|
|
33
|
+
import * as fs from "node:fs";
|
|
34
|
+
import { fileURLToPath } from "node:url";
|
|
35
|
+
import { checkUnixSocketPath } from "@wrongstack/core/utils";
|
|
36
|
+
var CONNECT_ATTEMPT_TIMEOUT_MS = 750;
|
|
37
|
+
var SERVER_START_TIMEOUT_MS = 1e4;
|
|
38
|
+
var SERVER_CONTROL_TIMEOUT_MS = 5e3;
|
|
39
|
+
var SERVER_HEALTH_TIMEOUT_MS = 3e3;
|
|
40
|
+
var SERVER_HEARTBEAT_INTERVAL_MS = 1e4;
|
|
41
|
+
var StaleProjectIndexServerError = class extends Error {
|
|
42
|
+
constructor(message, pid) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.pid = pid;
|
|
45
|
+
}
|
|
46
|
+
pid;
|
|
47
|
+
name = "StaleProjectIndexServerError";
|
|
48
|
+
};
|
|
49
|
+
var connectionStates = /* @__PURE__ */ new Map();
|
|
50
|
+
var connectionStateListeners = /* @__PURE__ */ new Set();
|
|
51
|
+
var latestConnectionState = {
|
|
52
|
+
status: "offline",
|
|
53
|
+
connected: false
|
|
54
|
+
};
|
|
55
|
+
function setLatestConnectionState(state) {
|
|
56
|
+
latestConnectionState = state;
|
|
57
|
+
}
|
|
58
|
+
function resolveProjectIndexDaemonAvailability(projectRoot, indexDir) {
|
|
59
|
+
if (process.env["WRONGSTACK_INDEX_INLINE"] || process.env["WRONGSTACK_INDEX_SERVER"] === "0") {
|
|
60
|
+
return { kind: "inline-requested" };
|
|
61
|
+
}
|
|
62
|
+
let builtUrl = null;
|
|
63
|
+
for (const rel of ["./project-server.js", "./codebase-index/project-server.js"]) {
|
|
64
|
+
try {
|
|
65
|
+
const url = new URL(rel, import.meta.url);
|
|
66
|
+
if (url.protocol === "file:" && fs.existsSync(fileURLToPath(url))) {
|
|
67
|
+
builtUrl = url;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (builtUrl === null) return { kind: "missing-build" };
|
|
74
|
+
if (projectRoot !== void 0) {
|
|
75
|
+
const endpoint = projectIndexServerEndpoint(projectRoot, indexDir);
|
|
76
|
+
const check = checkUnixSocketPath(endpoint);
|
|
77
|
+
if (!check.ok) {
|
|
78
|
+
return {
|
|
79
|
+
kind: "endpoint-invalid",
|
|
80
|
+
endpoint,
|
|
81
|
+
byteLength: check.byteLength,
|
|
82
|
+
maxBytes: check.maxBytes
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return { kind: "available", url: builtUrl };
|
|
87
|
+
}
|
|
88
|
+
function resolveProjectServerUrl() {
|
|
89
|
+
const availability = resolveProjectIndexDaemonAvailability();
|
|
90
|
+
return availability.kind === "available" ? availability.url : null;
|
|
91
|
+
}
|
|
92
|
+
function projectIndexServerExpectedBuildId() {
|
|
93
|
+
const override = process.env["WRONGSTACK_INDEX_SERVER_BUILD_ID"]?.trim();
|
|
94
|
+
if (override) return override;
|
|
95
|
+
const url = resolveProjectServerUrl();
|
|
96
|
+
return url ? projectIndexServerBuildId(url) : null;
|
|
97
|
+
}
|
|
98
|
+
function isProjectIndexServerAvailable() {
|
|
99
|
+
return resolveProjectServerUrl() !== null;
|
|
100
|
+
}
|
|
101
|
+
function publishConnectionState(endpoint, state) {
|
|
102
|
+
connectionStates.set(endpoint, state);
|
|
103
|
+
latestConnectionState = state;
|
|
104
|
+
for (const listener of connectionStateListeners) listener(state);
|
|
105
|
+
}
|
|
106
|
+
function getProjectIndexServerConnectionState(projectRoot, indexDir) {
|
|
107
|
+
if (projectRoot) {
|
|
108
|
+
const endpoint = projectIndexServerEndpoint(projectRoot, indexDir);
|
|
109
|
+
const existing = connectionStates.get(endpoint);
|
|
110
|
+
if (existing) return existing;
|
|
111
|
+
if (!isProjectIndexServerAvailable()) {
|
|
112
|
+
return { status: "unavailable", connected: false };
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
status: "offline",
|
|
116
|
+
connected: false,
|
|
117
|
+
projectRoot,
|
|
118
|
+
indexDir,
|
|
119
|
+
endpoint
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (latestConnectionState.endpoint) return latestConnectionState;
|
|
123
|
+
if (!isProjectIndexServerAvailable()) return { status: "unavailable", connected: false };
|
|
124
|
+
return latestConnectionState;
|
|
125
|
+
}
|
|
126
|
+
function onProjectIndexServerConnectionStateChange(listener) {
|
|
127
|
+
connectionStateListeners.add(listener);
|
|
128
|
+
return () => connectionStateListeners.delete(listener);
|
|
129
|
+
}
|
|
130
|
+
function remoteError(message, name) {
|
|
131
|
+
if (name === "LockError") return new LockError(message);
|
|
132
|
+
if (name === "IndexTimeoutError") return new IndexTimeoutError(message);
|
|
133
|
+
const error = new Error(message);
|
|
134
|
+
if (name && name !== "Error") error.name = name;
|
|
135
|
+
return error;
|
|
136
|
+
}
|
|
137
|
+
function isProjectIndexServerHealth(value) {
|
|
138
|
+
if (!value || typeof value !== "object") return false;
|
|
139
|
+
const health = value;
|
|
140
|
+
const memory = health.memory && typeof health.memory === "object" ? health.memory : void 0;
|
|
141
|
+
const activity = health.activity && typeof health.activity === "object" ? health.activity : void 0;
|
|
142
|
+
return typeof health.checkedAt === "number" && typeof health.uptimeMs === "number" && typeof memory?.rss === "number" && typeof memory.heapUsed === "number" && typeof memory.heapTotal === "number" && typeof memory.external === "number" && typeof health.clients === "number" && typeof health.activeRequests === "number" && typeof health.activeWrites === "number" && typeof health.queuedWrites === "number" && typeof health.pendingExternalFiles === "number" && typeof health.watchingExternal === "boolean" && typeof activity?.indexing === "boolean" && typeof activity.currentFile === "number" && typeof activity.totalFiles === "number" && typeof activity.generation === "number";
|
|
143
|
+
}
|
|
144
|
+
function delay(ms) {
|
|
145
|
+
return new Promise((resolve) => {
|
|
146
|
+
const timer = setTimeout(resolve, ms);
|
|
147
|
+
timer.unref?.();
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
function cancellationError(signal) {
|
|
151
|
+
return signal.reason instanceof Error ? signal.reason : new Error("Indexing cancelled");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/codebase-index/project-server-client.ts
|
|
155
|
+
import { spawn } from "node:child_process";
|
|
156
|
+
import * as fs2 from "node:fs";
|
|
157
|
+
import * as net from "node:net";
|
|
158
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
159
|
+
var ProjectServerConnection = class {
|
|
160
|
+
constructor(projectRoot, indexDir, endpoint) {
|
|
161
|
+
this.projectRoot = projectRoot;
|
|
162
|
+
this.indexDir = indexDir;
|
|
163
|
+
this.endpoint = endpoint;
|
|
164
|
+
this.transition("offline");
|
|
165
|
+
}
|
|
166
|
+
projectRoot;
|
|
167
|
+
indexDir;
|
|
168
|
+
endpoint;
|
|
169
|
+
socket = null;
|
|
170
|
+
/**
|
|
171
|
+
* Raw inbound bytes for the unified per-frame reader. Frames are sniffed
|
|
172
|
+
* individually — JSON text (newline-terminated) or binary (magic 0x57) —
|
|
173
|
+
* instead of latching a read mode, so a JSON broadcast between binary
|
|
174
|
+
* frames cannot desynchronize the reader.
|
|
175
|
+
*/
|
|
176
|
+
readBuffer = Buffer.alloc(0);
|
|
177
|
+
/** P6: true once the server advertises binary support and client accepts. */
|
|
178
|
+
useBinary = false;
|
|
179
|
+
info = null;
|
|
180
|
+
activity = null;
|
|
181
|
+
health = null;
|
|
182
|
+
healthCheck = null;
|
|
183
|
+
connecting = null;
|
|
184
|
+
connectResolve = null;
|
|
185
|
+
connectReject = null;
|
|
186
|
+
nextId = 1;
|
|
187
|
+
/** WS-027: read from the owner-only metadata file — see currentAuthToken. */
|
|
188
|
+
authToken;
|
|
189
|
+
pending = /* @__PURE__ */ new Map();
|
|
190
|
+
transition(status, options = {}) {
|
|
191
|
+
const previous = connectionStates.get(this.endpoint);
|
|
192
|
+
const pid = options.pid ?? (status === "connected" ? this.info?.pid : void 0);
|
|
193
|
+
const lastError = options.error === void 0 ? status === "error" || status === "degraded" || status === "unresponsive" ? previous?.lastError : void 0 : options.error instanceof Error ? options.error.message : String(options.error);
|
|
194
|
+
publishConnectionState(this.endpoint, {
|
|
195
|
+
status,
|
|
196
|
+
connected: status === "connected" || status === "degraded" || status === "unresponsive",
|
|
197
|
+
projectRoot: this.projectRoot,
|
|
198
|
+
indexDir: this.indexDir,
|
|
199
|
+
endpoint: this.endpoint,
|
|
200
|
+
pid,
|
|
201
|
+
lastError,
|
|
202
|
+
...this.activity ? { activity: this.activity } : {},
|
|
203
|
+
...this.health ? { health: this.health } : {}
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
isConnected() {
|
|
207
|
+
return this.socket !== null && !this.socket.destroyed && this.info !== null;
|
|
208
|
+
}
|
|
209
|
+
/** Safe LRU candidate: no request, connect, or health probe is in flight. */
|
|
210
|
+
isEvictable() {
|
|
211
|
+
return this.pending.size === 0 && this.connecting === null && this.healthCheck === null;
|
|
212
|
+
}
|
|
213
|
+
async checkHealth(spawnIfMissing = false, timeoutMs = SERVER_HEALTH_TIMEOUT_MS) {
|
|
214
|
+
await this.ensureConnected(spawnIfMissing);
|
|
215
|
+
if (this.healthCheck) return this.healthCheck;
|
|
216
|
+
const startedAt = Date.now();
|
|
217
|
+
this.healthCheck = this.request({ type: "ping" }, { timeoutMs }).then((server) => {
|
|
218
|
+
const now = Date.now();
|
|
219
|
+
this.health = {
|
|
220
|
+
status: "healthy",
|
|
221
|
+
checkedAt: now,
|
|
222
|
+
lastHealthyAt: now,
|
|
223
|
+
latencyMs: Math.max(0, now - startedAt),
|
|
224
|
+
missedHeartbeats: 0,
|
|
225
|
+
...isProjectIndexServerHealth(server) ? { server } : {}
|
|
226
|
+
};
|
|
227
|
+
this.transition("connected", { pid: this.info?.pid });
|
|
228
|
+
return this.health;
|
|
229
|
+
}).catch((error) => {
|
|
230
|
+
if (!this.isConnected()) throw error;
|
|
231
|
+
if ((this.health?.lastHealthyAt ?? 0) > startedAt) return this.health;
|
|
232
|
+
const missedHeartbeats = (this.health?.missedHeartbeats ?? 0) + 1;
|
|
233
|
+
const status = missedHeartbeats >= 3 ? "unresponsive" : "degraded";
|
|
234
|
+
this.health = {
|
|
235
|
+
status,
|
|
236
|
+
checkedAt: Date.now(),
|
|
237
|
+
lastHealthyAt: this.health?.lastHealthyAt ?? null,
|
|
238
|
+
latencyMs: null,
|
|
239
|
+
missedHeartbeats,
|
|
240
|
+
...this.health?.server ? { server: this.health.server } : {}
|
|
241
|
+
};
|
|
242
|
+
this.transition(status, { pid: this.info?.pid, error });
|
|
243
|
+
return this.health;
|
|
244
|
+
}).finally(() => {
|
|
245
|
+
this.healthCheck = null;
|
|
246
|
+
});
|
|
247
|
+
return this.healthCheck;
|
|
248
|
+
}
|
|
249
|
+
markResponsive() {
|
|
250
|
+
const now = Date.now();
|
|
251
|
+
this.health = {
|
|
252
|
+
status: "healthy",
|
|
253
|
+
checkedAt: now,
|
|
254
|
+
lastHealthyAt: now,
|
|
255
|
+
latencyMs: this.health?.latencyMs ?? null,
|
|
256
|
+
missedHeartbeats: 0,
|
|
257
|
+
...this.health?.server ? { server: this.health.server } : {}
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
async call(op, args, options) {
|
|
261
|
+
if (options.signal?.aborted) throw cancellationError(options.signal);
|
|
262
|
+
await this.ensureConnected(true);
|
|
263
|
+
if (options.signal?.aborted) throw cancellationError(options.signal);
|
|
264
|
+
return this.request({ type: "request", op, args }, options);
|
|
265
|
+
}
|
|
266
|
+
async shutdownRemote(reason) {
|
|
267
|
+
try {
|
|
268
|
+
await this.ensureConnected(false);
|
|
269
|
+
} catch {
|
|
270
|
+
return { stopped: false, reason: "not-running" };
|
|
271
|
+
}
|
|
272
|
+
const pid = this.info?.pid;
|
|
273
|
+
try {
|
|
274
|
+
this.transition("stopping", { pid });
|
|
275
|
+
await this.request(
|
|
276
|
+
{ type: "shutdown", reason },
|
|
277
|
+
{ timeoutMs: SERVER_CONTROL_TIMEOUT_MS }
|
|
278
|
+
);
|
|
279
|
+
return { stopped: true, pid };
|
|
280
|
+
} catch (error) {
|
|
281
|
+
const forceKilled = this.forceKillKnownServer();
|
|
282
|
+
return {
|
|
283
|
+
stopped: forceKilled,
|
|
284
|
+
pid,
|
|
285
|
+
reason: forceKilled ? `force-killed after graceful shutdown failed: ${error instanceof Error ? error.message : String(error)}` : error instanceof Error ? error.message : String(error)
|
|
286
|
+
};
|
|
287
|
+
} finally {
|
|
288
|
+
this.close();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
async configure(watchExternal, debounceMs, coalesceWindowMs) {
|
|
292
|
+
await this.ensureConnected(true);
|
|
293
|
+
const startedAt = Date.now();
|
|
294
|
+
const result = await this.request(
|
|
295
|
+
{ type: "configure", watchExternal, debounceMs, coalesceWindowMs },
|
|
296
|
+
{ timeoutMs: SERVER_CONTROL_TIMEOUT_MS }
|
|
297
|
+
);
|
|
298
|
+
if (isProjectIndexServerHealth(result.health)) {
|
|
299
|
+
const now = Date.now();
|
|
300
|
+
this.health = {
|
|
301
|
+
status: "healthy",
|
|
302
|
+
checkedAt: now,
|
|
303
|
+
lastHealthyAt: now,
|
|
304
|
+
latencyMs: Math.max(0, now - startedAt),
|
|
305
|
+
missedHeartbeats: 0,
|
|
306
|
+
server: result.health
|
|
307
|
+
};
|
|
308
|
+
this.transition("connected", { pid: this.info?.pid });
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
close() {
|
|
312
|
+
const socket = this.socket;
|
|
313
|
+
this.socket = null;
|
|
314
|
+
this.info = null;
|
|
315
|
+
this.activity = null;
|
|
316
|
+
this.health = null;
|
|
317
|
+
this.useBinary = false;
|
|
318
|
+
this.readBuffer = Buffer.alloc(0);
|
|
319
|
+
this.connectReject?.(new Error("codebase-index client disconnected"));
|
|
320
|
+
this.connectResolve = null;
|
|
321
|
+
this.connectReject = null;
|
|
322
|
+
if (socket && !socket.destroyed) socket.destroy();
|
|
323
|
+
this.rejectPending(new Error("codebase-index client disconnected"));
|
|
324
|
+
this.transition("offline");
|
|
325
|
+
maybeStopHeartbeatLoop();
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* WS-027: the per-process token, read from the daemon's owner-only metadata
|
|
329
|
+
* file — never from the `hello` frame, which the daemon sends to every
|
|
330
|
+
* socket that connects (the mistake WS-028 found in the SAGE daemon).
|
|
331
|
+
*
|
|
332
|
+
* Read lazily and re-read while unknown: the daemon starts listening before
|
|
333
|
+
* it writes metadata (endpoint ownership has to be won first), and a
|
|
334
|
+
* respawned daemon mints a new token, so a cached wrong one would be sticky.
|
|
335
|
+
*/
|
|
336
|
+
currentAuthToken() {
|
|
337
|
+
if (this.authToken === void 0) {
|
|
338
|
+
try {
|
|
339
|
+
const raw = fs2.readFileSync(
|
|
340
|
+
projectIndexServerMetadataPath(this.projectRoot, this.indexDir),
|
|
341
|
+
"utf8"
|
|
342
|
+
);
|
|
343
|
+
const parsed = JSON.parse(raw);
|
|
344
|
+
if (typeof parsed.authToken === "string" && parsed.authToken.length > 0) {
|
|
345
|
+
this.authToken = parsed.authToken;
|
|
346
|
+
}
|
|
347
|
+
} catch {
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return this.authToken;
|
|
351
|
+
}
|
|
352
|
+
request(message, options) {
|
|
353
|
+
const socket = this.socket;
|
|
354
|
+
if (!socket || socket.destroyed) {
|
|
355
|
+
return Promise.reject(new Error("codebase-index server connection is not available"));
|
|
356
|
+
}
|
|
357
|
+
const id = this.nextId++;
|
|
358
|
+
return new Promise((resolve, reject) => {
|
|
359
|
+
const timer = setTimeout(() => {
|
|
360
|
+
const entry = this.pending.get(id);
|
|
361
|
+
if (!entry) return;
|
|
362
|
+
this.pending.delete(id);
|
|
363
|
+
this.write({ type: "cancel", id });
|
|
364
|
+
const error = new IndexTimeoutError(
|
|
365
|
+
`Index ${message.type === "request" ? message.op : message.type} exceeded its ${options.timeoutMs}ms watchdog timeout`
|
|
366
|
+
);
|
|
367
|
+
this.cleanupPending(entry);
|
|
368
|
+
entry.reject(error);
|
|
369
|
+
}, options.timeoutMs);
|
|
370
|
+
timer.unref?.();
|
|
371
|
+
const signal = options.signal;
|
|
372
|
+
const onAbort = signal ? () => {
|
|
373
|
+
const entry = this.pending.get(id);
|
|
374
|
+
if (!entry) return;
|
|
375
|
+
this.pending.delete(id);
|
|
376
|
+
this.write({ type: "cancel", id });
|
|
377
|
+
this.cleanupPending(entry);
|
|
378
|
+
entry.reject(cancellationError(signal));
|
|
379
|
+
} : void 0;
|
|
380
|
+
this.pending.set(id, {
|
|
381
|
+
resolve,
|
|
382
|
+
reject,
|
|
383
|
+
timer,
|
|
384
|
+
signal,
|
|
385
|
+
onAbort,
|
|
386
|
+
onProgress: options.onProgress
|
|
387
|
+
});
|
|
388
|
+
if (signal && onAbort) {
|
|
389
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
390
|
+
if (signal.aborted) {
|
|
391
|
+
onAbort();
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
this.write({ ...message, id, authToken: this.currentAuthToken() });
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
async ensureConnected(spawnIfMissing) {
|
|
399
|
+
if (this.socket && !this.socket.destroyed && this.info) return;
|
|
400
|
+
if (this.connecting) return this.connecting;
|
|
401
|
+
this.transition("connecting");
|
|
402
|
+
this.connecting = this.connectWithElection(spawnIfMissing).catch((error) => {
|
|
403
|
+
this.transition("error", { error });
|
|
404
|
+
throw error;
|
|
405
|
+
}).finally(() => {
|
|
406
|
+
this.connecting = null;
|
|
407
|
+
});
|
|
408
|
+
return this.connecting;
|
|
409
|
+
}
|
|
410
|
+
async connectWithElection(spawnIfMissing) {
|
|
411
|
+
const deadline = Date.now() + (spawnIfMissing ? SERVER_START_TIMEOUT_MS : CONNECT_ATTEMPT_TIMEOUT_MS);
|
|
412
|
+
let spawned = false;
|
|
413
|
+
let staleAttempts = 0;
|
|
414
|
+
let lastError = new Error("codebase-index server unavailable");
|
|
415
|
+
while (Date.now() < deadline) {
|
|
416
|
+
try {
|
|
417
|
+
await this.connectOnce();
|
|
418
|
+
return;
|
|
419
|
+
} catch (error) {
|
|
420
|
+
lastError = error;
|
|
421
|
+
if (error instanceof StaleProjectIndexServerError) {
|
|
422
|
+
staleAttempts++;
|
|
423
|
+
if (!spawnIfMissing) break;
|
|
424
|
+
if (staleAttempts >= 3) this.forceKillServer(error.pid);
|
|
425
|
+
spawned = false;
|
|
426
|
+
await delay(100);
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (!spawnIfMissing) break;
|
|
431
|
+
if (!spawned) {
|
|
432
|
+
this.spawnDetachedServer();
|
|
433
|
+
spawned = true;
|
|
434
|
+
}
|
|
435
|
+
await delay(75);
|
|
436
|
+
}
|
|
437
|
+
throw lastError;
|
|
438
|
+
}
|
|
439
|
+
connectOnce() {
|
|
440
|
+
this.socket?.destroy();
|
|
441
|
+
this.socket = null;
|
|
442
|
+
this.authToken = void 0;
|
|
443
|
+
this.info = null;
|
|
444
|
+
this.activity = null;
|
|
445
|
+
this.health = null;
|
|
446
|
+
this.readBuffer = Buffer.alloc(0);
|
|
447
|
+
this.useBinary = false;
|
|
448
|
+
return new Promise((resolve, reject) => {
|
|
449
|
+
const socket = net.createConnection(this.endpoint);
|
|
450
|
+
this.socket = socket;
|
|
451
|
+
const timer = setTimeout(() => {
|
|
452
|
+
reject(new Error("codebase-index server handshake timed out"));
|
|
453
|
+
socket.destroy();
|
|
454
|
+
}, CONNECT_ATTEMPT_TIMEOUT_MS);
|
|
455
|
+
timer.unref?.();
|
|
456
|
+
const finishResolve = () => {
|
|
457
|
+
clearTimeout(timer);
|
|
458
|
+
this.connectResolve = null;
|
|
459
|
+
this.connectReject = null;
|
|
460
|
+
resolve();
|
|
461
|
+
};
|
|
462
|
+
const finishReject = (error) => {
|
|
463
|
+
clearTimeout(timer);
|
|
464
|
+
this.connectResolve = null;
|
|
465
|
+
this.connectReject = null;
|
|
466
|
+
reject(error);
|
|
467
|
+
};
|
|
468
|
+
this.connectResolve = finishResolve;
|
|
469
|
+
this.connectReject = finishReject;
|
|
470
|
+
socket.on("data", (chunk) => this.onData(socket, chunk));
|
|
471
|
+
socket.on("error", (error) => {
|
|
472
|
+
if (!this.info) finishReject(error);
|
|
473
|
+
});
|
|
474
|
+
socket.on("close", () => this.onClose(socket));
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Unified per-frame reader. Each frame is sniffed by its first byte:
|
|
479
|
+
* `0x57` ('W') → length-prefixed MessagePack binary, anything else →
|
|
480
|
+
* newline-delimited JSON text. Sniffing per frame (instead of latching a
|
|
481
|
+
* mode) is what makes mixed streams work: the server may interleave a JSON
|
|
482
|
+
* `index-state` broadcast between binary responses, and an old JSON-only
|
|
483
|
+
* server stays readable while `useBinary` is armed.
|
|
484
|
+
*
|
|
485
|
+
* Multibyte UTF-8 in JSON frames is safe: raw `0x0a` only occurs as the
|
|
486
|
+
* JSON delimiter (inside JSON strings `\n` is escaped), so a complete line
|
|
487
|
+
* is always complete UTF-8.
|
|
488
|
+
*/
|
|
489
|
+
onData(socket, chunk) {
|
|
490
|
+
if (socket !== this.socket) return;
|
|
491
|
+
this.readBuffer = this.readBuffer.length === 0 ? chunk : Buffer.concat([this.readBuffer, chunk]);
|
|
492
|
+
while (true) {
|
|
493
|
+
if (this.readBuffer.length === 0) return;
|
|
494
|
+
if (this.useBinary && isBinaryFrame(this.readBuffer[0])) {
|
|
495
|
+
if (this.readBuffer.length < 5) return;
|
|
496
|
+
const frameLen = this.readBuffer.readUInt32BE(1);
|
|
497
|
+
if (frameLen > MAX_BINARY_FRAME_BYTES) {
|
|
498
|
+
socket.destroy();
|
|
499
|
+
this.transition("offline", { error: "binary frame length exceeds the IPC limit" });
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
if (this.readBuffer.length < 5 + frameLen) return;
|
|
503
|
+
const payload = this.readBuffer.subarray(5, 5 + frameLen);
|
|
504
|
+
this.readBuffer = this.readBuffer.subarray(5 + frameLen);
|
|
505
|
+
let message2;
|
|
506
|
+
try {
|
|
507
|
+
message2 = decodeBinaryFrame(payload);
|
|
508
|
+
} catch {
|
|
509
|
+
socket.destroy(new Error("invalid binary codebase-index server response"));
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
this.onMessage(message2);
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
const newline = this.readBuffer.indexOf(10);
|
|
516
|
+
if (newline < 0) {
|
|
517
|
+
if (this.readBuffer.length > PROJECT_INDEX_SERVER_MAX_FRAME_CHARS) {
|
|
518
|
+
socket.destroy(new Error("codebase-index server response exceeds the IPC limit"));
|
|
519
|
+
}
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
if (newline > PROJECT_INDEX_SERVER_MAX_FRAME_CHARS) {
|
|
523
|
+
socket.destroy(new Error("codebase-index server response exceeds the IPC limit"));
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
const line = this.readBuffer.subarray(0, newline).toString("utf8");
|
|
527
|
+
this.readBuffer = this.readBuffer.subarray(newline + 1);
|
|
528
|
+
if (!line) continue;
|
|
529
|
+
let message;
|
|
530
|
+
try {
|
|
531
|
+
message = JSON.parse(line);
|
|
532
|
+
} catch {
|
|
533
|
+
socket.destroy(new Error("invalid codebase-index server response"));
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
this.onMessage(message);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
onMessage(message) {
|
|
540
|
+
if (message.type === "hello") {
|
|
541
|
+
if (message.protocolVersion !== PROJECT_INDEX_SERVER_PROTOCOL_VERSION) {
|
|
542
|
+
this.rejectStaleServer(
|
|
543
|
+
message,
|
|
544
|
+
`codebase-index protocol mismatch: client=${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}, server=${message.protocolVersion}`
|
|
545
|
+
);
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
const expectedBuildId = projectIndexServerExpectedBuildId();
|
|
549
|
+
if (expectedBuildId && message.buildId !== expectedBuildId) {
|
|
550
|
+
this.rejectStaleServer(
|
|
551
|
+
message,
|
|
552
|
+
`codebase-index build mismatch: client=${expectedBuildId}, server=${message.buildId ?? "legacy"}`
|
|
553
|
+
);
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
this.info = message;
|
|
557
|
+
this.markResponsive();
|
|
558
|
+
if (message.binarySupported && binaryFramingEnabled()) this.useBinary = true;
|
|
559
|
+
this.transition("connected", { pid: message.pid });
|
|
560
|
+
ensureHeartbeatLoop();
|
|
561
|
+
this.connectResolve?.();
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
if (message.type === "index-state") {
|
|
565
|
+
this.activity = message.state;
|
|
566
|
+
this.markResponsive();
|
|
567
|
+
this.transition("connected", { pid: this.info?.pid });
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
const entry = this.pending.get(message.id);
|
|
571
|
+
if (!entry) return;
|
|
572
|
+
this.markResponsive();
|
|
573
|
+
const status = connectionStates.get(this.endpoint)?.status;
|
|
574
|
+
if (status === "degraded" || status === "unresponsive") {
|
|
575
|
+
this.transition("connected", { pid: this.info?.pid });
|
|
576
|
+
}
|
|
577
|
+
if (message.type === "progress") {
|
|
578
|
+
entry.onProgress?.(message.current, message.total);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
this.pending.delete(message.id);
|
|
582
|
+
this.cleanupPending(entry);
|
|
583
|
+
if (message.ok) entry.resolve(message.result);
|
|
584
|
+
else entry.reject(remoteError(message.error, message.errorName));
|
|
585
|
+
}
|
|
586
|
+
onClose(socket) {
|
|
587
|
+
if (socket !== this.socket) return;
|
|
588
|
+
const wasConnected = this.info !== null;
|
|
589
|
+
this.socket = null;
|
|
590
|
+
this.info = null;
|
|
591
|
+
this.activity = null;
|
|
592
|
+
this.health = null;
|
|
593
|
+
const error = new Error("codebase-index server connection closed");
|
|
594
|
+
this.connectReject?.(error);
|
|
595
|
+
this.connectResolve = null;
|
|
596
|
+
this.connectReject = null;
|
|
597
|
+
this.rejectPending(error);
|
|
598
|
+
if (wasConnected) this.transition("error", { error });
|
|
599
|
+
maybeStopHeartbeatLoop();
|
|
600
|
+
}
|
|
601
|
+
cleanupPending(entry) {
|
|
602
|
+
clearTimeout(entry.timer);
|
|
603
|
+
if (entry.signal && entry.onAbort) {
|
|
604
|
+
entry.signal.removeEventListener("abort", entry.onAbort);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
rejectPending(error) {
|
|
608
|
+
const entries = [...this.pending.values()];
|
|
609
|
+
this.pending.clear();
|
|
610
|
+
for (const entry of entries) {
|
|
611
|
+
this.cleanupPending(entry);
|
|
612
|
+
entry.reject(error);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
write(message) {
|
|
616
|
+
const socket = this.socket;
|
|
617
|
+
if (!socket || socket.destroyed) return;
|
|
618
|
+
if (this.useBinary) {
|
|
619
|
+
socket.write(encodeBinaryFrame(message));
|
|
620
|
+
} else {
|
|
621
|
+
socket.write(encodeProjectServerMessage(message));
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
rejectStaleServer(message, reason) {
|
|
625
|
+
const socket = this.socket;
|
|
626
|
+
if (socket && !socket.destroyed) {
|
|
627
|
+
socket.write(
|
|
628
|
+
encodeProjectServerMessage({
|
|
629
|
+
type: "shutdown",
|
|
630
|
+
id: 0,
|
|
631
|
+
reason: "stale-build-replacement"
|
|
632
|
+
})
|
|
633
|
+
);
|
|
634
|
+
const timer = setTimeout(() => socket.destroy(), 25);
|
|
635
|
+
timer.unref?.();
|
|
636
|
+
}
|
|
637
|
+
this.connectReject?.(new StaleProjectIndexServerError(reason, message.pid));
|
|
638
|
+
}
|
|
639
|
+
spawnDetachedServer() {
|
|
640
|
+
const url = resolveProjectServerUrl();
|
|
641
|
+
if (!url) throw new Error("built codebase-index project server is unavailable");
|
|
642
|
+
if (process.platform !== "win32") {
|
|
643
|
+
try {
|
|
644
|
+
fs2.rmSync(this.endpoint, { force: true });
|
|
645
|
+
} catch {
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
const args = [fileURLToPath2(url), "--project-root", this.projectRoot];
|
|
649
|
+
if (this.indexDir) args.push("--index-dir", this.indexDir);
|
|
650
|
+
const child = spawn(process.execPath, args, {
|
|
651
|
+
detached: true,
|
|
652
|
+
stdio: "ignore",
|
|
653
|
+
windowsHide: true,
|
|
654
|
+
env: process.env
|
|
655
|
+
});
|
|
656
|
+
child.unref();
|
|
657
|
+
}
|
|
658
|
+
forceKillKnownServer() {
|
|
659
|
+
const pid = this.info?.pid;
|
|
660
|
+
return pid ? this.forceKillServer(pid) : false;
|
|
661
|
+
}
|
|
662
|
+
forceKillServer(pid) {
|
|
663
|
+
if (pid === process.pid) return false;
|
|
664
|
+
try {
|
|
665
|
+
process.kill(pid);
|
|
666
|
+
const metadataPath = projectIndexServerMetadataPath(this.projectRoot, this.indexDir);
|
|
667
|
+
try {
|
|
668
|
+
const metadata = JSON.parse(fs2.readFileSync(metadataPath, "utf8"));
|
|
669
|
+
if (metadata.pid === pid) fs2.rmSync(metadataPath, { force: true });
|
|
670
|
+
} catch {
|
|
671
|
+
}
|
|
672
|
+
return true;
|
|
673
|
+
} catch {
|
|
674
|
+
return false;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
var connections = /* @__PURE__ */ new Map();
|
|
679
|
+
var MAX_CACHED_CONNECTIONS = 8;
|
|
680
|
+
var heartbeatTimer;
|
|
681
|
+
function binaryFramingEnabled() {
|
|
682
|
+
const flag = process.env["WRONGSTACK_INDEX_BINARY"];
|
|
683
|
+
return flag === "1" || flag === "true";
|
|
684
|
+
}
|
|
685
|
+
function forgetConnection(endpoint, connection) {
|
|
686
|
+
if (connections.get(endpoint) === connection) connections.delete(endpoint);
|
|
687
|
+
connection.close();
|
|
688
|
+
connectionStates.delete(endpoint);
|
|
689
|
+
if (latestConnectionState.endpoint !== endpoint) return;
|
|
690
|
+
setLatestConnectionState(
|
|
691
|
+
[...connectionStates.values()].at(-1) ?? {
|
|
692
|
+
status: isProjectIndexServerAvailable() ? "offline" : "unavailable",
|
|
693
|
+
connected: false
|
|
694
|
+
}
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
function trimConnectionCache(protectedConnection) {
|
|
698
|
+
if (connections.size <= MAX_CACHED_CONNECTIONS) return;
|
|
699
|
+
for (const [endpoint, connection] of connections) {
|
|
700
|
+
if (connections.size <= MAX_CACHED_CONNECTIONS) break;
|
|
701
|
+
if (connection === protectedConnection || !connection.isEvictable()) continue;
|
|
702
|
+
forgetConnection(endpoint, connection);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
function ensureHeartbeatLoop() {
|
|
706
|
+
if (heartbeatTimer) return;
|
|
707
|
+
heartbeatTimer = setInterval(() => {
|
|
708
|
+
for (const connection of connections.values()) {
|
|
709
|
+
if (connection.isConnected()) void connection.checkHealth(false).catch(() => {
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
}, SERVER_HEARTBEAT_INTERVAL_MS);
|
|
713
|
+
heartbeatTimer.unref?.();
|
|
714
|
+
}
|
|
715
|
+
function maybeStopHeartbeatLoop() {
|
|
716
|
+
if (!heartbeatTimer) return;
|
|
717
|
+
if ([...connections.values()].some((connection) => connection.isConnected())) return;
|
|
718
|
+
clearInterval(heartbeatTimer);
|
|
719
|
+
heartbeatTimer = void 0;
|
|
720
|
+
}
|
|
721
|
+
function connectionFor(projectRoot, indexDir) {
|
|
722
|
+
const endpoint = projectIndexServerEndpoint(projectRoot, indexDir);
|
|
723
|
+
let connection = connections.get(endpoint);
|
|
724
|
+
if (!connection) {
|
|
725
|
+
connection = new ProjectServerConnection(projectRoot, indexDir, endpoint);
|
|
726
|
+
connections.set(endpoint, connection);
|
|
727
|
+
} else {
|
|
728
|
+
connections.delete(endpoint);
|
|
729
|
+
connections.set(endpoint, connection);
|
|
730
|
+
}
|
|
731
|
+
trimConnectionCache(connection);
|
|
732
|
+
return connection;
|
|
733
|
+
}
|
|
734
|
+
function callProjectIndexServer(op, args, options) {
|
|
735
|
+
return connectionFor(args.projectRoot, args.indexDir).call(op, args, options);
|
|
736
|
+
}
|
|
737
|
+
function ensureProjectIndexServer(options) {
|
|
738
|
+
return connectionFor(options.projectRoot, options.indexDir).configure(
|
|
739
|
+
options.watchExternal,
|
|
740
|
+
options.debounceMs,
|
|
741
|
+
options.coalesceWindowMs
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
function checkProjectIndexServerHealth(projectRoot, indexDir, options = {}) {
|
|
745
|
+
return connectionFor(projectRoot, indexDir).checkHealth(
|
|
746
|
+
false,
|
|
747
|
+
options.timeoutMs ?? SERVER_HEALTH_TIMEOUT_MS
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
async function shutdownProjectIndexServer(projectRoot, indexDir, reason) {
|
|
751
|
+
const endpoint = projectIndexServerEndpoint(projectRoot, indexDir);
|
|
752
|
+
const connection = connectionFor(projectRoot, indexDir);
|
|
753
|
+
try {
|
|
754
|
+
return await connection.shutdownRemote(reason);
|
|
755
|
+
} finally {
|
|
756
|
+
connection.close();
|
|
757
|
+
connections.delete(endpoint);
|
|
758
|
+
connectionStates.delete(endpoint);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
function closeProjectIndexServerClients() {
|
|
762
|
+
for (const connection of connections.values()) connection.close();
|
|
763
|
+
connections.clear();
|
|
764
|
+
connectionStates.clear();
|
|
765
|
+
setLatestConnectionState({
|
|
766
|
+
status: isProjectIndexServerAvailable() ? "offline" : "unavailable",
|
|
767
|
+
connected: false
|
|
768
|
+
});
|
|
769
|
+
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
|
770
|
+
heartbeatTimer = void 0;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// src/codebase-index/background-indexer.ts
|
|
774
|
+
import * as fs3 from "node:fs";
|
|
775
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
776
|
+
import { Worker } from "node:worker_threads";
|
|
777
|
+
var DEFAULT_FULL_INDEX_TIMEOUT_MS = 24e4;
|
|
778
|
+
var DEFAULT_INCREMENTAL_TIMEOUT_MS = 6e4;
|
|
779
|
+
var DEFAULT_QUERY_TIMEOUT_MS = 3e4;
|
|
780
|
+
var _ready = false;
|
|
781
|
+
var _indexing = false;
|
|
782
|
+
var _currentFile = 0;
|
|
783
|
+
var _totalFiles = 0;
|
|
784
|
+
var _lastError = null;
|
|
785
|
+
function isIndexReady() {
|
|
786
|
+
return _ready;
|
|
787
|
+
}
|
|
788
|
+
function isIndexing() {
|
|
789
|
+
return _indexing;
|
|
790
|
+
}
|
|
791
|
+
function getIndexState() {
|
|
792
|
+
const server = getProjectIndexServerConnectionState();
|
|
793
|
+
const remoteActivity = server.activity;
|
|
794
|
+
const remoteIndexing = remoteActivity?.indexing ?? false;
|
|
795
|
+
return {
|
|
796
|
+
ready: _ready || (remoteActivity?.generation ?? 0) > 0,
|
|
797
|
+
indexing: _indexing || remoteIndexing,
|
|
798
|
+
currentFile: remoteIndexing ? remoteActivity?.currentFile ?? 0 : _currentFile,
|
|
799
|
+
totalFiles: remoteIndexing ? remoteActivity?.totalFiles ?? 0 : _totalFiles,
|
|
800
|
+
lastError: remoteActivity?.lastError ?? _lastError,
|
|
801
|
+
server,
|
|
802
|
+
circuit: indexCircuitBreaker.snapshot()
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
var _listeners = [];
|
|
806
|
+
function onIndexStateChange(listener) {
|
|
807
|
+
_listeners.push(listener);
|
|
808
|
+
return () => {
|
|
809
|
+
_listeners = _listeners.filter((l) => l !== listener);
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
function emitState() {
|
|
813
|
+
const state = getIndexState();
|
|
814
|
+
for (const l of _listeners) l(state);
|
|
815
|
+
}
|
|
816
|
+
onProjectIndexServerConnectionStateChange(() => emitState());
|
|
817
|
+
function setIndexProgress(current, total) {
|
|
818
|
+
_currentFile = current;
|
|
819
|
+
_totalFiles = total;
|
|
820
|
+
emitState();
|
|
821
|
+
}
|
|
822
|
+
var worker = null;
|
|
823
|
+
var workerUnavailable = false;
|
|
824
|
+
var nextRpcId = 1;
|
|
825
|
+
var pending = /* @__PURE__ */ new Map();
|
|
826
|
+
function resolveWorkerUrl() {
|
|
827
|
+
if (process.env["WRONGSTACK_INDEX_INLINE"]) return null;
|
|
828
|
+
for (const rel of ["./worker.js", "./codebase-index/worker.js"]) {
|
|
829
|
+
try {
|
|
830
|
+
const url = new URL(rel, import.meta.url);
|
|
831
|
+
if (url.protocol === "file:" && fs3.existsSync(fileURLToPath3(url))) return url;
|
|
832
|
+
} catch {
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
return null;
|
|
836
|
+
}
|
|
837
|
+
function failAllPending(err) {
|
|
838
|
+
const entries = [...pending.values()];
|
|
839
|
+
pending.clear();
|
|
840
|
+
for (const p of entries) p.reject(err);
|
|
841
|
+
}
|
|
842
|
+
function ensureWorker() {
|
|
843
|
+
if (worker) return worker;
|
|
844
|
+
if (workerUnavailable) return null;
|
|
845
|
+
const url = resolveWorkerUrl();
|
|
846
|
+
if (!url) {
|
|
847
|
+
workerUnavailable = true;
|
|
848
|
+
return null;
|
|
849
|
+
}
|
|
850
|
+
try {
|
|
851
|
+
const w = new Worker(url, { name: "wstack-codebase-index" });
|
|
852
|
+
w.unref();
|
|
853
|
+
w.on("message", (msg) => {
|
|
854
|
+
if (msg.type === "progress") {
|
|
855
|
+
pending.get(msg.id)?.onProgress?.(msg.current, msg.total);
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
const entry = pending.get(msg.id);
|
|
859
|
+
if (!entry) return;
|
|
860
|
+
pending.delete(msg.id);
|
|
861
|
+
if (msg.ok) entry.resolve(msg.result);
|
|
862
|
+
else {
|
|
863
|
+
const error = msg.errorName === "LockError" ? new LockError(msg.error) : new Error(msg.error);
|
|
864
|
+
if (msg.errorName && msg.errorName !== "Error") {
|
|
865
|
+
error.name = msg.errorName;
|
|
866
|
+
}
|
|
867
|
+
entry.reject(error);
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
w.on("error", (err) => {
|
|
871
|
+
if (worker !== w) return;
|
|
872
|
+
worker = null;
|
|
873
|
+
failAllPending(err);
|
|
874
|
+
});
|
|
875
|
+
w.on("exit", () => {
|
|
876
|
+
if (worker !== w) return;
|
|
877
|
+
worker = null;
|
|
878
|
+
failAllPending(new Error("codebase-index worker exited"));
|
|
879
|
+
});
|
|
880
|
+
worker = w;
|
|
881
|
+
return w;
|
|
882
|
+
} catch {
|
|
883
|
+
workerUnavailable = true;
|
|
884
|
+
return null;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
function terminateWorker(reason) {
|
|
888
|
+
const w = worker;
|
|
889
|
+
worker = null;
|
|
890
|
+
failAllPending(reason);
|
|
891
|
+
if (w) void w.terminate().catch(() => {
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
async function shutdownCodebaseIndexHost() {
|
|
895
|
+
cancelPendingReindexes();
|
|
896
|
+
closeProjectIndexServerClients();
|
|
897
|
+
indexStorePool.closeAll();
|
|
898
|
+
const w = worker;
|
|
899
|
+
worker = null;
|
|
900
|
+
failAllPending(new Error("codebase-index host shut down"));
|
|
901
|
+
workerUnavailable = false;
|
|
902
|
+
if (w) {
|
|
903
|
+
try {
|
|
904
|
+
await w.terminate();
|
|
905
|
+
} catch {
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
var warnedInvalidEndpoints = /* @__PURE__ */ new Set();
|
|
910
|
+
function warnEndpointInvalidOnce(availability) {
|
|
911
|
+
if (warnedInvalidEndpoints.has(availability.endpoint)) return;
|
|
912
|
+
warnedInvalidEndpoints.add(availability.endpoint);
|
|
913
|
+
process.stderr.write(
|
|
914
|
+
`codebase-index: socket path is ${availability.byteLength} bytes, over this platform's ${availability.maxBytes}-byte sun_path limit (${availability.endpoint}). Subsequent calls will reject until TMPDIR is shortened to restore the shared daemon.
|
|
915
|
+
`
|
|
916
|
+
);
|
|
917
|
+
}
|
|
918
|
+
function callIndexOp(op, args, opts) {
|
|
919
|
+
const availability = resolveProjectIndexDaemonAvailability(args.projectRoot, args.indexDir);
|
|
920
|
+
if (availability.kind === "available") {
|
|
921
|
+
return callProjectIndexServer(op, args, opts);
|
|
922
|
+
}
|
|
923
|
+
if (availability.kind === "missing-build") {
|
|
924
|
+
return Promise.reject(
|
|
925
|
+
new Error(
|
|
926
|
+
"Built codebase-index project server is unavailable. Build @wrongstack/tools, or set WRONGSTACK_INDEX_INLINE=1 to explicitly accept a process-local index."
|
|
927
|
+
)
|
|
928
|
+
);
|
|
929
|
+
}
|
|
930
|
+
if (availability.kind === "endpoint-invalid") {
|
|
931
|
+
warnEndpointInvalidOnce(availability);
|
|
932
|
+
return Promise.reject(
|
|
933
|
+
new Error(
|
|
934
|
+
`codebase-index: socket path is ${availability.byteLength} bytes, over this platform's ${availability.maxBytes}-byte sun_path limit (${availability.endpoint}). Set a shorter TMPDIR to relocate the shared daemon, or set WRONGSTACK_INDEX_INLINE=1 to explicitly opt into a process-local index.`
|
|
935
|
+
)
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
const w = ensureWorker();
|
|
939
|
+
if (!w) return callInline(op, args, opts);
|
|
940
|
+
if (opts.signal?.aborted) {
|
|
941
|
+
return Promise.reject(
|
|
942
|
+
opts.signal.reason instanceof Error ? opts.signal.reason : new Error("Indexing cancelled")
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
return new Promise((resolve, reject) => {
|
|
946
|
+
const id = nextRpcId++;
|
|
947
|
+
const timer = setTimeout(() => {
|
|
948
|
+
pending.delete(id);
|
|
949
|
+
const err = new IndexTimeoutError(
|
|
950
|
+
`Index ${op} exceeded its ${opts.timeoutMs}ms watchdog timeout`
|
|
951
|
+
);
|
|
952
|
+
terminateWorker(err);
|
|
953
|
+
reject(err);
|
|
954
|
+
}, opts.timeoutMs);
|
|
955
|
+
timer.unref?.();
|
|
956
|
+
const onAbort = () => {
|
|
957
|
+
w.postMessage({ type: "cancel", id });
|
|
958
|
+
};
|
|
959
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
960
|
+
const cleanup = () => {
|
|
961
|
+
clearTimeout(timer);
|
|
962
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
963
|
+
};
|
|
964
|
+
pending.set(id, {
|
|
965
|
+
resolve: (v) => {
|
|
966
|
+
cleanup();
|
|
967
|
+
resolve(v);
|
|
968
|
+
},
|
|
969
|
+
reject: (e) => {
|
|
970
|
+
cleanup();
|
|
971
|
+
reject(e);
|
|
972
|
+
},
|
|
973
|
+
onProgress: opts.onProgress
|
|
974
|
+
});
|
|
975
|
+
w.postMessage({ type: "request", id, op, args });
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
async function callInline(op, args, opts) {
|
|
979
|
+
if (op !== "index" && _indexing) {
|
|
980
|
+
const error = new Error(
|
|
981
|
+
"Codebase index refresh in progress; retry after the completed generation is published."
|
|
982
|
+
);
|
|
983
|
+
error.name = "IndexRefreshInProgressError";
|
|
984
|
+
throw error;
|
|
985
|
+
}
|
|
986
|
+
const ac = new AbortController();
|
|
987
|
+
const onOuterAbort = () => ac.abort(opts.signal?.reason ?? new Error("Indexing cancelled"));
|
|
988
|
+
if (opts.signal?.aborted) onOuterAbort();
|
|
989
|
+
else opts.signal?.addEventListener("abort", onOuterAbort, { once: true });
|
|
990
|
+
let timer;
|
|
991
|
+
const watchdog = new Promise((_, reject) => {
|
|
992
|
+
timer = setTimeout(() => {
|
|
993
|
+
const err = new IndexTimeoutError(
|
|
994
|
+
`Index ${op} exceeded its ${opts.timeoutMs}ms watchdog timeout`
|
|
995
|
+
);
|
|
996
|
+
ac.abort(err);
|
|
997
|
+
reject(err);
|
|
998
|
+
}, opts.timeoutMs);
|
|
999
|
+
timer.unref?.();
|
|
1000
|
+
});
|
|
1001
|
+
const job = async () => {
|
|
1002
|
+
switch (op) {
|
|
1003
|
+
case "index":
|
|
1004
|
+
return await indexService(args, {
|
|
1005
|
+
signal: ac.signal,
|
|
1006
|
+
onProgress: opts.onProgress
|
|
1007
|
+
});
|
|
1008
|
+
case "search":
|
|
1009
|
+
return searchService(args);
|
|
1010
|
+
case "stats":
|
|
1011
|
+
return statsService(args);
|
|
1012
|
+
case "packageGraph":
|
|
1013
|
+
return packageGraphService(args);
|
|
1014
|
+
case "fileGraph":
|
|
1015
|
+
return fileGraphService(args);
|
|
1016
|
+
case "symbolGraph":
|
|
1017
|
+
return symbolGraphService(args);
|
|
1018
|
+
case "incomingCalls":
|
|
1019
|
+
return incomingCallsService(args);
|
|
1020
|
+
case "outgoingCalls":
|
|
1021
|
+
return outgoingCallsService(args);
|
|
1022
|
+
default:
|
|
1023
|
+
throw new Error(`unknown index op: ${String(op)}`);
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
try {
|
|
1027
|
+
return await Promise.race([job(), watchdog]);
|
|
1028
|
+
} finally {
|
|
1029
|
+
if (timer) clearTimeout(timer);
|
|
1030
|
+
opts.signal?.removeEventListener("abort", onOuterAbort);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
var chain = Promise.resolve();
|
|
1034
|
+
function withMutex(job) {
|
|
1035
|
+
const run = chain.then(job, job);
|
|
1036
|
+
chain = run.then(
|
|
1037
|
+
() => void 0,
|
|
1038
|
+
() => void 0
|
|
1039
|
+
);
|
|
1040
|
+
return run;
|
|
1041
|
+
}
|
|
1042
|
+
function circuitOpenError() {
|
|
1043
|
+
const c = indexCircuitBreaker.snapshot();
|
|
1044
|
+
return new CircuitOpenError(
|
|
1045
|
+
"Codebase indexing is temporarily paused after repeated failures" + (c.lastFailure ? ` (last: ${c.lastFailure})` : "") + (c.cooldownRemainingMs > 0 ? `; auto-retry in ${Math.ceil(c.cooldownRemainingMs / 1e3)}s` : "") + ". Use /codebase-reindex to retry now."
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
var DEFAULT_DEBOUNCE_MS = 400;
|
|
1049
|
+
var DEFAULT_COALESCE_WINDOW_MS = 50;
|
|
1050
|
+
var debounceTimers = /* @__PURE__ */ new Map();
|
|
1051
|
+
var readyReindexBatches = /* @__PURE__ */ new Map();
|
|
1052
|
+
function debounceKey(projectRoot, indexDir, file) {
|
|
1053
|
+
return JSON.stringify([projectRoot, indexDir ?? "", file]);
|
|
1054
|
+
}
|
|
1055
|
+
function reindexBatchKey(projectRoot, indexDir) {
|
|
1056
|
+
return JSON.stringify([projectRoot, indexDir ?? ""]);
|
|
1057
|
+
}
|
|
1058
|
+
function flushReadyReindexBatch(key) {
|
|
1059
|
+
const batch = readyReindexBatches.get(key);
|
|
1060
|
+
if (!batch) return;
|
|
1061
|
+
readyReindexBatches.delete(key);
|
|
1062
|
+
if (!indexCircuitBreaker.allowRequest()) {
|
|
1063
|
+
const error = circuitOpenError();
|
|
1064
|
+
for (const onError of batch.onErrors) onError(error);
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
void withMutex(
|
|
1068
|
+
() => callIndexOp(
|
|
1069
|
+
"index",
|
|
1070
|
+
{
|
|
1071
|
+
projectRoot: batch.projectRoot,
|
|
1072
|
+
files: [...batch.files].sort(),
|
|
1073
|
+
indexDir: batch.indexDir
|
|
1074
|
+
},
|
|
1075
|
+
{ timeoutMs: batch.timeoutMs }
|
|
1076
|
+
)
|
|
1077
|
+
).then(
|
|
1078
|
+
() => indexCircuitBreaker.recordSuccess(),
|
|
1079
|
+
(err) => {
|
|
1080
|
+
indexCircuitBreaker.recordFailure(err);
|
|
1081
|
+
for (const onError of batch.onErrors) onError(err);
|
|
1082
|
+
}
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
function addReadyReindex(opts) {
|
|
1086
|
+
const key = reindexBatchKey(opts.projectRoot, opts.indexDir);
|
|
1087
|
+
const windowMs = opts.coalesceWindowMs ?? DEFAULT_COALESCE_WINDOW_MS;
|
|
1088
|
+
const existing = readyReindexBatches.get(key);
|
|
1089
|
+
if (existing) {
|
|
1090
|
+
existing.files.add(opts.file);
|
|
1091
|
+
existing.timeoutMs = Math.max(existing.timeoutMs, opts.timeoutMs);
|
|
1092
|
+
if (opts.onError) existing.onErrors.add(opts.onError);
|
|
1093
|
+
if (windowMs > 0 && existing.flush) {
|
|
1094
|
+
clearTimeout(existing.flush);
|
|
1095
|
+
existing.flush = setTimeout(() => flushReadyReindexBatch(key), windowMs);
|
|
1096
|
+
existing.flush.unref?.();
|
|
1097
|
+
}
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
const batch = {
|
|
1101
|
+
projectRoot: opts.projectRoot,
|
|
1102
|
+
indexDir: opts.indexDir,
|
|
1103
|
+
files: /* @__PURE__ */ new Set([opts.file]),
|
|
1104
|
+
timeoutMs: opts.timeoutMs,
|
|
1105
|
+
onErrors: new Set(opts.onError ? [opts.onError] : [])
|
|
1106
|
+
};
|
|
1107
|
+
batch.flush = setTimeout(() => flushReadyReindexBatch(key), Math.max(0, windowMs));
|
|
1108
|
+
batch.flush.unref?.();
|
|
1109
|
+
readyReindexBatches.set(key, batch);
|
|
1110
|
+
}
|
|
1111
|
+
function isIndexableFile(filePath) {
|
|
1112
|
+
return isIndexablePath(filePath);
|
|
1113
|
+
}
|
|
1114
|
+
function isRecoverableConstraintError(err) {
|
|
1115
|
+
if (err instanceof Error) {
|
|
1116
|
+
const msg = err.message.toLowerCase();
|
|
1117
|
+
return msg.includes("unique constraint") || msg.includes("constraint failed");
|
|
1118
|
+
}
|
|
1119
|
+
return false;
|
|
1120
|
+
}
|
|
1121
|
+
async function runStartupIndex(opts) {
|
|
1122
|
+
if (!indexCircuitBreaker.allowRequest()) throw circuitOpenError();
|
|
1123
|
+
_indexing = true;
|
|
1124
|
+
emitState();
|
|
1125
|
+
try {
|
|
1126
|
+
const result = await withMutex(() => {
|
|
1127
|
+
_currentFile = 0;
|
|
1128
|
+
_totalFiles = 0;
|
|
1129
|
+
_lastError = null;
|
|
1130
|
+
return callIndexOp(
|
|
1131
|
+
"index",
|
|
1132
|
+
{
|
|
1133
|
+
projectRoot: opts.projectRoot,
|
|
1134
|
+
indexDir: opts.indexDir,
|
|
1135
|
+
force: opts.force,
|
|
1136
|
+
langs: opts.langs
|
|
1137
|
+
},
|
|
1138
|
+
{
|
|
1139
|
+
timeoutMs: opts.timeoutMs ?? DEFAULT_FULL_INDEX_TIMEOUT_MS,
|
|
1140
|
+
signal: opts.signal,
|
|
1141
|
+
onProgress: setIndexProgress
|
|
1142
|
+
}
|
|
1143
|
+
);
|
|
1144
|
+
});
|
|
1145
|
+
_ready = true;
|
|
1146
|
+
indexCircuitBreaker.recordSuccess();
|
|
1147
|
+
return result;
|
|
1148
|
+
} catch (err) {
|
|
1149
|
+
_lastError = err instanceof Error ? err.message : String(err);
|
|
1150
|
+
const originalError = _lastError;
|
|
1151
|
+
if (isRecoverableConstraintError(err) && !opts.force) {
|
|
1152
|
+
_lastError = null;
|
|
1153
|
+
const rebuildResult = await runStartupIndex({
|
|
1154
|
+
...opts,
|
|
1155
|
+
force: true
|
|
1156
|
+
});
|
|
1157
|
+
_ready = true;
|
|
1158
|
+
return {
|
|
1159
|
+
...rebuildResult,
|
|
1160
|
+
autoRecovered: { failure: originalError, rebuiltWithForce: true }
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
_ready = true;
|
|
1164
|
+
if (!opts.signal?.aborted) indexCircuitBreaker.recordFailure(err);
|
|
1165
|
+
throw err;
|
|
1166
|
+
} finally {
|
|
1167
|
+
_indexing = false;
|
|
1168
|
+
emitState();
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
function enqueueReindex(opts) {
|
|
1172
|
+
const files = opts.files.filter(isIndexableFile);
|
|
1173
|
+
if (files.length === 0) return;
|
|
1174
|
+
const ms = opts.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
1175
|
+
for (const file of files) {
|
|
1176
|
+
const key = debounceKey(opts.projectRoot, opts.indexDir, file);
|
|
1177
|
+
const existing = debounceTimers.get(key);
|
|
1178
|
+
if (existing) clearTimeout(existing);
|
|
1179
|
+
const timer = setTimeout(() => {
|
|
1180
|
+
debounceTimers.delete(key);
|
|
1181
|
+
addReadyReindex({
|
|
1182
|
+
projectRoot: opts.projectRoot,
|
|
1183
|
+
file,
|
|
1184
|
+
indexDir: opts.indexDir,
|
|
1185
|
+
timeoutMs: opts.timeoutMs ?? DEFAULT_INCREMENTAL_TIMEOUT_MS,
|
|
1186
|
+
onError: opts.onError,
|
|
1187
|
+
coalesceWindowMs: opts.coalesceWindowMs
|
|
1188
|
+
});
|
|
1189
|
+
}, ms);
|
|
1190
|
+
timer.unref?.();
|
|
1191
|
+
debounceTimers.set(key, timer);
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
function cancelPendingReindexes() {
|
|
1195
|
+
for (const t of debounceTimers.values()) clearTimeout(t);
|
|
1196
|
+
debounceTimers.clear();
|
|
1197
|
+
for (const batch of readyReindexBatches.values()) {
|
|
1198
|
+
if (batch.flush) clearTimeout(batch.flush);
|
|
1199
|
+
}
|
|
1200
|
+
readyReindexBatches.clear();
|
|
1201
|
+
}
|
|
1202
|
+
async function searchCodebaseIndex(args, opts = {}) {
|
|
1203
|
+
return callIndexOp("search", args, {
|
|
1204
|
+
timeoutMs: opts.timeoutMs ?? DEFAULT_QUERY_TIMEOUT_MS,
|
|
1205
|
+
signal: opts.signal
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
async function codebaseIndexStats(args, opts = {}) {
|
|
1209
|
+
return callIndexOp("stats", args, {
|
|
1210
|
+
timeoutMs: opts.timeoutMs ?? DEFAULT_QUERY_TIMEOUT_MS,
|
|
1211
|
+
signal: opts.signal
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
async function packageGraphService2(args) {
|
|
1215
|
+
return callIndexOp("packageGraph", args, { timeoutMs: DEFAULT_QUERY_TIMEOUT_MS });
|
|
1216
|
+
}
|
|
1217
|
+
async function fileGraphService2(args) {
|
|
1218
|
+
return callIndexOp("fileGraph", args, { timeoutMs: DEFAULT_QUERY_TIMEOUT_MS });
|
|
1219
|
+
}
|
|
1220
|
+
async function symbolGraphService2(args) {
|
|
1221
|
+
return callIndexOp("symbolGraph", args, { timeoutMs: DEFAULT_QUERY_TIMEOUT_MS });
|
|
1222
|
+
}
|
|
1223
|
+
async function incomingCallsService2(args) {
|
|
1224
|
+
return callIndexOp("incomingCalls", args, { timeoutMs: DEFAULT_QUERY_TIMEOUT_MS });
|
|
1225
|
+
}
|
|
1226
|
+
async function outgoingCallsService2(args) {
|
|
1227
|
+
return callIndexOp("outgoingCalls", args, { timeoutMs: DEFAULT_QUERY_TIMEOUT_MS });
|
|
1228
|
+
}
|
|
1229
|
+
function shutdownCodebaseIndexServer(projectRoot, indexDir, reason) {
|
|
1230
|
+
return shutdownProjectIndexServer(projectRoot, indexDir, reason);
|
|
1231
|
+
}
|
|
1232
|
+
function checkCodebaseIndexServerHealth(projectRoot, indexDir, options = {}) {
|
|
1233
|
+
return checkProjectIndexServerHealth(projectRoot, indexDir, options);
|
|
1234
|
+
}
|
|
1235
|
+
function ensureCodebaseIndexServer(options) {
|
|
1236
|
+
const availability = resolveProjectIndexDaemonAvailability(options.projectRoot, options.indexDir);
|
|
1237
|
+
if (availability.kind !== "available") {
|
|
1238
|
+
if (availability.kind === "endpoint-invalid") warnEndpointInvalidOnce(availability);
|
|
1239
|
+
return Promise.resolve();
|
|
1240
|
+
}
|
|
1241
|
+
return ensureProjectIndexServer({
|
|
1242
|
+
projectRoot: options.projectRoot,
|
|
1243
|
+
indexDir: options.indexDir,
|
|
1244
|
+
watchExternal: options.watchExternal ?? false,
|
|
1245
|
+
debounceMs: options.debounceMs ?? DEFAULT_DEBOUNCE_MS,
|
|
1246
|
+
coalesceWindowMs: options.coalesceWindowMs ?? DEFAULT_COALESCE_WINDOW_MS
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
export {
|
|
1251
|
+
resolveProjectIndexDaemonAvailability,
|
|
1252
|
+
isIndexReady,
|
|
1253
|
+
isIndexing,
|
|
1254
|
+
getIndexState,
|
|
1255
|
+
onIndexStateChange,
|
|
1256
|
+
shutdownCodebaseIndexHost,
|
|
1257
|
+
isIndexableFile,
|
|
1258
|
+
runStartupIndex,
|
|
1259
|
+
enqueueReindex,
|
|
1260
|
+
cancelPendingReindexes,
|
|
1261
|
+
searchCodebaseIndex,
|
|
1262
|
+
codebaseIndexStats,
|
|
1263
|
+
packageGraphService2 as packageGraphService,
|
|
1264
|
+
fileGraphService2 as fileGraphService,
|
|
1265
|
+
symbolGraphService2 as symbolGraphService,
|
|
1266
|
+
incomingCallsService2 as incomingCallsService,
|
|
1267
|
+
outgoingCallsService2 as outgoingCallsService,
|
|
1268
|
+
shutdownCodebaseIndexServer,
|
|
1269
|
+
checkCodebaseIndexServerHealth,
|
|
1270
|
+
ensureCodebaseIndexServer
|
|
1271
|
+
};
|
|
1272
|
+
//# sourceMappingURL=chunk-HYX2TZ7O.js.map
|