pi-lens 3.8.72 → 3.8.73
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 +46 -0
- package/README.md +25 -24
- package/dist/clients/deps/js-yaml.js +1 -1
- package/dist/clients/dispatch/runners/ast-grep-napi.js +60 -7
- package/dist/clients/instance-registry.js +12 -1
- package/dist/clients/lsp/index.js +86 -14
- package/dist/index.js +3709 -3409
- package/dist/tools/lsp-diagnostics.js +126 -9
- package/docs/servercapabilities.md +1 -1
- package/package.json +8 -9
- package/rules/tree-sitter-queries/typescript/no-equality-in-for-condition.yml +1 -0
- package/rules/tree-sitter-queries/typescript/no-jump-in-finally.yml +1 -0
|
@@ -11,7 +11,7 @@ import { getProjectIgnoreMatcher, isExcludedDirName, } from "../clients/file-uti
|
|
|
11
11
|
import { getLSPService, groupFilesByPrimaryServer, runPerServerGroups, } from "../clients/lsp/index.js";
|
|
12
12
|
import { buildScopeKey, createWorkspaceDiagnosticsCacheContext, } from "../clients/lsp/workspace-diagnostics-cache.js";
|
|
13
13
|
import { primaryServerId } from "../clients/lsp/config.js";
|
|
14
|
-
import { combineAbortSignals } from "../clients/deadline-utils.js";
|
|
14
|
+
import { combineAbortSignals, withDeadline } from "../clients/deadline-utils.js";
|
|
15
15
|
import { applyAuxiliarySuppressions, retagAuxiliaryDiagnostics, } from "../clients/dispatch/auxiliary-lsp.js";
|
|
16
16
|
import { detectFileRole } from "../clients/file-role.js";
|
|
17
17
|
import { classifyCascadeWaitTier } from "../clients/lsp/wait-policy/index.js";
|
|
@@ -59,6 +59,7 @@ const MAX_BATCH_FILES = 100;
|
|
|
59
59
|
const MAX_DIAGNOSTICS = 200;
|
|
60
60
|
const DEFAULT_BATCH_CONCURRENCY = 8;
|
|
61
61
|
const MAX_BATCH_CONCURRENCY = 16;
|
|
62
|
+
const DEFAULT_BATCH_FILE_DEADLINE_MS = 15_000;
|
|
62
63
|
// LSP severities: 1=Error, 2=Warning, 3=Information, 4=Hint
|
|
63
64
|
const SEVERITY_NAMES = {
|
|
64
65
|
1: "error",
|
|
@@ -66,6 +67,10 @@ const SEVERITY_NAMES = {
|
|
|
66
67
|
3: "information",
|
|
67
68
|
4: "hint",
|
|
68
69
|
};
|
|
70
|
+
function batchFileDeadlineMs() {
|
|
71
|
+
const raw = Number(process.env.PI_LENS_LSP_BATCH_FILE_MS);
|
|
72
|
+
return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_BATCH_FILE_DEADLINE_MS;
|
|
73
|
+
}
|
|
69
74
|
// #646: `primaryServerId` moved to clients/lsp/config.ts so this tool and
|
|
70
75
|
// tools/lens-diagnostics.ts's mode=full sweep share the exact same
|
|
71
76
|
// primary-vs-auxiliary classification instead of each keeping its own copy.
|
|
@@ -252,6 +257,19 @@ nextWriteIndex) {
|
|
|
252
257
|
const suffix = timedOutFiles > 0 ? ` (${timedOutFiles} timed out)` : "";
|
|
253
258
|
return `lsp_diagnostics${scope} — ${count} ${noun} · ${cleanFiles} clean · ${unconfirmedFiles} unconfirmed${suffix}`;
|
|
254
259
|
}
|
|
260
|
+
const outcomeCounts = details?.outcomeCounts;
|
|
261
|
+
const notConfirmed = outcomeCounts
|
|
262
|
+
? (outcomeCounts.inconclusive ?? 0) +
|
|
263
|
+
(outcomeCounts.unavailable ?? 0) +
|
|
264
|
+
(outcomeCounts.unsupported ?? 0) +
|
|
265
|
+
(outcomeCounts.failed ?? 0)
|
|
266
|
+
: 0;
|
|
267
|
+
if (notConfirmed > 0) {
|
|
268
|
+
return `lsp_diagnostics${scope} — ${count} ${noun} · ${notConfirmed} checks not confirmed`;
|
|
269
|
+
}
|
|
270
|
+
if ((details?.incompleteFiles ?? 0) > 0) {
|
|
271
|
+
return `lsp_diagnostics${scope} — incomplete (${details?.incompleteFiles} files not confirmed)`;
|
|
272
|
+
}
|
|
255
273
|
// Single-file mode: 0 diagnostics from an unconfirmed result — either a
|
|
256
274
|
// silent-on-clean server or (#570) a timed-out check — is not a clean
|
|
257
275
|
// render either.
|
|
@@ -321,12 +339,29 @@ nextWriteIndex) {
|
|
|
321
339
|
details: {},
|
|
322
340
|
};
|
|
323
341
|
}
|
|
324
|
-
if (Array.isArray(typedParams.paths) &&
|
|
325
|
-
typedParams.paths.length >
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
342
|
+
if (Array.isArray(typedParams.paths) && typedParams.paths.length > 0) {
|
|
343
|
+
if (typedParams.paths.length > MAX_BATCH_FILES) {
|
|
344
|
+
return {
|
|
345
|
+
content: [{
|
|
346
|
+
type: "text",
|
|
347
|
+
text: `paths accepts at most ${MAX_BATCH_FILES} files; received ${typedParams.paths.length}.`,
|
|
348
|
+
}],
|
|
349
|
+
isError: true,
|
|
350
|
+
details: { mode: "batch", filesChecked: 0 },
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
const rawPaths = typedParams.paths.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
354
|
+
if (rawPaths.length !== typedParams.paths.length) {
|
|
355
|
+
return {
|
|
356
|
+
content: [{ type: "text", text: "paths must contain non-empty file paths." }],
|
|
357
|
+
isError: true,
|
|
358
|
+
details: { mode: "batch", filesChecked: 0 },
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
// Preserve input order (including duplicate entries); normalize each
|
|
362
|
+
// path before grouping so Windows separators and dot segments cannot
|
|
363
|
+
// change cache/group identity. Explicit lists never enter the walker.
|
|
364
|
+
const absPaths = rawPaths.map((entry) => path.normalize(path.isAbsolute(entry) ? entry : path.resolve(cwd, entry)));
|
|
330
365
|
return runBatchFileDiagnostics(absPaths, severity, lspService, {
|
|
331
366
|
concurrency,
|
|
332
367
|
waitMs,
|
|
@@ -799,6 +834,34 @@ function tallyConfirmation(results) {
|
|
|
799
834
|
}
|
|
800
835
|
return { clean, unconfirmed, timedOut };
|
|
801
836
|
}
|
|
837
|
+
function classifyBatchFileOutcome(result) {
|
|
838
|
+
if (result.error)
|
|
839
|
+
return "failed";
|
|
840
|
+
// A timed-out/unconfirmed answer may contain partial findings, but it cannot
|
|
841
|
+
// honestly be called a complete findings result. Keep the raw findings for
|
|
842
|
+
// investigation while making the aggregate incomplete.
|
|
843
|
+
if (result.timedOut || result.confirmation === "unconfirmed") {
|
|
844
|
+
return "inconclusive";
|
|
845
|
+
}
|
|
846
|
+
if (result.diagnostics.length > 0)
|
|
847
|
+
return "findings";
|
|
848
|
+
if (result.unavailable) {
|
|
849
|
+
return result.primaryServerId ? "unavailable" : "unsupported";
|
|
850
|
+
}
|
|
851
|
+
if (!result.primaryServerId)
|
|
852
|
+
return "unsupported";
|
|
853
|
+
return "clean";
|
|
854
|
+
}
|
|
855
|
+
function inconclusiveBatchResult(file, reason) {
|
|
856
|
+
return {
|
|
857
|
+
file,
|
|
858
|
+
diagnostics: [],
|
|
859
|
+
confirmation: "unconfirmed",
|
|
860
|
+
timedOut: true,
|
|
861
|
+
outcome: "inconclusive",
|
|
862
|
+
inconclusiveReason: reason,
|
|
863
|
+
};
|
|
864
|
+
}
|
|
802
865
|
/**
|
|
803
866
|
* #570: build the explanatory clause for a batch/directory result that has
|
|
804
867
|
* unconfirmed files, distinguishing timed-out checks from the pre-existing
|
|
@@ -846,7 +909,32 @@ async function collectBatchDiagnostics(files, severity, lspService, options) {
|
|
|
846
909
|
const resolvedCwd = options.cwd ?? process.cwd();
|
|
847
910
|
const cacheCtx = createWorkspaceDiagnosticsCacheContext(resolvedCwd);
|
|
848
911
|
const scopeKey = buildScopeKey(options.serverScope ?? "all");
|
|
849
|
-
const results = await mapWithConcurrency(files, options.concurrency, (file) =>
|
|
912
|
+
const results = await mapWithConcurrency(files, options.concurrency, async (file) => {
|
|
913
|
+
const work = collectFileDiagnosticResult(file, severity, lspService, options.waitMs, options.nextWriteIndex, options.serverScope, cacheCtx, scopeKey, resolvedCwd);
|
|
914
|
+
const bounded = withDeadline(work, {
|
|
915
|
+
ms: batchFileDeadlineMs(),
|
|
916
|
+
onTimeout: "undefined",
|
|
917
|
+
onReject: "undefined",
|
|
918
|
+
});
|
|
919
|
+
const result = options.signal
|
|
920
|
+
? await Promise.race([
|
|
921
|
+
bounded,
|
|
922
|
+
new Promise((resolve) => {
|
|
923
|
+
if (options.signal?.aborted) {
|
|
924
|
+
resolve(undefined);
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
options.signal?.addEventListener("abort", () => resolve(undefined), {
|
|
928
|
+
once: true,
|
|
929
|
+
});
|
|
930
|
+
}),
|
|
931
|
+
])
|
|
932
|
+
: await bounded;
|
|
933
|
+
return (result ??
|
|
934
|
+
inconclusiveBatchResult(file, options.signal?.aborted
|
|
935
|
+
? "Batch aborted before this file completed."
|
|
936
|
+
: `File check exceeded ${batchFileDeadlineMs()}ms.`));
|
|
937
|
+
}, lspService, options.signal, options.onProgress);
|
|
850
938
|
// Persist whatever was recorded, including a partial/aborted sweep's
|
|
851
939
|
// already-completed files — same "don't throw away confirmed work"
|
|
852
940
|
// posture as `runWorkspaceDiagnostics`.
|
|
@@ -858,6 +946,10 @@ async function collectBatchDiagnostics(files, severity, lspService, options) {
|
|
|
858
946
|
const truncated = total > MAX_DIAGNOSTICS;
|
|
859
947
|
const display = truncated ? allDiags.slice(0, MAX_DIAGNOSTICS) : allDiags;
|
|
860
948
|
const { clean, unconfirmed, timedOut } = tallyConfirmation(results);
|
|
949
|
+
for (const result of results) {
|
|
950
|
+
result.outcome = classifyBatchFileOutcome(result);
|
|
951
|
+
}
|
|
952
|
+
const outcomeCounts = Object.fromEntries(["clean", "findings", "unsupported", "unavailable", "failed", "inconclusive"].map((outcome) => [outcome, results.filter((result) => result.outcome === outcome).length]));
|
|
861
953
|
// Per-file primary-server lookup so a flattened multi-file `display` list
|
|
862
954
|
// can still be split into "primary findings" vs "auxiliary findings" —
|
|
863
955
|
// `clean`/`unconfirmed` above already reflect ONLY the primary server's
|
|
@@ -877,6 +969,8 @@ async function collectBatchDiagnostics(files, severity, lspService, options) {
|
|
|
877
969
|
clean,
|
|
878
970
|
unconfirmed,
|
|
879
971
|
timedOut,
|
|
972
|
+
outcomeCounts,
|
|
973
|
+
incompleteFiles: Math.max(0, files.length - results.length),
|
|
880
974
|
};
|
|
881
975
|
}
|
|
882
976
|
async function runBatchFileDiagnostics(absPaths, severity, lspService, options) {
|
|
@@ -887,7 +981,7 @@ async function runBatchFileDiagnostics(absPaths, severity, lspService, options)
|
|
|
887
981
|
details: { mode: "batch", severity, filesChecked: 0 },
|
|
888
982
|
};
|
|
889
983
|
}
|
|
890
|
-
const { results, fileErrors, lspHealthWarnings, total, truncated, display, primaryDisplay, auxiliaryDisplay, clean, unconfirmed, timedOut, } = await collectBatchDiagnostics(absPaths, severity, lspService, options);
|
|
984
|
+
const { results, fileErrors, lspHealthWarnings, total, truncated, display, primaryDisplay, auxiliaryDisplay, clean, unconfirmed, timedOut, outcomeCounts, incompleteFiles, } = await collectBatchDiagnostics(absPaths, severity, lspService, options);
|
|
891
985
|
const lines = [
|
|
892
986
|
`Files checked: ${results.length}`,
|
|
893
987
|
`Total diagnostics: ${total}`,
|
|
@@ -895,6 +989,20 @@ async function runBatchFileDiagnostics(absPaths, severity, lspService, options)
|
|
|
895
989
|
];
|
|
896
990
|
if (options.waitMs !== undefined)
|
|
897
991
|
lines.push(`Wait budget: ${options.waitMs}ms`);
|
|
992
|
+
lines.push(`Outcomes: ${Object.entries(outcomeCounts)
|
|
993
|
+
.map(([outcome, count]) => `${outcome}=${count}`)
|
|
994
|
+
.join(", ")}`);
|
|
995
|
+
const notConfirmed = outcomeCounts.inconclusive +
|
|
996
|
+
outcomeCounts.unavailable +
|
|
997
|
+
outcomeCounts.unsupported +
|
|
998
|
+
outcomeCounts.failed +
|
|
999
|
+
incompleteFiles;
|
|
1000
|
+
if (notConfirmed > 0) {
|
|
1001
|
+
lines.push("", `Checks not confirmed: ${notConfirmed} (inconclusive/unavailable/unsupported/failed or not started).`);
|
|
1002
|
+
}
|
|
1003
|
+
if (incompleteFiles > 0) {
|
|
1004
|
+
lines.push("", `Batch incomplete: ${incompleteFiles} file${incompleteFiles === 1 ? "" : "s"} were not confirmed because the batch was aborted.`);
|
|
1005
|
+
}
|
|
898
1006
|
if (fileErrors.length > 0)
|
|
899
1007
|
lines.push("", "File errors:", ...fileErrors);
|
|
900
1008
|
if (lspHealthWarnings.length > 0) {
|
|
@@ -945,6 +1053,15 @@ async function runBatchFileDiagnostics(absPaths, severity, lspService, options)
|
|
|
945
1053
|
cleanFiles: clean,
|
|
946
1054
|
unconfirmedFiles: unconfirmed,
|
|
947
1055
|
timedOutFiles: timedOut > 0 ? timedOut : undefined,
|
|
1056
|
+
outcomes: results.map((result) => ({
|
|
1057
|
+
file: result.file,
|
|
1058
|
+
outcome: result.outcome,
|
|
1059
|
+
reason: result.inconclusiveReason ?? result.error ?? result.unavailable,
|
|
1060
|
+
primaryDiagnosticsCount: result.diagnostics.filter((diagnostic) => diagnostic.source === result.primaryServerId).length,
|
|
1061
|
+
auxiliaryDiagnosticsCount: result.diagnostics.filter((diagnostic) => diagnostic.source !== result.primaryServerId).length,
|
|
1062
|
+
})),
|
|
1063
|
+
outcomeCounts,
|
|
1064
|
+
incompleteFiles: incompleteFiles > 0 ? incompleteFiles : undefined,
|
|
948
1065
|
fileErrors: fileErrors.length > 0 ? fileErrors : undefined,
|
|
949
1066
|
lspHealthWarnings: lspHealthWarnings.length > 0 ? lspHealthWarnings : undefined,
|
|
950
1067
|
},
|
|
@@ -6,7 +6,7 @@ server's `initialize` handshake. Generated by `node scripts/server-capabilities.
|
|
|
6
6
|
absent on the generating host are listed under *Unavailable* — run in a
|
|
7
7
|
provisioned environment (the nightly) to capture those rows.
|
|
8
8
|
|
|
9
|
-
_Last generated: 2026-07-
|
|
9
|
+
_Last generated: 2026-07-28 on linux; 30 servers captured, 11 unavailable._
|
|
10
10
|
|
|
11
11
|
## Diagnostic mode + navigation/edit operations
|
|
12
12
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-lens",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.73",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Real-time code feedback for pi — LSP, linters, formatters, type-checking, structural analysis & booboo",
|
|
6
6
|
"repository": {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
16
|
"build": "tsc --project tsconfig.build.json",
|
|
17
|
-
"build:dist": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && npx --yes -p typescript@
|
|
17
|
+
"build:dist": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && npx --yes -p typescript@7.0.2 tsc --project tsconfig.dist.json --noCheck && npm run bundle:dist",
|
|
18
18
|
"bundle:dist": "node scripts/bundle-dist.mjs",
|
|
19
19
|
"prepare": "npm run build:dist && node scripts/download-grammars.js --core --dest grammars",
|
|
20
20
|
"lint": "tsc --project tsconfig.json",
|
|
@@ -82,10 +82,10 @@
|
|
|
82
82
|
"banner.png"
|
|
83
83
|
],
|
|
84
84
|
"dependencies": {
|
|
85
|
-
"@ast-grep/cli": "^0.
|
|
86
|
-
"@ast-grep/napi": "^0.
|
|
87
|
-
"@earendil-works/pi-tui": "^0.
|
|
88
|
-
"js-yaml": "^
|
|
85
|
+
"@ast-grep/cli": "^0.45.0",
|
|
86
|
+
"@ast-grep/napi": "^0.45.0",
|
|
87
|
+
"@earendil-works/pi-tui": "^0.82.1",
|
|
88
|
+
"js-yaml": "^5.2.2",
|
|
89
89
|
"minimatch": "^10.2.5",
|
|
90
90
|
"pidusage": "^4.0.1",
|
|
91
91
|
"typebox": "^1.0.0",
|
|
@@ -104,12 +104,11 @@
|
|
|
104
104
|
},
|
|
105
105
|
"devDependencies": {
|
|
106
106
|
"@biomejs/biome": "^2.4.10",
|
|
107
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
108
|
-
"@types/js-yaml": "^4.0.9",
|
|
107
|
+
"@earendil-works/pi-coding-agent": "^0.82.1",
|
|
109
108
|
"@types/node": "^26.0.0",
|
|
110
109
|
"@types/pidusage": "^2.0.5",
|
|
111
110
|
"@vitest/coverage-v8": "^4.1.5",
|
|
112
|
-
"typescript": "^
|
|
111
|
+
"typescript": "^7.0.2",
|
|
113
112
|
"typescript-language-server": "^5.1.3",
|
|
114
113
|
"vitest": "^4.1.1"
|
|
115
114
|
},
|