@wrongstack/tools 0.320.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/dist/_danger-detect.d.ts +8 -0
  2. package/dist/_syntax-check.d.ts +1 -2
  3. package/dist/audit.d.ts +4 -5
  4. package/dist/audit.js +6 -6
  5. package/dist/auto-proceed-loop-guard.js +9 -160
  6. package/dist/bash-kill-guard.d.ts +2 -3
  7. package/dist/bash.d.ts +2 -3
  8. package/dist/bash.js +4 -4
  9. package/dist/batch-tool-use.d.ts +2 -3
  10. package/dist/batch-tool-use.js +1 -1
  11. package/dist/browser/index.d.ts +1 -1
  12. package/dist/browser/index.js +1 -1
  13. package/dist/browser/tools.d.ts +56 -45
  14. package/dist/builtin.js +50 -50
  15. package/dist/{chunk-T77XOX7E.js → chunk-24T5XIJU.js} +1 -1
  16. package/dist/{chunk-DTQ75VBC.js → chunk-2CTZW6JQ.js} +2 -2
  17. package/dist/chunk-37QKUQ2Z.js +171 -0
  18. package/dist/{chunk-KWXIKGE3.js → chunk-3D5DJWOE.js} +3 -3
  19. package/dist/{chunk-XSSFBS5I.js → chunk-4ERMPDHV.js} +11 -6
  20. package/dist/{chunk-QSNVDHOZ.js → chunk-4WPBS45A.js} +4 -5
  21. package/dist/{chunk-4CQWNZ62.js → chunk-5ITQPCLC.js} +2 -2
  22. package/dist/{chunk-B4HTSV6Z.js → chunk-5KS24LTS.js} +11 -11
  23. package/dist/{chunk-QPNYDH5B.js → chunk-62OAECEQ.js} +29 -7
  24. package/dist/{chunk-DFQDJT22.js → chunk-6GOZTS6F.js} +3 -3
  25. package/dist/{chunk-34VLZ35Z.js → chunk-7HL23T3T.js} +63 -10
  26. package/dist/{chunk-HFXSNKCV.js → chunk-7JJFC6CT.js} +1 -1
  27. package/dist/{chunk-NDIT3SBZ.js → chunk-7N5U2RAU.js} +2 -2
  28. package/dist/{chunk-QBV725YG.js → chunk-7ODZTPCU.js} +2 -2
  29. package/dist/{chunk-4BABEJC2.js → chunk-AB2CAUOP.js} +64 -53
  30. package/dist/{chunk-6AAOT4PW.js → chunk-AQ5YBURX.js} +16 -4
  31. package/dist/{chunk-ZMW6WJFT.js → chunk-ASEXQRA7.js} +3 -3
  32. package/dist/{chunk-FRN7TIAA.js → chunk-B3MGJBFI.js} +1 -1
  33. package/dist/{chunk-JLYA4X7K.js → chunk-CGXTR34H.js} +21 -6
  34. package/dist/{chunk-IU4DV2PA.js → chunk-CHEK3ASV.js} +6 -3
  35. package/dist/{chunk-GOIZNNOA.js → chunk-DJYLI7EW.js} +1 -1
  36. package/dist/{chunk-Z6IHMJMC.js → chunk-DK4J2WMH.js} +11 -11
  37. package/dist/{chunk-OYP27E3I.js → chunk-DRRY6SCM.js} +47 -20
  38. package/dist/{chunk-BK732J7I.js → chunk-FBUP524S.js} +4 -4
  39. package/dist/{chunk-NYRLWDT5.js → chunk-FVM3O4JQ.js} +1 -1
  40. package/dist/chunk-FYDBUVQD.js +244 -0
  41. package/dist/{chunk-PV6HIZQD.js → chunk-GDDXX27P.js} +50 -50
  42. package/dist/{chunk-3GIKYHLA.js → chunk-GKSG5CPH.js} +6 -6
  43. package/dist/{chunk-ESLPBT5A.js → chunk-HDYUBRMF.js} +4 -3
  44. package/dist/{chunk-ELRRTWNP.js → chunk-IXIS7RSG.js} +2 -2
  45. package/dist/{chunk-HR67OWEP.js → chunk-IYH4OHBP.js} +5 -5
  46. package/dist/{chunk-W232EFEM.js → chunk-JDOO4P6Z.js} +7 -4
  47. package/dist/{chunk-OTPTQBYE.js → chunk-KDEXUQY7.js} +19 -8
  48. package/dist/{chunk-2OXHS3KZ.js → chunk-KLJZ4WV4.js} +4 -3
  49. package/dist/{chunk-3M7LLN7W.js → chunk-LQSOU63Z.js} +12 -11
  50. package/dist/{chunk-JVRLTEO7.js → chunk-M6UV67BH.js} +2 -2
  51. package/dist/{chunk-C2DQKVLO.js → chunk-MJSJWXBF.js} +1 -1
  52. package/dist/{chunk-NSPNFOJ7.js → chunk-MK2JK35J.js} +25 -10
  53. package/dist/{chunk-JSNSMG5R.js → chunk-MSOAX3GC.js} +1 -1
  54. package/dist/{chunk-J43O3BOX.js → chunk-N3K2SFEI.js} +532 -492
  55. package/dist/{chunk-J74TMK4M.js → chunk-NJX2RXKJ.js} +50 -7
  56. package/dist/{chunk-5UFVCIUB.js → chunk-NN2SNP2R.js} +4 -4
  57. package/dist/{chunk-WAQASTXB.js → chunk-O6BY43ZB.js} +29 -8
  58. package/dist/{chunk-QGF2C7TF.js → chunk-PVCB7RUZ.js} +2 -2
  59. package/dist/{chunk-YTKSNTT4.js → chunk-QA6IASYX.js} +8 -8
  60. package/dist/{chunk-VPALDFYM.js → chunk-RV7BVTDG.js} +1 -1
  61. package/dist/chunk-S57CDR2H.js +288 -0
  62. package/dist/{chunk-W2CECGTG.js → chunk-SMARDKAC.js} +1 -1
  63. package/dist/{chunk-QWKSKFKZ.js → chunk-SOR7OCYP.js} +1 -1
  64. package/dist/{chunk-I7FBZOEP.js → chunk-T3SMFF5Q.js} +11 -5
  65. package/dist/{chunk-4BHUSMNL.js → chunk-UI3ZJIG3.js} +1 -1
  66. package/dist/{chunk-WXJY3BKV.js → chunk-VBF2YXUW.js} +2 -2
  67. package/dist/{chunk-FQNXYA6W.js → chunk-VU3QBYL4.js} +36 -5
  68. package/dist/{chunk-VD2LV5WO.js → chunk-VZGPASGD.js} +37 -12
  69. package/dist/{chunk-UZFY3CKD.js → chunk-WEEVSWQB.js} +17 -6
  70. package/dist/chunk-XXRMF2BC.js +186 -0
  71. package/dist/{chunk-LRDIRJJL.js → chunk-YEISRT4S.js} +5 -11
  72. package/dist/{chunk-Q5JPPCZC.js → chunk-YJTXRH5Y.js} +3 -2
  73. package/dist/{chunk-23EX5WDO.js → chunk-YULVUTK5.js} +3 -3
  74. package/dist/{chunk-Z5Q3TXRY.js → chunk-ZTCJCYLN.js} +3 -3
  75. package/dist/circuit-breaker.d.ts +1 -0
  76. package/dist/circuit-breaker.js +1 -1
  77. package/dist/clarify.d.ts +1 -0
  78. package/dist/codebase-index/codebase-ast-replace-tool.d.ts +2 -3
  79. package/dist/codebase-index/codebase-impact-analysis-tool.d.ts +5 -4
  80. package/dist/codebase-index/codebase-incoming-calls-tool.d.ts +4 -3
  81. package/dist/codebase-index/codebase-index-tool.d.ts +2 -3
  82. package/dist/codebase-index/codebase-invariant-check-tool.d.ts +2 -3
  83. package/dist/codebase-index/codebase-outgoing-calls-tool.d.ts +4 -3
  84. package/dist/codebase-index/codebase-repo-map-tool.d.ts +2 -3
  85. package/dist/codebase-index/codebase-search-tool.d.ts +2 -3
  86. package/dist/codebase-index/codebase-skeleton-tool.d.ts +2 -3
  87. package/dist/codebase-index/codebase-stats-tool.d.ts +9 -3
  88. package/dist/codebase-index/codebase-targeted-test-tool.d.ts +4 -3
  89. package/dist/codebase-index/index.d.ts +11 -11
  90. package/dist/codebase-index/index.js +8 -8
  91. package/dist/codebase-index/project-server-framing.d.ts +29 -0
  92. package/dist/codebase-index/project-server-lifecycle.d.ts +20 -0
  93. package/dist/codebase-index/project-server-operations.d.ts +37 -0
  94. package/dist/codebase-index/project-server-types.d.ts +30 -0
  95. package/dist/codebase-index/project-server-watcher.d.ts +25 -0
  96. package/dist/codebase-index/project-server.js +448 -391
  97. package/dist/codebase-index/worker.js +2 -2
  98. package/dist/codebase-index/writer-init.d.ts +13 -0
  99. package/dist/codebase-index/writer-maintenance.d.ts +14 -0
  100. package/dist/codebase-index/writer.d.ts +0 -15
  101. package/dist/design.d.ts +2 -2
  102. package/dist/design.js +1 -1
  103. package/dist/diff.d.ts +4 -4
  104. package/dist/diff.js +1 -1
  105. package/dist/document.d.ts +3 -4
  106. package/dist/document.js +1 -1
  107. package/dist/e2e.d.ts +1 -1
  108. package/dist/e2e.js +1 -1
  109. package/dist/edit.d.ts +3 -3
  110. package/dist/edit.js +6 -6
  111. package/dist/exec.d.ts +2 -3
  112. package/dist/exec.js +4 -4
  113. package/dist/fetch.d.ts +4 -3
  114. package/dist/fetch.js +1 -1
  115. package/dist/format.d.ts +17 -45
  116. package/dist/format.js +12 -8
  117. package/dist/git.d.ts +3 -3
  118. package/dist/git.js +1 -1
  119. package/dist/glob.d.ts +2 -3
  120. package/dist/glob.js +2 -2
  121. package/dist/grep.d.ts +7 -5
  122. package/dist/grep.js +3 -3
  123. package/dist/index.d.ts +54 -45
  124. package/dist/index.js +118 -59
  125. package/dist/install.d.ts +7 -55
  126. package/dist/install.js +6 -6
  127. package/dist/json.d.ts +3 -4
  128. package/dist/json.js +2 -2
  129. package/dist/kanban-tool-types.d.ts +1 -2
  130. package/dist/kanban.d.ts +3 -19
  131. package/dist/kanban.js +1 -1
  132. package/dist/languages/execute-tool.d.ts +2 -3
  133. package/dist/languages/index.d.ts +2 -2
  134. package/dist/languages/index.js +4 -4
  135. package/dist/languages/tool.d.ts +3 -4
  136. package/dist/{legacy-bridge-FOV5ODDI.js → legacy-bridge-YXANP5QB.js} +6 -6
  137. package/dist/lint.d.ts +5 -4
  138. package/dist/lint.js +6 -6
  139. package/dist/logs.d.ts +3 -4
  140. package/dist/logs.js +2 -2
  141. package/dist/memory.d.ts +7 -8
  142. package/dist/memory.js +1 -1
  143. package/dist/mode.d.ts +3 -4
  144. package/dist/mode.js +1 -1
  145. package/dist/next-steps-tool.d.ts +2 -3
  146. package/dist/next-steps.d.ts +4 -0
  147. package/dist/next-steps.js +9 -175
  148. package/dist/outdated.d.ts +5 -29
  149. package/dist/outdated.js +3 -3
  150. package/dist/pack.js +51 -51
  151. package/dist/patch.d.ts +2 -3
  152. package/dist/patch.js +5 -5
  153. package/dist/plan.d.ts +4 -4
  154. package/dist/plan.js +3 -3
  155. package/dist/process-registry.js +2 -2
  156. package/dist/ps-slash.js +4 -4
  157. package/dist/pwsh.d.ts +1 -0
  158. package/dist/read.d.ts +5 -5
  159. package/dist/read.js +5 -5
  160. package/dist/replace.d.ts +2 -3
  161. package/dist/replace.js +6 -6
  162. package/dist/scaffold.d.ts +7 -3
  163. package/dist/scaffold.js +3 -1
  164. package/dist/search.d.ts +17 -3
  165. package/dist/search.js +5 -1
  166. package/dist/set-working-dir.d.ts +2 -3
  167. package/dist/skill.d.ts +4 -5
  168. package/dist/skill.js +1 -1
  169. package/dist/task.d.ts +6 -6
  170. package/dist/task.js +3 -3
  171. package/dist/test.d.ts +5 -4
  172. package/dist/test.js +6 -6
  173. package/dist/todo.d.ts +8 -8
  174. package/dist/todo.js +2 -2
  175. package/dist/tool-diff.js +11 -230
  176. package/dist/tool-help.d.ts +2 -3
  177. package/dist/tool-help.js +1 -1
  178. package/dist/tool-search.d.ts +2 -3
  179. package/dist/tool-search.js +1 -1
  180. package/dist/tool-summary.js +5 -281
  181. package/dist/tool-tier.js +52 -52
  182. package/dist/tool-use.d.ts +2 -3
  183. package/dist/tool-use.js +1 -1
  184. package/dist/tree.d.ts +5 -3
  185. package/dist/tree.js +5 -1
  186. package/dist/typecheck.d.ts +3 -3
  187. package/dist/typecheck.js +6 -6
  188. package/dist/write.d.ts +2 -3
  189. package/dist/write.js +6 -6
  190. package/package.json +5 -5
  191. package/dist/spawn-background.d.ts +0 -80
@@ -10,7 +10,7 @@ import {
10
10
  projectIndexServerBuildId,
11
11
  projectIndexServerEndpoint,
12
12
  projectIndexServerMetadataPath
13
- } from "../chunk-PV6HIZQD.js";
13
+ } from "../chunk-GDDXX27P.js";
14
14
  import {
15
15
  fileGraphService,
16
16
  incomingCallsService,
@@ -22,7 +22,7 @@ import {
22
22
  searchService,
23
23
  statsService,
24
24
  symbolGraphService
25
- } from "../chunk-J43O3BOX.js";
25
+ } from "../chunk-N3K2SFEI.js";
26
26
  import "../chunk-NU2IZ72J.js";
27
27
  import "../chunk-VVZPQPTY.js";
28
28
  import "../chunk-6IEBD27U.js";
@@ -30,22 +30,147 @@ import "../chunk-UCYQTZ7F.js";
30
30
  import {
31
31
  isIndexablePath
32
32
  } from "../chunk-YOHO4DVT.js";
33
- import "../chunk-DTQ75VBC.js";
33
+ import "../chunk-2CTZW6JQ.js";
34
34
  import "../chunk-KE7A76VC.js";
35
35
 
36
36
  // src/codebase-index/project-server.ts
37
37
  import { randomBytes } from "node:crypto";
38
- import * as fs from "node:fs";
38
+ import * as fs2 from "node:fs";
39
39
  import * as net from "node:net";
40
+ import * as path3 from "node:path";
41
+ import { startSharedHeapWatchdog, useDaemonPerfDefaults } from "@wrongstack/core/utils";
42
+ import { bindProjectEndpoint } from "@wrongstack/persistence";
43
+
44
+ // src/codebase-index/project-server-framing.ts
45
+ var MAX_CLIENT_WRITE_BUFFER_BYTES = 8 * 1024 * 1024;
46
+ function sendServerMessage(state, message) {
47
+ if (state.socket.destroyed) return;
48
+ if (state.socket.writableLength > MAX_CLIENT_WRITE_BUFFER_BYTES) {
49
+ state.socket.destroy(new Error("Index client fell too far behind on reads"));
50
+ return;
51
+ }
52
+ state.socket.write(
53
+ state.binary ? encodeBinaryFrame(message) : encodeProjectServerMessage(message)
54
+ );
55
+ }
56
+ function consumeClientChunk(state, chunk, onMessage) {
57
+ const buffer = state.buffer.length === 0 ? chunk : Buffer.concat([state.buffer, chunk]);
58
+ let scan = 0;
59
+ while (true) {
60
+ if (buffer.length - scan === 0) break;
61
+ if (isBinaryFrame(buffer[scan])) {
62
+ if (buffer.length - scan < 5) break;
63
+ const frameLen = buffer.readUInt32BE(scan + 1);
64
+ if (frameLen > MAX_INBOUND_BINARY_FRAME_BYTES) {
65
+ state.buffer = Buffer.alloc(0);
66
+ state.socket.destroy(new Error("codebase-index client binary frame exceeds the IPC limit"));
67
+ return;
68
+ }
69
+ if (buffer.length - scan < 5 + frameLen) break;
70
+ const payload = buffer.subarray(scan + 5, scan + 5 + frameLen);
71
+ let message2;
72
+ try {
73
+ message2 = decodeBinaryFrame(payload);
74
+ } catch {
75
+ state.buffer = Buffer.alloc(0);
76
+ state.socket.destroy(new Error("invalid codebase-index client binary frame"));
77
+ return;
78
+ }
79
+ scan += 5 + frameLen;
80
+ state.binary = true;
81
+ state.lastSeenAt = Date.now();
82
+ onMessage(state, message2);
83
+ continue;
84
+ }
85
+ const newline = buffer.indexOf(10, scan);
86
+ if (newline < 0) {
87
+ if (buffer.length - scan > PROJECT_INDEX_SERVER_MAX_FRAME_CHARS) {
88
+ state.buffer = Buffer.alloc(0);
89
+ state.socket.destroy(new Error("codebase-index client message exceeds the IPC limit"));
90
+ return;
91
+ }
92
+ break;
93
+ }
94
+ if (newline - scan > PROJECT_INDEX_SERVER_MAX_FRAME_CHARS) {
95
+ state.buffer = Buffer.alloc(0);
96
+ state.socket.destroy(new Error("codebase-index client message exceeds the IPC limit"));
97
+ return;
98
+ }
99
+ const line = buffer.subarray(scan, newline).toString("utf8");
100
+ scan = newline + 1;
101
+ if (!line) continue;
102
+ let message;
103
+ try {
104
+ message = JSON.parse(line);
105
+ } catch {
106
+ state.buffer = Buffer.alloc(0);
107
+ state.socket.destroy(new Error("invalid codebase-index client message"));
108
+ return;
109
+ }
110
+ state.lastSeenAt = Date.now();
111
+ onMessage(state, message);
112
+ }
113
+ state.buffer = scan === 0 ? buffer : Buffer.from(buffer.subarray(scan));
114
+ }
115
+
116
+ // src/codebase-index/project-server-lifecycle.ts
117
+ import * as fs from "node:fs";
40
118
  import * as path from "node:path";
119
+ import process2 from "node:process";
41
120
  import { restrictFilePermissions } from "@wrongstack/core/security";
42
- import {
43
- DEFAULT_WALK_IGNORE_SET,
44
- startSharedHeapWatchdog,
45
- useDaemonPerfDefaults,
46
- watchProjectTree
47
- } from "@wrongstack/core/utils";
48
- import { atomicWrite, bindProjectEndpoint } from "@wrongstack/persistence";
121
+ import { atomicWrite } from "@wrongstack/persistence";
122
+ function removeMetadataIfOwned(metadataPath2, pid) {
123
+ try {
124
+ const current = JSON.parse(fs.readFileSync(metadataPath2, "utf8"));
125
+ if (current.pid === pid) fs.rmSync(metadataPath2, { force: true });
126
+ } catch {
127
+ }
128
+ }
129
+ async function writeProjectServerMetadata(metadataPath2, metadata) {
130
+ fs.mkdirSync(path.dirname(metadataPath2), { recursive: true });
131
+ await atomicWrite(metadataPath2, `${JSON.stringify(metadata, null, 2)}
132
+ `, { mode: 384 });
133
+ await restrictFilePermissions(metadataPath2, {
134
+ label: "codebase-index-metadata",
135
+ warn: (message) => process2.stderr.write(`${message}
136
+ `)
137
+ });
138
+ }
139
+ var SIGNAL_GUARD = /* @__PURE__ */ Symbol.for(
140
+ "wrongstack.codebase-index.project-server.signalGuard"
141
+ );
142
+ function armCodebaseIndexSignalGuard(stop2) {
143
+ const signalGuardStore = globalThis;
144
+ let guard = signalGuardStore[SIGNAL_GUARD];
145
+ if (!guard) {
146
+ let current = async () => void 0;
147
+ let armed = false;
148
+ const handlers = /* @__PURE__ */ new Map();
149
+ const disarm = () => {
150
+ if (!armed) return;
151
+ armed = false;
152
+ for (const [signal, handler] of handlers) process2.removeListener(signal, handler);
153
+ handlers.clear();
154
+ };
155
+ guard = {
156
+ arm(next) {
157
+ current = next;
158
+ if (armed) return;
159
+ armed = true;
160
+ for (const signal of ["SIGINT", "SIGTERM"]) {
161
+ const handler = () => {
162
+ disarm();
163
+ void current(signal);
164
+ };
165
+ handlers.set(signal, handler);
166
+ process2.on(signal, handler);
167
+ }
168
+ }
169
+ };
170
+ signalGuardStore[SIGNAL_GUARD] = guard;
171
+ }
172
+ guard.arm(stop2);
173
+ }
49
174
 
50
175
  // src/codebase-index/project-server-cache.ts
51
176
  var GenerationLruCache = class {
@@ -155,6 +280,262 @@ var ServerQueryCaches = class {
155
280
  }
156
281
  };
157
282
 
283
+ // src/codebase-index/project-server-operations.ts
284
+ function preservesQueryCaches(args) {
285
+ return !args.force && args.files !== void 0 && args.files.length > 0;
286
+ }
287
+ function isShareableFullIndex(args) {
288
+ return !args.force && (!args.files || args.files.length === 0) && (!args.langs || args.langs.length === 0) && (!args.ignore || args.ignore.length === 0);
289
+ }
290
+ function fixedArgs(ctx, args) {
291
+ return { ...args, projectRoot: ctx.projectRoot, indexDir: ctx.indexDir };
292
+ }
293
+ async function runFullIndex(ctx, state, id, args) {
294
+ const subscriber = { state, id };
295
+ let active = ctx.getActiveFullIndex();
296
+ if (!active) {
297
+ const controller = new AbortController();
298
+ const subscribers = /* @__PURE__ */ new Set([subscriber]);
299
+ const promise = ctx.withIndexWrite(
300
+ (reportProgress) => indexService(fixedArgs(ctx, args), {
301
+ signal: controller.signal,
302
+ onProgress: (current, total) => {
303
+ reportProgress(current, total);
304
+ for (const item of subscribers) {
305
+ if (!item.state.cancelled.has(item.id)) {
306
+ ctx.send(item.state, { type: "progress", id: item.id, current, total });
307
+ }
308
+ }
309
+ }
310
+ }),
311
+ // Shareable full index: whole-index scope, caches drop on completion.
312
+ { preserveCaches: false }
313
+ );
314
+ active = { promise, controller, subscribers };
315
+ ctx.setActiveFullIndex(active);
316
+ void promise.finally(() => {
317
+ if (ctx.getActiveFullIndex() === active) ctx.setActiveFullIndex(null);
318
+ }).catch(() => {
319
+ });
320
+ } else {
321
+ active.subscribers.add(subscriber);
322
+ }
323
+ const selected = active;
324
+ state.cancel.set(id, () => {
325
+ state.cancelled.add(id);
326
+ selected.subscribers.delete(subscriber);
327
+ if (selected.subscribers.size === 0) {
328
+ selected.controller.abort(new Error("Indexing cancelled"));
329
+ }
330
+ });
331
+ try {
332
+ return await selected.promise;
333
+ } finally {
334
+ selected.subscribers.delete(subscriber);
335
+ }
336
+ }
337
+ async function dispatchOperation(ctx, state, message) {
338
+ const { id, op } = message;
339
+ const indexActivity2 = ctx.getActivity();
340
+ switch (op) {
341
+ case "index": {
342
+ const args = message.args;
343
+ if (isShareableFullIndex(args)) return runFullIndex(ctx, state, id, args);
344
+ const controller = new AbortController();
345
+ state.cancel.set(id, () => {
346
+ state.cancelled.add(id);
347
+ controller.abort(new Error("Indexing cancelled"));
348
+ });
349
+ return ctx.withIndexWrite(
350
+ (reportProgress) => indexService(fixedArgs(ctx, args), {
351
+ signal: controller.signal,
352
+ onProgress: (current, total) => {
353
+ reportProgress(current, total);
354
+ if (!state.cancelled.has(id)) {
355
+ ctx.send(state, { type: "progress", id, current, total });
356
+ }
357
+ }
358
+ }),
359
+ { preserveCaches: preservesQueryCaches(args) }
360
+ );
361
+ }
362
+ case "search": {
363
+ const { value, stale } = staleAwareRead(
364
+ ctx.queryCaches.searchCache,
365
+ JSON.stringify(message.args),
366
+ indexActivity2,
367
+ () => searchService(fixedArgs(ctx, message.args))
368
+ );
369
+ return stale ? { ...value, stale: true } : value;
370
+ }
371
+ case "stats":
372
+ if (indexActivity2.indexing) {
373
+ throw indexRefreshInProgressError(indexActivity2.currentFile, indexActivity2.totalFiles);
374
+ }
375
+ return staleAwareRead(
376
+ ctx.queryCaches.statsCache,
377
+ "stats",
378
+ indexActivity2,
379
+ () => statsService(fixedArgs(ctx, message.args))
380
+ ).value;
381
+ case "packageGraph": {
382
+ const { value, stale } = staleAwareRead(
383
+ ctx.queryCaches.packageGraphCache,
384
+ "package",
385
+ indexActivity2,
386
+ () => packageGraphService(fixedArgs(ctx, message.args))
387
+ );
388
+ return stale ? { ...value, stale: true } : value;
389
+ }
390
+ case "fileGraph": {
391
+ const key = message.args.packageFilter;
392
+ const { value, stale } = staleAwareRead(
393
+ ctx.queryCaches.fileGraphCache,
394
+ key,
395
+ indexActivity2,
396
+ () => fileGraphService(fixedArgs(ctx, message.args))
397
+ );
398
+ return stale ? { ...value, stale: true } : value;
399
+ }
400
+ case "symbolGraph": {
401
+ const key = message.args.fileFilter;
402
+ const { value, stale } = staleAwareRead(
403
+ ctx.queryCaches.symbolGraphCache,
404
+ key,
405
+ indexActivity2,
406
+ () => symbolGraphService(fixedArgs(ctx, message.args))
407
+ );
408
+ return stale ? { ...value, stale: true } : value;
409
+ }
410
+ case "incomingCalls": {
411
+ const callArgs = fixedArgs(ctx, message.args);
412
+ const cacheKey = JSON.stringify([
413
+ callArgs.symbol,
414
+ callArgs.file ?? "",
415
+ callArgs.limit ?? 100,
416
+ callArgs.transitive ?? false
417
+ ]);
418
+ const { value, stale } = staleAwareRead(
419
+ ctx.queryCaches.incomingCallsCache,
420
+ cacheKey,
421
+ indexActivity2,
422
+ () => incomingCallsService(callArgs)
423
+ );
424
+ return stale ? { ...value, stale: true } : value;
425
+ }
426
+ case "outgoingCalls": {
427
+ const callArgs = fixedArgs(ctx, message.args);
428
+ const cacheKey = JSON.stringify([
429
+ callArgs.symbol,
430
+ callArgs.file ?? "",
431
+ callArgs.limit ?? 100,
432
+ callArgs.transitive ?? false
433
+ ]);
434
+ const { value, stale } = staleAwareRead(
435
+ ctx.queryCaches.outgoingCallsCache,
436
+ cacheKey,
437
+ indexActivity2,
438
+ () => outgoingCallsService(callArgs)
439
+ );
440
+ return stale ? { ...value, stale: true } : value;
441
+ }
442
+ default:
443
+ throw new Error(`unknown index operation: ${String(op)}`);
444
+ }
445
+ }
446
+
447
+ // src/codebase-index/project-server-watcher.ts
448
+ import * as path2 from "node:path";
449
+ import {
450
+ DEFAULT_WALK_IGNORE_SET,
451
+ watchProjectTree
452
+ } from "@wrongstack/core/utils";
453
+ var DEFAULT_EXTERNAL_DEBOUNCE_MS = 400;
454
+ var DEFAULT_EXTERNAL_COALESCE_WINDOW_MS = 50;
455
+ function isIgnoredRelativePath(relativePath) {
456
+ return relativePath.split(/[/\\]/u).some((segment) => DEFAULT_WALK_IGNORE_SET.has(segment));
457
+ }
458
+ var ProjectServerWatcherManager = class {
459
+ constructor(options) {
460
+ this.options = options;
461
+ }
462
+ options;
463
+ externalWatcher;
464
+ externalDebounceMs = DEFAULT_EXTERNAL_DEBOUNCE_MS;
465
+ externalCoalesceWindowMs = DEFAULT_EXTERNAL_COALESCE_WINDOW_MS;
466
+ externalDebounceTimers = /* @__PURE__ */ new Map();
467
+ externalReadyFiles = /* @__PURE__ */ new Set();
468
+ externalReadyFlush;
469
+ get isWatching() {
470
+ return this.externalWatcher !== void 0;
471
+ }
472
+ get pendingFileCount() {
473
+ return this.externalDebounceTimers.size + this.externalReadyFiles.size;
474
+ }
475
+ enqueueExternalFile(file) {
476
+ const previous = this.externalDebounceTimers.get(file);
477
+ if (previous) clearTimeout(previous);
478
+ const timer = setTimeout(() => {
479
+ this.externalDebounceTimers.delete(file);
480
+ this.externalReadyFiles.add(file);
481
+ if (this.externalReadyFlush) clearTimeout(this.externalReadyFlush);
482
+ this.externalReadyFlush = setTimeout(() => {
483
+ this.externalReadyFlush = void 0;
484
+ const files = [...this.externalReadyFiles].sort();
485
+ this.externalReadyFiles.clear();
486
+ this.options.onFilesChanged(files);
487
+ }, this.externalCoalesceWindowMs);
488
+ this.externalReadyFlush.unref?.();
489
+ }, this.externalDebounceMs);
490
+ timer.unref?.();
491
+ this.externalDebounceTimers.set(file, timer);
492
+ }
493
+ ensureExternalWatcher() {
494
+ if (this.externalWatcher) return;
495
+ const projectRoot2 = this.options.projectRoot;
496
+ this.externalWatcher = watchProjectTree(
497
+ projectRoot2,
498
+ ({ filename }) => {
499
+ if (!filename || isIgnoredRelativePath(filename)) return;
500
+ const absolute = path2.resolve(projectRoot2, filename);
501
+ const relative2 = path2.relative(projectRoot2, absolute);
502
+ if (relative2 === ".." || relative2.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative2) || !isIndexablePath(absolute)) {
503
+ return;
504
+ }
505
+ this.enqueueExternalFile(absolute);
506
+ },
507
+ {
508
+ onError: () => {
509
+ }
510
+ }
511
+ );
512
+ }
513
+ stop() {
514
+ try {
515
+ this.externalWatcher?.close();
516
+ } catch {
517
+ }
518
+ this.externalWatcher = void 0;
519
+ for (const timer of this.externalDebounceTimers.values()) clearTimeout(timer);
520
+ this.externalDebounceTimers.clear();
521
+ if (this.externalReadyFlush) clearTimeout(this.externalReadyFlush);
522
+ this.externalReadyFlush = void 0;
523
+ this.externalReadyFiles.clear();
524
+ }
525
+ reconcile(clients2) {
526
+ const owners = [...clients2].filter((client) => client.watchExternal);
527
+ if (owners.length === 0) {
528
+ this.externalDebounceMs = DEFAULT_EXTERNAL_DEBOUNCE_MS;
529
+ this.externalCoalesceWindowMs = DEFAULT_EXTERNAL_COALESCE_WINDOW_MS;
530
+ this.stop();
531
+ return;
532
+ }
533
+ this.externalDebounceMs = Math.min(...owners.map((client) => client.debounceMs));
534
+ this.externalCoalesceWindowMs = Math.min(...owners.map((client) => client.coalesceWindowMs));
535
+ this.ensureExternalWatcher();
536
+ }
537
+ };
538
+
158
539
  // src/codebase-index/wal-maintenance.ts
159
540
  var DEFAULT_IDLE_MS = 3e4;
160
541
  var DEFAULT_OPTIMIZE_EVERY = 8;
@@ -235,8 +616,8 @@ function parseArgs(argv) {
235
616
  }
236
617
  if (!projectRoot2) throw new Error("codebase-index project server requires --project-root");
237
618
  return {
238
- projectRoot: path.resolve(projectRoot2),
239
- ...indexDir2 ? { indexDir: path.resolve(indexDir2) } : {}
619
+ projectRoot: path3.resolve(projectRoot2),
620
+ ...indexDir2 ? { indexDir: path3.resolve(indexDir2) } : {}
240
621
  };
241
622
  }
242
623
  useDaemonPerfDefaults();
@@ -245,16 +626,18 @@ var projectRoot = parsed.projectRoot;
245
626
  var indexDir = resolveIndexDir(projectRoot, parsed.indexDir);
246
627
  var endpoint = projectIndexServerEndpoint(projectRoot, indexDir);
247
628
  var metadataPath = projectIndexServerMetadataPath(projectRoot, indexDir);
248
- var idleMsRaw = Number(process.env["WRONGSTACK_INDEX_SERVER_IDLE_MS"]);
629
+ var idleMsRaw = Number(process.env["WRONGSTACK_INDEX_SERVER_IDLE_MS"]?.replaceAll("_", ""));
249
630
  var idleMs = Number.isFinite(idleMsRaw) && idleMsRaw >= 100 ? idleMsRaw : DEFAULT_IDLE_MS2;
250
- var clientLeaseMsRaw = Number(process.env["WRONGSTACK_INDEX_SERVER_CLIENT_LEASE_MS"]);
631
+ var clientLeaseMsRaw = Number(
632
+ process.env["WRONGSTACK_INDEX_SERVER_CLIENT_LEASE_MS"]?.replaceAll("_", "")
633
+ );
251
634
  var clientLeaseMs = Number.isFinite(clientLeaseMsRaw) && clientLeaseMsRaw >= 100 ? clientLeaseMsRaw : DEFAULT_CLIENT_LEASE_MS;
252
635
  var clientLeaseSweepMs = Math.min(1e4, Math.max(100, Math.floor(clientLeaseMs / 3)));
253
636
  var startedAt = (/* @__PURE__ */ new Date()).toISOString();
254
637
  var authToken = randomBytes(16).toString("hex");
255
638
  var markMetadataWritten;
256
- var metadataWritten = new Promise((resolve2) => {
257
- markMetadataWritten = resolve2;
639
+ var metadataWritten = new Promise((resolve3) => {
640
+ markMetadataWritten = resolve3;
258
641
  });
259
642
  var serverInfo = {
260
643
  protocolVersion: PROJECT_INDEX_SERVER_PROTOCOL_VERSION,
@@ -269,7 +652,7 @@ var serverInfo = {
269
652
  // mirrors per-frame, so JSON-only clients keep working unchanged.
270
653
  binarySupported: true
271
654
  };
272
- process.title = `wrongstack-codebase-index:${path.basename(projectRoot)}`;
655
+ process.title = `wrongstack-codebase-index:${path3.basename(projectRoot)}`;
273
656
  var clients = /* @__PURE__ */ new Set();
274
657
  var idleTimer;
275
658
  var stopping = false;
@@ -298,14 +681,6 @@ var stopMemoryWatchdog = startSharedHeapWatchdog({
298
681
  })
299
682
  });
300
683
  var lastProgressBroadcastAt = 0;
301
- var externalWatcher;
302
- var DEFAULT_EXTERNAL_DEBOUNCE_MS = 400;
303
- var DEFAULT_EXTERNAL_COALESCE_WINDOW_MS = 50;
304
- var externalDebounceMs = DEFAULT_EXTERNAL_DEBOUNCE_MS;
305
- var externalCoalesceWindowMs = DEFAULT_EXTERNAL_COALESCE_WINDOW_MS;
306
- var externalDebounceTimers = /* @__PURE__ */ new Map();
307
- var externalReadyFiles = /* @__PURE__ */ new Set();
308
- var externalReadyFlush;
309
684
  function clearQueryCaches() {
310
685
  queryCaches.clear();
311
686
  }
@@ -328,16 +703,8 @@ var walMaintenance = new WalMaintenance({
328
703
  }
329
704
  }
330
705
  });
331
- var MAX_CLIENT_WRITE_BUFFER_BYTES = 8 * 1024 * 1024;
332
706
  function send(state, message) {
333
- if (state.socket.destroyed) return;
334
- if (state.socket.writableLength > MAX_CLIENT_WRITE_BUFFER_BYTES) {
335
- state.socket.destroy(new Error("Index client fell too far behind on reads"));
336
- return;
337
- }
338
- state.socket.write(
339
- state.binary ? encodeBinaryFrame(message) : encodeProjectServerMessage(message)
340
- );
707
+ sendServerMessage(state, message);
341
708
  }
342
709
  function withWriteMutex(job) {
343
710
  queuedWrites++;
@@ -357,6 +724,25 @@ function withWriteMutex(job) {
357
724
  );
358
725
  return run;
359
726
  }
727
+ var watcherManager = new ProjectServerWatcherManager({
728
+ projectRoot,
729
+ onFilesChanged: (files) => {
730
+ void withIndexWrite(
731
+ (onProgress) => indexService(
732
+ {
733
+ projectRoot,
734
+ indexDir,
735
+ files
736
+ },
737
+ { onProgress }
738
+ ),
739
+ // Watcher runs are targeted file lists — keep the caches so the next
740
+ // refresh's stale-serve window has previous-generation answers.
741
+ { preserveCaches: true }
742
+ ).catch(() => {
743
+ });
744
+ }
745
+ });
360
746
  function serverHealth() {
361
747
  const memory = process.memoryUsage();
362
748
  const now = Date.now();
@@ -380,8 +766,8 @@ function serverHealth() {
380
766
  activeRequests,
381
767
  activeWrites,
382
768
  queuedWrites,
383
- pendingExternalFiles: externalDebounceTimers.size + externalReadyFiles.size,
384
- watchingExternal: externalWatcher !== void 0,
769
+ pendingExternalFiles: watcherManager.pendingFileCount,
770
+ watchingExternal: watcherManager.isWatching,
385
771
  watchingClients,
386
772
  clientLeaseTimeoutMs: clientLeaseMs,
387
773
  oldestClientIdleMs,
@@ -401,9 +787,6 @@ function reportIndexProgress(currentFile, totalFiles) {
401
787
  broadcastIndexActivity();
402
788
  }
403
789
  }
404
- function preservesQueryCaches(args) {
405
- return !args.force && args.files !== void 0 && args.files.length > 0;
406
- }
407
790
  function withIndexWrite(job, options) {
408
791
  return withWriteMutex(async () => {
409
792
  indexActivity = {
@@ -443,162 +826,18 @@ function withIndexWrite(job, options) {
443
826
  }
444
827
  });
445
828
  }
446
- function fixedArgs(args) {
447
- return { ...args, projectRoot, indexDir };
448
- }
449
- function isShareableFullIndex(args) {
450
- return !args.force && (!args.files || args.files.length === 0) && (!args.langs || args.langs.length === 0) && (!args.ignore || args.ignore.length === 0);
451
- }
452
- async function runFullIndex(state, id, args) {
453
- const subscriber = { state, id };
454
- let active = activeFullIndex;
455
- if (!active) {
456
- const controller = new AbortController();
457
- const subscribers = /* @__PURE__ */ new Set([subscriber]);
458
- const promise = withIndexWrite(
459
- (reportProgress) => indexService(fixedArgs(args), {
460
- signal: controller.signal,
461
- onProgress: (current, total) => {
462
- reportProgress(current, total);
463
- for (const item of subscribers) {
464
- if (!item.state.cancelled.has(item.id)) {
465
- send(item.state, { type: "progress", id: item.id, current, total });
466
- }
467
- }
468
- }
469
- }),
470
- // Shareable full index: whole-index scope, caches drop on completion.
471
- { preserveCaches: false }
472
- );
473
- active = { promise, controller, subscribers };
829
+ var operationContext = {
830
+ projectRoot,
831
+ indexDir,
832
+ queryCaches,
833
+ getActivity: () => indexActivity,
834
+ withIndexWrite,
835
+ send,
836
+ getActiveFullIndex: () => activeFullIndex,
837
+ setActiveFullIndex: (active) => {
474
838
  activeFullIndex = active;
475
- void promise.finally(() => {
476
- if (activeFullIndex === active) activeFullIndex = null;
477
- }).catch(() => {
478
- });
479
- } else {
480
- active.subscribers.add(subscriber);
481
- }
482
- const selected = active;
483
- state.cancel.set(id, () => {
484
- state.cancelled.add(id);
485
- selected.subscribers.delete(subscriber);
486
- if (selected.subscribers.size === 0) {
487
- selected.controller.abort(new Error("Indexing cancelled"));
488
- }
489
- });
490
- try {
491
- return await selected.promise;
492
- } finally {
493
- selected.subscribers.delete(subscriber);
494
839
  }
495
- }
496
- async function dispatchOperation(state, message) {
497
- const { id, op } = message;
498
- switch (op) {
499
- case "index": {
500
- const args = message.args;
501
- if (isShareableFullIndex(args)) return runFullIndex(state, id, args);
502
- const controller = new AbortController();
503
- state.cancel.set(id, () => {
504
- state.cancelled.add(id);
505
- controller.abort(new Error("Indexing cancelled"));
506
- });
507
- return withIndexWrite(
508
- (reportProgress) => indexService(fixedArgs(args), {
509
- signal: controller.signal,
510
- onProgress: (current, total) => {
511
- reportProgress(current, total);
512
- if (!state.cancelled.has(id)) send(state, { type: "progress", id, current, total });
513
- }
514
- }),
515
- { preserveCaches: preservesQueryCaches(args) }
516
- );
517
- }
518
- case "search": {
519
- const { value, stale } = staleAwareRead(
520
- queryCaches.searchCache,
521
- JSON.stringify(message.args),
522
- indexActivity,
523
- () => searchService(fixedArgs(message.args))
524
- );
525
- return stale ? { ...value, stale: true } : value;
526
- }
527
- case "stats":
528
- if (indexActivity.indexing) {
529
- throw indexRefreshInProgressError(indexActivity.currentFile, indexActivity.totalFiles);
530
- }
531
- return staleAwareRead(
532
- queryCaches.statsCache,
533
- "stats",
534
- indexActivity,
535
- () => statsService(fixedArgs(message.args))
536
- ).value;
537
- case "packageGraph": {
538
- const { value, stale } = staleAwareRead(
539
- queryCaches.packageGraphCache,
540
- "package",
541
- indexActivity,
542
- () => packageGraphService(fixedArgs(message.args))
543
- );
544
- return stale ? { ...value, stale: true } : value;
545
- }
546
- case "fileGraph": {
547
- const key = message.args.packageFilter;
548
- const { value, stale } = staleAwareRead(
549
- queryCaches.fileGraphCache,
550
- key,
551
- indexActivity,
552
- () => fileGraphService(fixedArgs(message.args))
553
- );
554
- return stale ? { ...value, stale: true } : value;
555
- }
556
- case "symbolGraph": {
557
- const key = message.args.fileFilter;
558
- const { value, stale } = staleAwareRead(
559
- queryCaches.symbolGraphCache,
560
- key,
561
- indexActivity,
562
- () => symbolGraphService(fixedArgs(message.args))
563
- );
564
- return stale ? { ...value, stale: true } : value;
565
- }
566
- case "incomingCalls": {
567
- const callArgs = fixedArgs(message.args);
568
- const cacheKey = JSON.stringify([
569
- callArgs.symbol,
570
- callArgs.file ?? "",
571
- callArgs.limit ?? 100,
572
- callArgs.transitive ?? false
573
- ]);
574
- const { value, stale } = staleAwareRead(
575
- queryCaches.incomingCallsCache,
576
- cacheKey,
577
- indexActivity,
578
- () => incomingCallsService(callArgs)
579
- );
580
- return stale ? { ...value, stale: true } : value;
581
- }
582
- case "outgoingCalls": {
583
- const callArgs = fixedArgs(message.args);
584
- const cacheKey = JSON.stringify([
585
- callArgs.symbol,
586
- callArgs.file ?? "",
587
- callArgs.limit ?? 100,
588
- callArgs.transitive ?? false
589
- ]);
590
- const { value, stale } = staleAwareRead(
591
- queryCaches.outgoingCallsCache,
592
- cacheKey,
593
- indexActivity,
594
- () => outgoingCallsService(callArgs)
595
- );
596
- return stale ? { ...value, stale: true } : value;
597
- }
598
- default:
599
- throw new Error(`unknown index operation: ${String(op)}`);
600
- }
601
- }
840
+ };
602
841
  async function handleMessage(state, message) {
603
842
  if (message.type === "cancel") {
604
843
  state.cancel.get(message.id)?.();
@@ -629,13 +868,13 @@ async function handleMessage(state, message) {
629
868
  0,
630
869
  message.coalesceWindowMs ?? DEFAULT_EXTERNAL_COALESCE_WINDOW_MS
631
870
  );
632
- reconcileExternalWatcher();
871
+ watcherManager.reconcile(clients);
633
872
  send(state, {
634
873
  type: "response",
635
874
  id: message.id,
636
875
  ok: true,
637
876
  result: {
638
- watching: externalWatcher !== void 0,
877
+ watching: watcherManager.isWatching,
639
878
  health: serverHealth()
640
879
  }
641
880
  });
@@ -644,7 +883,7 @@ async function handleMessage(state, message) {
644
883
  state.debounceMs = previousDebounceMs;
645
884
  state.coalesceWindowMs = previousCoalesceWindowMs;
646
885
  try {
647
- reconcileExternalWatcher();
886
+ watcherManager.reconcile(clients);
648
887
  } catch {
649
888
  }
650
889
  send(state, {
@@ -669,7 +908,7 @@ async function handleMessage(state, message) {
669
908
  }
670
909
  activeRequests++;
671
910
  try {
672
- const result = await dispatchOperation(state, message);
911
+ const result = await dispatchOperation(operationContext, state, message);
673
912
  if (!state.cancelled.has(message.id)) {
674
913
  send(state, { type: "response", id: message.id, ok: true, result });
675
914
  }
@@ -689,141 +928,8 @@ async function handleMessage(state, message) {
689
928
  state.cancelled.delete(message.id);
690
929
  }
691
930
  }
692
- function isIgnoredRelativePath(relativePath) {
693
- return relativePath.split(/[/\\]/u).some((segment) => DEFAULT_WALK_IGNORE_SET.has(segment));
694
- }
695
- function enqueueExternalFile(file) {
696
- const previous = externalDebounceTimers.get(file);
697
- if (previous) clearTimeout(previous);
698
- const timer = setTimeout(() => {
699
- externalDebounceTimers.delete(file);
700
- externalReadyFiles.add(file);
701
- if (externalReadyFlush) clearTimeout(externalReadyFlush);
702
- externalReadyFlush = setTimeout(() => {
703
- externalReadyFlush = void 0;
704
- const files = [...externalReadyFiles].sort();
705
- externalReadyFiles.clear();
706
- void withIndexWrite(
707
- (onProgress) => indexService(
708
- {
709
- projectRoot,
710
- indexDir,
711
- files
712
- },
713
- { onProgress }
714
- ),
715
- // Watcher runs are targeted file lists — keep the caches so the next
716
- // refresh's stale-serve window has previous-generation answers.
717
- { preserveCaches: true }
718
- ).catch(() => {
719
- });
720
- }, externalCoalesceWindowMs);
721
- externalReadyFlush.unref?.();
722
- }, externalDebounceMs);
723
- timer.unref?.();
724
- externalDebounceTimers.set(file, timer);
725
- }
726
- function ensureExternalWatcher() {
727
- if (externalWatcher) return;
728
- externalWatcher = watchProjectTree(
729
- projectRoot,
730
- ({ filename }) => {
731
- if (!filename || isIgnoredRelativePath(filename)) return;
732
- const absolute = path.resolve(projectRoot, filename);
733
- const relative2 = path.relative(projectRoot, absolute);
734
- if (relative2 === ".." || relative2.startsWith(`..${path.sep}`) || path.isAbsolute(relative2) || !isIndexablePath(absolute)) {
735
- return;
736
- }
737
- enqueueExternalFile(absolute);
738
- },
739
- {
740
- onError: () => {
741
- }
742
- }
743
- );
744
- }
745
- function stopExternalWatcher() {
746
- try {
747
- externalWatcher?.close();
748
- } catch {
749
- }
750
- externalWatcher = void 0;
751
- for (const timer of externalDebounceTimers.values()) clearTimeout(timer);
752
- externalDebounceTimers.clear();
753
- if (externalReadyFlush) clearTimeout(externalReadyFlush);
754
- externalReadyFlush = void 0;
755
- externalReadyFiles.clear();
756
- }
757
- function reconcileExternalWatcher() {
758
- const owners = [...clients].filter((client) => client.watchExternal);
759
- if (owners.length === 0) {
760
- externalDebounceMs = DEFAULT_EXTERNAL_DEBOUNCE_MS;
761
- externalCoalesceWindowMs = DEFAULT_EXTERNAL_COALESCE_WINDOW_MS;
762
- stopExternalWatcher();
763
- return;
764
- }
765
- externalDebounceMs = Math.min(...owners.map((client) => client.debounceMs));
766
- externalCoalesceWindowMs = Math.min(...owners.map((client) => client.coalesceWindowMs));
767
- ensureExternalWatcher();
768
- }
769
931
  function consume(state, chunk) {
770
- const buffer = state.buffer.length === 0 ? chunk : Buffer.concat([state.buffer, chunk]);
771
- let scan = 0;
772
- while (true) {
773
- if (buffer.length - scan === 0) break;
774
- if (isBinaryFrame(buffer[scan])) {
775
- if (buffer.length - scan < 5) break;
776
- const frameLen = buffer.readUInt32BE(scan + 1);
777
- if (frameLen > MAX_INBOUND_BINARY_FRAME_BYTES) {
778
- state.buffer = Buffer.alloc(0);
779
- state.socket.destroy(new Error("codebase-index client binary frame exceeds the IPC limit"));
780
- return;
781
- }
782
- if (buffer.length - scan < 5 + frameLen) break;
783
- const payload = buffer.subarray(scan + 5, scan + 5 + frameLen);
784
- let message2;
785
- try {
786
- message2 = decodeBinaryFrame(payload);
787
- } catch {
788
- state.buffer = Buffer.alloc(0);
789
- state.socket.destroy(new Error("invalid codebase-index client binary frame"));
790
- return;
791
- }
792
- scan += 5 + frameLen;
793
- state.binary = true;
794
- state.lastSeenAt = Date.now();
795
- void handleMessage(state, message2);
796
- continue;
797
- }
798
- const newline = buffer.indexOf(10, scan);
799
- if (newline < 0) {
800
- if (buffer.length - scan > PROJECT_INDEX_SERVER_MAX_FRAME_CHARS) {
801
- state.buffer = Buffer.alloc(0);
802
- state.socket.destroy(new Error("codebase-index client message exceeds the IPC limit"));
803
- return;
804
- }
805
- break;
806
- }
807
- if (newline - scan > PROJECT_INDEX_SERVER_MAX_FRAME_CHARS) {
808
- state.buffer = Buffer.alloc(0);
809
- state.socket.destroy(new Error("codebase-index client message exceeds the IPC limit"));
810
- return;
811
- }
812
- const line = buffer.subarray(scan, newline).toString("utf8");
813
- scan = newline + 1;
814
- if (!line) continue;
815
- let message;
816
- try {
817
- message = JSON.parse(line);
818
- } catch {
819
- state.buffer = Buffer.alloc(0);
820
- state.socket.destroy(new Error("invalid codebase-index client message"));
821
- return;
822
- }
823
- state.lastSeenAt = Date.now();
824
- void handleMessage(state, message);
825
- }
826
- state.buffer = scan === 0 ? buffer : Buffer.from(buffer.subarray(scan));
932
+ consumeClientChunk(state, chunk, handleMessage);
827
933
  }
828
934
  function scheduleIdleStop() {
829
935
  if (stopping || clients.size > 0) return;
@@ -831,24 +937,6 @@ function scheduleIdleStop() {
831
937
  idleTimer = setTimeout(() => void stop("idle-timeout"), idleMs);
832
938
  idleTimer.unref?.();
833
939
  }
834
- function removeMetadataIfOwned() {
835
- try {
836
- const current = JSON.parse(fs.readFileSync(metadataPath, "utf8"));
837
- if (current.pid === process.pid) fs.rmSync(metadataPath, { force: true });
838
- } catch {
839
- }
840
- }
841
- async function writeMetadata() {
842
- fs.mkdirSync(path.dirname(metadataPath), { recursive: true });
843
- const metadata = { ...serverInfo, authToken };
844
- await atomicWrite(metadataPath, `${JSON.stringify(metadata, null, 2)}
845
- `, { mode: 384 });
846
- await restrictFilePermissions(metadataPath, {
847
- label: "codebase-index-metadata",
848
- warn: (message) => process.stderr.write(`${message}
849
- `)
850
- });
851
- }
852
940
  var server = net.createServer((socket) => {
853
941
  if (idleTimer) clearTimeout(idleTimer);
854
942
  idleTimer = void 0;
@@ -876,7 +964,7 @@ var server = net.createServer((socket) => {
876
964
  state.cancel.clear();
877
965
  clients.delete(state);
878
966
  try {
879
- reconcileExternalWatcher();
967
+ watcherManager.reconcile(clients);
880
968
  } catch {
881
969
  }
882
970
  scheduleIdleStop();
@@ -904,21 +992,21 @@ async function stop(_reason) {
904
992
  state.socket.end();
905
993
  }
906
994
  activeFullIndex?.controller.abort(new Error("codebase-index server stopping"));
907
- stopExternalWatcher();
995
+ watcherManager.stop();
908
996
  walMaintenance.dispose();
909
997
  indexStorePool.closeAll();
910
- removeMetadataIfOwned();
911
- await new Promise((resolve2) => {
912
- server.close(() => resolve2());
998
+ removeMetadataIfOwned(metadataPath, process.pid);
999
+ await new Promise((resolve3) => {
1000
+ server.close(() => resolve3());
913
1001
  const timer = setTimeout(() => {
914
1002
  for (const state of clients) state.socket.destroy();
915
- resolve2();
1003
+ resolve3();
916
1004
  }, 500);
917
1005
  timer.unref?.();
918
1006
  });
919
1007
  if (process.platform !== "win32") {
920
1008
  try {
921
- fs.rmSync(endpoint, { force: true });
1009
+ fs2.rmSync(endpoint, { force: true });
922
1010
  } catch {
923
1011
  }
924
1012
  }
@@ -946,41 +1034,10 @@ void (async () => {
946
1034
  `);
947
1035
  process.exitCode = 1;
948
1036
  });
949
- await writeMetadata();
1037
+ const metadata = { ...serverInfo, authToken };
1038
+ await writeProjectServerMetadata(metadataPath, metadata);
950
1039
  markMetadataWritten?.();
951
1040
  scheduleIdleStop();
952
1041
  })();
953
- var SIGNAL_GUARD = /* @__PURE__ */ Symbol.for(
954
- "wrongstack.codebase-index.project-server.signalGuard"
955
- );
956
- var signalGuardStore = globalThis;
957
- var signalGuard = signalGuardStore[SIGNAL_GUARD];
958
- if (!signalGuard) {
959
- let current = async () => void 0;
960
- let armed = false;
961
- const handlers = /* @__PURE__ */ new Map();
962
- const disarm = () => {
963
- if (!armed) return;
964
- armed = false;
965
- for (const [signal, handler] of handlers) process.removeListener(signal, handler);
966
- handlers.clear();
967
- };
968
- signalGuard = {
969
- arm(next) {
970
- current = next;
971
- if (armed) return;
972
- armed = true;
973
- for (const signal of ["SIGINT", "SIGTERM"]) {
974
- const handler = () => {
975
- disarm();
976
- void current(signal);
977
- };
978
- handlers.set(signal, handler);
979
- process.on(signal, handler);
980
- }
981
- }
982
- };
983
- signalGuardStore[SIGNAL_GUARD] = signalGuard;
984
- }
985
- signalGuard.arm(stop);
1042
+ armCodebaseIndexSignalGuard(stop);
986
1043
  //# sourceMappingURL=project-server.js.map