pi-lens 3.8.26 → 3.8.27

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 (230) hide show
  1. package/CHANGELOG.md +102 -1
  2. package/README.md +182 -48
  3. package/clients/agent-behavior-client.ts +5 -2
  4. package/clients/ast-grep-client.ts +1 -1
  5. package/clients/diagnostic-logger.ts +4 -3
  6. package/clients/dispatch/dispatcher.ts +86 -9
  7. package/clients/dispatch/facts/function-facts.ts +79 -0
  8. package/clients/dispatch/facts/import-facts.ts +68 -0
  9. package/clients/dispatch/facts/try-catch-facts.ts +119 -23
  10. package/clients/dispatch/integration.ts +132 -5
  11. package/clients/dispatch/plan.ts +126 -5
  12. package/clients/dispatch/rules/async-noise.ts +8 -1
  13. package/clients/dispatch/rules/async-unnecessary-wrapper.ts +35 -0
  14. package/clients/dispatch/rules/high-complexity.ts +44 -0
  15. package/clients/dispatch/rules/high-fan-out.ts +55 -0
  16. package/clients/dispatch/rules/missing-error-propagation.ts +71 -0
  17. package/clients/dispatch/rules/quality-rules.ts +370 -0
  18. package/clients/dispatch/rules/sonar-rules.ts +500 -0
  19. package/clients/dispatch/rules/unsafe-boundary.ts +98 -0
  20. package/clients/dispatch/runner-context.ts +46 -1
  21. package/clients/dispatch/runners/biome-check.ts +39 -18
  22. package/clients/dispatch/runners/cpp-check.ts +164 -0
  23. package/clients/dispatch/runners/credo.ts +99 -0
  24. package/clients/dispatch/runners/dart-analyze.ts +127 -0
  25. package/clients/dispatch/runners/dotnet-build.ts +109 -0
  26. package/clients/dispatch/runners/elixir-check.ts +148 -0
  27. package/clients/dispatch/runners/fact-rules.ts +44 -0
  28. package/clients/dispatch/runners/gleam-check.ts +106 -0
  29. package/clients/dispatch/runners/hadolint.ts +97 -0
  30. package/clients/dispatch/runners/htmlhint.ts +106 -0
  31. package/clients/dispatch/runners/index.ts +46 -0
  32. package/clients/dispatch/runners/javac.ts +94 -0
  33. package/clients/dispatch/runners/ktlint.ts +159 -0
  34. package/clients/dispatch/runners/lsp.ts +21 -0
  35. package/clients/dispatch/runners/markdownlint.ts +99 -0
  36. package/clients/dispatch/runners/mypy.ts +123 -0
  37. package/clients/dispatch/runners/php-lint.ts +79 -0
  38. package/clients/dispatch/runners/phpstan.ts +123 -0
  39. package/clients/dispatch/runners/prettier-check.ts +110 -0
  40. package/clients/dispatch/runners/prisma-validate.ts +86 -0
  41. package/clients/dispatch/runners/psscriptanalyzer.ts +158 -0
  42. package/clients/dispatch/runners/pyright.ts +1 -1
  43. package/clients/dispatch/runners/ruff.ts +34 -12
  44. package/clients/dispatch/runners/shellcheck.ts +12 -8
  45. package/clients/dispatch/runners/shfmt.ts +96 -0
  46. package/clients/dispatch/runners/similarity.ts +48 -19
  47. package/clients/dispatch/runners/stylelint.ts +143 -0
  48. package/clients/dispatch/runners/taplo.ts +87 -0
  49. package/clients/dispatch/runners/tflint.ts +100 -0
  50. package/clients/dispatch/runners/tree-sitter.ts +34 -176
  51. package/clients/dispatch/runners/ts-lsp.ts +19 -2
  52. package/clients/dispatch/runners/type-safety.ts +6 -0
  53. package/clients/dispatch/runners/utils/runner-helpers.ts +6 -7
  54. package/clients/dispatch/runners/zig-check.ts +113 -0
  55. package/clients/file-kinds.ts +119 -6
  56. package/clients/file-role.ts +125 -0
  57. package/clients/fix-worklog.ts +117 -0
  58. package/clients/formatters.ts +62 -5
  59. package/clients/installer/index.ts +804 -70
  60. package/clients/language-policy.ts +75 -5
  61. package/clients/language-profile.ts +43 -0
  62. package/clients/lsp/client.ts +421 -436
  63. package/clients/lsp/config.ts +60 -19
  64. package/clients/lsp/index.ts +11 -59
  65. package/clients/lsp/interactive-install.ts +5 -28
  66. package/clients/lsp/language.ts +9 -2
  67. package/clients/lsp/launch.ts +229 -41
  68. package/clients/lsp/server.ts +581 -457
  69. package/clients/pipeline.ts +584 -288
  70. package/clients/project-index.ts +1 -1
  71. package/clients/review-graph/builder.ts +405 -0
  72. package/clients/review-graph/format.ts +51 -0
  73. package/clients/review-graph/query.ts +108 -0
  74. package/clients/review-graph/service.ts +64 -0
  75. package/clients/review-graph/types.ts +46 -0
  76. package/clients/runtime-coordinator.ts +17 -0
  77. package/clients/runtime-session.ts +353 -304
  78. package/clients/runtime-tool-result.ts +14 -2
  79. package/clients/runtime-turn.ts +3 -0
  80. package/clients/tree-sitter-cache.ts +2 -1
  81. package/clients/tree-sitter-client.ts +279 -340
  82. package/clients/tree-sitter-query-loader.ts +17 -3
  83. package/clients/tree-sitter-symbol-extractor.ts +48 -0
  84. package/commands/booboo.ts +363 -172
  85. package/config/ruff/core.toml +25 -0
  86. package/default-architect.yaml +2 -2
  87. package/index.ts +7 -3
  88. package/package.json +3 -5
  89. package/rules/ast-grep-rules/rules/consistent-existence-index-check-js.yml +15 -0
  90. package/rules/ast-grep-rules/rules/consistent-existence-index-check.yml +15 -0
  91. package/rules/ast-grep-rules/rules/empty-catch-js.yml +4 -1
  92. package/rules/ast-grep-rules/rules/empty-catch.yml +4 -1
  93. package/rules/ast-grep-rules/rules/enforce-node-protocol-js.yml +11 -0
  94. package/rules/ast-grep-rules/rules/enforce-node-protocol.yml +11 -0
  95. package/rules/ast-grep-rules/rules/no-absolute-path-import-js.yml +11 -0
  96. package/rules/ast-grep-rules/rules/no-absolute-path-import.yml +11 -0
  97. package/rules/ast-grep-rules/rules/no-array-reverse-mutation-js.yml +8 -0
  98. package/rules/ast-grep-rules/rules/no-array-reverse-mutation.yml +8 -0
  99. package/rules/ast-grep-rules/rules/no-array-sort-without-comparator-js.yml +8 -0
  100. package/rules/ast-grep-rules/rules/no-array-sort-without-comparator.yml +8 -0
  101. package/rules/ast-grep-rules/rules/no-await-expression-member-js.yml +11 -0
  102. package/rules/ast-grep-rules/rules/no-await-expression-member.yml +11 -0
  103. package/rules/ast-grep-rules/rules/no-await-in-loop.yml +17 -1
  104. package/rules/ast-grep-rules/rules/no-await-in-promise-methods-js.yml +12 -0
  105. package/rules/ast-grep-rules/rules/no-await-in-promise-methods.yml +12 -0
  106. package/rules/ast-grep-rules/rules/no-case-declarations-js.yml +5 -0
  107. package/rules/ast-grep-rules/rules/no-case-declarations.yml +5 -0
  108. package/rules/ast-grep-rules/rules/no-discarded-error-js.yml +0 -2
  109. package/rules/ast-grep-rules/rules/no-discarded-error.yml +0 -2
  110. package/rules/ast-grep-rules/rules/no-dupe-args-js.yml +1 -1
  111. package/rules/ast-grep-rules/rules/no-hardcoded-secrets.yml +2 -1
  112. package/rules/ast-grep-rules/rules/no-implied-eval-js.yml +4 -6
  113. package/rules/ast-grep-rules/rules/no-implied-eval.yml +4 -6
  114. package/rules/ast-grep-rules/rules/no-instanceof-array-js.yml +9 -0
  115. package/rules/ast-grep-rules/rules/no-instanceof-array.yml +9 -0
  116. package/rules/ast-grep-rules/rules/no-instanceof-builtins-js.yml +14 -0
  117. package/rules/ast-grep-rules/rules/no-instanceof-builtins.yml +14 -0
  118. package/rules/ast-grep-rules/rules/no-negation-in-equality-check-js.yml +12 -0
  119. package/rules/ast-grep-rules/rules/no-negation-in-equality-check.yml +12 -0
  120. package/rules/ast-grep-rules/rules/no-single-promise-in-promise-methods-js.yml +12 -0
  121. package/rules/ast-grep-rules/rules/no-single-promise-in-promise-methods.yml +12 -0
  122. package/rules/ast-grep-rules/rules/no-typeof-undefined-js.yml +19 -0
  123. package/rules/ast-grep-rules/rules/no-typeof-undefined.yml +19 -0
  124. package/rules/ast-grep-rules/rules/no-unnecessary-array-flat-depth-js.yml +8 -0
  125. package/rules/ast-grep-rules/rules/no-unnecessary-array-flat-depth.yml +8 -0
  126. package/rules/ast-grep-rules/rules/no-useless-length-check-js.yml +11 -0
  127. package/rules/ast-grep-rules/rules/no-useless-length-check.yml +11 -0
  128. package/rules/ast-grep-rules/rules/no-useless-promise-resolve-reject-js.yml +10 -0
  129. package/rules/ast-grep-rules/rules/no-useless-promise-resolve-reject.yml +10 -0
  130. package/rules/ast-grep-rules/rules/no-useless-rest-spread-js.yml +10 -0
  131. package/rules/ast-grep-rules/rules/no-useless-rest-spread.yml +10 -0
  132. package/rules/ast-grep-rules/rules/prefer-array-find-js.yml +10 -0
  133. package/rules/ast-grep-rules/rules/prefer-array-find.yml +10 -0
  134. package/rules/ast-grep-rules/rules/prefer-array-flat-map-js.yml +8 -0
  135. package/rules/ast-grep-rules/rules/prefer-array-flat-map.yml +8 -0
  136. package/rules/ast-grep-rules/rules/prefer-array-some-js.yml +11 -0
  137. package/rules/ast-grep-rules/rules/prefer-array-some.yml +11 -0
  138. package/rules/ast-grep-rules/rules/prefer-async-await-js.yml +13 -0
  139. package/rules/ast-grep-rules/rules/prefer-async-await.yml +13 -0
  140. package/rules/ast-grep-rules/rules/prefer-at-js.yml +10 -0
  141. package/rules/ast-grep-rules/rules/prefer-at.yml +10 -0
  142. package/rules/ast-grep-rules/rules/prefer-date-now-js.yml +10 -0
  143. package/rules/ast-grep-rules/rules/prefer-date-now.yml +10 -0
  144. package/rules/ast-grep-rules/rules/prefer-dom-node-append-js.yml +8 -0
  145. package/rules/ast-grep-rules/rules/prefer-dom-node-append.yml +8 -0
  146. package/rules/ast-grep-rules/rules/prefer-dom-node-text-content-js.yml +8 -0
  147. package/rules/ast-grep-rules/rules/prefer-dom-node-text-content.yml +8 -0
  148. package/rules/ast-grep-rules/rules/prefer-keyboard-event-key-js.yml +10 -0
  149. package/rules/ast-grep-rules/rules/prefer-keyboard-event-key.yml +10 -0
  150. package/rules/ast-grep-rules/rules/prefer-math-min-max-js.yml +12 -0
  151. package/rules/ast-grep-rules/rules/prefer-math-min-max.yml +12 -0
  152. package/rules/ast-grep-rules/rules/prefer-number-properties-js.yml +10 -0
  153. package/rules/ast-grep-rules/rules/prefer-number-properties.yml +10 -0
  154. package/rules/ast-grep-rules/rules/prefer-prototype-methods-js.yml +8 -0
  155. package/rules/ast-grep-rules/rules/prefer-prototype-methods.yml +8 -0
  156. package/rules/ast-grep-rules/rules/prefer-query-selector-js.yml +11 -0
  157. package/rules/ast-grep-rules/rules/prefer-query-selector.yml +11 -0
  158. package/rules/ast-grep-rules/rules/prefer-string-slice-js.yml +10 -0
  159. package/rules/ast-grep-rules/rules/prefer-string-slice.yml +10 -0
  160. package/rules/ast-grep-rules/rules/prefer-string-starts-ends-with-js.yml +10 -0
  161. package/rules/ast-grep-rules/rules/prefer-string-starts-ends-with.yml +10 -0
  162. package/rules/ast-grep-rules/rules/prefer-string-trim-start-end-js.yml +10 -0
  163. package/rules/ast-grep-rules/rules/prefer-string-trim-start-end.yml +10 -0
  164. package/rules/ast-grep-rules/rules/prefer-structured-clone-js.yml +9 -0
  165. package/rules/ast-grep-rules/rules/prefer-structured-clone.yml +9 -0
  166. package/rules/ast-grep-rules/rules/throw-new-error-js.yml +14 -0
  167. package/rules/ast-grep-rules/rules/throw-new-error.yml +14 -0
  168. package/rules/ast-grep-rules/rules/toctou-js.yml +1 -1
  169. package/rules/ast-grep-rules/rules/toctou.yml +1 -1
  170. package/rules/ast-grep-rules/rules/unchecked-sync-fs-js.yml +1 -1
  171. package/rules/ast-grep-rules/rules/unchecked-sync-fs.yml +1 -1
  172. package/rules/ast-grep-rules/rules/unchecked-throwing-call-js.yml +0 -2
  173. package/rules/ast-grep-rules/rules/unchecked-throwing-call.yml +0 -2
  174. package/rules/ast-grep-rules/rules-disabled/no-this-in-static-js.yml +14 -0
  175. package/rules/ast-grep-rules/rules-disabled/no-this-in-static.yml +14 -0
  176. package/rules/ast-grep-rules/rules-disabled/prefer-string-raw-js.yml +9 -0
  177. package/rules/ast-grep-rules/rules-disabled/prefer-string-raw.yml +9 -0
  178. package/rules/tree-sitter-queries/go/go-context-background-handler.yml +57 -0
  179. package/rules/tree-sitter-queries/go/go-defer-in-loop.yml +1 -1
  180. package/rules/tree-sitter-queries/go/go-direct-panic.yml +1 -1
  181. package/rules/tree-sitter-queries/go/go-empty-if-err.yml +1 -1
  182. package/rules/tree-sitter-queries/go/go-goroutine-loop-capture.yml +1 -1
  183. package/rules/tree-sitter-queries/go/go-hardcoded-secrets.yml +1 -1
  184. package/rules/tree-sitter-queries/go/go-mutex-copy.yml +57 -0
  185. package/rules/tree-sitter-queries/go/go-time-sleep-test.yml +50 -0
  186. package/rules/tree-sitter-queries/python/bare-except.yml +1 -1
  187. package/rules/tree-sitter-queries/python/eval-exec.yml +1 -1
  188. package/rules/tree-sitter-queries/python/is-vs-equals.yml +1 -1
  189. package/rules/tree-sitter-queries/python/mutable-default-arg.yml +1 -1
  190. package/rules/tree-sitter-queries/python/python-assert-production.yml +49 -0
  191. package/rules/tree-sitter-queries/python/python-cross-language-method.yml +61 -0
  192. package/rules/tree-sitter-queries/python/python-debugger.yml +1 -1
  193. package/rules/tree-sitter-queries/python/python-empty-except.yml +1 -1
  194. package/rules/tree-sitter-queries/python/python-hallucinated-import.yml +58 -0
  195. package/rules/tree-sitter-queries/python/python-hardcoded-secrets.yml +1 -1
  196. package/rules/tree-sitter-queries/python/python-mutable-class-attr.yml +1 -1
  197. package/rules/tree-sitter-queries/python/python-raise-string.yml +1 -1
  198. package/rules/tree-sitter-queries/python/python-sleep-in-test.yml +50 -0
  199. package/rules/tree-sitter-queries/python/python-subprocess-shell.yml +60 -0
  200. package/rules/tree-sitter-queries/python/python-unsafe-regex.yml +1 -1
  201. package/rules/tree-sitter-queries/python/unreachable-except.yml +1 -1
  202. package/rules/tree-sitter-queries/ruby/ruby-debugger.yml +1 -1
  203. package/rules/tree-sitter-queries/ruby/ruby-dynamic-send.yml +54 -0
  204. package/rules/tree-sitter-queries/ruby/ruby-empty-rescue.yml +1 -1
  205. package/rules/tree-sitter-queries/ruby/ruby-eval.yml +1 -1
  206. package/rules/tree-sitter-queries/ruby/ruby-hardcoded-secrets.yml +1 -1
  207. package/rules/tree-sitter-queries/ruby/ruby-rescue-exception.yml +1 -1
  208. package/rules/tree-sitter-queries/ruby/ruby-sleep-in-test.yml +48 -0
  209. package/rules/tree-sitter-queries/ruby/ruby-string-eval.yml +57 -0
  210. package/rules/tree-sitter-queries/ruby/ruby-unsafe-regex.yml +1 -1
  211. package/rules/tree-sitter-queries/rust/rust-expect.yml +47 -0
  212. package/rules/tree-sitter-queries/rust/rust-todo-unimplemented.yml +47 -0
  213. package/rules/tree-sitter-queries/rust/rust-unsafe-block.yml +41 -0
  214. package/rules/tree-sitter-queries/rust/rust-unwrap.yml +1 -1
  215. package/rules/tree-sitter-queries/typescript/ts-hallucinated-react-import.yml +61 -0
  216. package/rules/tree-sitter-queries/typescript/ts-insecure-random.yml +9 -5
  217. package/rules/tree-sitter-queries/typescript/ts-react-antipatterns.yml +50 -0
  218. package/rules/tree-sitter-queries/typescript/ts-ssrf.yml +2 -2
  219. package/rules/tree-sitter-queries/typescript/unsafe-regex.yml +1 -1
  220. package/scripts/download-grammars.js +4 -0
  221. package/rules/tree-sitter-queries/typescript/no-dupe-class-members.yml +0 -49
  222. /package/rules/ast-grep-rules/{rules → rules-disabled}/no-architecture-violation.yml +0 -0
  223. /package/rules/ast-grep-rules/{rules → rules-disabled}/no-param-reassign.yml +0 -0
  224. /package/rules/ast-grep-rules/{rules → rules-disabled}/no-shadow.yml +0 -0
  225. /package/rules/tree-sitter-queries/{typescript → typescript-disabled}/await-in-loop.yml +0 -0
  226. /package/rules/tree-sitter-queries/{typescript → typescript-disabled}/constructor-super.yml +0 -0
  227. /package/rules/tree-sitter-queries/{typescript → typescript-disabled}/empty-catch.yml +0 -0
  228. /package/rules/tree-sitter-queries/{typescript → typescript-disabled}/hardcoded-secrets.yml +0 -0
  229. /package/rules/tree-sitter-queries/{typescript → typescript-disabled}/long-parameter-list.yml +0 -0
  230. /package/rules/tree-sitter-queries/{typescript → typescript-disabled}/nested-ternary.yml +0 -0
package/CHANGELOG.md CHANGED
@@ -2,7 +2,108 @@
2
2
 
3
3
  All notable changes to pi-lens will be documented in this file.
4
4
 
5
- ## [Unreleased]
5
+ ## [3.8.27] - 2026-04-19
6
+
7
+ ### Added
8
+ - **Review graph impact cascade** — turn-end cascade now renders a review-graph impact view showing which files were affected and how diagnostics propagated.
9
+ - **Fact-rule pipeline in dispatch** — new `fact-rules` dispatch runner computes function-level facts (depth, cyclomatic complexity, call counts) and evaluates quality rules inline, replacing the bespoke tree-sitter booboo runner.
10
+ - **Function facts: depth / CC / calls** — tree-sitter extracts per-function cyclomatic complexity, nesting depth, and outgoing call count for fact-rule evaluation.
11
+ - **File role classification** — dispatch classifies files as `source`, `test`, `config`, or `vendor` and adjusts rule severity accordingly.
12
+ - **Inline suppression directives** — sources can suppress diagnostics with `// pi-lens-ignore` or `# pi-lens-ignore` comments; suppressed items are omitted from inline output.
13
+ - **High-complexity fact rule** — flags functions exceeding configurable cyclomatic complexity thresholds.
14
+ - **Unsafe-boundary fact rule** — detects dangerous boundary crossings (unvalidated user input → trusted context).
15
+ - **High-fan-out fact rule** — flags functions with excessive outgoing call count (default threshold 20).
16
+ - **`async-unnecessary-wrapper` ast-grep rule** — detects trivial async wrappers that just await and return.
17
+ - **`missing-error-propagation` ast-grep rule** — detects catch blocks that swallow errors without re-throwing or logging.
18
+ - **36 new ast-grep rules** — expanded coverage for security, correctness, and style across TypeScript, JavaScript, and Python.
19
+ - **5 quality fact rules** — structured quality checks driven by function-level metrics.
20
+ - **8 SonarJS-aligned rules** — try-catch enrichment and 8 rules ported from SonarJS patterns.
21
+ - **Slop-detection rules** — identifies low-signal / boilerplate-heavy code regions with observability log entries.
22
+ - **Dart-analyze dispatch runner** — runs `dart analyze` on `.dart` files.
23
+ - **Ktlint dispatch runner** — runs `ktlint` on `.kt` / `.kts` files.
24
+ - **TFLint dispatch runner** — runs `tflint` on `.tf` / `.tfvars` files.
25
+ - **Taplo dispatch runner + formatter** — runs `taplo` for TOML lint and format.
26
+ - **Credo dispatch runner** — runs `mix credo` on Elixir files (falls back to LSP).
27
+ - **Phpstan dispatch runner** — runs `phpstan` on PHP files (falls back to LSP).
28
+ - **Prettier-check dispatch runner** — runs `prettier --check` as a lint runner (not auto-fix, purely diagnostic).
29
+ - **PSScriptAnalyzer runner** — PowerShell linting via `Invoke-ScriptAnalyzer`, using temp `-File` instead of `-Command` to avoid cmd.exe mangling.
30
+ - **Hadolint dispatch runner** — Dockerfile lint with always-run dispatch gating.
31
+ - **Htmlhint dispatch runner** — HTML lint with tag-pair detection.
32
+ - **Docker / PHP / PowerShell / Prisma FileKind** — new language kind mappings enable LSP and dispatch for Dockerfile, `.php`, `.ps1`/`.psm1`, and `.prisma` files.
33
+ - **GitHub release downloader for installer** — `shellcheck`, `shfmt`, `rust-analyzer`, and `golangci-lint` are now auto-installed from GitHub releases with asset selection across platforms.
34
+ - **Auto-install gopls and ruby-lsp** — `gopls` installed via `go install`; `ruby-lsp` installed via `gem install` when not found.
35
+ - **Biome as default JS/TS linter** — when no ESLint or oxlint config exists, Biome runs as the default linter for write-path dispatch instead of silently skipping.
36
+ - **Bundled ruff config fallback** — Python projects without a `ruff.toml` / `pyproject.toml` ruff section now use a bundled safe-default config so ruff still produces useful findings.
37
+ - **Ruff autofix after diagnostics** — the ruff dispatch runner now applies safe autofixes after capturing diagnostics, mirroring Biome's write-path behavior.
38
+ - **Diagnostic history logging** — tree-sitter warnings and debounced ast-grep findings are now logged to session history for observability and `/lens-booboo` review.
39
+ - **Tree-sitter grammar downloads expanded** — additional grammars downloaded at install time for broader language coverage.
40
+ - **Java and C# fallback analysis** — dispatch includes fallback analysis paths for Java (`.java`) and C# (`.cs`) when LSP is unavailable.
41
+ - **CI: tsc type-check + vitest + install gate** — CI now runs `tsc --noEmit` and `vitest` as separate jobs; install-test is gated on both passing.
42
+ - **CI: tsx extension load check** — CI verifies that required extensions load correctly to catch missing dependency errors early.
43
+
44
+ ### Changed
45
+ - **Promote LSP-backed languages into dispatch** — languages with active LSP servers now route through dispatch's standard pipeline instead of ad-hoc paths.
46
+ - **Dispatch language fallbacks aligned** — LSP-backed and fallback runner selection now uses consistent language-to-capability mapping.
47
+ - **CSS / HTML / TOML / Elixir fallback wiring** — dispatch fallbacks now include CSS (stylelint), HTML (htmlhint), TOML (taplo), and Elixir (credo).
48
+ - **Prettier-check and stylelint cwd handling** — both runners now resolve project root correctly instead of skipping when the working directory overshoots.
49
+ - **OS portability: vendor/bin and sg resolution** — `vendor/bin` tools resolve with multi-extension support (`.bat`/`.cmd`/no-ext); `sg` candidate list works across platforms.
50
+ - **LSP: live Windows registry PATH** — LSP spawn reads the live `HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment\Path` at launch time so newly installed tools are immediately discoverable.
51
+ - **LSP: unified resolveAndLaunch** — four separate resolution mechanisms (local binary, global, npx, package manager) collapsed into a single `resolveAndLaunch` flow with clear fallback ordering.
52
+ - **LSP: telemetry and logging tightened** — init failures logged to `sessionstart.log`; terminal noise reduced; basename matching improved.
53
+ - **YAML LSP root fallback** — YAML language server uses `RootWithFallback` for seamless multi-root project support.
54
+ - **Dart / Terraform / TOML LSP: RootWithFallback** — same root-fallback pattern applied across these servers for reliable workspace detection.
55
+ - **Terraform-ls HashiCorp install fallback** — improved install path resolution for terraform-ls.
56
+ - **`empty-catch` and `unchecked-sync-fs` downgraded to warning** — too many false positives as errors; now `warning` severity.
57
+ - **High-fan-out threshold raised to 20** — reduced noise from earlier threshold of 10.
58
+ - **High-complexity and unsafe-boundary thresholds tightened** — reduced false positives at the default severity boundaries.
59
+ - **False-positive reduction: 8 rules + 3 error rules** — tuned OAuth/constants-related patterns, removed 3 error-level rules that flagged too broadly, and fixed `ts-ssrf` identifier argument matching.
60
+ - **Removed unused/noisy ast-grep rules** — culled rules that overlapped with tree-sitter coverage or produced excessive noise.
61
+ - **Moved duplicate TS tree-sitter rules** — overlapping rules relocated to `typescript-disabled/` to avoid double-reporting.
62
+ - **LSP crash diagnostics** — startup stderr captured and logged for faster root-cause analysis.
63
+ - **Tool PATH normalization** — cross-platform PATH resolution unified for LSP and dispatch tool spawning.
64
+ - **Cleaned up runtime dependencies** — moved `@ast-grep/napi` and `js-yaml` to `dependencies` (were `devDependencies`); removed unused deps.
65
+ - **Complexity reduction** — decomposed four highest-complexity functions (CC 75–153 → <20 each) for maintainability.
66
+
67
+ ### Fixed
68
+ - **Windows LSP startup fallback** — hardened spawn logic for `.cmd` wrappers, PATH resolution, and process creation on Windows.
69
+ - **C# launch and secondary language fallbacks** — C# LSP and secondary language servers start reliably in more project layouts.
70
+ - **Prettier-check / stylelint cwd overshoot** — both runners now find the project root correctly instead of silently skipping.
71
+ - **Hadolint asset name case** — GitHub release downloader resolves case-sensitive asset names.
72
+ - **Htmlhint / hadolint always-run dispatch** — both runners fire correctly regardless of file presence heuristics.
73
+ - **Bash LSP re-spawn** — bash-language-server restarts cleanly after unexpected exit.
74
+ - **HTML dispatch + htmlhint tag-pair detection** — HTML file kind wired into dispatch; htmlhint catches missing closing tags.
75
+ - **Intelephense needs `scripts`** — PHP LSP installed with `--scripts` flag so its postinstall binary is available.
76
+ - **Rust-analyzer: RootWithFallback + Windows .zip asset** — both root detection and Windows asset extraction fixed.
77
+ - **Managed Pyright launch path** — pyright LSP binary resolves correctly when installed as a managed tool.
78
+ - **Terraform / Kotlin / coverage fallback handling** — all three dispatch paths handle missing tools or configs gracefully.
79
+ - **Shellcheck auto-install** — auto-installer works across platforms with GitHub release asset selection.
80
+ - **Ktlint asset names** — ktlint release assets resolved with correct URL patterns.
81
+ - **Coverage notice for mode:all linters** — mode:all linters that can't generate coverage now emit a notice instead of crashing.
82
+ - **npm install 120s timeout** — `ensureTool` npm installs have a hard 120s timeout to prevent indefinite hangs.
83
+ - **npm install ERESOLVE retry** — installer retries npm installs on ERESOLVE dependency conflicts.
84
+ - **Remove spawnSync from `unchecked-throwing-call` rule** — rule no longer flags `spawnSync` calls as unhandled throwing calls.
85
+ - **`flush()` drain before write-complete** — diagnostic history flush now drains pending entries before awaiting write completion, preventing data loss on session end.
86
+ - **Runner checks diagnostics-only** — dispatch runner checks are now diagnostics-only, avoiding stale LSP state mutations.
87
+ - **Biome-lsp server removed** — duplicate `biome-lsp` server entry removed; Biome LSP is accessed through the standard biome binary.
88
+ - **Size guards + path caching for ensureTool** — tool availability checks are cached and sized to avoid re-probing on every call.
89
+ - **Test assertions after runner wiring** — test expectations updated for new runner ordering and diagnostics pipeline.
90
+ - **OS path separator normalization** — path separators and map keys normalized for cross-platform compatibility in diagnostics and LSP.
91
+ - **Drop unnecessary async from `ensureAvailable`** — removed spurious `async` that added nothing and complicated error handling.
92
+ - **Tree-sitter rule false positives** — fixed query syntax, scan scripts, and architect glob patterns that produced incorrect findings.
93
+
94
+ ### Performance
95
+ - **Startup: defer npm tool availability probes** — tool availability checks (Biome, ESLint, etc.) now run lazily out of the critical path, reducing session start latency.
96
+ - **Defer TypeScript loading in similarity runner** — similarity detection lazily imports the TypeScript parser, eliminating cold-start cost on first call.
97
+
98
+ ### Refactored
99
+ - **LSP: collapse resolution into `resolveAndLaunch`** — unified four spawn mechanisms into one function with clear platform-aware fallbacks.
100
+ - **Booboo: replace bespoke tree-sitter runner** — `/lens-booboo` tree-sitter checks now use the same fact-rule pipeline as dispatch, eliminating code duplication.
101
+ - **Drop redundant async from LSP spawn** — removed unnecessary `async`/`await` from functions that already return Promises.
102
+
103
+ ### Tests
104
+ - **GitHub release asset selection and PATH tests** — installer asset URL construction and PATH resolution covered by unit tests.
105
+ - **Rust-analyzer Windows .zip asset expectation** — test fixture updated for `.zip` extension on Windows.
106
+ - **Async-noise test multi-statement function** — test rule updated to match multi-statement function bodies.
6
107
 
7
108
  ## [3.8.26] - 2026-04-15
8
109
 
package/README.md CHANGED
@@ -8,14 +8,18 @@ pi-lens focuses on real-time inline code feedback for AI agents.
8
8
 
9
9
  On every `write` and `edit`, pi-lens runs a fast, language-aware pipeline (checks depend on file language, project config, and installed tools):
10
10
 
11
- - **Formatting + autofix**: language/tool-specific formatters and safe autofixers (Biome, Ruff, ESLint, and other toolchain-native formatters when available)
12
- - **Type checking**: unified LSP (enabled by default) with language fallbacks (for example `ts-lsp`, `pyright`)
13
- - **Lint + static analysis**: active runners for the current language and config
14
- - **Test running**: related-file tests, with failed-first reruns for faster feedback
15
- - **Security checks**: secret scanning and structural security rules
16
- - **Structural analysis**: tree-sitter + ast-grep for bug patterns across supported languages
17
- - **Delta reporting**: prioritize new issues over legacy baseline noise
18
- - **Coverage transparency**: when primary analysis tools are unavailable for a file kind, pi-lens emits a non-blocking inline "analysis unavailable" warning (deduped per file per session)
11
+ 1. **Secrets scan** blocking; aborts the write if credentials are detected
12
+ 2. **Auto-format** language-specific formatters (Biome, Prettier, Ruff, gofmt, rustfmt, and 25+ others)
13
+ 3. **Auto-fix** safe autofixes from 6 tools (Biome `--write`, Ruff `--fix`, ESLint `--fix`, stylelint `--fix`, sqlfluff `fix`, RuboCop `-a`) applied before analysis
14
+ 4. **LSP file sync** opens/updates the file in active language servers
15
+ 5. **Dispatch lint** parallel runner groups: LSP diagnostics, tree-sitter structural rules, ast-grep security/correctness rules, fact rules, language-specific linters, similarity detection, and architect checks
16
+ 6. **Test runner** runs the corresponding test file; reruns known failures first
17
+ 7. **Cascade diagnostics** review-graph impact cascade showing which other files were affected and how diagnostics propagated
18
+
19
+ Results are inline and actionable:
20
+ - **Blocking issues** — stop progress until fixed
21
+ - **Warnings** — summarized inline, detail in `/lens-booboo`
22
+ - **Health/telemetry** — available in `/lens-health`
19
23
 
20
24
  ### Session Start
21
25
 
@@ -28,7 +32,7 @@ At `session_start`, pi-lens:
28
32
  - emits missing-tool install hints for detected languages when relevant
29
33
  - injects session guidance through internal context (non-user channel) to reduce acknowledgement-only first responses
30
34
 
31
- 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. You can override startup behavior with `PI_LENS_STARTUP_MODE=full|minimal|quick`.
35
+ 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`.
32
36
 
33
37
  ### Turn End
34
38
 
@@ -37,12 +41,7 @@ At `turn_end`, pi-lens:
37
41
  - summarizes deferred findings (for example duplicates/circulars)
38
42
  - persists turn findings for next context injection
39
43
  - updates debt/diagnostic tracking and cleans transient state
40
-
41
- Inline output is intentionally concise and actionable.
42
-
43
- - **Blocking issues**: shown inline and stop progress until fixed
44
- - **Warnings**: summarized, with deeper detail in `/lens-booboo`
45
- - **Health/telemetry**: available in `/lens-health`
44
+ - renders a review-graph impact cascade showing affected files and diagnostic propagation
46
45
 
47
46
  ## Install
48
47
 
@@ -59,11 +58,15 @@ pi install git:github.com/apmantza/pi-lens
59
58
  ## Run
60
59
 
61
60
  ```bash
62
- # Standard mode
61
+ # Standard mode (LSP enabled by default)
63
62
  pi
64
63
 
65
- # Optional safety: disable unified LSP and use fallbacks
66
- pi --no-lsp
64
+ # Optional switches
65
+ pi --no-lsp # Disable unified LSP, use language-specific fallbacks
66
+ pi --no-autoformat # Skip auto-formatting
67
+ pi --no-autofix # Skip auto-fix (Biome, Ruff, ESLint, stylelint, sqlfluff, RuboCop)
68
+ pi --no-tests # Skip test runner
69
+ pi --no-shellcheck # Disable shellcheck runner
67
70
  ```
68
71
 
69
72
  ## Key Commands
@@ -71,50 +74,181 @@ pi --no-lsp
71
74
  - `/lens-booboo` — full quality report for current project state
72
75
  - `/lens-health` — runtime health, latency, and diagnostic telemetry
73
76
 
74
- ## Runners
77
+ ## Language Coverage
75
78
 
76
- Registered dispatch runners:
79
+ pi-lens supports **35+ languages** through dispatch runners and LSP integration:
77
80
 
78
- - `lsp`, `ts-lsp`, `pyright`
79
- - `biome-check-json`, `biome-lint`, `ruff-lint`, `eslint`, `oxlint`
80
- - `tree-sitter`, `ast-grep-napi`, `type-safety`, `similarity`
81
- - `architect`, `python-slop`, `shellcheck`, `spellcheck`
82
- - `yamllint`, `sqlfluff`
83
- - `go-vet`, `golangci-lint`, `rust-clippy`, `rubocop`
81
+ | Language | LSP | Dispatch Runners | Formatter |
82
+ |---|---|---|---|
83
+ | JavaScript/TypeScript | ✓ | lsp, ts-lsp, biome-check-json, tree-sitter, ast-grep-napi, type-safety, similarity, fact-rules, eslint, architect | biome, prettier |
84
+ | Python | ✓ | lsp, pyright, ruff-lint, tree-sitter, python-slop, architect | ruff, black |
85
+ | Go | ✓ | lsp, go-vet, golangci-lint, tree-sitter | gofmt |
86
+ | Rust | ✓ | lsp, rust-clippy, tree-sitter | rustfmt |
87
+ | Ruby | ✓ | lsp, rubocop, tree-sitter | rubocop, standardrb |
88
+ | C/C++ | ✓ | lsp, cpp-check | clang-format |
89
+ | Shell | ✓ | lsp, shellcheck | shfmt |
90
+ | CSS/SCSS/Less | ✓ | lsp, stylelint, prettier-check | biome, prettier |
91
+ | HTML | ✓ | lsp, htmlhint, prettier-check | prettier |
92
+ | YAML | ✓ | lsp, yamllint | prettier |
93
+ | JSON | ✓ | lsp | biome, prettier |
94
+ | SQL | — | sqlfluff | sqlfluff |
95
+ | Markdown | — | spellcheck, markdownlint | prettier |
96
+ | Docker | ✓ | lsp, hadolint | — |
97
+ | PHP | ✓ | lsp, php-lint, phpstan | php-cs-fixer |
98
+ | PowerShell | ✓ | lsp, psscriptanalyzer | — |
99
+ | Prisma | ✓ | lsp, prisma-validate | — |
100
+ | C# | ✓ | lsp, dotnet-build | csharpier |
101
+ | F# | ✓ | lsp | fantomas |
102
+ | Java | ✓ | lsp, javac | — |
103
+ | Kotlin | ✓ | lsp, ktlint | ktlint |
104
+ | Swift | ✓ | lsp | swiftformat |
105
+ | Dart | ✓ | lsp, dart-analyze | dart format |
106
+ | Lua | ✓ | lsp | stylua |
107
+ | Zig | ✓ | lsp, zig-check | zig fmt |
108
+ | Haskell | ✓ | lsp | ormolu |
109
+ | Elixir | ✓ | lsp, elixir-check, credo | mix format |
110
+ | Gleam | ✓ | lsp, gleam-check | gleam format |
111
+ | OCaml | ✓ | lsp | ocamlformat |
112
+ | Clojure | ✓ | lsp | — |
113
+ | Terraform | ✓ | lsp, tflint | terraform fmt |
114
+ | Nix | ✓ | lsp | nixfmt |
115
+ | TOML | ✓ | lsp, taplo | taplo |
116
+ | CMake | ✓ | lsp | — |
117
+
118
+ ## Fact Rules Pipeline
119
+
120
+ Dispatch includes a fact-rule engine that extracts function-level metrics (cyclomatic complexity, nesting depth, outgoing calls) and evaluates quality rules inline:
121
+
122
+ - **high-complexity** — flags functions exceeding configurable CC thresholds
123
+ - **unsafe-boundary** — detects dangerous boundary crossings (unvalidated user input → trusted context)
124
+ - **high-fan-out** — flags excessive outgoing call count (default threshold: 20)
125
+ - **comment-facts** — classifies comment quality (TODO density, doc coverage)
126
+ - **try-catch-facts** — flags empty/obscuring catch blocks
127
+ - **import-facts** — detects circular/star/unused imports
128
+ - **file-role** — classifies files as source/test/config/vendor and adjusts severity
129
+
130
+ ## Tree-sitter Rules
131
+
132
+ Structural rules are organized by language in `rules/tree-sitter-queries/`:
133
+
134
+ - **TypeScript** (18 rules): console-statement, debugger, deep-nesting, eval, sql-injection, ssrf, weak-hash, unsafe-regex, variable-shadowing, and more
135
+ - **Python** (26 rules): debug statements, hardcoded secrets, mutable class attrs, unsafe regex, empty except, and more
136
+ - **Go** (17 rules): defer-in-loop, hardcoded secrets, unchecked errors, and more
137
+ - **Rust** (6 rules): unsafe blocks, unwrap outside tests, and more
138
+ - **Ruby** (15 rules): empty rescue, rescue Exception, debugger, hardcoded secrets, and more
139
+
140
+ Plus **180+ ast-grep rules** in `rules/ast-grep-rules/` covering security (no-eval, jwt-no-verify, no-hardcoded-secrets, no-insecure-randomness), correctness (strict-equality, empty-catch, no-cond-assign), and style patterns across JS/TS/Python.
141
+
142
+ ## Review Graph
143
+
144
+ pi-lens builds a review graph (`file → symbol → dependency`) during session and uses it at turn end to render an impact cascade: which files were affected by a change and how diagnostics propagated through the dependency graph. Nodes track kind, language, and export status; edges track contains/imports/calls/references.
145
+
146
+ ## LSP Support
147
+
148
+ pi-lens includes **37 language server definitions**. LSP is **enabled by default** (`--lsp` or no flag). Servers are auto-discovered from PATH, project `node_modules`, and managed installs. When a server is not installed, pi-lens offers an interactive install prompt.
149
+
150
+ LSP servers for: TypeScript, Deno, Python (pyright + pylsp), 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.
84
151
 
85
- Some runners are language/config-gated and may skip when not applicable.
86
- `ast-grep-napi` runs in post-write dispatch for JS/TS with blocker-focused filtering; `/lens-booboo` additionally runs full CLI ast-grep scans.
152
+ ## Runners
153
+
154
+ 44 registered dispatch runners:
155
+
156
+ | Category | Runners |
157
+ |---|---|
158
+ | LSP | `lsp`, `ts-lsp`, `pyright` |
159
+ | JS/TS | `biome-check-json`, `eslint`, `ast-grep-napi`, `type-safety`, `similarity`, `tree-sitter`, `fact-rules` |
160
+ | Python | `ruff-lint`, `tree-sitter`, `python-slop`, `mypy` |
161
+ | Go | `go-vet`, `golangci-lint` |
162
+ | Rust | `rust-clippy` |
163
+ | Ruby | `rubocop` |
164
+ | PHP | `php-lint`, `phpstan` |
165
+ | C# | `dotnet-build` |
166
+ | Java | `javac` |
167
+ | Kotlin | `ktlint` |
168
+ | Dart | `dart-analyze` |
169
+ | Elixir | `elixir-check`, `credo` |
170
+ | Gleam | `gleam-check` |
171
+ | Zig | `zig-check` |
172
+ | C/C++ | `cpp-check` |
173
+ | Docker | `hadolint` |
174
+ | HTML | `htmlhint` |
175
+ | CSS | `stylelint`, `prettier-check` |
176
+ | Markdown | `markdownlint`, `spellcheck` |
177
+ | Shell | `shellcheck`, `shfmt` |
178
+ | YAML | `yamllint` |
179
+ | SQL | `sqlfluff` |
180
+ | TOML | `taplo` |
181
+ | Terraform | `tflint` |
182
+ | PowerShell | `psscriptanalyzer` |
183
+ | Prisma | `prisma-validate` |
184
+ | Architecture | `architect`, `fact-rules` |
185
+
186
+ Runners are language/config-gated and skip when not applicable. `ast-grep-napi` runs in post-write dispatch for JS/TS with blocker-focused filtering; `/lens-booboo` additionally runs full CLI ast-grep scans.
187
+
188
+ ## Formatters
189
+
190
+ pi-lens auto-detects and runs **26 formatters** based on project config:
191
+
192
+ biome, prettier, ruff, black, sqlfluff, gofmt, rustfmt, zig fmt, dart format, shfmt, nixfmt, mix format, ocamlformat, clang-format, ktlint, rubocop, standardrb, gleam format, terraform fmt, php-cs-fixer, csharpier, fantomas, swiftformat, stylua, ormolu, taplo
193
+
194
+ Detection rules:
195
+ - **Config-gated**: only runs when project config indicates usage (e.g. `biome.json`, `.prettierrc`, `ruff.toml`)
196
+ - **Nearest-wins**: when multiple formatter configs exist at different directory levels, the one closest to the edited file wins
197
+ - **Biome-default**: for JS/TS files without Prettier or Biome config, Biome is used as the default formatter
198
+ - **Ruff-default**: for Python files without Black config, Ruff format is used when available
87
199
 
88
200
  ## Dependencies
89
201
 
90
202
  Auto-install behavior depends on gate type:
91
203
 
92
- - **Config-gated**: installs only when project config/deps indicate usage.
93
- - **Flow/language-gated**: installs when the runtime path needs it for the current file/session flow.
94
- - **Operational prewarm**: installs during session warm scans / turn-end analysis paths.
204
+ - **Config-gated**: installs only when project config/deps indicate usage
205
+ - **Flow/language-gated**: installs when the runtime path needs it for the current file/session flow
206
+ - **Operational prewarm**: installs during session warm scans / turn-end analysis paths
207
+ - **GitHub release**: platform-specific binary downloaded from GitHub releases to `~/.pi-lens/bin/`
95
208
 
96
209
  | Tool | Purpose | Auto-installed | Gate |
97
210
  |---|---|---|---|
98
- | `@biomejs/biome` | JS/TS lint/format/autofix | Yes | Config-gated (`biome.json`/`biome.jsonc` or `@biomejs/biome` dep) |
99
- | `prettier` | Formatting fallback | Yes | Config-gated (Prettier dep or `package.json#prettier`) |
100
- | `yamllint` | YAML linting | Yes | Config-gated (`.yamllint*` / tool section / dep hint) |
101
- | `sqlfluff` | SQL linting/formatting | Yes | Config-gated (`.sqlfluff` / tool section / dep hint) |
102
- | `ruff` | Python lint/format/autofix | Yes | Language-default + flow-gated (Python detected; respects `--no-autofix-ruff`) |
103
- | `typescript-language-server` | Unified LSP diagnostics | Yes | Language-default + flow-gated (JS/TS detected and LSP enabled) |
104
- | `pyright` | Python type diagnostics fallback | Yes | Flow/language-gated (Python fallback paths) |
105
- | `@ast-grep/cli` (`sg`) | AST scans/search/replace | Yes | Operational prewarm + analysis flows |
106
- | `knip` | Dead code analysis | Yes | Operational prewarm + turn-end flows (JS/TS language + config gated at startup) |
107
- | `jscpd` | Duplicate code detection | Yes | Operational prewarm + turn-end flows (JS/TS language + config gated at startup) |
211
+ | `@biomejs/biome` | JS/TS lint/format/autofix | Yes | Config-gated |
212
+ | `prettier` | Formatting fallback | Yes | Config-gated |
213
+ | `yamllint` | YAML linting | Yes | Config-gated |
214
+ | `sqlfluff` | SQL linting/formatting | Yes | Config-gated |
215
+ | `ruff` | Python lint/format/autofix | Yes | Language-default + flow-gated |
216
+ | `typescript-language-server` | Unified LSP diagnostics | Yes | Language-default |
217
+ | `typescript` | TypeScript compiler | Yes | Language-default |
218
+ | `pyright` | Python type diagnostics fallback | Yes | Flow/language-gated |
219
+ | `@ast-grep/cli` (sg) | AST scans/search/replace | Yes | Operational prewarm |
220
+ | `knip` | Dead code analysis | Yes | Operational prewarm + config-gated |
221
+ | `jscpd` | Duplicate code detection | Yes | Operational prewarm + config-gated |
108
222
  | `madge` | Circular dependency analysis | Yes | Turn-end analysis flow |
109
-
110
- LSP is enabled by default. pi-lens includes many language-server definitions (including up to 31+ servers), and activates them when the server is installed and the project/root detection matches the file.
111
-
112
- Optional safety switch:
113
-
114
- - `--no-lsp` disables unified LSP dispatch and falls back to language-specific checks where available (for example `ts-lsp`, `pyright`).
115
- - `--lens-guard` (experimental) blocks `git commit`/`git push` attempts when unresolved pi-lens blockers are pending.
223
+ | `mypy` | Python type checking | Yes | Flow-gated |
224
+ | `stylelint` | CSS/SCSS/Less linting | Yes | Config-gated |
225
+ | `markdownlint-cli2` | Markdown linting | Yes | Config-gated |
226
+ | `shellcheck` | Shell script linting | Yes | GitHub release |
227
+ | `shfmt` | Shell script formatting | Yes | GitHub release |
228
+ | `rust-analyzer` | Rust LSP | Yes | GitHub release |
229
+ | `golangci-lint` | Go linting | Yes | GitHub release |
230
+ | `hadolint` | Dockerfile linting | Yes | GitHub release |
231
+ | `ktlint` | Kotlin linting | Yes | GitHub release |
232
+ | `tflint` | Terraform linting | Yes | GitHub release |
233
+ | `taplo` | TOML linting/formatting | Yes | GitHub release |
234
+ | `terraform-ls` | Terraform LSP | Yes | GitHub release |
235
+ | `htmlhint` | HTML linting | Yes | Config-gated |
236
+ | `@prisma/language-server` | Prisma LSP | Yes | Flow-gated |
237
+ | `dockerfile-language-server-nodejs` | Dockerfile LSP | Yes | Flow-gated |
238
+ | `intelephense` | PHP LSP | Yes | Flow-gated |
239
+ | `bash-language-server` | Bash LSP | Yes | Language-default |
240
+ | `yaml-language-server` | YAML LSP | Yes | Language-default |
241
+ | `vscode-langservers-extracted` | JSON/ESLint/CSS/HTML LSP | Yes | Language-default |
242
+ | `vscode-css-languageserver` | CSS LSP | Yes | Language-default |
243
+ | `vscode-html-languageserver-bin` | HTML LSP | Yes | Language-default |
244
+ | `svelte-language-server` | Svelte LSP | Yes | Flow-gated |
245
+ | `@vue/language-server` | Vue LSP | Yes | Flow-gated |
246
+ | `psscriptanalyzer` | PowerShell linting | Manual | — |
247
+
248
+ 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.
116
249
 
117
250
  ## Notes
118
251
 
119
252
  - Not every auto-install runs in every project: gate type decides when install is attempted.
120
253
  - Rule packs are customizable via project-level rule directories.
254
+ - Inline suppression: `// pi-lens-ignore` or `# pi-lens-ignore` comments suppress diagnostic output for that line.
@@ -8,6 +8,8 @@
8
8
  * No external dependencies — purely tracks tool call history.
9
9
  */
10
10
 
11
+ import { normalizeMapKey } from "./path-utils.js";
12
+
11
13
  // --- Types ---
12
14
 
13
15
  export type BehaviorWarning = {
@@ -107,9 +109,10 @@ export class AgentBehaviorClient {
107
109
 
108
110
  // Track edits per file
109
111
  if (filePath) {
112
+ const key = normalizeMapKey(filePath);
110
113
  this.fileEditCount.set(
111
- filePath,
112
- (this.fileEditCount.get(filePath) ?? 0) + 1,
114
+ key,
115
+ (this.fileEditCount.get(key) ?? 0) + 1,
113
116
  );
114
117
  }
115
118
  }
@@ -55,7 +55,7 @@ export class AstGrepClient {
55
55
  /**
56
56
  * Check if ast-grep CLI is available, auto-install if not
57
57
  */
58
- async ensureAvailable(): Promise<boolean> {
58
+ ensureAvailable(): Promise<boolean> {
59
59
  return this.runner.ensureAvailable();
60
60
  }
61
61
 
@@ -57,7 +57,7 @@ export interface Diagnostic {
57
57
  message?: string;
58
58
  }
59
59
 
60
- interface LogContext {
60
+ export interface LogContext {
61
61
  model: string;
62
62
  sessionId: string;
63
63
  turnIndex: number;
@@ -100,7 +100,7 @@ export function createDiagnosticLogger(): DiagnosticLogger {
100
100
  try {
101
101
  await fs.promises.appendFile(getLogFile(), lines);
102
102
  } catch (err) {
103
- // Log write failure but don't block
103
+ // pi-lens-ignore: missing-error-propagation — fire-and-forget log write, must not throw
104
104
  console.error("Failed to write diagnostic log:", err);
105
105
  }
106
106
  writing = false;
@@ -137,7 +137,8 @@ export function createDiagnosticLogger(): DiagnosticLogger {
137
137
  },
138
138
 
139
139
  async flush() {
140
- // Wait for any pending writes to complete
140
+ // Drain any buffered entries, then wait for the write to finish.
141
+ await writePending();
141
142
  while (writing) {
142
143
  await new Promise((resolve) => setTimeout(resolve, 10));
143
144
  }
@@ -26,6 +26,7 @@ import { RUNTIME_CONFIG } from "../runtime-config.js";
26
26
  import { safeSpawnAsync } from "../safe-spawn.js";
27
27
  import { classifyDiagnostic } from "./diagnostic-taxonomy.js";
28
28
  import { FactStore } from "./fact-store.js";
29
+ import { getToolPlan } from "./plan.js";
29
30
  import { resolveRunnerPath } from "./runner-context.js";
30
31
  import { getToolProfile } from "./tool-profile.js";
31
32
  import type {
@@ -122,10 +123,11 @@ export function createDispatchContext(
122
123
  blockingOnly?: boolean,
123
124
  modifiedRanges?: import("./types.js").ModifiedRange[],
124
125
  ): DispatchContext {
126
+ const absoluteFilePath = resolveRunnerPath(cwd, filePath);
125
127
  const normalizedCwd = normalizeMapKey(
126
- resolveLanguageRootForFile(path.resolve(cwd, filePath), cwd),
128
+ resolveLanguageRootForFile(absoluteFilePath, cwd),
127
129
  );
128
- const normalizedFilePath = resolveRunnerPath(normalizedCwd, filePath);
130
+ const normalizedFilePath = normalizeMapKey(absoluteFilePath);
129
131
  const kind = detectFileKind(normalizedFilePath);
130
132
 
131
133
  return {
@@ -208,6 +210,40 @@ function dedupeOverlappingDiagnostics(diagnostics: Diagnostic[]): Diagnostic[] {
208
210
  return [...byKey.values()];
209
211
  }
210
212
 
213
+ /**
214
+ * Apply inline suppression comments.
215
+ * Syntax: `// pi-lens-ignore: rule-id` (JS/TS) or `# pi-lens-ignore: rule-id` (Python/Ruby/etc.)
216
+ * Place on the same line as the diagnostic or the line immediately above it.
217
+ */
218
+ function applyInlineSuppressions(diagnostics: Diagnostic[], content: string): Diagnostic[] {
219
+ if (!content || !diagnostics.length) return diagnostics;
220
+
221
+ // Build a set of (line, ruleId) pairs that are suppressed.
222
+ // Line numbers are 1-based to match diagnostic line numbers.
223
+ const suppressed = new Set<string>();
224
+ const lines = content.split("\n");
225
+ const SUPPRESS_RE = /(?:\/\/|#)\s*pi-lens-ignore:\s*(.+)/;
226
+ for (let i = 0; i < lines.length; i++) {
227
+ const m = SUPPRESS_RE.exec(lines[i]);
228
+ if (!m) continue;
229
+ const rules = m[1].split(",").map((r) => r.trim()).filter(Boolean);
230
+ const suppressedLine = i + 1; // same line (1-based)
231
+ const nextLine = i + 2; // next line (1-based)
232
+ for (const ruleId of rules) {
233
+ suppressed.add(`${suppressedLine}:${ruleId}`);
234
+ suppressed.add(`${nextLine}:${ruleId}`);
235
+ }
236
+ }
237
+
238
+ if (suppressed.size === 0) return diagnostics;
239
+
240
+ return diagnostics.filter((d) => {
241
+ const ruleId = d.rule ?? d.id ?? "";
242
+ const line = d.line ?? 1;
243
+ return !suppressed.has(`${line}:${ruleId}`);
244
+ });
245
+ }
246
+
211
247
  function suppressLintOverlapsWithLsp(diagnostics: Diagnostic[]): Diagnostic[] {
212
248
  const lspBySpanClass = new Set<string>();
213
249
  const lspByLine = new Set<string>();
@@ -315,15 +351,40 @@ function buildCoverageNotice(
315
351
  );
316
352
  if (relevant.length === 0) return undefined;
317
353
 
318
- const hasCoverage = relevant.some(
354
+ // Check primary runners first
355
+ const primaryHasCoverage = relevant.some(
319
356
  (r) => r.status === "succeeded" || r.status === "failed",
320
357
  );
321
- if (hasCoverage) return undefined;
358
+ if (primaryHasCoverage) return undefined;
322
359
 
323
- const allSkipped = relevant.every(
360
+ const allPrimarySkipped = relevant.every(
324
361
  (r) => r.status === "skipped" || r.status === "when_skipped",
325
362
  );
326
- if (!allSkipped) return undefined;
363
+ if (!allPrimarySkipped) return undefined;
364
+
365
+ const plan = getToolPlan(ctx.kind);
366
+ const fallbackRunnerIds = new Set(
367
+ (plan?.groups ?? [])
368
+ .filter(
369
+ (group) =>
370
+ !group.runnerIds.every((runnerId) => primary.runnerIds.includes(runnerId)),
371
+ )
372
+ .flatMap((group) => group.runnerIds)
373
+ .filter((runnerId) => !primary.runnerIds.includes(runnerId)),
374
+ );
375
+
376
+ // Structural-only runners (tree-sitter, ast-grep, similarity) are not
377
+ // substitutes for real linters — don't suppress the notice if only they ran.
378
+ const STRUCTURAL_RUNNERS = new Set([
379
+ "tree-sitter", "ast-grep-napi", "similarity", "spellcheck", "architect", "fact-rules",
380
+ ]);
381
+ const anyLinterHasCoverage = runnerLatencies.some(
382
+ (r) =>
383
+ fallbackRunnerIds.has(r.runnerId) &&
384
+ !STRUCTURAL_RUNNERS.has(r.runnerId) &&
385
+ (r.status === "succeeded" || r.status === "failed"),
386
+ );
387
+ if (anyLinterHasCoverage) return undefined;
327
388
 
328
389
  const onceKey = `${ctx.kind}:${normalizeMapKey(ctx.filePath)}`;
329
390
  if (coverageNoticeSeen.has(onceKey)) return undefined;
@@ -607,7 +668,9 @@ export async function dispatchForFile(
607
668
  // This avoids partial-baseline corruption when processing multiple groups.
608
669
  const dedupedDiagnostics = dedupeOverlappingDiagnostics(allDiagnostics);
609
670
  const overlapSuppressed = suppressLintOverlapsWithLsp(dedupedDiagnostics);
610
- let visibleDiagnostics = overlapSuppressed;
671
+ const fileContent = ctx.facts.getFileFact<string>(ctx.filePath, "file.content") ?? "";
672
+ const inlineSuppressed = applyInlineSuppressions(overlapSuppressed, fileContent);
673
+ let visibleDiagnostics = inlineSuppressed;
611
674
  let resolvedCount = 0;
612
675
  if (ctx.deltaMode && previousBaseline) {
613
676
  const filtered = filterDelta(visibleDiagnostics, previousBaseline, (d) => d.id);
@@ -627,6 +690,20 @@ export async function dispatchForFile(
627
690
  (d) => d.semantic === "warning" || d.semantic === "none",
628
691
  );
629
692
  const fixedItems = visibleDiagnostics.filter((d) => d.semantic === "fixed");
693
+
694
+ // Append fixed and fixable diagnostics to the persistent worklog
695
+ if (fixedItems.length > 0) {
696
+ import("../fix-worklog.js").then(({ appendToWorklog }) => {
697
+ appendToWorklog(ctx.cwd, fixedItems, true);
698
+ }).catch(() => {});
699
+ }
700
+ const fixableWarnings = warnings.filter((d) => d.fixable);
701
+ if (fixableWarnings.length > 0) {
702
+ import("../fix-worklog.js").then(({ appendToWorklog }) => {
703
+ appendToWorklog(ctx.cwd, fixableWarnings, false);
704
+ }).catch(() => {});
705
+ }
706
+
630
707
  const inlineBlockers = blockers.filter((d) => d.tool !== "similarity");
631
708
  const inlineFixed = fixedItems.filter((d) => d.tool !== "similarity");
632
709
  const coverageNotice = buildCoverageNotice(ctx, runnerLatencies);
@@ -650,7 +727,7 @@ export async function dispatchForFile(
650
727
  totalDurationMs: overallEnd - _overallStart,
651
728
  runners: runnerLatencies,
652
729
  stoppedEarly: stopped,
653
- totalDiagnostics: allDiagnostics.length,
730
+ totalDiagnostics: visibleDiagnostics.length,
654
731
  blockers: blockers.length,
655
732
  warnings: warnings.length,
656
733
  };
@@ -685,7 +762,7 @@ export async function dispatchForFile(
685
762
  duration: r.durationMs,
686
763
  status: r.status,
687
764
  })),
688
- totalDiagnostics: allDiagnostics.length,
765
+ totalDiagnostics: visibleDiagnostics.length,
689
766
  blockers: blockers.length,
690
767
  },
691
768
  });