pi-lens 3.8.69 → 3.8.71

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 (147) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/README.md +31 -13
  3. package/dist/clients/bootstrap.js +3 -1
  4. package/dist/clients/bus-events-logger.js +53 -0
  5. package/dist/clients/bus-publish.js +45 -2
  6. package/dist/clients/cache/rule-cache.js +9 -5
  7. package/dist/clients/cache-manager.js +19 -13
  8. package/dist/clients/code-quality-warnings.js +27 -0
  9. package/dist/clients/dead-code-client.js +5 -23
  10. package/dist/clients/diagnostic-dispositions.js +298 -0
  11. package/dist/clients/diagnostics-publish.js +38 -2
  12. package/dist/clients/dispatch/auxiliary-lsp.js +144 -6
  13. package/dist/clients/dispatch/dispatcher.js +6 -1
  14. package/dist/clients/dispatch/facts/function-facts.js +83 -0
  15. package/dist/clients/dispatch/integration.js +19 -4
  16. package/dist/clients/dispatch/rules/async-noise.js +1 -1
  17. package/dist/clients/dispatch/rules/async-unnecessary-wrapper.js +1 -1
  18. package/dist/clients/dispatch/rules/cors-wildcard.js +1 -1
  19. package/dist/clients/dispatch/rules/error-obscuring.js +2 -1
  20. package/dist/clients/dispatch/rules/error-swallowing.js +2 -1
  21. package/dist/clients/dispatch/rules/framework-call-noise.js +67 -0
  22. package/dist/clients/dispatch/rules/high-complexity.js +12 -1
  23. package/dist/clients/dispatch/rules/high-fan-out.js +14 -3
  24. package/dist/clients/dispatch/rules/high-import-coupling.js +1 -1
  25. package/dist/clients/dispatch/rules/missing-error-propagation.js +1 -1
  26. package/dist/clients/dispatch/rules/no-commented-credentials.js +1 -1
  27. package/dist/clients/dispatch/rules/pass-through-wrappers.js +1 -1
  28. package/dist/clients/dispatch/rules/placeholder-comments.js +1 -1
  29. package/dist/clients/dispatch/rules/unsafe-boundary.js +1 -1
  30. package/dist/clients/dispatch/runners/ast-grep-napi.js +75 -18
  31. package/dist/clients/dispatch/runners/lsp.js +23 -34
  32. package/dist/clients/dispatch/runners/oxlint.js +1 -1
  33. package/dist/clients/dispatch/suppress-writer.js +68 -0
  34. package/dist/clients/dispatch/utils/lsp-diagnostics.js +4 -1
  35. package/dist/clients/disposition-logger.js +39 -0
  36. package/dist/clients/disposition-publish.js +109 -0
  37. package/dist/clients/file-utils.js +94 -33
  38. package/dist/clients/format-events-publish.js +299 -0
  39. package/dist/clients/git-tracked-ignore.js +208 -0
  40. package/dist/clients/gitleaks-client.js +40 -5
  41. package/dist/clients/installer/index.js +123 -6
  42. package/dist/clients/instance-reaper.js +229 -2
  43. package/dist/clients/instance-registry.js +141 -2
  44. package/dist/clients/json-cache-read.js +65 -0
  45. package/dist/clients/knip-client.js +35 -38
  46. package/dist/clients/language-profile.js +11 -11
  47. package/dist/clients/lens-engine.js +12 -0
  48. package/dist/clients/lens-map.js +1079 -0
  49. package/dist/clients/log-cleanup.js +61 -19
  50. package/dist/clients/lsp/aggregation.js +69 -2
  51. package/dist/clients/lsp/cascade-tier.js +31 -19
  52. package/dist/clients/lsp/client.js +124 -14
  53. package/dist/clients/lsp/config.js +27 -1
  54. package/dist/clients/lsp/index.js +1028 -68
  55. package/dist/clients/lsp/server-strategies.js +17 -8
  56. package/dist/clients/lsp/server.js +80 -28
  57. package/dist/clients/lsp/tsserver-sync.js +145 -0
  58. package/dist/clients/lsp/workspace-diagnostics-cache.js +172 -0
  59. package/dist/clients/lsp-budget.js +148 -0
  60. package/dist/clients/mcp/session.js +9 -0
  61. package/dist/clients/middle-man-analysis.js +260 -0
  62. package/dist/clients/module-report.js +173 -11
  63. package/dist/clients/ndjson-logger.js +24 -0
  64. package/dist/clients/opengrep-client.js +217 -0
  65. package/dist/clients/opengrep-config.js +2 -10
  66. package/dist/clients/path-utils.js +66 -0
  67. package/dist/clients/pipeline.js +5 -1
  68. package/dist/clients/project-diagnostics/cache.js +5 -12
  69. package/dist/clients/project-diagnostics/extractors.js +21 -1
  70. package/dist/clients/project-diagnostics/fresh-fetch.js +275 -0
  71. package/dist/clients/project-diagnostics/runner-adapters/opengrep.js +40 -0
  72. package/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +46 -0
  73. package/dist/clients/project-snapshot.js +3 -6
  74. package/dist/clients/quiet-window.js +57 -2
  75. package/dist/clients/read-expansion.js +2 -3
  76. package/dist/clients/read-guard-tool-lines.js +10 -0
  77. package/dist/clients/read-guard.js +1 -1
  78. package/dist/clients/resource-sampler.js +254 -0
  79. package/dist/clients/review-graph/builder.js +322 -42
  80. package/dist/clients/review-graph/import-resolvers.js +53 -19
  81. package/dist/clients/review-graph/symbol-id.js +57 -0
  82. package/dist/clients/runtime-agent-end.js +26 -0
  83. package/dist/clients/runtime-coordinator.js +9 -1
  84. package/dist/clients/runtime-session.js +93 -17
  85. package/dist/clients/runtime-tool-call.js +789 -0
  86. package/dist/clients/runtime-tool-result.js +15 -2
  87. package/dist/clients/runtime-turn.js +62 -13
  88. package/dist/clients/safe-spawn.js +48 -2
  89. package/dist/clients/session-state-store.js +6 -9
  90. package/dist/clients/sgconfig.js +2 -9
  91. package/dist/clients/source-filter.js +17 -25
  92. package/dist/clients/source-walker.js +66 -0
  93. package/dist/clients/startup-scan.js +105 -43
  94. package/dist/clients/symbol-containment.js +24 -0
  95. package/dist/clients/test-runner-client.js +544 -29
  96. package/dist/clients/tree-sitter-symbol-extractor.js +12 -5
  97. package/dist/clients/typos-config.js +2 -11
  98. package/dist/clients/widget-state.js +83 -1
  99. package/dist/clients/write-ordering-guard.js +58 -0
  100. package/dist/clients/zizmor-config.js +29 -10
  101. package/dist/index.js +25511 -19414
  102. package/dist/mcp/server.js +95 -19
  103. package/dist/tools/activate-tools.js +72 -0
  104. package/dist/tools/ast-dump.js +2 -8
  105. package/dist/tools/lens-diagnostic-mark.js +158 -0
  106. package/dist/tools/lens-diagnostics.js +327 -33
  107. package/dist/tools/lsp-diagnostics.js +525 -105
  108. package/dist/tools/module-report.js +18 -5
  109. package/docs/agent-tools.md +136 -0
  110. package/docs/ast-grep_rules_catalog.md +564 -0
  111. package/docs/astplayground.md +178 -0
  112. package/docs/audit1.md +177 -0
  113. package/docs/custom-rules.md +231 -0
  114. package/docs/dependencies.md +54 -0
  115. package/docs/dispositions.md +85 -0
  116. package/docs/env_variables.md +10 -0
  117. package/docs/environment-variables.md +83 -0
  118. package/docs/fable.md +140 -0
  119. package/docs/features.md +338 -0
  120. package/docs/globalconfig.md +79 -0
  121. package/docs/language-coverage.md +48 -0
  122. package/docs/lsp-capability-matrix.md +157 -0
  123. package/docs/lsp-latency-benchmark.md +169 -0
  124. package/docs/mcp.md +438 -0
  125. package/docs/module-report-read-symbol.md +258 -0
  126. package/docs/servercapabilities.md +118 -0
  127. package/docs/subagent-compat.md +281 -0
  128. package/docs/tools.md +16 -0
  129. package/docs/tools_improvement2.md +345 -0
  130. package/docs/tree-sitter_rules_catalog.md +296 -0
  131. package/docs/usage.md +129 -0
  132. package/docs/word-index.md +150 -0
  133. package/package.json +10 -1
  134. package/rules/ast-grep-rules/rule-tests/find-import-file-without-extension-test.yml +4 -0
  135. package/rules/ast-grep-rules/rule-tests/no-javascript-url-js-test.yml +5 -0
  136. package/rules/ast-grep-rules/rule-tests/no-javascript-url-test.yml +9 -0
  137. package/rules/ast-grep-rules/rules/array-callback-return-js.yml +1 -1
  138. package/rules/ast-grep-rules/rules/find-import-file-without-extension.yml +3 -1
  139. package/rules/ast-grep-rules/rules/jwt-no-verify-js.yml +1 -1
  140. package/rules/ast-grep-rules/rules/no-extra-boolean-cast-js.yml +1 -1
  141. package/rules/ast-grep-rules/rules/no-implied-eval-js.yml +1 -1
  142. package/rules/ast-grep-rules/rules/no-javascript-url-js.yml +13 -1
  143. package/rules/ast-grep-rules/rules/no-javascript-url.yml +12 -0
  144. package/rules/ast-grep-rules/rules/no-sql-in-code-js.yml +1 -1
  145. package/rules/ast-grep-rules/rules/weak-rsa-key-js.yml +1 -1
  146. package/skills/pi-lens-ast-grep/SKILL.md +3 -1
  147. package/dist/clients/production-readiness.js +0 -493
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Shared "read a JSON cache file off disk" boilerplate (#676).
3
+ *
4
+ * At least 7 sites across the codebase hand-duplicated the same shape:
5
+ * `JSON.parse(fs.readFileSync(path, "utf-8"))` → structural/version guard →
6
+ * return the parsed value, or `undefined`/`null` on ANY throw (missing file,
7
+ * unreadable, corrupt JSON, or a `validate` callback itself throwing). This
8
+ * module extracts only that read/parse/try-catch mechanics; every call
9
+ * site's own "is this actually a valid, current-version cache?" logic stays
10
+ * put, as the `validate` callback — consolidation is only meant to remove
11
+ * the duplicated plumbing, not to unify subtly different validation rules
12
+ * that call sites had for good reason (e.g. a second `ruleHash` field check
13
+ * alongside the version check).
14
+ *
15
+ * `validate` returning `undefined` means "treat this like a parse failure" —
16
+ * the same fail-open posture every existing site already had for a
17
+ * wrong-version/wrong-shape cache.
18
+ */
19
+ import * as fs from "node:fs";
20
+ import { promises as fsPromises } from "node:fs";
21
+ /**
22
+ * Synchronous read: `JSON.parse(fs.readFileSync(path, "utf-8"))`, run through
23
+ * `validate`, with any failure (missing file, unreadable, corrupt JSON,
24
+ * `validate` throwing) swallowed to `undefined`.
25
+ *
26
+ * `onError`, when provided, is invoked with the caught error BEFORE
27
+ * `undefined` is returned — for sites that log a read failure today (e.g.
28
+ * `CacheManager`'s verbose `this.log(...)`) rather than silently swallowing
29
+ * it. `onError` itself is never allowed to throw the failure back out.
30
+ */
31
+ export function readJsonCache(path, validate, onError) {
32
+ try {
33
+ const parsed = JSON.parse(fs.readFileSync(path, "utf-8"));
34
+ return validate(parsed);
35
+ }
36
+ catch (err) {
37
+ try {
38
+ onError?.(err);
39
+ }
40
+ catch {
41
+ /* onError must never mask the original fail-open behavior */
42
+ }
43
+ return undefined;
44
+ }
45
+ }
46
+ /**
47
+ * Async sibling of {@link readJsonCache}, for call sites already on the
48
+ * `fs.promises` API (e.g. `session-state-store.ts`). Same fail-open contract.
49
+ */
50
+ export async function readJsonCacheAsync(path, validate, onError) {
51
+ try {
52
+ const raw = await fsPromises.readFile(path, "utf-8");
53
+ const parsed = JSON.parse(raw);
54
+ return validate(parsed);
55
+ }
56
+ catch (err) {
57
+ try {
58
+ onError?.(err);
59
+ }
60
+ catch {
61
+ /* onError must never mask the original fail-open behavior */
62
+ }
63
+ return undefined;
64
+ }
65
+ }
@@ -9,9 +9,9 @@
9
9
  * Docs: https://knip.dev/
10
10
  */
11
11
  import * as fs from "node:fs";
12
- import * as os from "node:os";
13
12
  import * as path from "node:path";
14
- import { isAtOrAboveHomeDir } from "./path-utils.js";
13
+ import { getProjectDataDir } from "./file-utils.js";
14
+ import { findNearestMarkerRoot } from "./path-utils.js";
15
15
  import { safeSpawnAsync } from "./safe-spawn.js";
16
16
  const EMPTY_RESULT = {
17
17
  success: false,
@@ -54,44 +54,15 @@ export class KnipClient {
54
54
  * previously this fell back to `startDir`, which on a bare cwd like
55
55
  * `/home/v` caused knip to recurse through every project and balloon
56
56
  * memory/CPU.
57
+ *
58
+ * Delegates to the shared path-utils helper (refs #625) — never treats a
59
+ * package/knip config at or above $HOME as the project (escapes the
60
+ * workspace, #296/#250), and never walks past a `.git`/`.hg`/`.svn`
61
+ * boundary to pick up an unrelated parent's package.json (Unity/non-JS
62
+ * repos often have no package.json at their own root).
57
63
  */
58
64
  resolveProjectRoot(startDir, homeDirOverride) {
59
- const markers = [
60
- "package.json",
61
- "knip.json",
62
- "knip.ts",
63
- "knip.config.js",
64
- "knip.config.ts",
65
- ];
66
- const boundaries = [".git", ".hg", ".svn"];
67
- const homeDir = path.resolve(homeDirOverride ?? os.homedir());
68
- let current = path.resolve(startDir);
69
- // Safety bound: in practice depths are ~10. This cap just prevents a
70
- // pathological symlink loop from hanging the search.
71
- for (let depth = 0; depth < 64; depth++) {
72
- // Never treat a package/knip config at $HOME — or above it, such as
73
- // /home/package.json from a shared parent — as the project for startup
74
- // analysis. That escapes the workspace and can make knip scan the whole
75
- // home tree from an empty folder (#296/#250).
76
- if (isAtOrAboveHomeDir(current, homeDir)) {
77
- return null;
78
- }
79
- if (markers.some((m) => fs.existsSync(path.join(current, m)))) {
80
- return current;
81
- }
82
- // Do not escape the current repository just to find an unrelated parent
83
- // package.json. Unity/non-JS repos often have no package.json at their
84
- // root; walking past their .git boundary can accidentally pick up
85
- // ~/package.json and make knip scan the entire home directory.
86
- if (boundaries.some((m) => fs.existsSync(path.join(current, m)))) {
87
- return null;
88
- }
89
- const parent = path.dirname(current);
90
- if (parent === current)
91
- return null;
92
- current = parent;
93
- }
94
- return null;
65
+ return findNearestMarkerRoot(startDir, ["package.json", "knip.json", "knip.ts", "knip.config.js", "knip.config.ts"], { boundaries: [".git", ".hg", ".svn"], homeDir: homeDirOverride });
95
66
  }
96
67
  /**
97
68
  * Check if knip CLI is available, auto-install if not
@@ -175,6 +146,29 @@ export class KnipClient {
175
146
  return promise;
176
147
  }
177
148
  async runAnalyze(targetDir) {
149
+ // Cache dir is routed through pi-lens's project-data-dir convention (NOT
150
+ // knip's own default `./node_modules/.cache/knip`) so it lives alongside
151
+ // every other project cache (see cache-manager.ts, call-graph.ts) and is
152
+ // covered by the existing `.pi-lens/` gitignore entry.
153
+ //
154
+ // Caveat (per knip's docs): a cached run does NOT pick up newly-added
155
+ // `.gitignore` files automatically — the cache must be deleted to detect
156
+ // them. Not auto-handled here; this is a documented tradeoff, not a bug.
157
+ const cacheLocation = path.join(getProjectDataDir(targetDir), "cache", "knip");
158
+ // knip (verified against 6.26.0) silently fails to persist the cache when
159
+ // `--cache-location` points at a directory that doesn't exist yet: its
160
+ // internal auto-mkdir throws ENOENT (swallowed internally, debug-logged
161
+ // only) on Windows, so the very first run — and every run after, since the
162
+ // dir never gets created — degrades to an uncached scan with no error
163
+ // surfaced. Pre-creating the dir avoids that path entirely; matches the
164
+ // mkdirSync-before-spawn convention call-graph.ts already uses for its
165
+ // cache file's parent dir.
166
+ try {
167
+ fs.mkdirSync(cacheLocation, { recursive: true });
168
+ }
169
+ catch (err) {
170
+ this.log(`Failed to pre-create knip cache dir ${cacheLocation}: ${err}`);
171
+ }
178
172
  const args = [
179
173
  "--reporter=json",
180
174
  "--include",
@@ -183,6 +177,9 @@ export class KnipClient {
183
177
  // it makes knip exit 2 with zero output, silently disabling the scan —
184
178
  // verified against knip 6.20. Valid member-level type here is enumMembers.)
185
179
  "files,exports,types,dependencies,unlisted,enumMembers",
180
+ "--cache",
181
+ "--cache-location",
182
+ cacheLocation,
186
183
  ];
187
184
  const result = await safeSpawnAsync("knip", args, {
188
185
  timeout: ANALYSIS_TIMEOUT_MS,
@@ -1,9 +1,10 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { detectFileKind } from "./file-kinds.js";
4
- import { getProjectIgnoreMatcher, isExcludedDirName, } from "./file-utils.js";
4
+ import { getProjectIgnoreMatcher } from "./file-utils.js";
5
5
  import { LANGUAGE_POLICY, } from "./language-policy.js";
6
6
  import { getSourceFiles } from "./scan-utils.js";
7
+ import { readDirEntriesSafe, shouldRecurseIntoDir } from "./source-walker.js";
7
8
  export const SUPPORTED_FILE_KINDS = [
8
9
  "jsts",
9
10
  "python",
@@ -281,6 +282,10 @@ const MAX_WARMUP_SOURCE_FILES = 2000;
281
282
  export async function collectSourceFilesForWarmup(rootDir, maxFiles = MAX_WARMUP_SOURCE_FILES, yieldEvery = 100) {
282
283
  const root = path.resolve(rootDir);
283
284
  const ignoreMatcher = getProjectIgnoreMatcher(root);
285
+ // #703: prime the tracked-files set once before the walk so a tracked file
286
+ // matching a `.gitignore`/global pattern still counts toward language
287
+ // detection. Fail-open on no-git/spawn failure.
288
+ await ignoreMatcher.ensureTrackedIndex();
284
289
  const stack = [root];
285
290
  const out = [];
286
291
  let processedSinceYield = 0;
@@ -288,20 +293,15 @@ export async function collectSourceFilesForWarmup(rootDir, maxFiles = MAX_WARMUP
288
293
  const current = stack.pop();
289
294
  if (!current)
290
295
  continue;
291
- let entries = [];
292
- try {
293
- entries = fs.readdirSync(current, { withFileTypes: true });
294
- }
295
- catch {
296
- continue;
297
- }
296
+ const entries = readDirEntriesSafe(current);
298
297
  for (const entry of entries) {
299
298
  const fullPath = path.join(current, entry.name);
300
299
  if (entry.isDirectory()) {
301
- if (isExcludedDirName(entry.name))
302
- continue;
303
- if (ignoreMatcher.isIgnored(fullPath, true))
300
+ // Never checked symlinks — always follows them (unlike
301
+ // source-filter.ts's collectSourceFiles*, refs #191).
302
+ if (!shouldRecurseIntoDir(entry, fullPath, { ignoreMatcher, followSymlinks: true })) {
304
303
  continue;
304
+ }
305
305
  stack.push(fullPath);
306
306
  }
307
307
  else if (entry.isFile()) {
@@ -15,6 +15,7 @@
15
15
  */
16
16
  import { getDiagnosticTracker } from "./diagnostic-tracker.js";
17
17
  import { getLatencyReports, } from "./dispatch/integration.js";
18
+ import { getResourceFootprint as getResourceFootprintSnapshot, } from "./instance-registry.js";
18
19
  import { initLSPConfig } from "./lsp/config.js";
19
20
  import { getLSPService } from "./lsp/index.js";
20
21
  import { getOrLoadWarmWordIndex } from "./mcp/analyze.js";
@@ -57,6 +58,17 @@ export function diagnosticStats() {
57
58
  export function ensureLspConfig(cwd) {
58
59
  return initLSPConfig(cwd);
59
60
  }
61
+ /**
62
+ * #620: total CPU/RAM footprint attributable to pi-lens across every process
63
+ * it owns — every registered instance's host, plus that instance's live LSP
64
+ * children. Reads the machine-global `~/.pi-lens/instances.json` registry, so
65
+ * this answers across ALL concurrent pi-lens sessions/worktrees on the box,
66
+ * not just this one. Best-effort: reflects whatever heartbeats have landed so
67
+ * far — a stale-heartbeat instance simply reports its last-sampled numbers.
68
+ */
69
+ export function resourceFootprint() {
70
+ return getResourceFootprintSnapshot();
71
+ }
60
72
  function toSymbolSearchHit(result) {
61
73
  const line = result.lines[0] ?? 1;
62
74
  return {