pi-lens 3.8.71 → 3.8.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/CHANGELOG.md +106 -1
  2. package/README.md +8 -0
  3. package/config/markdownlint/core.json +6 -0
  4. package/dist/clients/atomic-write.js +83 -0
  5. package/dist/clients/config-enabled-shape.js +39 -0
  6. package/dist/clients/diagnostic-dispositions.js +64 -2
  7. package/dist/clients/dispatch/integration.js +2 -1
  8. package/dist/clients/dispatch/runners/lsp.js +54 -25
  9. package/dist/clients/dispatch/runners/markdownlint.js +7 -4
  10. package/dist/clients/dispatch/runners/pyright.js +1 -0
  11. package/dist/clients/dispatch/runners/tree-sitter.js +1 -1
  12. package/dist/clients/env-utils.js +38 -0
  13. package/dist/clients/file-utils.js +21 -3
  14. package/dist/clients/instance-registry.js +20 -20
  15. package/dist/clients/jscpd-client.js +52 -39
  16. package/dist/clients/language-profile.js +12 -19
  17. package/dist/clients/lens-config.js +106 -27
  18. package/dist/clients/lens-engine.js +129 -3
  19. package/dist/clients/lsp/cascade-tier.js +1 -38
  20. package/dist/clients/lsp/client.js +3 -1
  21. package/dist/clients/lsp/index.js +621 -95
  22. package/dist/clients/lsp/server-strategies.js +3 -224
  23. package/dist/clients/lsp/server.js +1 -1
  24. package/dist/clients/lsp/wait-policy/capability-snapshot.js +1 -0
  25. package/dist/clients/lsp/wait-policy/classification.js +63 -0
  26. package/dist/clients/lsp/wait-policy/index.js +10 -0
  27. package/dist/clients/lsp/wait-policy/strategies.js +244 -0
  28. package/dist/clients/lsp-budget.js +54 -16
  29. package/dist/clients/mcp/analyze.js +1 -1
  30. package/dist/clients/mcp/host-shim.js +12 -8
  31. package/dist/clients/mcp/ipc.js +118 -0
  32. package/dist/clients/mcp/session.js +2 -2
  33. package/dist/clients/pipeline.js +11 -6
  34. package/dist/clients/project-diagnostics/fresh-fetch.js +20 -1
  35. package/dist/clients/project-diagnostics/scanner.js +37 -6
  36. package/dist/clients/project-lens-config.js +167 -3
  37. package/dist/clients/project-report.js +627 -0
  38. package/dist/clients/project-scale.js +239 -0
  39. package/dist/clients/project-scan-policy.js +11 -1
  40. package/dist/clients/recent-touches.js +6 -11
  41. package/dist/clients/review-graph/builder.js +170 -13
  42. package/dist/clients/review-graph/import-resolvers.js +149 -13
  43. package/dist/clients/review-graph/tsconfig-paths.js +242 -0
  44. package/dist/clients/review-graph/workspace-modules.js +21 -9
  45. package/dist/clients/rules-scanner.js +15 -2
  46. package/dist/clients/runtime-agent-end.js +104 -17
  47. package/dist/clients/runtime-config.js +4 -0
  48. package/dist/clients/runtime-coordinator.js +50 -1
  49. package/dist/clients/runtime-session.js +41 -3
  50. package/dist/clients/runtime-tool-result.js +4 -3
  51. package/dist/clients/runtime-turn.js +4 -1
  52. package/dist/clients/safe-spawn.js +264 -40
  53. package/dist/clients/session-lifecycle.js +36 -0
  54. package/dist/clients/session-state-store.js +8 -3
  55. package/dist/clients/source-filter.js +136 -61
  56. package/dist/clients/source-walker.js +89 -0
  57. package/dist/clients/startup-scan.js +189 -111
  58. package/dist/clients/subagent-mode.js +14 -3
  59. package/dist/clients/todo-scanner.js +5 -1
  60. package/dist/clients/tool-policy.js +26 -4
  61. package/dist/clients/warm-attach.js +204 -0
  62. package/dist/clients/word-index.js +36 -5
  63. package/dist/clients/workspace-topology.js +266 -0
  64. package/dist/index.js +13826 -10251
  65. package/dist/mcp/server.js +125 -10
  66. package/dist/tools/ast-grep-search.js +21 -0
  67. package/dist/tools/lens-diagnostic-mark.js +198 -7
  68. package/dist/tools/lens-diagnostics.js +119 -21
  69. package/dist/tools/lsp-diagnostics.js +17 -1
  70. package/dist/tools/lsp-navigation.js +11 -2
  71. package/dist/tools/project-report.js +94 -0
  72. package/dist/tools/symbol-search.js +32 -8
  73. package/docs/environment-variables.md +10 -0
  74. package/docs/features.md +7 -1
  75. package/docs/globalconfig.md +70 -0
  76. package/docs/servercapabilities.md +1 -1
  77. package/docs/usage.md +13 -2
  78. package/package.json +1 -1
  79. 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 two fields the rest of pi-lens now honors:
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 { walkUpDirs } from "./path-utils.js";
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
- return { ignore, rules, raw, configPath };
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
  }