pi-lens 3.8.47 → 3.8.51
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.
- package/CHANGELOG.md +231 -1
- package/README.md +37 -6
- package/dist/clients/actionable-warnings-logger.js +51 -0
- package/dist/clients/actionable-warnings.js +542 -0
- package/dist/clients/agent-behavior-client.js +119 -0
- package/dist/clients/ast-grep-client.js +391 -0
- package/dist/clients/ast-grep-parser.js +86 -0
- package/dist/clients/ast-grep-rule-manager.js +91 -0
- package/dist/clients/ast-grep-tool-logger.js +150 -0
- package/dist/clients/ast-grep-types.js +9 -0
- package/dist/clients/ast-grep-yaml-synth.js +103 -0
- package/dist/clients/bash-file-access.js +360 -0
- package/dist/clients/biome-client.js +183 -0
- package/dist/clients/bootstrap.js +40 -0
- package/dist/clients/cache/rule-cache.js +75 -0
- package/dist/clients/cache-manager.js +273 -0
- package/dist/clients/call-graph.js +325 -0
- package/dist/clients/cascade-format.js +21 -0
- package/dist/clients/cascade-logger.js +25 -0
- package/dist/clients/cascade-types.js +1 -0
- package/dist/clients/code-quality-warnings.js +189 -0
- package/dist/clients/codebase-model.js +136 -0
- package/dist/clients/complexity-client.js +676 -0
- package/dist/clients/dependency-checker.js +359 -0
- package/dist/clients/diagnostic-logger.js +87 -0
- package/dist/clients/diagnostic-tracker.js +105 -0
- package/dist/clients/dispatch/diagnostic-taxonomy.js +66 -0
- package/dist/clients/dispatch/dispatcher.js +758 -0
- package/dist/clients/dispatch/fact-provider-types.js +1 -0
- package/dist/clients/dispatch/fact-rule-runner.js +17 -0
- package/dist/clients/dispatch/fact-runner.js +19 -0
- package/dist/clients/dispatch/fact-scheduler.js +68 -0
- package/dist/clients/dispatch/fact-store.js +47 -0
- package/dist/clients/dispatch/facts/comment-facts.js +35 -0
- package/dist/clients/dispatch/facts/file-content.js +19 -0
- package/dist/clients/dispatch/facts/function-facts.js +204 -0
- package/dist/clients/dispatch/facts/import-facts.js +163 -0
- package/dist/clients/dispatch/facts/try-catch-facts.js +112 -0
- package/dist/clients/dispatch/integration.js +1054 -0
- package/dist/clients/dispatch/plan.js +295 -0
- package/dist/clients/dispatch/priorities.js +21 -0
- package/dist/clients/dispatch/rules/async-noise.js +39 -0
- package/dist/clients/dispatch/rules/async-unnecessary-wrapper.js +27 -0
- package/dist/clients/dispatch/rules/error-obscuring.js +31 -0
- package/dist/clients/dispatch/rules/error-swallowing.js +28 -0
- package/dist/clients/dispatch/rules/high-complexity.js +36 -0
- package/dist/clients/dispatch/rules/high-fan-out.js +43 -0
- package/dist/clients/dispatch/rules/missing-error-propagation.js +62 -0
- package/dist/clients/dispatch/rules/pass-through-wrappers.js +38 -0
- package/dist/clients/dispatch/rules/placeholder-comments.js +40 -0
- package/dist/clients/dispatch/rules/quality-rules.js +297 -0
- package/dist/clients/dispatch/rules/sonar-rules.js +493 -0
- package/dist/clients/dispatch/rules/unsafe-boundary.js +95 -0
- package/dist/clients/dispatch/runner-context.js +52 -0
- package/dist/clients/dispatch/runners/actionlint.js +110 -0
- package/dist/clients/dispatch/runners/ast-grep-napi.js +302 -0
- package/dist/clients/dispatch/runners/biome-check.js +120 -0
- package/dist/clients/dispatch/runners/biome.js +69 -0
- package/dist/clients/dispatch/runners/cpp-check.js +225 -0
- package/dist/clients/dispatch/runners/credo.js +71 -0
- package/dist/clients/dispatch/runners/dart-analyze.js +202 -0
- package/dist/clients/dispatch/runners/detekt.js +165 -0
- package/dist/clients/dispatch/runners/dotnet-build.js +159 -0
- package/dist/clients/dispatch/runners/elixir-check.js +136 -0
- package/dist/clients/dispatch/runners/eslint.js +126 -0
- package/dist/clients/dispatch/runners/fact-rules.js +30 -0
- package/dist/clients/dispatch/runners/fish-indent.js +69 -0
- package/dist/clients/dispatch/runners/gleam-check.js +87 -0
- package/dist/clients/dispatch/runners/go-vet.js +50 -0
- package/dist/clients/dispatch/runners/golangci-lint.js +118 -0
- package/dist/clients/dispatch/runners/hadolint.js +71 -0
- package/dist/clients/dispatch/runners/htmlhint.js +90 -0
- package/dist/clients/dispatch/runners/index.js +105 -0
- package/dist/clients/dispatch/runners/javac.js +76 -0
- package/dist/clients/dispatch/runners/ktlint.js +136 -0
- package/dist/clients/dispatch/runners/lsp.js +217 -0
- package/dist/clients/dispatch/runners/markdownlint.js +113 -0
- package/dist/clients/dispatch/runners/mypy.js +68 -0
- package/dist/clients/dispatch/runners/oxlint.js +178 -0
- package/dist/clients/dispatch/runners/php-lint.js +62 -0
- package/dist/clients/dispatch/runners/phpstan.js +73 -0
- package/dist/clients/dispatch/runners/prisma-validate.js +62 -0
- package/dist/clients/dispatch/runners/psscriptanalyzer.js +149 -0
- package/dist/clients/dispatch/runners/pyright.js +115 -0
- package/dist/clients/dispatch/runners/python-slop.js +106 -0
- package/dist/clients/dispatch/runners/rubocop.js +90 -0
- package/dist/clients/dispatch/runners/ruff.js +94 -0
- package/dist/clients/dispatch/runners/rust-clippy.js +149 -0
- package/dist/clients/dispatch/runners/semgrep.js +197 -0
- package/dist/clients/dispatch/runners/shellcheck.js +160 -0
- package/dist/clients/dispatch/runners/shfmt.js +86 -0
- package/dist/clients/dispatch/runners/spellcheck.js +111 -0
- package/dist/clients/dispatch/runners/sqlfluff.js +146 -0
- package/dist/clients/dispatch/runners/stylelint.js +116 -0
- package/dist/clients/dispatch/runners/swiftlint.js +144 -0
- package/dist/clients/dispatch/runners/taplo.js +61 -0
- package/dist/clients/dispatch/runners/tflint.js +68 -0
- package/dist/clients/dispatch/runners/tree-sitter.js +690 -0
- package/dist/clients/dispatch/runners/ts-lsp.js +109 -0
- package/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +149 -0
- package/dist/clients/dispatch/runners/utils/lazy-installer.js +38 -0
- package/dist/clients/dispatch/runners/utils/runner-helpers.js +385 -0
- package/dist/clients/dispatch/runners/utils.js +36 -0
- package/dist/clients/dispatch/runners/vale.js +105 -0
- package/dist/clients/dispatch/runners/yaml-rule-parser.js +167 -0
- package/dist/clients/dispatch/runners/yamllint.js +70 -0
- package/dist/clients/dispatch/runners/zig-check.js +87 -0
- package/dist/clients/dispatch/tool-profile.js +30 -0
- package/dist/clients/dispatch/types.js +13 -0
- package/dist/clients/dispatch/utils/format-utils.js +45 -0
- package/dist/clients/dispatch/utils/lsp-diagnostics.js +28 -0
- package/{clients/env-utils.ts → dist/clients/env-utils.js} +9 -9
- package/dist/clients/event-loop-monitor.js +56 -0
- package/dist/clients/feature-hints.js +49 -0
- package/dist/clients/file-kinds.js +401 -0
- package/dist/clients/file-role.js +102 -0
- package/dist/clients/file-time.js +155 -0
- package/dist/clients/file-utils.js +454 -0
- package/dist/clients/fix-worklog.js +87 -0
- package/dist/clients/format-service.js +184 -0
- package/dist/clients/formatters.js +917 -0
- package/dist/clients/generated-artifacts.js +146 -0
- package/dist/clients/git-guard.js +32 -0
- package/dist/clients/gitleaks-client.js +317 -0
- package/dist/clients/go-client.js +84 -0
- package/dist/clients/govulncheck-client.js +378 -0
- package/dist/clients/indent-retarget.js +84 -0
- package/dist/clients/installer/index.js +1997 -0
- package/dist/clients/jscpd-client.js +282 -0
- package/dist/clients/knip-client.js +371 -0
- package/dist/clients/language-policy.js +232 -0
- package/dist/clients/language-profile.js +329 -0
- package/dist/clients/latency-logger.js +44 -0
- package/dist/clients/lens-config.js +135 -0
- package/dist/clients/lens-engine.js +91 -0
- package/dist/clients/lens-events.js +70 -0
- package/dist/clients/log-cleanup.js +203 -0
- package/dist/clients/lsp/aggregation.js +83 -0
- package/dist/clients/lsp/client.js +1053 -0
- package/dist/clients/lsp/config.js +165 -0
- package/dist/clients/lsp/edits.js +228 -0
- package/dist/clients/lsp/index.js +1405 -0
- package/dist/clients/lsp/interactive-install.js +355 -0
- package/dist/clients/lsp/language.js +175 -0
- package/dist/clients/lsp/launch.js +755 -0
- package/{clients/lsp/lsp-index.ts → dist/clients/lsp/lsp-index.js} +1 -2
- package/dist/clients/lsp/path-utils.js +5 -0
- package/dist/clients/lsp/server-strategies.js +59 -0
- package/dist/clients/lsp/server.js +1600 -0
- package/dist/clients/mcp/analyze.js +170 -0
- package/dist/clients/mcp/host-shim.js +27 -0
- package/dist/clients/mcp/ipc.js +84 -0
- package/dist/clients/mcp/review.js +115 -0
- package/dist/clients/mcp/session.js +136 -0
- package/dist/clients/metrics-client.js +108 -0
- package/dist/clients/metrics-history.js +388 -0
- package/dist/clients/oldtext-autopatch.js +127 -0
- package/dist/clients/package-root.js +38 -0
- package/dist/clients/partial-edit-apply.js +43 -0
- package/dist/clients/path-utils.js +205 -0
- package/dist/clients/pipeline.js +835 -0
- package/dist/clients/production-readiness.js +517 -0
- package/dist/clients/project-changes.js +68 -0
- package/dist/clients/project-conventions.js +177 -0
- package/dist/clients/project-diagnostics/cache.js +51 -0
- package/dist/clients/project-diagnostics/runner-adapters/knip.js +44 -0
- package/dist/clients/project-diagnostics/scanner.js +159 -0
- package/dist/clients/project-diagnostics/types.js +1 -0
- package/dist/clients/project-metadata.js +710 -0
- package/dist/clients/project-scan-policy.js +49 -0
- package/dist/clients/project-snapshot.js +137 -0
- package/dist/clients/read-expansion.js +289 -0
- package/dist/clients/read-guard-logger.js +77 -0
- package/dist/clients/read-guard-tool-lines.js +1002 -0
- package/dist/clients/read-guard.js +855 -0
- package/dist/clients/reverse-deps.js +182 -0
- package/dist/clients/review-graph/builder.js +984 -0
- package/dist/clients/review-graph/format.js +33 -0
- package/dist/clients/review-graph/query.js +166 -0
- package/dist/clients/review-graph/service.js +44 -0
- package/dist/clients/review-graph/types.js +1 -0
- package/dist/clients/review-graph/workspace-modules.js +445 -0
- package/dist/clients/ruff-client.js +159 -0
- package/dist/clients/rules-scanner.js +118 -0
- package/dist/clients/runner-tracker.js +153 -0
- package/dist/clients/runtime-agent-end.js +227 -0
- package/dist/clients/runtime-config.js +73 -0
- package/dist/clients/runtime-context.js +42 -0
- package/dist/clients/runtime-coordinator.js +365 -0
- package/dist/clients/runtime-session.js +868 -0
- package/dist/clients/runtime-tool-result.js +509 -0
- package/dist/clients/runtime-turn.js +602 -0
- package/dist/clients/rust-client.js +83 -0
- package/dist/clients/safe-spawn.js +301 -0
- package/dist/clients/sanitize.js +291 -0
- package/dist/clients/scan-utils.js +80 -0
- package/dist/clients/search-read-registration.js +66 -0
- package/dist/clients/secrets-scanner.js +181 -0
- package/dist/clients/semgrep-config.js +157 -0
- package/dist/clients/session-state-store.js +97 -0
- package/dist/clients/session-summary.js +37 -0
- package/dist/clients/sg-runner.js +496 -0
- package/dist/clients/source-filter.js +274 -0
- package/dist/clients/source-groups.js +96 -0
- package/dist/clients/startup-scan.js +255 -0
- package/dist/clients/startup-timing.js +32 -0
- package/dist/clients/symbol-types.js +5 -0
- package/dist/clients/test-runner-client.js +766 -0
- package/dist/clients/todo-scanner.js +198 -0
- package/dist/clients/tool-event.js +19 -0
- package/dist/clients/tool-policy.js +1856 -0
- package/dist/clients/tree-sitter-cache.js +244 -0
- package/dist/clients/tree-sitter-client.js +1235 -0
- package/dist/clients/tree-sitter-fixer.js +127 -0
- package/dist/clients/tree-sitter-logger.js +25 -0
- package/dist/clients/tree-sitter-navigator.js +269 -0
- package/dist/clients/tree-sitter-query-loader.js +428 -0
- package/dist/clients/tree-sitter-symbol-extractor.js +654 -0
- package/dist/clients/ts-service.js +130 -0
- package/dist/clients/type-coverage-client.js +128 -0
- package/dist/clients/types.js +11 -0
- package/dist/clients/typescript-client.js +509 -0
- package/dist/clients/widget-state.js +533 -0
- package/dist/clients/word-index.js +250 -0
- package/dist/commands/booboo.js +1412 -0
- package/dist/i18n.js +61 -0
- package/dist/index.js +1743 -0
- package/dist/mcp/analyze-cli.js +105 -0
- package/dist/mcp/server.js +745 -0
- package/dist/mcp/worker.js +46 -0
- package/dist/tools/ast-dump.js +62 -0
- package/dist/tools/ast-grep-replace.js +176 -0
- package/dist/tools/ast-grep-search.js +334 -0
- package/dist/tools/lens-diagnostics.js +469 -0
- package/{tools → dist/tools}/lsp-navigation.js +368 -16
- package/package.json +28 -18
- package/rules/ast-grep-rules/rules/nested-ternary-js.yml +2 -0
- package/rules/ast-grep-rules/rules/nested-ternary.yml +2 -0
- package/rules/ast-grep-rules/rules/no-constant-condition-js.yml +11 -18
- package/rules/ast-grep-rules/rules/no-constant-condition.yml +11 -18
- package/rules/ast-grep-rules/rules/no-mutable-export.yml +13 -0
- package/rules/ast-grep-rules/rules/no-octal-literal.yml +14 -0
- package/rules/ast-grep-rules/rules/no-sort-without-comparator.yml +14 -0
- package/rules/ast-grep-rules/rules/redos-nested-quantifier.yml +23 -0
- package/rules/ast-grep-rules/rules/switch-without-default.yml +19 -0
- package/rules/ast-grep-rules/{rules → rules-disabled}/constructor-super-js.yml +2 -0
- package/rules/ast-grep-rules/{rules → rules-disabled}/constructor-super.yml +3 -0
- package/rules/ast-grep-rules/{rules → rules-disabled}/no-hardcoded-secrets-js.yml +3 -0
- package/rules/ast-grep-rules/{rules → rules-disabled}/no-hardcoded-secrets.yml +3 -0
- package/rules/ast-grep-rules/{rules → rules-disabled}/no-process-env.yml +3 -0
- package/rules/ast-grep-rules/{rules → rules-disabled}/unchecked-sync-fs-js.yml +3 -0
- package/rules/ast-grep-rules/{rules → rules-disabled}/unchecked-sync-fs.yml +3 -0
- package/rules/ast-grep-rules/slop-patterns.yml +26 -35
- package/rules/rule-catalog.json +3 -5
- package/rules/tree-sitter-queries/java/{infinite-loop.yml → infinite-loop-java.yml} +1 -1
- package/rules/tree-sitter-queries/typescript/no-equality-in-for-condition.yml +40 -0
- package/rules/tree-sitter-queries/typescript/no-jump-in-finally.yml +47 -0
- package/scripts/analyze-pi-lens-logs.mjs +211 -7
- package/scripts/download-grammars.js +15 -0
- package/skills/ast-grep/SKILL.md +71 -2
- package/skills/write-ast-grep-rule/SKILL.md +93 -3
- package/skills/write-tree-sitter-rule/SKILL.md +36 -0
- package/clients/actionable-warnings-logger.ts +0 -65
- package/clients/actionable-warnings.ts +0 -653
- package/clients/agent-behavior-client.ts +0 -155
- package/clients/amain-types.ts +0 -165
- package/clients/ast-grep-client.ts +0 -396
- package/clients/ast-grep-parser.ts +0 -130
- package/clients/ast-grep-rule-manager.ts +0 -104
- package/clients/ast-grep-types.ts +0 -106
- package/clients/biome-client.ts +0 -657
- package/clients/bootstrap.ts +0 -83
- package/clients/cache/rule-cache.ts +0 -110
- package/clients/cache-manager.ts +0 -361
- package/clients/cascade-format.ts +0 -27
- package/clients/cascade-logger.ts +0 -78
- package/clients/cascade-types.ts +0 -36
- package/clients/code-quality-warnings.ts +0 -313
- package/clients/complexity-client.ts +0 -922
- package/clients/dependency-checker.ts +0 -466
- package/clients/diagnostic-logger.ts +0 -151
- package/clients/diagnostic-tracker.ts +0 -163
- package/clients/dispatch/diagnostic-taxonomy.ts +0 -81
- package/clients/dispatch/dispatcher.ts +0 -986
- package/clients/dispatch/fact-provider-types.ts +0 -22
- package/clients/dispatch/fact-rule-runner.ts +0 -22
- package/clients/dispatch/fact-runner.ts +0 -28
- package/clients/dispatch/fact-scheduler.ts +0 -79
- package/clients/dispatch/fact-store.ts +0 -65
- package/clients/dispatch/facts/comment-facts.ts +0 -59
- package/clients/dispatch/facts/file-content.ts +0 -20
- package/clients/dispatch/facts/function-facts.ts +0 -256
- package/clients/dispatch/facts/import-facts.ts +0 -68
- package/clients/dispatch/facts/try-catch-facts.ts +0 -177
- package/clients/dispatch/integration.ts +0 -1326
- package/clients/dispatch/plan.ts +0 -331
- package/clients/dispatch/priorities.ts +0 -22
- package/clients/dispatch/rules/async-noise.ts +0 -50
- package/clients/dispatch/rules/async-unnecessary-wrapper.ts +0 -35
- package/clients/dispatch/rules/error-obscuring.ts +0 -40
- package/clients/dispatch/rules/error-swallowing.ts +0 -35
- package/clients/dispatch/rules/high-complexity.ts +0 -44
- package/clients/dispatch/rules/high-fan-out.ts +0 -55
- package/clients/dispatch/rules/missing-error-propagation.ts +0 -71
- package/clients/dispatch/rules/pass-through-wrappers.ts +0 -52
- package/clients/dispatch/rules/placeholder-comments.ts +0 -47
- package/clients/dispatch/rules/quality-rules.ts +0 -375
- package/clients/dispatch/rules/sonar-rules.ts +0 -508
- package/clients/dispatch/rules/unsafe-boundary.ts +0 -104
- package/clients/dispatch/runner-context.ts +0 -61
- package/clients/dispatch/runners/actionlint.ts +0 -145
- package/clients/dispatch/runners/ast-grep-napi.ts +0 -576
- package/clients/dispatch/runners/biome-check.ts +0 -166
- package/clients/dispatch/runners/biome.ts +0 -78
- package/clients/dispatch/runners/cpp-check.ts +0 -267
- package/clients/dispatch/runners/credo.ts +0 -99
- package/clients/dispatch/runners/dart-analyze.ts +0 -226
- package/clients/dispatch/runners/detekt.ts +0 -192
- package/clients/dispatch/runners/dotnet-build.ts +0 -195
- package/clients/dispatch/runners/elixir-check.ts +0 -149
- package/clients/dispatch/runners/eslint.ts +0 -155
- package/clients/dispatch/runners/fact-rules.ts +0 -44
- package/clients/dispatch/runners/fish-indent.ts +0 -83
- package/clients/dispatch/runners/gleam-check.ts +0 -108
- package/clients/dispatch/runners/go-vet.ts +0 -66
- package/clients/dispatch/runners/golangci-lint.ts +0 -175
- package/clients/dispatch/runners/hadolint.ts +0 -103
- package/clients/dispatch/runners/htmlhint.ts +0 -118
- package/clients/dispatch/runners/index.ts +0 -113
- package/clients/dispatch/runners/javac.ts +0 -99
- package/clients/dispatch/runners/ktlint.ts +0 -173
- package/clients/dispatch/runners/lsp.ts +0 -243
- package/clients/dispatch/runners/markdownlint.ts +0 -132
- package/clients/dispatch/runners/mypy.ts +0 -89
- package/clients/dispatch/runners/oxlint.ts +0 -214
- package/clients/dispatch/runners/php-lint.ts +0 -82
- package/clients/dispatch/runners/phpstan.ts +0 -113
- package/clients/dispatch/runners/prisma-validate.ts +0 -89
- package/clients/dispatch/runners/psscriptanalyzer.ts +0 -177
- package/clients/dispatch/runners/pyright.ts +0 -143
- package/clients/dispatch/runners/python-slop.ts +0 -133
- package/clients/dispatch/runners/rubocop.ts +0 -144
- package/clients/dispatch/runners/ruff.ts +0 -133
- package/clients/dispatch/runners/rust-clippy.ts +0 -181
- package/clients/dispatch/runners/semgrep.ts +0 -271
- package/clients/dispatch/runners/shellcheck.ts +0 -195
- package/clients/dispatch/runners/shfmt.ts +0 -100
- package/clients/dispatch/runners/similarity.ts +0 -510
- package/clients/dispatch/runners/spellcheck.ts +0 -145
- package/clients/dispatch/runners/sqlfluff.ts +0 -174
- package/clients/dispatch/runners/stylelint.ts +0 -155
- package/clients/dispatch/runners/swiftlint.ts +0 -199
- package/clients/dispatch/runners/taplo.ts +0 -93
- package/clients/dispatch/runners/tflint.ts +0 -100
- package/clients/dispatch/runners/tree-sitter.ts +0 -812
- package/clients/dispatch/runners/ts-lsp.ts +0 -136
- package/clients/dispatch/runners/type-safety.ts +0 -197
- package/clients/dispatch/runners/utils/diagnostic-parsers.ts +0 -223
- package/clients/dispatch/runners/utils/lazy-installer.ts +0 -54
- package/clients/dispatch/runners/utils/runner-helpers.ts +0 -572
- package/clients/dispatch/runners/utils.ts +0 -58
- package/clients/dispatch/runners/vale.ts +0 -175
- package/clients/dispatch/runners/yaml-rule-parser.ts +0 -417
- package/clients/dispatch/runners/yamllint.ts +0 -92
- package/clients/dispatch/runners/zig-check.ts +0 -113
- package/clients/dispatch/tool-profile.ts +0 -41
- package/clients/dispatch/types.ts +0 -185
- package/clients/dispatch/utils/format-utils.ts +0 -56
- package/clients/dispatch/utils/lsp-diagnostics.ts +0 -42
- package/clients/feature-hints.ts +0 -79
- package/clients/file-kinds.ts +0 -467
- package/clients/file-role.ts +0 -145
- package/clients/file-time.ts +0 -208
- package/clients/file-utils.ts +0 -503
- package/clients/fix-worklog.ts +0 -121
- package/clients/format-service.ts +0 -276
- package/clients/formatters.ts +0 -1028
- package/clients/generated-artifacts.ts +0 -140
- package/clients/git-guard.ts +0 -41
- package/clients/go-client.ts +0 -242
- package/clients/indent-retarget.ts +0 -90
- package/clients/installer/index.ts +0 -2325
- package/clients/jscpd-client.ts +0 -348
- package/clients/knip-client.ts +0 -437
- package/clients/language-policy.ts +0 -263
- package/clients/language-profile.ts +0 -258
- package/clients/latency-logger.ts +0 -74
- package/clients/lens-config.ts +0 -177
- package/clients/lens-events.ts +0 -151
- package/clients/log-cleanup.ts +0 -255
- package/clients/lsp/aggregation.ts +0 -91
- package/clients/lsp/client.ts +0 -1505
- package/clients/lsp/config.ts +0 -216
- package/clients/lsp/edits.ts +0 -294
- package/clients/lsp/index.ts +0 -1439
- package/clients/lsp/interactive-install.ts +0 -424
- package/clients/lsp/language.ts +0 -223
- package/clients/lsp/launch.ts +0 -928
- package/clients/lsp/path-utils.ts +0 -12
- package/clients/lsp/server-strategies.ts +0 -81
- package/clients/lsp/server.ts +0 -2051
- package/clients/metrics-client.ts +0 -153
- package/clients/metrics-history.ts +0 -510
- package/clients/oldtext-autopatch.ts +0 -114
- package/clients/package-root.ts +0 -44
- package/clients/partial-edit-apply.ts +0 -76
- package/clients/path-utils.ts +0 -223
- package/clients/pipeline.ts +0 -1131
- package/clients/production-readiness.ts +0 -552
- package/clients/project-changes.ts +0 -112
- package/clients/project-conventions.ts +0 -215
- package/clients/project-index.ts +0 -403
- package/clients/project-metadata.ts +0 -809
- package/clients/project-scan-policy.ts +0 -79
- package/clients/project-snapshot.ts +0 -221
- package/clients/read-expansion.ts +0 -283
- package/clients/read-guard-logger.ts +0 -101
- package/clients/read-guard-tool-lines.ts +0 -804
- package/clients/read-guard.ts +0 -1044
- package/clients/reverse-deps.ts +0 -244
- package/clients/review-graph/builder.ts +0 -927
- package/clients/review-graph/format.ts +0 -51
- package/clients/review-graph/query.ts +0 -162
- package/clients/review-graph/service.ts +0 -69
- package/clients/review-graph/types.ts +0 -46
- package/clients/review-graph/workspace-modules.ts +0 -497
- package/clients/ruff-client.ts +0 -511
- package/clients/rules-scanner.ts +0 -149
- package/clients/runner-tracker.ts +0 -215
- package/clients/runtime-agent-end.ts +0 -331
- package/clients/runtime-config.ts +0 -77
- package/clients/runtime-context.ts +0 -79
- package/clients/runtime-coordinator.ts +0 -472
- package/clients/runtime-session.ts +0 -784
- package/clients/runtime-tool-result.ts +0 -650
- package/clients/runtime-turn.ts +0 -656
- package/clients/rust-client.ts +0 -270
- package/clients/safe-spawn.ts +0 -339
- package/clients/sanitize.ts +0 -356
- package/clients/scan-utils.ts +0 -75
- package/clients/secrets-scanner.ts +0 -214
- package/clients/semgrep-config.ts +0 -203
- package/clients/session-summary.ts +0 -53
- package/clients/sg-runner.ts +0 -470
- package/clients/source-filter.ts +0 -263
- package/clients/source-groups.ts +0 -140
- package/clients/startup-scan.ts +0 -150
- package/clients/state-matrix.ts +0 -202
- package/clients/subprocess-client.ts +0 -101
- package/clients/symbol-types.ts +0 -77
- package/clients/test-runner-client.ts +0 -1134
- package/clients/todo-scanner.ts +0 -243
- package/clients/tool-availability.ts +0 -250
- package/clients/tool-policy.ts +0 -2063
- package/clients/tree-sitter-cache.ts +0 -316
- package/clients/tree-sitter-client.ts +0 -1541
- package/clients/tree-sitter-fixer.ts +0 -217
- package/clients/tree-sitter-logger.ts +0 -51
- package/clients/tree-sitter-navigator.ts +0 -329
- package/clients/tree-sitter-query-loader.ts +0 -521
- package/clients/tree-sitter-symbol-extractor.ts +0 -442
- package/clients/ts-service.ts +0 -154
- package/clients/type-coverage-client.ts +0 -164
- package/clients/type-safety-client.ts +0 -193
- package/clients/types.ts +0 -59
- package/clients/typescript-client.ts +0 -698
- package/clients/widget-state.ts +0 -477
- package/commands/booboo.ts +0 -1837
- package/i18n.ts +0 -66
- package/index.ts +0 -1962
- package/tools/ast-grep-replace.js +0 -75
- package/tools/ast-grep-replace.ts +0 -112
- package/tools/ast-grep-search.js +0 -168
- package/tools/ast-grep-search.ts +0 -222
- package/tools/lsp-diagnostics.ts +0 -706
- package/tools/lsp-navigation.ts +0 -1124
- package/tools/shared.ts +0 -31
- package/tsconfig.json +0 -18
- /package/{tools → dist/tools}/lsp-diagnostics.js +0 -0
- /package/{tools → dist/tools}/shared.js +0 -0
- /package/rules/tree-sitter-queries/{abap → abap-disabled}/delete-where.yml +0 -0
- /package/rules/tree-sitter-queries/{cobol → cobol-disabled}/alter-statement.yml +0 -0
- /package/rules/tree-sitter-queries/{cobol → cobol-disabled}/lock-table-cobol.yml +0 -0
- /package/rules/tree-sitter-queries/{plsql → plsql-disabled}/delete-update-where.yml +0 -0
- /package/rules/tree-sitter-queries/{plsql → plsql-disabled}/end-loop-semicolon.yml +0 -0
- /package/rules/tree-sitter-queries/{plsql → plsql-disabled}/fetch-bulk-collect-limit.yml +0 -0
- /package/rules/tree-sitter-queries/{plsql → plsql-disabled}/forallsave-exceptions.yml +0 -0
- /package/rules/tree-sitter-queries/{plsql → plsql-disabled}/lock-table.yml +0 -0
- /package/rules/tree-sitter-queries/{plsql → plsql-disabled}/nchar-nvarchar2-bytes.yml +0 -0
- /package/rules/tree-sitter-queries/{plsql → plsql-disabled}/no-synchronize.yml +0 -0
- /package/rules/tree-sitter-queries/{plsql → plsql-disabled}/not-null-initialization.yml +0 -0
- /package/rules/tree-sitter-queries/{plsql → plsql-disabled}/raise-application-error-codes.yml +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,237 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to pi-lens will be documented in this file.
|
|
4
4
|
|
|
5
|
-
## [
|
|
5
|
+
## [3.8.51] - 2026-06-14
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Interrupting the agent (Esc) now cancels in-flight linter/formatter/type-check child processes (refs #197)** — pi-lens runs its dispatch tools via `safeSpawnAsync`, which already supported an `AbortSignal`, but nothing was feeding pi's per-turn `ctx.signal` into it, so an interrupted turn left its linters running until they hit their own timeout (up to 10–15s of wasted work, and on Windows orphaned process trees). The lifecycle handlers (`tool_result`, `agent_end`, `turn_end`) now publish the turn's `ctx.signal` as an ambient default that every `safeSpawnAsync` falls back to (`setAmbientAbortSignal`, cleared in each handler's `finally`), so Esc/abort tears down the in-flight children — process-tree kill on Windows. Threading the signal through every dispatch→runner→spawn call site would have been invasive; the ambient default captures the signal at spawn time, so clearing it after a handler returns only affects future spawns, never work already in flight. An explicit `options.signal` still takes precedence. Guarded by `tests/clients/safe-spawn-ambient-signal.test.ts`.
|
|
10
|
+
|
|
11
|
+
- **Resumed sessions rehydrate their diagnostics instead of starting empty (#190 Phase 1)** — quitting and resuming a session (`pi --session <id>`) made `lens_diagnostics` return nothing: pi-lens kept widget/diagnostic state in-memory only and reset on every `session_start`, treating resume as new. Root cause: it took the session id from the `session_start` event (which has none) and fell back to a fresh **per-process random id**, so nothing could be keyed across a resume. Now pi-lens reads pi's **stable** session id via `ctx.sessionManager.getSessionId()` and the `session_start.reason` (`new`/`resume`/`fork`/`reload`/`startup`): it persists the per-file widget diagnostics to disk at each `turn_end` (under `getProjectDataDir(cwd)/sessions/<id>.json`, atomic write, best-effort) and **rehydrates** that session's snapshot when one exists so `lens_diagnostics mode=all` and the widget show the prior findings; `reload` keeps in-memory state; an explicit `new` session starts clean. The rehydrate trigger is *"a persisted snapshot exists for this stable id"*, **not** `reason === "resume"` — a `pi --session <id>` launch fires `reason: "startup"` (only an in-process `switchSession` is `"resume"`), so gating on `"resume"` alone missed the common resume path; the reason→action mapping is now a unit-tested pure function (`sessionStartMode`). A brand-new session at startup has a fresh id with no snapshot → clean. Process-bound `lspServers` are deliberately not persisted (they re-spawn fresh).
|
|
12
|
+
|
|
13
|
+
**Phase 2** adds: (a) **fork branching** — `session_before_fork` stashes the source session's diagnostics in-memory and the forked session's `session_start` (reason="fork") adopts them, then persists under the new session id, so a `/fork` starts from the fork point's findings instead of empty (in-memory hand-off avoids deriving the source id from a file path, since pi stores the id in the session-file header, not the filename); (b) **freshness reconciliation (#180)** — on resume, files whose on-disk mtime is newer than the snapshot (edited between sessions) or that no longer exist are dropped before rehydration, so a resume never surfaces stale diagnostics; dropped files re-scan on their next edit. Still deferred on #190: `delta`-mode rehydration (gated by the `projectSeq`-reset freshness check, intertwined with #180's seq semantics) and tree-navigation (`/tree` doesn't change files on disk). Guarded by `tests/clients/session-state-store.test.ts` (export/import, save/load, end-to-end resume, fork hand-off, `dropStaleFiles`) and `tests/clients/runtime-session-lifecycle.test.ts` (stable-id pinning). Investigation closed the other two transitions as no-ops: `delta` mode is current-turn-scoped and its caches already persist per-project (no rehydration belongs there), and `/tree` navigation doesn't change files on disk. As discoverability for the turn-scoped default, `lens_diagnostics mode=delta` now appends a one-line hint when it's empty but the session-wide view has carried-over findings (e.g. just after a resume): "N findings across M files carried over — use mode=all".
|
|
14
|
+
|
|
15
|
+
- **`/lens-health` surfaces event-loop occupancy (#192)** — pi-lens now monitors event-loop delay in production (Node's native `monitorEventLoopDelay`, enabled at extension load, no per-event overhead) and `/lens-health` reports the worst synchronous block, p99, and mean for the session — flagging a >100ms block that can stutter the TUI. This is the dimension our duration-only logs were blind to (the one that let the ~1.5s scan freeze through, #188/#191). `latency.log` also records a `loop_block` entry for each new worst freeze, attributed to its turn, so blocks are queryable across sessions. Paired with the at-scale occupancy **test** harness (`tests/support/perf-harness.ts` — `measureMaxSyncBlockMs` + `generateSourceTree`) and CI budget guards. A dedicated `/lens-perf` view remains (#192).
|
|
16
|
+
|
|
17
|
+
- **Extension-wiring test harness + mock consolidation (closes #171)** — a single dependency-free mock of the host `ExtensionAPI` (`tests/support/pi-mock.ts`) that records everything `index.ts` registers (flags/commands/tools/lifecycle hooks) and lets a test drive a hook (`emit`) or command (`runCommand`) through the *real* entry, with `makeCtx()` capturing `ui.notify`/`setStatus`/`setWidget`. New `index-wiring` tests assert the full registration contract and that `context` injection is gated by `--no-lens-context` and flipped by `/lens-context-toggle` — glue that was previously untested and that the dist-packaging breakage showed we need. Consolidated the three parallel pi mocks onto this one: migrated `lens-toggle-command.test.ts` (template) and `index-integration.test.ts`, removed the duplicate `tests/support/mock-pi.ts`, and deleted `extension-hooks.test.ts` (its assertions never invoked the real entry — they registered on the mock and asserted the mock, so they were tautological and used stale flag names; the real registration contract is now covered by `index-wiring`). Dispatch-runner `RunnerContext` tests are a separate harness concern, out of scope here.
|
|
18
|
+
|
|
19
|
+
- **Startup-time logging (makes the #182 win measurable)** — pi-lens now records how long pi took to load it: `performance.now()` captured as the first statement in the extension entry (after all imports = full jiti transpile paid) gives ms from pi's process start to pi-lens load-complete. Emitted once per load as a human line in `sessionstart.log` (`pi-lens loaded: <ms>ms after process start (from dist|source)`) and a structured `latency.log` entry (`phase: "extension_loaded"`, `metadata.loadedFrom`). The `loadedFrom` tag distinguishes the precompiled `dist/` path from `source`/jiti, so the transpile-on-startup cost is now quantified rather than guessed (`clients/startup-timing.ts`).
|
|
20
|
+
|
|
21
|
+
- **Runner failures carry a `failureKind`, and the log-smell analyzer tells breakage from found-errors (refs #207)** — the dispatch latency log recorded `status:"failed"` both when a runner genuinely broke *and* when it simply found blocking diagnostics (the LSP runner reports `failed` for a file with type errors), so `scripts/analyze-pi-lens-logs.mjs` counted all of them as crashes — a false "98 runner failures" alarm over 24h where the real infra-failure count was **zero**. `RunnerResult` now carries `failureKind`/`failureMessage`: the LSP runner tags `server_error` (spawn/exit/JSON-RPC) vs `blocking_diagnostics` (found type errors — not a fault), and the central `runRunner` catch tags `timeout` vs `exception` (covering every runner's crash path); the dispatcher logs `metadata.failureKind` on the runner line. The analyzer reclassifies accordingly — only genuine breakage (`timeout`/`exception`/`server_error`) counts as the `runner-failures` smell, found-errors go to a separate per-runner tally, and legacy logs without the field fall back to a "failed + has diagnostics = found-errors" heuristic. It also now reads two live logs it was previously blind to — `actionable-warnings*.log` (advisory inject/suppress pipeline) and `ast-grep-tools*.log` (MCP search/replace telemetry) — with new `ast-grep-tool-errors` / `actionable-warning-errors` smells and per-source report sections. Guarded by `tests/scripts/analyze-pi-lens-logs.test.ts` (fixture-driven subprocess run: source discovery, the infra-vs-found-errors split, advisory aggregation, the ast-grep error smell).
|
|
22
|
+
|
|
23
|
+
- **ast-grep `search`/`replace` surface a remediation hint to the agent on error (refs #207)** — the tools already classified each failure (`classifyAstGrepError`) for telemetry, but only the two highest-frequency categories (`multiple_ast_nodes`, `cannot_parse_query`, curated by `sg-runner.ts`) reached the agent with guidance; the other four (`timeout`, `tool_not_found`, `json_parse_failed`, `other`) came back as raw stderr, and the rich `getPatternHint()` self-correction only fired on the *zero-matches* path, never on a hard error. `astGrepRemediationHint(kind)` now reuses that same classification to append a one-line fix on the error path (returns `null` for the already-curated categories so it never doubles up) — e.g. an empty `--rewrite` (previously raw clap CLI noise) now gets "verify the pattern is a single valid AST node … or fall back to grep". Guarded by `ast-grep-tool-logger.test.ts` (hint map incl. the real empty-`--rewrite` log case + a wrapped multiple-nodes error → no extra hint) and error-path tests in both tool suites (hint appended for raw errors, *not* for curated ones).
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- **Read-guard autopatch recovers Unicode-punctuation drift (Tier C)** — the autopatch ladder (`tryCorrectIndentationMismatchFromContent`) gained a tier that tolerates the punctuation models routinely swap: smart quotes ↔ straight (`“”‘’` ↔ `"'`), em/en-dash ↔ hyphen, and non-breaking / typographic spaces ↔ a regular space (common when `oldText` is pasted from rendered Markdown or the model "tidies" punctuation). Previously such an edit failed the exact + whitespace tiers and was blocked; now the tier matches on a Unicode-folded, whitespace-collapsed signature and — like Tiers A/B — **recovers and returns the verbatim file span** (the file's real characters), so the applied edit stays exact. Same safety contract: the folded signature must match exactly once, anchored on ≥2 non-blank lines. Borrowed from the fuzzy matcher in mitsuhiko/agent-stuff's multi-edit, but kept to pi-lens's verify-don't-guess discipline (no blind "closest match" fallthrough). Guarded by `read-guard-tool-lines.test.ts` (smart-quote / em-dash / NBSP recovery + single-line, ambiguous, and absent-content negatives).
|
|
28
|
+
|
|
29
|
+
- **Skills now load from the compiled `dist/` build (closes #205, reported by @feoh)** — the `resources_discover` handler resolved the skills directory relative to the module's own location (`path.dirname(import.meta.url) + "/skills"`). Under the `dist/` layout (#182) the module is `dist/index.js`, so that landed on the nonexistent `dist/skills/` and pi logged `skill path does not exist` while silently loading none of pi-lens's skills (ast-grep, lsp-navigation, write-ast-grep-rule, write-tree-sitter-rule). Now it uses `resolvePackagePath(import.meta.url, "skills")`, which walks up to the nearest `package.json` and lands on `<pkg>/skills/` in both the source and dist layouts. (The issue's suggested `path.join(extensionDir, "..", "skills")` would have fixed dist but broken source, where the module already sits at the package root.) Guarded by an `index-wiring` test that invokes the handler and asserts the path exists, ends in `skills`, and is not `dist/skills`.
|
|
30
|
+
|
|
31
|
+
- **SonarCloud security/reliability fixes (new-code period)** — (1) **`S5850` reliability bug** in `clients/word-index.ts`: the `TEST_VENDOR_RE` regex mixed anchors with a top-level `|`, leaving operator precedence ambiguous; wrapped the two alternatives in explicit non-capturing groups (behaviour verified identical, capture groups unchanged). (2) **`S4790` weak-hash** ×2: `clients/mcp/ipc.ts` (IPC socket/pipe name) and `clients/review-graph/builder.ts` (content fingerprint for change detection) used `sha1` for non-security hashing — switched to `sha256` (functionally equivalent here, silences the flag). (3) **`S7637`**: pinned the third-party `softprops/action-gh-release` GitHub Action to a full commit SHA (`b430933…` # v3) in `release.yml`. The remaining 48 `S5852` (ReDoS) and 4 `S4036` (PATH lookup) hotspots were reviewed and are safe by context — every flagged regex is single-quantifier or polynomial-at-worst over bounded, trusted input (source lines / tool output), with zero nested-quantifier (`(x+)+`) patterns in the codebase; PATH-based tool resolution is core to how pi-lens finds the user's installed linters/LSPs. These are review hotspots to mark *Safe* in SonarCloud, not code defects.
|
|
32
|
+
|
|
33
|
+
- **`audit:rule-catalog` no longer fails on duplicate rule_ids** — three catalog entries violated the registry's globally-unique-`rule_id` invariant (which the `-java`/`-js`/`-cobol` suffix convention exists to maintain). `infinite-loop` had genuine java *and* typescript rule files sharing one id, so the java variant was renamed to `infinite-loop-java` (id + file, matching the `unnecessary-bit-ops-java` precedent); `no-octal-values` and `short-circuit-logic` had phantom `typescript` catalog entries with no backing rule file (TS octal coverage already exists via the ast-grep `no-octal-literal` rule), so those were removed. The audit now reports 0 errors.
|
|
34
|
+
|
|
35
|
+
- **ast-grep-napi runner migrated to napi's native rule engine; hand-rolled interpreter deleted (closes #206)** — the runner used a ~240-line hand-rolled rule interpreter (`nodeMatchesCondition`/`findMatchingNodes`/`findByKind`/`legacyRuleMatches`) over a hand-rolled YAML parser (`parseSimpleYaml`). That parser could not faithfully serialize the ast-grep grammar — it flattened nested `any`/`has`, kept quotes inside `kind: "true"`, and dropped the metavariable key from `constraints` — so relational/`field`/`constraints` rules were silently skipped, and the interpreter's `has` both recursed AND matched the node itself (a self-referential `kind: X` has `kind: X` flagged **every** X: `nested-ternary` reported 720 of which ~678 were false). Now: (1) `parseSimpleYaml` is a thin `js-yaml` wrapper — the full grammar survives intact and is fed straight to `root.findAll({rule, constraints})`; one malformed document skips only itself, not its whole file; (2) the runner always uses napi (the `ast-grep-native-rules` flag and the entire legacy interpreter are gone); a rule napi rejects is skipped, never partially evaluated. Corpus changes to land the migration cleanly: quoted three rule `message:` scalars that began with `!!`/contained `:` (js-yaml threw on them, silently dropping the rules); rewrote five rules that used non-existent tree-sitter kinds (`element_access_expression`→`subscript_expression`, `property_access_expression`→`member_expression`, `block`→`statement_block`, `for_of_statement`→pattern) — they had been dead in both engines; added `stopBy: end` to `switch-without-default` and `nested-ternary`(+js) (their `has` targets a non-direct descendant — `switch_default` lives under `switch_body`); left direct-child `has` rules (`no-throw-string`, `no-discarded-error`, `else-return`, `redundant-state`/`follows`) at napi's neighbour default so they don't over-report. Earlier de-risking (this batch): `no-constant-condition`(+js) rewritten to a flat pattern any-list; `constructor-super`(+js)/`no-process-env`/`no-hardcoded-secrets`(+js)/`unchecked-sync-fs`(+js) moved to `rules-disabled/` (constraint/relational rules that were never actually running; secrets entries marked `deprecated` in the catalog). Net on this repo: 233 files, 0 errors, ~632 diagnostics with the `nested-ternary` false-positive bomb gone and previously-dead relational rules now correctly active. Guarded by `ast-grep-sonar-rules.test.ts` (native `has`/`stopBy` semantics: nested vs single ternary, switch with/without default, rewritten relational rules) and `yaml-rule-parser.test.ts` (faithful nested-`any`/`has` + `constraints` survive the parse; malformed doc returns null).
|
|
36
|
+
|
|
37
|
+
- **LSP registry-consistency guard (follow-up to #208)** — #208 was a server pi-lens wires that never actually came up, so we added a deterministic per-PR guard: `tests/clients/lsp/lsp-registry-consistency.test.ts` validates every `LSP_SERVERS` entry is well-formed — globally-unique ids, required `spawn`/`root`/`extensions`, clean extension tokens, sane optional timeouts — catching half-wired or duplicated entries cheaply. The complementary *live* end-to-end install→launch→`initialize` smoke (across all install strategies) is tracked in #209: a first cut produced false failures on Windows (a hand-rolled handshake that bypassed `vscode-jsonrpc` framing missed the flagship typescript server, which actually responds in ~120 ms), so it's being reworked to drive pi-lens's real LSP client before it lands as a nightly job.
|
|
38
|
+
|
|
39
|
+
- **LSP auto-install no longer rejects stdio servers that fail `--version` (closes #208, reported by a Fedora Silverblue user)** — `verifyToolBinary` confirmed a freshly-installed binary by running `<bin> --version` and requiring exit 0. Servers built on `vscode-languageserver-node` — the `vscode-langservers-extracted` family (JSON/CSS/HTML/ESLint) — reject a bare `--version`: `createConnection()` throws `Connection input stream is not set … '--node-ipc', '--stdio' or '--socket={number}'` and exits 1. So every install "verified as broken," got cleaned up, and those LSPs were never available (lost diagnostics/hover/format, plus repeated wasted install attempts at startup). Verification now treats that specific transport-required error as success — it is positive proof the binary loaded and is a working LSP server that simply needs `--stdio` to run. A genuinely broken install still fails, because it errors with a *different* message (`ERR_MODULE_NOT_FOUND`, `SyntaxError`, …) that doesn't match the pattern, so the broken-install guard is preserved. **Smoke-tested against the real `vscode-langservers-extracted@4.10.0` binaries**: JSON/CSS/HTML/ESLint all emit the transport error and now verify — these are exactly the four LSP servers pi-lens wires (`clients/lsp/server.ts`, each spawned with `--stdio` and auto-installed via `managedToolId`). pi-lens does **not** configure a Markdown LSP, so the package's Markdown binary is irrelevant here; it additionally fails to load under Node ≥24 (unrelated upstream `vscode-uri` ESM-interop `SyntaxError`, before the transport check), which verification correctly continues to reject — so it is neither used by pi-lens nor force-verified. The check is a small exported predicate (`isLspTransportRequiredError`) guarded by `tests/clients/installer/lsp-transport-verify.test.ts` (the exact vscode transport error → pass; the real Markdown `vscode-uri` crash / module-not-found / syntax-error / unknown-flag → still fail).
|
|
40
|
+
|
|
41
|
+
- **LSP last-known diagnostics cache is content-hash guarded — no more stale actionable-warnings (refs #207)** — the actionable-warnings turn_end read reused `getLastKnownDiagnostics` (keyed by path only) on the premise that dispatch's `touchFile` primed it that turn — but `touchFile` never wrote `lastKnownDiagnostics` (only the service-level `getDiagnostics` did, called by the *fresh* branch and the agent lsp tools). So once an earlier turn's fresh branch cached diagnostics, later turns on the same file served those **prior-turn** results as `"cache"` with no content guard — genuine staleness, worst when LSP is cold (the entry can't be refreshed that turn). This was the ~40% `lspSource:cache` seen in the log review. Now `touchFile` primes `lastKnownDiagnostics` together with a sha256 of the synced content (gated on `collectDiagnostics`), `getLastKnownDiagnostics(path, expectedContentHash)` returns the entry only on a hash match (the content-less service merge clears the hash, so those entries never pose as current; the unguarded widget read still gets last-known for display), and actionable-warnings hashes the on-disk bytes and passes them: match → verified-current reuse, mismatch/absent → fresh open+wait. `lspSource:"cache"` now means **verified-current reuse**. Guarded by `actionable-warnings-lsp-cache.test.ts` (passes the correct hash; reuses on match with no fresh read; rejects a stale entry on hash mismatch → forces a fresh read).
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
|
|
45
|
+
- **Rust/Go/type-coverage availability probes are now async (refs #197)** — `RustClient.findCargoPath`/`isAvailable`, `GoClient.findGoPath`/`isGoAvailable`, and `TypeCoverageClient.isAvailable`/`scan` were sync `safeSpawn` `--version`/path probes that blocked the event loop on first use; they're now `findCargoPathAsync`/`isAvailableAsync` etc. on `safeSpawnAsync`, with their callers (the `rust-clippy`/`go-vet` dispatch runners, the `session_start` active-tools list, and `/lens-booboo`) awaiting them. The unused `GoClient.isGoplsAvailable` was deleted outright. One intentionally-sync probe remains: `TestRunnerClient.detectRunner`'s `which pytest` check — it's cached per (cwd, runner) and only fires once for a Python project with no config-file runner, and converting it would ripple async through five methods into the per-edit turn path for no real gain.
|
|
46
|
+
|
|
47
|
+
- **Dispatch availability probes are now async-only (refs #197)** — the runner availability layer carried parallel sync/async probes; the sync ones blocked the event loop on first use. `createAvailabilityChecker` now exposes only `isAvailableAsync` (the never-taken sync `isAvailable` fallback is gone, and all ~25 runners + `resolveAvailableOrInstall` use the async path directly), the ast-grep availability chain collapsed to its async form (`AstGrepClient.runTempScanAsync` now `await`s `ensureAvailable()`, retiring the dead sync `AstGrepClient.isAvailable` → `SgRunner.isAvailable` → `isSgAvailable` → `probeAstGrepCommand` cascade), and the unused sync `isCommandAvailable` in `dispatch/runners/utils.ts` was deleted. No remaining sync spawn in the dispatch availability layer; behaviour unchanged (full suite green).
|
|
48
|
+
|
|
49
|
+
- **Tool installs and formatter probes no longer block the event loop (refs #197)** — converted the last event-loop-reachable synchronous spawns to `safeSpawnAsync`: the LSP runtime-install actions (`tryGoInstallGopls` `go install`, `tryDotnetToolInstall` `dotnet tool install`/`update`, `tryGemInstall` `gem install` — previously raw `spawnSync` that could freeze the TUI for the whole install, and `go install` had *no* timeout at all), and every formatter probe/install in `formatters.ts` (`gem install rubocop`, `rustup component add rustfmt`, `which`, `go env GOROOT`, `dotnet csharpier --version`, the PSScriptAnalyzer check). On Windows this also fixes a latent bug — `gem`/`dotnet` are often `.cmd` shims that bare `spawnSync(…, { shell:false })` can't launch, whereas `safeSpawnAsync` uses shell mode. Installs pass a new `ignoreAmbientSignal` option so they run to completion even if the agent turn is interrupted (matching the old uncancellable sync behaviour — an Esc can't strand a half-finished `gem install`); the quick probes stay cancellable. Equivalence-tested in `tests/clients/install-actions.test.ts` (same command/args, same success-on-exit-0 semantics, the dotnet NuGet-missing and update-fallback branches, the gem PATH update, and the formatter lazy-install dedupe guard) plus a `safeSpawnAsync` `ignoreAmbientSignal` unit test.
|
|
50
|
+
|
|
51
|
+
### Performance
|
|
52
|
+
|
|
53
|
+
- **Collapsed the redundant post-edit LSP double-push that discarded in-flight diagnostics (#203)** — on every edit pi-lens pushed the final post-format content to the language server twice: once in the pipeline `lsp_sync` phase (via `resyncLspFile` → `LSPService.openFile`) and again ~80ms later in the `dispatch-lsp-runner`. `openFile` never registered the push in the touch-debounce map (`markTouched`), so the dispatch runner's `shouldSkipNotify` always returned false and its `didChange` **cleared the diagnostics the first push had just set the server computing**, forcing a from-scratch recompute and a multi-second wait. Latency-log evidence (`~/.pi-lens/latency.log`, ~18k events): the notify-skip dedup fired on just 2 of ~465 dispatch touches, and ~280 of ~700 document-diagnostics waits timed out — **136 of 142 on TypeScript** (`typescript-language-server` is push-only, so the timeouts were us throwing away a push that did arrive, not waiting on one that never came). `resyncLspFile` now routes through `touchFile({ diagnostics: "none", source: "lsp_sync", clientScope: "primary" })`, so the sync push registers via `markTouched`; the dispatch touch moments later then hits `shouldSkipNotify=true`, reuses those diagnostics instead of re-clearing, and `waitForDiagnostics` fast-paths. Expected `dispatch_lint` p50 ~3.1s → ~2.2s on every LSP edit, with the `.ts` timeout population largely eliminated. The old `formatChanged`/`preserveDiagnostics` branch is dropped — `didChange` triggers a server recompute regardless, so letting the cache clear yields fresh, correctly-positioned diagnostics rather than stale pre-edit ones. Regression-tested in `tests/clients/pipeline.test.ts` (the sync routes through `touchFile` with the registering options, not `openFile`); the touch→touch dedup itself is already covered by `service-touch-collect.test.ts` (#116).
|
|
54
|
+
|
|
55
|
+
- **Per-server diagnostics-wait budget on the LSP hot path (#203)** — `touchFile` resolved its diagnostics-wait timeout from a flat default (the dispatch runner's 2500ms / a 1200ms floor), ignoring the per-server budgets already defined in `server-strategies.ts`. On the single-server primary path it now uses that server's `aggregateWaitMs` (TypeScript 1000ms, rust-analyzer 3000ms, python 1500ms, …), bounded by any caller ceiling — so a fast server isn't held to a flat multi-second wait while a slow one still gets the time it needs. Env override (`PI_LENS_LSP_DIAGNOSTICS_MAX_WAIT_MS`) still wins, and the multi-server `full`/cascade path keeps its flat resolution. Covered by `tests/clients/lsp/service-touch-collect.test.ts`.
|
|
56
|
+
|
|
57
|
+
- **Auto-warm the dominant language's LSP at `session_start` (#203)** — first-edit-of-session cold-spawn stalls (`lsp_client_wait_timeout`, observed up to 5s on TypeScript/Deno) happened because servers only pre-warmed when a project explicitly listed `warmFiles`. When none are configured, pi-lens now uses the language detection it already does to pre-spawn just the **dominant** language's server (highest source-file count) by opening one representative file — backgrounded off the interactive path. Only one server is warmed by design: launching every detected language's server at once (rust-analyzer + gopls + tsserver …) would spike the event loop at startup, working against the latency it protects. The scan is directory-reads-only (`inspectGeneratedHeaders:false`, no per-file opens). Covered by `tests/clients/runtime-session-warm.test.ts`.
|
|
58
|
+
|
|
59
|
+
- **Document-version-coherent diagnostics freshness (#203)** — `waitForDiagnostics` judged freshness off a monotonic push counter, so a stale `publishDiagnostics` for a superseded document version could satisfy a wait for the current one (a latent correctness gap exposed once the double-push above stops clearing the cache pre-wait). The client now records the LSP document version each push was computed against (`publishDiagnostics.version`) and rejects cached results that lag the latest `didChange`. Servers that omit a version are treated as current, so version-less servers are unaffected and the timeout remains the backstop. Covered by `tests/clients/lsp/client-internals.test.ts`.
|
|
60
|
+
|
|
61
|
+
### Removed
|
|
62
|
+
|
|
63
|
+
- **Deleted the dead synchronous linter/formatter methods from `BiomeClient`/`RuffClient` (refs #197)** — both clients carried a full legacy *sync* surface (`checkFile`, `checkFormatting`, `fixFile`, `fixFiles`, `formatFile`, `formatDiagnostics`, biome's `getFormatDiff`/`withValidatedPath`/`spawnBiome`, plus the now-orphaned private `parseDiagnostics`/`computeDiff`) built on the event-loop-blocking sync `safeSpawn`. An audit of the live dispatch path confirmed **none of these had any caller** — every per-edit path already runs async: the dispatch runners (`biome-check.ts`, `ruff.ts`) use `safeSpawnAsync`, autofix-on-write uses `fixFileAsync` (`pipeline.ts`), and format-on-write uses the async `formatService`. The audit that flagged "autofix-on-write blocks the loop" had conflated this dead sync code with the live `fixFileAsync` sitting next to it. Removing it deletes the most alarming sync-spawn call sites outright (-719 lines; biome-client 657→233, ruff-client 511→218) with zero behavior change (full suite green). The remaining sync `safeSpawn` sites are the cached availability probes and one-shot install actions tracked in #197.
|
|
64
|
+
|
|
65
|
+
- **Deleted the remaining dead legacy-sync methods + an unused module (refs #197)** — continuing the sync-`safeSpawn` cleanup: removed `TestRunnerClient.runTestFile` (sync; the live per-write path uses `runTestFileAsync`), `AstGrepClient.scanFile` → `SgRunner.execSync` → `SgRunner.tempScan`/`scanWithRule` (a fully dead sync ast-grep scan cascade; the ast-grep tools and temp-scans all use the async `exec`/`tempScanAsync` paths), and the entire `clients/subprocess-client.ts` (a 101-line abstract `SubprocessClient` base with **zero** importers). Orphaned helpers/imports went with them (`mapSeverity`, the `AstGrepParser` import, `sg-runner`'s now-unused sync `safeSpawn` import) and the obsolete `execSync` tests were removed (the co-located `formatMatches` tests were re-parented, not lost). ~360 fewer lines, zero behavior change (full suite green). Kept by design: `findCargoPath`/`findGoPath` and the `detectRunner` pytest probe (bounded, cached) and the `booboo` command-path probes (user-invoked).
|
|
66
|
+
|
|
67
|
+
- **Deleted the dead synchronous check methods from `RustClient`/`GoClient` (refs #197)** — same legacy pattern as the Biome/Ruff cleanup: the per-edit Rust/Go diagnostics already run through the async dispatch runners (`rust-clippy.ts`/`go-vet.ts`, which call `findCargoPath`/`findGoPath` + their own `safeSpawnAsync`), so the clients' sync `checkFile`/`clippyCheck`/`buildCheck`/`formatDiagnostics` methods (built on blocking `safeSpawn`) and their now-orphaned private `parseJsonOutput`/`parseOutput` + `CargoMessage` type had **no callers**. Removed (rust-client 270→107, go-client 242→126). The still-live probes are intentionally kept: `findCargoPath`/`findGoPath` (a bounded, cached, one-time `--version` fallback only hit when the tool isn't at a standard absolute path) and the status-list `isAvailable`/`isGoAvailable` (command/`runtime-session` path) — tracked as the residual in #197.
|
|
68
|
+
|
|
69
|
+
- **Deleted two more dead sync modules/functions (refs #197)** — the entire `clients/tool-availability.ts` module (a 251-line cached tool-availability layer — `isToolAvailable`/`getToolVersion`/`ToolAvailabilityChecker`/`TOOL_REGISTRY`) had **zero importers** anywhere in source or tests, and the sync `resolveLocalFirst()` in `runner-helpers.ts` was superseded by its live async twin `resolveLocalFirstAsync()` and likewise had no callers. Both built on sync `safeSpawn`; deleting them removes three more event-loop-blocking probe sites at zero risk. The genuinely *live* remaining sync probes — `createAvailabilityChecker`'s sync `isAvailable` fallback and `isSgAvailable()` (reached via the clients' legacy sync `isAvailable()` methods, e.g. `ast-grep-client`/`rust-client`/`type-coverage-client`) — are a cross-client availability-contract change tracked as the remaining (B) work in #197, not a deletion.
|
|
70
|
+
|
|
71
|
+
### Security
|
|
72
|
+
|
|
73
|
+
- **Patched a moderate ReDoS-class advisory in a transitive dep and added a CI audit gate** — `brace-expansion` (pulled via our direct `minimatch@^10`) resolved to a version under GHSA-jxxr-4gwj-5jf2 (a large numeric range defeats its documented `max` DoS protection). Bumped to `5.0.6` (lockfile-only; `npm audit` clean for both prod and full trees). It slipped through because Dependabot's weekly *version* updates only bump direct deps, and `minimatch@^10` was already satisfied — nothing was watching the transitive tree. CI now runs `npm audit --omit=dev --audit-level=high` in the lint job, so a known-vulnerable **production** dependency (what ships to users via `--omit=dev`) fails the build at PR time instead of being noticed by chance; the gate is scoped to high/critical to avoid blocking on fix-less moderate advisories, which Dependabot security updates can handle separately.
|
|
74
|
+
|
|
75
|
+
### Fixed
|
|
76
|
+
|
|
77
|
+
- **Production install no longer fails to build `dist/` under `npm install --omit=dev` (#193, thanks @feoh; guarded by #194)** — `prepare`/`build:dist` inherited `types: ["node"]` from the base tsconfig, so under pi's `--omit=dev` git install (dev-only `@types/node` absent) `tsc` failed with TS2688 *before* type-checking — `--noCheck` doesn't suppress a program-construction error, contrary to what #182 assumed. `tsconfig.dist.json` now sets `types: []` (the transpile-only dist build needs no ambient node types). A new CI job (`prod-install-build`) installs `--omit=dev` and builds `dist/` from source, so this can't regress — the tarball-based install-test never re-ran the build under `--omit=dev` (#194).
|
|
78
|
+
|
|
79
|
+
- **Faster startup: ship precompiled JS instead of transpiling on every launch (closes #182)** — pi-lens was distributed as TypeScript source (`main: index.ts`, `pi.extensions: ["./index.ts"]`), so pi's jiti loader transpiled ~215 `.ts` files on every cold start (including `/new`), adding ~3.5s. The package now ships a precompiled `dist/` and points `main` + `pi.extensions` at `./dist/index.js`, which pi loads directly (~1.5s). A `prepare` step (`tsconfig.dist.json` → `dist/`, transpile-only via `--noCheck`) builds it **on install — including `git:` installs, which run `npm install` not `npm pack` — and before publish**, so both install paths get the compiled output with no rebuild script. pi-lens's own asset resolution is unaffected: `rules/`, `config/`, and grammars resolve via `getPackageRoot()` (walks up to `package.json`), not module depth. Guarded by `tests/packaging.test.ts` (entry/`files` contract), an upgraded `scripts/check-extensions.mjs` (validates compiled `.js` imports resolve), and CI install-test steps that verify the tarball ships `dist/index.js` with no `.ts` source and that the compiled entry loads. The dev/test loop still uses the in-place `npm run build`.
|
|
80
|
+
|
|
81
|
+
- **Skills now actually load under the moved entry (closes #199)** — pi resolves each `pi.skills` entry relative to the extension entry's **file path** (`path.resolve(entryFile, skillEntry)`), not its directory. Once the entry moved to `./dist/index.js` (#182), `pi.skills: ["../skills"]` resolved to `<root>/dist/skills` — the `../` only cancels `index.js` and stays in `dist/` — which doesn't exist, so pi-lens's skills silently stopped loading and pi warned `[Skill conflicts] … skill path does not exist`. Reaching the real root `skills/` from `dist/index.js` needs to climb **two** levels, so `pi.skills` is now `["../../skills"]`. The earlier value was off by one and the CI/tarball check never caught it (it only verifies `skills/` *ships*, not that pi *resolves* it); `tests/packaging.test.ts` now statically replicates `resolve(entryFile, skillEntry)` and asserts it lands on the package's own root `skills/`.
|
|
82
|
+
|
|
83
|
+
- **`ast_grep_replace` apply no longer falsely reports "no matches" on a successful replacement (closes #178)** — the apply path counted matches *after* writing the fix, so any content-changing replacement reported `[APPLIED] No changes made (no matches found)` despite succeeding, misleading agents into thinking the edit failed. Both replace paths (pattern and rule) now report the **pre-apply** match count, and the apply-zero display message is unambiguous (`[NOT APPLIED] No matches found …`).
|
|
84
|
+
|
|
85
|
+
- **Reliable alphabetical sort of project-diagnostic sources** — `[...sources].sort()` relied on default UTF-16 ordering, which SonarCloud flags as unreliable (`typescript:S2871`); now uses an explicit `String.localeCompare` comparator.
|
|
86
|
+
|
|
87
|
+
- **Multi-line diagnostic messages no longer break TUI rendering (closes #189)** — diagnostics with multi-line messages (e.g. TS2769 "no overload matches this call") spilled across several widget rows and broke the layout (and the `L<line>: <message>` inline-blocker format), because `fitLine` clips by visible width but embedded newlines survive. `recordDiagnostics` now collapses whitespace runs to a single space at storage, so the widget, `lens_diagnostics`, and summaries all get single-line messages.
|
|
88
|
+
|
|
89
|
+
- **`session_start` no longer freezes TUI input on cold boot / `/new` (closes #188)** — the synchronous `session_start` walks (scan-context, language profile, todo / call-graph scheduling) ran O(N) without yielding, starving the stdin macrotask queue for 3–6s on large projects. Fixed with an `ignoreMatcher` path-memo (mtime-invalidated), process-lifetime memos for scan-context and language-profile, async chunked-yield walk variants, background scans deferred past the typing window, a per-file chunked todo scan, and a cold-start forced-quick + delayed-warmup. `session_start` total drops from 3000–6000ms to ~3ms on a 1832-file project. Env knobs: `PI_LENS_COLD_START_QUICK`, `PI_LENS_WARMUP_DELAY_MS`, `PI_LENS_STARTUP_MODE`. Together with #182 this fixes both halves of startup latency (jiti transpile + scan). Thanks @amit-gshe.
|
|
90
|
+
|
|
91
|
+
- **Source-file enumeration no longer blocks the event loop (perf hardening, follows #188)** — the file walk under the deferred todo / project-diagnostics scans (`collectSourceFiles`) was still a single ~1.5s synchronous burst on a 2k-file project (≈70% of it the per-file 4 KB generated-header read), blocking TUI input even though #188 had made the *callers* yield. Added a chunked-yield `collectSourceFilesAsync` (shares the filter logic with the sync collector via an extracted `classifyEntry`, so results are identical), memoized the generated-header verdict (keyed on path+mtime+size, self-invalidating on edit), and routed the background callers (todo, project-diagnostics) to the async path. Longest synchronous block during enumeration: **~1576ms → ≤38ms cold / 5.9ms warm**; returned file set asserted identical. Guarded by `tests/clients/source-filter-async.test.ts`; remaining (riskier) source-walk hardening tracked in #191.
|
|
92
|
+
|
|
93
|
+
- **Per-edit cascade graph rebuild no longer freezes the TUI (perf hardening, follows #188/#191)** — `buildOrUpdateGraph` runs on **every** write/edit (via `computeCascadeForFile`), and even on a pure cache hit it re-derived the workspace source-file list (sync tree walk + per-file 4 KB generated-header read) and re-statted every project file just to compute the cache-validity signature — the same sync-FS-over-all-files class #188 fixed for startup, here on the path that runs on every keystroke-triggered edit. Made the walk (`getGraphSourceFiles`) and the signature/stat loop (`sourceSignatureMapAsync`) async + chunked-yield, reusing the existing `collectSourceFilesAsync` (byte-identical file list via the shared `classifyEntry`) and producing the identical `file → "size:mtimeMs"` signature; `_doBuildGraph` already awaited the builder so the call contract is unchanged. Longest synchronous block on a 1,200-file project: **warm cache-hit ~770ms → ~47ms; cold full derivation ~2,215ms → ~46ms** (total FS work unchanged — the loop now yields instead of freezing). Verified behavior-preserving: the async sections touch only local accumulation (no shared cache/fact mutation), and concurrent different-file builds were already interleavable at the existing `await` points, so no new race. Guarded by `tests/clients/cascade-graph-occupancy.test.ts`. The walk still runs each edit (now yielding), but we deliberately stop here: the expensive work (tree-sitter parse + graph construction) is already cached, so this walk is only the cache-*validation* step, and memoizing it would trade always-fresh impact analysis for tens of ms of yielded work on an accuracy-critical path — with no FS watcher to catch out-of-band file changes. Closed #196 won't-do with that rationale.
|
|
94
|
+
|
|
95
|
+
- **`lens_diagnostics` no longer lists findings the agent already fixed this session (read-your-writes, closes #180)** — `mode=all` reads the widget's per-file diagnostic state, which only refreshes a file when that file is re-dispatched. Because per-edit dispatches are **debounced** (flushed at `turn_end`), an agent that fixed files and then queried `lens_diagnostics` in the same turn saw the **pre-fix** diagnostics still pending in the debounce window. Now the tool **flushes pending dispatches before reporting** (`flushDebouncedToolResults`, injected) so just-fixed files are re-dispatched and reflected, and then **reconciles the live widget against the filesystem** (`reconcileStaleWidgetFiles`): entries whose file changed on disk after their diagnostics were recorded (`mtime > touchedAt`, e.g. an external edit) or that were deleted are dropped — and `mode=all` notes how many were omitted ("N changed files omitted as stale — use mode=full to rescan") so a changed-but-unscanned file reads as *stale*, not falsely clean. Cross-file staleness (a neighbor whose own content is unchanged but whose diagnostic an edit elsewhere invalidated) is a separate follow-up. Guarded by `tests/tools/lens-diagnostics.test.ts` (flush invoked, stale note) and `tests/clients/session-state-store.test.ts` (`reconcileStaleWidgetFiles` drops edited/deleted, keeps unchanged).
|
|
96
|
+
|
|
97
|
+
- **`rust-analyzer` no longer spawns one process per directory while scaffolding (closes #201 for Rust)** — `RustServer.root` was `RootWithFallback(RustWorkspaceRoot())`, whose default fallback is `FileDirRoot` (the file's own directory). Before a `Cargo.toml` exists, `RustWorkspaceRoot()` returns `undefined`, so every `.rs` file fell back to its own directory as the root — and since LSP clients dedup by `` `${serverId}:${root}` ``, each directory spawned a **separate `rust-analyzer`** (the active-LSP count climbed one-per-file during project creation, and each server was rooted at a manifest-less dir where rust-analyzer can't function). Dropped the fallback for Rust: no `Cargo.toml` ⇒ `undefined` ⇒ the server is skipped (no spawn) until a manifest gives a stable, shared crate root, after which all files share one server. The with-manifest behavior is unchanged. (C# `csharp-ls` has the same fallback trap but a compounding bug — `createRootDetector` matches markers by exact filename, so `.csproj` never matches a real `Foo.csproj` and C# currently depends on the fallback entirely; fixing it needs extension/glob marker support, tracked on #201.)
|
|
98
|
+
|
|
99
|
+
- **Read-guard autopatch now tolerates mid-block blank-line drift in `oldText` (Tier A of #200)** — when an agent's `Edit` `oldText` differed from the file only by a blank line added/removed *inside* the block, the autopatch's fixed-length window matchers couldn't bridge it (any interior blank-line delta breaks 1:1 line alignment), so the edit failed `oldtext_not_found` and the agent had to re-read/retry. A new blank-line-insensitive matcher (`findBlankLineInsensitiveCandidate`) matches the `oldText`'s non-blank lines (indentation-insensitive) against consecutive content, skipping interior blanks, and — critically — **recovers and returns the real file span verbatim** so the applied `oldText` is actual file bytes. Safety-gated: anchored on ≥2 non-blank lines, requires the signature to match **exactly once** (refuses on 0 or ≥2), and inherits the caller's existing `correctedMatchCount === 1` check — it prefers a no-patch over ever patching the wrong span. Internal-whitespace tolerance (string-literal-sensitive, riskier) remains tracked as Tier B on #200.
|
|
100
|
+
|
|
101
|
+
- **Tests can no longer silently run against a stale in-place build (closes #198)** — `npm run build` emits compiled `.js` next to each `.ts`, and vitest resolves a test's `.js` import specifier to that literal compiled file. Editing a source `.ts` and running the suite without rebuilding therefore exercised the *previous* build — the change was silently untested while `npm run lint` (which type-checks the `.ts`) stayed green. A vitest `globalSetup` (`tests/support/check-build-freshness.ts`) now fails fast — for any launch, including a direct `npx vitest run` that a `pretest` hook would miss — when a compiled-source `.ts` under `clients/`/`commands/`/`tools/` (or root `index.ts`/`i18n.ts`) is newer than its `.js` or has none, with an actionable `⛔ Stale build … run npm run build` message. The detection logic is unit-tested against a temp fixture (`tests/build-freshness-guard.test.ts`). This is the guard for the gotcha that nearly mis-calibrated the cascade occupancy test.
|
|
102
|
+
|
|
103
|
+
- **LSP workspace-diagnostics + warm-path FS calls no longer block the event loop (perf hardening, follows #188/#191)** — four synchronous filesystem calls on LSP hot paths were converted to their async equivalents, all behavior-preserving: (1) `collectWorkspaceDiagnosticFiles` (the `lsp_diagnostics` project-wide enumeration) walked the tree with a non-yielding `readdirSync` recursion — **~44.5ms → 0.7ms** longest sync block at ~1,400 files, scaling linearly on monorepos — now an `fs.promises.readdir` yielding walk; (2) its per-file `readFileSync` worker reads → `await readFile`; (3) `handleNotifyOpen`'s document-open existence probe `existsSync` → `await access` (the `didChangeWatchedFiles` Created/Changed type is unchanged); (4) `isOnPath` (the runtime-install gate on the spawn fall-through) `spawnSync("where"/"which")` → the shared `isCommandAvailableAsync` (`safeSpawnAsync`, 5s timeout, same `status === 0` semantics) so a stalled finder can't freeze the loop. The spawn-dedup invariant (one in-flight launch per `serverId:root`) was verified already correct and left untouched. Guarded by `tests/clients/lsp/workspace-diagnostics-occupancy.test.ts`.
|
|
104
|
+
|
|
105
|
+
- **`lsp_diagnostics` cascade cleanup no longer stats files synchronously (perf hardening, partial #197)** — `LSPService.getAllDiagnostics` (the cascade-checking path) pruned tracked diagnostics with a blocking `existsSync` per file *inside* the prune predicate, holding the event loop across every tracked file. Existence is now resolved in an async pre-pass (`fs.promises.access`, concurrent) and pruning stays a synchronous in-memory map operation — same semantics (a file is pruned iff it's missing **or** older than the cascade TTL), via a new `client.getTrackedDiagnosticPaths()`. Guarded by `tests/clients/lsp/get-all-diagnostics-prune.test.ts`. The remaining sync calls under #197 (the `go`/`dotnet`/`gem` install `spawnSync` and the single-shot `launch.ts`/root-detection stats) are deliberately left: they run once per tool/launch, off the typing window, and the install conversion needs equivalence testing of real install side-effects + reconciling `safeSpawnAsync`'s forced `shell` / timeout against the install commands' `shell:false`.
|
|
106
|
+
|
|
107
|
+
## [3.8.50] - 2026-06-07
|
|
108
|
+
|
|
109
|
+
### Added
|
|
110
|
+
|
|
111
|
+
- **Function-level call graph + impact analysis (closes #154)** — a cross-file call graph is built at session-start (ref→def resolution, bidirectional callers/callees, in-degree centrality, ambiguity-discounted edges); at turn-end the symbols a modified file touches surface a `WillBreak`/`MayBreak`/`Review` impact advisory. Backed by `import-facts` extended to JS/JSX/MJS/CJS with dynamic imports, module-type detection and re-export edges, and a `review-graph` whose `MAIN_KINDS`/language mapping spans every WASM-backed grammar.
|
|
112
|
+
|
|
113
|
+
- **Internal codebase mental model (closes #155)** — a compact structural summary ranked by call-graph in-degree, cached to `<project-data>/cache/codebase-model.json`. Internal-only (a session-start debug line) until validated across real sessions; agent exposure + hybrid ranking are tracked in #162.
|
|
114
|
+
|
|
115
|
+
- **`lens_diagnostics` tool (closes #159)** — queries pi-lens's cached diagnostic state with no LSP/dispatch re-run. `mode=delta` = the current turn's fixable + code-quality warnings; `mode=all` = every file edited this session.
|
|
116
|
+
|
|
117
|
+
- **`ast_grep_search` results register as reads so a follow-up edit isn't blocked (refs #169)** — the search→edit flow (find where something must change, then edit those lines) was blocked by the read-guard because the search didn't count as a read. `ast_grep_search` now attaches the shown match locations to its result (`details.searchReads`), and the tool_result handler registers each as a read **± 2 lines** of context via the new `clients/search-read-registration.ts`. Only the shown lines are registered — never the whole file — so editing an unseen region is still guarded. (`lsp_navigation` and bash `grep` are the remaining parts of #169.)
|
|
118
|
+
|
|
119
|
+
- **Disable automatic context injection without disabling pi-lens (closes #165)** — a narrow opt-out for the prompt-cache cost of prepending automatic findings. `--no-lens-context` flag, `contextInjection.enabled: false` in `~/.pi-lens/config.json`, `PI_LENS_NO_CONTEXT_INJECTION=1` env, and a runtime `/lens-context-toggle` command. When off, the `context` hook stops prepending session-start guidance / turn-end findings / test findings, but everything else keeps running — tools, LSP, read-guard, formatting, inline tool-result feedback — and findings are still cached so `lens_diagnostics` and `/lens-health` work. Precedence: env → CLI flag → config.
|
|
120
|
+
|
|
121
|
+
### Fixed
|
|
122
|
+
|
|
123
|
+
- **Read-guard tracks non-Read file access (closes #168, refs #169)** — bash file views (`cat`/`head`/`tail`/`sed -n`) register as reads with their exact line ranges; bash writes (`>`/`>>`/`tee`/`sed -i`/`cp`/`mv`/`touch`) register as authored-by-agent like the Write tool; search-tool matches register the shown lines ±2 context. So a follow-up edit to something the agent viewed, wrote, or searched is no longer falsely blocked. `grep`/`find`/`ls` are not treated as content reads.
|
|
124
|
+
|
|
125
|
+
- **Bash-written files are re-analyzed (no more stale diagnostics after `git checkout`/`git restore`)** — a bash command that rewrites working-tree content (redirects, `tee`, `sed -i`, `cp`/`mv`, `touch`, and now `git checkout -- <file>` / `git restore <file>`) never went through the edit-tool pipeline, so its diagnostics, `fileSeq`, and change-log stayed frozen at the pre-write state — e.g. restoring a file would keep reporting the old broken-state warnings on every later `lens_diagnostics` call. Each in-project file a bash command writes/restores is now re-run through the dispatch pipeline (via a synthetic write) so its analysis refreshes. Whole-tree git ops (`reset --hard`, `stash pop`, `revert`, branch switches) don't name files and aren't covered.
|
|
126
|
+
|
|
127
|
+
- **`LSP Inactive` footer status no longer rendered in red (closes #167)** — having no LSP server running for the current file (or after the idle timer releases them) is a passive state, not a fault, but it was painted in the `error` (red) color, implying something was broken. It now uses the neutral `dim` (grey) color; `LSP Active (n)` stays green. Surfacing genuine LSP *failures* in red is tracked in #170.
|
|
128
|
+
|
|
129
|
+
- **Extension load no longer requires the host coding-agent package in `node_modules`** — `index.ts` and `clients/read-guard-tool-lines.ts` imported a *runtime* value (`isToolCallEventType`) from `@earendil-works/pi-coding-agent`. pi installs extension deps with `npm install --omit=dev`, so that package isn't present at runtime; and pulling it in drags a huge transitive tree (LLM provider SDKs) whose deeply nested paths exceed Windows' `MAX_PATH`, breaking `git clean -fdx` on `pi update` (→ a half-deleted `node_modules` → `Cannot find module 'vscode-jsonrpc/node.js'`). The one-line discriminant is now inlined in `clients/tool-event.ts`, so every `@earendil-works/pi-coding-agent` import is type-only (erased at runtime) — matching the established pi-extension pattern (e.g. `nicobailon/pi-subagents`).
|
|
130
|
+
|
|
131
|
+
- **`js-yaml` moved from `devDependencies` to `dependencies`** — `clients/ast-grep-yaml-synth.ts` imports it at runtime, but it was declared dev-only, so a production (`--omit=dev`) install left it missing and the extension failed to load with `Cannot find package 'js-yaml'`. (`@types/js-yaml` stays dev-only.) The CI install-test (production tarball install + `tsx` load) now exercises this path so misplaced runtime deps are caught before release.
|
|
132
|
+
|
|
133
|
+
- **Lockfile kept committed and guarded against drift** — `package-lock.json` had silently drifted from `package.json` (the exact `web-tree-sitter` pin was recorded as `^0.25.10` in the lock), which makes `npm ci` delete `node_modules` then hard-fail. The lock is now regenerated in sync, and a new `npm run check:lockfile` guard (run in CI) fails the build if any declared dependency spec diverges from the lock — so the drift that started this can't recur. CI/release also switched from `npm ci` to `npm install` so a future desync degrades (self-heals) instead of hard-failing.
|
|
134
|
+
|
|
135
|
+
### Changed
|
|
136
|
+
|
|
137
|
+
- **`lens_diagnostics` mode=all now shows the actual diagnostics, not just counts, and is no longer limited by the TUI's display cap** — previously it printed `file.ts 3W` with no indication of *what* the warnings were. It now lists each diagnostic in the same `L<line>: <message> [rule]` shape as the inline blocker output (blockers first, 🔴-marked), honouring the `severity` filter. The widget state keeps a separate **uncapped** per-file diagnostic list for the tool (the TUI still uses its 12-entry render cap), so `getFileDiagnosticSummaries()` exposes the **full** set instead of just the 12 the widget retained for rendering. The tool applies its own generous 50-per-file budget with an accurate `… N more in this file (showing 50 of N)` note (the old note double-counted via `blocking + errors + warnings`).
|
|
138
|
+
|
|
139
|
+
### Added
|
|
140
|
+
|
|
141
|
+
- **Six new structural rules covering SonarCloud BLOCKER/CRITICAL TS gaps** — pure-AST checks (no taint analysis required), each with tests run through the production runner. ast-grep: `no-sort-without-comparator` (S2871 — `.sort()`/`.toSorted()` with no compare function), `no-octal-literal` (S1314 — legacy leading-zero octals), `no-mutable-export` (S6861 — exported `let`/`var`), `switch-without-default` (S131 — `switch` with no `default` clause). tree-sitter: `no-equality-in-for-condition` (S888 — `==`/`!=` as a `for`-loop exit test), `no-jump-in-finally` (S1143 — `return`/`break`/`continue`/`throw` written directly in a `finally` block). All `warning` severity.
|
|
142
|
+
|
|
143
|
+
- **`redos-nested-quantifier` ast-grep rule — flags catastrophic-backtracking (ReDoS) regex literals** — detects an unbounded quantifier nested inside an unbounded-quantified group (`(a+)+`, `(a*)*`, `([a-z]+)*`, `(\d+){2,}`, `(a{2,})+`), the classic CWE-1333 / S5852 exponential case. Fires only when both inner and outer quantifiers are unbounded (`+`, `*`, `{n,}`); bounded quantifiers like `{2,3}` are intentionally not flagged. Runs in the NAPI runner via `kind: regex_pattern` + a linear detector regex (no self-ReDoS). `warning` severity with fix guidance (bounded quantifier, atomic-group emulation, negated character class, or RE2/node-re2 for untrusted input).
|
|
144
|
+
|
|
145
|
+
- Extended oxfmt formatter to CSS, SCSS, Less, HTML, JSON, YAML, Markdown, MDX, GraphQL, TOML, Vue files. Updated tool-policy entries and added unit tests.
|
|
146
|
+
|
|
147
|
+
- **`ast_grep_search` / `ast_grep_replace` structural-intent parameters — `insideKind`, `hasKind`, `follows`, `precedes` (closes #125 Phase 3)** — agents can now express cross-context queries without writing YAML. `insideKind: "function_declaration"` restricts matches to nodes inside that ancestor kind (searches all ancestors via `stopBy: end`); `hasKind` restricts to nodes containing a descendant; `follows`/`precedes` restrict by sibling pattern. Parameters synthesize a YAML rule via `clients/ast-grep-yaml-synth.ts` and route through `sg scan --config`. For `ast_grep_replace`, a `fix:` field is added to the synthesized rule so `sg scan --update-all` applies the rewrite. When `rule:` (Phase 4) is also provided, it takes precedence. 22 new tests covering synthesizer output, constraint combinations, language canonicalisation, routing, and YAML content assertions.
|
|
148
|
+
|
|
149
|
+
- **`ast_grep_search` raw YAML rule passthrough — `rule` parameter (closes #125 Phase 4)** — passing a complete ast-grep YAML rule bypasses `sg run -p` entirely and routes through `sg scan --config`, unlocking `all`/`any`/`not`, `nthChild`, `regex`, field constraints, and multi-pattern rules. Each path is scanned independently and results are merged. Pagination (`skip`) works the same as the pattern path.
|
|
150
|
+
|
|
151
|
+
- **`ast_grep_search` and `ast_grep_replace` metavariable captures in output (refs #125)** — named captures (`$VAR`, `$$$ARGS`) from `sg --json=compact` appear below each match. Language field (`[TypeScript]`) surfaced per match.
|
|
152
|
+
- **SgRunner binary resolution extended with platform package and Homebrew fallback (refs #153)** — probes `@ast-grep/cli-{os}-{arch}` npm packages (walking up 5 directory levels) and Homebrew (`brew --prefix ast-grep`) before falling back to auto-install.
|
|
153
|
+
|
|
154
|
+
- **Read expansion ancestry chain (refs #153)** — `ExpandedRead` now includes `ancestry?: AncestorSymbol[]` (outermost first) so the full structural path is available (e.g. `ReviewManager → runSynthesis`). The session-start debug log now shows the full path instead of just the immediate enclosing symbol.
|
|
155
|
+
|
|
156
|
+
### Fixed
|
|
157
|
+
|
|
158
|
+
- **Windows subprocess encoding (garbled tool output)** — `safeSpawnAsync` prefixes Windows shell commands with `chcp 65001 >nul 2>&1 &&` to force UTF-8 code page, eliminating garbled characters in `sg`/`biome`/`ruff` error messages.
|
|
159
|
+
|
|
160
|
+
- **Thrashing warning scoped to same tool+file pair** — consecutive counter resets when either the tool name or the file path changes; editing different files no longer triggers the warning.
|
|
161
|
+
|
|
162
|
+
- **Regex S5852 backtracking eliminated** — replaced `(.*?)` with `([^(]*)` and `/\r?\n/` with `/\r\n|\n/` in ast-grep-client and lsp-navigation.
|
|
163
|
+
|
|
164
|
+
- **`@earendil-works/pi-coding-agent` declared as optional peer dependency** — `devDependencies` retains the explicit version for local dev; install test updated to exclude host-provided peer from the `ERR_MODULE_NOT_FOUND` gate.
|
|
165
|
+
|
|
166
|
+
### Performance
|
|
167
|
+
|
|
168
|
+
- **Read expansion limit raised from 60 to 100 lines** — expansion now fires for reads up to 100 lines, making it useful for the typical 80-100 line agent reads that previously fell outside the threshold.
|
|
169
|
+
|
|
170
|
+
## [3.8.48] - 2026-06-05
|
|
171
|
+
|
|
172
|
+
### Added
|
|
173
|
+
|
|
174
|
+
- **`ast_dump` tool — expose tree-sitter AST structure for pattern debugging (closes #156)** — new `ast_dump` tool parses a source snippet with `sg --debug-query=ast|cst` and returns an indented AST tree with 1-indexed line:col positions and source snippets per node. Named nodes only by default; `includeAnonymous: true` shows all CST nodes including punctuation. Use this when `ast_grep_search` returns zero matches and the correct node kind or field name is unknown. Invalid language returns a clear error; partial/error trees are returned as-is so syntax errors are visible.
|
|
175
|
+
|
|
176
|
+
- **`lsp_navigation` `rename_file` operation — LSP-aware source file rename (closes #148)** — new `rename_file` operation sends `workspace/willRenameFiles` to all active LSP servers, collects and deduplicates returned workspace edits (primary type-checker server wins on range conflicts), renames the file on disk, sends `workspace/didRenameFiles`, then re-syncs touched files in LSP. Preview mode (`apply: false`) shows the merged workspace edits without touching disk. Overlap detection across server edit sets throws a descriptive error rather than producing corrupted output.
|
|
177
|
+
|
|
178
|
+
- **`lsp_navigation` `capabilities` operation — cached server feature map (closes #149)** — new operation reads `serverCapabilities` from the post-`initialize` cached state and renders a per-server table of which `lsp_navigation` operations are actually supported (definition, references, hover, rename, codeAction, workspaceSymbol, implementation, signatureHelp, callHierarchy, workspaceDiagnostics, rename_file). No LSP round-trip. Scoped to a specific file or all active servers when `filePath` is omitted.
|
|
179
|
+
|
|
180
|
+
- **`lsp_navigation` symbol-to-column resolution (closes #147)** — omitting `character` and supplying `symbol` resolves the correct column automatically by scanning the target line. Full fallback chain: word-boundary regex match → same with `#N` occurrence selector (`symbol: "foo#2"` = second occurrence) → case-insensitive match → first non-whitespace character. Eliminates the dominant class of position-mismatch retries where the agent knew the line but guessed the column wrong.
|
|
181
|
+
|
|
182
|
+
- **`ast_grep_replace` stale-preview detection, `ast_grep_search` pagination, and strictness parameter (closes #151)** — three improvements to the ast-grep tools. (1) Before applying (`apply: true`), a dry-run re-validates that the pattern still matches; if files changed since the preview, returns a `stalePreview` error rather than applying against wrong content. (2) `ast_grep_search` accepts `skip: N` to offset into large result sets; truncated results include a "Use skip=50 for the next page" hint. (3) Both tools accept `strictness: "smart" | "relaxed" | "ast" | "cst" | "signature" | "template"` passed to `sg --strictness`; `"relaxed"` is the most useful for patterns that miss matches due to optional trailing commas or semicolons.
|
|
183
|
+
|
|
184
|
+
- **`ast_grep_search` and `ast_grep_replace` surface metavariable captures (refs #125)** — named captures (`$VAR`, `$$$ARGS`) from `sg --json=compact` output are now shown below each match: `$VAR=x $VALUE=foo(a, b, c)` and `$$$ARGS=a,b,c`. Unnamed wildcards (`$$$` without a name) produce no extra line. Both `SgMatch` and `AstGrepMatch` interfaces include the full `metaVariables` payload for downstream consumers.
|
|
185
|
+
|
|
186
|
+
- **tree-sitter WASM coverage expanded from 13 to 26 languages (refs #152)** — `scripts/download-grammars.ts` now downloads bash, c_sharp, css, html, json, lua, ocaml, php, swift, toml, vue, yaml, zig from `tree-sitter-wasms` at install time. All 13 new grammars registered in `TreeSitterClient.LANG_MAP`.
|
|
187
|
+
|
|
188
|
+
- **C#, PHP, and CSS tree-sitter dispatch rules now active (refs #152)** — the three languages had existing `.scm` rule files that silently never fired because no WASM was loaded and they were absent from the rules runner's `EXT_TO_LANG` / `appliesTo`. Both gaps closed. PL/SQL (9 rules), ABAP (1 rule), and COBOL (2 rules) moved to `-disabled/` subdirectories — no standard tree-sitter WASM exists for these grammars so the rules could not execute.
|
|
189
|
+
|
|
190
|
+
- **Read expansion and symbol extraction extended to 9 more languages (refs #152)** — `clients/read-expansion.ts` `EXT_TO_LANG` / `ENCLOSING_TYPES` and `clients/tree-sitter-symbol-extractor.ts` `SYMBOL_QUERIES` wired for Java, Kotlin, Dart, Elixir, C, C++ (read expansion + symbols) and C#, PHP, Swift, Lua, OCaml, Zig, Bash (symbols). All use WASMs already downloaded by the grammar expansion above. Node-type names verified against each language's `node-types.json` before use.
|
|
191
|
+
|
|
192
|
+
- **Tool registration collision guard (closes #106)** — all four `pi.registerTool()` calls in `index.ts` are now wrapped in try/catch. When another extension (e.g. `@narumitw/pi-lsp`) has already registered the same tool name, the collision is caught silently instead of aborting pi-lens extension load.
|
|
193
|
+
|
|
194
|
+
- **gitleaks runner for cross-language committed-secret detection (closes #130)** — new `clients/gitleaks-client.ts` runs `gitleaks detect --no-git --source <root> --report-format json` at session_start when the project root has any opt-in signal: `.gitleaks.toml` / `.gitleaks.yaml` / `.gitleaks.yml` / `.gitleaksignore`, a `gitleaks`-substring dependency in `package.json`, or a `.husky/` or `.git/hooks/` pre-commit hook referencing gitleaks. Cross-language by design (operates on bytes via regex + entropy, not AST), so a single binary covers every repo we support. Auto-installs from GitHub releases via the existing installer pattern (same shape as `actionlint` / `hadolint` / `tflint` — registered entry at `clients/installer/index.ts`). At turn_end, the cached findings surface as a **blocker** (not advisory) — committed credentials are real production risk and need rotation before merge; the block lists up to 5 findings as `path:line — RULE-ID: description`. Parser handles gitleaks's standard JSON-array report shape with 19 unit tests covering all six opt-in signals, malformed JSON tolerance, missing-required-field skipping (rather than crashing), and lenient coercion of stringified `StartLine` values. Client lifecycle mirrors `KnipClient` / `JscpdClient` / `GovulncheckClient` (in-flight dedupe, off-main-thread session_start invocation via the existing `runTask(setImmediate)` wrapper). Per-edit re-scan is intentionally NOT wired — secrets either are or aren't in a file; the session_start cache is the authoritative source.
|
|
195
|
+
|
|
196
|
+
- **govulncheck runner for reachable Go CVE detection (closes #132)** — new `clients/govulncheck-client.ts` runs `govulncheck -mode=source -format=json ./...` at session_start when the analysis root contains a `go.mod`. Caches results by project root via `cacheManager.writeCache("govulncheck", ...)`. The advisory surfaces at turn_end via a single `🛡️ Go CVEs reachable from this code` block listing up to 5 findings with `OSV-ID (file:line) — upgrade to vX.Y.Z`, complementary to (not redundant with) trivy: govulncheck reports only CVEs whose vulnerable function is actually called from the build graph, dramatically lower false-positive rate vs. flat dep-CVE scanning. **Auto-installs via `go install golang.org/x/vuln/cmd/govulncheck@latest`** when missing — the `hasGoModule(analysisRoot)` gate guarantees the Go toolchain is available, so leaning on `go install` is honest (same pattern as how rust-clippy works on cargo projects). Falls back to `$GOBIN` / `$GOPATH/bin` / `~/go/bin` lookup when the installed binary isn't on `PATH`. Parser handles govulncheck's informal JSON stream (newline-delimited dominant case, concatenated multi-object lines, malformed-prefix tolerance) with 7 unit tests; client lifecycle mirrors `KnipClient` / `JscpdClient` (in-flight dedupe, off-main-thread session_start invocation via the existing `runTask(setImmediate)` wrapper).
|
|
197
|
+
|
|
198
|
+
- **Rolling actionable-warnings history** — every actionable warning surfaced at `turn_end` is now appended to `<project-data>/actionable-warnings.jsonl`, parallel to the existing `code-quality-warnings.jsonl`. Captures the fields `worklog.jsonl` drops: stable `aw:<hash>` ID for cross-turn correlation, suppression state, LSP code-action enrichment counts, and origin (dispatch / lsp / merged). Empty reports skip the write. Closes the symmetry gap where code-quality warnings persisted across turns/sessions but actionable warnings did not.
|
|
199
|
+
- **NDJSON telemetry for `ast_grep_search` / `ast_grep_replace`** — every invocation of the two agent-facing ast-grep tools now writes a record to `~/.pi-lens/ast-grep-tools.log` capturing pattern (truncated to 500 chars), `patternLineCount` (so single-line vs multi-line analyses are trivial), lang, outcome (`success` / `no_matches` / `error`), and a classified `errorKind` (`multiple_ast_nodes`, `cannot_parse_query`, `tool_not_found`, `timeout`, `json_parse_failed`, `other`). Rotates at 1 MiB. `classifyAstGrepError` recognises both sg-runner's friendly wrappers and the raw underlying stderr, case-insensitive. The data answers: how often do agents hit multi-statement failures? Which language emits which error most? Do retries succeed after the skill is read?
|
|
200
|
+
|
|
201
|
+
### Performance
|
|
202
|
+
|
|
203
|
+
- **Actionable-warnings turn-end report reuses dispatch-primed LSP diagnostics** — `buildActionableWarningsReport` was running its own LSP `openFile` + `getDiagnostics` loop per modified file, even though the dispatch pipeline had already run `touchFile` (open + diagnostics-wait + merge) for every modified file earlier in the same turn. The LSP service caches in `lastKnownDiagnostics`, but `getDiagnostics` ignored the cache and always re-spawned clients. New `LspService.getLastKnownDiagnostics(filePath)` returns the cached value without a re-fetch, distinguishing `[]` (cache-hit empty) from `undefined` (cache miss). actionable-warnings checks the cache first and falls through to the slow path only on a true miss. Latency log analysis showed reports >2 s on zero-warning turns dropping from common (63 of 733 in one rotation) to the sub-100 ms floor. `lsp_file_checked` NDJSON gains a `lspSource: "cache" | "fresh"` field so the cache-hit ratio is observable.
|
|
204
|
+
|
|
205
|
+
### Fixed
|
|
206
|
+
|
|
207
|
+
- **`oldtext_not_found` messages distinguish content-drift from indentation mismatch (refs #144)** — when the first line of `oldText` is found in the file but the surrounding block no longer matches, the error now explicitly states this is a content-drift failure (not an indentation issue) and that indentation autopatch already ran. Previously both cases produced a generic re-read message; agents wasted retries changing tabs to spaces when the real problem was a 60-line content drift from earlier edits in the same session.
|
|
208
|
+
|
|
209
|
+
- **LSP diagnostics version guard prevents stale results (refs #150)** — `waitForDiagnostics` now captures a `diagnosticsVersion` baseline immediately before `refreshFile`. Only accepts results when `diagnosticsVersion > baseline`, ensuring a fresh `publishDiagnostics` arrived after the sync. Eliminates false-clean results after rapid sequential edits where the server was still processing an earlier file state.
|
|
210
|
+
|
|
211
|
+
- **Lazy `codeAction/resolve` before applying code actions (refs #150)** — many LSP servers (rust-analyzer, typescript-language-server) return lightweight code action objects with no `edit` field, only populating it on an explicit `codeAction/resolve` request. Pi-lens now resolves lightweight actions before applying; falls back silently if the server does not support `resolveSupport`.
|
|
212
|
+
|
|
213
|
+
- **Workspace symbol deduplication (refs #150)** — workspace symbol results deduplicated by `name:containerName:kind:uri:startLine:startCol` before returning. Prevents duplicate entries when multiple LSP servers are active for the same file.
|
|
214
|
+
|
|
215
|
+
- **Diagnostic noise stripping (refs #150)** — "for further information visit `<url>`" lines and bare URL-only lines stripped from LSP diagnostic messages before they surface in dispatch output. Reduces noise from rust-analyzer/clippy and other servers that embed documentation URLs inline.
|
|
216
|
+
|
|
217
|
+
- **Workspace edit ordering and overlap detection (refs #150)** — `applyWorkspaceEdit` now flushes all text edits to disk before processing resource operations (create/rename/delete), preventing a rename from moving a file before its content is updated. Overlapping text edit ranges within a single server's response now throw a descriptive error (`"overlapping LSP edits: X conflicts with Y"`) rather than producing corrupted output.
|
|
218
|
+
|
|
219
|
+
- **README `PILENS_DATA_DIR` description corrected (closes #142)** — the previous description stated the default write location was `<cwd>/.pi-lens/`, which is only true for legacy projects that already have that directory. New installs have always defaulted to `~/.pi-lens/projects/<slug>/`. Added a callout for local model server users (llama.cpp, Ollama) noting that cache-file churn inside the workspace disrupts model context scoring and `PILENS_DATA_DIR` is the fix.
|
|
220
|
+
|
|
221
|
+
- **ast-grep SKILL.md documents `Multiple AST nodes are detected` failure modes (refs #125 Phase 1)** — added a new gotcha entry covering the two distinct shapes: (1) sequence-in-block — wrap in `{ }` to make it one AST node; (2) cross-context (module-level + block-level in the same pattern) — wrapping is invalid, use two scoped searches or a YAML `inside:`/`has:` rule instead.
|
|
222
|
+
|
|
223
|
+
- **Widget stop warning storm churn (PR #146)** — `widget-state.ts` now tracks whether each file has received a final diagnostics snapshot (`hasFinalDiagnosticsSnapshot`). The `✓ clean` header is suppressed while any file is pending, and pending files are excluded from the file row list until diagnostics land. Prevents the transient `✓ clean` flash observed on warning-heavy analysis passes in C++ and other multi-runner languages. Stored diagnostics per file capped at 12 while preserving full warning counts in `diagnosticCounts`.
|
|
224
|
+
|
|
225
|
+
- **jscpd clone detection now runs on non-JS/TS projects, and excludes compiled `dist/` from TS-project scans (closes #126)** — the source-file gate at `JscpdClient.hasSourceFilesRecursive` accepted only JS/TS extensions (commit 8b5d588), making pi-lens's jscpd integration effectively JS/TS-only even though jscpd's underlying tokenizer covers 15+ languages. Pure-Python, pure-Go, pure-Rust, pure-Java, etc. repos got zero clone detection. The gate now recognises every language jscpd tokenizes well: Python, Java, Go, Rust, Ruby, PHP, Swift, Kotlin, Dart, Lua, Scala, C/C++, C#, plus the existing JS/TS set. Gleam / Zig / Fish stay excluded — jscpd has no tokenizer for them. Separately, the session_start call site now auto-detects `isTsProject` via the presence of `tsconfig.json` and passes it to `scan()`, so TS projects with a `dist/` directory of compiled `.js` artifacts no longer flag them as duplicates of their `.ts` sources. The cache scanner key varies by this flag (`"jscpd"` vs `"jscpd-ts"`) so a stale pre-#126 cache invalidates on first read instead of masking the fix.
|
|
226
|
+
|
|
227
|
+
*Behaviour note*: a previously-skipped pure-Python / Go / Rust / Java repo now runs a real jscpd scan at session_start (seconds, scaling with file count). The scan is off the main thread via the existing `setImmediate` runTask wrapper, so the TUI is not blocked, and the result caches for subsequent sessions.
|
|
228
|
+
|
|
229
|
+
- **Read-guard autopatch now registers a synthetic read for the matched line range** — a successful unique-match indent or trailing-ws autopatch (`oldtext_indent_autopatched` / `oldtext_trailing_ws_autopatched`) proves the agent's `oldText` reflects real content at a unique span. Two systems used to disagree about this: the autopatch successfully matched, and 4–5 ms later the read-guard fired `zero_read` because no Read tool event existed for that file. Now the autopatch path registers a synthetic read covering the matched range via `runtime.readGuard.recordRead`, so the downstream guard check has the evidence it needs. Doesn't bypass `file_modified` (orthogonal) or widen coverage beyond the matched span. Fixes the observed pattern of autopatch-then-block on `model-selector.{ts,test.ts}` and any similar future cases.
|
|
230
|
+
|
|
231
|
+
### Removed
|
|
232
|
+
|
|
233
|
+
- **Deleted the regex-based `type-safety` runner** — three regex heuristics on raw source text (switch exhaustiveness without `default`, missing `return` in functions with non-void return type, `: any` / `as any`). All three checks are covered better — with real type information — by tools already in the dispatch pipeline: TypeScript LSP catches missing returns with proper control-flow analysis; Biome `noExplicitAny` and ESLint `@typescript-eslint/no-explicit-any` catch `any` usage; ESLint `@typescript-eslint/switch-exhaustiveness-check` is discriminant-type-aware. The regex `:\s*any\b` also matched identifiers like `anything`, `Many`, `Company`, comments, and strings — producing the dominant `type-safety:no-any-type` rule (244 of 404 entries in pi-drykiss's rolling history) with mostly false positives. Other typed languages need no equivalent: we already run their actual compilers / analyzers (pyright + mypy, go-vet + golangci-lint, rust-clippy, javac, cpp-check, dotnet-build, dart-analyze, phpstan, detekt, swiftlint, etc.). The orphan `clients/type-safety-client.ts` (a separate AST-based implementation with zero callers) was deleted alongside.
|
|
234
|
+
- **Deleted the state-matrix similarity infrastructure** — the 57×72 AST-kind transition matrix algorithm (`clients/state-matrix.ts`, `clients/amain-types.ts`, `clients/project-index.ts`) and all three of its consumers: the dispatch `similarity` runner, lens-booboo's "Runner 3: semantic similarity (Amain)" all-pairs comparison, and the `index.ts` Phase 7b pre-write inline check. The algorithm captured AST-kind shape distribution — not identifiers, control-flow ordering, data flow, function size, or imports. Two functions with the same kind distribution (e.g. all test functions, all map/filter chains, all early-return guards) scored ~1.0 cosine similarity despite doing completely different things. At the 0.98 threshold all three consumers produced zero observable output across 567 history entries in three active projects; at lower thresholds (~0.95) the same algorithm produced false-positive floods on idiom-shaped code. Refs #128 for the design intent of the eventual rewrite as AST-subtree fingerprinting with review-graph import-overlap gating. booboo's other similarity flow via `clients.astGrep.findSimilarFunctions` is preserved. Session-start cost drops by ~395 ms run + 212 ms queued (the index build/load task is gone).
|
|
235
|
+
- **Session_start `project-index` task** — built or loaded the now-deleted state-matrix index on every session start. Pure dead cost without the algorithm; removed.
|
|
6
236
|
|
|
7
237
|
## [3.8.47] - 2026-06-01
|
|
8
238
|
|
package/README.md
CHANGED
|
@@ -88,7 +88,12 @@ pi-lens includes **37 language server definitions**. LSP is **enabled by default
|
|
|
88
88
|
{ "warmFiles": ["src/main.cpp", "src/lib.cpp"] }
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
**Agent LSP tools:** `lsp_diagnostics` can check one file, a directory, or an explicit `filePaths` batch with bounded concurrency. `lsp_navigation` provides definitions, references, hover, workspace symbols, call hierarchy, rename edits, and `findSymbol` for filtered document-symbol lookup.
|
|
91
|
+
**Agent LSP tools:** `lsp_diagnostics` can check one file, a directory, or an explicit `filePaths` batch with bounded concurrency. `lsp_navigation` provides definitions, references, hover, workspace symbols, call hierarchy, rename edits, and `findSymbol` for filtered document-symbol lookup. Key operations:
|
|
92
|
+
|
|
93
|
+
- **`rename`** — renames a symbol across all references; `apply: true` writes workspace edits to disk with per-file LSP re-sync.
|
|
94
|
+
- **`rename_file`** — LSP-aware file rename: sends `workspace/willRenameFiles` to collect import-path rewrites, applies them, renames the file on disk, and notifies servers via `workspace/didRenameFiles`. `apply: false` previews the workspace edits without touching the filesystem.
|
|
95
|
+
- **`capabilities`** — shows which operations are supported by the active LSP server(s) for a file, read directly from the cached `initialize` response (no round-trip).
|
|
96
|
+
- **Symbol column resolution** — passing `symbol: "myFunc"` instead of an exact `character` position resolves the correct column automatically. Use `symbol: "foo#2"` for the second occurrence of `foo` on the line.
|
|
92
97
|
|
|
93
98
|
LSP servers for: TypeScript, Deno, Python (pyright/basedpyright + jedi), Go, Rust, Ruby (ruby-lsp + solargraph), PHP, C# (omnisharp), F#, Java, Kotlin, Swift, Dart, Lua, C/C++, Zig, Haskell, Elixir, Gleam, OCaml, Clojure, Terraform, Nix, Bash, Docker, YAML, JSON, HTML, TOML, Prisma, Vue, Svelte, ESLint, CSS.
|
|
94
99
|
|
|
@@ -153,12 +158,22 @@ When `actionableWarnings.autoFix.enabled` is set in global config (or `--lens-ac
|
|
|
153
158
|
|
|
154
159
|
When the agent reads a small slice of a file (≤ 60 lines), pi-lens transparently expands the read to the full enclosing symbol (function, method, or class) using the tree-sitter AST. The agent receives the full symbol as context, and the read guard records symbol-level coverage so edits anywhere within that symbol pass without requiring the agent to have read every line individually. Expansion runs within a 200 ms budget and falls back silently on unsupported file types or parse failures.
|
|
155
160
|
|
|
156
|
-
Supported: TypeScript, TSX, JavaScript, JSX, Python, Go, Rust, Ruby.
|
|
161
|
+
Supported: TypeScript, TSX, JavaScript, JSX, Python, Go, Rust, Ruby, Java, Kotlin, Dart, Elixir, C, C++, C#, PHP, Swift, Lua, OCaml, Zig, Bash.
|
|
157
162
|
|
|
158
163
|
### Fact Rules Pipeline
|
|
159
164
|
|
|
160
165
|
Covers JavaScript/TypeScript, Python, Go, Rust, Ruby, Shell, and CMake. A TypeScript AST-based fact-rule engine extracts function-level metrics and evaluates quality and security rules inline. Blocking rules surface immediately at write time; advisory rules are available via `/lens-booboo`.
|
|
161
166
|
|
|
167
|
+
### AST Search and Replace
|
|
168
|
+
|
|
169
|
+
`ast_grep_search` and `ast_grep_replace` provide AST-aware pattern matching across 40+ languages via the `sg` CLI. Key capabilities:
|
|
170
|
+
|
|
171
|
+
- **Metavariable captures** — named captures (`$VAR`, `$$$ARGS`) appear below each match: `$VAR=x $$$ARGS=a,b,c`.
|
|
172
|
+
- **Strictness modes** — `strictness: "relaxed"` ignores optional punctuation (trailing commas, semicolons) that causes zero matches in `smart` mode. Also supports `ast`, `cst`, `signature`, `template`.
|
|
173
|
+
- **Pagination** — `skip: N` offsets into large result sets; truncated results include a next-page hint.
|
|
174
|
+
- **Stale-preview detection** — `ast_grep_replace` re-validates the pattern before writing; returns a clear error if files changed since the preview instead of applying against wrong content.
|
|
175
|
+
- **`ast_dump`** — dumps the full tree-sitter AST for a source snippet. Use this when a pattern returns zero matches and the correct node kind or field name is unknown.
|
|
176
|
+
|
|
162
177
|
### Tree-sitter Rules
|
|
163
178
|
|
|
164
179
|
Structural rules organized by language in `rules/tree-sitter-queries/<language>/`. Rules marked **🔴** block the agent inline at write time (only for lines in the current edit); others are advisory.
|
|
@@ -211,6 +226,7 @@ pi
|
|
|
211
226
|
|
|
212
227
|
# Optional switches
|
|
213
228
|
pi --no-lens # Start pi-lens disabled for this session; /lens-toggle can re-enable
|
|
229
|
+
pi --no-lens-context # Disable automatic context injection only (tools/LSP/read-guard/format stay on); /lens-context-toggle
|
|
214
230
|
pi --no-lsp # Disable unified LSP diagnostics
|
|
215
231
|
pi --no-autoformat # Skip auto-formatting entirely
|
|
216
232
|
pi --immediate-format # Format immediately after each edit instead of deferring to agent_end
|
|
@@ -245,29 +261,44 @@ Hide the diagnostics widget by default, run formatting immediately after write/e
|
|
|
245
261
|
"enabled": false,
|
|
246
262
|
"maxFixes": 5
|
|
247
263
|
}
|
|
264
|
+
},
|
|
265
|
+
"contextInjection": {
|
|
266
|
+
"enabled": false
|
|
248
267
|
}
|
|
249
268
|
}
|
|
250
269
|
```
|
|
251
270
|
|
|
252
271
|
`format.mode` can be `"deferred"` (default) or `"immediate"`. Set `format.enabled` to `false` to match `--no-autoformat`. `/lens-widget-toggle` still works as a session-only override.
|
|
253
272
|
|
|
273
|
+
`contextInjection.enabled` (default `true`) controls whether pi-lens prepends automatic findings — session-start guidance, turn-end findings, and test findings — into the next model turn. Set it to `false` (or use `--no-lens-context` / `PI_LENS_NO_CONTEXT_INJECTION=1` / `/lens-context-toggle`) to keep tools, LSP, read-guard, and formatting running while avoiding the prompt-cache invalidation that injected messages cause in long, cache-sensitive sessions. Findings are still cached, so `lens_diagnostics` and `/lens-health` keep working.
|
|
274
|
+
|
|
254
275
|
`actionableWarnings.enabled` gates the turn_end report. `includeLspCodeActions` fetches LSP code actions for each warning (requires an active language server). `deltaOnly` (default `true`) limits the report to lines touched in the current turn. `autoFix.enabled` applies conservative LSP quickfixes at `agent_end`; `autoFix.maxFixes` caps the number applied per turn (default `5`).
|
|
255
276
|
|
|
256
277
|
## Environment Variables
|
|
257
278
|
|
|
258
279
|
- `PILENS_DATA_DIR` — redirect per-project state (scanner caches,
|
|
259
|
-
turn-state.json)
|
|
260
|
-
|
|
261
|
-
`<
|
|
262
|
-
|
|
280
|
+
turn-state.json) to a base directory outside the project. By default
|
|
281
|
+
pi-lens writes to `~/.pi-lens/projects/<sanitized-cwd-slug>/`. The one
|
|
282
|
+
exception is a legacy `<cwd>/.pi-lens/` directory: if that already exists
|
|
283
|
+
in the project, pi-lens continues to use it. Set `PILENS_DATA_DIR` to
|
|
284
|
+
permanently override both cases and write to
|
|
285
|
+
`<PILENS_DATA_DIR>/<sanitized-cwd-slug>/` instead. Particularly useful
|
|
286
|
+
when running pi with a local model server (llama.cpp, Ollama, etc.) that
|
|
287
|
+
monitors the project directory — cache-file churn inside the workspace can
|
|
288
|
+
disrupt the model's context scoring. Tool binaries always live in
|
|
263
289
|
`~/.pi-lens/bin/` regardless.
|
|
264
290
|
- `PI_LENS_STARTUP_MODE` — `full` | `minimal` | `quick`. Override the
|
|
265
291
|
auto-selected startup path. One-shot `pi --print` sessions auto-use `quick`
|
|
266
292
|
to reduce latency.
|
|
293
|
+
- `PI_LENS_NO_CONTEXT_INJECTION` — set to `1` to disable automatic context
|
|
294
|
+
injection (equivalent to `--no-lens-context` / `contextInjection.enabled:
|
|
295
|
+
false`). Tools, LSP, read-guard, and formatting stay active; findings are
|
|
296
|
+
still cached for `lens_diagnostics` and `/lens-health`.
|
|
267
297
|
|
|
268
298
|
## Key Commands
|
|
269
299
|
|
|
270
300
|
- `/lens-toggle` — toggle pi-lens on/off for the current session without restarting
|
|
301
|
+
- `/lens-context-toggle` — toggle automatic context injection on/off for the session (tools/LSP/read-guard/formatting stay active)
|
|
271
302
|
- `/lens-widget-toggle` — show/hide the pi-lens diagnostics widget below the editor
|
|
272
303
|
- `/lens-booboo` — full quality report for current project state
|
|
273
304
|
- `/lens-health` — runtime health, latency, and diagnostic telemetry
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { isTestMode } from "./env-utils.js";
|
|
4
|
+
import { getGlobalPiLensDir } from "./file-utils.js";
|
|
5
|
+
const AW_LOG_DIR = getGlobalPiLensDir();
|
|
6
|
+
const AW_LOG_FILE = path.join(AW_LOG_DIR, "actionable-warnings.log");
|
|
7
|
+
const AW_LOG_BACKUP_FILE = path.join(AW_LOG_DIR, "actionable-warnings.log.1");
|
|
8
|
+
const MAX_LOG_BYTES = Math.max(128 * 1024, Number.parseInt(process.env.PI_LENS_AW_LOG_MAX_BYTES ?? "1048576", 10) || 1048576);
|
|
9
|
+
try {
|
|
10
|
+
if (!fs.existsSync(AW_LOG_DIR)) {
|
|
11
|
+
fs.mkdirSync(AW_LOG_DIR, { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
void err;
|
|
16
|
+
}
|
|
17
|
+
function rotateIfNeeded() {
|
|
18
|
+
try {
|
|
19
|
+
if (!fs.existsSync(AW_LOG_FILE))
|
|
20
|
+
return;
|
|
21
|
+
const size = fs.statSync(AW_LOG_FILE).size;
|
|
22
|
+
if (size < MAX_LOG_BYTES)
|
|
23
|
+
return;
|
|
24
|
+
try {
|
|
25
|
+
fs.rmSync(AW_LOG_BACKUP_FILE, { force: true });
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
void err;
|
|
29
|
+
}
|
|
30
|
+
fs.renameSync(AW_LOG_FILE, AW_LOG_BACKUP_FILE);
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
void err;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function logActionableWarningsEvent(entry) {
|
|
37
|
+
if (isTestMode()) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const line = `${JSON.stringify({ ts: new Date().toISOString(), ...entry })}\n`;
|
|
41
|
+
try {
|
|
42
|
+
rotateIfNeeded();
|
|
43
|
+
fs.appendFileSync(AW_LOG_FILE, line);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
void err;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function getActionableWarningsLogPath() {
|
|
50
|
+
return AW_LOG_FILE;
|
|
51
|
+
}
|