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.
Files changed (83) hide show
  1. package/CHANGELOG.md +152 -1
  2. package/README.md +31 -22
  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/deps/js-yaml.js +1 -1
  7. package/dist/clients/diagnostic-dispositions.js +64 -2
  8. package/dist/clients/dispatch/integration.js +2 -1
  9. package/dist/clients/dispatch/runners/ast-grep-napi.js +60 -7
  10. package/dist/clients/dispatch/runners/lsp.js +54 -25
  11. package/dist/clients/dispatch/runners/markdownlint.js +7 -4
  12. package/dist/clients/dispatch/runners/pyright.js +1 -0
  13. package/dist/clients/dispatch/runners/tree-sitter.js +1 -1
  14. package/dist/clients/env-utils.js +38 -0
  15. package/dist/clients/file-utils.js +21 -3
  16. package/dist/clients/instance-registry.js +32 -21
  17. package/dist/clients/jscpd-client.js +52 -39
  18. package/dist/clients/language-profile.js +12 -19
  19. package/dist/clients/lens-config.js +106 -27
  20. package/dist/clients/lens-engine.js +129 -3
  21. package/dist/clients/lsp/cascade-tier.js +1 -38
  22. package/dist/clients/lsp/client.js +3 -1
  23. package/dist/clients/lsp/index.js +707 -109
  24. package/dist/clients/lsp/server-strategies.js +3 -224
  25. package/dist/clients/lsp/server.js +1 -1
  26. package/dist/clients/lsp/wait-policy/capability-snapshot.js +1 -0
  27. package/dist/clients/lsp/wait-policy/classification.js +63 -0
  28. package/dist/clients/lsp/wait-policy/index.js +10 -0
  29. package/dist/clients/lsp/wait-policy/strategies.js +244 -0
  30. package/dist/clients/lsp-budget.js +54 -16
  31. package/dist/clients/mcp/analyze.js +1 -1
  32. package/dist/clients/mcp/host-shim.js +12 -8
  33. package/dist/clients/mcp/ipc.js +118 -0
  34. package/dist/clients/mcp/session.js +2 -2
  35. package/dist/clients/pipeline.js +11 -6
  36. package/dist/clients/project-diagnostics/fresh-fetch.js +20 -1
  37. package/dist/clients/project-diagnostics/scanner.js +37 -6
  38. package/dist/clients/project-lens-config.js +167 -3
  39. package/dist/clients/project-report.js +627 -0
  40. package/dist/clients/project-scale.js +239 -0
  41. package/dist/clients/project-scan-policy.js +11 -1
  42. package/dist/clients/recent-touches.js +6 -11
  43. package/dist/clients/review-graph/builder.js +170 -13
  44. package/dist/clients/review-graph/import-resolvers.js +149 -13
  45. package/dist/clients/review-graph/tsconfig-paths.js +242 -0
  46. package/dist/clients/review-graph/workspace-modules.js +21 -9
  47. package/dist/clients/rules-scanner.js +15 -2
  48. package/dist/clients/runtime-agent-end.js +104 -17
  49. package/dist/clients/runtime-config.js +4 -0
  50. package/dist/clients/runtime-coordinator.js +50 -1
  51. package/dist/clients/runtime-session.js +41 -3
  52. package/dist/clients/runtime-tool-result.js +4 -3
  53. package/dist/clients/runtime-turn.js +4 -1
  54. package/dist/clients/safe-spawn.js +264 -40
  55. package/dist/clients/session-lifecycle.js +36 -0
  56. package/dist/clients/session-state-store.js +8 -3
  57. package/dist/clients/source-filter.js +136 -61
  58. package/dist/clients/source-walker.js +89 -0
  59. package/dist/clients/startup-scan.js +189 -111
  60. package/dist/clients/subagent-mode.js +14 -3
  61. package/dist/clients/todo-scanner.js +5 -1
  62. package/dist/clients/tool-policy.js +26 -4
  63. package/dist/clients/warm-attach.js +204 -0
  64. package/dist/clients/word-index.js +36 -5
  65. package/dist/clients/workspace-topology.js +266 -0
  66. package/dist/index.js +15205 -11330
  67. package/dist/mcp/server.js +125 -10
  68. package/dist/tools/ast-grep-search.js +21 -0
  69. package/dist/tools/lens-diagnostic-mark.js +198 -7
  70. package/dist/tools/lens-diagnostics.js +119 -21
  71. package/dist/tools/lsp-diagnostics.js +143 -10
  72. package/dist/tools/lsp-navigation.js +11 -2
  73. package/dist/tools/project-report.js +94 -0
  74. package/dist/tools/symbol-search.js +32 -8
  75. package/docs/environment-variables.md +10 -0
  76. package/docs/features.md +7 -1
  77. package/docs/globalconfig.md +70 -0
  78. package/docs/servercapabilities.md +1 -1
  79. package/docs/usage.md +13 -2
  80. package/package.json +8 -9
  81. package/rules/tree-sitter-queries/typescript/no-equality-in-for-condition.yml +1 -0
  82. package/rules/tree-sitter-queries/typescript/no-jump-in-finally.yml +1 -0
  83. package/scripts/download-grammars.js +1 -0
@@ -21,6 +21,7 @@ import { getLSPService } from "./lsp/index.js";
21
21
  import { getOrLoadWarmWordIndex } from "./mcp/analyze.js";
22
22
  import { scanProjectDiagnostics } from "./project-diagnostics/scanner.js";
23
23
  import * as path from "node:path";
24
+ import { minimatch } from "./deps/minimatch.js";
24
25
  import { normalizeMapKey } from "./path-utils.js";
25
26
  import { loadProjectSnapshot } from "./project-snapshot.js";
26
27
  import { centralityFromReverseDeps, deserializeWordIndex, searchWordIndex, triggerBackgroundWordIndexBuild, } from "./word-index.js";
@@ -31,6 +32,7 @@ export { ipcPathForCwd, requestWarmAnalyze, } from "./mcp/ipc.js";
31
32
  export { analyzeFileFresh, resolveRebuildScript, runRebuild, summarizeScan, } from "./mcp/review.js";
32
33
  export { runSessionStart, runTurnEnd, } from "./mcp/session.js";
33
34
  export { moduleReport, readEnclosing, readSymbol, renderCompactModuleReport, } from "./module-report.js";
35
+ export { projectReport, renderCompactProjectReport, } from "./project-report.js";
34
36
  // --- Query wrappers (own the remaining internal reach-ins) -------------------
35
37
  /** Recent dispatch latency reports (latency.log schema), newest first. */
36
38
  export function recentLatency(limit = 5, fileFilter) {
@@ -45,6 +47,19 @@ export function recentLatency(limit = 5, fileFilter) {
45
47
  export function projectScan(cwd, maxFiles) {
46
48
  return scanProjectDiagnostics({ cwd, tier: "cheap", maxFiles });
47
49
  }
50
+ /**
51
+ * #784: `scanTruncated` (#760) reaches this seam intact but, until now, no
52
+ * caller rendered it — a capped scan read as a complete clean sweep to the
53
+ * agent/user. One shared, unit-testable line adapters can append to their
54
+ * rendered summary, matching the #777 warm-skip notify's override-hint style.
55
+ * Returns `undefined` when the scan was not truncated (no line to append).
56
+ */
57
+ export function scanTruncationNotice(snapshot) {
58
+ if (!snapshot.scanTruncated)
59
+ return undefined;
60
+ return (`⚠ Scan truncated at ${snapshot.filesScanned} file(s) — results are partial; ` +
61
+ "raise maxProjectFiles in .pi-lens.json to scan fully.");
62
+ }
48
63
  /** Alive LSP client count + per-server status. */
49
64
  export function lspStatus() {
50
65
  const lsp = getLSPService();
@@ -69,6 +84,97 @@ export function ensureLspConfig(cwd) {
69
84
  export function resourceFootprint() {
70
85
  return getResourceFootprintSnapshot();
71
86
  }
87
+ // Same language identifiers ast_grep_search's `lang` param accepts
88
+ // (tools/shared.ts's LANGUAGES) mapped to source file extensions. Duplicated
89
+ // here rather than imported — clients/ never reaches into tools/ (see
90
+ // AGENTS.md's MCP-mirror layering note) — so this is symbol_search's own small
91
+ // copy, scoped to what its `lang` filter needs (extension matching only, no
92
+ // AST/LSP concerns).
93
+ const SYMBOL_SEARCH_LANG_EXTENSIONS = {
94
+ bash: [".sh", ".bash"],
95
+ c: [".c", ".h"],
96
+ cpp: [".cpp", ".cc", ".cxx", ".hpp", ".hh", ".hxx"],
97
+ csharp: [".cs"],
98
+ css: [".css", ".scss", ".less"],
99
+ elixir: [".ex", ".exs"],
100
+ go: [".go"],
101
+ haskell: [".hs", ".lhs"],
102
+ html: [".html", ".htm"],
103
+ java: [".java"],
104
+ javascript: [".js", ".jsx", ".mjs", ".cjs"],
105
+ json: [".json", ".jsonc", ".json5"],
106
+ kotlin: [".kt", ".kts"],
107
+ lua: [".lua"],
108
+ nix: [".nix"],
109
+ php: [".php"],
110
+ python: [".py", ".pyi"],
111
+ ruby: [".rb", ".rake", ".gemspec"],
112
+ rust: [".rs"],
113
+ scala: [".scala", ".sc"],
114
+ solidity: [".sol"],
115
+ swift: [".swift"],
116
+ tsx: [".tsx"],
117
+ typescript: [".ts", ".mts", ".cts"],
118
+ yaml: [".yaml", ".yml"],
119
+ };
120
+ function fileMatchesLang(file, lang) {
121
+ const exts = SYMBOL_SEARCH_LANG_EXTENSIONS[lang];
122
+ if (!exts)
123
+ return false;
124
+ return exts.includes(path.extname(file).toLowerCase());
125
+ }
126
+ /** Same glob semantics as ast_grep_search's `paths`: a bare directory/file
127
+ * entry scopes its whole subtree (via a `/**` suffix match), a full glob
128
+ * pattern is matched as-is. Matched relative to `cwd` so absolute index paths
129
+ * and repo-relative globs line up regardless of platform path separators. */
130
+ function fileMatchesPathGlobs(file, cwd, globs) {
131
+ const rel = path.relative(cwd, file).split(path.sep).join("/");
132
+ const options = { dot: true, nocase: process.platform === "win32" };
133
+ return globs.some((raw) => {
134
+ const pattern = raw.split("\\").join("/");
135
+ return (minimatch(rel, pattern, options) ||
136
+ minimatch(rel, `${pattern}/**`, options));
137
+ });
138
+ }
139
+ function buildSymbolSearchFileFilter(cwd, options) {
140
+ const paths = options.paths?.filter((p) => p.trim().length > 0);
141
+ const lang = options.lang?.trim();
142
+ if (!paths?.length && !lang)
143
+ return undefined;
144
+ return (file) => {
145
+ if (paths?.length && !fileMatchesPathGlobs(file, cwd, paths))
146
+ return false;
147
+ if (lang && !fileMatchesLang(file, lang))
148
+ return false;
149
+ return true;
150
+ };
151
+ }
152
+ /**
153
+ * Attaches read-only graph signals to already-ranked hits (#771) — mutates
154
+ * each hit in place. `fanIn` reuses the SAME `centrality` map `symbolSearch`
155
+ * already computed for its ranking boost (zero extra cost); `complexity` is
156
+ * the highest per-symbol `cyclomaticComplexity` the (warm) review graph
157
+ * recorded for that file. Caller only invokes this when `graph` is defined —
158
+ * never builds one itself.
159
+ */
160
+ function annotateSymbolSearchHitsWithGraph(hits, centrality, graph) {
161
+ for (const hit of hits) {
162
+ const normalized = normalizeMapKey(path.resolve(hit.file));
163
+ const fanIn = centrality.get(hit.file) ?? 0;
164
+ let complexity;
165
+ for (const symbolId of graph.symbolNodesByFile.get(normalized) ?? []) {
166
+ const raw = graph.nodes.get(symbolId)?.metadata?.cyclomaticComplexity;
167
+ if (typeof raw === "number" &&
168
+ (complexity === undefined || raw > complexity)) {
169
+ complexity = raw;
170
+ }
171
+ }
172
+ hit.annotations = {
173
+ fanIn,
174
+ ...(complexity !== undefined ? { complexity } : {}),
175
+ };
176
+ }
177
+ }
72
178
  function toSymbolSearchHit(result) {
73
179
  const line = result.lines[0] ?? 1;
74
180
  return {
@@ -98,8 +204,22 @@ function toSymbolSearchHit(result) {
98
204
  * pilens_analyze yet this process, or #348 phase 2's forward-index isn't
99
205
  * available) — this function's public contract (available/hint/results shape)
100
206
  * is unchanged either way.
207
+ *
208
+ * `options.paths`/`options.lang` (#771) scope the word index BEFORE ranking
209
+ * (`searchWordIndex`'s `fileFilter`), so a surviving file's score is identical
210
+ * to an unfiltered run; omitting both reproduces prior output byte-for-byte.
211
+ *
212
+ * Every hit is additionally annotated (#771) with the graph signals already
213
+ * available when the cached review graph happens to be warm —
214
+ * `getCachedReviewGraph` (`clients/review-graph/builder.ts`) is a READ-ONLY
215
+ * accessor: an in-memory miss falls through to a persisted-disk read, and
216
+ * NOTHING here ever triggers a fresh build. A cold cache (no in-memory graph,
217
+ * no persisted snapshot) simply omits `annotations` — this function's latency
218
+ * profile is unchanged either way. The module is dynamic-imported (mirroring
219
+ * module-report.ts's own lazy load of it) so an unused review graph costs
220
+ * nothing on this hot path.
101
221
  */
102
- export function symbolSearch(query, cwd, limit = 20) {
222
+ export async function symbolSearch(query, cwd, limit = 20, options = {}) {
103
223
  const snapshot = loadProjectSnapshot(cwd);
104
224
  const index = getOrLoadWarmWordIndex(cwd) ?? deserializeWordIndex(snapshot?.wordIndex);
105
225
  if (!index) {
@@ -114,11 +234,17 @@ export function symbolSearch(query, cwd, limit = 20) {
114
234
  // Boost well-connected files using the snapshot's reverse-dependency
115
235
  // (importedBy) counts; snapshot keys are normalized, index keys are raw.
116
236
  const centrality = centralityFromReverseDeps(index, snapshot?.reverseDeps, (file) => normalizeMapKey(path.resolve(file)));
117
- const results = searchWordIndex(index, query, { limit, centrality });
237
+ const fileFilter = buildSymbolSearchFileFilter(cwd, options);
238
+ const results = searchWordIndex(index, query, { limit, centrality, fileFilter });
239
+ const hits = results.map(toSymbolSearchHit);
240
+ const { getCachedReviewGraph } = await import("./review-graph/builder.js");
241
+ const graph = getCachedReviewGraph(cwd);
242
+ if (graph)
243
+ annotateSymbolSearchHitsWithGraph(hits, centrality, graph);
118
244
  return {
119
245
  available: true,
120
246
  query,
121
- results: results.map(toSymbolSearchHit),
247
+ results: hits,
122
248
  snapshotGeneratedAt: snapshot?.generatedAt,
123
249
  };
124
250
  }
@@ -57,8 +57,7 @@ import { logCascade } from "../cascade-logger.js";
57
57
  import { logLatency } from "../latency-logger.js";
58
58
  import { normalizeMapKey } from "../path-utils.js";
59
59
  import { registerQuietWindowTask } from "../quiet-window.js";
60
- import { getServersForFileWithConfig } from "./config.js";
61
- import { getStrategy } from "./server-strategies.js";
60
+ export { classifyCascadeWaitTier, classifyServerWaitTier, } from "./wait-policy/classification.js";
62
61
  // --- Kill switch (lazy, memoized — house style per clients/runtime-config.ts /
63
62
  // clients/quiet-window.ts's isQuietWindowEnabled) ---
64
63
  let _enabledCache;
@@ -75,42 +74,6 @@ export function isTierAwareCascadeEnabled() {
75
74
  export function _resetTierAwareCascadeEnabledForTests() {
76
75
  _enabledCache = undefined;
77
76
  }
78
- /**
79
- * Classify whether `filePath`'s PRIMARY language server is a cascade-lane
80
- * Tier-3 (push-only, silent-on-clean) server. Ambiguous or missing capability
81
- * data is always `"waits"` (today's behavior) — this function must never be
82
- * the reason a real answer gets missed.
83
- */
84
- export function classifyCascadeWaitTier(lspService, filePath, snapshots) {
85
- void lspService; // kept in the signature for call-site clarity/typing only
86
- const servers = getServersForFileWithConfig(filePath).filter((s) => s.role !== "auxiliary");
87
- const primary = servers[0];
88
- if (!primary)
89
- return "waits";
90
- const snapshot = snapshots.find((s) => s.serverId === primary.id);
91
- if (!snapshot)
92
- return "waits"; // no live snapshot yet — fail-safe
93
- const mode = snapshot.workspaceDiagnosticsSupport?.mode;
94
- if (mode !== "push-only")
95
- return "waits"; // pull servers are always affirmative
96
- const strategy = getStrategy(primary.id);
97
- if (strategy.silentOnClean !== true)
98
- return "waits"; // 2*/unknown push-only
99
- // #524/#529/#541/#558: `silentOnClean` on a server-id-keyed strategy is
100
- // only proven against the variant it was actually measured against.
101
- // "typescript" today means either classic typescript-language-server
102
- // (confirmed silent-on-clean, 2026-07-12 dual-environment re-measurement)
103
- // or TS7's native `tsc --lsp --stdio` (the SAME re-measurement found it
104
- // publishes 2 version-less diagnostic sets on clean — NOT silent, a
105
- // drift from the earlier #541 measurement). A native-ts7 snapshot must
106
- // NOT inherit the classic verdict: fall through to "waits", the same
107
- // ambiguous/fail-safe path an unmarked or non-push-only server already
108
- // takes. `launchVariant === "classic"` or absent (older snapshots that
109
- // predate the marker) keeps today's tier-3 behavior exactly.
110
- if (snapshot.launchVariant === "native-ts7")
111
- return "waits";
112
- return "tier3-silent";
113
- }
114
77
  // Keyed by normalized file path. A later touch for the same file simply
115
78
  // replaces the earlier entry (only the most recent touch matters — an
116
79
  // older touch's diagnostics, if they ever arrive, are still a strict superset
@@ -22,7 +22,7 @@ import { applyWorkspaceEdit } from "./edits.js";
22
22
  import { recordLspChild, removeLspChild } from "../instance-registry.js";
23
23
  import { normalizeMapKey, uriToPath } from "./path-utils.js";
24
24
  import { ADVERTISED_POSITION_ENCODINGS, convertCharacterOffset, lineTextAt, negotiatePositionEncoding, } from "./position-encoding.js";
25
- import { getStrategy } from "./server-strategies.js";
25
+ import { getStrategy } from "./wait-policy/index.js";
26
26
  import { WatchedFilesQueue } from "./watch-queue.js";
27
27
  // Opt-in publishDiagnostics trace (PILENS_PUB_DEBUG=1) — read once, negligible
28
28
  // hot-path cost. Surfaces each server's publish behavior (version + count) to
@@ -703,6 +703,8 @@ export async function clientWaitForDiagnostics(state, filePath, timeoutMs, optio
703
703
  if (outcome.status === "clean")
704
704
  sawClean = true;
705
705
  }
706
+ if (options.pullOnly)
707
+ return;
706
708
  if (outcome.status === "found" || sawClean)
707
709
  return;
708
710
  }