pybao-cli 1.3.87 → 1.3.88

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 (148) hide show
  1. package/dist/REPL-ORTTZIHU.js +47 -0
  2. package/dist/{acp-NMISJFTQ.js → acp-N5LUMCFW.js} +29 -29
  3. package/dist/{agentsValidate-G7H7CUZU.js → agentsValidate-WU4DXFHE.js} +7 -7
  4. package/dist/{ask-E7XSHQAN.js → ask-DW3ZOE75.js} +28 -28
  5. package/dist/{autoUpdater-EAH2H7AP.js → autoUpdater-KPSUPSW2.js} +3 -3
  6. package/dist/{chunk-HVMJPWC6.js → chunk-3VLZYTZF.js} +3 -3
  7. package/dist/{chunk-BHBHS3MY.js → chunk-474VRNOS.js} +3 -3
  8. package/dist/{chunk-N6ETO4PE.js → chunk-6563LX5Q.js} +4 -4
  9. package/dist/{chunk-HWIU6J2T.js → chunk-6NN7AYG5.js} +1035 -87
  10. package/dist/chunk-6NN7AYG5.js.map +7 -0
  11. package/dist/{chunk-F47KO7J2.js → chunk-7VIXLUAA.js} +1 -1
  12. package/dist/{chunk-BBZ2VZ5J.js → chunk-A33NVFXN.js} +3 -3
  13. package/dist/{chunk-MRU4VN5R.js → chunk-AKWG66R7.js} +2 -2
  14. package/dist/{chunk-XD5KXOSG.js → chunk-ALGAURMU.js} +2 -2
  15. package/dist/{chunk-UF2ARFEB.js → chunk-B3FVUMDX.js} +2 -2
  16. package/dist/{chunk-HHTFRFKN.js → chunk-BSX2GZFE.js} +2 -2
  17. package/dist/{chunk-KEALJIDY.js → chunk-E7KPOGEP.js} +208 -16
  18. package/dist/chunk-E7KPOGEP.js.map +7 -0
  19. package/dist/{chunk-T2HQBH46.js → chunk-FAZTRK5X.js} +3 -3
  20. package/dist/{chunk-KUIDN7FP.js → chunk-IKZIBMB2.js} +1 -1
  21. package/dist/{chunk-LSJILN2S.js → chunk-JGBEZWTT.js} +2 -2
  22. package/dist/{chunk-L62OCVZH.js → chunk-JWBNA7JZ.js} +16 -16
  23. package/dist/{chunk-VBJP55XA.js → chunk-K63HFXPF.js} +1 -1
  24. package/dist/{chunk-3RXA6IWG.js → chunk-KHYPI3CQ.js} +1 -1
  25. package/dist/{chunk-EQMKAITH.js → chunk-LUTA67LS.js} +1 -1
  26. package/dist/{chunk-H6NBGOQG.js → chunk-MV23FSF3.js} +1 -1
  27. package/dist/{chunk-H6NBGOQG.js.map → chunk-MV23FSF3.js.map} +1 -1
  28. package/dist/{chunk-7IKX4543.js → chunk-NDMBTSPF.js} +2 -2
  29. package/dist/{chunk-VAR6IG72.js → chunk-NZ4EJPCM.js} +1 -1
  30. package/dist/{chunk-WCX3ELKY.js → chunk-OK54YOWI.js} +89 -40
  31. package/dist/{chunk-WCX3ELKY.js.map → chunk-OK54YOWI.js.map} +2 -2
  32. package/dist/{chunk-Q32HEIH2.js → chunk-PAQMFEWF.js} +4 -4
  33. package/dist/{chunk-BPN5QY2K.js → chunk-PEJHTEHF.js} +2 -2
  34. package/dist/{chunk-Q7W7PGSO.js → chunk-PV5Z5CYP.js} +1 -1
  35. package/dist/{chunk-GEHCREEY.js → chunk-RNFV47CH.js} +3 -3
  36. package/dist/{chunk-6XDESUHK.js → chunk-RRMDKSTY.js} +1 -1
  37. package/dist/{chunk-SBF7BOS4.js → chunk-SIK2ANWJ.js} +3 -3
  38. package/dist/{chunk-4GB3OWJY.js → chunk-TD6X22GV.js} +3 -3
  39. package/dist/{chunk-KTLUYKAC.js → chunk-XJWHLOEL.js} +1 -1
  40. package/dist/{cli-VTNXK3N3.js → cli-HVWWEAL7.js} +87 -87
  41. package/dist/commands-CHRAM5WS.js +51 -0
  42. package/dist/{config-FBT5ABGI.js → config-Y4MVDPA6.js} +4 -4
  43. package/dist/{context-WBZK3AZX.js → context-UQPJORNF.js} +5 -5
  44. package/dist/{customCommands-IFWVN654.js → customCommands-YWYGEKU4.js} +4 -4
  45. package/dist/{env-5O5DDVID.js → env-F6OX4C6F.js} +2 -2
  46. package/dist/{file-K3P2ZXOY.js → file-AHNZVEQN.js} +4 -4
  47. package/dist/index.js +3 -3
  48. package/dist/{llm-XZHOT5QG.js → llm-EW2YTCZ5.js} +29 -29
  49. package/dist/{llmLazy-VVQLL2O4.js → llmLazy-5BNSJLTE.js} +1 -1
  50. package/dist/{loader-JC5XSGNC.js → loader-PLP3P26N.js} +4 -4
  51. package/dist/lsp-NWJUFY4J.js +33 -0
  52. package/dist/{lspAnchor-3AQPL2ZZ.js → lspAnchor-DUHDQOTT.js} +6 -6
  53. package/dist/{mcp-DTKD53FS.js → mcp-EYRRKW6Y.js} +7 -7
  54. package/dist/{mentionProcessor-JS7EY2LD.js → mentionProcessor-2SM3E3MP.js} +5 -5
  55. package/dist/{messages-GQMW2ANU.js → messages-S5JHQ3PM.js} +1 -1
  56. package/dist/{model-VGKT6DJE.js → model-7TNEREQL.js} +5 -5
  57. package/dist/{openai-TSJZC7MB.js → openai-BOA5DJUN.js} +5 -5
  58. package/dist/{outputStyles-5VLEN2UN.js → outputStyles-TVAV6JFM.js} +4 -4
  59. package/dist/{pluginRuntime-XKPZ3BX4.js → pluginRuntime-SPIXKVXE.js} +6 -6
  60. package/dist/{pluginValidation-ZHCDH3MZ.js → pluginValidation-IMP6FT66.js} +6 -6
  61. package/dist/prompts-GQLHTZEX.js +53 -0
  62. package/dist/{pybAgentSessionLoad-5CPNQIXV.js → pybAgentSessionLoad-R55QBFO6.js} +4 -4
  63. package/dist/{pybAgentSessionResume-VCYFMJCA.js → pybAgentSessionResume-NWYYW7YR.js} +4 -4
  64. package/dist/{pybAgentStreamJsonSession-DOTUU74W.js → pybAgentStreamJsonSession-LNJMEFT7.js} +1 -1
  65. package/dist/{pybHooks-W2EX2SUW.js → pybHooks-PAVO67PL.js} +4 -4
  66. package/dist/query-DG4EGQYV.js +55 -0
  67. package/dist/{registry-OUOC3F5A.js → registry-34E5OJNF.js} +11 -5
  68. package/dist/{ripgrep-MEONNP4W.js → ripgrep-KHBNZP7Y.js} +3 -3
  69. package/dist/{skillMarketplace-34N7IKSR.js → skillMarketplace-WKEXF2PI.js} +3 -3
  70. package/dist/{state-6NDQ3LIC.js → state-E757NA7C.js} +2 -2
  71. package/dist/{theme-7K5257BK.js → theme-KZNXBKP2.js} +5 -5
  72. package/dist/{toolPermissionSettings-2QVLET4R.js → toolPermissionSettings-FYVFSFBN.js} +6 -6
  73. package/dist/tools-XM6GAEJL.js +52 -0
  74. package/dist/{userInput-MF4TLCQC.js → userInput-ROFICTJI.js} +30 -30
  75. package/package.json +1 -1
  76. package/dist/REPL-J7EKVUMW.js +0 -47
  77. package/dist/chunk-HWIU6J2T.js.map +0 -7
  78. package/dist/chunk-KEALJIDY.js.map +0 -7
  79. package/dist/commands-SNKMFZOR.js +0 -51
  80. package/dist/lsp-NJNGVY3E.js +0 -17
  81. package/dist/prompts-MQWEK5T7.js +0 -53
  82. package/dist/query-OH75KHOB.js +0 -55
  83. package/dist/tools-6MGNB35T.js +0 -52
  84. /package/dist/{REPL-J7EKVUMW.js.map → REPL-ORTTZIHU.js.map} +0 -0
  85. /package/dist/{acp-NMISJFTQ.js.map → acp-N5LUMCFW.js.map} +0 -0
  86. /package/dist/{agentsValidate-G7H7CUZU.js.map → agentsValidate-WU4DXFHE.js.map} +0 -0
  87. /package/dist/{ask-E7XSHQAN.js.map → ask-DW3ZOE75.js.map} +0 -0
  88. /package/dist/{autoUpdater-EAH2H7AP.js.map → autoUpdater-KPSUPSW2.js.map} +0 -0
  89. /package/dist/{chunk-HVMJPWC6.js.map → chunk-3VLZYTZF.js.map} +0 -0
  90. /package/dist/{chunk-BHBHS3MY.js.map → chunk-474VRNOS.js.map} +0 -0
  91. /package/dist/{chunk-N6ETO4PE.js.map → chunk-6563LX5Q.js.map} +0 -0
  92. /package/dist/{chunk-F47KO7J2.js.map → chunk-7VIXLUAA.js.map} +0 -0
  93. /package/dist/{chunk-BBZ2VZ5J.js.map → chunk-A33NVFXN.js.map} +0 -0
  94. /package/dist/{chunk-MRU4VN5R.js.map → chunk-AKWG66R7.js.map} +0 -0
  95. /package/dist/{chunk-XD5KXOSG.js.map → chunk-ALGAURMU.js.map} +0 -0
  96. /package/dist/{chunk-UF2ARFEB.js.map → chunk-B3FVUMDX.js.map} +0 -0
  97. /package/dist/{chunk-HHTFRFKN.js.map → chunk-BSX2GZFE.js.map} +0 -0
  98. /package/dist/{chunk-T2HQBH46.js.map → chunk-FAZTRK5X.js.map} +0 -0
  99. /package/dist/{chunk-KUIDN7FP.js.map → chunk-IKZIBMB2.js.map} +0 -0
  100. /package/dist/{chunk-LSJILN2S.js.map → chunk-JGBEZWTT.js.map} +0 -0
  101. /package/dist/{chunk-L62OCVZH.js.map → chunk-JWBNA7JZ.js.map} +0 -0
  102. /package/dist/{chunk-VBJP55XA.js.map → chunk-K63HFXPF.js.map} +0 -0
  103. /package/dist/{chunk-3RXA6IWG.js.map → chunk-KHYPI3CQ.js.map} +0 -0
  104. /package/dist/{chunk-EQMKAITH.js.map → chunk-LUTA67LS.js.map} +0 -0
  105. /package/dist/{chunk-7IKX4543.js.map → chunk-NDMBTSPF.js.map} +0 -0
  106. /package/dist/{chunk-VAR6IG72.js.map → chunk-NZ4EJPCM.js.map} +0 -0
  107. /package/dist/{chunk-Q32HEIH2.js.map → chunk-PAQMFEWF.js.map} +0 -0
  108. /package/dist/{chunk-BPN5QY2K.js.map → chunk-PEJHTEHF.js.map} +0 -0
  109. /package/dist/{chunk-Q7W7PGSO.js.map → chunk-PV5Z5CYP.js.map} +0 -0
  110. /package/dist/{chunk-GEHCREEY.js.map → chunk-RNFV47CH.js.map} +0 -0
  111. /package/dist/{chunk-6XDESUHK.js.map → chunk-RRMDKSTY.js.map} +0 -0
  112. /package/dist/{chunk-SBF7BOS4.js.map → chunk-SIK2ANWJ.js.map} +0 -0
  113. /package/dist/{chunk-4GB3OWJY.js.map → chunk-TD6X22GV.js.map} +0 -0
  114. /package/dist/{chunk-KTLUYKAC.js.map → chunk-XJWHLOEL.js.map} +0 -0
  115. /package/dist/{cli-VTNXK3N3.js.map → cli-HVWWEAL7.js.map} +0 -0
  116. /package/dist/{commands-SNKMFZOR.js.map → commands-CHRAM5WS.js.map} +0 -0
  117. /package/dist/{config-FBT5ABGI.js.map → config-Y4MVDPA6.js.map} +0 -0
  118. /package/dist/{context-WBZK3AZX.js.map → context-UQPJORNF.js.map} +0 -0
  119. /package/dist/{customCommands-IFWVN654.js.map → customCommands-YWYGEKU4.js.map} +0 -0
  120. /package/dist/{env-5O5DDVID.js.map → env-F6OX4C6F.js.map} +0 -0
  121. /package/dist/{file-K3P2ZXOY.js.map → file-AHNZVEQN.js.map} +0 -0
  122. /package/dist/{llm-XZHOT5QG.js.map → llm-EW2YTCZ5.js.map} +0 -0
  123. /package/dist/{llmLazy-VVQLL2O4.js.map → llmLazy-5BNSJLTE.js.map} +0 -0
  124. /package/dist/{loader-JC5XSGNC.js.map → loader-PLP3P26N.js.map} +0 -0
  125. /package/dist/{lsp-NJNGVY3E.js.map → lsp-NWJUFY4J.js.map} +0 -0
  126. /package/dist/{lspAnchor-3AQPL2ZZ.js.map → lspAnchor-DUHDQOTT.js.map} +0 -0
  127. /package/dist/{mcp-DTKD53FS.js.map → mcp-EYRRKW6Y.js.map} +0 -0
  128. /package/dist/{mentionProcessor-JS7EY2LD.js.map → mentionProcessor-2SM3E3MP.js.map} +0 -0
  129. /package/dist/{messages-GQMW2ANU.js.map → messages-S5JHQ3PM.js.map} +0 -0
  130. /package/dist/{model-VGKT6DJE.js.map → model-7TNEREQL.js.map} +0 -0
  131. /package/dist/{openai-TSJZC7MB.js.map → openai-BOA5DJUN.js.map} +0 -0
  132. /package/dist/{outputStyles-5VLEN2UN.js.map → outputStyles-TVAV6JFM.js.map} +0 -0
  133. /package/dist/{pluginRuntime-XKPZ3BX4.js.map → pluginRuntime-SPIXKVXE.js.map} +0 -0
  134. /package/dist/{pluginValidation-ZHCDH3MZ.js.map → pluginValidation-IMP6FT66.js.map} +0 -0
  135. /package/dist/{prompts-MQWEK5T7.js.map → prompts-GQLHTZEX.js.map} +0 -0
  136. /package/dist/{pybAgentSessionLoad-5CPNQIXV.js.map → pybAgentSessionLoad-R55QBFO6.js.map} +0 -0
  137. /package/dist/{pybAgentSessionResume-VCYFMJCA.js.map → pybAgentSessionResume-NWYYW7YR.js.map} +0 -0
  138. /package/dist/{pybAgentStreamJsonSession-DOTUU74W.js.map → pybAgentStreamJsonSession-LNJMEFT7.js.map} +0 -0
  139. /package/dist/{pybHooks-W2EX2SUW.js.map → pybHooks-PAVO67PL.js.map} +0 -0
  140. /package/dist/{query-OH75KHOB.js.map → query-DG4EGQYV.js.map} +0 -0
  141. /package/dist/{registry-OUOC3F5A.js.map → registry-34E5OJNF.js.map} +0 -0
  142. /package/dist/{ripgrep-MEONNP4W.js.map → ripgrep-KHBNZP7Y.js.map} +0 -0
  143. /package/dist/{skillMarketplace-34N7IKSR.js.map → skillMarketplace-WKEXF2PI.js.map} +0 -0
  144. /package/dist/{state-6NDQ3LIC.js.map → state-E757NA7C.js.map} +0 -0
  145. /package/dist/{theme-7K5257BK.js.map → theme-KZNXBKP2.js.map} +0 -0
  146. /package/dist/{toolPermissionSettings-2QVLET4R.js.map → toolPermissionSettings-FYVFSFBN.js.map} +0 -0
  147. /package/dist/{tools-6MGNB35T.js.map → tools-XM6GAEJL.js.map} +0 -0
  148. /package/dist/{userInput-MF4TLCQC.js.map → userInput-ROFICTJI.js.map} +0 -0
@@ -2,20 +2,22 @@ import { createRequire as __pybCreateRequire } from "node:module";
2
2
  const require = __pybCreateRequire(import.meta.url);
3
3
  import {
4
4
  LspServerRegistry,
5
- findNearestRoot
6
- } from "./chunk-KEALJIDY.js";
5
+ findNearestRoot,
6
+ getInstallNotices
7
+ } from "./chunk-E7KPOGEP.js";
7
8
  import {
8
9
  levenshtein
9
10
  } from "./chunk-UZ34JEUK.js";
10
11
  import {
11
12
  getCwd
12
- } from "./chunk-KUIDN7FP.js";
13
+ } from "./chunk-IKZIBMB2.js";
13
14
 
14
15
  // src/lsp/index.ts
15
16
  import { extname as extname2 } from "path";
16
17
  import { pathToFileURL as pathToFileURL2 } from "url";
17
- import { readFile as readFile3 } from "fs/promises";
18
+ import { readFile as readFile4 } from "fs/promises";
18
19
  import * as http from "http";
20
+ import { createHash as createHash2 } from "crypto";
19
21
 
20
22
  // src/tools/search/LspTool/client/generic.ts
21
23
  import { createMessageConnection, StreamMessageReader, StreamMessageWriter } from "vscode-jsonrpc/node.js";
@@ -27,6 +29,7 @@ var DiagnosticsEventBus = class {
27
29
  lastEventAt = /* @__PURE__ */ new Map();
28
30
  debounceTimers = /* @__PURE__ */ new Map();
29
31
  waiting = /* @__PURE__ */ new Map();
32
+ subscriptions = /* @__PURE__ */ new Map();
30
33
  constructor(options) {
31
34
  this.debounceMs = options?.debounceMs ?? 150;
32
35
  }
@@ -44,6 +47,12 @@ var DiagnosticsEventBus = class {
44
47
  for (const resolver of Array.from(waiters)) {
45
48
  resolver();
46
49
  }
50
+ const subs = this.subscriptions.get(uri);
51
+ if (subs) {
52
+ for (const handler of Array.from(subs)) {
53
+ handler(uri);
54
+ }
55
+ }
47
56
  }, this.debounceMs);
48
57
  this.debounceTimers.set(uri, timer);
49
58
  }
@@ -73,6 +82,17 @@ var DiagnosticsEventBus = class {
73
82
  }, timeoutMs);
74
83
  });
75
84
  }
85
+ subscribe(uri, handler) {
86
+ const subs = this.subscriptions.get(uri) ?? /* @__PURE__ */ new Set();
87
+ subs.add(handler);
88
+ this.subscriptions.set(uri, subs);
89
+ return () => {
90
+ const current = this.subscriptions.get(uri);
91
+ if (!current) return;
92
+ current.delete(handler);
93
+ if (current.size === 0) this.subscriptions.delete(uri);
94
+ };
95
+ }
76
96
  };
77
97
  var GenericLspClient = class {
78
98
  constructor(serverCommand, serverArgs, cwd, rootPath, extraEnv, initializationOptions) {
@@ -296,7 +316,16 @@ var GenericLspClient = class {
296
316
  }
297
317
  async waitForReadiness(filePath, timeoutMs = 3e3) {
298
318
  const normalizedUri = this.normalizeUri(filePath);
319
+ let done = false;
320
+ const unsubscribe = this.diagnosticsBus.subscribe(normalizedUri, () => {
321
+ done = true;
322
+ });
299
323
  await this.diagnosticsBus.waitForIdle(normalizedUri, timeoutMs);
324
+ if (done) {
325
+ unsubscribe();
326
+ } else {
327
+ unsubscribe();
328
+ }
300
329
  }
301
330
  on(event, handler) {
302
331
  this.diagnosticEvents.on(event, handler);
@@ -352,6 +381,12 @@ var LspClientManager = class _LspClientManager {
352
381
  clients = /* @__PURE__ */ new Map();
353
382
  spawning = /* @__PURE__ */ new Map();
354
383
  broken = /* @__PURE__ */ new Set();
384
+ failures = /* @__PURE__ */ new Map();
385
+ readiness = /* @__PURE__ */ new Map();
386
+ prewarmByRoot = /* @__PURE__ */ new Map();
387
+ prewarmHits = /* @__PURE__ */ new Map();
388
+ spawnMetrics = /* @__PURE__ */ new Map();
389
+ requestMetrics = /* @__PURE__ */ new Map();
355
390
  constructor() {
356
391
  }
357
392
  static getInstance() {
@@ -367,6 +402,36 @@ var LspClientManager = class _LspClientManager {
367
402
  if (serverInfo?.languageId) return serverInfo.languageId;
368
403
  return LspServerRegistry.getInstance().getLanguageIdForExtension(ext);
369
404
  }
405
+ getPrewarmTtlMs() {
406
+ const override = Number.parseInt(process.env.PYB_LSP_PREWARM_TTL_MS ?? "", 10);
407
+ if (Number.isFinite(override) && override > 0) return override;
408
+ return 3e4;
409
+ }
410
+ getPrewarmTriggerCount() {
411
+ const override = Number.parseInt(process.env.PYB_LSP_PREWARM_TRIGGER_COUNT ?? "", 10);
412
+ if (Number.isFinite(override) && override > 0) return override;
413
+ return 2;
414
+ }
415
+ shouldPrewarm(rootPath) {
416
+ const now = this.now();
417
+ const last = this.prewarmByRoot.get(rootPath);
418
+ const ttlMs = this.getPrewarmTtlMs();
419
+ if (last !== void 0 && now - last < ttlMs) {
420
+ return false;
421
+ }
422
+ const nextHits = (this.prewarmHits.get(rootPath) ?? 0) + 1;
423
+ this.prewarmHits.set(rootPath, nextHits);
424
+ if (nextHits < this.getPrewarmTriggerCount()) {
425
+ return false;
426
+ }
427
+ this.prewarmHits.delete(rootPath);
428
+ this.prewarmByRoot.set(rootPath, now);
429
+ return true;
430
+ }
431
+ async maybeEnsureWorkspaceClients(rootPath) {
432
+ if (!this.shouldPrewarm(rootPath)) return;
433
+ await this.ensureWorkspaceClients(rootPath);
434
+ }
370
435
  async ensureWorkspaceClients(rootPath) {
371
436
  try {
372
437
  const files = await readdir(rootPath, { withFileTypes: true });
@@ -464,7 +529,7 @@ var LspClientManager = class _LspClientManager {
464
529
  const safeCwd = /[^\x00-\x7F]/.test(resolvedRoot) ? process.cwd() : resolvedRoot;
465
530
  let initOpts = serverInfo.initializationOptions;
466
531
  if (serverInfo.id === "pyright" || serverInfo.id === "ty") {
467
- const { detectPythonVenv } = await import("./registry-OUOC3F5A.js");
532
+ const { detectPythonVenv } = await import("./registry-34E5OJNF.js");
468
533
  const pythonPath = detectPythonVenv(resolvedRoot);
469
534
  if (pythonPath) {
470
535
  initOpts = { ...initOpts ?? {}, pythonPath };
@@ -479,6 +544,8 @@ var LspClientManager = class _LspClientManager {
479
544
  initOpts
480
545
  );
481
546
  await nextClient.initialize();
547
+ nextClient.serverId = serverInfo.id;
548
+ nextClient.serverKey = key;
482
549
  this.clients.set(key, nextClient);
483
550
  return nextClient;
484
551
  }
@@ -489,19 +556,30 @@ var LspClientManager = class _LspClientManager {
489
556
  this.clients.clear();
490
557
  this.broken.clear();
491
558
  this.spawning.clear();
559
+ this.failures.clear();
560
+ this.readiness.clear();
561
+ this.prewarmByRoot.clear();
562
+ this.prewarmHits.clear();
563
+ this.spawnMetrics.clear();
564
+ this.requestMetrics.clear();
492
565
  }
493
566
  async workspaceSymbol(query, rootPath = getCwd(), options) {
494
567
  if (this.clients.size === 0) {
495
- await this.ensureWorkspaceClients(rootPath);
568
+ await this.maybeEnsureWorkspaceClients(rootPath);
496
569
  }
497
570
  const results = [];
498
571
  for (const client of this.clients.values()) {
572
+ const serverId = client.serverId ?? "unknown";
573
+ this.recordRequestAttempt(serverId, "workspaceSymbol");
574
+ const startAt = this.now();
499
575
  try {
500
576
  const symbols = await client.workspaceSymbol(query);
501
577
  if (symbols && Array.isArray(symbols)) {
502
578
  results.push(...symbols);
503
579
  }
580
+ this.recordRequestSuccess(serverId, "workspaceSymbol", this.now() - startAt);
504
581
  } catch (e) {
582
+ this.recordRequestFailure(serverId, "workspaceSymbol", "error");
505
583
  }
506
584
  }
507
585
  if (results.length === 0 && query.length > 0) {
@@ -659,11 +737,67 @@ var LspClientManager = class _LspClientManager {
659
737
  diagnostics.push({ client: key, entries: snapshot });
660
738
  }
661
739
  }
740
+ const failures = Array.from(this.failures.entries()).map(([client, entry]) => ({
741
+ client,
742
+ reason: entry.reason,
743
+ count: entry.count,
744
+ until: entry.until,
745
+ last: entry.last
746
+ }));
747
+ const readiness = Array.from(this.readiness.entries()).map(([filePath, at]) => ({
748
+ filePath,
749
+ at
750
+ }));
751
+ const prewarms = Array.from(this.prewarmByRoot.entries()).map(([root, at]) => ({
752
+ root,
753
+ at
754
+ }));
755
+ const metrics = Array.from(this.spawnMetrics.entries()).map(([server, entry]) => {
756
+ const { avgLatency, p95Latency } = this.computeLatencyStats(entry.latencies);
757
+ const successRate = entry.attempts === 0 ? 0 : entry.successes / entry.attempts;
758
+ return {
759
+ server,
760
+ attempts: entry.attempts,
761
+ successes: entry.successes,
762
+ failures: entry.failures,
763
+ successRate,
764
+ latencyAvgMs: avgLatency,
765
+ latencyP95Ms: p95Latency,
766
+ failureReasons: { ...entry.failureReasons }
767
+ };
768
+ });
769
+ const regressionFactor = this.getRequestRegressionFactor();
770
+ const requestMetrics = Array.from(this.requestMetrics.values()).map((entry) => {
771
+ const { avgLatency, p95Latency } = this.computeLatencyStats(entry.latencies);
772
+ const successRate = entry.attempts === 0 ? 0 : entry.successes / entry.attempts;
773
+ const baselineAvg = entry.baseline?.avgMs ?? 0;
774
+ const baselineP95 = entry.baseline?.p95Ms ?? 0;
775
+ const regression = entry.baseline !== void 0 && (avgLatency > baselineAvg * regressionFactor || p95Latency > baselineP95 * regressionFactor);
776
+ return {
777
+ server: entry.serverId,
778
+ operation: entry.operation,
779
+ attempts: entry.attempts,
780
+ successes: entry.successes,
781
+ failures: entry.failures,
782
+ successRate,
783
+ latencyAvgMs: avgLatency,
784
+ latencyP95Ms: p95Latency,
785
+ baselineAvgMs: baselineAvg,
786
+ baselineP95Ms: baselineP95,
787
+ regression,
788
+ failureReasons: { ...entry.failureReasons }
789
+ };
790
+ });
662
791
  return {
663
792
  clients: Array.from(this.clients.keys()),
664
793
  spawning: Array.from(this.spawning.keys()),
665
794
  broken: Array.from(this.broken.values()),
666
- diagnostics
795
+ diagnostics,
796
+ failures,
797
+ readiness,
798
+ prewarms,
799
+ metrics,
800
+ requestMetrics
667
801
  };
668
802
  }
669
803
  // New helper to expose waitForReadiness to clients who only have a manager
@@ -671,6 +805,7 @@ var LspClientManager = class _LspClientManager {
671
805
  const client = await this.getClientForFile(filePath);
672
806
  if (client) {
673
807
  await client.waitForReadiness(filePath, timeoutMs);
808
+ this.readiness.set(filePath, this.now());
674
809
  }
675
810
  }
676
811
  matchesServerFilters(filePath, serverInfo) {
@@ -708,43 +843,173 @@ var LspClientManager = class _LspClientManager {
708
843
  }
709
844
  return defaultRoot;
710
845
  }
846
+ now() {
847
+ return Date.now();
848
+ }
849
+ computeLatencyStats(latencies) {
850
+ const latencySamples = [...latencies].sort((a, b) => a - b);
851
+ const avgLatency = latencySamples.length === 0 ? 0 : latencySamples.reduce((sum, value) => sum + value, 0) / latencySamples.length;
852
+ const p95Index = latencySamples.length === 0 ? -1 : Math.ceil(latencySamples.length * 0.95) - 1;
853
+ const p95Latency = p95Index >= 0 ? latencySamples[p95Index] : 0;
854
+ return { avgLatency, p95Latency };
855
+ }
856
+ getMetricsEntry(serverId) {
857
+ let entry = this.spawnMetrics.get(serverId);
858
+ if (!entry) {
859
+ entry = {
860
+ attempts: 0,
861
+ successes: 0,
862
+ failures: 0,
863
+ latencies: [],
864
+ failureReasons: { prepare: 0, spawn: 0 }
865
+ };
866
+ this.spawnMetrics.set(serverId, entry);
867
+ }
868
+ return entry;
869
+ }
870
+ recordAttempt(serverId) {
871
+ const entry = this.getMetricsEntry(serverId);
872
+ entry.attempts += 1;
873
+ }
874
+ recordSuccess(serverId, latencyMs) {
875
+ const entry = this.getMetricsEntry(serverId);
876
+ entry.successes += 1;
877
+ entry.latencies.push(latencyMs);
878
+ if (entry.latencies.length > 50) {
879
+ entry.latencies.shift();
880
+ }
881
+ }
882
+ recordFailure(serverId, reason) {
883
+ const entry = this.getMetricsEntry(serverId);
884
+ entry.failures += 1;
885
+ entry.failureReasons[reason] += 1;
886
+ }
887
+ getRequestKey(serverId, operation) {
888
+ return `${serverId}::${operation}`;
889
+ }
890
+ getRequestEntry(serverId, operation) {
891
+ const key = this.getRequestKey(serverId, operation);
892
+ let entry = this.requestMetrics.get(key);
893
+ if (!entry) {
894
+ entry = {
895
+ serverId,
896
+ operation,
897
+ attempts: 0,
898
+ successes: 0,
899
+ failures: 0,
900
+ latencies: [],
901
+ failureReasons: { unavailable: 0, error: 0, timeout: 0, invalid: 0 }
902
+ };
903
+ this.requestMetrics.set(key, entry);
904
+ }
905
+ return entry;
906
+ }
907
+ getRequestBaselineSampleSize() {
908
+ return 10;
909
+ }
910
+ getRequestRegressionFactor() {
911
+ return 1.5;
912
+ }
913
+ recordRequestAttempt(serverId, operation) {
914
+ const entry = this.getRequestEntry(serverId, operation);
915
+ entry.attempts += 1;
916
+ }
917
+ recordRequestSuccess(serverId, operation, latencyMs) {
918
+ const entry = this.getRequestEntry(serverId, operation);
919
+ entry.successes += 1;
920
+ entry.latencies.push(latencyMs);
921
+ if (entry.latencies.length > 50) {
922
+ entry.latencies.shift();
923
+ }
924
+ if (!entry.baseline && entry.latencies.length >= this.getRequestBaselineSampleSize()) {
925
+ const { avgLatency, p95Latency } = this.computeLatencyStats(entry.latencies);
926
+ entry.baseline = {
927
+ avgMs: avgLatency,
928
+ p95Ms: p95Latency,
929
+ samples: entry.latencies.length
930
+ };
931
+ }
932
+ }
933
+ recordRequestFailure(serverId, operation, reason) {
934
+ const entry = this.getRequestEntry(serverId, operation);
935
+ entry.failures += 1;
936
+ entry.failureReasons[reason] += 1;
937
+ }
938
+ getBaseTtlMs(reason) {
939
+ const override = Number.parseInt(process.env.PYB_LSP_BROKEN_TTL_MS ?? "", 10);
940
+ if (Number.isFinite(override) && override > 0) return override;
941
+ return reason === "prepare" ? 5e3 : 1e4;
942
+ }
943
+ markBroken(key, reason) {
944
+ const prev = this.failures.get(key);
945
+ const count = prev ? prev.count + 1 : 1;
946
+ const base = this.getBaseTtlMs(reason);
947
+ const ttl = Math.min(base * Math.pow(2, count - 1), 6e4);
948
+ const until = this.now() + ttl;
949
+ this.failures.set(key, { count, until, reason, last: this.now() });
950
+ this.broken.add(key);
951
+ }
952
+ isWithinBrokenTtl(key) {
953
+ const entry = this.failures.get(key);
954
+ if (!entry) return false;
955
+ const active = this.now() < entry.until;
956
+ if (!active) {
957
+ this.failures.delete(key);
958
+ this.broken.delete(key);
959
+ }
960
+ return active;
961
+ }
711
962
  async getClientForServer(filePath, serverInfo, rootPath) {
712
963
  if (!this.matchesServerFilters(filePath, serverInfo)) {
713
964
  return null;
714
965
  }
715
966
  const resolvedRoot = rootPath ?? await this.resolveRootForServer(filePath, serverInfo);
967
+ const key = `${resolvedRoot}:${serverInfo.id}`;
968
+ if (this.isWithinBrokenTtl(key)) {
969
+ return null;
970
+ }
971
+ const existingSpawn = this.spawning.get(key);
972
+ if (existingSpawn) {
973
+ return existingSpawn;
974
+ }
975
+ const existingClient = this.clients.get(key);
976
+ if (existingClient) {
977
+ return existingClient;
978
+ }
979
+ this.recordAttempt(serverInfo.id);
716
980
  try {
717
981
  const ready = await serverInfo.prepare();
718
982
  if (!ready) {
983
+ this.recordFailure(serverInfo.id, "prepare");
984
+ this.markBroken(key, "prepare");
719
985
  return null;
720
986
  }
721
987
  } catch (e) {
988
+ this.recordFailure(serverInfo.id, "prepare");
989
+ this.markBroken(key, "prepare");
722
990
  return null;
723
991
  }
724
- const key = `${resolvedRoot}:${serverInfo.id}`;
725
- if (this.broken.has(key)) {
726
- return null;
727
- }
728
- const existingSpawn = this.spawning.get(key);
729
- if (existingSpawn) {
730
- return existingSpawn;
731
- }
732
- let client = this.clients.get(key);
733
- if (!client) {
734
- const spawnPromise = (async () => {
735
- try {
736
- return await this.spawnClient(resolvedRoot, serverInfo, key);
737
- } catch (error) {
738
- this.broken.add(key);
739
- return null;
740
- } finally {
741
- this.spawning.delete(key);
992
+ const spawnStart = this.now();
993
+ const spawnPromise = (async () => {
994
+ try {
995
+ const nextClient = await this.spawnClient(resolvedRoot, serverInfo, key);
996
+ if (nextClient) {
997
+ this.recordSuccess(serverInfo.id, this.now() - spawnStart);
998
+ } else {
999
+ this.recordFailure(serverInfo.id, "spawn");
1000
+ this.markBroken(key, "spawn");
742
1001
  }
743
- })();
744
- this.spawning.set(key, spawnPromise);
745
- client = await spawnPromise;
746
- }
747
- return client;
1002
+ return nextClient;
1003
+ } catch (error) {
1004
+ this.recordFailure(serverInfo.id, "spawn");
1005
+ this.markBroken(key, "spawn");
1006
+ return null;
1007
+ } finally {
1008
+ this.spawning.delete(key);
1009
+ }
1010
+ })();
1011
+ this.spawning.set(key, spawnPromise);
1012
+ return spawnPromise;
748
1013
  }
749
1014
  };
750
1015
 
@@ -1159,63 +1424,39 @@ var initParser = async () => {
1159
1424
  // src/utils/tree-sitter/lsp-adapter.ts
1160
1425
  import { readFile as readFile2 } from "fs/promises";
1161
1426
  var SYMBOL_KIND_MAP2 = {
1162
- // TypeScript / JavaScript
1163
1427
  function_declaration: "Function",
1428
+ function_expression: "Function",
1429
+ arrow_function: "Function",
1164
1430
  method_definition: "Method",
1165
1431
  class_declaration: "Class",
1166
1432
  interface_declaration: "Interface",
1433
+ enum_declaration: "Enum",
1434
+ type_alias_declaration: "Type",
1167
1435
  variable_declarator: "Variable",
1168
1436
  export_statement: "Module",
1169
- // Python
1170
1437
  function_definition: "Function",
1171
1438
  class_definition: "Class",
1172
- // Bash
1173
- // function_definition is shared with Python/others
1174
- // C#
1439
+ assignment: "Variable",
1175
1440
  namespace_declaration: "Namespace",
1176
1441
  method_declaration: "Method",
1177
1442
  struct_declaration: "Struct",
1178
- enum_declaration: "Enum",
1179
1443
  property_declaration: "Property",
1180
- // class_declaration shared
1181
- // interface_declaration shared
1182
- // Go
1183
1444
  type_declaration: "Class",
1184
- // often struct/interface
1185
1445
  field_declaration: "Field",
1186
- // function_declaration shared
1187
- // method_declaration shared
1188
- // Rust
1189
1446
  function_item: "Function",
1190
1447
  struct_item: "Struct",
1191
1448
  enum_item: "Enum",
1192
1449
  impl_item: "Class",
1193
1450
  trait_item: "Interface",
1194
1451
  mod_item: "Module",
1195
- // Java
1196
- // class_declaration shared
1197
- // method_declaration shared
1198
- // interface_declaration shared
1199
- // enum_declaration shared
1200
- // Scala
1201
1452
  object_definition: "Class",
1202
1453
  trait_definition: "Interface",
1203
- // class_definition shared with Python
1204
- // function_definition shared with Python
1205
- // Ruby
1206
1454
  module: "Module"
1207
- // class shared
1208
- // method shared
1209
- // PHP
1210
- // class_declaration shared
1211
- // method_declaration shared
1212
- // function_definition shared
1213
1455
  };
1214
1456
  var TreeSitterLspAdapter = class {
1215
1457
  static async getDocumentSymbols(filePath) {
1216
1458
  const langId = ParserRegistry.getLanguage(filePath);
1217
1459
  if (!langId) {
1218
- console.warn(`No language found for file: ${filePath}`);
1219
1460
  return [];
1220
1461
  }
1221
1462
  try {
@@ -1224,8 +1465,10 @@ var TreeSitterLspAdapter = class {
1224
1465
  const tree = parser.parse(content);
1225
1466
  return this.collectSymbols(tree.rootNode);
1226
1467
  } catch (error) {
1227
- console.error(`Error parsing file ${filePath}:`, error);
1228
- return [];
1468
+ if (error instanceof Error) {
1469
+ throw new Error(`Tree-sitter parse failed for ${filePath}: ${error.message}`);
1470
+ }
1471
+ throw new Error(`Tree-sitter parse failed for ${filePath}`);
1229
1472
  }
1230
1473
  }
1231
1474
  static collectSymbols(node) {
@@ -1259,7 +1502,7 @@ var TreeSitterLspAdapter = class {
1259
1502
  const nameNode = node.childForFieldName("name");
1260
1503
  if (nameNode) return nameNode.text;
1261
1504
  for (const child of node.namedChildren) {
1262
- if (child.type === "identifier" || child.type === "type_identifier") {
1505
+ if (child.type === "identifier" || child.type === "type_identifier" || child.type === "property_identifier" || child.type === "field_identifier" || child.type === "scoped_identifier") {
1263
1506
  return child.text;
1264
1507
  }
1265
1508
  }
@@ -1269,25 +1512,121 @@ var TreeSitterLspAdapter = class {
1269
1512
 
1270
1513
  // src/utils/tree-sitter/scope-analyzer.ts
1271
1514
  import { Query } from "web-tree-sitter";
1515
+ import { readFile as readFile3 } from "fs/promises";
1516
+ import path3 from "path";
1517
+ import { fileURLToPath as fileURLToPath5 } from "url";
1518
+ import { createHash } from "crypto";
1272
1519
  var QUERIES = {
1273
1520
  python: `
1274
1521
  (function_definition name: (identifier) @name)
1275
1522
  (parameters (identifier) @param)
1276
1523
  (assignment left: (identifier) @var)
1524
+ (for_statement left: (identifier) @var)
1277
1525
  `,
1278
1526
  typescript: `
1279
1527
  (function_declaration name: (identifier) @name)
1528
+ (method_definition name: (property_identifier) @name)
1280
1529
  (variable_declarator name: (identifier) @var)
1530
+ (variable_declarator name: (array_pattern (identifier) @var))
1531
+ (variable_declarator name: (object_pattern (pair_pattern key: (property_identifier) value: (identifier) @var)))
1532
+ (variable_declarator name: (object_pattern (pair_pattern key: (property_identifier) value: (assignment_pattern left: (identifier) @var))))
1533
+ (variable_declarator name: (object_pattern (shorthand_property_identifier_pattern) @var))
1281
1534
  (required_parameter pattern: (identifier) @param)
1535
+ (required_parameter pattern: (array_pattern (identifier) @param))
1536
+ (required_parameter pattern: (object_pattern (shorthand_property_identifier_pattern) @param))
1282
1537
  (optional_parameter pattern: (identifier) @param)
1538
+ (shorthand_property_identifier_pattern) @var
1539
+ (catch_clause (identifier) @var)
1540
+ (for_in_statement left: (identifier) @var)
1541
+ (for_statement initializer: (lexical_declaration (variable_declarator name: (identifier) @var)))
1283
1542
  `
1284
1543
  };
1544
+ var SCOPE_NODE_TYPES = {
1545
+ python: /* @__PURE__ */ new Set(["function_definition", "class_definition", "module"]),
1546
+ typescript: /* @__PURE__ */ new Set([
1547
+ "function_declaration",
1548
+ "function",
1549
+ "function_expression",
1550
+ "arrow_function",
1551
+ "method_definition",
1552
+ "class_declaration",
1553
+ "statement_block",
1554
+ "program"
1555
+ ])
1556
+ };
1557
+ var SCOPE_BOUNDARY_TYPES = {
1558
+ python: /* @__PURE__ */ new Set(["function_definition", "class_definition"]),
1559
+ typescript: /* @__PURE__ */ new Set([
1560
+ "function_declaration",
1561
+ "function",
1562
+ "function_expression",
1563
+ "arrow_function",
1564
+ "method_definition",
1565
+ "class_declaration",
1566
+ "statement_block"
1567
+ ])
1568
+ };
1569
+ var __filename = fileURLToPath5(import.meta.url);
1570
+ var __dirname2 = path3.dirname(__filename);
1571
+ async function loadScopeQuery(langKey) {
1572
+ const queryPath = path3.join(__dirname2, "queries", langKey, "locals.scm");
1573
+ try {
1574
+ return await readFile3(queryPath, "utf-8");
1575
+ } catch {
1576
+ return QUERIES[langKey] ?? null;
1577
+ }
1578
+ }
1579
+ var QUERY_PRIORITY_LANGUAGES = ["typescript", "python"];
1580
+ var SCOPE_CACHE = /* @__PURE__ */ new Map();
1581
+ var SCOPE_CACHE_TTL_MS = 30 * 1e3;
1582
+ function computeHash(value) {
1583
+ return createHash("sha256").update(value).digest("hex");
1584
+ }
1585
+ function getScopeCacheKey(langKey, code, position) {
1586
+ const hash = computeHash(code);
1587
+ return `${langKey}:${hash}:${position.row}:${position.column}`;
1588
+ }
1589
+ function getCachedScope(key, now) {
1590
+ const cached = SCOPE_CACHE.get(key);
1591
+ if (!cached) return null;
1592
+ if (now >= cached.expiresAt) {
1593
+ SCOPE_CACHE.delete(key);
1594
+ return null;
1595
+ }
1596
+ return cached.value;
1597
+ }
1285
1598
  var ScopeAnalyzer = class {
1599
+ static async getQuerySource(langKey) {
1600
+ return loadScopeQuery(langKey);
1601
+ }
1602
+ static async getQueryCoverage(langKey, code) {
1603
+ const parser = await loadLanguage(langKey);
1604
+ const queryStr = await loadScopeQuery(langKey);
1605
+ if (!queryStr) return { total: 0, unique: 0, names: [] };
1606
+ const language = parser.language;
1607
+ if (!language) return { total: 0, unique: 0, names: [] };
1608
+ const query = new Query(language, queryStr);
1609
+ const tree = parser.parse(code);
1610
+ const captures = query.captures(tree.rootNode);
1611
+ const names = captures.map((c) => c.node.text);
1612
+ const uniqueNames = Array.from(new Set(names));
1613
+ return { total: captures.length, unique: uniqueNames.length, names: uniqueNames };
1614
+ }
1615
+ static isPriorityLanguage(langKey) {
1616
+ return QUERY_PRIORITY_LANGUAGES.includes(langKey);
1617
+ }
1618
+ static getPriorityLanguages() {
1619
+ return [...QUERY_PRIORITY_LANGUAGES];
1620
+ }
1286
1621
  static async getScope(filename, code, position) {
1287
1622
  const langKey = ParserRegistry.getLanguage(filename);
1288
1623
  if (!langKey) {
1289
1624
  throw new Error(`Unsupported language for file: ${filename}`);
1290
1625
  }
1626
+ const now = Date.now();
1627
+ const cacheKey = getScopeCacheKey(langKey, code, position);
1628
+ const cached = getCachedScope(cacheKey, now);
1629
+ if (cached) return cached;
1291
1630
  const parser = await loadLanguage(langKey);
1292
1631
  const tree = parser.parse(code);
1293
1632
  const node = tree.rootNode.descendantForPosition(position);
@@ -1297,17 +1636,16 @@ var ScopeAnalyzer = class {
1297
1636
  ancestors.push(current);
1298
1637
  current = current.parent;
1299
1638
  }
1300
- const scopeNodes = ancestors.filter(
1301
- (n) => n.type === "function_definition" || n.type === "function_declaration" || n.type === "module" || // Python root
1302
- n.type === "program"
1303
- // TS root
1304
- );
1639
+ const scopeNodes = ancestors.filter((n) => {
1640
+ const types = SCOPE_NODE_TYPES[langKey];
1641
+ return types ? types.has(n.type) : false;
1642
+ });
1305
1643
  const result = {
1306
1644
  locals: [],
1307
1645
  closure: []
1308
1646
  };
1309
1647
  if (scopeNodes.length === 0) return result;
1310
- const queryStr = QUERIES[langKey];
1648
+ const queryStr = await loadScopeQuery(langKey);
1311
1649
  if (!queryStr) return result;
1312
1650
  const language = parser.language;
1313
1651
  if (!language) return result;
@@ -1319,23 +1657,53 @@ var ScopeAnalyzer = class {
1319
1657
  for (const capture of captures) {
1320
1658
  const defNode = capture.node;
1321
1659
  let temp = defNode.parent;
1322
- if (temp && (temp.type === "function_definition" || temp.type === "function_declaration")) {
1323
- if (capture.name === "name") {
1324
- temp = temp.parent;
1325
- }
1326
- }
1327
1660
  let isDirect = true;
1328
1661
  while (temp && temp.id !== scopeNode.id) {
1329
- if (temp.type === "function_definition" || temp.type === "function_declaration") {
1662
+ const boundaryTypes = SCOPE_BOUNDARY_TYPES[langKey];
1663
+ if (boundaryTypes?.has(temp.type)) {
1330
1664
  isDirect = false;
1331
1665
  break;
1332
1666
  }
1333
1667
  temp = temp.parent;
1334
1668
  }
1669
+ if (capture.name === "name") {
1670
+ const boundaryTypes = SCOPE_BOUNDARY_TYPES[langKey];
1671
+ if (boundaryTypes?.has(defNode.parent?.type ?? "")) {
1672
+ temp = defNode.parent?.parent ?? null;
1673
+ isDirect = true;
1674
+ while (temp && temp.id !== scopeNode.id) {
1675
+ if (boundaryTypes?.has(temp.type)) {
1676
+ isDirect = false;
1677
+ break;
1678
+ }
1679
+ temp = temp.parent;
1680
+ }
1681
+ }
1682
+ }
1335
1683
  if (isDirect) {
1336
1684
  names.add(defNode.text);
1337
1685
  }
1338
1686
  }
1687
+ if (i === 0 && scopeNode.type === "statement_block") {
1688
+ const boundaryTypes = SCOPE_BOUNDARY_TYPES[langKey];
1689
+ const parent = scopeNode.parent;
1690
+ if (parent && boundaryTypes?.has(parent.type)) {
1691
+ const parentCaptures = query.captures(parent);
1692
+ for (const capture of parentCaptures) {
1693
+ if (capture.name === "param") {
1694
+ names.add(capture.node.text);
1695
+ }
1696
+ }
1697
+ }
1698
+ if (parent && (parent.type === "for_in_statement" || parent.type === "for_statement")) {
1699
+ const parentCaptures = query.captures(parent);
1700
+ for (const capture of parentCaptures) {
1701
+ if (capture.name === "var") {
1702
+ names.add(capture.node.text);
1703
+ }
1704
+ }
1705
+ }
1706
+ }
1339
1707
  if (i === 0) {
1340
1708
  result.locals = Array.from(names);
1341
1709
  } else {
@@ -1345,6 +1713,7 @@ var ScopeAnalyzer = class {
1345
1713
  }
1346
1714
  }
1347
1715
  result.closure = Array.from(new Set(result.closure));
1716
+ SCOPE_CACHE.set(cacheKey, { value: result, expiresAt: now + SCOPE_CACHE_TTL_MS });
1348
1717
  return result;
1349
1718
  }
1350
1719
  };
@@ -1383,6 +1752,135 @@ ${formatTreeSitterSymbols(symbols)}` : "No symbols found.";
1383
1752
  fileCount: symbols.length > 0 ? 1 : 0
1384
1753
  };
1385
1754
  }
1755
+ var fastPathFailures = /* @__PURE__ */ new Map();
1756
+ var fastPathLastFallbackReason = /* @__PURE__ */ new Map();
1757
+ var scopeLastFallbackReason = /* @__PURE__ */ new Map();
1758
+ var scopeStrategyUsed = /* @__PURE__ */ new Map();
1759
+ var operationFallbackReasons = /* @__PURE__ */ new Map();
1760
+ var LSP_FALLBACK_BOUNDARY = /* @__PURE__ */ new Set(["documentSymbol"]);
1761
+ var FALLBACK_TOGGLES = [
1762
+ { operation: "diagnostics", env: "PYB_LSP_FALLBACK_DIAGNOSTICS", defaultEnabled: false },
1763
+ { operation: "findReferences", env: "PYB_LSP_FALLBACK_REFERENCES", defaultEnabled: false },
1764
+ {
1765
+ operation: "prepareCallHierarchy",
1766
+ env: "PYB_LSP_FALLBACK_CALL_HIERARCHY",
1767
+ defaultEnabled: false
1768
+ },
1769
+ {
1770
+ operation: "incomingCalls",
1771
+ env: "PYB_LSP_FALLBACK_CALL_HIERARCHY",
1772
+ defaultEnabled: false
1773
+ },
1774
+ {
1775
+ operation: "outgoingCalls",
1776
+ env: "PYB_LSP_FALLBACK_CALL_HIERARCHY",
1777
+ defaultEnabled: false
1778
+ }
1779
+ ];
1780
+ function isFallbackToggleEnabled(envKey, defaultEnabled) {
1781
+ const raw = String(process.env[envKey] ?? "").trim().toLowerCase();
1782
+ if (!raw) return defaultEnabled;
1783
+ if (raw === "0" || raw === "false" || raw === "off" || raw === "no") return false;
1784
+ if (raw === "1" || raw === "true" || raw === "on" || raw === "yes") return true;
1785
+ return defaultEnabled;
1786
+ }
1787
+ function refreshFallbackBoundaryFromEnv() {
1788
+ for (const toggle of FALLBACK_TOGGLES) {
1789
+ if (isFallbackToggleEnabled(toggle.env, toggle.defaultEnabled)) {
1790
+ LSP_FALLBACK_BOUNDARY.add(toggle.operation);
1791
+ } else {
1792
+ LSP_FALLBACK_BOUNDARY.delete(toggle.operation);
1793
+ }
1794
+ }
1795
+ }
1796
+ refreshFallbackBoundaryFromEnv();
1797
+ var FAST_PATH_CONFIG = {
1798
+ exception: { windowMs: 5 * 60 * 1e3, threshold: 3 },
1799
+ empty: { windowMs: 60 * 1e3, threshold: 2 },
1800
+ timeout: { windowMs: 30 * 1e3, threshold: 1 }
1801
+ };
1802
+ var FAST_PATH_TIMEOUT_MS = 500;
1803
+ var FAST_PATH_CACHE_TTL_MS = 30 * 1e3;
1804
+ var fastPathSymbolCache = /* @__PURE__ */ new Map();
1805
+ function recordFastPathFailure(filePath, reason, now) {
1806
+ let perFile = fastPathFailures.get(filePath);
1807
+ if (!perFile) {
1808
+ perFile = /* @__PURE__ */ new Map();
1809
+ fastPathFailures.set(filePath, perFile);
1810
+ }
1811
+ const config = FAST_PATH_CONFIG[reason];
1812
+ const existing = perFile.get(reason);
1813
+ if (!existing || now - existing.firstAt > config.windowMs) {
1814
+ perFile.set(reason, { count: 1, firstAt: now, lastAt: now });
1815
+ return;
1816
+ }
1817
+ existing.count += 1;
1818
+ existing.lastAt = now;
1819
+ }
1820
+ function clearFastPathFailures(filePath) {
1821
+ fastPathFailures.delete(filePath);
1822
+ fastPathLastFallbackReason.delete(filePath);
1823
+ }
1824
+ function recordOperationFallback(operation, filePath, reason) {
1825
+ let perOperation = operationFallbackReasons.get(operation);
1826
+ if (!perOperation) {
1827
+ perOperation = /* @__PURE__ */ new Map();
1828
+ operationFallbackReasons.set(operation, perOperation);
1829
+ }
1830
+ perOperation.set(filePath, reason);
1831
+ }
1832
+ function getOperationFallbackSnapshot(operation) {
1833
+ const entries = operationFallbackReasons.get(operation);
1834
+ if (!entries) return [];
1835
+ return Array.from(entries.entries()).map(([filePath, reason]) => ({
1836
+ filePath,
1837
+ reason
1838
+ }));
1839
+ }
1840
+ function computeContentHash(content) {
1841
+ return createHash2("sha256").update(content).digest("hex");
1842
+ }
1843
+ function getCachedFastPathSymbols(filePath, hash, now) {
1844
+ const cached = fastPathSymbolCache.get(filePath);
1845
+ if (!cached) return null;
1846
+ if (cached.hash !== hash || now >= cached.expiresAt) {
1847
+ fastPathSymbolCache.delete(filePath);
1848
+ return null;
1849
+ }
1850
+ return cached.symbols;
1851
+ }
1852
+ function setCachedFastPathSymbols(filePath, hash, symbols, now) {
1853
+ fastPathSymbolCache.set(filePath, {
1854
+ hash,
1855
+ symbols,
1856
+ expiresAt: now + FAST_PATH_CACHE_TTL_MS
1857
+ });
1858
+ }
1859
+ function shouldSkipFastPath(filePath, now) {
1860
+ const perFile = fastPathFailures.get(filePath);
1861
+ if (!perFile) return null;
1862
+ for (const [reason, state] of perFile.entries()) {
1863
+ const config = FAST_PATH_CONFIG[reason];
1864
+ if (now - state.firstAt <= config.windowMs && state.count >= config.threshold) {
1865
+ return reason;
1866
+ }
1867
+ }
1868
+ return null;
1869
+ }
1870
+ function withTimeout(promise, timeoutMs) {
1871
+ return new Promise((resolve, reject) => {
1872
+ const timer = setTimeout(() => {
1873
+ reject(new Error("Tree-sitter timeout"));
1874
+ }, timeoutMs);
1875
+ promise.then((result) => {
1876
+ clearTimeout(timer);
1877
+ resolve(result);
1878
+ }).catch((error) => {
1879
+ clearTimeout(timer);
1880
+ reject(error);
1881
+ });
1882
+ });
1883
+ }
1386
1884
  function normalizeWorkspaceSymbol(sym) {
1387
1885
  if (!sym || typeof sym !== "object") return null;
1388
1886
  const range = sym.range ?? sym.location?.range;
@@ -1390,6 +1888,97 @@ function normalizeWorkspaceSymbol(sym) {
1390
1888
  const uri = sym.uri ?? sym.location?.uri;
1391
1889
  return { ...sym, range, uri };
1392
1890
  }
1891
+ function isIdentifierChar(char) {
1892
+ return /[A-Za-z0-9_$']/.test(char);
1893
+ }
1894
+ function extractTokenAtPosition(lines, zeroBasedLine, zeroBasedCharacter) {
1895
+ if (zeroBasedLine < 0 || zeroBasedLine >= lines.length) return null;
1896
+ const line = lines[zeroBasedLine] ?? "";
1897
+ if (zeroBasedCharacter < 0 || zeroBasedCharacter > line.length) return null;
1898
+ const tokenRe = /[\w$'!]+|[+\-*/%&|^~<>=]+/g;
1899
+ let match;
1900
+ while ((match = tokenRe.exec(line)) !== null) {
1901
+ const start = match.index;
1902
+ const end = start + match[0].length;
1903
+ if (zeroBasedCharacter >= start && zeroBasedCharacter < end) {
1904
+ const token = match[0];
1905
+ return token.length > 50 ? token.slice(0, 50) : token;
1906
+ }
1907
+ }
1908
+ return null;
1909
+ }
1910
+ function findTokenOccurrencesInLines(token, lines) {
1911
+ const results = [];
1912
+ if (!token) return results;
1913
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
1914
+ const line = lines[lineIndex];
1915
+ let fromIndex = 0;
1916
+ while (fromIndex < line.length) {
1917
+ const index = line.indexOf(token, fromIndex);
1918
+ if (index === -1) break;
1919
+ const before = index > 0 ? line[index - 1] : "";
1920
+ const afterIndex = index + token.length;
1921
+ const after = afterIndex < line.length ? line[afterIndex] : "";
1922
+ if ((!before || !isIdentifierChar(before)) && (!after || !isIdentifierChar(after))) {
1923
+ results.push({ line: lineIndex, start: index, end: afterIndex });
1924
+ }
1925
+ fromIndex = index + token.length;
1926
+ }
1927
+ }
1928
+ return results;
1929
+ }
1930
+ function findSymbolAtPosition(symbols, line, character) {
1931
+ let matched = null;
1932
+ const visit = (node) => {
1933
+ if (!node?.range) return;
1934
+ const start = node.range.start;
1935
+ const end = node.range.end;
1936
+ const within = line > start.line || line === start.line && character >= start.character ? line < end.line || line === end.line && character <= end.character : false;
1937
+ if (within) {
1938
+ matched = node;
1939
+ if (Array.isArray(node.children)) {
1940
+ for (const child of node.children) {
1941
+ visit(child);
1942
+ }
1943
+ }
1944
+ }
1945
+ };
1946
+ for (const sym of symbols) visit(sym);
1947
+ return matched;
1948
+ }
1949
+ async function getTreeSitterDiagnostics(filePath) {
1950
+ const lang = ParserRegistry.getLanguage(filePath);
1951
+ if (!lang) return [];
1952
+ const parser = await loadLanguage(lang);
1953
+ const content = await readFile4(filePath, "utf-8");
1954
+ const tree = parser.parse(content);
1955
+ if (!tree) return [];
1956
+ const diagnostics = [];
1957
+ const seen = /* @__PURE__ */ new Set();
1958
+ const visit = (node) => {
1959
+ const shouldReport = node.type === "ERROR" || node.isMissing || node.hasError && node.childCount === 0;
1960
+ if (shouldReport) {
1961
+ const range = {
1962
+ start: { line: node.startPosition.row, character: node.startPosition.column },
1963
+ end: { line: node.endPosition.row, character: node.endPosition.column }
1964
+ };
1965
+ const key = `${range.start.line}:${range.start.character}:${range.end.line}:${range.end.character}:${node.type}`;
1966
+ if (!seen.has(key)) {
1967
+ diagnostics.push({
1968
+ range,
1969
+ message: `Syntax error near ${node.type}`,
1970
+ severity: 1
1971
+ });
1972
+ seen.add(key);
1973
+ }
1974
+ }
1975
+ for (const child of node.children) {
1976
+ visit(child);
1977
+ }
1978
+ };
1979
+ visit(tree.rootNode);
1980
+ return diagnostics;
1981
+ }
1393
1982
  function sortWorkspaceSymbols(symbols) {
1394
1983
  return [...symbols].sort((a, b) => {
1395
1984
  const nameA = String(a?.name ?? "").toLowerCase();
@@ -1406,10 +1995,27 @@ function sortWorkspaceSymbols(symbols) {
1406
1995
  return charA - charB;
1407
1996
  });
1408
1997
  }
1998
+ function resolveScopeStrategy() {
1999
+ const raw = String(process.env.PYB_GET_SCOPE_STRATEGY ?? "").trim().toLowerCase();
2000
+ if (raw === "lsp") return "lsp";
2001
+ return "tree-sitter";
2002
+ }
2003
+ function getDocumentSymbolFallbackSnapshot() {
2004
+ return Array.from(fastPathLastFallbackReason.entries()).map(([filePath, reason]) => ({
2005
+ filePath,
2006
+ reason
2007
+ }));
2008
+ }
2009
+ function getScopeFallbackSnapshot() {
2010
+ return Array.from(scopeLastFallbackReason.entries()).map(([filePath, reason]) => ({
2011
+ filePath,
2012
+ reason
2013
+ }));
2014
+ }
1409
2015
  function serveStatusWithProvider(statusProvider, options) {
1410
2016
  const host = options?.host ?? "127.0.0.1";
1411
2017
  const port = typeof options?.port === "number" ? options.port : 7337;
1412
- const path3 = options?.path ?? "/lsp/status";
2018
+ const path4 = options?.path ?? "/lsp/status";
1413
2019
  const allowCors = options?.allowCors ?? true;
1414
2020
  const corsHeaders = allowCors ? {
1415
2021
  "Access-Control-Allow-Origin": "*",
@@ -1419,7 +2025,7 @@ function serveStatusWithProvider(statusProvider, options) {
1419
2025
  const server = http.createServer((req, res) => {
1420
2026
  const method = req.method ?? "GET";
1421
2027
  const url = new URL(req.url ?? "/", `http://${host}`);
1422
- if (url.pathname !== path3) {
2028
+ if (url.pathname !== path4) {
1423
2029
  res.writeHead(404, corsHeaders);
1424
2030
  res.end("Not Found");
1425
2031
  return;
@@ -1439,66 +2045,103 @@ function serveStatusWithProvider(statusProvider, options) {
1439
2045
  res.end(JSON.stringify(status, null, 2));
1440
2046
  });
1441
2047
  server.port = port;
1442
- server.listen(port, host);
2048
+ server.listen(port, host, () => {
2049
+ const address = server.address();
2050
+ if (address && typeof address === "object") {
2051
+ ;
2052
+ server.port = address.port;
2053
+ }
2054
+ });
1443
2055
  return server;
1444
2056
  }
1445
2057
  var LspAPI = {
1446
2058
  async run(input) {
1447
2059
  const rootPath = input.rootPath ?? getCwd();
1448
- const client = await LspClientManager.getInstance().getClient(
2060
+ const manager = LspClientManager.getInstance();
2061
+ const client = await manager.getClient(
1449
2062
  input.filePath,
1450
2063
  rootPath
1451
2064
  );
2065
+ const ext = extname2(input.filePath);
2066
+ const fallbackServer = LspServerRegistry.getInstance().getServerForExtension(ext);
2067
+ const serverId = client ? client.serverId ?? fallbackServer?.id ?? "unknown" : fallbackServer?.id ?? "unknown";
2068
+ const recordAttempt = (operation) => {
2069
+ manager.recordRequestAttempt(serverId, operation);
2070
+ };
2071
+ const recordSuccess = (operation, startedAt) => {
2072
+ manager.recordRequestSuccess(serverId, operation, Date.now() - startedAt);
2073
+ };
2074
+ const recordFailure = (operation, reason) => {
2075
+ manager.recordRequestFailure(serverId, operation, reason);
2076
+ };
1452
2077
  if (client) {
1453
2078
  try {
1454
2079
  switch (input.operation) {
1455
2080
  case "goToDefinition": {
1456
2081
  if (input.line === void 0 || input.character === void 0) {
2082
+ recordAttempt("goToDefinition");
2083
+ recordFailure("goToDefinition", "invalid");
1457
2084
  return {
1458
2085
  formatted: "Error performing goToDefinition: Missing line/character",
1459
2086
  resultCount: 0,
1460
2087
  fileCount: 0
1461
2088
  };
1462
2089
  }
2090
+ recordAttempt("goToDefinition");
2091
+ const startedAt = Date.now();
1463
2092
  const result = await client.goToDefinition(
1464
2093
  input.filePath,
1465
2094
  input.line,
1466
2095
  input.character
1467
2096
  );
2097
+ recordSuccess("goToDefinition", startedAt);
1468
2098
  return formatGenericDefinitionResult(result);
1469
2099
  }
1470
2100
  case "findReferences": {
1471
2101
  if (input.line === void 0 || input.character === void 0) {
2102
+ recordAttempt("findReferences");
2103
+ recordFailure("findReferences", "invalid");
1472
2104
  return {
1473
2105
  formatted: "Error performing findReferences: Missing line/character",
1474
2106
  resultCount: 0,
1475
2107
  fileCount: 0
1476
2108
  };
1477
2109
  }
2110
+ recordAttempt("findReferences");
2111
+ const startedAt = Date.now();
1478
2112
  const result = await client.findReferences(
1479
2113
  input.filePath,
1480
2114
  input.line,
1481
2115
  input.character
1482
2116
  );
2117
+ recordSuccess("findReferences", startedAt);
1483
2118
  return formatGenericReferencesResult(result);
1484
2119
  }
1485
2120
  case "hover": {
1486
2121
  if (input.line === void 0 || input.character === void 0) {
2122
+ recordAttempt("hover");
2123
+ recordFailure("hover", "invalid");
1487
2124
  return {
1488
2125
  formatted: "Error performing hover: Missing line/character",
1489
2126
  resultCount: 0,
1490
2127
  fileCount: 0
1491
2128
  };
1492
2129
  }
2130
+ recordAttempt("hover");
2131
+ const startedAt = Date.now();
1493
2132
  const result = await client.hover(
1494
2133
  input.filePath,
1495
2134
  input.line,
1496
2135
  input.character
1497
2136
  );
2137
+ recordSuccess("hover", startedAt);
1498
2138
  return formatGenericHoverResult(result, input.line, input.character);
1499
2139
  }
1500
2140
  case "documentSymbol": {
2141
+ recordAttempt("documentSymbol");
2142
+ const startedAt = Date.now();
1501
2143
  const result = await client.documentSymbol(input.filePath);
2144
+ recordSuccess("documentSymbol", startedAt);
1502
2145
  return formatGenericDocumentSymbolResult(result);
1503
2146
  }
1504
2147
  case "workspaceSymbol": {
@@ -1511,44 +2154,58 @@ var LspAPI = {
1511
2154
  }
1512
2155
  case "goToImplementation": {
1513
2156
  if (input.line === void 0 || input.character === void 0) {
2157
+ recordAttempt("goToImplementation");
2158
+ recordFailure("goToImplementation", "invalid");
1514
2159
  return {
1515
2160
  formatted: "Error performing goToImplementation: Missing line/character",
1516
2161
  resultCount: 0,
1517
2162
  fileCount: 0
1518
2163
  };
1519
2164
  }
2165
+ recordAttempt("goToImplementation");
2166
+ const startedAt = Date.now();
1520
2167
  const result = await client.goToImplementation(
1521
2168
  input.filePath,
1522
2169
  input.line,
1523
2170
  input.character
1524
2171
  );
2172
+ recordSuccess("goToImplementation", startedAt);
1525
2173
  return formatGenericDefinitionResult(result);
1526
2174
  }
1527
2175
  case "prepareCallHierarchy": {
1528
2176
  if (input.line === void 0 || input.character === void 0) {
2177
+ recordAttempt("prepareCallHierarchy");
2178
+ recordFailure("prepareCallHierarchy", "invalid");
1529
2179
  return {
1530
2180
  formatted: "Error performing prepareCallHierarchy: Missing line/character",
1531
2181
  resultCount: 0,
1532
2182
  fileCount: 0
1533
2183
  };
1534
2184
  }
2185
+ recordAttempt("prepareCallHierarchy");
2186
+ const startedAt = Date.now();
1535
2187
  const result = await client.prepareCallHierarchy(
1536
2188
  input.filePath,
1537
2189
  input.line,
1538
2190
  input.character
1539
2191
  );
2192
+ recordSuccess("prepareCallHierarchy", startedAt);
1540
2193
  return formatGenericCallHierarchyItemsResult(
1541
2194
  Array.isArray(result) ? result : []
1542
2195
  );
1543
2196
  }
1544
2197
  case "incomingCalls": {
1545
2198
  if (input.line === void 0 || input.character === void 0) {
2199
+ recordAttempt("incomingCalls");
2200
+ recordFailure("incomingCalls", "invalid");
1546
2201
  return {
1547
2202
  formatted: "Error performing incomingCalls: Missing line/character",
1548
2203
  resultCount: 0,
1549
2204
  fileCount: 0
1550
2205
  };
1551
2206
  }
2207
+ recordAttempt("incomingCalls");
2208
+ const startedAt = Date.now();
1552
2209
  const items = await client.prepareCallHierarchy(
1553
2210
  input.filePath,
1554
2211
  input.line,
@@ -1560,16 +2217,21 @@ var LspAPI = {
1560
2217
  )
1561
2218
  );
1562
2219
  const calls = callResults.flat().filter(Boolean);
2220
+ recordSuccess("incomingCalls", startedAt);
1563
2221
  return formatGenericIncomingCallsResult(calls);
1564
2222
  }
1565
2223
  case "outgoingCalls": {
1566
2224
  if (input.line === void 0 || input.character === void 0) {
2225
+ recordAttempt("outgoingCalls");
2226
+ recordFailure("outgoingCalls", "invalid");
1567
2227
  return {
1568
2228
  formatted: "Error performing outgoingCalls: Missing line/character",
1569
2229
  resultCount: 0,
1570
2230
  fileCount: 0
1571
2231
  };
1572
2232
  }
2233
+ recordAttempt("outgoingCalls");
2234
+ const startedAt = Date.now();
1573
2235
  const items = await client.prepareCallHierarchy(
1574
2236
  input.filePath,
1575
2237
  input.line,
@@ -1581,9 +2243,12 @@ var LspAPI = {
1581
2243
  )
1582
2244
  );
1583
2245
  const calls = callResults.flat().filter(Boolean);
2246
+ recordSuccess("outgoingCalls", startedAt);
1584
2247
  return formatGenericOutgoingCallsResult(calls);
1585
2248
  }
1586
2249
  case "diagnostics": {
2250
+ recordAttempt("diagnostics");
2251
+ const startedAt = Date.now();
1587
2252
  if (input.waitForDiagnostics) {
1588
2253
  await client.waitForReadiness(
1589
2254
  input.filePath,
@@ -1591,6 +2256,7 @@ var LspAPI = {
1591
2256
  );
1592
2257
  }
1593
2258
  const diagnostics = client.getDiagnostics(input.filePath);
2259
+ recordSuccess("diagnostics", startedAt);
1594
2260
  return formatGenericDiagnosticsResult(diagnostics);
1595
2261
  }
1596
2262
  default: {
@@ -1602,6 +2268,8 @@ var LspAPI = {
1602
2268
  }
1603
2269
  }
1604
2270
  } catch (error) {
2271
+ recordAttempt(input.operation);
2272
+ recordFailure(input.operation, "error");
1605
2273
  const message = error instanceof Error ? error.message : String(error);
1606
2274
  return {
1607
2275
  formatted: `Error performing ${input.operation} with generic client: ${message}`,
@@ -1610,7 +2278,112 @@ var LspAPI = {
1610
2278
  };
1611
2279
  }
1612
2280
  }
1613
- const ext = extname2(input.filePath);
2281
+ const fallbackOperation = input.operation;
2282
+ recordAttempt(input.operation);
2283
+ recordFailure(input.operation, "unavailable");
2284
+ if (LSP_FALLBACK_BOUNDARY.has(fallbackOperation)) {
2285
+ const lang = ParserRegistry.getLanguage(input.filePath);
2286
+ if (!lang) {
2287
+ recordOperationFallback(fallbackOperation, input.filePath, "unsupported");
2288
+ } else {
2289
+ recordOperationFallback(fallbackOperation, input.filePath, "lsp-unavailable");
2290
+ if (fallbackOperation === "diagnostics") {
2291
+ try {
2292
+ const diagnostics = await getTreeSitterDiagnostics(input.filePath);
2293
+ const formatted = formatGenericDiagnosticsResult(diagnostics);
2294
+ return {
2295
+ formatted: `Diagnostics fallback: ${formatted.formatted}`,
2296
+ resultCount: formatted.resultCount,
2297
+ fileCount: formatted.fileCount
2298
+ };
2299
+ } catch {
2300
+ return {
2301
+ formatted: `Diagnostics fallback: LSP unavailable for ${ext}. Static analysis not available.`,
2302
+ resultCount: 0,
2303
+ fileCount: 0
2304
+ };
2305
+ }
2306
+ }
2307
+ if (fallbackOperation === "findReferences") {
2308
+ if (input.line !== void 0 && input.character !== void 0) {
2309
+ try {
2310
+ const content = await readFile4(input.filePath, "utf-8");
2311
+ const lines = content.split("\n");
2312
+ const token = extractTokenAtPosition(
2313
+ lines,
2314
+ input.line - 1,
2315
+ input.character - 1
2316
+ );
2317
+ if (token) {
2318
+ const occurrences = findTokenOccurrencesInLines(token, lines);
2319
+ const uri = normalizeUri(input.filePath);
2320
+ const refs = occurrences.map((entry) => ({
2321
+ uri,
2322
+ range: {
2323
+ start: { line: entry.line, character: entry.start },
2324
+ end: { line: entry.line, character: entry.end }
2325
+ }
2326
+ }));
2327
+ if (refs.length > 0) {
2328
+ const formatted = formatGenericReferencesResult(refs);
2329
+ return {
2330
+ formatted: `References fallback: ${formatted.formatted}`,
2331
+ resultCount: formatted.resultCount,
2332
+ fileCount: formatted.fileCount
2333
+ };
2334
+ }
2335
+ }
2336
+ } catch {
2337
+ }
2338
+ }
2339
+ return {
2340
+ formatted: `References fallback: LSP unavailable for ${ext}. Returning 0 references.`,
2341
+ resultCount: 0,
2342
+ fileCount: 0
2343
+ };
2344
+ }
2345
+ if (fallbackOperation === "prepareCallHierarchy") {
2346
+ if (input.line !== void 0 && input.character !== void 0) {
2347
+ try {
2348
+ const symbols = await TreeSitterLspAdapter.getDocumentSymbols(input.filePath);
2349
+ const symbol = findSymbolAtPosition(
2350
+ symbols,
2351
+ input.line - 1,
2352
+ input.character - 1
2353
+ );
2354
+ if (symbol) {
2355
+ const item = {
2356
+ name: symbol.name,
2357
+ kind: symbol.kind,
2358
+ uri: normalizeUri(input.filePath),
2359
+ range: symbol.range,
2360
+ selectionRange: symbol.range
2361
+ };
2362
+ const formatted = formatGenericCallHierarchyItemsResult([item]);
2363
+ return {
2364
+ formatted: `Call hierarchy fallback: ${formatted.formatted}`,
2365
+ resultCount: formatted.resultCount,
2366
+ fileCount: formatted.fileCount
2367
+ };
2368
+ }
2369
+ } catch {
2370
+ }
2371
+ }
2372
+ return {
2373
+ formatted: `Call hierarchy fallback: LSP unavailable for ${ext}. Returning 0 items.`,
2374
+ resultCount: 0,
2375
+ fileCount: 0
2376
+ };
2377
+ }
2378
+ if (fallbackOperation === "incomingCalls" || fallbackOperation === "outgoingCalls") {
2379
+ return {
2380
+ formatted: `Call hierarchy fallback: LSP unavailable for ${ext}. Returning 0 items.`,
2381
+ resultCount: 0,
2382
+ fileCount: 0
2383
+ };
2384
+ }
2385
+ }
2386
+ }
1614
2387
  return {
1615
2388
  formatted: `No LSP server available for file type: ${ext}`,
1616
2389
  resultCount: 0,
@@ -1623,7 +2396,7 @@ var LspAPI = {
1623
2396
  async touchFile(filePath, options = {}) {
1624
2397
  const client = await LspClientManager.getInstance().getClientForFile(filePath);
1625
2398
  if (!client) return false;
1626
- const content = await readFile3(filePath, "utf-8");
2399
+ const content = await readFile4(filePath, "utf-8");
1627
2400
  const languageId = getLanguageId(extname2(filePath));
1628
2401
  await client.didOpen(filePath, content, languageId);
1629
2402
  if (options.wait) {
@@ -1687,12 +2460,132 @@ var LspFacade = {
1687
2460
  async run(input) {
1688
2461
  const rootPath = input.rootPath ?? getCwd();
1689
2462
  if (input.operation === "documentSymbol") {
1690
- const lang = ParserRegistry.getLanguage(input.filePath);
1691
- if (lang) {
2463
+ const flag = String(process.env.PYB_LSP_STRICT_FIRST ?? "").trim().toLowerCase();
2464
+ const lspFirst = flag === "1" || flag === "true" || flag === "on" || flag === "yes";
2465
+ if (lspFirst) {
1692
2466
  try {
1693
- const symbols = await TreeSitterLspAdapter.getDocumentSymbols(input.filePath);
1694
- return formatTreeSitterDocumentSymbolResult(symbols);
2467
+ const lspSymbols = await LspAPI.documentSymbolRaw(input.filePath, rootPath);
2468
+ if (lspSymbols && Array.isArray(lspSymbols) && lspSymbols.length > 0) {
2469
+ return formatGenericDocumentSymbolResult(lspSymbols);
2470
+ }
2471
+ const now = Date.now();
2472
+ const lang = ParserRegistry.getLanguage(input.filePath);
2473
+ if (!lang) {
2474
+ fastPathLastFallbackReason.set(input.filePath, "unsupported");
2475
+ } else {
2476
+ let contentHash = null;
2477
+ try {
2478
+ const content = await readFile4(input.filePath, "utf-8");
2479
+ contentHash = computeContentHash(content);
2480
+ const cached = getCachedFastPathSymbols(input.filePath, contentHash, now);
2481
+ if (cached) {
2482
+ clearFastPathFailures(input.filePath);
2483
+ return formatTreeSitterDocumentSymbolResult(cached);
2484
+ }
2485
+ } catch {
2486
+ }
2487
+ try {
2488
+ const symbols = await withTimeout(
2489
+ TreeSitterLspAdapter.getDocumentSymbols(input.filePath),
2490
+ FAST_PATH_TIMEOUT_MS
2491
+ );
2492
+ if (symbols.length > 0) {
2493
+ clearFastPathFailures(input.filePath);
2494
+ if (contentHash) {
2495
+ setCachedFastPathSymbols(input.filePath, contentHash, symbols, now);
2496
+ }
2497
+ return formatTreeSitterDocumentSymbolResult(symbols);
2498
+ }
2499
+ recordFastPathFailure(input.filePath, "empty", now);
2500
+ fastPathLastFallbackReason.set(input.filePath, "empty");
2501
+ } catch (error) {
2502
+ const message = error instanceof Error ? error.message : String(error);
2503
+ const reason = message.toLowerCase().includes("timeout") ? "timeout" : "exception";
2504
+ recordFastPathFailure(input.filePath, reason, now);
2505
+ fastPathLastFallbackReason.set(input.filePath, reason);
2506
+ }
2507
+ }
1695
2508
  } catch {
2509
+ const now = Date.now();
2510
+ const lang = ParserRegistry.getLanguage(input.filePath);
2511
+ if (!lang) {
2512
+ fastPathLastFallbackReason.set(input.filePath, "unsupported");
2513
+ } else {
2514
+ let contentHash = null;
2515
+ try {
2516
+ const content = await readFile4(input.filePath, "utf-8");
2517
+ contentHash = computeContentHash(content);
2518
+ const cached = getCachedFastPathSymbols(input.filePath, contentHash, now);
2519
+ if (cached) {
2520
+ clearFastPathFailures(input.filePath);
2521
+ return formatTreeSitterDocumentSymbolResult(cached);
2522
+ }
2523
+ } catch {
2524
+ }
2525
+ try {
2526
+ const symbols = await withTimeout(
2527
+ TreeSitterLspAdapter.getDocumentSymbols(input.filePath),
2528
+ FAST_PATH_TIMEOUT_MS
2529
+ );
2530
+ if (symbols.length > 0) {
2531
+ clearFastPathFailures(input.filePath);
2532
+ if (contentHash) {
2533
+ setCachedFastPathSymbols(input.filePath, contentHash, symbols, now);
2534
+ }
2535
+ return formatTreeSitterDocumentSymbolResult(symbols);
2536
+ }
2537
+ recordFastPathFailure(input.filePath, "empty", now);
2538
+ fastPathLastFallbackReason.set(input.filePath, "empty");
2539
+ } catch (error) {
2540
+ const message = error instanceof Error ? error.message : String(error);
2541
+ const reason = message.toLowerCase().includes("timeout") ? "timeout" : "exception";
2542
+ recordFastPathFailure(input.filePath, reason, now);
2543
+ fastPathLastFallbackReason.set(input.filePath, reason);
2544
+ }
2545
+ }
2546
+ }
2547
+ } else {
2548
+ const lang = ParserRegistry.getLanguage(input.filePath);
2549
+ const now = Date.now();
2550
+ if (!lang) {
2551
+ fastPathLastFallbackReason.set(input.filePath, "unsupported");
2552
+ } else {
2553
+ const skipReason = shouldSkipFastPath(input.filePath, now);
2554
+ if (skipReason) {
2555
+ fastPathLastFallbackReason.set(input.filePath, skipReason);
2556
+ } else {
2557
+ let contentHash = null;
2558
+ try {
2559
+ const content = await readFile4(input.filePath, "utf-8");
2560
+ contentHash = computeContentHash(content);
2561
+ const cached = getCachedFastPathSymbols(input.filePath, contentHash, now);
2562
+ if (cached) {
2563
+ clearFastPathFailures(input.filePath);
2564
+ return formatTreeSitterDocumentSymbolResult(cached);
2565
+ }
2566
+ } catch {
2567
+ }
2568
+ try {
2569
+ const symbols = await withTimeout(
2570
+ TreeSitterLspAdapter.getDocumentSymbols(input.filePath),
2571
+ FAST_PATH_TIMEOUT_MS
2572
+ );
2573
+ if (symbols.length > 0) {
2574
+ clearFastPathFailures(input.filePath);
2575
+ if (contentHash) {
2576
+ setCachedFastPathSymbols(input.filePath, contentHash, symbols, now);
2577
+ }
2578
+ return formatTreeSitterDocumentSymbolResult(symbols);
2579
+ }
2580
+ recordFastPathFailure(input.filePath, "empty", now);
2581
+ fastPathLastFallbackReason.set(input.filePath, "empty");
2582
+ } catch (error) {
2583
+ const message = error instanceof Error ? error.message : String(error);
2584
+ const reason = message.toLowerCase().includes("timeout") ? "timeout" : "exception";
2585
+ recordFastPathFailure(input.filePath, reason, now);
2586
+ fastPathLastFallbackReason.set(input.filePath, reason);
2587
+ }
2588
+ }
1696
2589
  }
1697
2590
  }
1698
2591
  }
@@ -1713,7 +2606,13 @@ var LspFacade = {
1713
2606
  };
1714
2607
  }
1715
2608
  try {
1716
- const code = await readFile3(input.filePath, "utf-8");
2609
+ const strategy = resolveScopeStrategy();
2610
+ if (strategy === "lsp") {
2611
+ scopeLastFallbackReason.set(input.filePath, "lsp-unavailable");
2612
+ }
2613
+ const effectiveStrategy = strategy === "lsp" ? "tree-sitter" : strategy;
2614
+ scopeStrategyUsed.set(input.filePath, effectiveStrategy);
2615
+ const code = await readFile4(input.filePath, "utf-8");
1717
2616
  const scope = await ScopeAnalyzer.getScope(input.filePath, code, {
1718
2617
  row: input.line - 1,
1719
2618
  column: input.character - 1
@@ -1820,9 +2719,50 @@ var LspFacade = {
1820
2719
  return serveStatusWithProvider(() => LspFacade.status(), options);
1821
2720
  },
1822
2721
  status() {
1823
- return LspAPI.status();
2722
+ const base = LspAPI.status();
2723
+ return {
2724
+ ...base,
2725
+ installNotices: getInstallNotices(),
2726
+ fallbacks: {
2727
+ documentSymbol: getDocumentSymbolFallbackSnapshot(),
2728
+ getScope: getScopeFallbackSnapshot(),
2729
+ diagnostics: getOperationFallbackSnapshot("diagnostics"),
2730
+ findReferences: getOperationFallbackSnapshot("findReferences"),
2731
+ prepareCallHierarchy: getOperationFallbackSnapshot("prepareCallHierarchy"),
2732
+ incomingCalls: getOperationFallbackSnapshot("incomingCalls"),
2733
+ outgoingCalls: getOperationFallbackSnapshot("outgoingCalls"),
2734
+ boundary: Array.from(LSP_FALLBACK_BOUNDARY.values())
2735
+ }
2736
+ };
1824
2737
  }
1825
2738
  };
2739
+ function __resetLspFastPathStateForTests() {
2740
+ fastPathFailures.clear();
2741
+ fastPathLastFallbackReason.clear();
2742
+ fastPathSymbolCache.clear();
2743
+ }
2744
+ function __resetLspOperationFallbackForTests() {
2745
+ operationFallbackReasons.clear();
2746
+ }
2747
+ function __getLspFastPathFallbackReasonForTests(filePath) {
2748
+ return fastPathLastFallbackReason.get(filePath) ?? null;
2749
+ }
2750
+ function __resetScopeStateForTests() {
2751
+ scopeLastFallbackReason.clear();
2752
+ scopeStrategyUsed.clear();
2753
+ }
2754
+ function __getScopeFallbackReasonForTests(filePath) {
2755
+ return scopeLastFallbackReason.get(filePath) ?? null;
2756
+ }
2757
+ function __getScopeStrategyForTests(filePath) {
2758
+ return scopeStrategyUsed.get(filePath) ?? null;
2759
+ }
2760
+ function __getFallbackBoundaryForTests() {
2761
+ return Array.from(LSP_FALLBACK_BOUNDARY.values());
2762
+ }
2763
+ function __refreshLspFallbackBoundaryForTests() {
2764
+ refreshFallbackBoundaryFromEnv();
2765
+ }
1826
2766
 
1827
2767
  export {
1828
2768
  formatDiagnosticsPretty,
@@ -1830,5 +2770,13 @@ export {
1830
2770
  loadLanguage,
1831
2771
  initParser,
1832
2772
  LspAPI,
1833
- LspFacade
2773
+ LspFacade,
2774
+ __resetLspFastPathStateForTests,
2775
+ __resetLspOperationFallbackForTests,
2776
+ __getLspFastPathFallbackReasonForTests,
2777
+ __resetScopeStateForTests,
2778
+ __getScopeFallbackReasonForTests,
2779
+ __getScopeStrategyForTests,
2780
+ __getFallbackBoundaryForTests,
2781
+ __refreshLspFallbackBoundaryForTests
1834
2782
  };