pi-lens 3.8.21 → 3.8.22
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/CHANGELOG.md +8 -0
- package/README.md +2 -0
- package/clients/dispatch/runners/lsp.ts +58 -3
- package/clients/dispatch/runners/tree-sitter.ts +467 -0
- package/clients/lsp/client.ts +229 -3
- package/clients/lsp/index.ts +111 -1
- package/clients/pipeline.ts +2 -2
- package/clients/runtime-session.ts +43 -5
- package/clients/tree-sitter-client.ts +162 -0
- package/clients/tree-sitter-logger.ts +47 -0
- package/clients/tree-sitter-query-loader.ts +13 -2
- package/package.json +3 -1
- package/rules/rule-catalog.json +64 -0
- package/rules/tree-sitter-queries/go/go-bare-error.yml +19 -7
- package/rules/tree-sitter-queries/go/go-command-injection.yml +55 -0
- package/rules/tree-sitter-queries/go/go-direct-panic.yml +45 -0
- package/rules/tree-sitter-queries/go/go-empty-if-err.yml +47 -0
- package/rules/tree-sitter-queries/go/go-goroutine-loop-capture.yml +49 -0
- package/rules/tree-sitter-queries/go/go-ignored-call-result.yml +51 -0
- package/rules/tree-sitter-queries/go/go-insecure-random.yml +51 -0
- package/rules/tree-sitter-queries/go/go-log-fatal.yml +49 -0
- package/rules/tree-sitter-queries/go/go-path-traversal.yml +51 -0
- package/rules/tree-sitter-queries/go/go-shared-map-write-goroutine.yml +54 -0
- package/rules/tree-sitter-queries/go/go-sql-injection.yml +55 -0
- package/rules/tree-sitter-queries/go/go-weak-hash.yml +51 -0
- package/rules/tree-sitter-queries/python/python-command-injection.yml +63 -0
- package/rules/tree-sitter-queries/python/python-insecure-deserialization.yml +48 -0
- package/rules/tree-sitter-queries/python/python-insecure-random.yml +51 -0
- package/rules/tree-sitter-queries/python/python-path-traversal.yml +55 -0
- package/rules/tree-sitter-queries/python/python-sql-injection.yml +47 -0
- package/rules/tree-sitter-queries/python/python-ssrf.yml +50 -0
- package/rules/tree-sitter-queries/python/python-thread-global-write.yml +58 -0
- package/rules/tree-sitter-queries/python/python-weak-hash.yml +51 -0
- package/rules/tree-sitter-queries/ruby/ruby-command-injection.yml +56 -0
- package/rules/tree-sitter-queries/ruby/ruby-insecure-deserialization.yml +47 -0
- package/rules/tree-sitter-queries/ruby/ruby-insecure-random.yml +54 -0
- package/rules/tree-sitter-queries/ruby/ruby-weak-hash.yml +50 -0
- package/rules/tree-sitter-queries/rust/rust-lock-held-across-await.yml +59 -0
- package/rules/tree-sitter-queries/typescript/ts-command-injection.yml +60 -0
- package/rules/tree-sitter-queries/typescript/ts-detached-async-call.yml +56 -0
- package/rules/tree-sitter-queries/typescript/ts-insecure-random.yml +54 -0
- package/rules/tree-sitter-queries/typescript/ts-ssrf.yml +53 -0
- package/rules/tree-sitter-queries/typescript/ts-weak-hash.yml +54 -0
- package/scripts/validate-rule-catalog.mjs +227 -0
- package/skills/lsp-navigation/SKILL.md +15 -3
- package/tools/lsp-navigation.js +259 -28
- package/tools/lsp-navigation.ts +294 -29
package/clients/lsp/client.ts
CHANGED
|
@@ -49,6 +49,53 @@ export interface LSPHover {
|
|
|
49
49
|
range?: LSPLocation["range"];
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
export interface LSPSignatureHelp {
|
|
53
|
+
signatures: Array<{
|
|
54
|
+
label: string;
|
|
55
|
+
documentation?: string | { kind: string; value: string };
|
|
56
|
+
parameters?: Array<{
|
|
57
|
+
label: string | [number, number];
|
|
58
|
+
documentation?: string | { kind: string; value: string };
|
|
59
|
+
}>;
|
|
60
|
+
}>;
|
|
61
|
+
activeSignature?: number;
|
|
62
|
+
activeParameter?: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface LSPCodeAction {
|
|
66
|
+
title: string;
|
|
67
|
+
kind?: string;
|
|
68
|
+
diagnostics?: LSPDiagnostic[];
|
|
69
|
+
edit?: unknown;
|
|
70
|
+
command?: unknown;
|
|
71
|
+
data?: unknown;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface LSPWorkspaceEdit {
|
|
75
|
+
changes?: Record<string, unknown[]>;
|
|
76
|
+
documentChanges?: unknown[];
|
|
77
|
+
changeAnnotations?: Record<string, unknown>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface LSPWorkspaceDiagnosticsSupport {
|
|
81
|
+
advertised: boolean;
|
|
82
|
+
mode: "pull" | "push-only";
|
|
83
|
+
diagnosticProviderKind: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface LSPOperationSupport {
|
|
87
|
+
definition: boolean;
|
|
88
|
+
references: boolean;
|
|
89
|
+
hover: boolean;
|
|
90
|
+
signatureHelp: boolean;
|
|
91
|
+
documentSymbol: boolean;
|
|
92
|
+
workspaceSymbol: boolean;
|
|
93
|
+
codeAction: boolean;
|
|
94
|
+
rename: boolean;
|
|
95
|
+
implementation: boolean;
|
|
96
|
+
callHierarchy: boolean;
|
|
97
|
+
}
|
|
98
|
+
|
|
52
99
|
export interface LSPSymbol {
|
|
53
100
|
name: string;
|
|
54
101
|
kind: number;
|
|
@@ -93,6 +140,10 @@ export interface LSPClientInfo {
|
|
|
93
140
|
waitForDiagnostics(filePath: string, timeoutMs?: number): Promise<void>;
|
|
94
141
|
/** Get all tracked diagnostics (for cascade checking) */
|
|
95
142
|
getAllDiagnostics(): Map<string, LSPDiagnostic[]>;
|
|
143
|
+
/** Capability snapshot for workspace diagnostics support */
|
|
144
|
+
getWorkspaceDiagnosticsSupport(): LSPWorkspaceDiagnosticsSupport;
|
|
145
|
+
/** Capability snapshot for navigation/edit operations */
|
|
146
|
+
getOperationSupport(): LSPOperationSupport;
|
|
96
147
|
/** Go to definition — returns Location[] */
|
|
97
148
|
definition(
|
|
98
149
|
filePath: string,
|
|
@@ -112,10 +163,31 @@ export interface LSPClientInfo {
|
|
|
112
163
|
line: number,
|
|
113
164
|
character: number,
|
|
114
165
|
): Promise<LSPHover | null>;
|
|
166
|
+
/** Signature help at position */
|
|
167
|
+
signatureHelp(
|
|
168
|
+
filePath: string,
|
|
169
|
+
line: number,
|
|
170
|
+
character: number,
|
|
171
|
+
): Promise<LSPSignatureHelp | null>;
|
|
115
172
|
/** Symbols in a document */
|
|
116
173
|
documentSymbol(filePath: string): Promise<LSPSymbol[]>;
|
|
117
174
|
/** Workspace-wide symbol search */
|
|
118
175
|
workspaceSymbol(query: string): Promise<LSPSymbol[]>;
|
|
176
|
+
/** Available code actions at a range */
|
|
177
|
+
codeAction(
|
|
178
|
+
filePath: string,
|
|
179
|
+
line: number,
|
|
180
|
+
character: number,
|
|
181
|
+
endLine: number,
|
|
182
|
+
endCharacter: number,
|
|
183
|
+
): Promise<LSPCodeAction[]>;
|
|
184
|
+
/** Rename symbol at position */
|
|
185
|
+
rename(
|
|
186
|
+
filePath: string,
|
|
187
|
+
line: number,
|
|
188
|
+
character: number,
|
|
189
|
+
newName: string,
|
|
190
|
+
): Promise<LSPWorkspaceEdit | null>;
|
|
119
191
|
/** Go to implementation */
|
|
120
192
|
implementation(
|
|
121
193
|
filePath: string,
|
|
@@ -141,8 +213,18 @@ export interface LSPClientInfo {
|
|
|
141
213
|
|
|
142
214
|
// --- Constants ---
|
|
143
215
|
|
|
144
|
-
const DIAGNOSTICS_DEBOUNCE_MS =
|
|
145
|
-
|
|
216
|
+
const DIAGNOSTICS_DEBOUNCE_MS = positiveIntFromEnv(
|
|
217
|
+
"PI_LENS_LSP_DIAGNOSTICS_DEBOUNCE_MS",
|
|
218
|
+
150,
|
|
219
|
+
); // ms — waits for follow-up semantic diagnostics
|
|
220
|
+
const INITIALIZE_TIMEOUT_MS = positiveIntFromEnv(
|
|
221
|
+
"PI_LENS_LSP_INIT_TIMEOUT_MS",
|
|
222
|
+
15_000,
|
|
223
|
+
); // 15s — npx downloads are handled by ensureTool, not here
|
|
224
|
+
const DIAGNOSTICS_WAIT_TIMEOUT_MS = positiveIntFromEnv(
|
|
225
|
+
"PI_LENS_LSP_DIAGNOSTICS_WAIT_MS",
|
|
226
|
+
10_000,
|
|
227
|
+
);
|
|
146
228
|
|
|
147
229
|
// --- Client Factory ---
|
|
148
230
|
|
|
@@ -327,6 +409,9 @@ export async function createLSPClient(options: {
|
|
|
327
409
|
);
|
|
328
410
|
}
|
|
329
411
|
|
|
412
|
+
const workspaceDiagnosticsSupport = detectWorkspaceDiagnosticsSupport(initResult);
|
|
413
|
+
const operationSupport = detectOperationSupport(initResult);
|
|
414
|
+
|
|
330
415
|
// Send initialized notification
|
|
331
416
|
await safeSendNotification(connection, "initialized", {});
|
|
332
417
|
|
|
@@ -437,7 +522,15 @@ export async function createLSPClient(options: {
|
|
|
437
522
|
return new Map(diagnostics);
|
|
438
523
|
},
|
|
439
524
|
|
|
440
|
-
|
|
525
|
+
getWorkspaceDiagnosticsSupport() {
|
|
526
|
+
return workspaceDiagnosticsSupport;
|
|
527
|
+
},
|
|
528
|
+
|
|
529
|
+
getOperationSupport() {
|
|
530
|
+
return operationSupport;
|
|
531
|
+
},
|
|
532
|
+
|
|
533
|
+
async waitForDiagnostics(filePath, timeoutMs = DIAGNOSTICS_WAIT_TIMEOUT_MS) {
|
|
441
534
|
const normalizedPath = normalizeMapKey(filePath);
|
|
442
535
|
|
|
443
536
|
// Fast path: diagnostics already available
|
|
@@ -517,6 +610,20 @@ export async function createLSPClient(options: {
|
|
|
517
610
|
return result ?? null;
|
|
518
611
|
},
|
|
519
612
|
|
|
613
|
+
async signatureHelp(filePath, line, character) {
|
|
614
|
+
if (!isProcessAlive()) return null;
|
|
615
|
+
const uri = pathToFileURL(filePath).href;
|
|
616
|
+
const result = await safeSendRequest<LSPSignatureHelp>(
|
|
617
|
+
connection,
|
|
618
|
+
"textDocument/signatureHelp",
|
|
619
|
+
{
|
|
620
|
+
textDocument: { uri },
|
|
621
|
+
position: { line, character },
|
|
622
|
+
},
|
|
623
|
+
);
|
|
624
|
+
return result ?? null;
|
|
625
|
+
},
|
|
626
|
+
|
|
520
627
|
async documentSymbol(filePath) {
|
|
521
628
|
if (!isProcessAlive()) return [];
|
|
522
629
|
const uri = pathToFileURL(filePath).href;
|
|
@@ -542,6 +649,51 @@ export async function createLSPClient(options: {
|
|
|
542
649
|
return result ?? [];
|
|
543
650
|
},
|
|
544
651
|
|
|
652
|
+
async codeAction(
|
|
653
|
+
filePath,
|
|
654
|
+
line,
|
|
655
|
+
character,
|
|
656
|
+
endLine,
|
|
657
|
+
endCharacter,
|
|
658
|
+
) {
|
|
659
|
+
if (!isProcessAlive()) return [];
|
|
660
|
+
const uri = pathToFileURL(filePath).href;
|
|
661
|
+
const result = await safeSendRequest<unknown[]>(
|
|
662
|
+
connection,
|
|
663
|
+
"textDocument/codeAction",
|
|
664
|
+
{
|
|
665
|
+
textDocument: { uri },
|
|
666
|
+
range: {
|
|
667
|
+
start: { line, character },
|
|
668
|
+
end: { line: endLine, character: endCharacter },
|
|
669
|
+
},
|
|
670
|
+
context: {
|
|
671
|
+
diagnostics: diagnostics.get(normalizeMapKey(filePath)) ?? [],
|
|
672
|
+
},
|
|
673
|
+
},
|
|
674
|
+
);
|
|
675
|
+
if (!result || !Array.isArray(result)) return [];
|
|
676
|
+
return result.filter(
|
|
677
|
+
(item): item is LSPCodeAction =>
|
|
678
|
+
typeof item === "object" && item !== null && "title" in item,
|
|
679
|
+
);
|
|
680
|
+
},
|
|
681
|
+
|
|
682
|
+
async rename(filePath, line, character, newName) {
|
|
683
|
+
if (!isProcessAlive()) return null;
|
|
684
|
+
const uri = pathToFileURL(filePath).href;
|
|
685
|
+
const result = await safeSendRequest<LSPWorkspaceEdit>(
|
|
686
|
+
connection,
|
|
687
|
+
"textDocument/rename",
|
|
688
|
+
{
|
|
689
|
+
textDocument: { uri },
|
|
690
|
+
position: { line, character },
|
|
691
|
+
newName,
|
|
692
|
+
},
|
|
693
|
+
);
|
|
694
|
+
return result ?? null;
|
|
695
|
+
},
|
|
696
|
+
|
|
545
697
|
async implementation(filePath, line, character) {
|
|
546
698
|
if (!isProcessAlive()) return [];
|
|
547
699
|
const uri = pathToFileURL(filePath).href;
|
|
@@ -722,3 +874,77 @@ async function withTimeout<T>(
|
|
|
722
874
|
),
|
|
723
875
|
]);
|
|
724
876
|
}
|
|
877
|
+
|
|
878
|
+
function positiveIntFromEnv(name: string, fallback: number): number {
|
|
879
|
+
const raw = process.env[name];
|
|
880
|
+
if (!raw) return fallback;
|
|
881
|
+
const parsed = Number.parseInt(raw, 10);
|
|
882
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return fallback;
|
|
883
|
+
return parsed;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
function detectWorkspaceDiagnosticsSupport(
|
|
887
|
+
initResult: unknown,
|
|
888
|
+
): LSPWorkspaceDiagnosticsSupport {
|
|
889
|
+
const capabilities =
|
|
890
|
+
typeof initResult === "object" && initResult !== null
|
|
891
|
+
? (initResult as { capabilities?: Record<string, unknown> }).capabilities
|
|
892
|
+
: undefined;
|
|
893
|
+
const diagnosticProvider = capabilities?.diagnosticProvider;
|
|
894
|
+
if (!diagnosticProvider) {
|
|
895
|
+
return {
|
|
896
|
+
advertised: false,
|
|
897
|
+
mode: "push-only",
|
|
898
|
+
diagnosticProviderKind: "none",
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
if (typeof diagnosticProvider === "boolean") {
|
|
903
|
+
return {
|
|
904
|
+
advertised: diagnosticProvider,
|
|
905
|
+
mode: diagnosticProvider ? "pull" : "push-only",
|
|
906
|
+
diagnosticProviderKind: "boolean",
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
if (typeof diagnosticProvider === "object") {
|
|
911
|
+
return {
|
|
912
|
+
advertised: true,
|
|
913
|
+
mode: "pull",
|
|
914
|
+
diagnosticProviderKind: "object",
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
return {
|
|
919
|
+
advertised: false,
|
|
920
|
+
mode: "push-only",
|
|
921
|
+
diagnosticProviderKind: typeof diagnosticProvider,
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
function detectOperationSupport(initResult: unknown): LSPOperationSupport {
|
|
926
|
+
const capabilities =
|
|
927
|
+
typeof initResult === "object" && initResult !== null
|
|
928
|
+
? (initResult as { capabilities?: Record<string, unknown> }).capabilities
|
|
929
|
+
: undefined;
|
|
930
|
+
|
|
931
|
+
const hasProvider = (key: string): boolean => {
|
|
932
|
+
const value = capabilities?.[key];
|
|
933
|
+
if (value === undefined || value === null) return false;
|
|
934
|
+
if (typeof value === "boolean") return value;
|
|
935
|
+
return true;
|
|
936
|
+
};
|
|
937
|
+
|
|
938
|
+
return {
|
|
939
|
+
definition: hasProvider("definitionProvider"),
|
|
940
|
+
references: hasProvider("referencesProvider"),
|
|
941
|
+
hover: hasProvider("hoverProvider"),
|
|
942
|
+
signatureHelp: hasProvider("signatureHelpProvider"),
|
|
943
|
+
documentSymbol: hasProvider("documentSymbolProvider"),
|
|
944
|
+
workspaceSymbol: hasProvider("workspaceSymbolProvider"),
|
|
945
|
+
codeAction: hasProvider("codeActionProvider"),
|
|
946
|
+
rename: hasProvider("renameProvider"),
|
|
947
|
+
implementation: hasProvider("implementationProvider"),
|
|
948
|
+
callHierarchy: hasProvider("callHierarchyProvider"),
|
|
949
|
+
};
|
|
950
|
+
}
|
package/clients/lsp/index.ts
CHANGED
|
@@ -53,6 +53,7 @@ export interface SpawnedServer {
|
|
|
53
53
|
export class LSPService {
|
|
54
54
|
private state: LSPState;
|
|
55
55
|
private languagePolicyCache = new Map<string, { allowInstall: boolean; expiresAt: number }>();
|
|
56
|
+
private workspaceProbeLogged = new Set<string>();
|
|
56
57
|
|
|
57
58
|
constructor() {
|
|
58
59
|
this.state = {
|
|
@@ -192,11 +193,25 @@ export class LSPService {
|
|
|
192
193
|
root,
|
|
193
194
|
initialization: spawned.initialization,
|
|
194
195
|
});
|
|
196
|
+
const wsDiag =
|
|
197
|
+
typeof client.getWorkspaceDiagnosticsSupport === "function"
|
|
198
|
+
? client.getWorkspaceDiagnosticsSupport()
|
|
199
|
+
: {
|
|
200
|
+
advertised: false,
|
|
201
|
+
mode: "push-only" as const,
|
|
202
|
+
diagnosticProviderKind: "unavailable",
|
|
203
|
+
};
|
|
195
204
|
|
|
196
205
|
this.state.clients.set(key, client);
|
|
197
206
|
logSessionStart(
|
|
198
207
|
`lsp spawn ${server.id}: success source=${spawned.source ?? server.installPolicy ?? "unknown"} (${Date.now() - startedAt}ms)`,
|
|
199
208
|
);
|
|
209
|
+
if (!this.workspaceProbeLogged.has(key)) {
|
|
210
|
+
logSessionStart(
|
|
211
|
+
`lsp workspace-diag probe ${server.id}: advertised=${wsDiag.advertised} mode=${wsDiag.mode} provider=${wsDiag.diagnosticProviderKind}`,
|
|
212
|
+
);
|
|
213
|
+
this.workspaceProbeLogged.add(key);
|
|
214
|
+
}
|
|
200
215
|
return { client, info: server };
|
|
201
216
|
} catch (err) {
|
|
202
217
|
logSessionStart(
|
|
@@ -294,6 +309,15 @@ export class LSPService {
|
|
|
294
309
|
return spawned.client.hover(filePath, line, character);
|
|
295
310
|
}
|
|
296
311
|
|
|
312
|
+
/**
|
|
313
|
+
* Navigation: signature help at cursor position
|
|
314
|
+
*/
|
|
315
|
+
async signatureHelp(filePath: string, line: number, character: number) {
|
|
316
|
+
const spawned = await this.getClientForFile(filePath);
|
|
317
|
+
if (!spawned) return null;
|
|
318
|
+
return spawned.client.signatureHelp(filePath, line, character);
|
|
319
|
+
}
|
|
320
|
+
|
|
297
321
|
/**
|
|
298
322
|
* Navigation: symbols in document
|
|
299
323
|
*/
|
|
@@ -306,13 +330,98 @@ export class LSPService {
|
|
|
306
330
|
/**
|
|
307
331
|
* Navigation: workspace-wide symbol search
|
|
308
332
|
*/
|
|
309
|
-
async workspaceSymbol(query: string) {
|
|
333
|
+
async workspaceSymbol(query: string, filePath?: string) {
|
|
334
|
+
if (filePath) {
|
|
335
|
+
const spawned = await this.getClientForFile(filePath);
|
|
336
|
+
if (!spawned) return [];
|
|
337
|
+
return spawned.client.workspaceSymbol(query);
|
|
338
|
+
}
|
|
339
|
+
|
|
310
340
|
// Use the first active client for workspace-level queries
|
|
311
341
|
const clients = Array.from(this.state.clients.values());
|
|
312
342
|
if (clients.length === 0) return [];
|
|
313
343
|
return clients[0].workspaceSymbol(query);
|
|
314
344
|
}
|
|
315
345
|
|
|
346
|
+
/**
|
|
347
|
+
* Capability snapshot for LSP operations.
|
|
348
|
+
* If filePath is provided, probes that server; otherwise uses first active client.
|
|
349
|
+
*/
|
|
350
|
+
async getOperationSupport(filePath?: string): Promise<
|
|
351
|
+
import("./client.js").LSPOperationSupport | null
|
|
352
|
+
> {
|
|
353
|
+
if (filePath) {
|
|
354
|
+
const spawned = await this.getClientForFile(filePath);
|
|
355
|
+
if (!spawned) return null;
|
|
356
|
+
const getter = spawned.client.getOperationSupport;
|
|
357
|
+
if (typeof getter !== "function") return null;
|
|
358
|
+
return getter();
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const first = this.state.clients.values().next().value;
|
|
362
|
+
if (!first) return null;
|
|
363
|
+
const getter = first.getOperationSupport;
|
|
364
|
+
if (typeof getter !== "function") return null;
|
|
365
|
+
return getter();
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Capability snapshot for workspace diagnostics support.
|
|
370
|
+
* If filePath is provided, probes that server; otherwise uses first active client.
|
|
371
|
+
*/
|
|
372
|
+
async getWorkspaceDiagnosticsSupport(filePath?: string): Promise<
|
|
373
|
+
import("./client.js").LSPWorkspaceDiagnosticsSupport | null
|
|
374
|
+
> {
|
|
375
|
+
if (filePath) {
|
|
376
|
+
const spawned = await this.getClientForFile(filePath);
|
|
377
|
+
if (!spawned) return null;
|
|
378
|
+
const getter = spawned.client.getWorkspaceDiagnosticsSupport;
|
|
379
|
+
if (typeof getter !== "function") return null;
|
|
380
|
+
return getter();
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const first = this.state.clients.values().next().value;
|
|
384
|
+
if (!first) return null;
|
|
385
|
+
const getter = first.getWorkspaceDiagnosticsSupport;
|
|
386
|
+
if (typeof getter !== "function") return null;
|
|
387
|
+
return getter();
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Navigation: available code actions at position/range
|
|
392
|
+
*/
|
|
393
|
+
async codeAction(
|
|
394
|
+
filePath: string,
|
|
395
|
+
line: number,
|
|
396
|
+
character: number,
|
|
397
|
+
endLine: number,
|
|
398
|
+
endCharacter: number,
|
|
399
|
+
) {
|
|
400
|
+
const spawned = await this.getClientForFile(filePath);
|
|
401
|
+
if (!spawned) return [];
|
|
402
|
+
return spawned.client.codeAction(
|
|
403
|
+
filePath,
|
|
404
|
+
line,
|
|
405
|
+
character,
|
|
406
|
+
endLine,
|
|
407
|
+
endCharacter,
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Navigation: rename symbol at position
|
|
413
|
+
*/
|
|
414
|
+
async rename(
|
|
415
|
+
filePath: string,
|
|
416
|
+
line: number,
|
|
417
|
+
character: number,
|
|
418
|
+
newName: string,
|
|
419
|
+
) {
|
|
420
|
+
const spawned = await this.getClientForFile(filePath);
|
|
421
|
+
if (!spawned) return null;
|
|
422
|
+
return spawned.client.rename(filePath, line, character, newName);
|
|
423
|
+
}
|
|
424
|
+
|
|
316
425
|
/**
|
|
317
426
|
* Navigation: go to implementation
|
|
318
427
|
*/
|
|
@@ -402,6 +511,7 @@ export class LSPService {
|
|
|
402
511
|
}
|
|
403
512
|
this.state.clients.clear();
|
|
404
513
|
this.state.broken.clear();
|
|
514
|
+
this.workspaceProbeLogged.clear();
|
|
405
515
|
}
|
|
406
516
|
|
|
407
517
|
/**
|
package/clients/pipeline.ts
CHANGED
|
@@ -672,8 +672,8 @@ export async function runPipeline(
|
|
|
672
672
|
for (const e of limited) {
|
|
673
673
|
const line = (e.range?.start?.line ?? 0) + 1;
|
|
674
674
|
const col = (e.range?.start?.character ?? 0) + 1;
|
|
675
|
-
const code = e.code ? `
|
|
676
|
-
c += `\n ${
|
|
675
|
+
const code = e.code ? ` code=${String(e.code)}` : "";
|
|
676
|
+
c += `\n line ${line}, col ${col}${code}: ${e.message.split("\n")[0].slice(0, 100)}`;
|
|
677
677
|
}
|
|
678
678
|
c += `${suffix}\n</diagnostics>`;
|
|
679
679
|
}
|
|
@@ -62,11 +62,27 @@ interface SessionStartDeps {
|
|
|
62
62
|
resetLSPService: () => void;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
type StartupMode = "full" | "minimal" | "quick";
|
|
66
|
+
|
|
65
67
|
function isCommandAvailable(command: string, args: string[] = ["--version"]): boolean {
|
|
66
68
|
const result = safeSpawn(command, args, { timeout: 5000 });
|
|
67
69
|
return !result.error && result.status === 0;
|
|
68
70
|
}
|
|
69
71
|
|
|
72
|
+
function resolveStartupMode(): StartupMode {
|
|
73
|
+
const envMode = (process.env.PI_LENS_STARTUP_MODE ?? "").trim().toLowerCase();
|
|
74
|
+
if (envMode === "full" || envMode === "minimal" || envMode === "quick") {
|
|
75
|
+
return envMode;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const argv = process.argv;
|
|
79
|
+
if (argv.includes("--print") || argv.includes("-p")) {
|
|
80
|
+
return "quick";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return "full";
|
|
84
|
+
}
|
|
85
|
+
|
|
70
86
|
function getLanguageInstallHints(
|
|
71
87
|
languageProfile: ReturnType<typeof detectProjectLanguageProfile>,
|
|
72
88
|
): string[] {
|
|
@@ -99,6 +115,9 @@ export async function handleSessionStart(
|
|
|
99
115
|
deps: SessionStartDeps,
|
|
100
116
|
): Promise<void> {
|
|
101
117
|
const sessionStartMs = Date.now();
|
|
118
|
+
const startupMode = resolveStartupMode();
|
|
119
|
+
const allowBootstrapTasks = startupMode === "full";
|
|
120
|
+
const quickMode = startupMode === "quick";
|
|
102
121
|
const {
|
|
103
122
|
ctxCwd,
|
|
104
123
|
getFlag,
|
|
@@ -131,10 +150,14 @@ export async function handleSessionStart(
|
|
|
131
150
|
runtime.complexityBaselines.clear();
|
|
132
151
|
resetDispatchBaselines();
|
|
133
152
|
runtime.resetForSession();
|
|
153
|
+
dbg(`session_start startup mode: ${startupMode}`);
|
|
134
154
|
|
|
135
155
|
if (getFlag("lens-lsp") && !getFlag("no-lsp")) {
|
|
136
156
|
resetLSPService();
|
|
137
157
|
dbg("session_start: LSP service reset");
|
|
158
|
+
dbg(
|
|
159
|
+
"session_start: phase0 workspace diagnostics observation enabled (capability probe only)",
|
|
160
|
+
);
|
|
138
161
|
}
|
|
139
162
|
|
|
140
163
|
if (getFlag("auto-install")) {
|
|
@@ -166,7 +189,7 @@ export async function handleSessionStart(
|
|
|
166
189
|
log(`Active tools: ${tools.join(", ")}`);
|
|
167
190
|
dbg(`session_start tools: ${tools.join(", ")}`);
|
|
168
191
|
|
|
169
|
-
if (getFlag("lens-lsp") && !getFlag("no-lsp")) {
|
|
192
|
+
if (allowBootstrapTasks && getFlag("lens-lsp") && !getFlag("no-lsp")) {
|
|
170
193
|
const cleaned = cleanStaleTsBuildInfo(ctxCwd ?? process.cwd());
|
|
171
194
|
if (cleaned.length > 0) {
|
|
172
195
|
notify(
|
|
@@ -179,6 +202,15 @@ export async function handleSessionStart(
|
|
|
179
202
|
|
|
180
203
|
const hasWorkspaceCwd = typeof ctxCwd === "string" && ctxCwd.length > 0;
|
|
181
204
|
const cwd = ctxCwd ?? process.cwd();
|
|
205
|
+
if (quickMode) {
|
|
206
|
+
runtime.projectRoot = cwd;
|
|
207
|
+
dbg(
|
|
208
|
+
"session_start: quick mode active - skipping language profiling, preinstall, scans, and error debt baseline",
|
|
209
|
+
);
|
|
210
|
+
dbg(`session_start total: ${Date.now() - sessionStartMs}ms`);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
182
214
|
const startupScan = resolveStartupScanContext(cwd);
|
|
183
215
|
const scanRoot = startupScan.projectRoot ?? cwd;
|
|
184
216
|
const useScanRootForSignals =
|
|
@@ -217,7 +249,9 @@ export async function handleSessionStart(
|
|
|
217
249
|
return true;
|
|
218
250
|
});
|
|
219
251
|
|
|
220
|
-
if (
|
|
252
|
+
if (!allowBootstrapTasks) {
|
|
253
|
+
dbg("session_start: skipping tool preinstall (startup mode)");
|
|
254
|
+
} else if (startupDefaults.length > 0) {
|
|
221
255
|
dbg(`session_start: pre-install defaults -> ${startupDefaults.join(", ")}`);
|
|
222
256
|
for (const tool of startupDefaults) {
|
|
223
257
|
const startedAt = Date.now();
|
|
@@ -247,7 +281,7 @@ export async function handleSessionStart(
|
|
|
247
281
|
dbg("session_start: no language defaults selected for pre-install");
|
|
248
282
|
}
|
|
249
283
|
|
|
250
|
-
{
|
|
284
|
+
if (allowBootstrapTasks) {
|
|
251
285
|
const pkgPath = path.join(analysisRoot, "package.json");
|
|
252
286
|
try {
|
|
253
287
|
const raw = await nodeFs.promises.readFile(pkgPath, "utf-8");
|
|
@@ -272,6 +306,8 @@ export async function handleSessionStart(
|
|
|
272
306
|
} catch {
|
|
273
307
|
// no package.json at cwd root
|
|
274
308
|
}
|
|
309
|
+
} else {
|
|
310
|
+
dbg("session_start: skipping prettier preinstall probe (startup mode)");
|
|
275
311
|
}
|
|
276
312
|
|
|
277
313
|
const hasArchitectRules = architectClient.loadConfig(analysisRoot);
|
|
@@ -349,7 +385,9 @@ export async function handleSessionStart(
|
|
|
349
385
|
// Each consumer already handles the "not ready yet" case gracefully
|
|
350
386
|
// (cachedExports.size > 0, cachedProjectIndex != null, cache miss paths).
|
|
351
387
|
|
|
352
|
-
if (!
|
|
388
|
+
if (!allowBootstrapTasks) {
|
|
389
|
+
dbg("session_start: skipping startup background scans (startup mode)");
|
|
390
|
+
} else if (!startupScan.canWarmCaches) {
|
|
353
391
|
dbg(
|
|
354
392
|
`session_start: skipping heavy scans (${startupScan.reason ?? "unknown"})`,
|
|
355
393
|
);
|
|
@@ -500,7 +538,7 @@ export async function handleSessionStart(
|
|
|
500
538
|
`session_start: background scans launched (${startupNotes.length} startup note(s))`,
|
|
501
539
|
);
|
|
502
540
|
|
|
503
|
-
const errorDebtEnabled = getFlag("error-debt");
|
|
541
|
+
const errorDebtEnabled = allowBootstrapTasks && getFlag("error-debt");
|
|
504
542
|
const pendingDebt = cacheManager.readCache<{
|
|
505
543
|
pendingCheck: boolean;
|
|
506
544
|
baselineTestsPassed: boolean;
|