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.
- package/dist/REPL-35WMIAEP.js +47 -0
- package/dist/{acp-Y5GVEAK5.js → acp-XZAGDT3N.js} +30 -30
- package/dist/{agentsValidate-HVG4G32H.js → agentsValidate-ZPLW7S35.js} +7 -7
- package/dist/{ask-Q4D437ZY.js → ask-ROXENA55.js} +31 -31
- package/dist/{autoUpdater-2HZ5MXSY.js → autoUpdater-NDX26QV7.js} +3 -3
- package/dist/{chunk-DFGDXB5I.js → chunk-2ULPCMQN.js} +2 -2
- package/dist/{chunk-VCS4L3LA.js → chunk-2WVYPGFK.js} +2 -2
- package/dist/{chunk-D7EHET2A.js → chunk-3HVYGUQ7.js} +3 -3
- package/dist/{chunk-4C2BJ5TH.js → chunk-44DDEUXT.js} +488 -696
- package/dist/chunk-44DDEUXT.js.map +7 -0
- package/dist/{chunk-IGEQR5ET.js → chunk-47PFBKB5.js} +3 -3
- package/dist/{chunk-7ZK32QRX.js → chunk-4NRDC2MN.js} +2 -2
- package/dist/{chunk-DSHUCMWX.js → chunk-5ON6SGBK.js} +1 -1
- package/dist/{chunk-DSHUCMWX.js.map → chunk-5ON6SGBK.js.map} +1 -1
- package/dist/{chunk-VU4GCLM2.js → chunk-B6YMKCYD.js} +2 -2
- package/dist/{chunk-W7CPW6S2.js → chunk-BXOS6YEB.js} +3 -3
- package/dist/{chunk-LGG56SIC.js → chunk-CGHZ2VAY.js} +1 -1
- package/dist/{chunk-4UESJIJZ.js → chunk-CMQGXFRW.js} +1 -1
- package/dist/{chunk-TQAQR7CB.js → chunk-EY2TENGG.js} +1 -1
- package/dist/{chunk-FMZTGW27.js → chunk-HN63ZGCX.js} +4 -4
- package/dist/{chunk-5SWRWOXZ.js → chunk-I23P7CAV.js} +3 -3
- package/dist/{chunk-SHKXEYZX.js → chunk-JFW7GGFH.js} +162 -53
- package/dist/chunk-JFW7GGFH.js.map +7 -0
- package/dist/{chunk-JQ7U4FNJ.js → chunk-L5MUGKPW.js} +3 -3
- package/dist/{chunk-HZQ5D4KE.js → chunk-M2J4YYEX.js} +428 -500
- package/dist/chunk-M2J4YYEX.js.map +7 -0
- package/dist/{chunk-T6MZIN3Y.js → chunk-Q6XLF2PE.js} +1 -1
- package/dist/{chunk-CJ5J54UV.js → chunk-QX6FKMRR.js} +6 -3
- package/dist/chunk-QX6FKMRR.js.map +7 -0
- package/dist/{chunk-IBMWS4VP.js → chunk-T4EFAH55.js} +1 -1
- package/dist/{chunk-GVQW5V5E.js → chunk-U5J6MTAO.js} +2 -2
- package/dist/{chunk-T4CDNQDK.js → chunk-VWENSRU2.js} +4 -7
- package/dist/chunk-VWENSRU2.js.map +7 -0
- package/dist/{chunk-X7NKBF4S.js → chunk-VYINZ75I.js} +1110 -162
- package/dist/chunk-VYINZ75I.js.map +7 -0
- package/dist/{chunk-SRPG24JR.js → chunk-W3RWB5JG.js} +9 -10
- package/dist/{chunk-SRPG24JR.js.map → chunk-W3RWB5JG.js.map} +2 -2
- package/dist/{chunk-ILVR3OKO.js → chunk-WWYDMHKU.js} +1 -1
- package/dist/{chunk-BNTZKC47.js → chunk-X6KY4AMW.js} +2 -2
- package/dist/{chunk-WHWTXVZ4.js → chunk-XLOFU6ZK.js} +1 -1
- package/dist/{chunk-GW4FHFLN.js → chunk-YOK3FML7.js} +1 -1
- package/dist/{chunk-T7MGCO5Q.js → chunk-ZFDSP2ES.js} +2 -2
- package/dist/{chunk-VBDYRFAI.js → chunk-ZI6CKPRG.js} +3 -6
- package/dist/chunk-ZI6CKPRG.js.map +7 -0
- package/dist/{cli-YWSRHJXF.js → cli-W6BTK6IG.js} +88 -88
- package/dist/commands-OGUECGBR.js +51 -0
- package/dist/{config-XEZZSNSO.js → config-MJPE676F.js} +4 -4
- package/dist/{context-O7XDPBZM.js → context-XLNMR7UJ.js} +12 -9
- package/dist/{customCommands-T227ND56.js → customCommands-NJWIPIRB.js} +4 -4
- package/dist/{env-LGLECBD2.js → env-UNJ7A6Y6.js} +2 -2
- package/dist/{file-LRWOIEO2.js → file-CYIU2LVL.js} +4 -4
- package/dist/index.js +3 -3
- package/dist/{llm-KH33MHA2.js → llm-XG5AIU4E.js} +32 -32
- package/dist/{llmLazy-QDJLCLFB.js → llmLazy-7HIZGF35.js} +1 -1
- package/dist/{loader-DOLDAFSG.js → loader-3PAJ74VN.js} +4 -4
- package/dist/{lsp-F2AYEHJW.js → lsp-S2GYO6LP.js} +16 -8
- package/dist/{lspAnchor-LDTVHYPK.js → lspAnchor-VZAH2A7D.js} +6 -6
- package/dist/{mcp-3SENV5JM.js → mcp-36TM4EWY.js} +7 -7
- package/dist/{mentionProcessor-AES6QJFQ.js → mentionProcessor-YIBFG2BE.js} +5 -5
- package/dist/{messages-A77FBKUD.js → messages-GMVNWQYB.js} +1 -1
- package/dist/{model-T5FSSEGT.js → model-QA5R6ZTQ.js} +5 -5
- package/dist/{openai-4Z5HQAPJ.js → openai-AISJCDRB.js} +5 -5
- package/dist/{outputStyles-NPZ5JCX4.js → outputStyles-UNF5PJDX.js} +4 -4
- package/dist/{pluginRuntime-ESATQIVZ.js → pluginRuntime-OPB7TGEW.js} +6 -6
- package/dist/{pluginValidation-ZIDSORKU.js → pluginValidation-GNC62BIO.js} +6 -6
- package/dist/prompts-H6FALR72.js +53 -0
- package/dist/{pybAgentSessionLoad-INUFKXJY.js → pybAgentSessionLoad-FZKVG353.js} +4 -4
- package/dist/{pybAgentSessionResume-QW7NW3GM.js → pybAgentSessionResume-HHSBJMUF.js} +4 -4
- package/dist/{pybAgentStreamJsonSession-GKZPRQGC.js → pybAgentStreamJsonSession-UZOLZ2EE.js} +1 -1
- package/dist/{pybHooks-HD3YL2EO.js → pybHooks-BDHY2ZPR.js} +4 -4
- package/dist/query-XTV7VLUK.js +55 -0
- package/dist/{registry-VYHW665M.js → registry-YC7VJDV4.js} +5 -5
- package/dist/{ripgrep-RUMV7DUX.js → ripgrep-HXHIAV7L.js} +3 -3
- package/dist/{skillMarketplace-TEDBVTRN.js → skillMarketplace-4F6MUJZA.js} +3 -3
- package/dist/{state-BESFSMYW.js → state-BVQ44522.js} +2 -2
- package/dist/{theme-O5XJ5B2X.js → theme-VG4SDCTS.js} +5 -5
- package/dist/{toolPermissionSettings-N4VMBCRF.js → toolPermissionSettings-V4VXC5RQ.js} +6 -6
- package/dist/tools-WZEQJIS5.js +52 -0
- package/dist/{userInput-65GMTCNY.js → userInput-4FUGSRKW.js} +31 -31
- package/package.json +1 -1
- package/dist/REPL-4UQT2JYD.js +0 -47
- package/dist/chunk-4C2BJ5TH.js.map +0 -7
- package/dist/chunk-CJ5J54UV.js.map +0 -7
- package/dist/chunk-HZQ5D4KE.js.map +0 -7
- package/dist/chunk-SHKXEYZX.js.map +0 -7
- package/dist/chunk-T4CDNQDK.js.map +0 -7
- package/dist/chunk-VBDYRFAI.js.map +0 -7
- package/dist/chunk-X7NKBF4S.js.map +0 -7
- package/dist/commands-KRK3MO6Q.js +0 -51
- package/dist/prompts-VSJKN5BB.js +0 -53
- package/dist/query-F5YSMI7D.js +0 -61
- package/dist/tools-V5NSQILS.js +0 -52
- /package/dist/{REPL-4UQT2JYD.js.map → REPL-35WMIAEP.js.map} +0 -0
- /package/dist/{acp-Y5GVEAK5.js.map → acp-XZAGDT3N.js.map} +0 -0
- /package/dist/{agentsValidate-HVG4G32H.js.map → agentsValidate-ZPLW7S35.js.map} +0 -0
- /package/dist/{ask-Q4D437ZY.js.map → ask-ROXENA55.js.map} +0 -0
- /package/dist/{autoUpdater-2HZ5MXSY.js.map → autoUpdater-NDX26QV7.js.map} +0 -0
- /package/dist/{chunk-DFGDXB5I.js.map → chunk-2ULPCMQN.js.map} +0 -0
- /package/dist/{chunk-VCS4L3LA.js.map → chunk-2WVYPGFK.js.map} +0 -0
- /package/dist/{chunk-D7EHET2A.js.map → chunk-3HVYGUQ7.js.map} +0 -0
- /package/dist/{chunk-IGEQR5ET.js.map → chunk-47PFBKB5.js.map} +0 -0
- /package/dist/{chunk-7ZK32QRX.js.map → chunk-4NRDC2MN.js.map} +0 -0
- /package/dist/{chunk-VU4GCLM2.js.map → chunk-B6YMKCYD.js.map} +0 -0
- /package/dist/{chunk-W7CPW6S2.js.map → chunk-BXOS6YEB.js.map} +0 -0
- /package/dist/{chunk-LGG56SIC.js.map → chunk-CGHZ2VAY.js.map} +0 -0
- /package/dist/{chunk-4UESJIJZ.js.map → chunk-CMQGXFRW.js.map} +0 -0
- /package/dist/{chunk-TQAQR7CB.js.map → chunk-EY2TENGG.js.map} +0 -0
- /package/dist/{chunk-FMZTGW27.js.map → chunk-HN63ZGCX.js.map} +0 -0
- /package/dist/{chunk-5SWRWOXZ.js.map → chunk-I23P7CAV.js.map} +0 -0
- /package/dist/{chunk-JQ7U4FNJ.js.map → chunk-L5MUGKPW.js.map} +0 -0
- /package/dist/{chunk-T6MZIN3Y.js.map → chunk-Q6XLF2PE.js.map} +0 -0
- /package/dist/{chunk-IBMWS4VP.js.map → chunk-T4EFAH55.js.map} +0 -0
- /package/dist/{chunk-GVQW5V5E.js.map → chunk-U5J6MTAO.js.map} +0 -0
- /package/dist/{chunk-ILVR3OKO.js.map → chunk-WWYDMHKU.js.map} +0 -0
- /package/dist/{chunk-BNTZKC47.js.map → chunk-X6KY4AMW.js.map} +0 -0
- /package/dist/{chunk-WHWTXVZ4.js.map → chunk-XLOFU6ZK.js.map} +0 -0
- /package/dist/{chunk-GW4FHFLN.js.map → chunk-YOK3FML7.js.map} +0 -0
- /package/dist/{chunk-T7MGCO5Q.js.map → chunk-ZFDSP2ES.js.map} +0 -0
- /package/dist/{cli-YWSRHJXF.js.map → cli-W6BTK6IG.js.map} +0 -0
- /package/dist/{commands-KRK3MO6Q.js.map → commands-OGUECGBR.js.map} +0 -0
- /package/dist/{config-XEZZSNSO.js.map → config-MJPE676F.js.map} +0 -0
- /package/dist/{context-O7XDPBZM.js.map → context-XLNMR7UJ.js.map} +0 -0
- /package/dist/{customCommands-T227ND56.js.map → customCommands-NJWIPIRB.js.map} +0 -0
- /package/dist/{env-LGLECBD2.js.map → env-UNJ7A6Y6.js.map} +0 -0
- /package/dist/{file-LRWOIEO2.js.map → file-CYIU2LVL.js.map} +0 -0
- /package/dist/{llm-KH33MHA2.js.map → llm-XG5AIU4E.js.map} +0 -0
- /package/dist/{llmLazy-QDJLCLFB.js.map → llmLazy-7HIZGF35.js.map} +0 -0
- /package/dist/{loader-DOLDAFSG.js.map → loader-3PAJ74VN.js.map} +0 -0
- /package/dist/{lsp-F2AYEHJW.js.map → lsp-S2GYO6LP.js.map} +0 -0
- /package/dist/{lspAnchor-LDTVHYPK.js.map → lspAnchor-VZAH2A7D.js.map} +0 -0
- /package/dist/{mcp-3SENV5JM.js.map → mcp-36TM4EWY.js.map} +0 -0
- /package/dist/{mentionProcessor-AES6QJFQ.js.map → mentionProcessor-YIBFG2BE.js.map} +0 -0
- /package/dist/{messages-A77FBKUD.js.map → messages-GMVNWQYB.js.map} +0 -0
- /package/dist/{model-T5FSSEGT.js.map → model-QA5R6ZTQ.js.map} +0 -0
- /package/dist/{openai-4Z5HQAPJ.js.map → openai-AISJCDRB.js.map} +0 -0
- /package/dist/{outputStyles-NPZ5JCX4.js.map → outputStyles-UNF5PJDX.js.map} +0 -0
- /package/dist/{pluginRuntime-ESATQIVZ.js.map → pluginRuntime-OPB7TGEW.js.map} +0 -0
- /package/dist/{pluginValidation-ZIDSORKU.js.map → pluginValidation-GNC62BIO.js.map} +0 -0
- /package/dist/{prompts-VSJKN5BB.js.map → prompts-H6FALR72.js.map} +0 -0
- /package/dist/{pybAgentSessionLoad-INUFKXJY.js.map → pybAgentSessionLoad-FZKVG353.js.map} +0 -0
- /package/dist/{pybAgentSessionResume-QW7NW3GM.js.map → pybAgentSessionResume-HHSBJMUF.js.map} +0 -0
- /package/dist/{pybAgentStreamJsonSession-GKZPRQGC.js.map → pybAgentStreamJsonSession-UZOLZ2EE.js.map} +0 -0
- /package/dist/{pybHooks-HD3YL2EO.js.map → pybHooks-BDHY2ZPR.js.map} +0 -0
- /package/dist/{query-F5YSMI7D.js.map → query-XTV7VLUK.js.map} +0 -0
- /package/dist/{registry-VYHW665M.js.map → registry-YC7VJDV4.js.map} +0 -0
- /package/dist/{ripgrep-RUMV7DUX.js.map → ripgrep-HXHIAV7L.js.map} +0 -0
- /package/dist/{skillMarketplace-TEDBVTRN.js.map → skillMarketplace-4F6MUJZA.js.map} +0 -0
- /package/dist/{state-BESFSMYW.js.map → state-BVQ44522.js.map} +0 -0
- /package/dist/{theme-O5XJ5B2X.js.map → theme-VG4SDCTS.js.map} +0 -0
- /package/dist/{toolPermissionSettings-N4VMBCRF.js.map → toolPermissionSettings-V4VXC5RQ.js.map} +0 -0
- /package/dist/{tools-V5NSQILS.js.map → tools-WZEQJIS5.js.map} +0 -0
- /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-
|
|
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-
|
|
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
|
|
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-
|
|
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
|
-
|
|
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
|
|
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
|
|
1464
|
-
const
|
|
1465
|
-
const
|
|
1466
|
-
|
|
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
|
|
1475
|
-
const
|
|
1476
|
-
|
|
1477
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1493
|
-
return symbols;
|
|
1494
|
-
}
|
|
2029
|
+
});
|
|
1495
2030
|
}
|
|
1496
|
-
|
|
1497
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
1570
|
-
|
|
2159
|
+
function normalizeScopeLanguage(langKey) {
|
|
2160
|
+
if (langKey === "tsx") return "typescript";
|
|
2161
|
+
return langKey;
|
|
2162
|
+
}
|
|
1571
2163
|
async function loadScopeQuery(langKey) {
|
|
1572
|
-
const
|
|
1573
|
-
|
|
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
|
|
1583
|
-
return
|
|
2170
|
+
function computeHash3(value) {
|
|
2171
|
+
return createHash3("sha256").update(value).digest("hex");
|
|
1584
2172
|
}
|
|
1585
2173
|
function getScopeCacheKey(langKey, code, position) {
|
|
1586
|
-
const hash =
|
|
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
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
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[
|
|
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(
|
|
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
|
|
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[
|
|
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[
|
|
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[
|
|
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
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1748
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
1818
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
3342
|
+
setFastPathLastFallback(input.filePath, "unsupported", now);
|
|
2475
3343
|
} else {
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
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
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3391
|
+
setFastPathLastFallback(input.filePath, "unsupported", now);
|
|
2513
3392
|
} else {
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
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
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3441
|
+
setFastPathLastFallback(input.filePath, "unsupported", now);
|
|
2552
3442
|
} else {
|
|
2553
3443
|
const skipReason = shouldSkipFastPath(input.filePath, now);
|
|
2554
3444
|
if (skipReason) {
|
|
2555
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
};
|