pi-lens 3.8.71 → 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 +152 -1
- package/README.md +31 -22
- package/config/markdownlint/core.json +6 -0
- package/dist/clients/atomic-write.js +83 -0
- package/dist/clients/config-enabled-shape.js +39 -0
- package/dist/clients/deps/js-yaml.js +1 -1
- package/dist/clients/diagnostic-dispositions.js +64 -2
- package/dist/clients/dispatch/integration.js +2 -1
- package/dist/clients/dispatch/runners/ast-grep-napi.js +60 -7
- package/dist/clients/dispatch/runners/lsp.js +54 -25
- package/dist/clients/dispatch/runners/markdownlint.js +7 -4
- package/dist/clients/dispatch/runners/pyright.js +1 -0
- package/dist/clients/dispatch/runners/tree-sitter.js +1 -1
- package/dist/clients/env-utils.js +38 -0
- package/dist/clients/file-utils.js +21 -3
- package/dist/clients/instance-registry.js +32 -21
- package/dist/clients/jscpd-client.js +52 -39
- package/dist/clients/language-profile.js +12 -19
- package/dist/clients/lens-config.js +106 -27
- package/dist/clients/lens-engine.js +129 -3
- package/dist/clients/lsp/cascade-tier.js +1 -38
- package/dist/clients/lsp/client.js +3 -1
- package/dist/clients/lsp/index.js +707 -109
- package/dist/clients/lsp/server-strategies.js +3 -224
- package/dist/clients/lsp/server.js +1 -1
- package/dist/clients/lsp/wait-policy/capability-snapshot.js +1 -0
- package/dist/clients/lsp/wait-policy/classification.js +63 -0
- package/dist/clients/lsp/wait-policy/index.js +10 -0
- package/dist/clients/lsp/wait-policy/strategies.js +244 -0
- package/dist/clients/lsp-budget.js +54 -16
- package/dist/clients/mcp/analyze.js +1 -1
- package/dist/clients/mcp/host-shim.js +12 -8
- package/dist/clients/mcp/ipc.js +118 -0
- package/dist/clients/mcp/session.js +2 -2
- package/dist/clients/pipeline.js +11 -6
- package/dist/clients/project-diagnostics/fresh-fetch.js +20 -1
- package/dist/clients/project-diagnostics/scanner.js +37 -6
- package/dist/clients/project-lens-config.js +167 -3
- package/dist/clients/project-report.js +627 -0
- package/dist/clients/project-scale.js +239 -0
- package/dist/clients/project-scan-policy.js +11 -1
- package/dist/clients/recent-touches.js +6 -11
- package/dist/clients/review-graph/builder.js +170 -13
- package/dist/clients/review-graph/import-resolvers.js +149 -13
- package/dist/clients/review-graph/tsconfig-paths.js +242 -0
- package/dist/clients/review-graph/workspace-modules.js +21 -9
- package/dist/clients/rules-scanner.js +15 -2
- package/dist/clients/runtime-agent-end.js +104 -17
- package/dist/clients/runtime-config.js +4 -0
- package/dist/clients/runtime-coordinator.js +50 -1
- package/dist/clients/runtime-session.js +41 -3
- package/dist/clients/runtime-tool-result.js +4 -3
- package/dist/clients/runtime-turn.js +4 -1
- package/dist/clients/safe-spawn.js +264 -40
- package/dist/clients/session-lifecycle.js +36 -0
- package/dist/clients/session-state-store.js +8 -3
- package/dist/clients/source-filter.js +136 -61
- package/dist/clients/source-walker.js +89 -0
- package/dist/clients/startup-scan.js +189 -111
- package/dist/clients/subagent-mode.js +14 -3
- package/dist/clients/todo-scanner.js +5 -1
- package/dist/clients/tool-policy.js +26 -4
- package/dist/clients/warm-attach.js +204 -0
- package/dist/clients/word-index.js +36 -5
- package/dist/clients/workspace-topology.js +266 -0
- package/dist/index.js +15205 -11330
- package/dist/mcp/server.js +125 -10
- package/dist/tools/ast-grep-search.js +21 -0
- package/dist/tools/lens-diagnostic-mark.js +198 -7
- package/dist/tools/lens-diagnostics.js +119 -21
- package/dist/tools/lsp-diagnostics.js +143 -10
- package/dist/tools/lsp-navigation.js +11 -2
- package/dist/tools/project-report.js +94 -0
- package/dist/tools/symbol-search.js +32 -8
- package/docs/environment-variables.md +10 -0
- package/docs/features.md +7 -1
- package/docs/globalconfig.md +70 -0
- package/docs/servercapabilities.md +1 -1
- package/docs/usage.md +13 -2
- 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
- package/scripts/download-grammars.js +1 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Project-level `.pi-lens.json` config loader.
|
|
3
3
|
*
|
|
4
4
|
* Reads an optional `.pi-lens.json` (or `pi-lens.json`) at the project root and
|
|
5
|
-
* surfaces
|
|
5
|
+
* surfaces the fields the rest of pi-lens honors:
|
|
6
6
|
*
|
|
7
7
|
* - `ignore` — gitignore-style glob patterns added to every scan (LSP walk,
|
|
8
8
|
* fact-rules, tree-sitter, jscpd, knip, review graph, source-filter). Wired
|
|
@@ -13,6 +13,28 @@
|
|
|
13
13
|
* rules["high-complexity"].threshold — cyclomatic complexity (default 15)
|
|
14
14
|
* rules["high-fan-out"].threshold — distinct-function calls (default 20)
|
|
15
15
|
*
|
|
16
|
+
* - `maxProjectFiles` — the base project-size scale knob (#776). Read by
|
|
17
|
+
* `clients/project-scale.ts`'s `getProjectScaleBase`, which derives the
|
|
18
|
+
* five subsystem size budgets (project-diagnostics scanner, review graph,
|
|
19
|
+
* startup scan, jscpd, word index) as documented ratios of this value.
|
|
20
|
+
*
|
|
21
|
+
* - `reviewGraph.maxFiles` — explicit review-graph file-budget override
|
|
22
|
+
* (#775 R2), for monorepos that want a bigger graph than
|
|
23
|
+
* `project-scale.ts`'s adaptive taper would derive from
|
|
24
|
+
* `maxProjectFiles` alone. Tolerantly parsed (numeric strings coerce via
|
|
25
|
+
* `toPositiveFinite`, same as `maxProjectFiles`) and clamped to
|
|
26
|
+
* `[100, 20_000]` — a value outside that range is silently clamped
|
|
27
|
+
* rather than rejected (still an explicit, deliberate opt-in; only a
|
|
28
|
+
* non-numeric/non-positive value warns and is dropped). Read by
|
|
29
|
+
* `getReviewGraphMaxFilesDerived`, where it takes precedence over the
|
|
30
|
+
* taper (but the subsystem's own PRE-EXISTING
|
|
31
|
+
* `PI_LENS_REVIEW_GRAPH_MAX_FILES` env override still wins outright over
|
|
32
|
+
* both, unchanged).
|
|
33
|
+
*
|
|
34
|
+
* - `format.enabled`, `autofix.enabled`, and
|
|
35
|
+
* `actionableWarnings.autoFix.enabled` — project-owned mutation controls.
|
|
36
|
+
* These can disable pi-lens writes while leaving diagnostics enabled.
|
|
37
|
+
*
|
|
16
38
|
* The file is loaded once per `(path, mtimeMs)` and cached — editing the file
|
|
17
39
|
* invalidates the cache so the next access sees the new values without
|
|
18
40
|
* restarting pi. Discovery is cached by starting directory and validated by the
|
|
@@ -25,14 +47,32 @@
|
|
|
25
47
|
*
|
|
26
48
|
* A malformed file is treated as "no config" and logged once — we never want a
|
|
27
49
|
* stray syntax error in user-edited JSON to break diagnostics.
|
|
50
|
+
*
|
|
51
|
+
* `findPiLensConfigInDir` / `loadPiLensConfigInDir` are the per-directory
|
|
52
|
+
* (no upward walk) counterparts used by `file-utils.ts`'s
|
|
53
|
+
* `getProjectIgnoreMatcher` to layer NESTED `.pi-lens.json` `ignore` fields
|
|
54
|
+
* the same way nested `.gitignore`s are already layered (#783): every
|
|
55
|
+
* ancestor directory between the git root and a scanned file is checked for
|
|
56
|
+
* its own config file, so a package-local `.pi-lens.json`'s `ignore`
|
|
57
|
+
* patterns apply to files inside that package, in addition to (and with
|
|
58
|
+
* higher precedence than) the root config's `ignore` patterns.
|
|
28
59
|
*/
|
|
29
60
|
import * as fs from "node:fs";
|
|
61
|
+
import * as os from "node:os";
|
|
30
62
|
import * as path from "node:path";
|
|
31
|
-
import {
|
|
63
|
+
import { parseEnabledShape } from "./config-enabled-shape.js";
|
|
64
|
+
import { toPositiveFinite } from "./env-utils.js";
|
|
65
|
+
import { isAtOrAboveHomeDir, walkUpDirs } from "./path-utils.js";
|
|
66
|
+
import { findPiLensConfigMarkerInDir } from "./workspace-topology.js";
|
|
32
67
|
const PROJECT_CONFIG_BASENAMES = [".pi-lens.json", "pi-lens.json"];
|
|
68
|
+
/** Clamp bounds for `reviewGraph.maxFiles` — see the field's doc comment above. */
|
|
69
|
+
const REVIEW_GRAPH_MAX_FILES_MIN = 100;
|
|
70
|
+
const REVIEW_GRAPH_MAX_FILES_MAX = 20_000;
|
|
33
71
|
export const EMPTY_PROJECT_CONFIG = {
|
|
34
72
|
ignore: [],
|
|
35
73
|
rules: {},
|
|
74
|
+
maxProjectFiles: undefined,
|
|
75
|
+
reviewGraph: undefined,
|
|
36
76
|
raw: undefined,
|
|
37
77
|
configPath: undefined,
|
|
38
78
|
};
|
|
@@ -62,6 +102,71 @@ export function resetProjectLensConfigCache() {
|
|
|
62
102
|
discoveryCache.clear();
|
|
63
103
|
warnedInvalidConfigs.clear();
|
|
64
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Look for a `.pi-lens.json`/`pi-lens.json` directly IN `dir` — no upward
|
|
107
|
+
* walk. Used to layer nested per-package configs (#783) the same way
|
|
108
|
+
* `file-utils.ts` layers nested `.gitignore`s: each ancestor directory
|
|
109
|
+
* between the git root and a target file is checked for its OWN config
|
|
110
|
+
* file, independent of whatever config `loadPiLensProjectConfig`'s upward
|
|
111
|
+
* walk would find starting from `dir`.
|
|
112
|
+
*
|
|
113
|
+
* Sourced from the shared workspace-topology marker index (#806) — one
|
|
114
|
+
* `readdir` pass per directory visit collects this marker alongside
|
|
115
|
+
* `tsconfig.json`/workspace-manifest markers other consumers need for the
|
|
116
|
+
* SAME directory, instead of each subsystem re-probing it independently.
|
|
117
|
+
*/
|
|
118
|
+
export function findPiLensConfigInDir(dir) {
|
|
119
|
+
const marker = findPiLensConfigMarkerInDir(dir);
|
|
120
|
+
if (!marker)
|
|
121
|
+
return undefined;
|
|
122
|
+
return { path: marker.path, dir: marker.dir, mtimeMs: marker.mtimeMs };
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Find the closest config, between an edited file and the project root, that
|
|
126
|
+
* explicitly defines one mutation flag. The walk uses the shared primitive
|
|
127
|
+
* and refuses to inspect HOME or any ancestor of HOME.
|
|
128
|
+
*/
|
|
129
|
+
export function findNestedProjectMutationValue(name, editedFilePath, projectRoot, homeDir = os.homedir()) {
|
|
130
|
+
const root = path.resolve(projectRoot);
|
|
131
|
+
const start = path.dirname(path.resolve(editedFilePath));
|
|
132
|
+
for (const dir of walkUpDirs(start)) {
|
|
133
|
+
if (isAtOrAboveHomeDir(dir, homeDir))
|
|
134
|
+
break;
|
|
135
|
+
const rel = path.relative(root, dir);
|
|
136
|
+
if (rel.startsWith("..") || path.isAbsolute(rel))
|
|
137
|
+
break;
|
|
138
|
+
const config = loadPiLensConfigInDir(dir);
|
|
139
|
+
const enabled = name === "no-autoformat"
|
|
140
|
+
? config.format?.enabled
|
|
141
|
+
: name === "no-autofix"
|
|
142
|
+
? config.autofix?.enabled
|
|
143
|
+
: config.actionableWarnings?.autoFix?.enabled;
|
|
144
|
+
if (enabled !== undefined)
|
|
145
|
+
return { value: enabled, dir };
|
|
146
|
+
if (dir === root)
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Load the `.pi-lens.json`/`pi-lens.json` directly IN `dir` (no upward
|
|
153
|
+
* walk) — the per-directory counterpart to `loadPiLensProjectConfig`'s
|
|
154
|
+
* upward-walking discovery. Shares `configCache` (keyed by absolute config
|
|
155
|
+
* path + mtime), so a directory whose config was already loaded via the
|
|
156
|
+
* upward-walk path (e.g. the git root itself) is not re-read here.
|
|
157
|
+
*/
|
|
158
|
+
export function loadPiLensConfigInDir(dir) {
|
|
159
|
+
const info = findPiLensConfigInDir(dir);
|
|
160
|
+
if (!info)
|
|
161
|
+
return EMPTY_PROJECT_CONFIG;
|
|
162
|
+
const cached = configCache.get(info.path);
|
|
163
|
+
if (cached && cached.mtimeMs === info.mtimeMs) {
|
|
164
|
+
return cached.config;
|
|
165
|
+
}
|
|
166
|
+
const config = parseConfigFile(info.path);
|
|
167
|
+
configCache.set(info.path, { mtimeMs: info.mtimeMs, config });
|
|
168
|
+
return config;
|
|
169
|
+
}
|
|
65
170
|
export function findPiLensProjectConfig(startDir) {
|
|
66
171
|
const cacheKey = path.resolve(startDir);
|
|
67
172
|
const cached = discoveryCache.get(cacheKey);
|
|
@@ -119,6 +224,9 @@ function warnInvalidConfigOnce(configPath, reason) {
|
|
|
119
224
|
warnedInvalidConfigs.add(key);
|
|
120
225
|
console.error(`[pi-lens] ignoring invalid project config ${configPath}: ${reason}`);
|
|
121
226
|
}
|
|
227
|
+
function parseEnabledConfig(configPath, fieldPath, value) {
|
|
228
|
+
return parseEnabledShape(value, fieldPath, (reason) => warnInvalidConfigOnce(configPath, reason));
|
|
229
|
+
}
|
|
122
230
|
function parseConfigFile(configPath) {
|
|
123
231
|
let raw;
|
|
124
232
|
try {
|
|
@@ -137,6 +245,20 @@ function parseConfigFile(configPath) {
|
|
|
137
245
|
const ignore = Array.isArray(obj.ignore)
|
|
138
246
|
? obj.ignore.filter((p) => typeof p === "string")
|
|
139
247
|
: [];
|
|
248
|
+
const format = parseEnabledConfig(configPath, "format", obj.format);
|
|
249
|
+
const autofix = parseEnabledConfig(configPath, "autofix", obj.autofix);
|
|
250
|
+
const actionableWarningsRaw = obj.actionableWarnings &&
|
|
251
|
+
typeof obj.actionableWarnings === "object" &&
|
|
252
|
+
!Array.isArray(obj.actionableWarnings)
|
|
253
|
+
? obj.actionableWarnings
|
|
254
|
+
: undefined;
|
|
255
|
+
if (obj.actionableWarnings !== undefined && !actionableWarningsRaw) {
|
|
256
|
+
warnInvalidConfigOnce(configPath, "actionableWarnings must be an object");
|
|
257
|
+
}
|
|
258
|
+
const actionableWarningsAutoFix = parseEnabledConfig(configPath, "actionableWarnings.autoFix", actionableWarningsRaw?.autoFix);
|
|
259
|
+
const actionableWarnings = actionableWarningsRaw
|
|
260
|
+
? { autoFix: actionableWarningsAutoFix }
|
|
261
|
+
: undefined;
|
|
140
262
|
const rules = {};
|
|
141
263
|
if (obj.rules && typeof obj.rules === "object" && !Array.isArray(obj.rules)) {
|
|
142
264
|
const rawRules = obj.rules;
|
|
@@ -155,5 +277,47 @@ function parseConfigFile(configPath) {
|
|
|
155
277
|
}
|
|
156
278
|
}
|
|
157
279
|
}
|
|
158
|
-
|
|
280
|
+
let maxProjectFiles;
|
|
281
|
+
if ("maxProjectFiles" in obj) {
|
|
282
|
+
if (typeof obj.maxProjectFiles === "number" &&
|
|
283
|
+
Number.isFinite(obj.maxProjectFiles) &&
|
|
284
|
+
obj.maxProjectFiles > 0) {
|
|
285
|
+
maxProjectFiles = obj.maxProjectFiles;
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
warnInvalidConfigOnce(configPath, "maxProjectFiles must be a positive finite number");
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
let reviewGraph;
|
|
292
|
+
if (obj.reviewGraph !== undefined) {
|
|
293
|
+
if (!obj.reviewGraph ||
|
|
294
|
+
typeof obj.reviewGraph !== "object" ||
|
|
295
|
+
Array.isArray(obj.reviewGraph)) {
|
|
296
|
+
warnInvalidConfigOnce(configPath, "reviewGraph must be an object");
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
const rg = obj.reviewGraph;
|
|
300
|
+
if ("maxFiles" in rg) {
|
|
301
|
+
const parsed = toPositiveFinite(rg.maxFiles);
|
|
302
|
+
if (parsed > 0) {
|
|
303
|
+
const clamped = Math.min(REVIEW_GRAPH_MAX_FILES_MAX, Math.max(REVIEW_GRAPH_MAX_FILES_MIN, Math.floor(parsed)));
|
|
304
|
+
reviewGraph = { maxFiles: clamped };
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
warnInvalidConfigOnce(configPath, "reviewGraph.maxFiles must be a positive finite number");
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
ignore,
|
|
314
|
+
rules,
|
|
315
|
+
format,
|
|
316
|
+
autofix,
|
|
317
|
+
actionableWarnings,
|
|
318
|
+
maxProjectFiles,
|
|
319
|
+
reviewGraph,
|
|
320
|
+
raw,
|
|
321
|
+
configPath,
|
|
322
|
+
};
|
|
159
323
|
}
|