@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.
Files changed (182) hide show
  1. package/dist/_edit-match.d.ts +3 -2
  2. package/dist/_output-spool.d.ts +4 -3
  3. package/dist/_shell-pick.d.ts +2 -1
  4. package/dist/_spawn-stream.d.ts +2 -1
  5. package/dist/_syntax-check.d.ts +2 -1
  6. package/dist/audit.js +10 -4055
  7. package/dist/bash-kill-guard.d.ts +3 -6
  8. package/dist/bash.js +9 -2308
  9. package/dist/batch-tool-use.js +2 -187
  10. package/dist/browser/index.js +25 -1339
  11. package/dist/browser/network-guard-proxy.d.ts +2 -1
  12. package/dist/builtin.js +69 -33949
  13. package/dist/chunk-22B7GE5T.js +260 -0
  14. package/dist/chunk-2VSZC6QM.js +113 -0
  15. package/dist/chunk-3PVYQFRQ.js +472 -0
  16. package/dist/chunk-42BHI7TN.js +337 -0
  17. package/dist/chunk-4BHUSMNL.js +104 -0
  18. package/dist/chunk-4I4LWFOO.js +431 -0
  19. package/dist/chunk-4UESMQDP.js +122 -0
  20. package/dist/chunk-553IQUMO.js +478 -0
  21. package/dist/chunk-5H4GSJAI.js +223 -0
  22. package/dist/chunk-6RNIFLG4.js +305 -0
  23. package/dist/chunk-7EY5DCRO.js +434 -0
  24. package/dist/chunk-A6BRWIXN.js +317 -0
  25. package/dist/chunk-AHHDWNXC.js +200 -0
  26. package/dist/chunk-BSLVR56S.js +199 -0
  27. package/dist/chunk-BTSFTGIL.js +7 -0
  28. package/dist/chunk-D33S4RQG.js +222 -0
  29. package/dist/chunk-DA4KNE2H.js +4412 -0
  30. package/dist/chunk-DR7JOQTC.js +520 -0
  31. package/dist/chunk-EEOFQDT6.js +898 -0
  32. package/dist/chunk-EJWSCQ6D.js +137 -0
  33. package/dist/chunk-EMRGN4KM.js +170 -0
  34. package/dist/chunk-FQZOHLG3.js +89 -0
  35. package/dist/chunk-GOIZNNOA.js +221 -0
  36. package/dist/chunk-HFXSNKCV.js +196 -0
  37. package/dist/chunk-HYX2TZ7O.js +1272 -0
  38. package/dist/chunk-I64BS3VC.js +15 -0
  39. package/dist/chunk-IVWV2UM6.js +955 -0
  40. package/dist/chunk-IXMNTNW5.js +238 -0
  41. package/dist/chunk-IYK7XGDZ.js +892 -0
  42. package/dist/chunk-J5SKD6LN.js +235 -0
  43. package/dist/chunk-JNNVRHMV.js +2111 -0
  44. package/dist/chunk-JOHXDF3D.js +295 -0
  45. package/dist/chunk-JRJJX3L5.js +424 -0
  46. package/dist/chunk-K3WNSHUF.js +1371 -0
  47. package/dist/chunk-L25CVUVR.js +2830 -0
  48. package/dist/chunk-LIJQMYYX.js +59 -0
  49. package/dist/chunk-LWKUTMD5.js +197 -0
  50. package/dist/chunk-MQX2KXVW.js +54 -0
  51. package/dist/chunk-MZZN7VCD.js +359 -0
  52. package/dist/chunk-NBDZMMG2.js +169 -0
  53. package/dist/chunk-NKJW5GFO.js +366 -0
  54. package/dist/chunk-NKNOBUBU.js +522 -0
  55. package/dist/chunk-O4MBV4AX.js +134 -0
  56. package/dist/chunk-OUBDL3WH.js +3515 -0
  57. package/dist/chunk-PQKK5JH7.js +101 -0
  58. package/dist/chunk-PY2IOS7C.js +1000 -0
  59. package/dist/chunk-Q5JPPCZC.js +12 -0
  60. package/dist/chunk-QBV725YG.js +128 -0
  61. package/dist/chunk-QZQSPUBF.js +315 -0
  62. package/dist/chunk-RGISBZBD.js +231 -0
  63. package/dist/chunk-RJZQWEMS.js +84 -0
  64. package/dist/chunk-RXISBFR5.js +315 -0
  65. package/dist/chunk-S5CCLZL3.js +99 -0
  66. package/dist/chunk-SDLVH5RZ.js +178 -0
  67. package/dist/chunk-T2F4S7U4.js +1723 -0
  68. package/dist/chunk-TEBQ4VPZ.js +163 -0
  69. package/dist/chunk-U4D5S3VQ.js +21 -0
  70. package/dist/chunk-U4VAATKP.js +193 -0
  71. package/dist/chunk-UCYQTZ7F.js +113 -0
  72. package/dist/chunk-UKU2ZX4C.js +203 -0
  73. package/dist/chunk-UNSKG6DC.js +159 -0
  74. package/dist/chunk-V43EQ6VR.js +68 -0
  75. package/dist/chunk-VB3MJGCV.js +610 -0
  76. package/dist/chunk-VFKPWSOX.js +154 -0
  77. package/dist/chunk-VJNHFZ7J.js +190 -0
  78. package/dist/chunk-VXECAJJ7.js +1091 -0
  79. package/dist/chunk-WHIIIREL.js +223 -0
  80. package/dist/chunk-Z6DAKQ3U.js +1 -0
  81. package/dist/chunk-ZJQ4IGXK.js +474 -0
  82. package/dist/chunk-ZLTFLC25.js +385 -0
  83. package/dist/circuit-breaker.js +3 -191
  84. package/dist/codebase-index/ast-invariant-engine.d.ts +5 -4
  85. package/dist/codebase-index/binary-frame.d.ts +0 -2
  86. package/dist/codebase-index/bm25.d.ts +1 -1
  87. package/dist/codebase-index/circuit-breaker.d.ts +2 -1
  88. package/dist/codebase-index/codebase-ast-replace-tool.d.ts +3 -2
  89. package/dist/codebase-index/codebase-impact-analysis-tool.d.ts +4 -3
  90. package/dist/codebase-index/codebase-invariant-check-tool.d.ts +3 -2
  91. package/dist/codebase-index/codebase-repo-map-tool.d.ts +3 -2
  92. package/dist/codebase-index/codebase-skeleton-tool.d.ts +3 -2
  93. package/dist/codebase-index/codebase-targeted-test-tool.d.ts +3 -2
  94. package/dist/codebase-index/content-hash.d.ts +0 -2
  95. package/dist/codebase-index/import-extractor.d.ts +0 -13
  96. package/dist/codebase-index/index-service.d.ts +1 -1
  97. package/dist/codebase-index/index.js +84 -13688
  98. package/dist/codebase-index/languages.d.ts +2 -1
  99. package/dist/codebase-index/module-roots.d.ts +3 -2
  100. package/dist/codebase-index/parser-batch.d.ts +0 -5
  101. package/dist/codebase-index/parser-dispatch.d.ts +2 -1
  102. package/dist/codebase-index/parser-output.d.ts +4 -3
  103. package/dist/codebase-index/parser-worker-pool.d.ts +0 -2
  104. package/dist/codebase-index/parser-worker-script.d.ts +1 -16
  105. package/dist/codebase-index/parser-worker-script.js +8 -3995
  106. package/dist/codebase-index/project-server-client-state.d.ts +2 -2
  107. package/dist/codebase-index/project-server-endpoint.d.ts +0 -15
  108. package/dist/codebase-index/project-server.js +60 -8548
  109. package/dist/codebase-index/tree-sitter/visitor.d.ts +2 -1
  110. package/dist/codebase-index/tree-sitter-parser.d.ts +0 -2
  111. package/dist/codebase-index/ts-parser.d.ts +1 -1
  112. package/dist/codebase-index/vector-search.d.ts +2 -1
  113. package/dist/codebase-index/wal-maintenance.d.ts +3 -2
  114. package/dist/codebase-index/worker.js +20 -8416
  115. package/dist/codebase-index/writer-admin.d.ts +0 -1
  116. package/dist/codebase-index/writer-search.d.ts +0 -9
  117. package/dist/design.js +2 -299
  118. package/dist/diff.js +4 -256
  119. package/dist/document.js +4 -222
  120. package/dist/e2e.js +5 -476
  121. package/dist/edit.js +14 -10200
  122. package/dist/exec.js +16 -3131
  123. package/dist/fetch.js +8 -398
  124. package/dist/format.js +10 -3750
  125. package/dist/generic-parser-VUXDIAOZ.js +13 -0
  126. package/dist/git.js +4 -380
  127. package/dist/glob.js +6 -280
  128. package/dist/go-parser-JXYKJHRC.js +469 -0
  129. package/dist/grep.js +7 -604
  130. package/dist/index.js +318 -35387
  131. package/dist/install.js +10 -4091
  132. package/dist/json-parser-IBHZ4BZC.js +213 -0
  133. package/dist/json.js +4 -665
  134. package/dist/kanban-contract-actions.d.ts +1 -1
  135. package/dist/kanban-detail-actions.d.ts +2 -1
  136. package/dist/kanban-presence.d.ts +2 -1
  137. package/dist/kanban-serializer.d.ts +0 -8
  138. package/dist/kanban-split-task-handler.d.ts +2 -2
  139. package/dist/kanban-tool-types.d.ts +2 -1
  140. package/dist/kanban.js +3 -2096
  141. package/dist/languages/diagnostics.d.ts +2 -1
  142. package/dist/languages/index.js +23 -4664
  143. package/dist/languages/legacy-bridge.d.ts +3 -2
  144. package/dist/languages/types.d.ts +2 -1
  145. package/dist/legacy-bridge-7M6QLUQN.js +18 -0
  146. package/dist/lint.js +10 -3706
  147. package/dist/logs.js +4 -287
  148. package/dist/memory.js +6 -215
  149. package/dist/mode.js +3 -99
  150. package/dist/outdated.js +4 -5170
  151. package/dist/pack.js +65 -33897
  152. package/dist/parser-dispatch-QH4XPGG6.js +14 -0
  153. package/dist/patch.js +13 -9935
  154. package/dist/plan.js +5 -3286
  155. package/dist/process-registry.js +8 -651
  156. package/dist/ps-slash.js +12 -1439
  157. package/dist/pwsh.d.ts +0 -1
  158. package/dist/py-parser-IS4HKYMF.js +16 -0
  159. package/dist/read.js +13 -9816
  160. package/dist/replace.js +15 -10014
  161. package/dist/rs-parser-XHUUO2KZ.js +81 -0
  162. package/dist/scaffold.js +4 -253
  163. package/dist/search.js +5 -585
  164. package/dist/session-kanban-sync.d.ts +0 -6
  165. package/dist/session-kanban.js +30 -972
  166. package/dist/skill.js +2 -215
  167. package/dist/spawn-background.d.ts +1 -1
  168. package/dist/task.js +5 -3410
  169. package/dist/test.js +10 -3773
  170. package/dist/todo.js +4 -2891
  171. package/dist/tool-help.js +3 -123
  172. package/dist/tool-search.js +3 -79
  173. package/dist/tool-tier.js +68 -33984
  174. package/dist/tool-use.js +2 -93
  175. package/dist/tree-sitter-parser-AIERW3YI.js +15 -0
  176. package/dist/tree.js +4 -288
  177. package/dist/ts-parser-RCTYKSVI.js +232 -0
  178. package/dist/typecheck.js +10 -3753
  179. package/dist/win32.js +6 -60
  180. package/dist/write.js +14 -9882
  181. package/dist/yaml-parser-4SAXV3PL.js +157 -0
  182. 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