pi-lens 3.8.28 → 3.8.30

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 +150 -0
  2. package/README.md +21 -3
  3. package/clients/architect-client.ts +2 -7
  4. package/clients/ast-grep-client.ts +1 -8
  5. package/clients/ast-grep-parser.ts +1 -1
  6. package/clients/ast-grep-rule-manager.ts +2 -2
  7. package/clients/biome-client.ts +3 -14
  8. package/clients/complexity-client.ts +5 -5
  9. package/clients/dependency-checker.ts +2 -2
  10. package/clients/dispatch/diagnostic-taxonomy.ts +30 -16
  11. package/clients/dispatch/dispatcher.ts +48 -21
  12. package/clients/dispatch/facts/try-catch-facts.ts +4 -3
  13. package/clients/dispatch/integration.ts +57 -38
  14. package/clients/dispatch/rules/error-swallowing.ts +1 -1
  15. package/clients/dispatch/rules/sonar-rules.ts +21 -15
  16. package/clients/dispatch/rules/unsafe-boundary.ts +13 -7
  17. package/clients/dispatch/runners/ast-grep-napi.ts +15 -13
  18. package/clients/dispatch/runners/biome-check.ts +65 -31
  19. package/clients/dispatch/runners/eslint.ts +4 -29
  20. package/clients/dispatch/runners/golangci-lint.ts +2 -5
  21. package/clients/dispatch/runners/index.ts +24 -25
  22. package/clients/dispatch/runners/lsp.ts +20 -22
  23. package/clients/dispatch/runners/pyright.ts +6 -3
  24. package/clients/dispatch/runners/python-slop.ts +1 -2
  25. package/clients/dispatch/runners/shellcheck.ts +3 -13
  26. package/clients/dispatch/runners/similarity.ts +68 -48
  27. package/clients/dispatch/runners/tree-sitter.ts +166 -117
  28. package/clients/dispatch/runners/ts-lsp.ts +4 -55
  29. package/clients/dispatch/runners/utils/diagnostic-parsers.ts +10 -7
  30. package/clients/dispatch/runners/utils/runner-helpers.ts +53 -11
  31. package/clients/dispatch/types.ts +10 -1
  32. package/clients/file-role.ts +74 -35
  33. package/clients/file-utils.ts +39 -0
  34. package/clients/fix-worklog.ts +5 -2
  35. package/clients/formatters.ts +7 -2
  36. package/clients/go-client.ts +0 -1
  37. package/clients/installer/index.ts +474 -170
  38. package/clients/jscpd-client.ts +2 -2
  39. package/clients/knip-client.ts +12 -5
  40. package/clients/language-policy.ts +55 -32
  41. package/clients/language-profile.ts +38 -12
  42. package/clients/latency-logger.ts +1 -1
  43. package/clients/log-cleanup.ts +253 -0
  44. package/clients/lsp/client.ts +118 -54
  45. package/clients/lsp/config.ts +0 -3
  46. package/clients/lsp/index.ts +125 -69
  47. package/clients/lsp/interactive-install.ts +3 -7
  48. package/clients/lsp/launch.ts +204 -64
  49. package/clients/lsp/server.ts +382 -173
  50. package/clients/native-rust-client.ts +32 -17
  51. package/clients/pipeline.ts +57 -75
  52. package/clients/production-readiness.ts +0 -4
  53. package/clients/project-index.ts +11 -2
  54. package/clients/review-graph/builder.ts +59 -43
  55. package/clients/ruff-client.ts +1 -1
  56. package/clients/runner-tracker.ts +4 -2
  57. package/clients/runtime-session.ts +42 -164
  58. package/clients/runtime-tool-result.ts +42 -26
  59. package/clients/runtime-turn.ts +68 -16
  60. package/clients/rust-client.ts +2 -3
  61. package/clients/scan-utils.ts +0 -4
  62. package/clients/secrets-scanner.ts +33 -4
  63. package/clients/sg-runner.ts +46 -22
  64. package/clients/subprocess-client.ts +2 -2
  65. package/clients/test-runner-client.ts +7 -34
  66. package/clients/tool-availability.ts +0 -1
  67. package/clients/tree-sitter-cache.ts +1 -1
  68. package/clients/tree-sitter-client.ts +68 -65
  69. package/clients/tree-sitter-symbol-extractor.ts +12 -3
  70. package/clients/type-coverage-client.ts +0 -1
  71. package/clients/type-safety-client.ts +0 -2
  72. package/commands/booboo.ts +161 -93
  73. package/config/biome/core.jsonc +1 -3
  74. package/index.ts +488 -493
  75. package/package.json +1 -1
  76. package/rules/ast-grep-rules/rules/unchecked-sync-fs.yml +27 -22
  77. package/rules/tree-sitter-queries/go/go-command-injection.yml +5 -5
  78. package/rules/tree-sitter-queries/go/go-direct-panic.yml +2 -2
  79. package/rules/tree-sitter-queries/go/go-empty-if-err.yml +2 -2
  80. package/rules/tree-sitter-queries/go/go-ignored-call-result.yml +4 -3
  81. package/rules/tree-sitter-queries/go/go-insecure-random.yml +3 -3
  82. package/rules/tree-sitter-queries/go/go-log-fatal.yml +3 -3
  83. package/rules/tree-sitter-queries/go/go-path-traversal.yml +3 -3
  84. package/rules/tree-sitter-queries/go/go-sql-injection.yml +4 -4
  85. package/rules/tree-sitter-queries/go/go-weak-hash.yml +3 -3
  86. package/rules/tree-sitter-queries/python/python-command-injection.yml +7 -7
  87. package/rules/tree-sitter-queries/python/python-cross-language-method.yml +2 -2
  88. package/rules/tree-sitter-queries/python/python-insecure-deserialization.yml +3 -3
  89. package/rules/tree-sitter-queries/python/python-insecure-random.yml +3 -3
  90. package/rules/tree-sitter-queries/python/python-sql-injection.yml +2 -2
  91. package/rules/tree-sitter-queries/python/python-ssrf.yml +3 -3
  92. package/rules/tree-sitter-queries/python/python-subprocess-shell.yml +4 -4
  93. package/rules/tree-sitter-queries/python/python-weak-hash.yml +3 -3
  94. package/rules/tree-sitter-queries/ruby/ruby-insecure-deserialization.yml +3 -3
  95. package/rules/tree-sitter-queries/typescript/console-statement.yml +1 -2
  96. package/rules/tree-sitter-queries/typescript/deep-promise-chain.yml +5 -5
  97. package/rules/tree-sitter-queries/typescript/ts-command-injection.yml +6 -4
  98. package/rules/tree-sitter-queries/typescript/ts-detached-async-call.yml +2 -2
  99. package/rules/tree-sitter-queries/typescript/ts-insecure-random.yml +4 -4
  100. package/rules/tree-sitter-queries/typescript/ts-react-antipatterns.yml +6 -4
  101. package/rules/tree-sitter-queries/typescript/ts-ssrf.yml +4 -3
  102. package/rules/tree-sitter-queries/typescript/ts-weak-hash.yml +3 -3
  103. package/scripts/download-grammars.js +0 -1
  104. package/skills/ast-grep/SKILL.md +38 -251
  105. package/tools/ast-grep-replace.js +1 -1
  106. package/tools/ast-grep-replace.ts +1 -1
  107. package/tools/ast-grep-search.js +1 -1
  108. package/tools/ast-grep-search.ts +1 -1
  109. package/tools/lsp-navigation.js +4 -4
  110. package/tools/lsp-navigation.ts +4 -4
  111. package/clients/auto-loop.ts +0 -200
  112. package/clients/config-validator.ts +0 -558
  113. package/clients/fix-scanners.ts +0 -303
  114. package/clients/scan-architectural-debt.ts +0 -203
  115. package/config/eslint/core.mjs +0 -28
  116. package/rules/ast-grep-rules/rules/empty-catch-js.yml +0 -48
  117. package/rules/ast-grep-rules/rules/empty-catch.yml +0 -48
  118. package/rules/ast-grep-rules/rules/getter-return-js.yml +0 -59
  119. package/rules/ast-grep-rules/rules/getter-return.yml +0 -59
  120. package/rules/ast-grep-rules/rules/in-correct-optional-input-type.yml +0 -63
  121. package/rules/ast-grep-rules/rules/long-method.yml +0 -15
  122. package/rules/ast-grep-rules/rules/missed-concurrency-js.yml +0 -25
  123. package/rules/ast-grep-rules/rules/missed-concurrency.yml +0 -25
  124. package/rules/ast-grep-rules/rules/missing-component-decorator.yml +0 -30
  125. package/rules/ast-grep-rules/rules/no-array-sort-without-comparator-js.yml +0 -8
  126. package/rules/ast-grep-rules/rules/no-array-sort-without-comparator.yml +0 -8
  127. package/rules/ast-grep-rules/rules/no-await-in-loop-js.yml +0 -30
  128. package/rules/ast-grep-rules/rules/no-await-in-loop.yml +0 -46
  129. package/rules/ast-grep-rules/rules/no-constructor-return-js.yml +0 -28
  130. package/rules/ast-grep-rules/rules/no-constructor-return.yml +0 -28
  131. package/rules/ast-grep-rules/rules/no-delete-operator.yml +0 -9
  132. package/rules/ast-grep-rules/rules/no-dupe-args-js.yml +0 -15
  133. package/rules/ast-grep-rules/rules/no-dupe-args.yml +0 -15
  134. package/rules/ast-grep-rules/rules/no-single-char-var.yml +0 -12
  135. package/rules/ast-grep-rules/rules/prefer-async-await-js.yml +0 -13
  136. package/rules/ast-grep-rules/rules/prefer-async-await.yml +0 -13
  137. package/rules/ast-grep-rules/rules/toctou-js.yml +0 -112
  138. package/rules/ast-grep-rules/rules/toctou.yml +0 -112
  139. package/rules/ast-grep-rules/rules/unchecked-throwing-call-ruby.yml +0 -47
package/CHANGELOG.md CHANGED
@@ -2,6 +2,156 @@
2
2
 
3
3
  All notable changes to pi-lens will be documented in this file.
4
4
 
5
+ ## [Unreleased]
6
+
7
+ ## [3.8.30] - 2026-04-22
8
+
9
+ ### Fixed
10
+ - **lsp_navigation permanently disabled** — removed stale `lens-lsp` flag check (flag was removed in 3.8.29) that caused every `lsp_navigation` call to short-circuit with `lsp_disabled`; tool now only gates on `--no-lsp`
11
+ - **ast_grep_search / ast_grep_replace auto-install** — switched availability check from sync `isAvailable()` to async `ensureAvailable()` so the auto-installer triggers when `sg` is missing
12
+ - **@ast-grep/cli postinstall skipped** — added `@ast-grep/cli` to `NEEDS_POSTINSTALL`; without it `--ignore-scripts` left ASCII stubs in place of `sg.exe` / `ast-grep.exe` on Windows
13
+ - **Windows .exe binary lookup** — `getToolPath` now also probes the `.exe` extension on Windows, covering packages (like `@ast-grep/cli`) that place a `.exe` directly without a `.cmd` wrapper
14
+ - **jscpd broken on Node 24** — pinned `jscpd` to `3.5.10`; v4 introduced a `reprism` dependency whose `lib/languages/` directory is absent from the published package
15
+ - **TypeScript LSP using home dir as workspace root** — wrapped `TypeScriptServer` and `ESLintServer` roots with `IgnoreHomeRoot` so a `package.json` / eslint config in `~` can no longer hijacks the workspace root; fallback is the file's own directory
16
+ - **CI npm publish runs without token** — gated `publish-npm` job and dry-run step on `NPM_TOKEN` secret being set
17
+ - **Stale compiled .js triggered test failures** — rebuilt project; `secrets-scanner.js` and `project-index.js` were from before the env-var-name false-positive fix and line-number capture fix respectively
18
+ - **ast_grep_search test mock** — updated test mock from `isAvailable` to `ensureAvailable` to match the new async availability check
19
+ - **Stale LSP diagnostics in cascade** — cascade diagnostics now skip entries older than 240s, preventing false positives from earlier test injections bleeding across turns
20
+ - **Biome check on Vue/Svelte** — biome-check-json was briefly skipped on `.vue`/`.svelte` but restored after confirming Biome 2.x has native support; the 3 blocking diagnostics were real lint findings, not parse errors
21
+ - **Vue/Svelte TypeScript SDK** — extracted `findTsserverPath` helper and wired it into `VueServer` and `SvelteServer` `initializationOptions` so Vue/Svelte LSP servers find the correct `typescript.tsdk`
22
+ - **Broken npm .cmd shims on Windows** — `launch.ts` now validates npm `.cmd` shims before spawning; if the target JS file doesn't exist the shim exits with code 1 after a 500ms startup window, pre-checking avoids the delay for all LSP servers on Windows
23
+ - **Tree-sitter WASM path in hoisted installs** — `tree-sitter-client.ts` now resolves `web-tree-sitter/tree-sitter.wasm` via `createRequire` so Node walks `node_modules` ancestors correctly; fixes `ENOENT` crash in pnpm/monorepo layouts where the wasm is not nested under pi-lens's own `node_modules`
24
+ - **Grammar directory lookups in hoisted installs** — `findGrammarsDir` uses the same `createRequire` fix to anchor `web-tree-sitter/grammars` and `tree-sitter-wasms/out` paths correctly in pnpm/monorepo layouts
25
+ - **tree-sitter-gleam download 404** — removed `tree-sitter-gleam.wasm` from grammar downloads; the file was never published in `tree-sitter-wasms@0.1.13`
26
+ - **Pipeline deduplication** — `handleToolResult` now deduplicates concurrent pipeline calls for the same file; the pi framework fires `tool_result` once per hunk in an Edit array, causing duplicate pipeline runs and doubled agent output
27
+
28
+ ### Changed
29
+ - **Tuned false-positive thresholds across all runners** — reduced noise in `lens-booboo` and dispatch for all users:
30
+ - Added `FACT_SEVERITY_FILTER` (`error`/`warning` only) and `MIN_TREE_SITTER_HITS_PER_RULE = 3`
31
+ - Filtered entropy/AI-style warnings from complexity metrics
32
+ - Aligned complexity markdown headers with actual thresholds (`MI < 20`, `cognitive > 80`, `nesting > 8`)
33
+ - Raised `SEMANTIC_SIMILARITY_THRESHOLD` from `0.96` → `0.98` (aligned with dispatch similarity runner)
34
+ - Raised duplicate-string-literal `MIN_DUPLICATES` from `4` → `10`
35
+ - Unregistered `no-magic-numbers` and `high-entropy-string` fact rules globally
36
+
37
+ ### Removed
38
+ - **Dead code across 32 files** — removed 51 sites of unused imports, locals, and parameters flagged by `tsc --noUnusedLocals --noUnusedParameters`:
39
+ - `clients/architect-client.ts`, `ast-grep-client.ts`, `biome-client.ts`, `complexity-client.ts`, `go-client.ts`, `rust-client.ts`, `scan-utils.ts`, `secrets-scanner.ts`, `subprocess-client.ts`, `test-runner-client.ts`, `tool-availability.ts`, `tree-sitter-cache.ts`, `tree-sitter-client.ts`, `type-coverage-client.ts`, `type-safety-client.ts`
40
+ - `clients/dispatch/dispatcher.ts`, `runners/ast-grep-napi.ts`, `runners/golangci-lint.ts`, `runners/index.ts`, `runners/python-slop.ts`, `runners/ts-lsp.ts`, `runners/utils/diagnostic-parsers.ts`
41
+ - `clients/lsp/client.ts`, `config.ts`, `interactive-install.ts`, `launch.ts`, `server.ts`
42
+ - `clients/pipeline.ts`, `review-graph/builder.ts`, `runner-tracker.ts`
43
+ - `commands/booboo.ts`, `index.ts`
44
+
45
+ ### Tests
46
+ - **Pipeline regression tests** — `tests/clients/pipeline.test.ts` (11 tests): secrets blocking, format modification, LSP sync, dispatch blockers, autofix output, test runner skip, all-clear output
47
+ - **Autofix helper tests** — `tests/clients/autofix-helpers.test.ts` (12 tests): config detection (eslint, stylelint, sqlfluff), malformed JSON handling, file change detection after command
48
+ - **LSP lifecycle tests** — `tests/clients/lsp/lifecycle.test.ts` (4 tests): missing binary error, process spawn, immediate exit detection, process kill
49
+ - **FormatService tests** — `tests/clients/format-service.test.ts` (11 tests): disabled/skip mode, no matching formatters, successful run with change detection, formatter failure, external modification detection, singleton behavior, state clearing, file tracking
50
+ - **Dispatch integration tests** — `tests/clients/dispatch/integration.test.ts` (11 tests): `dispatchLintWithResult` empty results, result propagation, warnings-only; `shouldDispatch` for supported/unsupported; `getAvailableRunners` for supported/unsupported
51
+ - **LSP client internals tests** — `tests/clients/lsp/client-internals.test.ts` (13 tests): `handleNotifyOpen` (first open, re-open, pending opens, clear diagnostics, skip when not alive), `handleNotifyChange` (didChange when open, fallback to didOpen, clear stale diagnostics, skip when not alive), `clientWaitForDiagnostics` (immediate resolve if cached, resolve via emitter, timeout, ignore other files)
52
+ - **Runtime event flow test fix** — added missing `gatherCascadeDiagnostics` mock export to `tests/clients/runtime-event-flow.test.ts`
53
+ - **LSP launch tests** — `tests/clients/lsp/launch.test.ts` (8 new tests): `isCmdShimValid` unit tests (target exists/missing, non-npm shim, unreadable file, `.mjs` extension), early `.cmd` shim rejection without spawning, `.ps1` bypass to `.cmd` sibling, `.ps1` fallback to direct `node <js>` execution
54
+ - **Tree-sitter hoisted-install tests** — `tests/clients/tree-sitter-client-init.test.ts` (3 tests): wasm resolution via `require.resolve`, `locateFile` directory derivation, `findGrammarsDir` external package resolution
55
+
56
+ ### Refactored
57
+ - **Extract `detectFileChangedAfterCommand`** — moved from `clients/pipeline.ts` to `clients/file-utils.ts` and exported for reuse/testing; imported back into `pipeline.ts`; `tests/clients/autofix-helpers.test.ts` now imports the real function instead of reimplementing a copy
58
+ - **Export testable pipeline helpers** — exported `hasEslintConfig`, `hasStylelintConfig`, `hasSqlfluffConfig` from `clients/pipeline.ts` so config detection is testable
59
+ - **Export LSP client internals** — exported `clientWaitForDiagnostics`, `handleNotifyOpen`, `handleNotifyChange`, and `LSPClientState` from `clients/lsp/client.ts` for direct testing with mocks
60
+ - **Export `isCmdShimValid`** — exported from `clients/lsp/launch.ts` so the npm `.cmd` shim validator is unit-testable
61
+
62
+ ### CI
63
+ - **Dead-code gate** — `lint-and-typecheck` job now runs `tsc --noUnusedLocals --noUnusedParameters --noEmit` alongside `--noEmit` so dead code regressions fail CI immediately
64
+
65
+ ## [3.8.29] - 2026-04-21
66
+
67
+ ### Added
68
+ - **New diagnostic commands** — added `/lens-tools` and `/lens-health` for system visibility:
69
+ - `/lens-tools` — shows tool installation status: globally installed, pi-lens auto-installed, or npx fallback
70
+ - `/lens-health` — shows runtime health: pipeline crashes, slow runners, diagnostic stats
71
+ - Both provide actionable visibility into the pi-lens toolchain
72
+ - **Streamlined ast-grep skill** — reduced skill from 7,759 bytes to 2,313 bytes (~70% reduction):
73
+ - Removed verbose CLI tips and YAML rule authoring sections (agent uses tools, not CLI)
74
+ - Removed redundant testing documentation
75
+ - Kept essential: Golden Rules, Quick Reference, Common Gotchas
76
+ - **Configurable log cleanup** — automatic retention and rotation for `~/.pi-lens/*.log` files:
77
+ - Environment variable `PI_LENS_LOG_RETENTION_DAYS` (default: 7) — days to keep log files
78
+ - Environment variable `PI_LENS_MAX_LOG_SIZE_MB` (default: 10) — max size before rotation
79
+ - Runs automatically on session start, notifies when cleanup occurs
80
+ - Rotated backups (`.log.*`) cleaned after retention period
81
+ - Project-level logs (`{cwd}/.pi-lens/*`) intentionally excluded from cleanup
82
+
83
+ ### Changed
84
+ - **`/lens-tools` output improved** — added explanatory note when GitHub-release tools are shown as missing: "GitHub-release tools auto-install when you open files of those languages"
85
+ - **Simplified agent prompts** — removed verbose prompt sections to reduce token burn:
86
+ - Removed startup notes about project rules count (now just logged, not shown)
87
+ - Removed tooling hints for missing language tools (Go/Rust/Ruby install suggestions)
88
+ - Removed project rules section from system prompt (no longer injects `## Project Rules` block)
89
+ - Updated core guidance to clarify: automated checks run on edits/writes, blocking errors shown inline must be fixed
90
+ - **Simplified CLI flags** — removed 16 flags to reduce surface area and cognitive load:
91
+ - Removed per-tool disable flags: `--no-biome`, `--no-ast-grep`, `--no-shellcheck`, `--no-madge`, `--no-oxlint`, `--no-ruff`, `--no-go`, `--no-rust`
92
+ - Removed per-tool autofix flags: `--no-autofix-biome`, `--no-autofix-ruff`
93
+ - Removed feature flags: `--lens-verbose`, `--error-debt`, `--auto-install`, `--lens-eslint-core`
94
+ - Removed redundant `--lens-lsp` flag (LSP is default-on; use `--no-lsp` to disable)
95
+ - Removed internal dead flag: `--lens-blocking-only`
96
+ - **Removed `--no-lsp-install` flag** — LSP servers now always auto-install when needed (no manual opt-out)
97
+ - New minimal flag set: `--no-lsp`, `--no-autoformat`, `--no-autofix`, `--no-tests`, `--no-delta`, `--lens-guard`
98
+ - **Cross-platform line ending handling** — all `.split("\n")` changed to `.split(/\r?\n/)` for Windows CRLF compatibility (11 files updated)
99
+
100
+ ### Fixed
101
+ - **Biome VCS/ignore file errors eliminated** — disabled VCS integration in biome config to prevent "ignore file not found" errors:
102
+ - Changed `vcs.enabled: true` → `vcs.enabled: false` in `config/biome/core.jsonc`
103
+ - Biome was searching for `.gitignore` files that don't exist when running on arbitrary projects via pi-lens
104
+ - Eliminates biome:parse-error spam in logs when biome runs outside its config directory
105
+ - **LSP server thrashing eliminated** — added 240s idle timeout to prevent repeated LSP shutdown/startup cycles:
106
+ - New `scheduleLSPIdleReset()` in `runtime-turn.ts` defers server reset when no files modified
107
+ - Cancel pending reset when active editing resumes (avoids interrupting workflows)
108
+ - Eliminates ~1-2s cold-start penalty during active development sessions
109
+ - Debug logging added for scheduling and cancellation events
110
+ - **Biome check runner JSON parsing** — fixed error where biome's stderr warnings broke JSON parsing:
111
+ - Changed from parsing `stdout || stderr` to parsing `stdout` only
112
+ - Biome outputs text warnings (e.g., "couldn't find ignore file") to stderr which broke the JSON parser
113
+ - Fixes biome-check-json runner failing with parse errors instead of providing lint diagnostics
114
+ - **Auto-install verification gap** — `getToolPath()` now verifies tool binaries actually work before using them:
115
+ - Runs `--version` check on local npm tools (not just file existence)
116
+ - Detects broken/corrupted installations (e.g., wrapper exists but package missing)
117
+ - Triggers automatic reinstall when binary verification fails
118
+ - Fixes case where `@biomejs/biome` package deleted but `.cmd` wrapper remained
119
+ - **Error swallowing in tool availability checks** — `runtime-session.ts` now logs errors when biome/ast-grep/ruff/knip/dep/jscpd availability checks fail (was silently returning `false`)
120
+ - **Biome check runner reliability** — fixed path resolution and configuration issues causing "skipped" status and parse errors:
121
+ - Fixed biome flag: `--output-format=json` → `--reporter=json`
122
+ - Fixed `findBiome()` to check `~/.pi-lens/tools/` directory (was falling back to bare "biome" not in PATH)
123
+ - Fixed `findBiome()` to return `{cmd, argsPrefix}` object for proper npx fallback with `@biomejs/biome` prefix
124
+ - Added `vcs.root: "."` to `config/biome/core.jsonc` to respect project `.gitignore`
125
+ - **LSP error messaging** — improved error messages for Windows .cmd shim failures to distinguish "npm .cmd shim failed (underlying binary not installed)" from "may be missing or corrupted"
126
+ - **Windows installer improvements** — multiple fixes for Windows tool discovery and LSP stability:
127
+ - Prefer `.cmd` over extensionless in local TOOLS_DIR path lookup on Windows
128
+ - Bypass PS1 hangs in LSP initialization with hard-kill on timeout
129
+ - Remove `.ps1` from pyright managed candidates and ast-grep discovery on Windows
130
+ - Use `SYSTEMDRIVE` env var instead of hardcoded `C:` for cargo fallback path
131
+ - **Rust LSP** — exponential backoff circuit breaker for failing LSP connections
132
+ - **Installer reliability** — remove `console.error` verbosity, route all events to `sessionstart.log`
133
+ - **Circular dependencies** — fixed circular dependencies identified in code review
134
+ - **Knip race condition** — fixed race condition in knip tool discovery
135
+ - **Non-blocking tool availability checks** — changed all `ensureAvailable()` methods to use async `safeSpawnAsync` instead of sync `safeSpawn`, completing the startup unblocking work:
136
+ - `ruff-client.ts`, `biome-client.ts`, `sg-runner.ts` (first batch)
137
+ - `knip-client.ts`, `dependency-checker.ts`, `jscpd-client.ts` (second batch)
138
+ - `sg-runner.ts` — added missing `safeSpawnAsync` import
139
+ - **Secrets scanner false positives** — fixed incorrect flagging of environment variable name references (e.g., `"FIREWORKS_API_KEY"`, `"AWS_ACCESS_KEY_ID"`) as hardcoded secrets:
140
+ - Added word boundaries to `hardcoded-secret` regex pattern
141
+ - Added `looksLikeEnvVarName()` filter to skip UPPERCASE_SNAKE_CASE values
142
+ - Prevents false positives when env var names are used as placeholder strings
143
+
144
+ ### Changed
145
+ - **Biome check performance** — reduced lint latency from ~1.4s to ~100ms per file (92% improvement):
146
+ - Removed redundant `--version` pre-check spawn (~200ms saved)
147
+ - Switched from `biome check` to `biome lint` command (skip format validation)
148
+ - Added binary path caching per cwd to avoid repeated fs checks
149
+ - Benchmark: 107ms average vs 1400ms baseline
150
+ - **Tree-sitter performance** — reduced structural analysis latency by 30-50%:
151
+ - Execute queries in parallel with concurrency limit of 6 (was sequential)
152
+ - Skip entity snapshot extraction for changes under 5 lines (~500-800ms saved for trivial edits)
153
+ - Reduces tree-sitter latency from ~3s to ~1-2s for typical files
154
+
5
155
  ## [3.8.28] - 2026-04-19
6
156
 
7
157
  ### Fixed
package/README.md CHANGED
@@ -42,6 +42,7 @@ At `turn_end`, pi-lens:
42
42
  - persists turn findings for next context injection
43
43
  - updates debt/diagnostic tracking and cleans transient state
44
44
  - renders a review-graph impact cascade showing affected files and diagnostic propagation
45
+ - manages LSP server lifecycle with a 240s idle timeout (resets when editing resumes)
45
46
 
46
47
  ## Install
47
48
 
@@ -62,17 +63,22 @@ pi install git:github.com/apmantza/pi-lens
62
63
  pi
63
64
 
64
65
  # Optional switches
65
- pi --no-lsp # Disable unified LSP, use language-specific fallbacks
66
+ pi --no-lsp # Disable unified LSP diagnostics
66
67
  pi --no-autoformat # Skip auto-formatting
67
68
  pi --no-autofix # Skip auto-fix (Biome, Ruff, ESLint, stylelint, sqlfluff, RuboCop)
68
69
  pi --no-tests # Skip test runner
69
- pi --no-shellcheck # Disable shellcheck runner
70
+ pi --no-delta # Disable delta mode (show all diagnostics, not just new ones)
71
+ pi --lens-guard # Block git commit/push when unresolved blockers exist (experimental)
70
72
  ```
71
73
 
74
+ LSP is enabled by default. Use `--no-lsp` to use language-specific fallbacks (ts-lsp, pyright) instead of the unified LSP service.
75
+
72
76
  ## Key Commands
73
77
 
74
78
  - `/lens-booboo` — full quality report for current project state
75
79
  - `/lens-health` — runtime health, latency, and diagnostic telemetry
80
+ - `/lens-tools` — tool installation status: globally installed, auto-installed, or npx fallback
81
+ - `/lens-tdi` — Technical Debt Index (TDI) and project health trend
76
82
 
77
83
  ## Language Coverage
78
84
 
@@ -147,6 +153,8 @@ pi-lens builds a review graph (`file → symbol → dependency`) during session
147
153
 
148
154
  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
155
 
156
+ **LSP Idle Management:** LSP servers shut down after 240 seconds of inactivity (no files modified) to free resources. The timer resets when you resume editing, preventing cold-start penalties during active development.
157
+
150
158
  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.
151
159
 
152
160
  ## Runners
@@ -251,4 +259,14 @@ Additional language servers (gopls, ruby-lsp, solargraph, etc.) are auto-detecte
251
259
 
252
260
  - Not every auto-install runs in every project: gate type decides when install is attempted.
253
261
  - 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.
262
+ - Inline suppression: `// pi-lens-ignore` or `# pi-lens-ignore` comments suppress diagnostic output for that line.
263
+
264
+ ## Environment Variables
265
+
266
+ | Variable | Default | Description |
267
+ |----------|---------|-------------|
268
+ | `PI_LENS_STARTUP_MODE` | auto | `full`, `minimal`, or `quick` — override session startup behavior |
269
+ | `PI_LENS_LOG_RETENTION_DAYS` | 7 | Days to retain log files before automatic cleanup |
270
+ | `PI_LENS_MAX_LOG_SIZE_MB` | 10 | Max size in MB before rotating active log files |
271
+
272
+ Logs are stored in `~/.pi-lens/` and automatically cleaned up at session start based on these settings.
@@ -53,7 +53,6 @@ export interface FileArchitectResult {
53
53
  export class ArchitectClient {
54
54
  private config: ArchitectConfig | null = null;
55
55
  private isUserConfig: boolean = false;
56
- private configPath: string | undefined;
57
56
  private log: (msg: string) => void;
58
57
 
59
58
  constructor(verbose = false) {
@@ -78,7 +77,6 @@ export class ArchitectClient {
78
77
  try {
79
78
  const content = fs.readFileSync(configPath, "utf-8");
80
79
  this.config = this.parseYaml(content);
81
- this.configPath = configPath;
82
80
  this.isUserConfig = true;
83
81
  this.log(`Loaded user architect config from ${configPath}`);
84
82
  return true;
@@ -112,7 +110,6 @@ export class ArchitectClient {
112
110
  try {
113
111
  const content = fs.readFileSync(defaultPath, "utf-8");
114
112
  this.config = this.parseYaml(content);
115
- this.configPath = defaultPath;
116
113
  this.isUserConfig = false;
117
114
  this.log(
118
115
  "Using default architect rules (create .pi-lens/architect.yaml to customize)",
@@ -187,7 +184,7 @@ export class ArchitectClient {
187
184
  // biome-ignore lint/suspicious/noAssignInExpressions: RegExp.exec iteration
188
185
  while ((match = regex.exec(content)) !== null) {
189
186
  // Convert index to line number
190
- const lineNum = content.slice(0, match.index).split("\n").length;
187
+ const lineNum = content.slice(0, match.index).split(/\r?\n/).length;
191
188
  violations.push({
192
189
  pattern: rule.pattern,
193
190
  message: check.message,
@@ -263,7 +260,7 @@ export class ArchitectClient {
263
260
  const ruleBlocks = content.split(/(?=^ {2}- pattern:)/m);
264
261
 
265
262
  for (const block of ruleBlocks) {
266
- const lines = block.split("\n");
263
+ const lines = block.split(/\r?\n/);
267
264
  let rule: ArchitectRule | null = null;
268
265
  let section: "must_not" | "must" | null = null;
269
266
  let violation: {
@@ -387,5 +384,3 @@ export class ArchitectClient {
387
384
  }
388
385
 
389
386
  // --- Singleton ---
390
-
391
- const _instance: ArchitectClient | null = null;
@@ -22,13 +22,6 @@ import type {
22
22
  import { resolvePackagePath } from "./package-root.js";
23
23
  import { SgRunner } from "./sg-runner.js";
24
24
 
25
- const _getExtensionDir = () => {
26
- if (typeof __dirname !== "undefined") {
27
- return __dirname;
28
- }
29
- return ".";
30
- };
31
-
32
25
  // --- Client ---
33
26
 
34
27
  export class AstGrepClient {
@@ -345,7 +338,7 @@ message: found
345
338
  output += ` ${ruleInfo} (${loc})${fix}\n`;
346
339
 
347
340
  if (d.ruleDescription?.note) {
348
- const shortNote = d.ruleDescription.note.split("\n")[0];
341
+ const shortNote = d.ruleDescription.note.split(/\r?\n/)[0];
349
342
  output += ` → ${shortNote}\n`;
350
343
  }
351
344
  }
@@ -51,7 +51,7 @@ export class AstGrepParser {
51
51
  }
52
52
 
53
53
  return output
54
- .split("\n")
54
+ .split(/\r?\n/)
55
55
  .filter((l) => l.trim())
56
56
  .map((line) => {
57
57
  try {
@@ -67,7 +67,7 @@ export class AstGrepRuleManager {
67
67
  );
68
68
  if (noteMatch) {
69
69
  result.note = noteMatch[1]
70
- .split("\n")
70
+ .split(/\r?\n/)
71
71
  .map((line) => line.trim())
72
72
  .filter((line) => line.length > 0)
73
73
  .join(" ");
@@ -82,7 +82,7 @@ export class AstGrepRuleManager {
82
82
  const fixMatch = content.match(/^fix:\s*\|?([\s\S]*?)(?=^\w|^rule:|Z)/m);
83
83
  if (fixMatch) {
84
84
  result.fix = fixMatch[1]
85
- .split("\n")
85
+ .split(/\r?\n/)
86
86
  .map((line) => line.replace(/^\s*\|?\s*/, ""))
87
87
  .filter((line) => line.length > 0)
88
88
  .join("\n");
@@ -28,17 +28,6 @@ export interface BiomeDiagnostic {
28
28
  fixable: boolean;
29
29
  }
30
30
 
31
- interface BiomeJsonDiagnostic {
32
- message: string;
33
- severity: "error" | "warning" | "info" | "hint";
34
- category: string;
35
- span?: {
36
- start: { line: number; column: number };
37
- end: { line: number; column: number };
38
- };
39
- advice?: Array<{ message: string }>;
40
- }
41
-
42
31
  // --- Client ---
43
32
 
44
33
  export class BiomeClient {
@@ -137,7 +126,7 @@ export class BiomeClient {
137
126
  if (this.biomeAvailable !== null) return this.biomeAvailable;
138
127
 
139
128
  // Check if already available
140
- const result = this.spawnBiome(["--version"], 10000);
129
+ const result = await this.spawnBiomeAsync(["--version"], 10000);
141
130
  if (!result.error && result.status === 0) {
142
131
  this.biomeAvailable = true;
143
132
  return true;
@@ -568,8 +557,8 @@ export class BiomeClient {
568
557
  }
569
558
 
570
559
  private computeDiff(original: string, formatted: string): string {
571
- const origLines = original.split("\n");
572
- const formLines = formatted.split("\n");
560
+ const origLines = original.split(/\r?\n/);
561
+ const formLines = formatted.split(/\r?\n/);
573
562
 
574
563
  let changedLines = 0;
575
564
  const changes: string[] = [];
@@ -213,7 +213,7 @@ export class ComplexityClient {
213
213
  sourceFile: ts.SourceFile;
214
214
  }): FileComplexity {
215
215
  const { absolutePath, content, sourceFile } = parsed;
216
- const lines = content.split("\n");
216
+ const lines = content.split(/\r?\n/);
217
217
 
218
218
  // Line counts and function collection
219
219
  const { codeLines, commentLines } = this.countLines(sourceFile, lines);
@@ -363,7 +363,6 @@ export class ComplexityClient {
363
363
  * Calculate max parameters across all functions
364
364
  */
365
365
  private calculateMaxParams(functions: FunctionMetrics[]): number {
366
- const _maxParams = 0;
367
366
  // We stored function params in the metrics during analysis
368
367
  // For now, estimate based on function length (longer functions often have more params)
369
368
  return Math.min(
@@ -393,7 +392,7 @@ export class ComplexityClient {
393
392
  /\/\*\*?\s*(Overview|Summary|Description|Example|Usage)\s*\*?\//i,
394
393
  ];
395
394
 
396
- const lines = sourceText.split("\n");
395
+ const lines = sourceText.split(/\r?\n/);
397
396
  for (const line of lines) {
398
397
  // Only check comment lines
399
398
  const trimmed = line.trim();
@@ -583,9 +582,10 @@ export class ComplexityClient {
583
582
  let match;
584
583
  while ((match = commentRegex.exec(text)) !== null) {
585
584
  const lineStart = text.lastIndexOf("\n", match.index) + 1;
586
- const startLine = text.substring(0, lineStart).split("\n").length - 1;
585
+ const startLine = text.substring(0, lineStart).split(/\r?\n/).length - 1;
587
586
  const endLine =
588
- text.substring(0, match.index + match[0].length).split("\n").length - 1;
587
+ text.substring(0, match.index + match[0].length).split(/\r?\n/).length -
588
+ 1;
589
589
  for (let i = startLine; i <= endLine; i++) {
590
590
  commentPositions.add(i);
591
591
  }
@@ -11,7 +11,7 @@
11
11
 
12
12
  import * as fs from "node:fs";
13
13
  import * as path from "node:path";
14
- import { safeSpawn } from "./safe-spawn.js";
14
+ import { safeSpawn, safeSpawnAsync } from "./safe-spawn.js";
15
15
 
16
16
  // --- Types ---
17
17
 
@@ -63,7 +63,7 @@ export class DependencyChecker {
63
63
  if (this.available !== null) return this.available;
64
64
 
65
65
  // Check if available in PATH
66
- const result = safeSpawn("madge", ["--version"], {
66
+ const result = await safeSpawnAsync("madge", ["--version"], {
67
67
  timeout: 5000,
68
68
  });
69
69
  this.available = !result.error && result.status === 0;
@@ -1,14 +1,4 @@
1
- import type { Diagnostic } from "./types.js";
2
-
3
- export type DefectClass =
4
- | "silent-error"
5
- | "injection"
6
- | "secrets"
7
- | "async-misuse"
8
- | "correctness"
9
- | "safety"
10
- | "style"
11
- | "unknown";
1
+ import type { DefectClass, Diagnostic } from "./types.js";
12
2
 
13
3
  const SILENT_ERROR_HINTS = [
14
4
  "empty-catch",
@@ -20,9 +10,27 @@ const SILENT_ERROR_HINTS = [
20
10
  "silent",
21
11
  ];
22
12
 
23
- const INJECTION_HINTS = ["sql-injection", "eval", "exec", "inner-html", "javascript-url"];
24
- const SECRET_HINTS = ["secret", "token", "password", "api-key", "hardcoded-secrets"];
25
- const ASYNC_HINTS = ["await-in-loop", "promise", "concurrency", "async", "then-catch"];
13
+ const INJECTION_HINTS = [
14
+ "sql-injection",
15
+ "eval",
16
+ "exec",
17
+ "inner-html",
18
+ "javascript-url",
19
+ ];
20
+ const SECRET_HINTS = [
21
+ "secret",
22
+ "token",
23
+ "password",
24
+ "api-key",
25
+ "hardcoded-secrets",
26
+ ];
27
+ const ASYNC_HINTS = [
28
+ "await-in-loop",
29
+ "promise",
30
+ "concurrency",
31
+ "async",
32
+ "then-catch",
33
+ ];
26
34
 
27
35
  function hasAny(haystack: string, hints: string[]): boolean {
28
36
  return hints.some((h) => haystack.includes(h));
@@ -40,7 +48,11 @@ export function classifyDefect(
40
48
  if (hasAny(text, SECRET_HINTS)) return "secrets";
41
49
  if (hasAny(text, ASYNC_HINTS)) return "async-misuse";
42
50
 
43
- if (text.includes("no-") || text.includes("return") || text.includes("constructor")) {
51
+ if (
52
+ text.includes("no-") ||
53
+ text.includes("return") ||
54
+ text.includes("constructor")
55
+ ) {
44
56
  return "correctness";
45
57
  }
46
58
 
@@ -50,6 +62,8 @@ export function classifyDefect(
50
62
  return "unknown";
51
63
  }
52
64
 
53
- export function classifyDiagnostic(d: Pick<Diagnostic, "rule" | "tool" | "message">): DefectClass {
65
+ export function classifyDiagnostic(
66
+ d: Pick<Diagnostic, "rule" | "tool" | "message">,
67
+ ): DefectClass {
54
68
  return classifyDefect(d.rule, d.tool, d.message);
55
69
  }
@@ -17,15 +17,15 @@
17
17
  import * as path from "node:path";
18
18
  import type { FileKind } from "../file-kinds.js";
19
19
  import { detectFileKind } from "../file-kinds.js";
20
+ import { isTestFile } from "../file-utils.js";
20
21
  import { getPrimaryDispatchGroup } from "../language-policy.js";
21
22
  import { resolveLanguageRootForFile } from "../language-profile.js";
22
- import { isTestFile } from "../file-utils.js";
23
23
  import { logLatency } from "../latency-logger.js";
24
24
  import { normalizeMapKey } from "../path-utils.js";
25
25
  import { RUNTIME_CONFIG } from "../runtime-config.js";
26
26
  import { safeSpawnAsync } from "../safe-spawn.js";
27
27
  import { classifyDiagnostic } from "./diagnostic-taxonomy.js";
28
- import { FactStore } from "./fact-store.js";
28
+ import type { FactStore } from "./fact-store.js";
29
29
  import { getToolPlan } from "./plan.js";
30
30
  import { resolveRunnerPath } from "./runner-context.js";
31
31
  import { getToolProfile } from "./tool-profile.js";
@@ -135,7 +135,7 @@ export function createDispatchContext(
135
135
  cwd: normalizedCwd,
136
136
  kind,
137
137
  pi,
138
- autofix: !!(pi.getFlag("autofix-biome") || pi.getFlag("autofix-ruff")),
138
+ autofix: false,
139
139
  deltaMode: !pi.getFlag("no-delta"),
140
140
  facts,
141
141
  blockingOnly,
@@ -215,7 +215,10 @@ function dedupeOverlappingDiagnostics(diagnostics: Diagnostic[]): Diagnostic[] {
215
215
  * Syntax: `// pi-lens-ignore: rule-id` (JS/TS) or `# pi-lens-ignore: rule-id` (Python/Ruby/etc.)
216
216
  * Place on the same line as the diagnostic or the line immediately above it.
217
217
  */
218
- function applyInlineSuppressions(diagnostics: Diagnostic[], content: string): Diagnostic[] {
218
+ function applyInlineSuppressions(
219
+ diagnostics: Diagnostic[],
220
+ content: string,
221
+ ): Diagnostic[] {
219
222
  if (!content || !diagnostics.length) return diagnostics;
220
223
 
221
224
  // Build a set of (line, ruleId) pairs that are suppressed.
@@ -226,9 +229,12 @@ function applyInlineSuppressions(diagnostics: Diagnostic[], content: string): Di
226
229
  for (let i = 0; i < lines.length; i++) {
227
230
  const m = SUPPRESS_RE.exec(lines[i]);
228
231
  if (!m) continue;
229
- const rules = m[1].split(",").map((r) => r.trim()).filter(Boolean);
232
+ const rules = m[1]
233
+ .split(",")
234
+ .map((r) => r.trim())
235
+ .filter(Boolean);
230
236
  const suppressedLine = i + 1; // same line (1-based)
231
- const nextLine = i + 2; // next line (1-based)
237
+ const nextLine = i + 2; // next line (1-based)
232
238
  for (const ruleId of rules) {
233
239
  suppressed.add(`${suppressedLine}:${ruleId}`);
234
240
  suppressed.add(`${nextLine}:${ruleId}`);
@@ -342,7 +348,7 @@ function buildCoverageNotice(
342
348
  runnerLatencies: RunnerLatency[],
343
349
  ): Diagnostic | undefined {
344
350
  if (!ctx.kind) return undefined;
345
- const lspEnabled = !!ctx.pi.getFlag("lens-lsp") && !ctx.pi.getFlag("no-lsp");
351
+ const lspEnabled = !ctx.pi.getFlag("no-lsp");
346
352
  const primary = getPrimaryDispatchGroup(ctx.kind, lspEnabled);
347
353
  if (!primary || primary.runnerIds.length === 0) return undefined;
348
354
 
@@ -367,7 +373,9 @@ function buildCoverageNotice(
367
373
  (plan?.groups ?? [])
368
374
  .filter(
369
375
  (group) =>
370
- !group.runnerIds.every((runnerId) => primary.runnerIds.includes(runnerId)),
376
+ !group.runnerIds.every((runnerId) =>
377
+ primary.runnerIds.includes(runnerId),
378
+ ),
371
379
  )
372
380
  .flatMap((group) => group.runnerIds)
373
381
  .filter((runnerId) => !primary.runnerIds.includes(runnerId)),
@@ -376,7 +384,12 @@ function buildCoverageNotice(
376
384
  // Structural-only runners (tree-sitter, ast-grep, similarity) are not
377
385
  // substitutes for real linters — don't suppress the notice if only they ran.
378
386
  const STRUCTURAL_RUNNERS = new Set([
379
- "tree-sitter", "ast-grep-napi", "similarity", "spellcheck", "architect", "fact-rules",
387
+ "tree-sitter",
388
+ "ast-grep-napi",
389
+ "similarity",
390
+ "spellcheck",
391
+ "architect",
392
+ "fact-rules",
380
393
  ]);
381
394
  const anyLinterHasCoverage = runnerLatencies.some(
382
395
  (r) =>
@@ -499,7 +512,7 @@ async function runGroup(
499
512
  ? group.runnerIds.filter((id) => {
500
513
  const runner = registry.get(id);
501
514
  return runner && ctx.kind && group.filterKinds?.includes(ctx.kind);
502
- })
515
+ })
503
516
  : group.runnerIds;
504
517
 
505
518
  const semantic = group.semantic ?? "warning";
@@ -614,7 +627,6 @@ export async function dispatchForFile(
614
627
  ): Promise<DispatchResult> {
615
628
  const _overallStart = Date.now();
616
629
  const allDiagnostics: Diagnostic[] = [];
617
- const _fixed: Diagnostic[] = [];
618
630
  let stopped = false;
619
631
  const runnerLatencies: RunnerLatency[] = [];
620
632
 
@@ -668,12 +680,20 @@ export async function dispatchForFile(
668
680
  // This avoids partial-baseline corruption when processing multiple groups.
669
681
  const dedupedDiagnostics = dedupeOverlappingDiagnostics(allDiagnostics);
670
682
  const overlapSuppressed = suppressLintOverlapsWithLsp(dedupedDiagnostics);
671
- const fileContent = ctx.facts.getFileFact<string>(ctx.filePath, "file.content") ?? "";
672
- const inlineSuppressed = applyInlineSuppressions(overlapSuppressed, fileContent);
683
+ const fileContent =
684
+ ctx.facts.getFileFact<string>(ctx.filePath, "file.content") ?? "";
685
+ const inlineSuppressed = applyInlineSuppressions(
686
+ overlapSuppressed,
687
+ fileContent,
688
+ );
673
689
  let visibleDiagnostics = inlineSuppressed;
674
690
  let resolvedCount = 0;
675
691
  if (ctx.deltaMode && previousBaseline) {
676
- const filtered = filterDelta(visibleDiagnostics, previousBaseline, (d) => d.id);
692
+ const filtered = filterDelta(
693
+ visibleDiagnostics,
694
+ previousBaseline,
695
+ (d) => d.id,
696
+ );
677
697
  visibleDiagnostics = promoteDeltaUnusedToBlockers(filtered.new);
678
698
  resolvedCount = filtered.fixed.length;
679
699
  }
@@ -693,15 +713,19 @@ export async function dispatchForFile(
693
713
 
694
714
  // Append fixed and fixable diagnostics to the persistent worklog
695
715
  if (fixedItems.length > 0) {
696
- import("../fix-worklog.js").then(({ appendToWorklog }) => {
697
- appendToWorklog(ctx.cwd, fixedItems, true);
698
- }).catch(() => {});
716
+ import("../fix-worklog.js")
717
+ .then(({ appendToWorklog }) => {
718
+ appendToWorklog(ctx.cwd, fixedItems, true);
719
+ })
720
+ .catch(() => {});
699
721
  }
700
722
  const fixableWarnings = warnings.filter((d) => d.fixable);
701
723
  if (fixableWarnings.length > 0) {
702
- import("../fix-worklog.js").then(({ appendToWorklog }) => {
703
- appendToWorklog(ctx.cwd, fixableWarnings, false);
704
- }).catch(() => {});
724
+ import("../fix-worklog.js")
725
+ .then(({ appendToWorklog }) => {
726
+ appendToWorklog(ctx.cwd, fixableWarnings, false);
727
+ })
728
+ .catch(() => {});
705
729
  }
706
730
 
707
731
  const inlineBlockers = blockers.filter((d) => d.tool !== "similarity");
@@ -804,7 +828,10 @@ function looksLikeDiagnosticCodePath(value: string): boolean {
804
828
  return false;
805
829
  }
806
830
 
807
- function normalizeDiagnosticFilePath(ctx: DispatchContext, rawPath?: string): string {
831
+ function normalizeDiagnosticFilePath(
832
+ ctx: DispatchContext,
833
+ rawPath?: string,
834
+ ): string {
808
835
  if (typeof rawPath === "string" && looksLikeDiagnosticCodePath(rawPath)) {
809
836
  ctx.log(
810
837
  `runner path normalization: ignored diagnostic code-like path '${rawPath}', using current file`,