@herbertgao/pi-extensions 2026.8.4 → 2026.8.5
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/README.md +4 -4
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +10 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +10 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +24 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +91 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +7 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +21 -12
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +4 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +5 -8
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +22 -12
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +2 -4
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +8 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +8 -4
- package/node_modules/@herbertgao/pi-subagents/src/agent-color.ts +72 -67
- package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +255 -0
- package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +191 -3
- package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +15 -2
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +289 -113
- package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +24 -1
- package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +24 -2
- package/node_modules/@herbertgao/pi-subagents/src/ui/schedule-menu.ts +9 -8
- package/node_modules/@herbertgao/pi-subagents/src/ui/select-item.ts +48 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +7 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +7 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +16 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +4 -2
- package/node_modules/@narumitw/pi-btw/README.md +8 -7
- package/node_modules/@narumitw/pi-btw/package.json +2 -2
- package/node_modules/@narumitw/pi-btw/src/menu.ts +1 -0
- package/node_modules/pi-lens/CHANGELOG.md +2440 -954
- package/node_modules/pi-lens/README.md +13 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +73 -18
- package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +21 -4
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +68 -16
- package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -1
- package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +37 -10
- package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +63 -0
- package/node_modules/pi-lens/dist/clients/atomic-write.js +114 -14
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +193 -23
- package/node_modules/pi-lens/dist/clients/biome-client.js +29 -25
- package/node_modules/pi-lens/dist/clients/bootstrap.js +11 -2
- package/node_modules/pi-lens/dist/clients/bounded-cache.js +34 -0
- package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +192 -0
- package/node_modules/pi-lens/dist/clients/bus-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +47 -5
- package/node_modules/pi-lens/dist/clients/cache-manager.js +97 -9
- package/node_modules/pi-lens/dist/clients/cache-observability.js +301 -14
- package/node_modules/pi-lens/dist/clients/call-graph.js +449 -117
- package/node_modules/pi-lens/dist/clients/child-unref.js +85 -0
- package/node_modules/pi-lens/dist/clients/codebase-model.js +52 -23
- package/node_modules/pi-lens/dist/clients/collateral-test-role.js +40 -0
- package/node_modules/pi-lens/dist/clients/complexity-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/console-guard-install.js +14 -0
- package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/debug-handles.js +206 -0
- package/node_modules/pi-lens/dist/clients/debug-heap.js +167 -0
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +153 -0
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +347 -75
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +61 -11
- package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +63 -20
- package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +23 -7
- package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +8 -18
- package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +8 -5
- package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +35 -0
- package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +17 -8
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +1085 -681
- package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +14 -0
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +11 -1
- package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +50 -0
- package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +135 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +23 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +5 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +154 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +5 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +14 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +9 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +4 -8
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +163 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +37 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +3 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +13 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +277 -26
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
- package/node_modules/pi-lens/dist/clients/disposition-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
- package/node_modules/pi-lens/dist/clients/extension-log.js +139 -0
- package/node_modules/pi-lens/dist/clients/extension-mode.js +73 -0
- package/node_modules/pi-lens/dist/clients/file-kinds.js +57 -0
- package/node_modules/pi-lens/dist/clients/file-role.js +18 -3
- package/node_modules/pi-lens/dist/clients/file-utils.js +56 -34
- package/node_modules/pi-lens/dist/clients/format-events-publish.js +16 -3
- package/node_modules/pi-lens/dist/clients/format-service.js +24 -8
- package/node_modules/pi-lens/dist/clients/formatters-lazy.js +8 -0
- package/node_modules/pi-lens/dist/clients/formatters.js +358 -61
- package/node_modules/pi-lens/dist/clients/generated-artifacts.js +148 -15
- package/node_modules/pi-lens/dist/clients/git-guard.js +681 -11
- package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
- package/node_modules/pi-lens/dist/clients/go-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +8 -0
- package/node_modules/pi-lens/dist/clients/grammar-source.js +12 -4
- package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +21 -3
- package/node_modules/pi-lens/dist/clients/host-ports.js +31 -0
- package/node_modules/pi-lens/dist/clients/installer/index.js +390 -99
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +31 -54
- package/node_modules/pi-lens/dist/clients/knip-client.js +15 -36
- package/node_modules/pi-lens/dist/clients/language-policy.js +14 -2
- package/node_modules/pi-lens/dist/clients/language-profile.js +13 -7
- package/node_modules/pi-lens/dist/clients/latency-logger.js +23 -1
- package/node_modules/pi-lens/dist/clients/lens-config.js +12 -1
- package/node_modules/pi-lens/dist/clients/lens-engine.js +86 -17
- package/node_modules/pi-lens/dist/clients/lens-events.js +27 -4
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
- package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +30 -0
- package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/client.js +493 -48
- package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +86 -0
- package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
- package/node_modules/pi-lens/dist/clients/lsp/index.js +1807 -780
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +32 -26
- package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +83 -0
- package/node_modules/pi-lens/dist/clients/lsp/server.js +196 -32
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
- package/node_modules/pi-lens/dist/clients/lsp-lazy.js +8 -0
- package/node_modules/pi-lens/dist/clients/lsp-mutation.js +248 -0
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +123 -32
- package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
- package/node_modules/pi-lens/dist/clients/mcp/session.js +216 -8
- package/node_modules/pi-lens/dist/clients/memory-sampler.js +115 -0
- package/node_modules/pi-lens/dist/clients/metrics-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/metrics-history.js +2 -1
- package/node_modules/pi-lens/dist/clients/module-report-lsp.js +5 -0
- package/node_modules/pi-lens/dist/clients/module-report.js +168 -3
- package/node_modules/pi-lens/dist/clients/ndjson-logger.js +319 -118
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +1 -1
- package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +120 -5
- package/node_modules/pi-lens/dist/clients/path-utils.js +110 -9
- package/node_modules/pi-lens/dist/clients/pipeline.js +47 -13
- package/node_modules/pi-lens/dist/clients/print-mode.js +21 -0
- package/node_modules/pi-lens/dist/clients/project-changes.js +40 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +3 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +8 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +21 -1
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +86 -9
- package/node_modules/pi-lens/dist/clients/project-report.js +27 -13
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +175 -20
- package/node_modules/pi-lens/dist/clients/project-trust.js +181 -0
- package/node_modules/pi-lens/dist/clients/quiet-window.js +18 -4
- package/node_modules/pi-lens/dist/clients/read-bridge.js +147 -0
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +159 -1
- package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +150 -29
- package/node_modules/pi-lens/dist/clients/read-guard.js +185 -4
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +70 -84
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +1122 -221
- package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +2 -1
- package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +21 -10
- package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +46 -0
- package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +57 -6
- package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +51 -3
- package/node_modules/pi-lens/dist/clients/review-graph-logger.js +30 -1
- package/node_modules/pi-lens/dist/clients/ruff-client.js +27 -44
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +107 -73
- package/node_modules/pi-lens/dist/clients/runtime-context.js +36 -12
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +74 -6
- package/node_modules/pi-lens/dist/clients/runtime-session.js +405 -117
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +71 -48
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +218 -23
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +237 -64
- package/node_modules/pi-lens/dist/clients/rust-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
- package/node_modules/pi-lens/dist/clients/sg-runner.js +284 -174
- package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +224 -0
- package/node_modules/pi-lens/dist/clients/source-filter.js +184 -20
- package/node_modules/pi-lens/dist/clients/source-walker.js +66 -10
- package/node_modules/pi-lens/dist/clients/startup-scan.js +15 -16
- package/node_modules/pi-lens/dist/clients/startup-timing.js +1 -1
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +38 -8
- package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
- package/node_modules/pi-lens/dist/clients/tool-policy.js +388 -39
- package/node_modules/pi-lens/dist/clients/tool-render.js +155 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +6 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +419 -50
- package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +21 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +7 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +7 -2
- package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +44 -7
- package/node_modules/pi-lens/dist/clients/user-notify.js +51 -0
- package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +57 -0
- package/node_modules/pi-lens/dist/clients/warm-attach.js +33 -13
- package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
- package/node_modules/pi-lens/dist/clients/word-index.js +391 -64
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
- package/node_modules/pi-lens/dist/index.js +49361 -36888
- package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
- package/node_modules/pi-lens/dist/mcp/server.js +211 -44
- package/node_modules/pi-lens/dist/tools/ast-grep-search.js +93 -27
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +200 -36
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +153 -40
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -3
- package/node_modules/pi-lens/dist/tools/module-report.js +9 -0
- package/node_modules/pi-lens/docs/agent-guide.md +2 -2
- package/node_modules/pi-lens/docs/agent-tools.md +21 -10
- package/node_modules/pi-lens/docs/analysisall.md +192 -0
- package/node_modules/pi-lens/docs/api-ports-inventory.md +165 -0
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +5 -2
- package/node_modules/pi-lens/docs/durable-store-audit-1202.md +32 -0
- package/node_modules/pi-lens/docs/environment-variables.md +44 -0
- package/node_modules/pi-lens/docs/fable.md +27 -0
- package/node_modules/pi-lens/docs/features.md +31 -2
- package/node_modules/pi-lens/docs/globalconfig.md +45 -1
- package/node_modules/pi-lens/docs/language-coverage.md +4 -3
- package/node_modules/pi-lens/docs/lsp-capability-matrix.md +2 -2
- package/node_modules/pi-lens/docs/mcp.md +37 -4
- package/node_modules/pi-lens/docs/servercapabilities.md +3 -3
- package/node_modules/pi-lens/docs/settings.md +12 -1
- package/node_modules/pi-lens/docs/tools.md +4 -1
- package/node_modules/pi-lens/docs/usage.md +18 -0
- package/node_modules/pi-lens/package.json +10 -10
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
- package/node_modules/pi-lens/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
- package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +2 -2
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +55 -2
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +25 -0
- package/node_modules/pi-mcp-adapter/README.md +34 -3
- package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
- package/node_modules/pi-mcp-adapter/commands.ts +15 -7
- package/node_modules/pi-mcp-adapter/direct-tools.ts +54 -7
- package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -8
- package/node_modules/pi-mcp-adapter/index.ts +8 -3
- package/node_modules/pi-mcp-adapter/init.ts +29 -5
- package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -4
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
- package/node_modules/pi-mcp-adapter/mcp-auth.ts +60 -2
- package/node_modules/pi-mcp-adapter/mcp-panel.ts +45 -9
- package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +22 -20
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +27 -4
- package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
- package/node_modules/pi-mcp-adapter/package.json +3 -3
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
- package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
- package/node_modules/pi-mcp-adapter/server-manager.ts +36 -7
- package/node_modules/pi-mcp-adapter/session-recovery.ts +8 -2
- package/node_modules/pi-mcp-adapter/tool-approval.ts +48 -7
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +42 -4
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
- package/node_modules/pi-mcp-adapter/types.ts +93 -27
- package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
- package/package.json +11 -12
- package/node_modules/pi-lens/dist/clients/ast-grep-parser.js +0 -86
- package/node_modules/pi-lens/dist/clients/lsp/interactive-install.js +0 -367
- package/node_modules/pi-lens/dist/clients/lsp/lsp-index.js +0 -10
- package/node_modules/pi-lens/dist/clients/lsp/server-strategies.js +0 -5
- package/node_modules/pi-lens/dist/clients/project-metadata.js +0 -690
- package/node_modules/pi-lens/dist/clients/source-groups.js +0 -96
- package/node_modules/pi-lens/dist/clients/startup-marker.js +0 -6
- package/node_modules/pi-lens/dist/clients/types.js +0 -11
|
@@ -14,6 +14,7 @@ import { primaryServerId } from "../clients/lsp/config.js";
|
|
|
14
14
|
import { combineAbortSignals, withDeadline } from "../clients/deadline-utils.js";
|
|
15
15
|
import { applyAuxiliarySuppressions, retagAuxiliaryDiagnostics, } from "../clients/dispatch/auxiliary-lsp.js";
|
|
16
16
|
import { detectFileRole } from "../clients/file-role.js";
|
|
17
|
+
import { hashDiagnosticContent, } from "../clients/lsp/diagnostic-binding.js";
|
|
17
18
|
import { classifyCascadeWaitTier } from "../clients/lsp/wait-policy/index.js";
|
|
18
19
|
import { attemptTsserverSyncDiagnostics, } from "../clients/lsp/tsserver-sync.js";
|
|
19
20
|
import { convertLspDiagnostics } from "../clients/dispatch/utils/lsp-diagnostics.js";
|
|
@@ -184,14 +185,30 @@ function projectIgnorePredicate(root) {
|
|
|
184
185
|
return () => false;
|
|
185
186
|
}
|
|
186
187
|
}
|
|
187
|
-
|
|
188
|
+
/**
|
|
189
|
+
* #1137: async so a directory read never blocks the event loop (and pi's TUI).
|
|
190
|
+
*
|
|
191
|
+
* This walk was fully synchronous with NO yielding of any kind, bounded only by
|
|
192
|
+
* `maxFiles` *kept* — an ignored-heavy or cloud-backed (OneDrive/network) tree
|
|
193
|
+
* could traverse unboundedly many entries, and a single stalled `readdirSync`
|
|
194
|
+
* held the loop for the whole stall. It is also called once PER LANGUAGE in
|
|
195
|
+
* `runDirectoryDiagnostics`'s `LANG_EXTENSIONS` loop, so a directory-mode
|
|
196
|
+
* `lsp_diagnostics` could pay that cost several times over.
|
|
197
|
+
*
|
|
198
|
+
* The traversal is deliberately still **depth-first with immediate descent**
|
|
199
|
+
* (not the shared stack-based `walkTreeStackAsync`): the `maxFiles` cap makes
|
|
200
|
+
* traversal ORDER observable — a stack walk would keep a different subset of
|
|
201
|
+
* files on an over-large tree. Only scheduling changes here; the returned list
|
|
202
|
+
* is identical. Awaiting each directory read is itself the yield point.
|
|
203
|
+
*/
|
|
204
|
+
async function collectFiles(dir, extensions, maxFiles, isIgnored = () => false) {
|
|
188
205
|
const files = [];
|
|
189
|
-
function walk(current) {
|
|
206
|
+
async function walk(current) {
|
|
190
207
|
if (files.length >= maxFiles)
|
|
191
208
|
return;
|
|
192
209
|
let entries;
|
|
193
210
|
try {
|
|
194
|
-
entries = fs.
|
|
211
|
+
entries = await fs.promises.readdir(current, { withFileTypes: true });
|
|
195
212
|
}
|
|
196
213
|
catch {
|
|
197
214
|
return;
|
|
@@ -204,7 +221,7 @@ function collectFiles(dir, extensions, maxFiles, isIgnored = () => false) {
|
|
|
204
221
|
const full = path.join(current, entry.name);
|
|
205
222
|
if (entry.isDirectory()) {
|
|
206
223
|
if (!isExcludedDirName(entry.name) && !isIgnored(full, true))
|
|
207
|
-
walk(full);
|
|
224
|
+
await walk(full);
|
|
208
225
|
}
|
|
209
226
|
else if (entry.isFile() && extensions.includes(path.extname(full))) {
|
|
210
227
|
if (isIgnored(full, false))
|
|
@@ -213,15 +230,15 @@ function collectFiles(dir, extensions, maxFiles, isIgnored = () => false) {
|
|
|
213
230
|
}
|
|
214
231
|
}
|
|
215
232
|
}
|
|
216
|
-
walk(dir);
|
|
233
|
+
await walk(dir);
|
|
217
234
|
return files;
|
|
218
235
|
}
|
|
219
236
|
export function createLspDiagnosticsTool(
|
|
220
|
-
// #571: same shared write-ordering token source `lens_diagnostics`
|
|
221
|
-
// uses (index.ts injects `() => runtime.nextWriteIndex()`)
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
//
|
|
237
|
+
// #571/#1198: same shared write-ordering token source `lens_diagnostics`
|
|
238
|
+
// mode=full uses (index.ts injects `() => runtime.nextWriteIndex()`). A
|
|
239
|
+
// confirmed result reconciled into the footer reserves its token when the
|
|
240
|
+
// per-file check starts, so settlement order cannot make an older result look
|
|
241
|
+
// newer. Optional/undefined in tests.
|
|
225
242
|
nextWriteIndex) {
|
|
226
243
|
return {
|
|
227
244
|
name: "lsp_diagnostics",
|
|
@@ -419,19 +436,14 @@ nextWriteIndex) {
|
|
|
419
436
|
async function collectDiagnosticsForFile(absPath, lspService, waitMs, serverScope = "all") {
|
|
420
437
|
let timedOut = false;
|
|
421
438
|
let content;
|
|
422
|
-
//
|
|
423
|
-
//
|
|
424
|
-
//
|
|
425
|
-
//
|
|
426
|
-
//
|
|
427
|
-
//
|
|
428
|
-
//
|
|
429
|
-
//
|
|
430
|
-
// `serverScope: "primary"` never actually skipped the auxiliary scanners
|
|
431
|
-
// (getDiagnostics always queries every registered server for the file).
|
|
432
|
-
// `touched` is only undefined when touchFile itself couldn't produce a
|
|
433
|
-
// result (service destroyed, no clients resolved) — that's the one case
|
|
434
|
-
// that still needs the getDiagnostics() fallback below.
|
|
439
|
+
// `touchFile` is the authoritative collection boundary for every scope: it
|
|
440
|
+
// preserves per-touch timeout, content-binding, and silent-clean confirmation
|
|
441
|
+
// metadata while returning diagnostics from only the requested clients. The
|
|
442
|
+
// legacy openFile/getDiagnostics path remains only for an older/mock service
|
|
443
|
+
// without touchFile, or when touchFile cannot resolve any clients.
|
|
444
|
+
// #1179: the result is an explicit wrapper so side-channel fields survive
|
|
445
|
+
// array copies; confirmation was added when Marksman's lower-level clean verdict
|
|
446
|
+
// proved that a successful empty collection also needs explicit provenance.
|
|
435
447
|
let touched;
|
|
436
448
|
let usedTouch = false;
|
|
437
449
|
try {
|
|
@@ -447,12 +459,26 @@ async function collectDiagnosticsForFile(absPath, lspService, waitMs, serverScop
|
|
|
447
459
|
? attached.response.diagnostics.filter((item) => item.source === primaryServerId(absPath))
|
|
448
460
|
: attached.response.diagnostics;
|
|
449
461
|
const filtered = applyAuxiliarySuppressions(scopedDiagnostics, content, { fileRole: detectFileRole(absPath, content) });
|
|
450
|
-
return {
|
|
462
|
+
return {
|
|
463
|
+
diagnostics: filtered,
|
|
464
|
+
timedOut: false,
|
|
465
|
+
// #1253: the incumbent's touch carries the same confirmation
|
|
466
|
+
// provenance a local touch does (an `available: true` answer is
|
|
467
|
+
// already gated on `fresh && !inconclusive`, but that is NOT
|
|
468
|
+
// evidence a silent-on-clean server was confirmed — only the
|
|
469
|
+
// explicit flag is). The incumbent always touches with
|
|
470
|
+
// `clientScope: "with-auxiliary"`, so this is an AGGREGATE
|
|
471
|
+
// confirmation: it carries the same caveat all-scope local
|
|
472
|
+
// touches do, and classic TypeScript still needs the primary-only
|
|
473
|
+
// tsserver sync check below rather than this verdict.
|
|
474
|
+
confirmedByTouch: attached.response.confirmation === "confirmed" &&
|
|
475
|
+
primaryServerId(absPath) !== "typescript",
|
|
476
|
+
content,
|
|
477
|
+
};
|
|
451
478
|
}
|
|
452
479
|
}
|
|
453
480
|
const serviceWithTouch = lspService;
|
|
454
|
-
if (
|
|
455
|
-
typeof serviceWithTouch.touchFile === "function") {
|
|
481
|
+
if (typeof serviceWithTouch.touchFile === "function") {
|
|
456
482
|
usedTouch = true;
|
|
457
483
|
touched = await serviceWithTouch.touchFile(absPath, content, {
|
|
458
484
|
diagnostics: "document",
|
|
@@ -480,7 +506,7 @@ async function collectDiagnosticsForFile(absPath, lspService, waitMs, serverScop
|
|
|
480
506
|
// serverScope:"primary" actually skip auxiliary scanners and drops the
|
|
481
507
|
// common case back to a single LSP round trip instead of two.
|
|
482
508
|
const diagnostics = usedTouch && touched !== undefined
|
|
483
|
-
? touched
|
|
509
|
+
? touched.diags
|
|
484
510
|
: await lspService.getDiagnostics(absPath, waitMs !== undefined ? "document" : "full");
|
|
485
511
|
// #586: honor each auxiliary profile's native inline-suppression comment
|
|
486
512
|
// (e.g. opengrep's `// nosemgrep`, #441) the same way the per-edit dispatch
|
|
@@ -496,7 +522,18 @@ async function collectDiagnosticsForFile(absPath, lspService, waitMs, serverScop
|
|
|
496
522
|
fileRole: detectFileRole(absPath, content),
|
|
497
523
|
})
|
|
498
524
|
: diagnostics;
|
|
499
|
-
|
|
525
|
+
// #1095: surface the touch's content binding (only the touch path carries
|
|
526
|
+
// one; the openFile-only / getDiagnostics fallback leaves it undefined →
|
|
527
|
+
// "unknown", no demotion).
|
|
528
|
+
const binding = usedTouch ? touched?.binding : undefined;
|
|
529
|
+
const confirmedByTouch = usedTouch && touched?.confirmation === "confirmed";
|
|
530
|
+
return {
|
|
531
|
+
diagnostics: filtered,
|
|
532
|
+
timedOut,
|
|
533
|
+
confirmedByTouch,
|
|
534
|
+
content,
|
|
535
|
+
binding,
|
|
536
|
+
};
|
|
500
537
|
}
|
|
501
538
|
function diagnosticsToFileDiags(file, diagnostics) {
|
|
502
539
|
return diagnostics.map((d) => ({
|
|
@@ -567,6 +604,17 @@ async function resolveEmptyResult(file, lspService) {
|
|
|
567
604
|
return { confirmed: true, diagnostics: [] };
|
|
568
605
|
}
|
|
569
606
|
}
|
|
607
|
+
/**
|
|
608
|
+
* A primary-scope touch is authoritative because `touchFile` runs that
|
|
609
|
+
* primary's own confirmation path (including TypeScript's sync fallback). For
|
|
610
|
+
* all-scope TypeScript touches, the aggregate silent-clean gate can settle
|
|
611
|
+
* without that primary-only sync request, so preserve `resolveEmptyResult`'s
|
|
612
|
+
* existing tsserver fallback instead of accepting the aggregate verdict.
|
|
613
|
+
*/
|
|
614
|
+
function canTrustTouchConfirmation(file, serverScope, confirmedByTouch) {
|
|
615
|
+
return (confirmedByTouch &&
|
|
616
|
+
(serverScope === "primary" || primaryServerId(file) !== "typescript"));
|
|
617
|
+
}
|
|
570
618
|
/**
|
|
571
619
|
* #571: reconcile this tool's fresh LSP result into the footer cache
|
|
572
620
|
* (`widget-state.ts`'s `allDiagnostics`) — same shared choke point
|
|
@@ -574,6 +622,11 @@ async function resolveEmptyResult(file, lspService) {
|
|
|
574
622
|
* `reconcileScanDiagnostics`). A manual `lsp_diagnostics` check that proves a
|
|
575
623
|
* stale footer error is actually gone (the real-world case that surfaced
|
|
576
624
|
* #571) is exactly the kind of confirmed result that should correct it.
|
|
625
|
+
* Direct `lsp_diagnostics` deliberately does not perform a second disk read at
|
|
626
|
+
* this reconciliation seam: its collecting touch already read the content and
|
|
627
|
+
* supplies the binding verdict. `false` is unconfirmed and is never written;
|
|
628
|
+
* `true` is trusted, while an absent/`unknown` verdict preserves the legacy
|
|
629
|
+
* fail-open policy for servers that cannot bind their diagnostics to content.
|
|
577
630
|
*
|
|
578
631
|
* `rawDiags` (pre-severity-filter) is what gets written — the footer records
|
|
579
632
|
* the true known state, independent of this call's display-only severity
|
|
@@ -592,8 +645,22 @@ async function resolveEmptyResult(file, lspService) {
|
|
|
592
645
|
* /skipTestFiles-filtered at write time — an empty string here is then a safe
|
|
593
646
|
* no-op re-check, not a behavior gap).
|
|
594
647
|
*/
|
|
595
|
-
function reconcileWidgetFromLspResult(file, rawDiags, confirmation,
|
|
596
|
-
|
|
648
|
+
function reconcileWidgetFromLspResult(file, rawDiags, confirmation, writeIndex, cwd, content,
|
|
649
|
+
// #1095: true when the result's content binding demonstrably mismatches disk
|
|
650
|
+
// (boundToCurrentDisk === false). Such a result — even a NON-EMPTY one, which
|
|
651
|
+
// the pre-#1095 "non-empty is definitionally confirmed" doctrine would have
|
|
652
|
+
// written straight through — must NOT re-cement the footer with a stale view
|
|
653
|
+
// (#1092's window-1 transcript: 17 live-looking diagnostics while tsc exits
|
|
654
|
+
// 0). "unknown"/true bindings leave the doctrine unchanged (fallback preserved
|
|
655
|
+
// for servers that never bind a version).
|
|
656
|
+
boundMismatch = false,
|
|
657
|
+
// #1093: when these `rawDiags` were actually OBSERVED. Fresh touches observe
|
|
658
|
+
// now (`undefined` → `Date.now()`); a cache HIT replays diagnostics scanned
|
|
659
|
+
// earlier, so its caller passes the cache entry's `scannedAt` here so the
|
|
660
|
+
// footer's `touchedAt` isn't re-armed to now() and the mtime-staleness gate
|
|
661
|
+
// stays live (the #1092 re-arming defect).
|
|
662
|
+
observedAt) {
|
|
663
|
+
const confirmed = (rawDiags.length > 0 || confirmation !== "unconfirmed") && !boundMismatch;
|
|
597
664
|
if (!confirmed)
|
|
598
665
|
return;
|
|
599
666
|
try {
|
|
@@ -603,7 +670,7 @@ function reconcileWidgetFromLspResult(file, rawDiags, confirmation, nextWriteInd
|
|
|
603
670
|
scanOrigin: "lsp_diagnostics",
|
|
604
671
|
});
|
|
605
672
|
const retagged = retagAuxiliaryDiagnostics(diagnostics, rawDiags, content ?? "", { cwd, fileRole: detectFileRole(file, content) });
|
|
606
|
-
reconcileScanDiagnostics(file, retagged, true,
|
|
673
|
+
reconcileScanDiagnostics(file, retagged, true, writeIndex, observedAt);
|
|
607
674
|
}
|
|
608
675
|
catch {
|
|
609
676
|
// Never let a footer-reconciliation hiccup fail the diagnostics check.
|
|
@@ -621,6 +688,10 @@ cacheCtx, scopeKey,
|
|
|
621
688
|
// `process.cwd()` for any call site that predates this (there are none
|
|
622
689
|
// today besides the two below, both of which now pass it explicitly).
|
|
623
690
|
cwd = process.cwd()) {
|
|
691
|
+
// Reserve the ordering token when this diagnostic operation starts, not when
|
|
692
|
+
// its LSP promise settles. A slower old result must not receive a newer token
|
|
693
|
+
// merely because it completed later (#1198).
|
|
694
|
+
const writeIndex = nextWriteIndex?.();
|
|
624
695
|
let stat;
|
|
625
696
|
try {
|
|
626
697
|
stat = fs.statSync(file);
|
|
@@ -633,14 +704,27 @@ cwd = process.cwd()) {
|
|
|
633
704
|
}
|
|
634
705
|
if (cacheCtx && scopeKey !== undefined) {
|
|
635
706
|
const cached = cacheCtx.lookup(file, scopeKey);
|
|
636
|
-
|
|
707
|
+
// #1095: a cached entry whose content binding demonstrably mismatches disk
|
|
708
|
+
// (bytes changed without the mtime bump the freshness gate would catch) is
|
|
709
|
+
// NOT served — fall through to a fresh touch instead of replaying a stale
|
|
710
|
+
// cached result. "unknown"/true bindings serve as before.
|
|
711
|
+
if (cached && cached.binding.boundToCurrentDisk !== false) {
|
|
637
712
|
const filteredDiags = applySeverityFilter(cached.diagnostics, severity);
|
|
638
713
|
const confirmation = cached.diagnostics.length === 0 ? "clean" : undefined;
|
|
639
714
|
// #692: cached.diagnostics were already suppression-/skipTestFiles-
|
|
640
715
|
// filtered at write time (this same code path); no file content was
|
|
641
716
|
// cached alongside them, so `undefined` here is a safe re-check, not
|
|
642
717
|
// a gap.
|
|
643
|
-
|
|
718
|
+
// #1093: this is a CACHE HIT — a replay of an OLD observation. Stamp the
|
|
719
|
+
// footer's `touchedAt` with the cache entry's original `scannedAt`, NOT
|
|
720
|
+
// now(), or a repeat check that only re-serves the cache would keep
|
|
721
|
+
// re-arming the mtime-staleness gate and a resolved finding would render
|
|
722
|
+
// forever (the #1092 defect).
|
|
723
|
+
reconcileWidgetFromLspResult(file, cached.diagnostics, confirmation, writeIndex, cwd, undefined,
|
|
724
|
+
// This branch only runs when the cache binding is not a mismatch
|
|
725
|
+
// (guarded above), so boundMismatch is false; #1093's observedAt is
|
|
726
|
+
// the cache entry's original scan time.
|
|
727
|
+
false, cached.scannedAt);
|
|
644
728
|
return {
|
|
645
729
|
file,
|
|
646
730
|
diagnostics: diagnosticsToFileDiags(file, filteredDiags),
|
|
@@ -649,7 +733,7 @@ cwd = process.cwd()) {
|
|
|
649
733
|
};
|
|
650
734
|
}
|
|
651
735
|
}
|
|
652
|
-
const { diagnostics: rawDiags, timedOut, content: collectedContent, } = await collectDiagnosticsForFile(file, lspService, waitMs, serverScope);
|
|
736
|
+
const { diagnostics: rawDiags, timedOut, confirmedByTouch, content: collectedContent, binding, } = await collectDiagnosticsForFile(file, lspService, waitMs, serverScope);
|
|
653
737
|
const health = lspService.getDiagnosticsHealth?.(file);
|
|
654
738
|
// #570: a timed-out priming check is never a confirmed "clean" — treat it
|
|
655
739
|
// as unconfirmed without consulting the (unrelated) silent-tier
|
|
@@ -665,6 +749,11 @@ cwd = process.cwd()) {
|
|
|
665
749
|
confirmation = "unconfirmed";
|
|
666
750
|
}
|
|
667
751
|
}
|
|
752
|
+
else if (canTrustTouchConfirmation(file, serverScope, confirmedByTouch)) {
|
|
753
|
+
if (applySeverityFilter(rawDiags, severity).length === 0) {
|
|
754
|
+
confirmation = "clean";
|
|
755
|
+
}
|
|
756
|
+
}
|
|
668
757
|
else if (rawDiags.length === 0) {
|
|
669
758
|
const resolved = await resolveEmptyResult(file, lspService);
|
|
670
759
|
effectiveRawDiags = resolved.diagnostics;
|
|
@@ -677,16 +766,26 @@ cwd = process.cwd()) {
|
|
|
677
766
|
else if (applySeverityFilter(rawDiags, severity).length === 0) {
|
|
678
767
|
confirmation = await classifyEmptyResult(file, lspService);
|
|
679
768
|
}
|
|
769
|
+
// #1095: a result whose content binding demonstrably mismatches disk is
|
|
770
|
+
// demoted to "unconfirmed" — it must neither confirm the footer nor be cached
|
|
771
|
+
// as clean, regardless of how many diagnostics it carries (the #1092
|
|
772
|
+
// re-cementing path). "unknown"/true bindings leave the verdict untouched.
|
|
773
|
+
const boundMismatch = binding?.boundToCurrentDisk === false;
|
|
774
|
+
if (boundMismatch)
|
|
775
|
+
confirmation = "unconfirmed";
|
|
680
776
|
const filteredDiags = applySeverityFilter(effectiveRawDiags, severity);
|
|
681
|
-
reconcileWidgetFromLspResult(file, effectiveRawDiags, confirmation,
|
|
777
|
+
reconcileWidgetFromLspResult(file, effectiveRawDiags, confirmation, writeIndex, cwd, collectedContent, boundMismatch);
|
|
682
778
|
// #671: only a CONFIRMED outcome ("clean", or a non-empty result — either
|
|
683
779
|
// is definitionally confirmed per this function's own doctrine above) is
|
|
684
780
|
// safe to cache; "unconfirmed" (timeout OR a silent-tier server's
|
|
685
781
|
// unescapable empty push) must never be persisted as a cacheable clean
|
|
686
782
|
// result — same false-clean bug class `runWorkspaceDiagnostics`'s cache
|
|
687
|
-
// wiring guards against.
|
|
783
|
+
// wiring guards against. #1095: the entry carries the content fingerprint so a
|
|
784
|
+
// later lookup can verify it against disk beyond the mtime proxy.
|
|
688
785
|
if (cacheCtx && scopeKey !== undefined && confirmation !== "unconfirmed") {
|
|
689
|
-
cacheCtx.record(file, scopeKey, effectiveRawDiags, stat.mtimeMs
|
|
786
|
+
cacheCtx.record(file, scopeKey, effectiveRawDiags, stat.mtimeMs, collectedContent !== undefined
|
|
787
|
+
? hashDiagnosticContent(collectedContent)
|
|
788
|
+
: undefined);
|
|
690
789
|
}
|
|
691
790
|
return {
|
|
692
791
|
file,
|
|
@@ -698,7 +797,10 @@ cwd = process.cwd()) {
|
|
|
698
797
|
};
|
|
699
798
|
}
|
|
700
799
|
async function runFileDiagnostics(absPath, severity, lspService, waitMs, nextWriteIndex, serverScope = "all", cwd = process.cwd()) {
|
|
701
|
-
|
|
800
|
+
// Reserve the token before awaiting this file's LSP result. The direct-file
|
|
801
|
+
// path performs its own confirmation/reconciliation below (#1198).
|
|
802
|
+
const writeIndex = nextWriteIndex?.();
|
|
803
|
+
const { diagnostics: rawDiags, timedOut, confirmedByTouch, content: collectedContent, binding, } = await collectDiagnosticsForFile(absPath, lspService, waitMs, serverScope);
|
|
702
804
|
const lspHealth = lspService.getDiagnosticsHealth?.(absPath);
|
|
703
805
|
const unavailable = lspUnavailableMessage(absPath, lspHealth);
|
|
704
806
|
// #533: an empty result needs a confirmed/unconfirmed verdict — a push-only,
|
|
@@ -717,6 +819,11 @@ async function runFileDiagnostics(absPath, severity, lspService, waitMs, nextWri
|
|
|
717
819
|
confirmation = "unconfirmed";
|
|
718
820
|
}
|
|
719
821
|
}
|
|
822
|
+
else if (canTrustTouchConfirmation(absPath, serverScope, confirmedByTouch)) {
|
|
823
|
+
if (applySeverityFilter(rawDiags, severity).length === 0) {
|
|
824
|
+
confirmation = "clean";
|
|
825
|
+
}
|
|
826
|
+
}
|
|
720
827
|
else if (rawDiags.length === 0) {
|
|
721
828
|
const resolved = await resolveEmptyResult(absPath, lspService);
|
|
722
829
|
effectiveRawDiags = resolved.diagnostics;
|
|
@@ -729,12 +836,18 @@ async function runFileDiagnostics(absPath, severity, lspService, waitMs, nextWri
|
|
|
729
836
|
else if (applySeverityFilter(rawDiags, severity).length === 0) {
|
|
730
837
|
confirmation = await classifyEmptyResult(absPath, lspService);
|
|
731
838
|
}
|
|
839
|
+
// #1095: demote a result whose content binding mismatches disk to
|
|
840
|
+
// "unconfirmed" (the #1092 re-cementing path) — a non-empty stale result is no
|
|
841
|
+
// longer "definitionally confirmed". "unknown"/true bindings are unchanged.
|
|
842
|
+
const boundMismatch = binding?.boundToCurrentDisk === false;
|
|
843
|
+
if (boundMismatch)
|
|
844
|
+
confirmation = "unconfirmed";
|
|
732
845
|
const filtered = applySeverityFilter(effectiveRawDiags, severity);
|
|
733
846
|
const total = filtered.length;
|
|
734
847
|
const truncated = total > MAX_DIAGNOSTICS;
|
|
735
848
|
const limited = truncated ? filtered.slice(0, MAX_DIAGNOSTICS) : filtered;
|
|
736
849
|
const unconfirmed = confirmation === "unconfirmed";
|
|
737
|
-
reconcileWidgetFromLspResult(absPath, effectiveRawDiags, confirmation,
|
|
850
|
+
reconcileWidgetFromLspResult(absPath, effectiveRawDiags, confirmation, writeIndex, cwd, collectedContent, boundMismatch);
|
|
738
851
|
const primaryId = primaryServerId(absPath);
|
|
739
852
|
const primaryDiags = limited.filter((d) => d.source === primaryId);
|
|
740
853
|
const auxiliaryDiags = limited.filter((d) => d.source !== primaryId);
|
|
@@ -1072,7 +1185,7 @@ async function runDirectoryDiagnostics(absPath, severity, lspService, options) {
|
|
|
1072
1185
|
let collectedFiles = [];
|
|
1073
1186
|
const isIgnored = projectIgnorePredicate(absPath);
|
|
1074
1187
|
for (const [ext, exts] of Object.entries(LANG_EXTENSIONS)) {
|
|
1075
|
-
collectedFiles = collectFiles(absPath, exts, MAX_FILES + 1, isIgnored);
|
|
1188
|
+
collectedFiles = await collectFiles(absPath, exts, MAX_FILES + 1, isIgnored);
|
|
1076
1189
|
if (collectedFiles.length > 0) {
|
|
1077
1190
|
extension = ext;
|
|
1078
1191
|
break;
|
|
@@ -8,6 +8,7 @@ import * as path from "node:path";
|
|
|
8
8
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
9
9
|
import { Type } from "../clients/deps/typebox.js";
|
|
10
10
|
import { logLatency } from "../clients/latency-logger.js";
|
|
11
|
+
import { newLspMutationCorrelationId, recordLspMutationOutcome, } from "../clients/lsp-mutation.js";
|
|
11
12
|
import { compactRenderResult } from "./render-compact.js";
|
|
12
13
|
import { applyWorkspaceEdit, summarizeWorkspaceEdit, } from "../clients/lsp/edits.js";
|
|
13
14
|
import { getLSPService } from "../clients/lsp/index.js";
|
|
@@ -550,7 +551,7 @@ export function createLspNavigationTool(
|
|
|
550
551
|
* constructed in; callers that only have a single fixed project root (e.g.
|
|
551
552
|
* pi's own `getLensFlag`) may ignore the second argument.
|
|
552
553
|
*/
|
|
553
|
-
getFlag) {
|
|
554
|
+
getFlag, mutationDeps) {
|
|
554
555
|
return {
|
|
555
556
|
name: "lsp_navigation",
|
|
556
557
|
label: "LSP Navigate",
|
|
@@ -675,7 +676,15 @@ getFlag) {
|
|
|
675
676
|
let supported = null;
|
|
676
677
|
let diagnosticsMode = "unknown";
|
|
677
678
|
let columnResolution;
|
|
679
|
+
let mutationContext;
|
|
680
|
+
let requestedApply = false;
|
|
678
681
|
const finalize = (payload, meta) => {
|
|
682
|
+
if (payload.isError &&
|
|
683
|
+
requestedApply &&
|
|
684
|
+
mutationContext &&
|
|
685
|
+
["rename", "rename_file", "executeCommand"].includes(meta.operation)) {
|
|
686
|
+
recordLspMutationOutcome(mutationContext, "failed");
|
|
687
|
+
}
|
|
679
688
|
const normalizedFilePath = meta.filePath.replace(/\\/g, "/");
|
|
680
689
|
logLatency({
|
|
681
690
|
type: "phase",
|
|
@@ -759,6 +768,21 @@ getFlag) {
|
|
|
759
768
|
});
|
|
760
769
|
}
|
|
761
770
|
const operation = normalizedOperation;
|
|
771
|
+
requestedApply = apply === true;
|
|
772
|
+
if (requestedApply &&
|
|
773
|
+
["rename", "rename_file", "executeCommand"].includes(operation)) {
|
|
774
|
+
const cwd = ctx.cwd || ".";
|
|
775
|
+
mutationContext = {
|
|
776
|
+
cwd,
|
|
777
|
+
correlationId: newLspMutationCorrelationId(_toolCallId),
|
|
778
|
+
tool: "lsp_navigation",
|
|
779
|
+
source: "lsp-edit",
|
|
780
|
+
...mutationDeps,
|
|
781
|
+
readGuard: getFlag("no-read-guard", cwd)
|
|
782
|
+
? undefined
|
|
783
|
+
: mutationDeps?.readGuard,
|
|
784
|
+
};
|
|
785
|
+
}
|
|
762
786
|
const isCallHierarchyTraversal = operation === "incomingCalls" || operation === "outgoingCalls";
|
|
763
787
|
const needsFilePath = operation !== "workspaceDiagnostics" &&
|
|
764
788
|
operation !== "workspaceSymbol" &&
|
|
@@ -1060,7 +1084,7 @@ getFlag) {
|
|
|
1060
1084
|
edit,
|
|
1061
1085
|
};
|
|
1062
1086
|
}
|
|
1063
|
-
const applied = await applyWorkspaceEdit(edit, ctx.cwd || ".");
|
|
1087
|
+
const applied = await applyWorkspaceEdit(edit, ctx.cwd || ".", { mutationContext });
|
|
1064
1088
|
for (const touchedFile of applied.files) {
|
|
1065
1089
|
try {
|
|
1066
1090
|
await openFileBestEffort(lspService, touchedFile, false);
|
|
@@ -1081,6 +1105,7 @@ getFlag) {
|
|
|
1081
1105
|
const result = await lspService.renameFile(filePath, resolvedNewFilePath, {
|
|
1082
1106
|
cwd: ctx.cwd || ".",
|
|
1083
1107
|
apply: apply ?? false,
|
|
1108
|
+
...(mutationContext ? { mutationContext } : {}),
|
|
1084
1109
|
});
|
|
1085
1110
|
if (result.applied) {
|
|
1086
1111
|
for (const touchedFile of result.files ?? []) {
|
|
@@ -1122,7 +1147,7 @@ getFlag) {
|
|
|
1122
1147
|
if (!isAdvertised) {
|
|
1123
1148
|
throw new Error(`__UNSUPPORTED__ command "${command}" is not advertised by the active LSP server (advertised: ${advertised.join(", ") || "none"})`);
|
|
1124
1149
|
}
|
|
1125
|
-
return lspService.executeCommand(rawPath ? filePath : undefined, command, commandArguments);
|
|
1150
|
+
return lspService.executeCommand(rawPath ? filePath : undefined, command, commandArguments, mutationContext);
|
|
1126
1151
|
}
|
|
1127
1152
|
case "incomingCalls": {
|
|
1128
1153
|
if (!callHierarchyItem) {
|
|
@@ -1177,6 +1202,24 @@ getFlag) {
|
|
|
1177
1202
|
}
|
|
1178
1203
|
}
|
|
1179
1204
|
}
|
|
1205
|
+
if (mutationContext &&
|
|
1206
|
+
requestedApply &&
|
|
1207
|
+
!mutationContext.summaryEmitted) {
|
|
1208
|
+
const outcome = operation === "rename_file" &&
|
|
1209
|
+
result &&
|
|
1210
|
+
typeof result === "object" &&
|
|
1211
|
+
"applied" in result &&
|
|
1212
|
+
result.applied === false
|
|
1213
|
+
? "failed"
|
|
1214
|
+
: operation === "executeCommand" &&
|
|
1215
|
+
result &&
|
|
1216
|
+
typeof result === "object" &&
|
|
1217
|
+
"executed" in result &&
|
|
1218
|
+
result.executed === false
|
|
1219
|
+
? "failed"
|
|
1220
|
+
: "skipped";
|
|
1221
|
+
recordLspMutationOutcome(mutationContext, outcome);
|
|
1222
|
+
}
|
|
1180
1223
|
}
|
|
1181
1224
|
catch (err) {
|
|
1182
1225
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -29,6 +29,7 @@ export function createModuleReportTool(getProjectRoot) {
|
|
|
29
29
|
description: "Structured, navigable overview of a source module — a token-efficient substitute for reading the whole file. Returns each symbol's name/kind/signature/line-range (plus a first-line `doc` summary when a doc comment is attached), important inline callbacks/closures/lambdas with stable handles, plus who-uses-this, risk flags, and ranked recommendedReads. To read a symbol's body: call read/read_symbol with offset=startLine, limit=endLine-startLine+1 on THIS report's `path` — those aren't repeated per symbol. Prefer this before a full read; then use read_symbol (or read) for the exact body you need.\n" +
|
|
30
30
|
"Single mode: language-uniform tree-sitter outline + review-graph who-uses-this + inline executable extraction; degrades to outline-only when no cached graph is available. `semantic.source` reports whether graph data was used.\n" +
|
|
31
31
|
'Pass `blastRadius: true` to also get the cross-file blast radius — the transitive dependents of this module aggregated to ranked file `read` args ("if you change this, verify these files"). Read-only over the cached graph; omitted on a cold cache. Supersedes the standalone impact query.\n' +
|
|
32
|
+
'Pass `callGraph: true` to include bounded derived callers/callees from the cached FunctionCallGraph; unavailable cache state is explicit and never reported as zero calls.\n' +
|
|
32
33
|
'`view: "compact"` returns a line-oriented text rendering (one line per symbol/callback, cheapest option) instead of JSON — same data, roughly a quarter of the token cost; use it for a quick skim. Default view returns JSON. An outline shows shape, not bodies — it does NOT count as having read a symbol\'s body for editing; use read_symbol for that.',
|
|
33
34
|
promptSnippet: "Navigable file outline — a cheap substitute for reading a whole file",
|
|
34
35
|
renderResult: compactRenderResult(({ details, args, isError }) => {
|
|
@@ -65,6 +66,12 @@ export function createModuleReportTool(getProjectRoot) {
|
|
|
65
66
|
blastRadiusDepth: Type.Optional(Type.Number({
|
|
66
67
|
description: "Max hops for the blast-radius walk (default 3). Only used with blastRadius.",
|
|
67
68
|
})),
|
|
69
|
+
callGraph: Type.Optional(Type.Boolean({
|
|
70
|
+
description: "Include bounded derived callers/callees from the cached FunctionCallGraph; cold or stale cache state is explicit.",
|
|
71
|
+
})),
|
|
72
|
+
maxCallGraphEntries: Type.Optional(Type.Number({
|
|
73
|
+
description: "Per-direction cap for call-graph relations (default 20).",
|
|
74
|
+
})),
|
|
68
75
|
}),
|
|
69
76
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
70
77
|
// Resolve the file against the agent's cwd (sibling-tool convention); build
|
|
@@ -79,6 +86,8 @@ export function createModuleReportTool(getProjectRoot) {
|
|
|
79
86
|
view: params.view,
|
|
80
87
|
blastRadius: params.blastRadius,
|
|
81
88
|
blastRadiusDepth: params.blastRadiusDepth,
|
|
89
|
+
callGraph: params.callGraph,
|
|
90
|
+
maxCallGraphEntries: params.maxCallGraphEntries,
|
|
82
91
|
});
|
|
83
92
|
}
|
|
84
93
|
catch (err) {
|
|
@@ -122,7 +122,7 @@ unresolved blockers exist, with:
|
|
|
122
122
|
> `🔴 COMMIT BLOCKED (--lens-guard): unresolved blockers must be fixed before
|
|
123
123
|
> commit/push. … Run lens_diagnostics mode=all for full details, then commit again.`
|
|
124
124
|
|
|
125
|
-
Don't try to route around it — clear the blockers, then commit.
|
|
125
|
+
Don't try to route around it — clear the blockers, then commit. The guard is strictly opt-in (off by default) and is marked **EXPERIMENTAL**. It gates only structured blocking findings, including blocking test failures under the current test-runner policy; advisory/no-action-required findings never gate. The blocker state is sequence- and session-bound, so an ambiguous or stale blocker record blocks conservatively until pi-lens runs again; advisory records never gate.
|
|
126
126
|
|
|
127
127
|
---
|
|
128
128
|
|
|
@@ -207,7 +207,7 @@ Verified in `index.ts`:
|
|
|
207
207
|
| `/lens-toggle` | Turn pi-lens on/off for the session. |
|
|
208
208
|
| `/lens-context-toggle` | Toggle context injection (tools/LSP/read-guard/formatting stay active). |
|
|
209
209
|
| `/lens-widget-toggle` | Show/hide the diagnostics footer widget. |
|
|
210
|
-
| `/lens-health` | Runtime health: pipeline crashes, slow runners, last dispatch latency. |
|
|
210
|
+
| `/lens-health` | Runtime health: pipeline crashes, slow runners, last dispatch latency, and a bounded degradation-ledger summary (trust refusals, LSP breakers, formatter skips/failures, idle evictions, WASM aborts, timeout tallies). |
|
|
211
211
|
| `/lens-perf` | Slowest latency-log phases (p50/p99). |
|
|
212
212
|
| `/lens-tools` | Tool installation status (global / auto-installed / npx fallback). |
|
|
213
213
|
| `/lens-tdi` | Technical Debt Index and project health trend. |
|
|
@@ -14,9 +14,11 @@ share) — current exceptions: `ast_grep_outline` and `ast_grep_dump`
|
|
|
14
14
|
are registered but
|
|
15
15
|
inactive by default; the model activates the ones it needs via the always-active
|
|
16
16
|
loader tool `pi_lens_activate_tools`, per pi's dynamic-tool-loading API
|
|
17
|
-
(`pi.setActiveTools`/`pi.getActiveTools`).
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
(`pi.setActiveTools`/`pi.getActiveTools`). The loader explicitly reports
|
|
18
|
+
"Available starting next turn"; do not retry the tool in the same turn.
|
|
19
|
+
Feature-detected: on hosts without that API, the six situational tools fall back
|
|
20
|
+
to being statically active, exactly as before (`tools/activate-tools.ts`, wired
|
|
21
|
+
in `index.ts`).
|
|
20
22
|
|
|
21
23
|
## Per-edit
|
|
22
24
|
|
|
@@ -40,13 +42,22 @@ exactly as before (`tools/activate-tools.ts`, wired in `index.ts`).
|
|
|
40
42
|
- **`ast_grep_search`** — AST-aware structural search across ~40 languages via
|
|
41
43
|
the `sg` CLI. Supports metavariables (`$VAR`, `$$$ARGS`), `strictness`
|
|
42
44
|
modes (`smart`, `relaxed`, `ast`, `cst`, `signature`, `template`), structural
|
|
43
|
-
constraints (`insideKind`, `hasKind`, `
|
|
44
|
-
passthrough, `validateOnly` for compile/shape
|
|
45
|
-
files, and pagination via `skip` / `maxMatches`
|
|
46
|
-
max 200; also sets the pagination step).
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
constraints (`insideKind`, `hasKind`, `hasDescendantKind`, `follows`,
|
|
46
|
+
`precedes`), raw YAML `rule` passthrough, `validateOnly` for compile/shape
|
|
47
|
+
checks without scanning project files, and pagination via `skip` / `maxMatches`
|
|
48
|
+
(per-call cap, default 50, max 200; also sets the pagination step).
|
|
49
|
+
`nodeKind` is an expert grammar-specific escape hatch: it finds every node of
|
|
50
|
+
the exact kind used by the target grammar. Node kinds are not universal across
|
|
51
|
+
languages; use `ast_grep_dump` to discover the kind in the target language. It is mutually exclusive with `pattern` and `rule`.
|
|
52
|
+
`hasKind` retains ast-grep's immediate-child semantics; use
|
|
53
|
+
`hasDescendantKind` for an explicit recursive descendant search. A future
|
|
54
|
+
canonical `find`/`query` facade (call/function/import/etc.) should map to
|
|
55
|
+
this same synthesized YAML path, with per-language templates and a clear
|
|
56
|
+
unsupported-concept error — it is intentionally not a second search engine
|
|
57
|
+
in this patch.
|
|
58
|
+
`groupByFile: true` renders a compact one-line-per-file distribution
|
|
59
|
+
(`L<line>:<col>` locations) instead of each match body — for high-volume
|
|
60
|
+
searches. `pattern` is optional when a `rule` or `nodeKind` is given.
|
|
50
61
|
Results include `details.matchLocations[]` — each hit carries a ready
|
|
51
62
|
`readSlice` (`path`/`offset`/`limit`) for a bounded context read; zero-match
|
|
52
63
|
results include a `suggestedDump` hint pointing at `ast_grep_dump`.
|