pybao-cli 1.3.97 → 1.3.99

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 (152) hide show
  1. package/dist/REPL-35WMIAEP.js +47 -0
  2. package/dist/{acp-Y5GVEAK5.js → acp-XZAGDT3N.js} +30 -30
  3. package/dist/{agentsValidate-HVG4G32H.js → agentsValidate-ZPLW7S35.js} +7 -7
  4. package/dist/{ask-Q4D437ZY.js → ask-ROXENA55.js} +31 -31
  5. package/dist/{autoUpdater-2HZ5MXSY.js → autoUpdater-NDX26QV7.js} +3 -3
  6. package/dist/{chunk-DFGDXB5I.js → chunk-2ULPCMQN.js} +2 -2
  7. package/dist/{chunk-VCS4L3LA.js → chunk-2WVYPGFK.js} +2 -2
  8. package/dist/{chunk-D7EHET2A.js → chunk-3HVYGUQ7.js} +3 -3
  9. package/dist/{chunk-4C2BJ5TH.js → chunk-44DDEUXT.js} +488 -696
  10. package/dist/chunk-44DDEUXT.js.map +7 -0
  11. package/dist/{chunk-IGEQR5ET.js → chunk-47PFBKB5.js} +3 -3
  12. package/dist/{chunk-7ZK32QRX.js → chunk-4NRDC2MN.js} +2 -2
  13. package/dist/{chunk-DSHUCMWX.js → chunk-5ON6SGBK.js} +1 -1
  14. package/dist/{chunk-DSHUCMWX.js.map → chunk-5ON6SGBK.js.map} +1 -1
  15. package/dist/{chunk-VU4GCLM2.js → chunk-B6YMKCYD.js} +2 -2
  16. package/dist/{chunk-W7CPW6S2.js → chunk-BXOS6YEB.js} +3 -3
  17. package/dist/{chunk-LGG56SIC.js → chunk-CGHZ2VAY.js} +1 -1
  18. package/dist/{chunk-4UESJIJZ.js → chunk-CMQGXFRW.js} +1 -1
  19. package/dist/{chunk-TQAQR7CB.js → chunk-EY2TENGG.js} +1 -1
  20. package/dist/{chunk-FMZTGW27.js → chunk-HN63ZGCX.js} +4 -4
  21. package/dist/{chunk-5SWRWOXZ.js → chunk-I23P7CAV.js} +3 -3
  22. package/dist/{chunk-SHKXEYZX.js → chunk-JFW7GGFH.js} +162 -53
  23. package/dist/chunk-JFW7GGFH.js.map +7 -0
  24. package/dist/{chunk-JQ7U4FNJ.js → chunk-L5MUGKPW.js} +3 -3
  25. package/dist/{chunk-HZQ5D4KE.js → chunk-M2J4YYEX.js} +428 -500
  26. package/dist/chunk-M2J4YYEX.js.map +7 -0
  27. package/dist/{chunk-T6MZIN3Y.js → chunk-Q6XLF2PE.js} +1 -1
  28. package/dist/{chunk-CJ5J54UV.js → chunk-QX6FKMRR.js} +6 -3
  29. package/dist/chunk-QX6FKMRR.js.map +7 -0
  30. package/dist/{chunk-IBMWS4VP.js → chunk-T4EFAH55.js} +1 -1
  31. package/dist/{chunk-GVQW5V5E.js → chunk-U5J6MTAO.js} +2 -2
  32. package/dist/{chunk-T4CDNQDK.js → chunk-VWENSRU2.js} +4 -7
  33. package/dist/chunk-VWENSRU2.js.map +7 -0
  34. package/dist/{chunk-X7NKBF4S.js → chunk-VYINZ75I.js} +1110 -162
  35. package/dist/chunk-VYINZ75I.js.map +7 -0
  36. package/dist/{chunk-SRPG24JR.js → chunk-W3RWB5JG.js} +9 -10
  37. package/dist/{chunk-SRPG24JR.js.map → chunk-W3RWB5JG.js.map} +2 -2
  38. package/dist/{chunk-ILVR3OKO.js → chunk-WWYDMHKU.js} +1 -1
  39. package/dist/{chunk-BNTZKC47.js → chunk-X6KY4AMW.js} +2 -2
  40. package/dist/{chunk-WHWTXVZ4.js → chunk-XLOFU6ZK.js} +1 -1
  41. package/dist/{chunk-GW4FHFLN.js → chunk-YOK3FML7.js} +1 -1
  42. package/dist/{chunk-T7MGCO5Q.js → chunk-ZFDSP2ES.js} +2 -2
  43. package/dist/{chunk-VBDYRFAI.js → chunk-ZI6CKPRG.js} +3 -6
  44. package/dist/chunk-ZI6CKPRG.js.map +7 -0
  45. package/dist/{cli-YWSRHJXF.js → cli-W6BTK6IG.js} +88 -88
  46. package/dist/commands-OGUECGBR.js +51 -0
  47. package/dist/{config-XEZZSNSO.js → config-MJPE676F.js} +4 -4
  48. package/dist/{context-O7XDPBZM.js → context-XLNMR7UJ.js} +12 -9
  49. package/dist/{customCommands-T227ND56.js → customCommands-NJWIPIRB.js} +4 -4
  50. package/dist/{env-LGLECBD2.js → env-UNJ7A6Y6.js} +2 -2
  51. package/dist/{file-LRWOIEO2.js → file-CYIU2LVL.js} +4 -4
  52. package/dist/index.js +3 -3
  53. package/dist/{llm-KH33MHA2.js → llm-XG5AIU4E.js} +32 -32
  54. package/dist/{llmLazy-QDJLCLFB.js → llmLazy-7HIZGF35.js} +1 -1
  55. package/dist/{loader-DOLDAFSG.js → loader-3PAJ74VN.js} +4 -4
  56. package/dist/{lsp-F2AYEHJW.js → lsp-S2GYO6LP.js} +16 -8
  57. package/dist/{lspAnchor-LDTVHYPK.js → lspAnchor-VZAH2A7D.js} +6 -6
  58. package/dist/{mcp-3SENV5JM.js → mcp-36TM4EWY.js} +7 -7
  59. package/dist/{mentionProcessor-AES6QJFQ.js → mentionProcessor-YIBFG2BE.js} +5 -5
  60. package/dist/{messages-A77FBKUD.js → messages-GMVNWQYB.js} +1 -1
  61. package/dist/{model-T5FSSEGT.js → model-QA5R6ZTQ.js} +5 -5
  62. package/dist/{openai-4Z5HQAPJ.js → openai-AISJCDRB.js} +5 -5
  63. package/dist/{outputStyles-NPZ5JCX4.js → outputStyles-UNF5PJDX.js} +4 -4
  64. package/dist/{pluginRuntime-ESATQIVZ.js → pluginRuntime-OPB7TGEW.js} +6 -6
  65. package/dist/{pluginValidation-ZIDSORKU.js → pluginValidation-GNC62BIO.js} +6 -6
  66. package/dist/prompts-H6FALR72.js +53 -0
  67. package/dist/{pybAgentSessionLoad-INUFKXJY.js → pybAgentSessionLoad-FZKVG353.js} +4 -4
  68. package/dist/{pybAgentSessionResume-QW7NW3GM.js → pybAgentSessionResume-HHSBJMUF.js} +4 -4
  69. package/dist/{pybAgentStreamJsonSession-GKZPRQGC.js → pybAgentStreamJsonSession-UZOLZ2EE.js} +1 -1
  70. package/dist/{pybHooks-HD3YL2EO.js → pybHooks-BDHY2ZPR.js} +4 -4
  71. package/dist/query-XTV7VLUK.js +55 -0
  72. package/dist/{registry-VYHW665M.js → registry-YC7VJDV4.js} +5 -5
  73. package/dist/{ripgrep-RUMV7DUX.js → ripgrep-HXHIAV7L.js} +3 -3
  74. package/dist/{skillMarketplace-TEDBVTRN.js → skillMarketplace-4F6MUJZA.js} +3 -3
  75. package/dist/{state-BESFSMYW.js → state-BVQ44522.js} +2 -2
  76. package/dist/{theme-O5XJ5B2X.js → theme-VG4SDCTS.js} +5 -5
  77. package/dist/{toolPermissionSettings-N4VMBCRF.js → toolPermissionSettings-V4VXC5RQ.js} +6 -6
  78. package/dist/tools-WZEQJIS5.js +52 -0
  79. package/dist/{userInput-65GMTCNY.js → userInput-4FUGSRKW.js} +31 -31
  80. package/package.json +1 -1
  81. package/dist/REPL-4UQT2JYD.js +0 -47
  82. package/dist/chunk-4C2BJ5TH.js.map +0 -7
  83. package/dist/chunk-CJ5J54UV.js.map +0 -7
  84. package/dist/chunk-HZQ5D4KE.js.map +0 -7
  85. package/dist/chunk-SHKXEYZX.js.map +0 -7
  86. package/dist/chunk-T4CDNQDK.js.map +0 -7
  87. package/dist/chunk-VBDYRFAI.js.map +0 -7
  88. package/dist/chunk-X7NKBF4S.js.map +0 -7
  89. package/dist/commands-KRK3MO6Q.js +0 -51
  90. package/dist/prompts-VSJKN5BB.js +0 -53
  91. package/dist/query-F5YSMI7D.js +0 -61
  92. package/dist/tools-V5NSQILS.js +0 -52
  93. /package/dist/{REPL-4UQT2JYD.js.map → REPL-35WMIAEP.js.map} +0 -0
  94. /package/dist/{acp-Y5GVEAK5.js.map → acp-XZAGDT3N.js.map} +0 -0
  95. /package/dist/{agentsValidate-HVG4G32H.js.map → agentsValidate-ZPLW7S35.js.map} +0 -0
  96. /package/dist/{ask-Q4D437ZY.js.map → ask-ROXENA55.js.map} +0 -0
  97. /package/dist/{autoUpdater-2HZ5MXSY.js.map → autoUpdater-NDX26QV7.js.map} +0 -0
  98. /package/dist/{chunk-DFGDXB5I.js.map → chunk-2ULPCMQN.js.map} +0 -0
  99. /package/dist/{chunk-VCS4L3LA.js.map → chunk-2WVYPGFK.js.map} +0 -0
  100. /package/dist/{chunk-D7EHET2A.js.map → chunk-3HVYGUQ7.js.map} +0 -0
  101. /package/dist/{chunk-IGEQR5ET.js.map → chunk-47PFBKB5.js.map} +0 -0
  102. /package/dist/{chunk-7ZK32QRX.js.map → chunk-4NRDC2MN.js.map} +0 -0
  103. /package/dist/{chunk-VU4GCLM2.js.map → chunk-B6YMKCYD.js.map} +0 -0
  104. /package/dist/{chunk-W7CPW6S2.js.map → chunk-BXOS6YEB.js.map} +0 -0
  105. /package/dist/{chunk-LGG56SIC.js.map → chunk-CGHZ2VAY.js.map} +0 -0
  106. /package/dist/{chunk-4UESJIJZ.js.map → chunk-CMQGXFRW.js.map} +0 -0
  107. /package/dist/{chunk-TQAQR7CB.js.map → chunk-EY2TENGG.js.map} +0 -0
  108. /package/dist/{chunk-FMZTGW27.js.map → chunk-HN63ZGCX.js.map} +0 -0
  109. /package/dist/{chunk-5SWRWOXZ.js.map → chunk-I23P7CAV.js.map} +0 -0
  110. /package/dist/{chunk-JQ7U4FNJ.js.map → chunk-L5MUGKPW.js.map} +0 -0
  111. /package/dist/{chunk-T6MZIN3Y.js.map → chunk-Q6XLF2PE.js.map} +0 -0
  112. /package/dist/{chunk-IBMWS4VP.js.map → chunk-T4EFAH55.js.map} +0 -0
  113. /package/dist/{chunk-GVQW5V5E.js.map → chunk-U5J6MTAO.js.map} +0 -0
  114. /package/dist/{chunk-ILVR3OKO.js.map → chunk-WWYDMHKU.js.map} +0 -0
  115. /package/dist/{chunk-BNTZKC47.js.map → chunk-X6KY4AMW.js.map} +0 -0
  116. /package/dist/{chunk-WHWTXVZ4.js.map → chunk-XLOFU6ZK.js.map} +0 -0
  117. /package/dist/{chunk-GW4FHFLN.js.map → chunk-YOK3FML7.js.map} +0 -0
  118. /package/dist/{chunk-T7MGCO5Q.js.map → chunk-ZFDSP2ES.js.map} +0 -0
  119. /package/dist/{cli-YWSRHJXF.js.map → cli-W6BTK6IG.js.map} +0 -0
  120. /package/dist/{commands-KRK3MO6Q.js.map → commands-OGUECGBR.js.map} +0 -0
  121. /package/dist/{config-XEZZSNSO.js.map → config-MJPE676F.js.map} +0 -0
  122. /package/dist/{context-O7XDPBZM.js.map → context-XLNMR7UJ.js.map} +0 -0
  123. /package/dist/{customCommands-T227ND56.js.map → customCommands-NJWIPIRB.js.map} +0 -0
  124. /package/dist/{env-LGLECBD2.js.map → env-UNJ7A6Y6.js.map} +0 -0
  125. /package/dist/{file-LRWOIEO2.js.map → file-CYIU2LVL.js.map} +0 -0
  126. /package/dist/{llm-KH33MHA2.js.map → llm-XG5AIU4E.js.map} +0 -0
  127. /package/dist/{llmLazy-QDJLCLFB.js.map → llmLazy-7HIZGF35.js.map} +0 -0
  128. /package/dist/{loader-DOLDAFSG.js.map → loader-3PAJ74VN.js.map} +0 -0
  129. /package/dist/{lsp-F2AYEHJW.js.map → lsp-S2GYO6LP.js.map} +0 -0
  130. /package/dist/{lspAnchor-LDTVHYPK.js.map → lspAnchor-VZAH2A7D.js.map} +0 -0
  131. /package/dist/{mcp-3SENV5JM.js.map → mcp-36TM4EWY.js.map} +0 -0
  132. /package/dist/{mentionProcessor-AES6QJFQ.js.map → mentionProcessor-YIBFG2BE.js.map} +0 -0
  133. /package/dist/{messages-A77FBKUD.js.map → messages-GMVNWQYB.js.map} +0 -0
  134. /package/dist/{model-T5FSSEGT.js.map → model-QA5R6ZTQ.js.map} +0 -0
  135. /package/dist/{openai-4Z5HQAPJ.js.map → openai-AISJCDRB.js.map} +0 -0
  136. /package/dist/{outputStyles-NPZ5JCX4.js.map → outputStyles-UNF5PJDX.js.map} +0 -0
  137. /package/dist/{pluginRuntime-ESATQIVZ.js.map → pluginRuntime-OPB7TGEW.js.map} +0 -0
  138. /package/dist/{pluginValidation-ZIDSORKU.js.map → pluginValidation-GNC62BIO.js.map} +0 -0
  139. /package/dist/{prompts-VSJKN5BB.js.map → prompts-H6FALR72.js.map} +0 -0
  140. /package/dist/{pybAgentSessionLoad-INUFKXJY.js.map → pybAgentSessionLoad-FZKVG353.js.map} +0 -0
  141. /package/dist/{pybAgentSessionResume-QW7NW3GM.js.map → pybAgentSessionResume-HHSBJMUF.js.map} +0 -0
  142. /package/dist/{pybAgentStreamJsonSession-GKZPRQGC.js.map → pybAgentStreamJsonSession-UZOLZ2EE.js.map} +0 -0
  143. /package/dist/{pybHooks-HD3YL2EO.js.map → pybHooks-BDHY2ZPR.js.map} +0 -0
  144. /package/dist/{query-F5YSMI7D.js.map → query-XTV7VLUK.js.map} +0 -0
  145. /package/dist/{registry-VYHW665M.js.map → registry-YC7VJDV4.js.map} +0 -0
  146. /package/dist/{ripgrep-RUMV7DUX.js.map → ripgrep-HXHIAV7L.js.map} +0 -0
  147. /package/dist/{skillMarketplace-TEDBVTRN.js.map → skillMarketplace-4F6MUJZA.js.map} +0 -0
  148. /package/dist/{state-BESFSMYW.js.map → state-BVQ44522.js.map} +0 -0
  149. /package/dist/{theme-O5XJ5B2X.js.map → theme-VG4SDCTS.js.map} +0 -0
  150. /package/dist/{toolPermissionSettings-N4VMBCRF.js.map → toolPermissionSettings-V4VXC5RQ.js.map} +0 -0
  151. /package/dist/{tools-V5NSQILS.js.map → tools-WZEQJIS5.js.map} +0 -0
  152. /package/dist/{userInput-65GMTCNY.js.map → userInput-4FUGSRKW.js.map} +0 -0
@@ -4,20 +4,20 @@ import {
4
4
  LspServerRegistry,
5
5
  findNearestRoot,
6
6
  getInstallNotices
7
- } from "./chunk-CJ5J54UV.js";
7
+ } from "./chunk-QX6FKMRR.js";
8
8
  import {
9
9
  levenshtein
10
10
  } from "./chunk-UZ34JEUK.js";
11
11
  import {
12
12
  getCwd
13
- } from "./chunk-4UESJIJZ.js";
13
+ } from "./chunk-CMQGXFRW.js";
14
14
 
15
15
  // src/lsp/index.ts
16
16
  import { extname as extname2 } from "path";
17
17
  import { pathToFileURL as pathToFileURL2 } from "url";
18
18
  import { readFile as readFile4 } from "fs/promises";
19
19
  import * as http from "http";
20
- import { createHash as createHash2 } from "crypto";
20
+ import { createHash as createHash4 } from "crypto";
21
21
 
22
22
  // src/tools/search/LspTool/client/generic.ts
23
23
  import { createMessageConnection, StreamMessageReader, StreamMessageWriter } from "vscode-jsonrpc/node.js";
@@ -387,6 +387,8 @@ var LspClientManager = class _LspClientManager {
387
387
  prewarmHits = /* @__PURE__ */ new Map();
388
388
  spawnMetrics = /* @__PURE__ */ new Map();
389
389
  requestMetrics = /* @__PURE__ */ new Map();
390
+ compareSamples = /* @__PURE__ */ new Map();
391
+ qualitySamples = /* @__PURE__ */ new Map();
390
392
  constructor() {
391
393
  }
392
394
  static getInstance() {
@@ -529,7 +531,7 @@ var LspClientManager = class _LspClientManager {
529
531
  const safeCwd = /[^\x00-\x7F]/.test(resolvedRoot) ? process.cwd() : resolvedRoot;
530
532
  let initOpts = serverInfo.initializationOptions;
531
533
  if (serverInfo.id === "pyright" || serverInfo.id === "ty") {
532
- const { detectPythonVenv } = await import("./registry-VYHW665M.js");
534
+ const { detectPythonVenv } = await import("./registry-YC7VJDV4.js");
533
535
  const pythonPath = detectPythonVenv(resolvedRoot);
534
536
  if (pythonPath) {
535
537
  initOpts = { ...initOpts ?? {}, pythonPath };
@@ -551,7 +553,10 @@ var LspClientManager = class _LspClientManager {
551
553
  }
552
554
  async shutdownAll() {
553
555
  for (const client of this.clients.values()) {
554
- await client.shutdown();
556
+ const shutdown = client.shutdown;
557
+ if (typeof shutdown === "function") {
558
+ await shutdown();
559
+ }
555
560
  }
556
561
  this.clients.clear();
557
562
  this.broken.clear();
@@ -562,6 +567,8 @@ var LspClientManager = class _LspClientManager {
562
567
  this.prewarmHits.clear();
563
568
  this.spawnMetrics.clear();
564
569
  this.requestMetrics.clear();
570
+ this.compareSamples.clear();
571
+ this.qualitySamples.clear();
565
572
  }
566
573
  async workspaceSymbol(query, rootPath = getCwd(), options) {
567
574
  if (this.clients.size === 0) {
@@ -784,11 +791,109 @@ var LspClientManager = class _LspClientManager {
784
791
  latencyP95Ms: p95Latency,
785
792
  baselineAvgMs: baselineAvg,
786
793
  baselineP95Ms: baselineP95,
794
+ baselineSamples: entry.baseline?.samples ?? 0,
787
795
  regression,
788
796
  failureReasons: { ...entry.failureReasons }
789
797
  };
790
798
  });
799
+ const compareMetrics = Array.from(this.compareSamples.entries()).map(([language, samples]) => {
800
+ const sampleCount = samples.length;
801
+ if (sampleCount === 0) {
802
+ return {
803
+ language,
804
+ samples: 0,
805
+ traversalAvg: 0,
806
+ queryAvg: 0,
807
+ missingAvg: 0,
808
+ extraAvg: 0,
809
+ missingRateAvg: 0,
810
+ extraRateAvg: 0,
811
+ lastSampleAt: null
812
+ };
813
+ }
814
+ const totals = samples.reduce(
815
+ (acc, sample) => {
816
+ acc.traversal += sample.traversalCount;
817
+ acc.query += sample.queryCount;
818
+ acc.missing += sample.missingCount;
819
+ acc.extra += sample.extraCount;
820
+ acc.missingRate += sample.traversalCount > 0 ? sample.missingCount / sample.traversalCount : 0;
821
+ acc.extraRate += sample.queryCount > 0 ? sample.extraCount / sample.queryCount : 0;
822
+ acc.lastAt = Math.max(acc.lastAt, sample.at);
823
+ return acc;
824
+ },
825
+ {
826
+ traversal: 0,
827
+ query: 0,
828
+ missing: 0,
829
+ extra: 0,
830
+ missingRate: 0,
831
+ extraRate: 0,
832
+ lastAt: 0
833
+ }
834
+ );
835
+ return {
836
+ language,
837
+ samples: sampleCount,
838
+ traversalAvg: totals.traversal / sampleCount,
839
+ queryAvg: totals.query / sampleCount,
840
+ missingAvg: totals.missing / sampleCount,
841
+ extraAvg: totals.extra / sampleCount,
842
+ missingRateAvg: totals.missingRate / sampleCount,
843
+ extraRateAvg: totals.extraRate / sampleCount,
844
+ lastSampleAt: totals.lastAt
845
+ };
846
+ });
847
+ const tsQualityMetrics = Array.from(this.qualitySamples.entries()).map(([language, samples]) => {
848
+ const sampleCount = samples.length;
849
+ if (sampleCount === 0) {
850
+ return {
851
+ language,
852
+ samples: 0,
853
+ successRate: 0,
854
+ emptyRate: 0,
855
+ coverageAvg: 0,
856
+ traversalAvg: 0,
857
+ queryAvg: 0,
858
+ lastSampleAt: null
859
+ };
860
+ }
861
+ const totals = samples.reduce(
862
+ (acc, sample) => {
863
+ acc.success += sample.success ? 1 : 0;
864
+ acc.empty += sample.empty ? 1 : 0;
865
+ acc.traversal += sample.traversalCount;
866
+ acc.query += sample.queryCount;
867
+ acc.coverage += sample.traversalCount > 0 ? sample.queryCount / sample.traversalCount : 1;
868
+ acc.lastAt = Math.max(acc.lastAt, sample.at);
869
+ return acc;
870
+ },
871
+ {
872
+ success: 0,
873
+ empty: 0,
874
+ traversal: 0,
875
+ query: 0,
876
+ coverage: 0,
877
+ lastAt: 0
878
+ }
879
+ );
880
+ return {
881
+ language,
882
+ samples: sampleCount,
883
+ successRate: totals.success / sampleCount,
884
+ emptyRate: totals.empty / sampleCount,
885
+ coverageAvg: totals.coverage / sampleCount,
886
+ traversalAvg: totals.traversal / sampleCount,
887
+ queryAvg: totals.query / sampleCount,
888
+ lastSampleAt: totals.lastAt
889
+ };
890
+ });
791
891
  return {
892
+ schemaVersion: 1,
893
+ sampleWindow: {
894
+ latencySamples: 50,
895
+ baselineSamples: this.getRequestBaselineSampleSize()
896
+ },
792
897
  clients: Array.from(this.clients.keys()),
793
898
  spawning: Array.from(this.spawning.keys()),
794
899
  broken: Array.from(this.broken.values()),
@@ -797,7 +902,9 @@ var LspClientManager = class _LspClientManager {
797
902
  readiness,
798
903
  prewarms,
799
904
  metrics,
800
- requestMetrics
905
+ requestMetrics,
906
+ compareMetrics,
907
+ tsQualityMetrics
801
908
  };
802
909
  }
803
910
  // New helper to expose waitForReadiness to clients who only have a manager
@@ -910,6 +1017,16 @@ var LspClientManager = class _LspClientManager {
910
1017
  getRequestRegressionFactor() {
911
1018
  return 1.5;
912
1019
  }
1020
+ getCompareSampleLimit() {
1021
+ const override = Number.parseInt(process.env.PYB_TS_COMPARE_SAMPLE_LIMIT ?? "", 10);
1022
+ if (Number.isFinite(override) && override > 0) return override;
1023
+ return 50;
1024
+ }
1025
+ getQualitySampleLimit() {
1026
+ const override = Number.parseInt(process.env.PYB_TS_QUALITY_SAMPLE_LIMIT ?? "", 10);
1027
+ if (Number.isFinite(override) && override > 0) return override;
1028
+ return 50;
1029
+ }
913
1030
  recordRequestAttempt(serverId, operation) {
914
1031
  const entry = this.getRequestEntry(serverId, operation);
915
1032
  entry.attempts += 1;
@@ -935,6 +1052,26 @@ var LspClientManager = class _LspClientManager {
935
1052
  entry.failures += 1;
936
1053
  entry.failureReasons[reason] += 1;
937
1054
  }
1055
+ recordCompareSample(language, sample) {
1056
+ const limit = this.getCompareSampleLimit();
1057
+ if (limit <= 0) return;
1058
+ const list = this.compareSamples.get(language) ?? [];
1059
+ list.push(sample);
1060
+ if (list.length > limit) {
1061
+ list.splice(0, list.length - limit);
1062
+ }
1063
+ this.compareSamples.set(language, list);
1064
+ }
1065
+ recordQualitySample(language, sample) {
1066
+ const limit = this.getQualitySampleLimit();
1067
+ if (limit <= 0) return;
1068
+ const list = this.qualitySamples.get(language) ?? [];
1069
+ list.push(sample);
1070
+ if (list.length > limit) {
1071
+ list.splice(0, list.length - limit);
1072
+ }
1073
+ this.qualitySamples.set(language, list);
1074
+ }
938
1075
  getBaseTtlMs(reason) {
939
1076
  const override = Number.parseInt(process.env.PYB_LSP_BROKEN_TTL_MS ?? "", 10);
940
1077
  if (Number.isFinite(override) && override > 0) return override;
@@ -1288,7 +1425,11 @@ var ParserRegistry = class {
1288
1425
  ".tsx": "tsx",
1289
1426
  ".js": "typescript",
1290
1427
  // Use TS parser for JS
1428
+ ".mjs": "typescript",
1429
+ ".cjs": "typescript",
1291
1430
  ".jsx": "tsx",
1431
+ ".mts": "typescript",
1432
+ ".cts": "typescript",
1292
1433
  ".py": "python",
1293
1434
  ".sh": "bash",
1294
1435
  ".bash": "bash",
@@ -1337,7 +1478,6 @@ var ParserRegistry = class {
1337
1478
  };
1338
1479
 
1339
1480
  // src/utils/tree-sitter/loader.ts
1340
- import { Parser, Language } from "web-tree-sitter";
1341
1481
  import { fileURLToPath as fileURLToPath4 } from "url";
1342
1482
  import path2 from "path";
1343
1483
  var getDirname = () => {
@@ -1348,6 +1488,13 @@ var getDirname = () => {
1348
1488
  }
1349
1489
  };
1350
1490
  var _dirname = getDirname();
1491
+ var webTreeSitterModule = null;
1492
+ var loadWebTreeSitter = async () => {
1493
+ if (!webTreeSitterModule) {
1494
+ webTreeSitterModule = import("web-tree-sitter");
1495
+ }
1496
+ return webTreeSitterModule;
1497
+ };
1351
1498
  var parserCache = /* @__PURE__ */ new Map();
1352
1499
  var isInitialized = false;
1353
1500
  var LANGUAGE_MAP = {
@@ -1382,6 +1529,7 @@ async function ensureInitialized() {
1382
1529
  resourcesDir = path2.resolve(_dirname, "../../../resources/tree-sitter");
1383
1530
  }
1384
1531
  const treeWasmPath = path2.join(resourcesDir, "tree-sitter.wasm");
1532
+ const { Parser } = await loadWebTreeSitter();
1385
1533
  await Parser.init({
1386
1534
  locateFile: () => treeWasmPath
1387
1535
  });
@@ -1404,6 +1552,7 @@ var loadLanguage = async (lang) => {
1404
1552
  resourcesDir = path2.resolve(_dirname, "../../../resources/tree-sitter");
1405
1553
  }
1406
1554
  const wasmPath = path2.join(resourcesDir, fileName);
1555
+ const { Language, Parser } = await loadWebTreeSitter();
1407
1556
  const language = await Language.load(wasmPath);
1408
1557
  const parser = new Parser();
1409
1558
  parser.setLanguage(language);
@@ -1422,7 +1571,167 @@ var initParser = async () => {
1422
1571
  };
1423
1572
 
1424
1573
  // src/utils/tree-sitter/lsp-adapter.ts
1425
- import { readFile as readFile2 } from "fs/promises";
1574
+ import { readFile as readFile3 } from "fs/promises";
1575
+ import { createHash as createHash2 } from "crypto";
1576
+
1577
+ // src/utils/tree-sitter/query-registry.ts
1578
+ import { readFile as readFile2, readdir as readdir2 } from "fs/promises";
1579
+ import path3 from "path";
1580
+ import { fileURLToPath as fileURLToPath5 } from "url";
1581
+ import { createHash } from "crypto";
1582
+ import { Query } from "web-tree-sitter";
1583
+ var SOURCE_CACHE = /* @__PURE__ */ new Map();
1584
+ var COMPILED_CACHE = /* @__PURE__ */ new Map();
1585
+ var LIST_CACHE = /* @__PURE__ */ new Map();
1586
+ var __filename = fileURLToPath5(import.meta.url);
1587
+ var __dirname2 = path3.dirname(__filename);
1588
+ function normalizeLanguage(langKey) {
1589
+ if (langKey === "tsx") return "typescript";
1590
+ return langKey;
1591
+ }
1592
+ function getQueryDir(langKey) {
1593
+ const normalized = normalizeLanguage(langKey);
1594
+ return path3.join(__dirname2, "queries", normalized);
1595
+ }
1596
+ function computeHash(value) {
1597
+ return createHash("sha256").update(value).digest("hex");
1598
+ }
1599
+ function getSourceCacheKey(langKey, queryName) {
1600
+ return `${normalizeLanguage(langKey)}:${queryName}`;
1601
+ }
1602
+ function extractCaptureCounts(source) {
1603
+ const counts = {};
1604
+ const regex = /@([A-Za-z0-9_.-]+)/g;
1605
+ let match = regex.exec(source);
1606
+ while (match) {
1607
+ const name = match[1];
1608
+ counts[name] = (counts[name] ?? 0) + 1;
1609
+ match = regex.exec(source);
1610
+ }
1611
+ return counts;
1612
+ }
1613
+ function isValidQueryFileName(name) {
1614
+ return /^[a-z0-9_-]+\.scm$/i.test(name);
1615
+ }
1616
+ var QueryRegistry = class {
1617
+ static async getQuerySource(langKey, queryName) {
1618
+ const key = getSourceCacheKey(langKey, queryName);
1619
+ if (SOURCE_CACHE.has(key)) {
1620
+ return SOURCE_CACHE.get(key) ?? null;
1621
+ }
1622
+ const dir = getQueryDir(langKey);
1623
+ const queryPath = path3.join(dir, `${queryName}.scm`);
1624
+ try {
1625
+ const source = await readFile2(queryPath, "utf-8");
1626
+ SOURCE_CACHE.set(key, source);
1627
+ return source;
1628
+ } catch {
1629
+ SOURCE_CACHE.set(key, null);
1630
+ return null;
1631
+ }
1632
+ }
1633
+ static async getCompiledQuery(langKey, queryName) {
1634
+ const source = await this.getQuerySource(langKey, queryName);
1635
+ if (!source) return null;
1636
+ const sourceHash = computeHash(source);
1637
+ const key = getSourceCacheKey(langKey, queryName);
1638
+ const cached = COMPILED_CACHE.get(key);
1639
+ if (cached && cached.sourceHash === sourceHash) {
1640
+ return cached.query;
1641
+ }
1642
+ const parser = await loadLanguage(normalizeLanguage(langKey));
1643
+ const language = parser.language;
1644
+ if (!language) return null;
1645
+ const query = new Query(language, source);
1646
+ COMPILED_CACHE.set(key, { sourceHash, query });
1647
+ return query;
1648
+ }
1649
+ static async listQueries(langKey) {
1650
+ const normalized = normalizeLanguage(langKey);
1651
+ if (LIST_CACHE.has(normalized)) {
1652
+ return LIST_CACHE.get(normalized);
1653
+ }
1654
+ const dir = getQueryDir(normalized);
1655
+ try {
1656
+ const files = await readdir2(dir, { withFileTypes: true });
1657
+ const queries = files.filter((entry) => entry.isFile() && entry.name.endsWith(".scm")).map((entry) => entry.name.replace(/\.scm$/, "")).sort();
1658
+ LIST_CACHE.set(normalized, queries);
1659
+ return queries;
1660
+ } catch {
1661
+ LIST_CACHE.set(normalized, []);
1662
+ return [];
1663
+ }
1664
+ }
1665
+ static async validateQueryLayout(langKey) {
1666
+ const normalized = normalizeLanguage(langKey);
1667
+ const dir = getQueryDir(normalized);
1668
+ const errors = [];
1669
+ const invalidEntries = [];
1670
+ const queries = [];
1671
+ try {
1672
+ const entries = await readdir2(dir, { withFileTypes: true });
1673
+ for (const entry of entries) {
1674
+ if (entry.isDirectory()) {
1675
+ invalidEntries.push(entry.name);
1676
+ errors.push(`invalid_directory:${entry.name}`);
1677
+ continue;
1678
+ }
1679
+ if (!entry.isFile()) {
1680
+ invalidEntries.push(entry.name);
1681
+ errors.push(`invalid_entry:${entry.name}`);
1682
+ continue;
1683
+ }
1684
+ if (!entry.name.endsWith(".scm")) {
1685
+ invalidEntries.push(entry.name);
1686
+ errors.push(`invalid_extension:${entry.name}`);
1687
+ continue;
1688
+ }
1689
+ if (!isValidQueryFileName(entry.name)) {
1690
+ invalidEntries.push(entry.name);
1691
+ errors.push(`invalid_name:${entry.name}`);
1692
+ continue;
1693
+ }
1694
+ queries.push(entry.name.replace(/\.scm$/, ""));
1695
+ }
1696
+ return {
1697
+ valid: errors.length === 0,
1698
+ errors,
1699
+ queries: queries.sort(),
1700
+ invalidEntries
1701
+ };
1702
+ } catch (error) {
1703
+ const message = error instanceof Error ? error.message : String(error);
1704
+ return {
1705
+ valid: false,
1706
+ errors: [`read_error:${message}`],
1707
+ queries: [],
1708
+ invalidEntries: []
1709
+ };
1710
+ }
1711
+ }
1712
+ static async getCoverageReport(langKey) {
1713
+ const normalized = normalizeLanguage(langKey);
1714
+ const queryNames = await this.listQueries(normalized);
1715
+ const queries = [];
1716
+ let totalCaptures = 0;
1717
+ for (const name of queryNames) {
1718
+ const source = await this.getQuerySource(normalized, name);
1719
+ if (!source) continue;
1720
+ const captureCounts = extractCaptureCounts(source);
1721
+ const total = Object.values(captureCounts).reduce((sum, count) => sum + count, 0);
1722
+ totalCaptures += total;
1723
+ queries.push({ name, captureCounts, totalCaptures: total });
1724
+ }
1725
+ return {
1726
+ language: normalized,
1727
+ totalQueries: queries.length,
1728
+ totalCaptures,
1729
+ queries
1730
+ };
1731
+ }
1732
+ };
1733
+
1734
+ // src/utils/tree-sitter/lsp-adapter.ts
1426
1735
  var SYMBOL_KIND_MAP2 = {
1427
1736
  function_declaration: "Function",
1428
1737
  function_expression: "Function",
@@ -1453,31 +1762,262 @@ var SYMBOL_KIND_MAP2 = {
1453
1762
  trait_definition: "Interface",
1454
1763
  module: "Module"
1455
1764
  };
1765
+ var PARSE_CACHE = /* @__PURE__ */ new Map();
1766
+ var PARSE_CACHE_TTL_MS = 30 * 1e3;
1767
+ var INCREMENTAL_CACHE = /* @__PURE__ */ new Map();
1768
+ var INCREMENTAL_CACHE_TTL_MS = 30 * 1e3;
1769
+ var PARSE_CACHE_STATS = {
1770
+ incrementalHits: 0,
1771
+ incrementalMisses: 0,
1772
+ fullHits: 0,
1773
+ fullMisses: 0,
1774
+ fallbackCount: 0
1775
+ };
1776
+ function computeHash2(value) {
1777
+ return createHash2("sha256").update(value).digest("hex");
1778
+ }
1779
+ function getParseCacheKey(langId, filePath, code) {
1780
+ const hash = computeHash2(code);
1781
+ return `${langId}:${filePath}:${hash}`;
1782
+ }
1783
+ function getIncrementalCacheKey(langId, filePath) {
1784
+ return `${langId}:${filePath}`;
1785
+ }
1786
+ function getPositionForIndex(text, index) {
1787
+ let row = 0;
1788
+ let column = 0;
1789
+ let i = 0;
1790
+ const limit = Math.max(0, Math.min(index, text.length));
1791
+ while (i < limit) {
1792
+ if (text[i] === "\n") {
1793
+ row += 1;
1794
+ column = 0;
1795
+ } else {
1796
+ column += 1;
1797
+ }
1798
+ i += 1;
1799
+ }
1800
+ return { row, column };
1801
+ }
1802
+ function computeTextEdit(oldText, newText) {
1803
+ if (oldText === newText) return null;
1804
+ let startIndex = 0;
1805
+ const oldLen = oldText.length;
1806
+ const newLen = newText.length;
1807
+ const minLen = Math.min(oldLen, newLen);
1808
+ while (startIndex < minLen && oldText[startIndex] === newText[startIndex]) {
1809
+ startIndex += 1;
1810
+ }
1811
+ let oldEndIndex = oldLen;
1812
+ let newEndIndex = newLen;
1813
+ while (oldEndIndex > startIndex && newEndIndex > startIndex && oldText[oldEndIndex - 1] === newText[newEndIndex - 1]) {
1814
+ oldEndIndex -= 1;
1815
+ newEndIndex -= 1;
1816
+ }
1817
+ const startPosition = getPositionForIndex(oldText, startIndex);
1818
+ const oldEndPosition = getPositionForIndex(oldText, oldEndIndex);
1819
+ const newEndPosition = getPositionForIndex(newText, newEndIndex);
1820
+ return {
1821
+ startIndex,
1822
+ oldEndIndex,
1823
+ newEndIndex,
1824
+ startPosition,
1825
+ oldEndPosition,
1826
+ newEndPosition
1827
+ };
1828
+ }
1829
+ async function parseWithUnified(langId, code, filePath, now, preferIncremental) {
1830
+ const incrementalKey = getIncrementalCacheKey(langId, filePath);
1831
+ const parser = await loadLanguage(langId);
1832
+ const cachedIncremental = INCREMENTAL_CACHE.get(incrementalKey);
1833
+ let usedIncremental = false;
1834
+ if (preferIncremental && cachedIncremental && now < cachedIncremental.expiresAt) {
1835
+ if (cachedIncremental.content === code) {
1836
+ PARSE_CACHE_STATS.incrementalHits += 1;
1837
+ return cachedIncremental.tree;
1838
+ }
1839
+ const edit = computeTextEdit(cachedIncremental.content, code);
1840
+ if (edit) {
1841
+ try {
1842
+ cachedIncremental.tree.edit(edit);
1843
+ const tree2 = parser.parse(code, cachedIncremental.tree);
1844
+ INCREMENTAL_CACHE.set(incrementalKey, {
1845
+ tree: tree2,
1846
+ content: code,
1847
+ expiresAt: now + INCREMENTAL_CACHE_TTL_MS
1848
+ });
1849
+ PARSE_CACHE_STATS.incrementalHits += 1;
1850
+ return tree2;
1851
+ } catch {
1852
+ usedIncremental = true;
1853
+ }
1854
+ } else {
1855
+ usedIncremental = true;
1856
+ }
1857
+ }
1858
+ if (preferIncremental) {
1859
+ PARSE_CACHE_STATS.incrementalMisses += 1;
1860
+ }
1861
+ const fullKey = getParseCacheKey(langId, filePath, code);
1862
+ const cachedFull = PARSE_CACHE.get(fullKey);
1863
+ if (cachedFull && now < cachedFull.expiresAt) {
1864
+ PARSE_CACHE_STATS.fullHits += 1;
1865
+ INCREMENTAL_CACHE.set(incrementalKey, {
1866
+ tree: cachedFull.tree,
1867
+ content: code,
1868
+ expiresAt: now + INCREMENTAL_CACHE_TTL_MS
1869
+ });
1870
+ if (usedIncremental) {
1871
+ PARSE_CACHE_STATS.fallbackCount += 1;
1872
+ }
1873
+ return cachedFull.tree;
1874
+ }
1875
+ PARSE_CACHE_STATS.fullMisses += 1;
1876
+ const tree = parser.parse(code);
1877
+ PARSE_CACHE.set(fullKey, { tree, expiresAt: now + PARSE_CACHE_TTL_MS });
1878
+ INCREMENTAL_CACHE.set(incrementalKey, {
1879
+ tree,
1880
+ content: code,
1881
+ expiresAt: now + INCREMENTAL_CACHE_TTL_MS
1882
+ });
1883
+ if (usedIncremental) {
1884
+ PARSE_CACHE_STATS.fallbackCount += 1;
1885
+ }
1886
+ return tree;
1887
+ }
1888
+ async function parseWithCache(langId, code, filePath, now = Date.now()) {
1889
+ return parseWithUnified(langId, code, filePath, now, false);
1890
+ }
1891
+ async function parseWithIncremental(langId, code, filePath, now = Date.now()) {
1892
+ return parseWithUnified(langId, code, filePath, now, true);
1893
+ }
1894
+ function getParseCacheMetrics() {
1895
+ return {
1896
+ incremental: {
1897
+ hits: PARSE_CACHE_STATS.incrementalHits,
1898
+ misses: PARSE_CACHE_STATS.incrementalMisses,
1899
+ size: INCREMENTAL_CACHE.size
1900
+ },
1901
+ full: {
1902
+ hits: PARSE_CACHE_STATS.fullHits,
1903
+ misses: PARSE_CACHE_STATS.fullMisses,
1904
+ size: PARSE_CACHE.size
1905
+ },
1906
+ fallbackCount: PARSE_CACHE_STATS.fallbackCount
1907
+ };
1908
+ }
1909
+ function collectSymbolsFromNode(node) {
1910
+ const symbols = [];
1911
+ const kind = SYMBOL_KIND_MAP2[node.type];
1912
+ if (kind) {
1913
+ const name = TreeSitterLspAdapter.getName(node);
1914
+ if (name) {
1915
+ const symbol = {
1916
+ name,
1917
+ kind,
1918
+ range: {
1919
+ start: { line: node.startPosition.row, character: node.startPosition.column },
1920
+ end: { line: node.endPosition.row, character: node.endPosition.column }
1921
+ },
1922
+ children: []
1923
+ };
1924
+ for (const child of node.namedChildren) {
1925
+ symbol.children?.push(...collectSymbolsFromNode(child));
1926
+ }
1927
+ symbols.push(symbol);
1928
+ return symbols;
1929
+ }
1930
+ }
1931
+ for (const child of node.namedChildren) {
1932
+ symbols.push(...collectSymbolsFromNode(child));
1933
+ }
1934
+ return symbols;
1935
+ }
1456
1936
  var TreeSitterLspAdapter = class {
1457
- static async getDocumentSymbols(filePath) {
1937
+ static async getDocumentSymbols(filePath, options) {
1458
1938
  const langId = ParserRegistry.getLanguage(filePath);
1459
1939
  if (!langId) {
1460
1940
  return [];
1461
1941
  }
1462
1942
  try {
1463
- const parser = await loadLanguage(langId);
1464
- const content = await readFile2(filePath, "utf-8");
1465
- const tree = parser.parse(content);
1466
- return this.collectSymbols(tree.rootNode);
1943
+ const content = await readFile3(filePath, "utf-8");
1944
+ const tree = await parseWithIncremental(langId, content, filePath);
1945
+ const mode = this.resolveSymbolMode(options);
1946
+ const queryName = options?.queryName ?? "symbols";
1947
+ const traversalSymbols = collectSymbolsFromNode(tree.rootNode);
1948
+ const querySymbols = await this.collectSymbolsViaQuery(tree.rootNode, langId, queryName);
1949
+ let result;
1950
+ if (mode === "traversal") {
1951
+ result = traversalSymbols;
1952
+ } else if (mode === "query") {
1953
+ result = querySymbols.length > 0 ? querySymbols : traversalSymbols;
1954
+ } else {
1955
+ result = traversalSymbols.length > 0 ? traversalSymbols : querySymbols;
1956
+ }
1957
+ const traversalCount = this.flattenSymbols(traversalSymbols).length;
1958
+ const queryCount = this.flattenSymbols(querySymbols).length;
1959
+ LspClientManager.getInstance().recordQualitySample(langId, {
1960
+ success: true,
1961
+ empty: result.length === 0,
1962
+ traversalCount,
1963
+ queryCount,
1964
+ at: Date.now()
1965
+ });
1966
+ return result;
1467
1967
  } catch (error) {
1968
+ LspClientManager.getInstance().recordQualitySample(langId, {
1969
+ success: false,
1970
+ empty: true,
1971
+ traversalCount: 0,
1972
+ queryCount: 0,
1973
+ at: Date.now()
1974
+ });
1468
1975
  if (error instanceof Error) {
1469
1976
  throw new Error(`Tree-sitter parse failed for ${filePath}: ${error.message}`);
1470
1977
  }
1471
1978
  throw new Error(`Tree-sitter parse failed for ${filePath}`);
1472
1979
  }
1473
1980
  }
1474
- static collectSymbols(node) {
1475
- const symbols = [];
1476
- const kind = SYMBOL_KIND_MAP2[node.type];
1477
- if (kind) {
1981
+ static async compareDocumentSymbols(filePath, options) {
1982
+ const langId = ParserRegistry.getLanguage(filePath);
1983
+ if (!langId) {
1984
+ return { traversal: [], query: [], missingInQuery: [], extraInQuery: [] };
1985
+ }
1986
+ const content = await readFile3(filePath, "utf-8");
1987
+ const tree = await parseWithIncremental(langId, content, filePath);
1988
+ const traversal = collectSymbolsFromNode(tree.rootNode);
1989
+ const queryName = options?.queryName ?? "symbols";
1990
+ const query = await this.collectSymbolsViaQuery(tree.rootNode, langId, queryName);
1991
+ return this.compareSymbolSets(traversal, query);
1992
+ }
1993
+ static resolveSymbolMode(options) {
1994
+ if (options?.mode) return options.mode;
1995
+ const raw = String(process.env.PYB_TS_SYMBOLS_MODE ?? "").trim().toLowerCase();
1996
+ if (raw === "query") return "query";
1997
+ if (raw === "compare") return "compare";
1998
+ return "traversal";
1999
+ }
2000
+ static async collectSymbolsViaQuery(node, langId, queryName) {
2001
+ const query = await QueryRegistry.getCompiledQuery(langId, queryName);
2002
+ if (!query) return [];
2003
+ const captures = query.captures(node);
2004
+ const seen = /* @__PURE__ */ new Map();
2005
+ for (const capture of captures) {
2006
+ if (capture.name !== "symbol") continue;
2007
+ seen.set(capture.node.id, capture.node);
2008
+ }
2009
+ return this.buildSymbolTree(Array.from(seen.values()));
2010
+ }
2011
+ static buildSymbolTree(nodes) {
2012
+ const symbolsById = /* @__PURE__ */ new Map();
2013
+ for (const node of nodes) {
2014
+ const kind = SYMBOL_KIND_MAP2[node.type];
2015
+ if (!kind) continue;
1478
2016
  const name = this.getName(node);
1479
- if (name) {
1480
- const symbol = {
2017
+ if (!name) continue;
2018
+ symbolsById.set(node.id, {
2019
+ node,
2020
+ symbol: {
1481
2021
  name,
1482
2022
  kind,
1483
2023
  range: {
@@ -1485,18 +2025,66 @@ var TreeSitterLspAdapter = class {
1485
2025
  end: { line: node.endPosition.row, character: node.endPosition.column }
1486
2026
  },
1487
2027
  children: []
1488
- };
1489
- for (const child of node.namedChildren) {
1490
- symbol.children?.push(...this.collectSymbols(child));
1491
2028
  }
1492
- symbols.push(symbol);
1493
- return symbols;
1494
- }
2029
+ });
1495
2030
  }
1496
- for (const child of node.namedChildren) {
1497
- symbols.push(...this.collectSymbols(child));
2031
+ const roots = [];
2032
+ for (const entry of symbolsById.values()) {
2033
+ let parent = entry.node.parent;
2034
+ let parentSymbol = null;
2035
+ while (parent) {
2036
+ const parentEntry = symbolsById.get(parent.id);
2037
+ if (parentEntry) {
2038
+ parentSymbol = parentEntry.symbol;
2039
+ break;
2040
+ }
2041
+ parent = parent.parent;
2042
+ }
2043
+ if (parentSymbol) {
2044
+ parentSymbol.children = parentSymbol.children ?? [];
2045
+ parentSymbol.children.push(entry.symbol);
2046
+ } else {
2047
+ roots.push(entry.symbol);
2048
+ }
1498
2049
  }
1499
- return symbols;
2050
+ const sortSymbols = (items) => {
2051
+ items.sort((a, b) => {
2052
+ if (a.range.start.line !== b.range.start.line) {
2053
+ return a.range.start.line - b.range.start.line;
2054
+ }
2055
+ return a.range.start.character - b.range.start.character;
2056
+ });
2057
+ for (const item of items) {
2058
+ if (item.children && item.children.length > 0) {
2059
+ sortSymbols(item.children);
2060
+ }
2061
+ }
2062
+ };
2063
+ sortSymbols(roots);
2064
+ return roots;
2065
+ }
2066
+ static compareSymbolSets(traversal, query) {
2067
+ const traversalKeys = new Set(this.flattenSymbols(traversal).map(this.symbolKey));
2068
+ const queryKeys = new Set(this.flattenSymbols(query).map(this.symbolKey));
2069
+ const missingInQuery = Array.from(traversalKeys).filter((key) => !queryKeys.has(key));
2070
+ const extraInQuery = Array.from(queryKeys).filter((key) => !traversalKeys.has(key));
2071
+ return { traversal, query, missingInQuery, extraInQuery };
2072
+ }
2073
+ static flattenSymbols(symbols) {
2074
+ const result = [];
2075
+ const walk = (items) => {
2076
+ for (const item of items) {
2077
+ result.push(item);
2078
+ if (item.children && item.children.length > 0) {
2079
+ walk(item.children);
2080
+ }
2081
+ }
2082
+ };
2083
+ walk(symbols);
2084
+ return result;
2085
+ }
2086
+ static symbolKey(symbol) {
2087
+ return `${symbol.name}|${symbol.kind}|${symbol.range.start.line}|${symbol.range.start.character}`;
1500
2088
  }
1501
2089
  static getName(node) {
1502
2090
  const nameNode = node.childForFieldName("name");
@@ -1511,36 +2099,8 @@ var TreeSitterLspAdapter = class {
1511
2099
  };
1512
2100
 
1513
2101
  // src/utils/tree-sitter/scope-analyzer.ts
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";
1519
- var QUERIES = {
1520
- python: `
1521
- (function_definition name: (identifier) @name)
1522
- (parameters (identifier) @param)
1523
- (assignment left: (identifier) @var)
1524
- (for_statement left: (identifier) @var)
1525
- `,
1526
- typescript: `
1527
- (function_declaration name: (identifier) @name)
1528
- (method_definition name: (property_identifier) @name)
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))
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))
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)))
1542
- `
1543
- };
2102
+ import { Query as Query2 } from "web-tree-sitter";
2103
+ import { createHash as createHash3 } from "crypto";
1544
2104
  var SCOPE_NODE_TYPES = {
1545
2105
  python: /* @__PURE__ */ new Set(["function_definition", "class_definition", "module"]),
1546
2106
  typescript: /* @__PURE__ */ new Set([
@@ -1552,6 +2112,28 @@ var SCOPE_NODE_TYPES = {
1552
2112
  "class_declaration",
1553
2113
  "statement_block",
1554
2114
  "program"
2115
+ ]),
2116
+ go: /* @__PURE__ */ new Set([
2117
+ "function_declaration",
2118
+ "method_declaration",
2119
+ "function_literal",
2120
+ "block",
2121
+ "source_file"
2122
+ ]),
2123
+ rust: /* @__PURE__ */ new Set([
2124
+ "function_item",
2125
+ "impl_item",
2126
+ "trait_item",
2127
+ "block",
2128
+ "source_file"
2129
+ ]),
2130
+ java: /* @__PURE__ */ new Set([
2131
+ "method_declaration",
2132
+ "constructor_declaration",
2133
+ "class_declaration",
2134
+ "interface_declaration",
2135
+ "block",
2136
+ "program"
1555
2137
  ])
1556
2138
  };
1557
2139
  var SCOPE_BOUNDARY_TYPES = {
@@ -1564,26 +2146,32 @@ var SCOPE_BOUNDARY_TYPES = {
1564
2146
  "method_definition",
1565
2147
  "class_declaration",
1566
2148
  "statement_block"
2149
+ ]),
2150
+ go: /* @__PURE__ */ new Set(["function_declaration", "method_declaration", "function_literal"]),
2151
+ rust: /* @__PURE__ */ new Set(["function_item", "impl_item", "trait_item"]),
2152
+ java: /* @__PURE__ */ new Set([
2153
+ "method_declaration",
2154
+ "constructor_declaration",
2155
+ "class_declaration",
2156
+ "interface_declaration"
1567
2157
  ])
1568
2158
  };
1569
- var __filename = fileURLToPath5(import.meta.url);
1570
- var __dirname2 = path3.dirname(__filename);
2159
+ function normalizeScopeLanguage(langKey) {
2160
+ if (langKey === "tsx") return "typescript";
2161
+ return langKey;
2162
+ }
1571
2163
  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
- }
2164
+ const normalized = normalizeScopeLanguage(langKey);
2165
+ return QueryRegistry.getQuerySource(normalized, "locals");
1578
2166
  }
1579
2167
  var QUERY_PRIORITY_LANGUAGES = ["typescript", "python"];
1580
2168
  var SCOPE_CACHE = /* @__PURE__ */ new Map();
1581
2169
  var SCOPE_CACHE_TTL_MS = 30 * 1e3;
1582
- function computeHash(value) {
1583
- return createHash("sha256").update(value).digest("hex");
2170
+ function computeHash3(value) {
2171
+ return createHash3("sha256").update(value).digest("hex");
1584
2172
  }
1585
2173
  function getScopeCacheKey(langKey, code, position) {
1586
- const hash = computeHash(code);
2174
+ const hash = computeHash3(code);
1587
2175
  return `${langKey}:${hash}:${position.row}:${position.column}`;
1588
2176
  }
1589
2177
  function getCachedScope(key, now) {
@@ -1605,7 +2193,7 @@ var ScopeAnalyzer = class {
1605
2193
  if (!queryStr) return { total: 0, unique: 0, names: [] };
1606
2194
  const language = parser.language;
1607
2195
  if (!language) return { total: 0, unique: 0, names: [] };
1608
- const query = new Query(language, queryStr);
2196
+ const query = new Query2(language, queryStr);
1609
2197
  const tree = parser.parse(code);
1610
2198
  const captures = query.captures(tree.rootNode);
1611
2199
  const names = captures.map((c) => c.node.text);
@@ -1613,7 +2201,8 @@ var ScopeAnalyzer = class {
1613
2201
  return { total: captures.length, unique: uniqueNames.length, names: uniqueNames };
1614
2202
  }
1615
2203
  static isPriorityLanguage(langKey) {
1616
- return QUERY_PRIORITY_LANGUAGES.includes(langKey);
2204
+ const normalized = normalizeScopeLanguage(langKey);
2205
+ return QUERY_PRIORITY_LANGUAGES.includes(normalized);
1617
2206
  }
1618
2207
  static getPriorityLanguages() {
1619
2208
  return [...QUERY_PRIORITY_LANGUAGES];
@@ -1623,12 +2212,13 @@ var ScopeAnalyzer = class {
1623
2212
  if (!langKey) {
1624
2213
  throw new Error(`Unsupported language for file: ${filename}`);
1625
2214
  }
2215
+ const normalizedLangKey = normalizeScopeLanguage(langKey);
1626
2216
  const now = Date.now();
1627
- const cacheKey = getScopeCacheKey(langKey, code, position);
2217
+ const cacheKey = getScopeCacheKey(normalizedLangKey, code, position);
1628
2218
  const cached = getCachedScope(cacheKey, now);
1629
2219
  if (cached) return cached;
1630
2220
  const parser = await loadLanguage(langKey);
1631
- const tree = parser.parse(code);
2221
+ const tree = await parseWithCache(langKey, code, filename, now);
1632
2222
  const node = tree.rootNode.descendantForPosition(position);
1633
2223
  const ancestors = [];
1634
2224
  let current = node;
@@ -1637,7 +2227,7 @@ var ScopeAnalyzer = class {
1637
2227
  current = current.parent;
1638
2228
  }
1639
2229
  const scopeNodes = ancestors.filter((n) => {
1640
- const types = SCOPE_NODE_TYPES[langKey];
2230
+ const types = SCOPE_NODE_TYPES[normalizedLangKey];
1641
2231
  return types ? types.has(n.type) : false;
1642
2232
  });
1643
2233
  const result = {
@@ -1645,11 +2235,11 @@ var ScopeAnalyzer = class {
1645
2235
  closure: []
1646
2236
  };
1647
2237
  if (scopeNodes.length === 0) return result;
1648
- const queryStr = await loadScopeQuery(langKey);
2238
+ const queryStr = await loadScopeQuery(normalizedLangKey);
1649
2239
  if (!queryStr) return result;
1650
2240
  const language = parser.language;
1651
2241
  if (!language) return result;
1652
- const query = new Query(language, queryStr);
2242
+ const query = new Query2(language, queryStr);
1653
2243
  for (let i = 0; i < scopeNodes.length; i++) {
1654
2244
  const scopeNode = scopeNodes[i];
1655
2245
  const captures = query.captures(scopeNode);
@@ -1659,7 +2249,7 @@ var ScopeAnalyzer = class {
1659
2249
  let temp = defNode.parent;
1660
2250
  let isDirect = true;
1661
2251
  while (temp && temp.id !== scopeNode.id) {
1662
- const boundaryTypes = SCOPE_BOUNDARY_TYPES[langKey];
2252
+ const boundaryTypes = SCOPE_BOUNDARY_TYPES[normalizedLangKey];
1663
2253
  if (boundaryTypes?.has(temp.type)) {
1664
2254
  isDirect = false;
1665
2255
  break;
@@ -1667,7 +2257,7 @@ var ScopeAnalyzer = class {
1667
2257
  temp = temp.parent;
1668
2258
  }
1669
2259
  if (capture.name === "name") {
1670
- const boundaryTypes = SCOPE_BOUNDARY_TYPES[langKey];
2260
+ const boundaryTypes = SCOPE_BOUNDARY_TYPES[normalizedLangKey];
1671
2261
  if (boundaryTypes?.has(defNode.parent?.type ?? "")) {
1672
2262
  temp = defNode.parent?.parent ?? null;
1673
2263
  isDirect = true;
@@ -1685,7 +2275,7 @@ var ScopeAnalyzer = class {
1685
2275
  }
1686
2276
  }
1687
2277
  if (i === 0 && scopeNode.type === "statement_block") {
1688
- const boundaryTypes = SCOPE_BOUNDARY_TYPES[langKey];
2278
+ const boundaryTypes = SCOPE_BOUNDARY_TYPES[normalizedLangKey];
1689
2279
  const parent = scopeNode.parent;
1690
2280
  if (parent && boundaryTypes?.has(parent.type)) {
1691
2281
  const parentCaptures = query.captures(parent);
@@ -1731,29 +2321,149 @@ function normalizeUri(filePath) {
1731
2321
  }
1732
2322
  return uri;
1733
2323
  }
1734
- function formatTreeSitterSymbols(symbols, depth = 0) {
1735
- let text = "";
1736
- const indent = " ".repeat(depth);
1737
- for (const s of symbols) {
1738
- text += `${indent}- ${s.name} (${s.kind}) [Ln ${s.range.start.line + 1}]
1739
- `;
1740
- if (s.children && s.children.length > 0) {
1741
- text += formatTreeSitterSymbols(s.children, depth + 1);
2324
+ var SYMBOL_MODULE_KINDS = /* @__PURE__ */ new Set(["Module", "Namespace"]);
2325
+ var SYMBOL_CLASS_KINDS = /* @__PURE__ */ new Set(["Class", "Interface", "Struct", "Enum", "Type"]);
2326
+ var SYMBOL_FUNCTION_KINDS = /* @__PURE__ */ new Set(["Function", "Method"]);
2327
+ function resolveNumberEnv(raw, fallback) {
2328
+ const parsed = Number(raw);
2329
+ if (Number.isFinite(parsed) && parsed > 0) return Math.floor(parsed);
2330
+ return fallback;
2331
+ }
2332
+ function collectSymbolSummary(symbols) {
2333
+ const summary = {
2334
+ total: 0,
2335
+ modules: { count: 0, names: [] },
2336
+ classes: { count: 0, names: [] },
2337
+ functions: { count: 0, names: [] },
2338
+ others: 0
2339
+ };
2340
+ const visit = (items) => {
2341
+ for (const item of items) {
2342
+ summary.total += 1;
2343
+ if (SYMBOL_MODULE_KINDS.has(item.kind)) {
2344
+ summary.modules.count += 1;
2345
+ summary.modules.names.push(item.name);
2346
+ } else if (SYMBOL_CLASS_KINDS.has(item.kind)) {
2347
+ summary.classes.count += 1;
2348
+ summary.classes.names.push(item.name);
2349
+ } else if (SYMBOL_FUNCTION_KINDS.has(item.kind)) {
2350
+ summary.functions.count += 1;
2351
+ summary.functions.names.push(item.name);
2352
+ } else {
2353
+ summary.others += 1;
2354
+ }
2355
+ if (item.children && item.children.length > 0) {
2356
+ visit(item.children);
2357
+ }
1742
2358
  }
1743
- }
1744
- return text;
2359
+ };
2360
+ visit(symbols);
2361
+ return summary;
2362
+ }
2363
+ function formatSummaryList(label, names, maxNames) {
2364
+ if (names.length === 0) return null;
2365
+ const sliced = names.slice(0, maxNames);
2366
+ const suffix = names.length > maxNames ? `, +${names.length - maxNames}` : "";
2367
+ return `${label}: ${sliced.join(", ")}${suffix}`;
2368
+ }
2369
+ function formatSymbolSummary(summary, maxNames) {
2370
+ const lines = [
2371
+ `Summary: total ${summary.total}, modules ${summary.modules.count}, classes ${summary.classes.count}, functions ${summary.functions.count}`
2372
+ ];
2373
+ const moduleLine = formatSummaryList("Modules", summary.modules.names, maxNames);
2374
+ const classLine = formatSummaryList("Classes", summary.classes.names, maxNames);
2375
+ const functionLine = formatSummaryList("Functions", summary.functions.names, maxNames);
2376
+ if (moduleLine) lines.push(moduleLine);
2377
+ if (classLine) lines.push(classLine);
2378
+ if (functionLine) lines.push(functionLine);
2379
+ return lines.join("\n");
2380
+ }
2381
+ function renderSymbolTree(symbols, maxNodes) {
2382
+ const lines = [];
2383
+ let rendered = 0;
2384
+ const total = countSymbols(symbols);
2385
+ const visit = (items, depth) => {
2386
+ if (rendered >= maxNodes) return;
2387
+ const indent = " ".repeat(depth);
2388
+ for (const item of items) {
2389
+ if (rendered >= maxNodes) return;
2390
+ lines.push(`${indent}- ${item.name} (${item.kind}) [Ln ${item.range.start.line + 1}]`);
2391
+ rendered += 1;
2392
+ if (item.children && item.children.length > 0) {
2393
+ visit(item.children, depth + 1);
2394
+ }
2395
+ if (rendered >= maxNodes) return;
2396
+ }
2397
+ };
2398
+ visit(symbols, 0);
2399
+ return {
2400
+ text: lines.join("\n"),
2401
+ rendered,
2402
+ total,
2403
+ truncated: rendered < total
2404
+ };
2405
+ }
2406
+ function countSymbols(symbols) {
2407
+ let count = 0;
2408
+ const visit = (items) => {
2409
+ for (const item of items) {
2410
+ count += 1;
2411
+ if (item.children && item.children.length > 0) {
2412
+ visit(item.children);
2413
+ }
2414
+ }
2415
+ };
2416
+ visit(symbols);
2417
+ return count;
2418
+ }
2419
+ function resolveCompareSampleRate() {
2420
+ const raw = Number(process.env.PYB_TS_COMPARE_SAMPLE_RATE);
2421
+ if (Number.isFinite(raw) && raw >= 0) return Math.min(1, raw);
2422
+ return 0.1;
2423
+ }
2424
+ function shouldSampleCompare() {
2425
+ const rate = resolveCompareSampleRate();
2426
+ if (rate <= 0) return false;
2427
+ if (rate >= 1) return true;
2428
+ return Math.random() < rate;
2429
+ }
2430
+ function recordCompareMetrics(filePath, comparison) {
2431
+ const language = ParserRegistry.getLanguage(filePath);
2432
+ if (!language) return;
2433
+ const traversal = Array.isArray(comparison?.traversal) ? comparison.traversal : [];
2434
+ const query = Array.isArray(comparison?.query) ? comparison.query : [];
2435
+ const missing = Array.isArray(comparison?.missingInQuery) ? comparison.missingInQuery : [];
2436
+ const extra = Array.isArray(comparison?.extraInQuery) ? comparison.extraInQuery : [];
2437
+ LspClientManager.getInstance().recordCompareSample(language, {
2438
+ traversalCount: countSymbols(traversal),
2439
+ queryCount: countSymbols(query),
2440
+ missingCount: missing.length,
2441
+ extraCount: extra.length,
2442
+ at: Date.now()
2443
+ });
1745
2444
  }
1746
2445
  function formatTreeSitterDocumentSymbolResult(symbols) {
1747
- const formatted = symbols.length > 0 ? `Document symbols:
1748
- ${formatTreeSitterSymbols(symbols)}` : "No symbols found.";
2446
+ if (symbols.length === 0) {
2447
+ return { formatted: "No symbols found.", resultCount: 0, fileCount: 0 };
2448
+ }
2449
+ const maxNodes = resolveNumberEnv(process.env.PYB_TS_SYMBOLS_MAX_NODES, 200);
2450
+ const maxNames = resolveNumberEnv(process.env.PYB_TS_SYMBOLS_SUMMARY_MAX_NAMES, 6);
2451
+ const summary = collectSymbolSummary(symbols);
2452
+ const summaryText = formatSymbolSummary(summary, maxNames);
2453
+ const rendered = renderSymbolTree(symbols, maxNodes);
2454
+ const parts = [`Document symbols:`, summaryText, rendered.text].filter(Boolean);
2455
+ const truncationLine = rendered.truncated ? `Truncated: showing ${rendered.rendered}/${rendered.total} symbols. Set PYB_TS_SYMBOLS_MAX_NODES to increase.` : "";
2456
+ const formatted = truncationLine ? `${parts.join("\n")}
2457
+ ${truncationLine}` : parts.join("\n");
1749
2458
  return {
1750
2459
  formatted,
1751
2460
  resultCount: symbols.length,
1752
- fileCount: symbols.length > 0 ? 1 : 0
2461
+ fileCount: 1
1753
2462
  };
1754
2463
  }
1755
2464
  var fastPathFailures = /* @__PURE__ */ new Map();
1756
2465
  var fastPathLastFallbackReason = /* @__PURE__ */ new Map();
2466
+ var fastPathHealth = /* @__PURE__ */ new Map();
1757
2467
  var scopeLastFallbackReason = /* @__PURE__ */ new Map();
1758
2468
  var scopeStrategyUsed = /* @__PURE__ */ new Map();
1759
2469
  var operationFallbackReasons = /* @__PURE__ */ new Map();
@@ -1801,7 +2511,9 @@ var FAST_PATH_CONFIG = {
1801
2511
  };
1802
2512
  var FAST_PATH_TIMEOUT_MS = 500;
1803
2513
  var FAST_PATH_CACHE_TTL_MS = 30 * 1e3;
2514
+ var FAST_PATH_DOWNGRADE_TIMEOUT_MS = 60 * 1e3;
1804
2515
  var fastPathSymbolCache = /* @__PURE__ */ new Map();
2516
+ var fastPathDowngrade = /* @__PURE__ */ new Map();
1805
2517
  function recordFastPathFailure(filePath, reason, now) {
1806
2518
  let perFile = fastPathFailures.get(filePath);
1807
2519
  if (!perFile) {
@@ -1812,15 +2524,114 @@ function recordFastPathFailure(filePath, reason, now) {
1812
2524
  const existing = perFile.get(reason);
1813
2525
  if (!existing || now - existing.firstAt > config.windowMs) {
1814
2526
  perFile.set(reason, { count: 1, firstAt: now, lastAt: now });
1815
- return;
2527
+ } else {
2528
+ existing.count += 1;
2529
+ existing.lastAt = now;
2530
+ }
2531
+ let state = fastPathHealth.get(filePath);
2532
+ if (!state) {
2533
+ state = {
2534
+ successCount: 0,
2535
+ failureCount: 0,
2536
+ timeoutCount: 0,
2537
+ lastSuccessAt: null,
2538
+ lastFailureAt: null,
2539
+ avgDurationMs: null,
2540
+ durations: []
2541
+ };
2542
+ fastPathHealth.set(filePath, state);
2543
+ }
2544
+ state.failureCount += 1;
2545
+ if (reason === "timeout") {
2546
+ state.timeoutCount += 1;
2547
+ fastPathDowngrade.set(filePath, {
2548
+ until: now + FAST_PATH_DOWNGRADE_TIMEOUT_MS,
2549
+ reason: "timeout"
2550
+ });
2551
+ }
2552
+ state.lastFailureAt = now;
2553
+ }
2554
+ function recordFastPathSuccess(filePath, durationMs, at) {
2555
+ let state = fastPathHealth.get(filePath);
2556
+ if (!state) {
2557
+ state = {
2558
+ successCount: 0,
2559
+ failureCount: 0,
2560
+ timeoutCount: 0,
2561
+ lastSuccessAt: null,
2562
+ lastFailureAt: null,
2563
+ avgDurationMs: null,
2564
+ durations: []
2565
+ };
2566
+ fastPathHealth.set(filePath, state);
2567
+ }
2568
+ state.successCount += 1;
2569
+ state.lastSuccessAt = at;
2570
+ if (durationMs >= 0) {
2571
+ if (state.avgDurationMs === null) {
2572
+ state.avgDurationMs = durationMs;
2573
+ } else {
2574
+ state.avgDurationMs = (state.avgDurationMs * (state.successCount - 1) + durationMs) / state.successCount;
2575
+ }
2576
+ state.durations.push(durationMs);
2577
+ if (state.durations.length > 50) {
2578
+ state.durations.shift();
2579
+ }
1816
2580
  }
1817
- existing.count += 1;
1818
- existing.lastAt = now;
2581
+ }
2582
+ function resolveFastPathBaseTimeoutMs() {
2583
+ return resolveNumberEnv(process.env.PYB_FAST_PATH_TIMEOUT_BASE_MS, FAST_PATH_TIMEOUT_MS);
2584
+ }
2585
+ function resolveFastPathTimeoutMinMs() {
2586
+ return resolveNumberEnv(process.env.PYB_FAST_PATH_TIMEOUT_MIN_MS, 200);
2587
+ }
2588
+ function resolveFastPathTimeoutMaxMs() {
2589
+ return resolveNumberEnv(process.env.PYB_FAST_PATH_TIMEOUT_MAX_MS, 2e3);
2590
+ }
2591
+ function resolveFastPathBytesPerMs() {
2592
+ return resolveNumberEnv(process.env.PYB_FAST_PATH_TIMEOUT_BYTES_PER_MS, 4e3);
2593
+ }
2594
+ function resolveFastPathHistoryFactor() {
2595
+ const raw = Number(process.env.PYB_FAST_PATH_TIMEOUT_HISTORY_FACTOR);
2596
+ if (Number.isFinite(raw) && raw > 0) return raw;
2597
+ return 1.2;
2598
+ }
2599
+ function resolveFastPathHistoryMinSamples() {
2600
+ return resolveNumberEnv(process.env.PYB_FAST_PATH_TIMEOUT_HISTORY_MIN_SAMPLES, 5);
2601
+ }
2602
+ function computeP95(values) {
2603
+ if (!values.length) return null;
2604
+ const sorted = [...values].sort((a, b) => a - b);
2605
+ const index = Math.min(sorted.length - 1, Math.floor(sorted.length * 0.95));
2606
+ return sorted[index];
2607
+ }
2608
+ function getFastPathTimeoutMs(filePath, contentLength) {
2609
+ const base = resolveFastPathBaseTimeoutMs();
2610
+ const bytesPerMs = resolveFastPathBytesPerMs();
2611
+ const min = resolveFastPathTimeoutMinMs();
2612
+ const max = resolveFastPathTimeoutMaxMs();
2613
+ const sizeBudget = bytesPerMs > 0 ? Math.ceil(contentLength / bytesPerMs) : 0;
2614
+ let timeout = base + sizeBudget;
2615
+ const state = fastPathHealth.get(filePath);
2616
+ const minSamples = resolveFastPathHistoryMinSamples();
2617
+ if (state && state.durations.length >= minSamples) {
2618
+ const p95 = computeP95(state.durations);
2619
+ if (p95 !== null) {
2620
+ const factor = resolveFastPathHistoryFactor();
2621
+ timeout = Math.max(timeout, Math.ceil(p95 * factor));
2622
+ }
2623
+ }
2624
+ if (timeout < min) return min;
2625
+ if (timeout > max) return max;
2626
+ return timeout;
1819
2627
  }
1820
2628
  function clearFastPathFailures(filePath) {
1821
2629
  fastPathFailures.delete(filePath);
1822
2630
  fastPathLastFallbackReason.delete(filePath);
1823
2631
  }
2632
+ function setFastPathLastFallback(filePath, reason, at) {
2633
+ fastPathLastFallbackReason.set(filePath, { reason, at });
2634
+ }
1824
2635
  function recordOperationFallback(operation, filePath, reason) {
1825
2636
  let perOperation = operationFallbackReasons.get(operation);
1826
2637
  if (!perOperation) {
@@ -1838,7 +2649,7 @@ function getOperationFallbackSnapshot(operation) {
1838
2649
  }));
1839
2650
  }
1840
2651
  function computeContentHash(content) {
1841
- return createHash2("sha256").update(content).digest("hex");
2652
+ return createHash4("sha256").update(content).digest("hex");
1842
2653
  }
1843
2654
  function getCachedFastPathSymbols(filePath, hash, now) {
1844
2655
  const cached = fastPathSymbolCache.get(filePath);
@@ -1857,6 +2668,13 @@ function setCachedFastPathSymbols(filePath, hash, symbols, now) {
1857
2668
  });
1858
2669
  }
1859
2670
  function shouldSkipFastPath(filePath, now) {
2671
+ const downgrade = fastPathDowngrade.get(filePath);
2672
+ if (downgrade) {
2673
+ if (now < downgrade.until) {
2674
+ return downgrade.reason;
2675
+ }
2676
+ fastPathDowngrade.delete(filePath);
2677
+ }
1860
2678
  const perFile = fastPathFailures.get(filePath);
1861
2679
  if (!perFile) return null;
1862
2680
  for (const [reason, state] of perFile.entries()) {
@@ -2001,11 +2819,57 @@ function resolveScopeStrategy() {
2001
2819
  return "tree-sitter";
2002
2820
  }
2003
2821
  function getDocumentSymbolFallbackSnapshot() {
2004
- return Array.from(fastPathLastFallbackReason.entries()).map(([filePath, reason]) => ({
2822
+ return Array.from(fastPathLastFallbackReason.entries()).map(([filePath, fallback]) => ({
2005
2823
  filePath,
2006
- reason
2824
+ reason: fallback.reason,
2825
+ at: fallback.at
2007
2826
  }));
2008
2827
  }
2828
+ function getFastPathFailuresSummary() {
2829
+ const summary = {
2830
+ exception: { count: 0, lastAt: null },
2831
+ empty: { count: 0, lastAt: null },
2832
+ timeout: { count: 0, lastAt: null },
2833
+ unsupported: { count: 0, lastAt: null }
2834
+ };
2835
+ for (const perFile of fastPathFailures.values()) {
2836
+ for (const [reason, state] of perFile.entries()) {
2837
+ const entry = summary[reason];
2838
+ entry.count += state.count;
2839
+ entry.lastAt = entry.lastAt === null ? state.lastAt : Math.max(entry.lastAt, state.lastAt);
2840
+ }
2841
+ }
2842
+ for (const fallback of fastPathLastFallbackReason.values()) {
2843
+ if (fallback.reason !== "unsupported") continue;
2844
+ const entry = summary.unsupported;
2845
+ entry.count += 1;
2846
+ entry.lastAt = entry.lastAt === null ? fallback.at : Math.max(entry.lastAt, fallback.at);
2847
+ }
2848
+ return summary;
2849
+ }
2850
+ function getFastPathLastFallbackSnapshot() {
2851
+ return Array.from(fastPathLastFallbackReason.entries()).map(([filePath, fallback]) => ({
2852
+ filePath,
2853
+ reason: fallback.reason,
2854
+ at: fallback.at
2855
+ }));
2856
+ }
2857
+ function getFastPathHealthSummary(now) {
2858
+ return Array.from(fastPathHealth.entries()).map(([filePath, state]) => {
2859
+ const downgrade = fastPathDowngrade.get(filePath);
2860
+ const downgradeUntil = downgrade && now < downgrade.until ? downgrade.until : null;
2861
+ return {
2862
+ filePath,
2863
+ successCount: state.successCount,
2864
+ failureCount: state.failureCount,
2865
+ timeoutCount: state.timeoutCount,
2866
+ lastSuccessAt: state.lastSuccessAt,
2867
+ lastFailureAt: state.lastFailureAt,
2868
+ avgDurationMs: state.avgDurationMs,
2869
+ downgradeUntil
2870
+ };
2871
+ });
2872
+ }
2009
2873
  function getScopeFallbackSnapshot() {
2010
2874
  return Array.from(scopeLastFallbackReason.entries()).map(([filePath, reason]) => ({
2011
2875
  filePath,
@@ -2465,82 +3329,108 @@ var LspFacade = {
2465
3329
  if (lspFirst) {
2466
3330
  try {
2467
3331
  const lspSymbols = await LspAPI.documentSymbolRaw(input.filePath, rootPath);
3332
+ const lang = ParserRegistry.getLanguage(input.filePath);
3333
+ if (lspSymbols !== null && lang && shouldSampleCompare()) {
3334
+ void TreeSitterLspAdapter.compareDocumentSymbols(input.filePath).then((comparison) => recordCompareMetrics(input.filePath, comparison)).catch(() => {
3335
+ });
3336
+ }
2468
3337
  if (lspSymbols && Array.isArray(lspSymbols) && lspSymbols.length > 0) {
2469
3338
  return formatGenericDocumentSymbolResult(lspSymbols);
2470
3339
  }
2471
3340
  const now = Date.now();
2472
- const lang = ParserRegistry.getLanguage(input.filePath);
2473
3341
  if (!lang) {
2474
- fastPathLastFallbackReason.set(input.filePath, "unsupported");
3342
+ setFastPathLastFallback(input.filePath, "unsupported", now);
2475
3343
  } 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);
3344
+ const skipReason = shouldSkipFastPath(input.filePath, now);
3345
+ if (skipReason) {
3346
+ setFastPathLastFallback(input.filePath, skipReason, now);
3347
+ } else {
3348
+ let contentHash = null;
3349
+ let contentLength = 0;
3350
+ try {
3351
+ const start = Date.now();
3352
+ const content = await readFile4(input.filePath, "utf-8");
3353
+ contentLength = content.length;
3354
+ contentHash = computeContentHash(content);
3355
+ const cached = getCachedFastPathSymbols(input.filePath, contentHash, now);
3356
+ if (cached) {
3357
+ clearFastPathFailures(input.filePath);
3358
+ recordFastPathSuccess(input.filePath, Date.now() - start, Date.now());
3359
+ return formatTreeSitterDocumentSymbolResult(cached);
3360
+ }
3361
+ } catch {
2484
3362
  }
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);
3363
+ try {
3364
+ const start = Date.now();
3365
+ const symbols = await withTimeout(
3366
+ TreeSitterLspAdapter.getDocumentSymbols(input.filePath),
3367
+ getFastPathTimeoutMs(input.filePath, contentLength)
3368
+ );
3369
+ if (symbols.length > 0) {
3370
+ clearFastPathFailures(input.filePath);
3371
+ if (contentHash) {
3372
+ setCachedFastPathSymbols(input.filePath, contentHash, symbols, now);
3373
+ }
3374
+ recordFastPathSuccess(input.filePath, Date.now() - start, Date.now());
3375
+ return formatTreeSitterDocumentSymbolResult(symbols);
2496
3376
  }
2497
- return formatTreeSitterDocumentSymbolResult(symbols);
3377
+ recordFastPathFailure(input.filePath, "empty", now);
3378
+ setFastPathLastFallback(input.filePath, "empty", now);
3379
+ } catch (error) {
3380
+ const message = error instanceof Error ? error.message : String(error);
3381
+ const reason = message.toLowerCase().includes("timeout") ? "timeout" : "exception";
3382
+ recordFastPathFailure(input.filePath, reason, now);
3383
+ setFastPathLastFallback(input.filePath, reason, now);
2498
3384
  }
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
3385
  }
2507
3386
  }
2508
3387
  } catch {
2509
3388
  const now = Date.now();
2510
3389
  const lang = ParserRegistry.getLanguage(input.filePath);
2511
3390
  if (!lang) {
2512
- fastPathLastFallbackReason.set(input.filePath, "unsupported");
3391
+ setFastPathLastFallback(input.filePath, "unsupported", now);
2513
3392
  } 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);
3393
+ const skipReason = shouldSkipFastPath(input.filePath, now);
3394
+ if (skipReason) {
3395
+ setFastPathLastFallback(input.filePath, skipReason, now);
3396
+ } else {
3397
+ let contentHash = null;
3398
+ let contentLength = 0;
3399
+ try {
3400
+ const start = Date.now();
3401
+ const content = await readFile4(input.filePath, "utf-8");
3402
+ contentLength = content.length;
3403
+ contentHash = computeContentHash(content);
3404
+ const cached = getCachedFastPathSymbols(input.filePath, contentHash, now);
3405
+ if (cached) {
3406
+ clearFastPathFailures(input.filePath);
3407
+ recordFastPathSuccess(input.filePath, Date.now() - start, Date.now());
3408
+ return formatTreeSitterDocumentSymbolResult(cached);
3409
+ }
3410
+ } catch {
2522
3411
  }
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);
3412
+ try {
3413
+ const start = Date.now();
3414
+ const symbols = await withTimeout(
3415
+ TreeSitterLspAdapter.getDocumentSymbols(input.filePath),
3416
+ getFastPathTimeoutMs(input.filePath, contentLength)
3417
+ );
3418
+ if (symbols.length > 0) {
3419
+ clearFastPathFailures(input.filePath);
3420
+ if (contentHash) {
3421
+ setCachedFastPathSymbols(input.filePath, contentHash, symbols, now);
3422
+ }
3423
+ recordFastPathSuccess(input.filePath, Date.now() - start, Date.now());
3424
+ return formatTreeSitterDocumentSymbolResult(symbols);
2534
3425
  }
2535
- return formatTreeSitterDocumentSymbolResult(symbols);
3426
+ recordFastPathFailure(input.filePath, "empty", now);
3427
+ setFastPathLastFallback(input.filePath, "empty", now);
3428
+ } catch (error) {
3429
+ const message = error instanceof Error ? error.message : String(error);
3430
+ const reason = message.toLowerCase().includes("timeout") ? "timeout" : "exception";
3431
+ recordFastPathFailure(input.filePath, reason, now);
3432
+ setFastPathLastFallback(input.filePath, reason, now);
2536
3433
  }
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
3434
  }
2545
3435
  }
2546
3436
  }
@@ -2548,42 +3438,48 @@ var LspFacade = {
2548
3438
  const lang = ParserRegistry.getLanguage(input.filePath);
2549
3439
  const now = Date.now();
2550
3440
  if (!lang) {
2551
- fastPathLastFallbackReason.set(input.filePath, "unsupported");
3441
+ setFastPathLastFallback(input.filePath, "unsupported", now);
2552
3442
  } else {
2553
3443
  const skipReason = shouldSkipFastPath(input.filePath, now);
2554
3444
  if (skipReason) {
2555
- fastPathLastFallbackReason.set(input.filePath, skipReason);
3445
+ setFastPathLastFallback(input.filePath, skipReason, now);
2556
3446
  } else {
2557
3447
  let contentHash = null;
3448
+ let contentLength = 0;
2558
3449
  try {
3450
+ const start = Date.now();
2559
3451
  const content = await readFile4(input.filePath, "utf-8");
3452
+ contentLength = content.length;
2560
3453
  contentHash = computeContentHash(content);
2561
3454
  const cached = getCachedFastPathSymbols(input.filePath, contentHash, now);
2562
3455
  if (cached) {
2563
3456
  clearFastPathFailures(input.filePath);
3457
+ recordFastPathSuccess(input.filePath, Date.now() - start, Date.now());
2564
3458
  return formatTreeSitterDocumentSymbolResult(cached);
2565
3459
  }
2566
3460
  } catch {
2567
3461
  }
2568
3462
  try {
3463
+ const start = Date.now();
2569
3464
  const symbols = await withTimeout(
2570
3465
  TreeSitterLspAdapter.getDocumentSymbols(input.filePath),
2571
- FAST_PATH_TIMEOUT_MS
3466
+ getFastPathTimeoutMs(input.filePath, contentLength)
2572
3467
  );
2573
3468
  if (symbols.length > 0) {
2574
3469
  clearFastPathFailures(input.filePath);
2575
3470
  if (contentHash) {
2576
3471
  setCachedFastPathSymbols(input.filePath, contentHash, symbols, now);
2577
3472
  }
3473
+ recordFastPathSuccess(input.filePath, Date.now() - start, Date.now());
2578
3474
  return formatTreeSitterDocumentSymbolResult(symbols);
2579
3475
  }
2580
3476
  recordFastPathFailure(input.filePath, "empty", now);
2581
- fastPathLastFallbackReason.set(input.filePath, "empty");
3477
+ setFastPathLastFallback(input.filePath, "empty", now);
2582
3478
  } catch (error) {
2583
3479
  const message = error instanceof Error ? error.message : String(error);
2584
3480
  const reason = message.toLowerCase().includes("timeout") ? "timeout" : "exception";
2585
3481
  recordFastPathFailure(input.filePath, reason, now);
2586
- fastPathLastFallbackReason.set(input.filePath, reason);
3482
+ setFastPathLastFallback(input.filePath, reason, now);
2587
3483
  }
2588
3484
  }
2589
3485
  }
@@ -2723,6 +3619,7 @@ var LspFacade = {
2723
3619
  return {
2724
3620
  ...base,
2725
3621
  installNotices: getInstallNotices(),
3622
+ parseCache: getParseCacheMetrics(),
2726
3623
  fallbacks: {
2727
3624
  documentSymbol: getDocumentSymbolFallbackSnapshot(),
2728
3625
  getScope: getScopeFallbackSnapshot(),
@@ -2732,6 +3629,11 @@ var LspFacade = {
2732
3629
  incomingCalls: getOperationFallbackSnapshot("incomingCalls"),
2733
3630
  outgoingCalls: getOperationFallbackSnapshot("outgoingCalls"),
2734
3631
  boundary: Array.from(LSP_FALLBACK_BOUNDARY.values())
3632
+ },
3633
+ fastPath: {
3634
+ failuresSummary: getFastPathFailuresSummary(),
3635
+ lastFallback: getFastPathLastFallbackSnapshot(),
3636
+ healthSummary: getFastPathHealthSummary(Date.now())
2735
3637
  }
2736
3638
  };
2737
3639
  }
@@ -2740,12 +3642,42 @@ function __resetLspFastPathStateForTests() {
2740
3642
  fastPathFailures.clear();
2741
3643
  fastPathLastFallbackReason.clear();
2742
3644
  fastPathSymbolCache.clear();
3645
+ fastPathHealth.clear();
3646
+ fastPathDowngrade.clear();
2743
3647
  }
2744
3648
  function __resetLspOperationFallbackForTests() {
2745
3649
  operationFallbackReasons.clear();
2746
3650
  }
2747
3651
  function __getLspFastPathFallbackReasonForTests(filePath) {
2748
- return fastPathLastFallbackReason.get(filePath) ?? null;
3652
+ return fastPathLastFallbackReason.get(filePath)?.reason ?? null;
3653
+ }
3654
+ function __getLspFastPathHealthForTests(filePath) {
3655
+ return fastPathHealth.get(filePath) ?? null;
3656
+ }
3657
+ function __setLspFastPathDurationsForTests(filePath, durations) {
3658
+ let state = fastPathHealth.get(filePath);
3659
+ if (!state) {
3660
+ state = {
3661
+ successCount: 0,
3662
+ failureCount: 0,
3663
+ timeoutCount: 0,
3664
+ lastSuccessAt: null,
3665
+ lastFailureAt: null,
3666
+ avgDurationMs: null,
3667
+ durations: []
3668
+ };
3669
+ fastPathHealth.set(filePath, state);
3670
+ }
3671
+ state.durations = [...durations];
3672
+ if (state.durations.length > 0) {
3673
+ const total = state.durations.reduce((acc, value) => acc + value, 0);
3674
+ state.avgDurationMs = total / state.durations.length;
3675
+ } else {
3676
+ state.avgDurationMs = null;
3677
+ }
3678
+ }
3679
+ function __getFastPathTimeoutForTests(filePath, contentLength) {
3680
+ return getFastPathTimeoutMs(filePath, contentLength);
2749
3681
  }
2750
3682
  function __resetScopeStateForTests() {
2751
3683
  scopeLastFallbackReason.clear();
@@ -2763,6 +3695,18 @@ function __getFallbackBoundaryForTests() {
2763
3695
  function __refreshLspFallbackBoundaryForTests() {
2764
3696
  refreshFallbackBoundaryFromEnv();
2765
3697
  }
3698
+ function __formatTreeSitterDocumentSymbolsForTests(symbols, options) {
3699
+ if (symbols.length === 0) return "No symbols found.";
3700
+ const maxNodes = resolveNumberEnv(String(options?.maxNodes ?? ""), 200);
3701
+ const maxNames = resolveNumberEnv(String(options?.summaryMaxNames ?? ""), 6);
3702
+ const summary = collectSymbolSummary(symbols);
3703
+ const summaryText = formatSymbolSummary(summary, maxNames);
3704
+ const rendered = renderSymbolTree(symbols, maxNodes);
3705
+ const parts = [`Document symbols:`, summaryText, rendered.text].filter(Boolean);
3706
+ const truncationLine = rendered.truncated ? `Truncated: showing ${rendered.rendered}/${rendered.total} symbols. Set PYB_TS_SYMBOLS_MAX_NODES to increase.` : "";
3707
+ return truncationLine ? `${parts.join("\n")}
3708
+ ${truncationLine}` : parts.join("\n");
3709
+ }
2766
3710
 
2767
3711
  export {
2768
3712
  formatDiagnosticsPretty,
@@ -2774,9 +3718,13 @@ export {
2774
3718
  __resetLspFastPathStateForTests,
2775
3719
  __resetLspOperationFallbackForTests,
2776
3720
  __getLspFastPathFallbackReasonForTests,
3721
+ __getLspFastPathHealthForTests,
3722
+ __setLspFastPathDurationsForTests,
3723
+ __getFastPathTimeoutForTests,
2777
3724
  __resetScopeStateForTests,
2778
3725
  __getScopeFallbackReasonForTests,
2779
3726
  __getScopeStrategyForTests,
2780
3727
  __getFallbackBoundaryForTests,
2781
- __refreshLspFallbackBoundaryForTests
3728
+ __refreshLspFallbackBoundaryForTests,
3729
+ __formatTreeSitterDocumentSymbolsForTests
2782
3730
  };