pi-lens 3.8.46 → 3.8.50

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 (139) hide show
  1. package/CHANGELOG.md +168 -0
  2. package/README.md +106 -110
  3. package/clients/actionable-warnings-logger.ts +4 -6
  4. package/clients/actionable-warnings.ts +132 -22
  5. package/clients/agent-behavior-client.ts +12 -3
  6. package/clients/ast-grep-client.ts +210 -3
  7. package/clients/ast-grep-tool-logger.ts +174 -0
  8. package/clients/ast-grep-types.ts +15 -0
  9. package/clients/ast-grep-yaml-synth.ts +122 -0
  10. package/clients/bash-file-access.ts +220 -0
  11. package/clients/biome-client.ts +82 -30
  12. package/clients/bootstrap.ts +10 -0
  13. package/clients/cache/rule-cache.ts +4 -1
  14. package/clients/call-graph.ts +444 -0
  15. package/clients/cascade-logger.ts +4 -6
  16. package/clients/cascade-types.ts +19 -0
  17. package/clients/codebase-model.ts +194 -0
  18. package/clients/diagnostic-logger.ts +2 -4
  19. package/clients/dispatch/dispatcher.ts +18 -13
  20. package/clients/dispatch/facts/import-facts.ts +155 -5
  21. package/clients/dispatch/integration.ts +14 -14
  22. package/clients/dispatch/plan.ts +0 -4
  23. package/clients/dispatch/priorities.ts +0 -1
  24. package/clients/dispatch/rules/quality-rules.ts +1 -1
  25. package/clients/dispatch/rules/sonar-rules.ts +45 -71
  26. package/clients/dispatch/runners/biome.ts +15 -7
  27. package/clients/dispatch/runners/cpp-check.ts +44 -17
  28. package/clients/dispatch/runners/credo.ts +13 -6
  29. package/clients/dispatch/runners/dart-analyze.ts +101 -8
  30. package/clients/dispatch/runners/detekt.ts +82 -4
  31. package/clients/dispatch/runners/dotnet-build.ts +1 -0
  32. package/clients/dispatch/runners/elixir-check.ts +9 -7
  33. package/clients/dispatch/runners/eslint.ts +29 -7
  34. package/clients/dispatch/runners/fact-rules.ts +1 -1
  35. package/clients/dispatch/runners/go-vet.ts +1 -0
  36. package/clients/dispatch/runners/golangci-lint.ts +49 -0
  37. package/clients/dispatch/runners/index.ts +0 -5
  38. package/clients/dispatch/runners/lsp.ts +8 -0
  39. package/clients/dispatch/runners/markdownlint.ts +44 -0
  40. package/clients/dispatch/runners/mypy.ts +1 -0
  41. package/clients/dispatch/runners/oxlint.ts +84 -20
  42. package/clients/dispatch/runners/pyright.ts +1 -0
  43. package/clients/dispatch/runners/rust-clippy.ts +85 -26
  44. package/clients/dispatch/runners/sqlfluff.ts +66 -0
  45. package/clients/dispatch/runners/stylelint.ts +44 -0
  46. package/clients/dispatch/runners/swiftlint.ts +71 -1
  47. package/clients/dispatch/runners/tree-sitter.ts +10 -2
  48. package/clients/dispatch/runners/utils/diagnostic-parsers.ts +10 -1
  49. package/clients/dispatch/runners/utils/runner-helpers.ts +28 -2
  50. package/clients/dispatch/tool-profile.ts +0 -1
  51. package/clients/dispatch/types.ts +2 -0
  52. package/clients/env-utils.ts +45 -0
  53. package/clients/file-utils.ts +16 -0
  54. package/clients/formatters.ts +12 -1
  55. package/clients/gitleaks-client.ts +363 -0
  56. package/clients/govulncheck-client.ts +476 -0
  57. package/clients/installer/index.ts +30 -7
  58. package/clients/jscpd-client.ts +14 -1
  59. package/clients/latency-logger.ts +4 -6
  60. package/clients/lens-config.ts +56 -0
  61. package/clients/log-cleanup.ts +10 -8
  62. package/clients/lsp/client.ts +123 -9
  63. package/clients/lsp/edits.ts +66 -1
  64. package/clients/lsp/index.ts +334 -32
  65. package/clients/lsp/launch.ts +6 -8
  66. package/clients/lsp/server.ts +92 -24
  67. package/clients/oldtext-autopatch.ts +46 -0
  68. package/clients/path-utils.ts +41 -0
  69. package/clients/pipeline.ts +12 -30
  70. package/clients/project-conventions.ts +215 -0
  71. package/clients/project-snapshot.ts +18 -12
  72. package/clients/read-expansion.ts +87 -1
  73. package/clients/read-guard-logger.ts +4 -7
  74. package/clients/read-guard-tool-lines.ts +80 -12
  75. package/clients/read-guard.ts +10 -0
  76. package/clients/review-graph/builder.ts +94 -15
  77. package/clients/ruff-client.ts +17 -2
  78. package/clients/runtime-agent-end.ts +4 -2
  79. package/clients/runtime-config.ts +44 -0
  80. package/clients/runtime-coordinator.ts +21 -25
  81. package/clients/runtime-session.ts +163 -53
  82. package/clients/runtime-tool-result.ts +180 -2
  83. package/clients/runtime-turn.ts +89 -2
  84. package/clients/safe-spawn.ts +5 -1
  85. package/clients/search-read-registration.ts +89 -0
  86. package/clients/semgrep-config.ts +9 -19
  87. package/clients/sg-runner.ts +234 -9
  88. package/clients/tool-event.ts +24 -0
  89. package/clients/tool-policy.ts +91 -61
  90. package/clients/tree-sitter-client.ts +13 -0
  91. package/clients/tree-sitter-logger.ts +4 -6
  92. package/clients/tree-sitter-symbol-extractor.ts +249 -0
  93. package/clients/ts-service.ts +2 -2
  94. package/clients/widget-state.ts +382 -68
  95. package/commands/booboo.ts +0 -123
  96. package/index.ts +170 -96
  97. package/package.json +18 -9
  98. package/rules/ast-grep-rules/rule-schema.json +130 -0
  99. package/rules/ast-grep-rules/rules/no-mutable-export.yml +13 -0
  100. package/rules/ast-grep-rules/rules/no-octal-literal.yml +14 -0
  101. package/rules/ast-grep-rules/rules/no-sort-without-comparator.yml +14 -0
  102. package/rules/ast-grep-rules/rules/redos-nested-quantifier.yml +23 -0
  103. package/rules/ast-grep-rules/rules/switch-without-default.yml +16 -0
  104. package/rules/tree-sitter-queries/rule-schema.json +136 -0
  105. package/rules/tree-sitter-queries/typescript/no-equality-in-for-condition.yml +40 -0
  106. package/rules/tree-sitter-queries/typescript/no-jump-in-finally.yml +47 -0
  107. package/scripts/download-grammars.js +15 -0
  108. package/skills/ast-grep/SKILL.md +104 -24
  109. package/skills/lsp-navigation/SKILL.md +52 -68
  110. package/skills/write-ast-grep-rule/SKILL.md +106 -0
  111. package/skills/write-tree-sitter-rule/SKILL.md +122 -0
  112. package/tools/ast-dump.js +62 -0
  113. package/tools/ast-dump.ts +79 -0
  114. package/tools/ast-grep-replace.js +95 -6
  115. package/tools/ast-grep-replace.ts +137 -16
  116. package/tools/ast-grep-search.js +161 -9
  117. package/tools/ast-grep-search.ts +218 -19
  118. package/tools/lens-diagnostics.js +194 -0
  119. package/tools/lens-diagnostics.ts +248 -0
  120. package/tools/lsp-navigation.js +277 -9
  121. package/tools/lsp-navigation.ts +377 -9
  122. package/clients/amain-types.ts +0 -165
  123. package/clients/dispatch/runners/similarity.ts +0 -515
  124. package/clients/dispatch/runners/type-safety.ts +0 -194
  125. package/clients/project-index.ts +0 -403
  126. package/clients/state-matrix.ts +0 -202
  127. package/clients/type-safety-client.ts +0 -193
  128. /package/rules/tree-sitter-queries/{abap → abap-disabled}/delete-where.yml +0 -0
  129. /package/rules/tree-sitter-queries/{cobol → cobol-disabled}/alter-statement.yml +0 -0
  130. /package/rules/tree-sitter-queries/{cobol → cobol-disabled}/lock-table-cobol.yml +0 -0
  131. /package/rules/tree-sitter-queries/{plsql → plsql-disabled}/delete-update-where.yml +0 -0
  132. /package/rules/tree-sitter-queries/{plsql → plsql-disabled}/end-loop-semicolon.yml +0 -0
  133. /package/rules/tree-sitter-queries/{plsql → plsql-disabled}/fetch-bulk-collect-limit.yml +0 -0
  134. /package/rules/tree-sitter-queries/{plsql → plsql-disabled}/forallsave-exceptions.yml +0 -0
  135. /package/rules/tree-sitter-queries/{plsql → plsql-disabled}/lock-table.yml +0 -0
  136. /package/rules/tree-sitter-queries/{plsql → plsql-disabled}/nchar-nvarchar2-bytes.yml +0 -0
  137. /package/rules/tree-sitter-queries/{plsql → plsql-disabled}/no-synchronize.yml +0 -0
  138. /package/rules/tree-sitter-queries/{plsql → plsql-disabled}/not-null-initialization.yml +0 -0
  139. /package/rules/tree-sitter-queries/{plsql → plsql-disabled}/raise-application-error-codes.yml +0 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,174 @@ All notable changes to pi-lens will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [3.8.50] - 2026-06-07
8
+
9
+ ### Added
10
+
11
+ - **Function-level call graph + impact analysis (closes #154)** — a cross-file call graph is built at session-start (ref→def resolution, bidirectional callers/callees, in-degree centrality, ambiguity-discounted edges); at turn-end the symbols a modified file touches surface a `WillBreak`/`MayBreak`/`Review` impact advisory. Backed by `import-facts` extended to JS/JSX/MJS/CJS with dynamic imports, module-type detection and re-export edges, and a `review-graph` whose `MAIN_KINDS`/language mapping spans every WASM-backed grammar.
12
+
13
+ - **Internal codebase mental model (closes #155)** — a compact structural summary ranked by call-graph in-degree, cached to `<project-data>/cache/codebase-model.json`. Internal-only (a session-start debug line) until validated across real sessions; agent exposure + hybrid ranking are tracked in #162.
14
+
15
+ - **`lens_diagnostics` tool (closes #159)** — queries pi-lens's cached diagnostic state with no LSP/dispatch re-run. `mode=delta` = the current turn's fixable + code-quality warnings; `mode=all` = every file edited this session.
16
+
17
+ - **`ast_grep_search` results register as reads so a follow-up edit isn't blocked (refs #169)** — the search→edit flow (find where something must change, then edit those lines) was blocked by the read-guard because the search didn't count as a read. `ast_grep_search` now attaches the shown match locations to its result (`details.searchReads`), and the tool_result handler registers each as a read **± 2 lines** of context via the new `clients/search-read-registration.ts`. Only the shown lines are registered — never the whole file — so editing an unseen region is still guarded. (`lsp_navigation` and bash `grep` are the remaining parts of #169.)
18
+
19
+ - **Disable automatic context injection without disabling pi-lens (closes #165)** — a narrow opt-out for the prompt-cache cost of prepending automatic findings. `--no-lens-context` flag, `contextInjection.enabled: false` in `~/.pi-lens/config.json`, `PI_LENS_NO_CONTEXT_INJECTION=1` env, and a runtime `/lens-context-toggle` command. When off, the `context` hook stops prepending session-start guidance / turn-end findings / test findings, but everything else keeps running — tools, LSP, read-guard, formatting, inline tool-result feedback — and findings are still cached so `lens_diagnostics` and `/lens-health` work. Precedence: env → CLI flag → config.
20
+
21
+ ### Fixed
22
+
23
+ - **Read-guard tracks non-Read file access (closes #168, refs #169)** — bash file views (`cat`/`head`/`tail`/`sed -n`) register as reads with their exact line ranges; bash writes (`>`/`>>`/`tee`/`sed -i`/`cp`/`mv`/`touch`) register as authored-by-agent like the Write tool; search-tool matches register the shown lines ±2 context. So a follow-up edit to something the agent viewed, wrote, or searched is no longer falsely blocked. `grep`/`find`/`ls` are not treated as content reads.
24
+
25
+ - **Bash-written files are re-analyzed (no more stale diagnostics after `git checkout`/`git restore`)** — a bash command that rewrites working-tree content (redirects, `tee`, `sed -i`, `cp`/`mv`, `touch`, and now `git checkout -- <file>` / `git restore <file>`) never went through the edit-tool pipeline, so its diagnostics, `fileSeq`, and change-log stayed frozen at the pre-write state — e.g. restoring a file would keep reporting the old broken-state warnings on every later `lens_diagnostics` call. Each in-project file a bash command writes/restores is now re-run through the dispatch pipeline (via a synthetic write) so its analysis refreshes. Whole-tree git ops (`reset --hard`, `stash pop`, `revert`, branch switches) don't name files and aren't covered.
26
+
27
+ - **`LSP Inactive` footer status no longer rendered in red (closes #167)** — having no LSP server running for the current file (or after the idle timer releases them) is a passive state, not a fault, but it was painted in the `error` (red) color, implying something was broken. It now uses the neutral `dim` (grey) color; `LSP Active (n)` stays green. Surfacing genuine LSP *failures* in red is tracked in #170.
28
+
29
+ - **Extension load no longer requires the host coding-agent package in `node_modules`** — `index.ts` and `clients/read-guard-tool-lines.ts` imported a *runtime* value (`isToolCallEventType`) from `@earendil-works/pi-coding-agent`. pi installs extension deps with `npm install --omit=dev`, so that package isn't present at runtime; and pulling it in drags a huge transitive tree (LLM provider SDKs) whose deeply nested paths exceed Windows' `MAX_PATH`, breaking `git clean -fdx` on `pi update` (→ a half-deleted `node_modules` → `Cannot find module 'vscode-jsonrpc/node.js'`). The one-line discriminant is now inlined in `clients/tool-event.ts`, so every `@earendil-works/pi-coding-agent` import is type-only (erased at runtime) — matching the established pi-extension pattern (e.g. `nicobailon/pi-subagents`).
30
+
31
+ - **`js-yaml` moved from `devDependencies` to `dependencies`** — `clients/ast-grep-yaml-synth.ts` imports it at runtime, but it was declared dev-only, so a production (`--omit=dev`) install left it missing and the extension failed to load with `Cannot find package 'js-yaml'`. (`@types/js-yaml` stays dev-only.) The CI install-test (production tarball install + `tsx` load) now exercises this path so misplaced runtime deps are caught before release.
32
+
33
+ - **Lockfile kept committed and guarded against drift** — `package-lock.json` had silently drifted from `package.json` (the exact `web-tree-sitter` pin was recorded as `^0.25.10` in the lock), which makes `npm ci` delete `node_modules` then hard-fail. The lock is now regenerated in sync, and a new `npm run check:lockfile` guard (run in CI) fails the build if any declared dependency spec diverges from the lock — so the drift that started this can't recur. CI/release also switched from `npm ci` to `npm install` so a future desync degrades (self-heals) instead of hard-failing.
34
+
35
+ ### Changed
36
+
37
+ - **`lens_diagnostics` mode=all now shows the actual diagnostics, not just counts, and is no longer limited by the TUI's display cap** — previously it printed `file.ts 3W` with no indication of *what* the warnings were. It now lists each diagnostic in the same `L<line>: <message> [rule]` shape as the inline blocker output (blockers first, 🔴-marked), honouring the `severity` filter. The widget state keeps a separate **uncapped** per-file diagnostic list for the tool (the TUI still uses its 12-entry render cap), so `getFileDiagnosticSummaries()` exposes the **full** set instead of just the 12 the widget retained for rendering. The tool applies its own generous 50-per-file budget with an accurate `… N more in this file (showing 50 of N)` note (the old note double-counted via `blocking + errors + warnings`).
38
+
39
+ ### Added
40
+
41
+ - **Six new structural rules covering SonarCloud BLOCKER/CRITICAL TS gaps** — pure-AST checks (no taint analysis required), each with tests run through the production runner. ast-grep: `no-sort-without-comparator` (S2871 — `.sort()`/`.toSorted()` with no compare function), `no-octal-literal` (S1314 — legacy leading-zero octals), `no-mutable-export` (S6861 — exported `let`/`var`), `switch-without-default` (S131 — `switch` with no `default` clause). tree-sitter: `no-equality-in-for-condition` (S888 — `==`/`!=` as a `for`-loop exit test), `no-jump-in-finally` (S1143 — `return`/`break`/`continue`/`throw` written directly in a `finally` block). All `warning` severity.
42
+
43
+ - **`redos-nested-quantifier` ast-grep rule — flags catastrophic-backtracking (ReDoS) regex literals** — detects an unbounded quantifier nested inside an unbounded-quantified group (`(a+)+`, `(a*)*`, `([a-z]+)*`, `(\d+){2,}`, `(a{2,})+`), the classic CWE-1333 / S5852 exponential case. Fires only when both inner and outer quantifiers are unbounded (`+`, `*`, `{n,}`); bounded quantifiers like `{2,3}` are intentionally not flagged. Runs in the NAPI runner via `kind: regex_pattern` + a linear detector regex (no self-ReDoS). `warning` severity with fix guidance (bounded quantifier, atomic-group emulation, negated character class, or RE2/node-re2 for untrusted input).
44
+
45
+ - Extended oxfmt formatter to CSS, SCSS, Less, HTML, JSON, YAML, Markdown, MDX, GraphQL, TOML, Vue files. Updated tool-policy entries and added unit tests.
46
+
47
+ - **`ast_grep_search` / `ast_grep_replace` structural-intent parameters — `insideKind`, `hasKind`, `follows`, `precedes` (closes #125 Phase 3)** — agents can now express cross-context queries without writing YAML. `insideKind: "function_declaration"` restricts matches to nodes inside that ancestor kind (searches all ancestors via `stopBy: end`); `hasKind` restricts to nodes containing a descendant; `follows`/`precedes` restrict by sibling pattern. Parameters synthesize a YAML rule via `clients/ast-grep-yaml-synth.ts` and route through `sg scan --config`. For `ast_grep_replace`, a `fix:` field is added to the synthesized rule so `sg scan --update-all` applies the rewrite. When `rule:` (Phase 4) is also provided, it takes precedence. 22 new tests covering synthesizer output, constraint combinations, language canonicalisation, routing, and YAML content assertions.
48
+
49
+ - **`ast_grep_search` raw YAML rule passthrough — `rule` parameter (closes #125 Phase 4)** — passing a complete ast-grep YAML rule bypasses `sg run -p` entirely and routes through `sg scan --config`, unlocking `all`/`any`/`not`, `nthChild`, `regex`, field constraints, and multi-pattern rules. Each path is scanned independently and results are merged. Pagination (`skip`) works the same as the pattern path.
50
+
51
+ - **`ast_grep_search` and `ast_grep_replace` metavariable captures in output (refs #125)** — named captures (`$VAR`, `$$$ARGS`) from `sg --json=compact` appear below each match. Language field (`[TypeScript]`) surfaced per match.
52
+ - **SgRunner binary resolution extended with platform package and Homebrew fallback (refs #153)** — probes `@ast-grep/cli-{os}-{arch}` npm packages (walking up 5 directory levels) and Homebrew (`brew --prefix ast-grep`) before falling back to auto-install.
53
+
54
+ - **Read expansion ancestry chain (refs #153)** — `ExpandedRead` now includes `ancestry?: AncestorSymbol[]` (outermost first) so the full structural path is available (e.g. `ReviewManager → runSynthesis`). The session-start debug log now shows the full path instead of just the immediate enclosing symbol.
55
+
56
+ ### Fixed
57
+
58
+ - **Windows subprocess encoding (garbled tool output)** — `safeSpawnAsync` prefixes Windows shell commands with `chcp 65001 >nul 2>&1 &&` to force UTF-8 code page, eliminating garbled characters in `sg`/`biome`/`ruff` error messages.
59
+
60
+ - **Thrashing warning scoped to same tool+file pair** — consecutive counter resets when either the tool name or the file path changes; editing different files no longer triggers the warning.
61
+
62
+ - **Regex S5852 backtracking eliminated** — replaced `(.*?)` with `([^(]*)` and `/\r?\n/` with `/\r\n|\n/` in ast-grep-client and lsp-navigation.
63
+
64
+ - **`@earendil-works/pi-coding-agent` declared as optional peer dependency** — `devDependencies` retains the explicit version for local dev; install test updated to exclude host-provided peer from the `ERR_MODULE_NOT_FOUND` gate.
65
+
66
+ ### Performance
67
+
68
+ - **Read expansion limit raised from 60 to 100 lines** — expansion now fires for reads up to 100 lines, making it useful for the typical 80-100 line agent reads that previously fell outside the threshold.
69
+
70
+ ## [3.8.48] - 2026-06-05
71
+
72
+ ### Added
73
+
74
+ - **`ast_dump` tool — expose tree-sitter AST structure for pattern debugging (closes #156)** — new `ast_dump` tool parses a source snippet with `sg --debug-query=ast|cst` and returns an indented AST tree with 1-indexed line:col positions and source snippets per node. Named nodes only by default; `includeAnonymous: true` shows all CST nodes including punctuation. Use this when `ast_grep_search` returns zero matches and the correct node kind or field name is unknown. Invalid language returns a clear error; partial/error trees are returned as-is so syntax errors are visible.
75
+
76
+ - **`lsp_navigation` `rename_file` operation — LSP-aware source file rename (closes #148)** — new `rename_file` operation sends `workspace/willRenameFiles` to all active LSP servers, collects and deduplicates returned workspace edits (primary type-checker server wins on range conflicts), renames the file on disk, sends `workspace/didRenameFiles`, then re-syncs touched files in LSP. Preview mode (`apply: false`) shows the merged workspace edits without touching disk. Overlap detection across server edit sets throws a descriptive error rather than producing corrupted output.
77
+
78
+ - **`lsp_navigation` `capabilities` operation — cached server feature map (closes #149)** — new operation reads `serverCapabilities` from the post-`initialize` cached state and renders a per-server table of which `lsp_navigation` operations are actually supported (definition, references, hover, rename, codeAction, workspaceSymbol, implementation, signatureHelp, callHierarchy, workspaceDiagnostics, rename_file). No LSP round-trip. Scoped to a specific file or all active servers when `filePath` is omitted.
79
+
80
+ - **`lsp_navigation` symbol-to-column resolution (closes #147)** — omitting `character` and supplying `symbol` resolves the correct column automatically by scanning the target line. Full fallback chain: word-boundary regex match → same with `#N` occurrence selector (`symbol: "foo#2"` = second occurrence) → case-insensitive match → first non-whitespace character. Eliminates the dominant class of position-mismatch retries where the agent knew the line but guessed the column wrong.
81
+
82
+ - **`ast_grep_replace` stale-preview detection, `ast_grep_search` pagination, and strictness parameter (closes #151)** — three improvements to the ast-grep tools. (1) Before applying (`apply: true`), a dry-run re-validates that the pattern still matches; if files changed since the preview, returns a `stalePreview` error rather than applying against wrong content. (2) `ast_grep_search` accepts `skip: N` to offset into large result sets; truncated results include a "Use skip=50 for the next page" hint. (3) Both tools accept `strictness: "smart" | "relaxed" | "ast" | "cst" | "signature" | "template"` passed to `sg --strictness`; `"relaxed"` is the most useful for patterns that miss matches due to optional trailing commas or semicolons.
83
+
84
+ - **`ast_grep_search` and `ast_grep_replace` surface metavariable captures (refs #125)** — named captures (`$VAR`, `$$$ARGS`) from `sg --json=compact` output are now shown below each match: `$VAR=x $VALUE=foo(a, b, c)` and `$$$ARGS=a,b,c`. Unnamed wildcards (`$$$` without a name) produce no extra line. Both `SgMatch` and `AstGrepMatch` interfaces include the full `metaVariables` payload for downstream consumers.
85
+
86
+ - **tree-sitter WASM coverage expanded from 13 to 26 languages (refs #152)** — `scripts/download-grammars.ts` now downloads bash, c_sharp, css, html, json, lua, ocaml, php, swift, toml, vue, yaml, zig from `tree-sitter-wasms` at install time. All 13 new grammars registered in `TreeSitterClient.LANG_MAP`.
87
+
88
+ - **C#, PHP, and CSS tree-sitter dispatch rules now active (refs #152)** — the three languages had existing `.scm` rule files that silently never fired because no WASM was loaded and they were absent from the rules runner's `EXT_TO_LANG` / `appliesTo`. Both gaps closed. PL/SQL (9 rules), ABAP (1 rule), and COBOL (2 rules) moved to `-disabled/` subdirectories — no standard tree-sitter WASM exists for these grammars so the rules could not execute.
89
+
90
+ - **Read expansion and symbol extraction extended to 9 more languages (refs #152)** — `clients/read-expansion.ts` `EXT_TO_LANG` / `ENCLOSING_TYPES` and `clients/tree-sitter-symbol-extractor.ts` `SYMBOL_QUERIES` wired for Java, Kotlin, Dart, Elixir, C, C++ (read expansion + symbols) and C#, PHP, Swift, Lua, OCaml, Zig, Bash (symbols). All use WASMs already downloaded by the grammar expansion above. Node-type names verified against each language's `node-types.json` before use.
91
+
92
+ - **Tool registration collision guard (closes #106)** — all four `pi.registerTool()` calls in `index.ts` are now wrapped in try/catch. When another extension (e.g. `@narumitw/pi-lsp`) has already registered the same tool name, the collision is caught silently instead of aborting pi-lens extension load.
93
+
94
+ - **gitleaks runner for cross-language committed-secret detection (closes #130)** — new `clients/gitleaks-client.ts` runs `gitleaks detect --no-git --source <root> --report-format json` at session_start when the project root has any opt-in signal: `.gitleaks.toml` / `.gitleaks.yaml` / `.gitleaks.yml` / `.gitleaksignore`, a `gitleaks`-substring dependency in `package.json`, or a `.husky/` or `.git/hooks/` pre-commit hook referencing gitleaks. Cross-language by design (operates on bytes via regex + entropy, not AST), so a single binary covers every repo we support. Auto-installs from GitHub releases via the existing installer pattern (same shape as `actionlint` / `hadolint` / `tflint` — registered entry at `clients/installer/index.ts`). At turn_end, the cached findings surface as a **blocker** (not advisory) — committed credentials are real production risk and need rotation before merge; the block lists up to 5 findings as `path:line — RULE-ID: description`. Parser handles gitleaks's standard JSON-array report shape with 19 unit tests covering all six opt-in signals, malformed JSON tolerance, missing-required-field skipping (rather than crashing), and lenient coercion of stringified `StartLine` values. Client lifecycle mirrors `KnipClient` / `JscpdClient` / `GovulncheckClient` (in-flight dedupe, off-main-thread session_start invocation via the existing `runTask(setImmediate)` wrapper). Per-edit re-scan is intentionally NOT wired — secrets either are or aren't in a file; the session_start cache is the authoritative source.
95
+
96
+ - **govulncheck runner for reachable Go CVE detection (closes #132)** — new `clients/govulncheck-client.ts` runs `govulncheck -mode=source -format=json ./...` at session_start when the analysis root contains a `go.mod`. Caches results by project root via `cacheManager.writeCache("govulncheck", ...)`. The advisory surfaces at turn_end via a single `🛡️ Go CVEs reachable from this code` block listing up to 5 findings with `OSV-ID (file:line) — upgrade to vX.Y.Z`, complementary to (not redundant with) trivy: govulncheck reports only CVEs whose vulnerable function is actually called from the build graph, dramatically lower false-positive rate vs. flat dep-CVE scanning. **Auto-installs via `go install golang.org/x/vuln/cmd/govulncheck@latest`** when missing — the `hasGoModule(analysisRoot)` gate guarantees the Go toolchain is available, so leaning on `go install` is honest (same pattern as how rust-clippy works on cargo projects). Falls back to `$GOBIN` / `$GOPATH/bin` / `~/go/bin` lookup when the installed binary isn't on `PATH`. Parser handles govulncheck's informal JSON stream (newline-delimited dominant case, concatenated multi-object lines, malformed-prefix tolerance) with 7 unit tests; client lifecycle mirrors `KnipClient` / `JscpdClient` (in-flight dedupe, off-main-thread session_start invocation via the existing `runTask(setImmediate)` wrapper).
97
+
98
+ - **Rolling actionable-warnings history** — every actionable warning surfaced at `turn_end` is now appended to `<project-data>/actionable-warnings.jsonl`, parallel to the existing `code-quality-warnings.jsonl`. Captures the fields `worklog.jsonl` drops: stable `aw:<hash>` ID for cross-turn correlation, suppression state, LSP code-action enrichment counts, and origin (dispatch / lsp / merged). Empty reports skip the write. Closes the symmetry gap where code-quality warnings persisted across turns/sessions but actionable warnings did not.
99
+ - **NDJSON telemetry for `ast_grep_search` / `ast_grep_replace`** — every invocation of the two agent-facing ast-grep tools now writes a record to `~/.pi-lens/ast-grep-tools.log` capturing pattern (truncated to 500 chars), `patternLineCount` (so single-line vs multi-line analyses are trivial), lang, outcome (`success` / `no_matches` / `error`), and a classified `errorKind` (`multiple_ast_nodes`, `cannot_parse_query`, `tool_not_found`, `timeout`, `json_parse_failed`, `other`). Rotates at 1 MiB. `classifyAstGrepError` recognises both sg-runner's friendly wrappers and the raw underlying stderr, case-insensitive. The data answers: how often do agents hit multi-statement failures? Which language emits which error most? Do retries succeed after the skill is read?
100
+
101
+ ### Performance
102
+
103
+ - **Actionable-warnings turn-end report reuses dispatch-primed LSP diagnostics** — `buildActionableWarningsReport` was running its own LSP `openFile` + `getDiagnostics` loop per modified file, even though the dispatch pipeline had already run `touchFile` (open + diagnostics-wait + merge) for every modified file earlier in the same turn. The LSP service caches in `lastKnownDiagnostics`, but `getDiagnostics` ignored the cache and always re-spawned clients. New `LspService.getLastKnownDiagnostics(filePath)` returns the cached value without a re-fetch, distinguishing `[]` (cache-hit empty) from `undefined` (cache miss). actionable-warnings checks the cache first and falls through to the slow path only on a true miss. Latency log analysis showed reports >2 s on zero-warning turns dropping from common (63 of 733 in one rotation) to the sub-100 ms floor. `lsp_file_checked` NDJSON gains a `lspSource: "cache" | "fresh"` field so the cache-hit ratio is observable.
104
+
105
+ ### Fixed
106
+
107
+ - **`oldtext_not_found` messages distinguish content-drift from indentation mismatch (refs #144)** — when the first line of `oldText` is found in the file but the surrounding block no longer matches, the error now explicitly states this is a content-drift failure (not an indentation issue) and that indentation autopatch already ran. Previously both cases produced a generic re-read message; agents wasted retries changing tabs to spaces when the real problem was a 60-line content drift from earlier edits in the same session.
108
+
109
+ - **LSP diagnostics version guard prevents stale results (refs #150)** — `waitForDiagnostics` now captures a `diagnosticsVersion` baseline immediately before `refreshFile`. Only accepts results when `diagnosticsVersion > baseline`, ensuring a fresh `publishDiagnostics` arrived after the sync. Eliminates false-clean results after rapid sequential edits where the server was still processing an earlier file state.
110
+
111
+ - **Lazy `codeAction/resolve` before applying code actions (refs #150)** — many LSP servers (rust-analyzer, typescript-language-server) return lightweight code action objects with no `edit` field, only populating it on an explicit `codeAction/resolve` request. Pi-lens now resolves lightweight actions before applying; falls back silently if the server does not support `resolveSupport`.
112
+
113
+ - **Workspace symbol deduplication (refs #150)** — workspace symbol results deduplicated by `name:containerName:kind:uri:startLine:startCol` before returning. Prevents duplicate entries when multiple LSP servers are active for the same file.
114
+
115
+ - **Diagnostic noise stripping (refs #150)** — "for further information visit `<url>`" lines and bare URL-only lines stripped from LSP diagnostic messages before they surface in dispatch output. Reduces noise from rust-analyzer/clippy and other servers that embed documentation URLs inline.
116
+
117
+ - **Workspace edit ordering and overlap detection (refs #150)** — `applyWorkspaceEdit` now flushes all text edits to disk before processing resource operations (create/rename/delete), preventing a rename from moving a file before its content is updated. Overlapping text edit ranges within a single server's response now throw a descriptive error (`"overlapping LSP edits: X conflicts with Y"`) rather than producing corrupted output.
118
+
119
+ - **README `PILENS_DATA_DIR` description corrected (closes #142)** — the previous description stated the default write location was `<cwd>/.pi-lens/`, which is only true for legacy projects that already have that directory. New installs have always defaulted to `~/.pi-lens/projects/<slug>/`. Added a callout for local model server users (llama.cpp, Ollama) noting that cache-file churn inside the workspace disrupts model context scoring and `PILENS_DATA_DIR` is the fix.
120
+
121
+ - **ast-grep SKILL.md documents `Multiple AST nodes are detected` failure modes (refs #125 Phase 1)** — added a new gotcha entry covering the two distinct shapes: (1) sequence-in-block — wrap in `{ }` to make it one AST node; (2) cross-context (module-level + block-level in the same pattern) — wrapping is invalid, use two scoped searches or a YAML `inside:`/`has:` rule instead.
122
+
123
+ - **Widget stop warning storm churn (PR #146)** — `widget-state.ts` now tracks whether each file has received a final diagnostics snapshot (`hasFinalDiagnosticsSnapshot`). The `✓ clean` header is suppressed while any file is pending, and pending files are excluded from the file row list until diagnostics land. Prevents the transient `✓ clean` flash observed on warning-heavy analysis passes in C++ and other multi-runner languages. Stored diagnostics per file capped at 12 while preserving full warning counts in `diagnosticCounts`.
124
+
125
+ - **jscpd clone detection now runs on non-JS/TS projects, and excludes compiled `dist/` from TS-project scans (closes #126)** — the source-file gate at `JscpdClient.hasSourceFilesRecursive` accepted only JS/TS extensions (commit 8b5d588), making pi-lens's jscpd integration effectively JS/TS-only even though jscpd's underlying tokenizer covers 15+ languages. Pure-Python, pure-Go, pure-Rust, pure-Java, etc. repos got zero clone detection. The gate now recognises every language jscpd tokenizes well: Python, Java, Go, Rust, Ruby, PHP, Swift, Kotlin, Dart, Lua, Scala, C/C++, C#, plus the existing JS/TS set. Gleam / Zig / Fish stay excluded — jscpd has no tokenizer for them. Separately, the session_start call site now auto-detects `isTsProject` via the presence of `tsconfig.json` and passes it to `scan()`, so TS projects with a `dist/` directory of compiled `.js` artifacts no longer flag them as duplicates of their `.ts` sources. The cache scanner key varies by this flag (`"jscpd"` vs `"jscpd-ts"`) so a stale pre-#126 cache invalidates on first read instead of masking the fix.
126
+
127
+ *Behaviour note*: a previously-skipped pure-Python / Go / Rust / Java repo now runs a real jscpd scan at session_start (seconds, scaling with file count). The scan is off the main thread via the existing `setImmediate` runTask wrapper, so the TUI is not blocked, and the result caches for subsequent sessions.
128
+
129
+ - **Read-guard autopatch now registers a synthetic read for the matched line range** — a successful unique-match indent or trailing-ws autopatch (`oldtext_indent_autopatched` / `oldtext_trailing_ws_autopatched`) proves the agent's `oldText` reflects real content at a unique span. Two systems used to disagree about this: the autopatch successfully matched, and 4–5 ms later the read-guard fired `zero_read` because no Read tool event existed for that file. Now the autopatch path registers a synthetic read covering the matched range via `runtime.readGuard.recordRead`, so the downstream guard check has the evidence it needs. Doesn't bypass `file_modified` (orthogonal) or widen coverage beyond the matched span. Fixes the observed pattern of autopatch-then-block on `model-selector.{ts,test.ts}` and any similar future cases.
130
+
131
+ ### Removed
132
+
133
+ - **Deleted the regex-based `type-safety` runner** — three regex heuristics on raw source text (switch exhaustiveness without `default`, missing `return` in functions with non-void return type, `: any` / `as any`). All three checks are covered better — with real type information — by tools already in the dispatch pipeline: TypeScript LSP catches missing returns with proper control-flow analysis; Biome `noExplicitAny` and ESLint `@typescript-eslint/no-explicit-any` catch `any` usage; ESLint `@typescript-eslint/switch-exhaustiveness-check` is discriminant-type-aware. The regex `:\s*any\b` also matched identifiers like `anything`, `Many`, `Company`, comments, and strings — producing the dominant `type-safety:no-any-type` rule (244 of 404 entries in pi-drykiss's rolling history) with mostly false positives. Other typed languages need no equivalent: we already run their actual compilers / analyzers (pyright + mypy, go-vet + golangci-lint, rust-clippy, javac, cpp-check, dotnet-build, dart-analyze, phpstan, detekt, swiftlint, etc.). The orphan `clients/type-safety-client.ts` (a separate AST-based implementation with zero callers) was deleted alongside.
134
+ - **Deleted the state-matrix similarity infrastructure** — the 57×72 AST-kind transition matrix algorithm (`clients/state-matrix.ts`, `clients/amain-types.ts`, `clients/project-index.ts`) and all three of its consumers: the dispatch `similarity` runner, lens-booboo's "Runner 3: semantic similarity (Amain)" all-pairs comparison, and the `index.ts` Phase 7b pre-write inline check. The algorithm captured AST-kind shape distribution — not identifiers, control-flow ordering, data flow, function size, or imports. Two functions with the same kind distribution (e.g. all test functions, all map/filter chains, all early-return guards) scored ~1.0 cosine similarity despite doing completely different things. At the 0.98 threshold all three consumers produced zero observable output across 567 history entries in three active projects; at lower thresholds (~0.95) the same algorithm produced false-positive floods on idiom-shaped code. Refs #128 for the design intent of the eventual rewrite as AST-subtree fingerprinting with review-graph import-overlap gating. booboo's other similarity flow via `clients.astGrep.findSimilarFunctions` is preserved. Session-start cost drops by ~395 ms run + 212 ms queued (the index build/load task is gone).
135
+ - **Session_start `project-index` task** — built or loaded the now-deleted state-matrix index on every session start. Pure dead cost without the algorithm; removed.
136
+
137
+ ## [3.8.47] - 2026-06-01
138
+
139
+ ### Added
140
+
141
+ - **Actionable-warnings ecosystem expansion (closes #112)** — six dispatch runners now propagate `fixable` + a `fixSuggestion` so the actionable-warnings advisory can surface them instead of dropping them into code-quality. rust-clippy and golangci-lint read the structured replacement metadata each tool already publishes (`suggested_replacement` / `Replacement`); sqlfluff, detekt, swiftlint, and dart-analyze use curated allowlists of rules their respective `--fix` / `--auto-correct` / `dart fix --apply` commands rewrite deterministically. oxlint, stylelint, and markdownlint received the same treatment earlier in the cycle. Each slice ships parser-level unit tests against the runner's real output shape.
142
+ - **Framework / convention detector foundation (#118 Phases 1 + 2)** — new `clients/project-conventions.ts` exports `detectProjectConventions(cwd)` returning detected `frameworks` (react / next / vite / vitest in the first cut, each with confidence + signals), `testRunners`, `buildTools`, and `agentDocs`. Detection is purely deterministic — no LLM, no spawn — from `package.json` deps, canonical config files, and directory shape. `ProjectSnapshot` gained an optional `conventions` field with explicit-arg → previously-saved → fresh-detect precedence so a snapshot rewrite without conventions inherits rather than blanks.
143
+ - **Per-runner timeoutMs overrides the global 30 s default (#107)** — each `RunnerDefinition` may now declare its own `timeoutMs`; the dispatch harness honours it instead of the shared `RUNNER_TIMEOUT_FLOOR_MS`. The floor is also configurable via `pi-lens.runnerTimeoutFloorMs` config and `PI_LENS_RUNNER_TIMEOUT_FLOOR_MS` env, guarded against NaN, and lazy-resolved so tests can reset it.
144
+ - **LSP diagnostics-wait cap with env override (#117)** — dispatch LSP wait is now capped at 2.5 s by default to prevent slow language servers from holding edit feedback; tunable via `PI_LENS_LSP_DIAGNOSTICS_MAX_WAIT_MS`. A new `lsp_diagnostics_timeout` phase event and a `diagnosticsTimedOut` flag in the success log surface when the cap fires.
145
+ - **Tool-result debounce window (#115)** — `PI_LENS_TOOL_RESULT_DEBOUNCE_MS` (default 0, max 1 s) coalesces sequential tool_results for the same file so burst edits no longer rerun the full pipeline on every keystroke. Off by default; opt-in via env.
146
+ - **Custom rules guide + JSON schemas** — new docs and JSON schemas for tree-sitter and ast-grep custom rule authoring, plus tightened agent skill docs for write-ast-grep-rule, write-tree-sitter-rule, ast-grep, and lsp-navigation.
147
+ - **Read-guard `oldtext_duplicate` disambiguation** — the first `oldtext_not_found` and every `oldtext_duplicate` now include surrounding line context so the agent can pick the right occurrence without rereading the whole file.
148
+
149
+ ### Performance
150
+
151
+ - **In-flight dedupe on RuffClient and BiomeClient (#120)** — concurrent first-time callers to `ensureAvailable()` now share a single probe + auto-install promise via `ensureInFlight`, mirroring the pattern that closed #113 for SgRunner. Previously two parallel session-start tasks (one Python, one JS/TS) could each race the `ensureTool()` auto-install branch and produce partial state in `~/.pi-lens/tools`.
152
+ - **SgRunner in-flight dedupe (#113)** — concurrent ast-grep `ensureAvailable()` callers now share one probe; the auto-install branch runs at most once across a session.
153
+ - **Centralized `~/.pi-lens` and `walkUpDirs` helpers** — every `~/.pi-lens` computation now routes through `getGlobalPiLensDir()` (#122), and the parent-dir walk is consolidated as a `walkUpDirs` generator + `findNearestContaining` helper in `path-utils.ts`. Same behaviour, fewer ad-hoc walks.
154
+
155
+ ### Fixed
156
+
157
+ - **Cascade reverse-dependency neighbors now use the in-memory index** — the cascade builder was building a reverse-dep index from the review graph, saving it to the project snapshot, then immediately reloading it from disk to compute affected-file neighbors. The reload almost always returned `null` during active editing because the project sequence had advanced past the snapshot sequence, silently discarding the freshly computed data every time. Affected-file queries now run directly against the in-memory index built from the just-completed graph.
158
+ - **Tree-sitter rule cache preserves `has_fix` across the roundtrip** — `has_fix` was set on first load but dropped on the cache rehydration path, so cached runs never marked tree-sitter findings as fixable. Restored — the cache now roundtrips the flag end to end.
159
+ - **TypeScript LSP starts for pi-extension files when only `~/.pi/agent/package.json` exists (#123)** — root detection now performs a bounded walk to the extension boundary; if no marker is found inside that scope, it falls back to `FileDirRoot` provided the agent-level `package.json` exists, instead of silently giving up.
160
+ - **BiomeClient resolves binaries per project cwd, not `process.cwd()` (#121)** — `getBiomeBinary` now accepts a per-call cwd and caches resolved binaries keyed by cwd, so monorepos with sub-package biome installs reach the right binary even when pi-lens was invoked from a different directory.
161
+ - **Skip redundant `notify.open` on `touchFile` when content was already pushed within the debounce window (#116)** — split `shouldSkipTouch` from `shouldSkipNotify`; the latter avoids re-opening but still waits for diagnostics so cache invalidation isn't lost. A `notifySkipped` flag in the latency log records when the optimization fires.
162
+ - **dispatch runner `--version` probes flow through `createAvailabilityChecker`** — cpp-check is now cwd-keyed and dedupes concurrent first-time callers, eliminating one of the hottest uncached spawn paths in the audit.
163
+ - **PILENS_DATA_DIR compliance in actionable-warnings, review-graph, semgrep-config** — these paths now route through `getProjectDataDir(cwd)` instead of hardcoding `.pi-lens/` under cwd, so the data dir override is respected end to end.
164
+ - **Read-guard tracks session writes in an explicit Set** — unreliable mtime checks could let a Write→Edit sequence be blocked by a zero-read violation; an explicit per-session write set is the new authoritative signal.
165
+ - **Read-guard partial apply routes through post-edit analysis** — when only some `oldText` edits resolve, partial application performs exact replacements and then invokes the normal `handleToolResult` pipeline so staleness stamps, modified ranges, deferred formatting, dispatch diagnostics, cascade, and warning collection stay in sync with disk.
166
+ - **Read-guard staleness escalation fires across inter-turn gaps** — `REPEAT_FAILURE_TTL_MS` raised from 30 s to 300 s so repeated stale `oldText` attempts 2–3 minutes apart are still counted as the same streak; at ≥ 2 failures the preflight error is upgraded from `🔄 RETRYABLE` to `🛑 RE-READ REQUIRED`.
167
+ - **dart-analyze / detekt drop the dead sync `isAvailable` fallback** — both runners now use only the async availability check, eliminating a dead code path that masked test-mock mismatches.
168
+ - **ReDoS hotspots in oxlint rule extraction and cors-wildcard patterns** — bounded the affected regexes; the oxlint fix also backfills `defectClass` on five runners that had been missing it.
169
+ - **5 runners that were missing `defectClass`** — backfilled correctness/style/etc. classifications so downstream taxonomy + advisory routing work consistently.
170
+
171
+ ### Widget
172
+
173
+ - **Quieter widget glyphs and tighter horizontal layout** — warning glyph swapped from triangle to exclamation mark, dispatch findings pack into a single horizontal row at normal widths, the red dot now reflects blocking semantics (not just severity), and the divider/filename header / non-blocking fillers in horizontal mode were dropped.
174
+
7
175
  ## [3.8.46] - 2026-05-27
8
176
 
9
177
  ### Added
package/README.md CHANGED
@@ -8,6 +8,22 @@ pi-lens focuses on real-time inline code feedback for AI agents.
8
8
 
9
9
  ## What It Does
10
10
 
11
+ ### At Session Start
12
+
13
+ At `session_start`, pi-lens:
14
+
15
+ - resets runtime state and diagnostic telemetry
16
+ - detects project root, language profile, and active tools
17
+ - applies language-aware startup defaults for tool preinstall
18
+ - warms caches and optional indexes (with overlap/session guardrails)
19
+ - emits missing-tool install hints for detected languages when relevant
20
+ - prepends session guidance before the user's prompt so provider bridges keep the real prompt active
21
+ - opens `warmFiles` (if configured in `.pi-lens/lsp.json`) to seed lazy-indexing language servers like clangd before the first symbol query
22
+
23
+ Startup scan context and language profile are cached in the project snapshot and reused on subsequent `/new` invocations when the project has not changed, avoiding repeated full filesystem walks (~2.5 s saved on medium-to-large projects). Background startup scans are deferred past the interactive session-start path so they do not inflate visible `/new` latency.
24
+
25
+ For one-shot print sessions (for example `pi --print ...`), pi-lens auto-uses a quick startup path that skips heavy bootstrap work to reduce startup latency. Override with `PI_LENS_STARTUP_MODE=full|minimal|quick`.
26
+
11
27
  ### On Write/Edit
12
28
 
13
29
  On every `write` and `edit`, pi-lens runs a fast, language-aware pipeline (checks depend on file language, project config, and installed tools):
@@ -34,22 +50,6 @@ At `agent_end` (once per user prompt, after all agent tool calls complete):
34
50
  - **Conservative LSP warning autofix** — when `actionableWarnings.autoFix.enabled` is set, applies up to 5 preferred LSP quickfixes for warnings flagged in the turn's actionable warnings report. Each fix is re-validated against the live LSP server at apply time, checked for ambiguity (skipped if multiple eligible actions exist), and gated by a safety check before any write occurs. Changed files are registered with the read-guard and cache manager
35
51
  - **Summary notification** — concise status: how many files were formatted, which changed, and whether any formatter failed
36
52
 
37
- ### Session Start
38
-
39
- At `session_start`, pi-lens:
40
-
41
- - resets runtime state and diagnostic telemetry
42
- - detects project root, language profile, and active tools
43
- - applies language-aware startup defaults for tool preinstall
44
- - warms caches and optional indexes (with overlap/session guardrails)
45
- - emits missing-tool install hints for detected languages when relevant
46
- - prepends session guidance before the user's prompt so provider bridges keep the real prompt active
47
- - opens `warmFiles` (if configured in `.pi-lens/lsp.json`) to seed lazy-indexing language servers like clangd before the first symbol query
48
-
49
- Startup scan context and language profile are cached in the project snapshot and reused on subsequent `/new` invocations when the project has not changed, avoiding repeated full filesystem walks (~2.5 s saved on medium-to-large projects). Background startup scans are deferred past the interactive session-start path so they do not inflate visible `/new` latency.
50
-
51
- For one-shot print sessions (for example `pi --print ...`), pi-lens auto-uses a quick startup path that skips heavy bootstrap work to reduce startup latency. Override with `PI_LENS_STARTUP_MODE=full|minimal|quick`.
52
-
53
53
  ### Turn End
54
54
 
55
55
  At `turn_end`, pi-lens:
@@ -88,7 +88,12 @@ pi-lens includes **37 language server definitions**. LSP is **enabled by default
88
88
  { "warmFiles": ["src/main.cpp", "src/lib.cpp"] }
89
89
  ```
90
90
 
91
- **Agent LSP tools:** `lsp_diagnostics` can check one file, a directory, or an explicit `filePaths` batch with bounded concurrency. `lsp_navigation` provides definitions, references, hover, workspace symbols, call hierarchy, rename edits, and `findSymbol` for filtered document-symbol lookup. Rename accepts `apply: true` to write the returned workspace edits to disk immediately, with per-file LSP re-sync after application.
91
+ **Agent LSP tools:** `lsp_diagnostics` can check one file, a directory, or an explicit `filePaths` batch with bounded concurrency. `lsp_navigation` provides definitions, references, hover, workspace symbols, call hierarchy, rename edits, and `findSymbol` for filtered document-symbol lookup. Key operations:
92
+
93
+ - **`rename`** — renames a symbol across all references; `apply: true` writes workspace edits to disk with per-file LSP re-sync.
94
+ - **`rename_file`** — LSP-aware file rename: sends `workspace/willRenameFiles` to collect import-path rewrites, applies them, renames the file on disk, and notifies servers via `workspace/didRenameFiles`. `apply: false` previews the workspace edits without touching the filesystem.
95
+ - **`capabilities`** — shows which operations are supported by the active LSP server(s) for a file, read directly from the cached `initialize` response (no round-trip).
96
+ - **Symbol column resolution** — passing `symbol: "myFunc"` instead of an exact `character` position resolves the correct column automatically. Use `symbol: "foo#2"` for the second occurrence of `foo` on the line.
92
97
 
93
98
  LSP servers for: TypeScript, Deno, Python (pyright/basedpyright + jedi), Go, Rust, Ruby (ruby-lsp + solargraph), PHP, C# (omnisharp), F#, Java, Kotlin, Swift, Dart, Lua, C/C++, Zig, Haskell, Elixir, Gleam, OCaml, Clojure, Terraform, Nix, Bash, Docker, YAML, JSON, HTML, TOML, Prisma, Vue, Svelte, ESLint, CSS.
94
99
 
@@ -153,60 +158,35 @@ When `actionableWarnings.autoFix.enabled` is set in global config (or `--lens-ac
153
158
 
154
159
  When the agent reads a small slice of a file (≤ 60 lines), pi-lens transparently expands the read to the full enclosing symbol (function, method, or class) using the tree-sitter AST. The agent receives the full symbol as context, and the read guard records symbol-level coverage so edits anywhere within that symbol pass without requiring the agent to have read every line individually. Expansion runs within a 200 ms budget and falls back silently on unsupported file types or parse failures.
155
160
 
156
- Supported: TypeScript, TSX, JavaScript, JSX, Python, Go, Rust, Ruby.
161
+ Supported: TypeScript, TSX, JavaScript, JSX, Python, Go, Rust, Ruby, Java, Kotlin, Dart, Elixir, C, C++, C#, PHP, Swift, Lua, OCaml, Zig, Bash.
157
162
 
158
163
  ### Fact Rules Pipeline
159
164
 
160
165
  Covers JavaScript/TypeScript, Python, Go, Rust, Ruby, Shell, and CMake. A TypeScript AST-based fact-rule engine extracts function-level metrics and evaluates quality and security rules inline. Blocking rules surface immediately at write time; advisory rules are available via `/lens-booboo`.
161
166
 
162
- **Blocking (surface inline at write time):**
163
-
164
- - **cors-wildcard** — `Access-Control-Allow-Origin: *` in server-side code
165
- - **error-swallowing** — empty catch block (skips documented local fallbacks and fs-boundary catches)
166
- - **no-commented-credentials** — password/token/secret in commented-out code
167
- - **high-entropy-string** — string literals with suspiciously high Shannon entropy (possible hardcoded secret)
167
+ ### AST Search and Replace
168
168
 
169
- **Advisory (accessible via `/lens-booboo`):**
169
+ `ast_grep_search` and `ast_grep_replace` provide AST-aware pattern matching across 40+ languages via the `sg` CLI. Key capabilities:
170
170
 
171
- - **high-complexity** / **no-complex-conditionals**cyclomatic complexity and deeply nested conditions
172
- - **high-fan-out** — function calls too many distinct functions (coordination smell)
173
- - **unsafe-boundary** — dangerous `any` casts at API boundaries
174
- - **async-noise** / **async-unnecessary-wrapper**async functions with no await; wrappers that add no value
175
- - **pass-through-wrappers**trivial wrapper functions
176
- - **dynamic-regexp** — `new RegExp(variable)` (potential ReDoS; complements tree-sitter `unsafe-regex`)
177
- - **jwt-without-verify** — `jwt.sign()` without `jwt.verify()` in the same file
178
- - **missing-error-propagation** — catch blocks that log but don't rethrow
179
- - **error-obscuring** — catch blocks that wrap errors in a different type
180
- - **duplicate-string-literal** / **no-boolean-params** / **high-import-coupling** — code-quality signals
171
+ - **Metavariable captures** — named captures (`$VAR`, `$$$ARGS`) appear below each match: `$VAR=x $$$ARGS=a,b,c`.
172
+ - **Strictness modes** — `strictness: "relaxed"` ignores optional punctuation (trailing commas, semicolons) that causes zero matches in `smart` mode. Also supports `ast`, `cst`, `signature`, `template`.
173
+ - **Pagination** — `skip: N` offsets into large result sets; truncated results include a next-page hint.
174
+ - **Stale-preview detection** — `ast_grep_replace` re-validates the pattern before writing; returns a clear error if files changed since the preview instead of applying against wrong content.
175
+ - **`ast_dump`**dumps the full tree-sitter AST for a source snippet. Use this when a pattern returns zero matches and the correct node kind or field name is unknown.
181
176
 
182
177
  ### Tree-sitter Rules
183
178
 
184
- Structural rules organized by language in `rules/tree-sitter-queries/`. Rules marked **🔴** block the agent inline at write time (only for lines in the current edit); others are advisory.
185
-
186
- **TypeScript (23 rules):**
187
- 🔴 `eval`, `sql-injection`, `ts-command-injection`, `ts-ssrf`, `ts-xss-dom-sink`, `ts-dynamic-require`, `ts-open-redirect`, `ts-nosql-injection`, `ts-weak-hash`, `ts-hallucinated-react-import`, `unsafe-regex`, `debugger`, `default-not-last`, `duplicate-function-arg`, `empty-switch-case`, `infinite-loop`, `self-assignment`, `switch-case-termination`
188
- ⚠️ `console-statement`, `deep-promise-chain`, `mixed-async-styles`, `ts-insecure-random`, `ts-detached-async-call`, `ts-react-antipatterns`, `ts-weak-hash`, `variable-shadowing`
189
-
190
- **Python:** 🔴 `python-command-injection`, `python-sql-injection`, `python-insecure-deserialization`, `python-weak-hash`, `python-hallucinated-import` + 20 advisory rules
191
-
192
- **Go:** 🔴 `go-command-injection`, `go-sql-injection`, `go-shared-map-write-goroutine`, `go-weak-hash` + 13 advisory rules
193
-
194
- **Rust:** 🔴 `rust-lock-held-across-await` + 3 advisory rules (`rust-unsafe-block`, `rust-expect`, `rust-clone-in-loop`)
195
-
196
- **Ruby:** 🔴 `ruby-weak-hash` + 14 advisory rules
179
+ Structural rules organized by language in `rules/tree-sitter-queries/<language>/`. Rules marked **🔴** block the agent inline at write time (only for lines in the current edit); others are advisory.
197
180
 
198
181
  **Suppressing a finding:** add `// pi-lens-ignore: rule-id` on the flagged line or the line above (JS/TS), or `# pi-lens-ignore: rule-id` for Python/Ruby/Shell. This suppresses that specific rule at that location only.
199
182
 
200
- **Project-wide disabling** is not currently supported through config there is no `.pi-lens/disabled-rules` file. Use inline suppression for per-occurrence overrides. When editing pi-lens itself, move a rule file to the `<language>-disabled/` directory to prevent it from running.
183
+ **Bring your own rules:** drop YAML query files into `rules/tree-sitter-queries/<language>/` in your project — pi-lens merges them with the built-ins on session start. The schema, predicates (`eq`, `match`, `any-of`), and `inline_tier` (`blocking` | `warning` | `review`) are documented in [`docs/custom-rules.md`](docs/custom-rules.md). A `rules/tree-sitter-queries/rule-schema.json` JSON Schema is bundled for editor autocomplete via `.vscode/settings.json`.
201
184
 
202
185
  ### Ast-Grep Rules
203
186
 
204
- **180+ rules** in `rules/ast-grep-rules/` across JS, TS, and Python:
187
+ Pattern-based structural rules in `rules/ast-grep-rules/` across JS, TS, and Python — covers security (eval, hardcoded secrets, insecure randomness, dangerous DOM sinks), correctness (strict equality, constant conditions, duplicate keys), code smells (nested ternaries, long parameter lists, redundant state), and agent stubs (unimplemented bodies, raise NotImplementedError).
205
188
 
206
- - **Security**no-eval, jwt-no-verify, no-hardcoded-secrets, no-insecure-randomness, no-inner-html, no-javascript-url, weak-rsa-key
207
- - **Correctness** — strict-equality, no-cond-assign, no-constant-condition, no-dupe-keys, no-nan-comparison, array-callback-return, constructor-super
208
- - **Style/smells** — nested-ternary, long-parameter-list, large-class, prefer-optional-chain, redundant-state, require-await
209
- - **Agent stubs** — no-unimplemented-stub, no-raise-not-implemented, no-ellipsis-body
189
+ **Bring your own rules:** drop YAML rule files into `rules/ast-grep-rules/rules/<id>.yml` in your project pi-lens merges them with the built-ins; same `id` as a built-in overrides it. The supported subset of ast-grep's rule schema (the NAPI runner does not support `inside` / `follows` / `precedes` / `stopBy` / `field` / `nthChild` / `constraints` — use a tree-sitter rule when you need relational context) is documented in [`docs/custom-rules.md`](docs/custom-rules.md), with a `rules/ast-grep-rules/rule-schema.json` JSON Schema for editor autocomplete.
210
190
 
211
191
  ### Semgrep CLI Integration (Experimental)
212
192
 
@@ -238,58 +218,6 @@ metadata:
238
218
  confidence: high
239
219
  ```
240
220
 
241
- ## Dependencies
242
-
243
- Auto-install behavior depends on gate type:
244
-
245
- - **Config-gated**: installs only when project config/deps indicate usage
246
- - **Flow/language-gated**: installs when the runtime path needs it for the current file/session flow
247
- - **Operational prewarm**: installs during session warm scans / turn-end analysis paths
248
- - **GitHub release**: platform-specific binary downloaded from GitHub releases to `~/.pi-lens/bin/`
249
-
250
- | Tool | Purpose | Auto-installed | Gate |
251
- | ----------------------------------- | -------------------------------- | -------------- | ---------------------------------- |
252
- | `@biomejs/biome` | JS/TS lint/format/autofix | Yes | Config-gated |
253
- | `prettier` | Formatting fallback | Yes | Config-gated |
254
- | `yamllint` | YAML linting | Yes | Config-gated |
255
- | `actionlint` | GitHub Actions workflow linting | Yes | GitHub release |
256
- | `sqlfluff` | SQL linting/formatting | Yes | Config-gated |
257
- | `ruff` | Python lint/format/autofix | Yes | Language-default + flow-gated |
258
- | `typescript-language-server` | Unified LSP diagnostics | Yes | Language-default |
259
- | `typescript` | TypeScript compiler | Yes | Language-default |
260
- | `pyright` | Python type diagnostics fallback | Yes | Flow/language-gated |
261
- | `@ast-grep/cli` (sg) | AST scans/search/replace | Yes | Operational prewarm |
262
- | `knip` | Dead code analysis | Yes | Operational prewarm + config-gated |
263
- | `jscpd` | Duplicate code detection | Yes | Operational prewarm + config-gated |
264
- | `madge` | Circular dependency analysis | Yes | Turn-end analysis flow |
265
- | `mypy` | Python type checking | Yes | Flow-gated |
266
- | `stylelint` | CSS/SCSS/Less linting | Yes | Config-gated |
267
- | `markdownlint-cli2` | Markdown linting | Yes | Config-gated |
268
- | `shellcheck` | Shell script linting | Yes | GitHub release |
269
- | `shfmt` | Shell script formatting | Yes | GitHub release |
270
- | `rust-analyzer` | Rust LSP | Yes | GitHub release |
271
- | `golangci-lint` | Go linting | Yes | GitHub release |
272
- | `hadolint` | Dockerfile linting | Yes | GitHub release |
273
- | `ktlint` | Kotlin linting | Yes | GitHub release |
274
- | `tflint` | Terraform linting | Yes | GitHub release |
275
- | `taplo` | TOML linting/formatting | Yes | GitHub release |
276
- | `terraform-ls` | Terraform LSP | Yes | GitHub release |
277
- | `htmlhint` | HTML linting | Yes | Config-gated |
278
- | `@prisma/language-server` | Prisma LSP | Yes | Flow-gated |
279
- | `dockerfile-language-server-nodejs` | Dockerfile LSP | Yes | Flow-gated |
280
- | `intelephense` | PHP LSP | Yes | Flow-gated |
281
- | `bash-language-server` | Bash LSP | Yes | Language-default |
282
- | `yaml-language-server` | YAML LSP | Yes | Language-default |
283
- | `vscode-langservers-extracted` | JSON/ESLint/CSS/HTML LSP | Yes | Language-default |
284
- | `vscode-css-languageserver` | CSS LSP | Yes | Language-default |
285
- | `vscode-html-languageserver-bin` | HTML LSP | Yes | Language-default |
286
- | `svelte-language-server` | Svelte LSP | Yes | Flow-gated |
287
- | `@vue/language-server` | Vue LSP | Yes | Flow-gated |
288
- | `semgrep` | Experimental security dispatch | Manual | Local config / explicit opt-in |
289
- | `psscriptanalyzer` | PowerShell linting | Manual | — |
290
-
291
- Additional language servers (gopls, ruby-lsp, solargraph, etc.) are auto-detected from PATH or installed via native package managers (`go install`, `gem install`) when their language is detected.
292
-
293
221
  ## Run
294
222
 
295
223
  ```bash
@@ -298,6 +226,7 @@ pi
298
226
 
299
227
  # Optional switches
300
228
  pi --no-lens # Start pi-lens disabled for this session; /lens-toggle can re-enable
229
+ pi --no-lens-context # Disable automatic context injection only (tools/LSP/read-guard/format stay on); /lens-context-toggle
301
230
  pi --no-lsp # Disable unified LSP diagnostics
302
231
  pi --no-autoformat # Skip auto-formatting entirely
303
232
  pi --immediate-format # Format immediately after each edit instead of deferring to agent_end
@@ -332,29 +261,44 @@ Hide the diagnostics widget by default, run formatting immediately after write/e
332
261
  "enabled": false,
333
262
  "maxFixes": 5
334
263
  }
264
+ },
265
+ "contextInjection": {
266
+ "enabled": false
335
267
  }
336
268
  }
337
269
  ```
338
270
 
339
271
  `format.mode` can be `"deferred"` (default) or `"immediate"`. Set `format.enabled` to `false` to match `--no-autoformat`. `/lens-widget-toggle` still works as a session-only override.
340
272
 
273
+ `contextInjection.enabled` (default `true`) controls whether pi-lens prepends automatic findings — session-start guidance, turn-end findings, and test findings — into the next model turn. Set it to `false` (or use `--no-lens-context` / `PI_LENS_NO_CONTEXT_INJECTION=1` / `/lens-context-toggle`) to keep tools, LSP, read-guard, and formatting running while avoiding the prompt-cache invalidation that injected messages cause in long, cache-sensitive sessions. Findings are still cached, so `lens_diagnostics` and `/lens-health` keep working.
274
+
341
275
  `actionableWarnings.enabled` gates the turn_end report. `includeLspCodeActions` fetches LSP code actions for each warning (requires an active language server). `deltaOnly` (default `true`) limits the report to lines touched in the current turn. `autoFix.enabled` applies conservative LSP quickfixes at `agent_end`; `autoFix.maxFixes` caps the number applied per turn (default `5`).
342
276
 
343
277
  ## Environment Variables
344
278
 
345
279
  - `PILENS_DATA_DIR` — redirect per-project state (scanner caches,
346
- turn-state.json) out of the project directory. By default pi-lens writes
347
- `<cwd>/.pi-lens/`; if set, it writes to
348
- `<PILENS_DATA_DIR>/<sanitized-cwd-slug>/` instead. Useful for keeping repos
349
- clean or for mounted/ephemeral setups. Tool binaries always live in
280
+ turn-state.json) to a base directory outside the project. By default
281
+ pi-lens writes to `~/.pi-lens/projects/<sanitized-cwd-slug>/`. The one
282
+ exception is a legacy `<cwd>/.pi-lens/` directory: if that already exists
283
+ in the project, pi-lens continues to use it. Set `PILENS_DATA_DIR` to
284
+ permanently override both cases and write to
285
+ `<PILENS_DATA_DIR>/<sanitized-cwd-slug>/` instead. Particularly useful
286
+ when running pi with a local model server (llama.cpp, Ollama, etc.) that
287
+ monitors the project directory — cache-file churn inside the workspace can
288
+ disrupt the model's context scoring. Tool binaries always live in
350
289
  `~/.pi-lens/bin/` regardless.
351
290
  - `PI_LENS_STARTUP_MODE` — `full` | `minimal` | `quick`. Override the
352
291
  auto-selected startup path. One-shot `pi --print` sessions auto-use `quick`
353
292
  to reduce latency.
293
+ - `PI_LENS_NO_CONTEXT_INJECTION` — set to `1` to disable automatic context
294
+ injection (equivalent to `--no-lens-context` / `contextInjection.enabled:
295
+ false`). Tools, LSP, read-guard, and formatting stay active; findings are
296
+ still cached for `lens_diagnostics` and `/lens-health`.
354
297
 
355
298
  ## Key Commands
356
299
 
357
300
  - `/lens-toggle` — toggle pi-lens on/off for the current session without restarting
301
+ - `/lens-context-toggle` — toggle automatic context injection on/off for the session (tools/LSP/read-guard/formatting stay active)
358
302
  - `/lens-widget-toggle` — show/hide the pi-lens diagnostics widget below the editor
359
303
  - `/lens-booboo` — full quality report for current project state
360
304
  - `/lens-health` — runtime health, latency, and diagnostic telemetry
@@ -410,3 +354,55 @@ Dispatch is diagnostics-oriented: automatic formatting and safe autofix happen i
410
354
  | Nix | ✓ | lsp | nixfmt |
411
355
  | TOML | ✓ | lsp, taplo | taplo |
412
356
  | CMake | ✓ | lsp | cmake-format |
357
+
358
+ ## Dependencies
359
+
360
+ Auto-install behavior depends on gate type:
361
+
362
+ - **Config-gated**: installs only when project config/deps indicate usage
363
+ - **Flow/language-gated**: installs when the runtime path needs it for the current file/session flow
364
+ - **Operational prewarm**: installs during session warm scans / turn-end analysis paths
365
+ - **GitHub release**: platform-specific binary downloaded from GitHub releases to `~/.pi-lens/bin/`
366
+
367
+ | Tool | Purpose | Auto-installed | Gate |
368
+ | ----------------------------------- | -------------------------------- | -------------- | ---------------------------------- |
369
+ | `@biomejs/biome` | JS/TS lint/format/autofix | Yes | Config-gated |
370
+ | `prettier` | Formatting fallback | Yes | Config-gated |
371
+ | `yamllint` | YAML linting | Yes | Config-gated |
372
+ | `actionlint` | GitHub Actions workflow linting | Yes | GitHub release |
373
+ | `sqlfluff` | SQL linting/formatting | Yes | Config-gated |
374
+ | `ruff` | Python lint/format/autofix | Yes | Language-default + flow-gated |
375
+ | `typescript-language-server` | Unified LSP diagnostics | Yes | Language-default |
376
+ | `typescript` | TypeScript compiler | Yes | Language-default |
377
+ | `pyright` | Python type diagnostics fallback | Yes | Flow/language-gated |
378
+ | `@ast-grep/cli` (sg) | AST scans/search/replace | Yes | Operational prewarm |
379
+ | `knip` | Dead code analysis | Yes | Operational prewarm + config-gated |
380
+ | `jscpd` | Duplicate code detection | Yes | Operational prewarm + config-gated |
381
+ | `madge` | Circular dependency analysis | Yes | Turn-end analysis flow |
382
+ | `mypy` | Python type checking | Yes | Flow-gated |
383
+ | `stylelint` | CSS/SCSS/Less linting | Yes | Config-gated |
384
+ | `markdownlint-cli2` | Markdown linting | Yes | Config-gated |
385
+ | `shellcheck` | Shell script linting | Yes | GitHub release |
386
+ | `shfmt` | Shell script formatting | Yes | GitHub release |
387
+ | `rust-analyzer` | Rust LSP | Yes | GitHub release |
388
+ | `golangci-lint` | Go linting | Yes | GitHub release |
389
+ | `hadolint` | Dockerfile linting | Yes | GitHub release |
390
+ | `ktlint` | Kotlin linting | Yes | GitHub release |
391
+ | `tflint` | Terraform linting | Yes | GitHub release |
392
+ | `taplo` | TOML linting/formatting | Yes | GitHub release |
393
+ | `terraform-ls` | Terraform LSP | Yes | GitHub release |
394
+ | `htmlhint` | HTML linting | Yes | Config-gated |
395
+ | `@prisma/language-server` | Prisma LSP | Yes | Flow-gated |
396
+ | `dockerfile-language-server-nodejs` | Dockerfile LSP | Yes | Flow-gated |
397
+ | `intelephense` | PHP LSP | Yes | Flow-gated |
398
+ | `bash-language-server` | Bash LSP | Yes | Language-default |
399
+ | `yaml-language-server` | YAML LSP | Yes | Language-default |
400
+ | `vscode-langservers-extracted` | JSON/ESLint/CSS/HTML LSP | Yes | Language-default |
401
+ | `vscode-css-languageserver` | CSS LSP | Yes | Language-default |
402
+ | `vscode-html-languageserver-bin` | HTML LSP | Yes | Language-default |
403
+ | `svelte-language-server` | Svelte LSP | Yes | Flow-gated |
404
+ | `@vue/language-server` | Vue LSP | Yes | Flow-gated |
405
+ | `semgrep` | Experimental security dispatch | Manual | Local config / explicit opt-in |
406
+ | `psscriptanalyzer` | PowerShell linting | Manual | — |
407
+
408
+ Additional language servers (gopls, ruby-lsp, solargraph, etc.) are auto-detected from PATH or installed via native package managers (`go install`, `gem install`) when their language is detected.
@@ -1,8 +1,9 @@
1
1
  import * as fs from "node:fs";
2
- import * as os from "node:os";
3
2
  import * as path from "node:path";
3
+ import { isTestMode } from "./env-utils.js";
4
+ import { getGlobalPiLensDir } from "./file-utils.js";
4
5
 
5
- const AW_LOG_DIR = path.join(os.homedir(), ".pi-lens");
6
+ const AW_LOG_DIR = getGlobalPiLensDir();
6
7
  const AW_LOG_FILE = path.join(AW_LOG_DIR, "actionable-warnings.log");
7
8
  const AW_LOG_BACKUP_FILE = path.join(AW_LOG_DIR, "actionable-warnings.log.1");
8
9
  const MAX_LOG_BYTES = Math.max(
@@ -47,10 +48,7 @@ function rotateIfNeeded(): void {
47
48
  export function logActionableWarningsEvent(
48
49
  entry: ActionableWarningsLogEntry,
49
50
  ): void {
50
- if (
51
- process.env.PI_LENS_TEST_MODE === "1" ||
52
- (process.env.VITEST && process.env.PI_LENS_TEST_MODE !== "0")
53
- ) {
51
+ if (isTestMode()) {
54
52
  return;
55
53
  }
56
54
  const line = `${JSON.stringify({ ts: new Date().toISOString(), ...entry })}\n`;