@herbertgao/pi-extensions 2026.8.3 → 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 +45 -4
- package/examples/pi-footer.json +275 -0
- 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-footer/CHANGELOG.md +145 -0
- package/node_modules/pi-footer/LICENSE +21 -0
- package/node_modules/pi-footer/README.md +354 -0
- package/node_modules/pi-footer/package.json +68 -0
- package/node_modules/pi-footer/src/cache.ts +106 -0
- package/node_modules/pi-footer/src/colors.ts +237 -0
- package/node_modules/pi-footer/src/config.ts +202 -0
- package/node_modules/pi-footer/src/event-widgets.ts +41 -0
- package/node_modules/pi-footer/src/extension-statuses.ts +102 -0
- package/node_modules/pi-footer/src/git.ts +135 -0
- package/node_modules/pi-footer/src/index.ts +268 -0
- package/node_modules/pi-footer/src/metrics.ts +141 -0
- package/node_modules/pi-footer/src/presets.ts +383 -0
- package/node_modules/pi-footer/src/render.ts +114 -0
- package/node_modules/pi-footer/src/separators.ts +52 -0
- package/node_modules/pi-footer/src/types.ts +122 -0
- package/node_modules/pi-footer/src/ui/color-level-confirm.ts +22 -0
- package/node_modules/pi-footer/src/ui/color-options.ts +195 -0
- package/node_modules/pi-footer/src/ui/config-lifecycle.ts +61 -0
- package/node_modules/pi-footer/src/ui/edit-colors.ts +13 -0
- package/node_modules/pi-footer/src/ui/events.ts +35 -0
- package/node_modules/pi-footer/src/ui/extension-status-picker.ts +60 -0
- package/node_modules/pi-footer/src/ui/extension-statuses.ts +73 -0
- package/node_modules/pi-footer/src/ui/fields.ts +252 -0
- package/node_modules/pi-footer/src/ui/global-menu.ts +126 -0
- package/node_modules/pi-footer/src/ui/helpers.ts +44 -0
- package/node_modules/pi-footer/src/ui/layout.ts +17 -0
- package/node_modules/pi-footer/src/ui/line-list.ts +19 -0
- package/node_modules/pi-footer/src/ui/model.ts +64 -0
- package/node_modules/pi-footer/src/ui/navigation.ts +29 -0
- package/node_modules/pi-footer/src/ui/option-edit.ts +93 -0
- package/node_modules/pi-footer/src/ui/overlay-render.ts +103 -0
- package/node_modules/pi-footer/src/ui/screen-context.ts +18 -0
- package/node_modules/pi-footer/src/ui/screen-controller.ts +27 -0
- package/node_modules/pi-footer/src/ui/screen-render.ts +32 -0
- package/node_modules/pi-footer/src/ui/screen-state.ts +20 -0
- package/node_modules/pi-footer/src/ui/screen.ts +243 -0
- package/node_modules/pi-footer/src/ui/screens/add-widget.ts +92 -0
- package/node_modules/pi-footer/src/ui/screens/confirm-exit.ts +52 -0
- package/node_modules/pi-footer/src/ui/screens/controller.ts +12 -0
- package/node_modules/pi-footer/src/ui/screens/edit-colors.ts +68 -0
- package/node_modules/pi-footer/src/ui/screens/edit-widget.ts +115 -0
- package/node_modules/pi-footer/src/ui/screens/extension-status-row.ts +61 -0
- package/node_modules/pi-footer/src/ui/screens/global.ts +75 -0
- package/node_modules/pi-footer/src/ui/screens/line-list.ts +108 -0
- package/node_modules/pi-footer/src/ui/screens/main.ts +99 -0
- package/node_modules/pi-footer/src/ui/screens/terminal.ts +121 -0
- package/node_modules/pi-footer/src/ui/screens/widget-list.ts +154 -0
- package/node_modules/pi-footer/src/ui/terminal-menu.ts +32 -0
- package/node_modules/pi-footer/src/ui/theme.ts +48 -0
- package/node_modules/pi-footer/src/ui/title-bar.ts +100 -0
- package/node_modules/pi-footer/src/ui/widget-actions.ts +76 -0
- package/node_modules/pi-footer/src/ui.ts +66 -0
- package/node_modules/pi-footer/src/widgets/context.ts +28 -0
- package/node_modules/pi-footer/src/widgets/core/active-tools.ts +17 -0
- package/node_modules/pi-footer/src/widgets/core/context-window.ts +24 -0
- package/node_modules/pi-footer/src/widgets/core/cwd-basename.ts +19 -0
- package/node_modules/pi-footer/src/widgets/core/cwd.ts +108 -0
- package/node_modules/pi-footer/src/widgets/core/event.ts +34 -0
- package/node_modules/pi-footer/src/widgets/core/external-status.ts +102 -0
- package/node_modules/pi-footer/src/widgets/core/model-provider.ts +20 -0
- package/node_modules/pi-footer/src/widgets/core/model.ts +35 -0
- package/node_modules/pi-footer/src/widgets/core/provider.ts +17 -0
- package/node_modules/pi-footer/src/widgets/core/session-name.ts +17 -0
- package/node_modules/pi-footer/src/widgets/core/text-verbosity.ts +17 -0
- package/node_modules/pi-footer/src/widgets/core/thinking-level.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/ahead-behind.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/branch.ts +81 -0
- package/node_modules/pi-footer/src/widgets/git/clean.ts +19 -0
- package/node_modules/pi-footer/src/widgets/git/deletions.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/diff.ts +38 -0
- package/node_modules/pi-footer/src/widgets/git/insertions.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/remote.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/root.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/sha.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/staged.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/status.ts +19 -0
- package/node_modules/pi-footer/src/widgets/git/unstaged.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/untracked.ts +17 -0
- package/node_modules/pi-footer/src/widgets/instance.ts +118 -0
- package/node_modules/pi-footer/src/widgets/layout/custom-text.ts +18 -0
- package/node_modules/pi-footer/src/widgets/layout/flex-separator.ts +32 -0
- package/node_modules/pi-footer/src/widgets/layout/separator.ts +57 -0
- package/node_modules/pi-footer/src/widgets/layout/spacer.ts +31 -0
- package/node_modules/pi-footer/src/widgets/options.ts +122 -0
- package/node_modules/pi-footer/src/widgets/project/runtime.ts +361 -0
- package/node_modules/pi-footer/src/widgets/registry.ts +223 -0
- package/node_modules/pi-footer/src/widgets/session/assistant-messages.ts +17 -0
- package/node_modules/pi-footer/src/widgets/session/compactions.ts +17 -0
- package/node_modules/pi-footer/src/widgets/session/elapsed.ts +18 -0
- package/node_modules/pi-footer/src/widgets/session/last-activity.ts +18 -0
- package/node_modules/pi-footer/src/widgets/session/messages.ts +19 -0
- package/node_modules/pi-footer/src/widgets/session/session-id.ts +17 -0
- package/node_modules/pi-footer/src/widgets/session/session-start.ts +18 -0
- package/node_modules/pi-footer/src/widgets/session/tool-results.ts +17 -0
- package/node_modules/pi-footer/src/widgets/session/total-messages.ts +19 -0
- package/node_modules/pi-footer/src/widgets/session/total-time.ts +18 -0
- package/node_modules/pi-footer/src/widgets/session/user-messages.ts +17 -0
- package/node_modules/pi-footer/src/widgets/store.ts +26 -0
- package/node_modules/pi-footer/src/widgets/tokens/cache-hit-rate.ts +55 -0
- package/node_modules/pi-footer/src/widgets/tokens/cache-read.ts +18 -0
- package/node_modules/pi-footer/src/widgets/tokens/cache-write.ts +18 -0
- package/node_modules/pi-footer/src/widgets/tokens/context-bar.ts +108 -0
- package/node_modules/pi-footer/src/widgets/tokens/context-length.ts +26 -0
- package/node_modules/pi-footer/src/widgets/tokens/context-remaining.ts +29 -0
- package/node_modules/pi-footer/src/widgets/tokens/context.ts +29 -0
- package/node_modules/pi-footer/src/widgets/tokens/cost.ts +51 -0
- package/node_modules/pi-footer/src/widgets/tokens/input-speed.ts +25 -0
- package/node_modules/pi-footer/src/widgets/tokens/input-tokens.ts +18 -0
- package/node_modules/pi-footer/src/widgets/tokens/output-speed.ts +25 -0
- package/node_modules/pi-footer/src/widgets/tokens/output-tokens.ts +18 -0
- package/node_modules/pi-footer/src/widgets/tokens/tokens.ts +20 -0
- package/node_modules/pi-footer/src/widgets/tokens/total-speed.ts +25 -0
- package/node_modules/pi-footer/src/widgets/tokens/total-tokens.ts +18 -0
- package/node_modules/pi-footer/src/widgets/types.ts +181 -0
- package/node_modules/pi-footer/src/widgets/utils/colors.ts +11 -0
- package/node_modules/pi-footer/src/widgets/utils/context.ts +112 -0
- package/node_modules/pi-footer/src/widgets/utils/session.ts +19 -0
- package/node_modules/pi-footer/src/widgets/utils/token-format.ts +78 -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 +17 -13
- 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
|
@@ -12,8 +12,11 @@ import os from "node:os";
|
|
|
12
12
|
import path from "node:path";
|
|
13
13
|
import { isTestMode } from "../env-utils.js";
|
|
14
14
|
import { getGlobalPiLensDir } from "../file-utils.js";
|
|
15
|
+
import { isFullyQualified } from "../path-utils.js";
|
|
15
16
|
import { findGlobalBinary } from "../package-manager.js";
|
|
16
17
|
import { redactSecrets } from "../redact/secrets.js";
|
|
18
|
+
import { classifySpawnFailure, SpawnFailureError, } from "../safe-spawn.js";
|
|
19
|
+
import { getRubyVersionDirNamesAsync } from "./ruby-drive-dirs.js";
|
|
17
20
|
const isWindows = process.platform === "win32";
|
|
18
21
|
/**
|
|
19
22
|
* Whether a resolved command must be spawned through a shell on Windows.
|
|
@@ -109,7 +112,7 @@ function getLiveWindowsPath() {
|
|
|
109
112
|
}
|
|
110
113
|
return _liveWindowsPath;
|
|
111
114
|
}
|
|
112
|
-
function buildAugmentedPath(basePath) {
|
|
115
|
+
async function buildAugmentedPath(basePath) {
|
|
113
116
|
const candidates = [];
|
|
114
117
|
const nodeDir = path.dirname(process.execPath);
|
|
115
118
|
if (nodeDir) {
|
|
@@ -125,16 +128,12 @@ function buildAugmentedPath(basePath) {
|
|
|
125
128
|
candidates.push(PI_LENS_TOOLS_BIN_DIR);
|
|
126
129
|
candidates.push(path.join(driveRoot, "Program Files", "Go", "bin"));
|
|
127
130
|
candidates.push(path.join(driveRoot, "Go", "bin"));
|
|
128
|
-
// Ruby installer drops versioned dirs (e.g. Ruby34-x64) on the drive root
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
catch {
|
|
137
|
-
// drive root not readable — skip
|
|
131
|
+
// Ruby installer drops versioned dirs (e.g. Ruby34-x64) on the drive root.
|
|
132
|
+
// Read off the event loop and memoized once per process (#1137): a
|
|
133
|
+
// synchronous drive-root enumeration on every LSP spawn blocked the loop
|
|
134
|
+
// for the whole stall on a slow cloud/network-backed drive root.
|
|
135
|
+
for (const entry of await getRubyVersionDirNamesAsync(driveRoot)) {
|
|
136
|
+
candidates.push(path.join(driveRoot, entry, "bin"));
|
|
138
137
|
}
|
|
139
138
|
}
|
|
140
139
|
// On Windows, merge the live registry PATH so newly installed tools are visible
|
|
@@ -339,10 +338,12 @@ function _attachErrorHandler(proc, context, logContext, rejectOnImmediateError)
|
|
|
339
338
|
err.message +
|
|
340
339
|
(stderrPreview ? " stderr=" + compactLogValue(stderrPreview) : ""));
|
|
341
340
|
}
|
|
342
|
-
//
|
|
343
|
-
if (rejectOnImmediateError &&
|
|
344
|
-
err
|
|
345
|
-
|
|
341
|
+
// Preserve intent at the boundary instead of leaking a raw errno check.
|
|
342
|
+
if (rejectOnImmediateError && logContext) {
|
|
343
|
+
void classifySpawnFailure(err, {
|
|
344
|
+
command: logContext.command,
|
|
345
|
+
cwd: logContext.cwd,
|
|
346
|
+
}).then(rejectOnImmediateError);
|
|
346
347
|
}
|
|
347
348
|
});
|
|
348
349
|
proc.on("close", (code, signal) => {
|
|
@@ -402,7 +403,7 @@ function _attachErrorHandler(proc, context, logContext, rejectOnImmediateError)
|
|
|
402
403
|
export async function launchLSP(command, args = [], options = {}) {
|
|
403
404
|
const cwd = String(options.cwd ?? process.cwd());
|
|
404
405
|
const mergedEnv = { ...process.env, ...options.env };
|
|
405
|
-
const augmentedPath = buildAugmentedPath(resolvePathValue(mergedEnv));
|
|
406
|
+
const augmentedPath = await buildAugmentedPath(resolvePathValue(mergedEnv));
|
|
406
407
|
const env = {
|
|
407
408
|
...mergedEnv,
|
|
408
409
|
PATH: augmentedPath,
|
|
@@ -412,10 +413,10 @@ export async function launchLSP(command, args = [], options = {}) {
|
|
|
412
413
|
// - If already absolute, use as-is
|
|
413
414
|
// - If it's a simple command (no path separators), let system find it via PATH
|
|
414
415
|
// - Otherwise, resolve relative to cwd
|
|
415
|
-
const isRelativePath = !
|
|
416
|
+
const isRelativePath = !isFullyQualified(command) &&
|
|
416
417
|
(command.includes(path.sep) || command.includes("/"));
|
|
417
418
|
const explicitCommand = isRelativePath ? path.resolve(cwd, command) : command;
|
|
418
|
-
const resolvedCommand = !
|
|
419
|
+
const resolvedCommand = !isFullyQualified(command) &&
|
|
419
420
|
!command.includes(path.sep) &&
|
|
420
421
|
!command.includes("/")
|
|
421
422
|
? (findBinaryOnPath(command, env) ?? explicitCommand)
|
|
@@ -428,7 +429,7 @@ export async function launchLSP(command, args = [], options = {}) {
|
|
|
428
429
|
// If command not found, try npm global as fallback (handles PATH caching after install)
|
|
429
430
|
let spawnCommand = resolvedCommand;
|
|
430
431
|
// First, try to find in npm global if it's a simple command name
|
|
431
|
-
if (!
|
|
432
|
+
if (!isFullyQualified(command) &&
|
|
432
433
|
!command.includes(path.sep) &&
|
|
433
434
|
!command.includes("/")) {
|
|
434
435
|
const globalBinPath = await findGlobalBinary(command);
|
|
@@ -444,7 +445,8 @@ export async function launchLSP(command, args = [], options = {}) {
|
|
|
444
445
|
/\.(cmd|bat)$/i.test(spawnCommand) &&
|
|
445
446
|
!isCmdShimValid(spawnCommand)) {
|
|
446
447
|
logSessionStart(`lsp cmd-shim-invalid: ${spawnCommand} target missing — skipping candidate`);
|
|
447
|
-
|
|
448
|
+
const cause = Object.assign(new Error(`LSP .cmd shim target not found: ${spawnCommand}`), { code: "ENOENT" });
|
|
449
|
+
throw await classifySpawnFailure(cause, { command, cwd });
|
|
448
450
|
}
|
|
449
451
|
// P0 FIX: Never spawn .ps1 wrappers on Windows — they hang when PowerShell
|
|
450
452
|
// execution policy is Restricted/AllSigned. Prefer .cmd or direct node.
|
|
@@ -466,7 +468,7 @@ export async function launchLSP(command, args = [], options = {}) {
|
|
|
466
468
|
}
|
|
467
469
|
catch (err) {
|
|
468
470
|
// If spawn failed with simple command, try npm global
|
|
469
|
-
if (!
|
|
471
|
+
if (!isFullyQualified(command) &&
|
|
470
472
|
!command.includes(path.sep) &&
|
|
471
473
|
!command.includes("/")) {
|
|
472
474
|
const globalBinPath = await findGlobalBinary(command);
|
|
@@ -476,11 +478,11 @@ export async function launchLSP(command, args = [], options = {}) {
|
|
|
476
478
|
proc = trySpawn(globalBinPath, args, cwd, env, needsShellGlobal);
|
|
477
479
|
}
|
|
478
480
|
else {
|
|
479
|
-
throw err;
|
|
481
|
+
throw await classifySpawnFailure(err, { command, cwd });
|
|
480
482
|
}
|
|
481
483
|
}
|
|
482
484
|
else {
|
|
483
|
-
throw err;
|
|
485
|
+
throw await classifySpawnFailure(err, { command, cwd });
|
|
484
486
|
}
|
|
485
487
|
}
|
|
486
488
|
if (!proc.stdin || !proc.stdout || !proc.stderr) {
|
|
@@ -512,10 +514,14 @@ export async function launchLSP(command, args = [], options = {}) {
|
|
|
512
514
|
let settled = false;
|
|
513
515
|
// Attach error handler that can reject for immediate errors
|
|
514
516
|
proc.on("error", (err) => {
|
|
515
|
-
if (!settled
|
|
517
|
+
if (!settled) {
|
|
516
518
|
settled = true;
|
|
517
|
-
|
|
518
|
-
|
|
519
|
+
void classifySpawnFailure(err, { command, cwd }).then((failure) => {
|
|
520
|
+
const detail = formatStartupStderr(startupStderr);
|
|
521
|
+
reject(detail
|
|
522
|
+
? new SpawnFailureError(failure.kind, `${failure.message}${detail}`, failure.cause)
|
|
523
|
+
: failure);
|
|
524
|
+
});
|
|
519
525
|
}
|
|
520
526
|
});
|
|
521
527
|
// Also listen for immediate exit
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
* Re-export from shared path-utils.
|
|
3
3
|
* Kept as a local module for LSP imports that use relative paths.
|
|
4
4
|
*/
|
|
5
|
-
export { isUnderDir, normalizeFilePath, normalizeMapKey, pathsEqual, pathToUri, uriToPath, } from "../path-utils.js";
|
|
5
|
+
export { isUnderDir, normalizeFilePath, normalizeEphemeralMapKey, normalizeMapKey, pathsEqual, pathToUri, uriToDiskPath, uriToPath, } from "../path-utils.js";
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Windows Ruby-installer drive-root version-dir discovery (refs #1137).
|
|
3
|
+
*
|
|
4
|
+
* The Ruby installer drops versioned directories (e.g. `Ruby34-x64`) directly
|
|
5
|
+
* at a drive root, and the drive and version suffix vary — so both the LSP
|
|
6
|
+
* candidate builder (`rubyBinCandidates`) and the spawn PATH augmenter
|
|
7
|
+
* (`buildAugmentedPath`) scan the drive root for `ruby<N>...` directories.
|
|
8
|
+
*
|
|
9
|
+
* Enumerating a drive root **synchronously on every LSP spawn** was a genuine
|
|
10
|
+
* #1137 event-loop offender: a stalled cloud/network-backed drive-root
|
|
11
|
+
* `readdirSync` blocks the Node loop (and pi's TUI) for the entire stall. This
|
|
12
|
+
* module makes that enumeration:
|
|
13
|
+
* 1. **O(1) amortized** — the matching directory names are memoized for the
|
|
14
|
+
* process (the set is static for a session; the previous inline code
|
|
15
|
+
* equally required a restart to notice a new Ruby install on PATH), and
|
|
16
|
+
* 2. **non-blocking on the hot path** — `buildAugmentedPath` (called on every
|
|
17
|
+
* spawn) reads the drive root via `fs.promises.readdir` off the loop.
|
|
18
|
+
*
|
|
19
|
+
* Both readers share ONE cache keyed by `driveRoot`, so at most one drive-root
|
|
20
|
+
* read happens per drive per process regardless of which reader runs first.
|
|
21
|
+
* Callers derive their own paths from the returned directory NAMES (the two
|
|
22
|
+
* readers build different suffixes: `bin/<tool>` vs `bin`), so only the raw
|
|
23
|
+
* `ruby<N>` names are cached.
|
|
24
|
+
*/
|
|
25
|
+
import { BoundedLruCache } from "../bounded-cache.js";
|
|
26
|
+
import { normalizeMapKey } from "../path-utils.js";
|
|
27
|
+
import * as fs from "node:fs";
|
|
28
|
+
/** Matches the Ruby installer's `Ruby34-x64`, `ruby3.3`, … drive-root dirs. */
|
|
29
|
+
const RUBY_VERSION_DIR = /^ruby\d/i;
|
|
30
|
+
/** Process-lifetime memo of matching dir names, keyed by drive root (e.g. `C:\`). */
|
|
31
|
+
const rubyDirNamesCache = new BoundedLruCache(16);
|
|
32
|
+
function rubyDriveKey(driveRoot) {
|
|
33
|
+
return normalizeMapKey(driveRoot);
|
|
34
|
+
}
|
|
35
|
+
function filterRubyDirNames(entries) {
|
|
36
|
+
return entries.filter((name) => RUBY_VERSION_DIR.test(name));
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Memoized **synchronous** enumeration for sync callers (`rubyBinCandidates`).
|
|
40
|
+
* First call reads the drive root once (fail-open to `[]` on an unreadable
|
|
41
|
+
* root); every later call is a cache hit. If an async caller populated the
|
|
42
|
+
* cache first, this returns that result with no drive-root read at all.
|
|
43
|
+
*/
|
|
44
|
+
export function getRubyVersionDirNamesSync(driveRoot) {
|
|
45
|
+
const key = rubyDriveKey(driveRoot);
|
|
46
|
+
const cached = rubyDirNamesCache.get(key);
|
|
47
|
+
if (cached !== undefined)
|
|
48
|
+
return cached;
|
|
49
|
+
let names;
|
|
50
|
+
try {
|
|
51
|
+
names = filterRubyDirNames(fs.readdirSync(driveRoot));
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
names = [];
|
|
55
|
+
}
|
|
56
|
+
rubyDirNamesCache.set(key, names);
|
|
57
|
+
return names;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Memoized **async** twin for the hot spawn path (`buildAugmentedPath`) — reads
|
|
61
|
+
* the drive root via `fs.promises.readdir` so a slow/stalled cloud-backed drive
|
|
62
|
+
* root never blocks the event loop. Shares the same process cache as the sync
|
|
63
|
+
* reader (identical content, so a benign sync/async populate race is fine).
|
|
64
|
+
*/
|
|
65
|
+
export async function getRubyVersionDirNamesAsync(driveRoot) {
|
|
66
|
+
const key = rubyDriveKey(driveRoot);
|
|
67
|
+
const cached = rubyDirNamesCache.get(key);
|
|
68
|
+
if (cached !== undefined)
|
|
69
|
+
return cached;
|
|
70
|
+
let names;
|
|
71
|
+
try {
|
|
72
|
+
names = filterRubyDirNames(await fs.promises.readdir(driveRoot));
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
names = [];
|
|
76
|
+
}
|
|
77
|
+
rubyDirNamesCache.set(key, names);
|
|
78
|
+
return names;
|
|
79
|
+
}
|
|
80
|
+
/** Test-only: clear the process memo so each case starts cold. */
|
|
81
|
+
export function __resetRubyDriveDirsCacheForTest() {
|
|
82
|
+
rubyDirNamesCache.clear();
|
|
83
|
+
}
|
|
@@ -6,13 +6,14 @@
|
|
|
6
6
|
* - Auto-installation strategies
|
|
7
7
|
* - Platform-specific handling
|
|
8
8
|
*/
|
|
9
|
-
import { existsSync, mkdirSync
|
|
9
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
10
10
|
import { access, readFile, readdir, stat, } from "node:fs/promises";
|
|
11
11
|
import os from "node:os";
|
|
12
12
|
import path from "node:path";
|
|
13
|
-
import { getGlobalPiLensDir } from "../file-utils.js";
|
|
13
|
+
import { getGlobalPiLensDir, getProjectIgnoreGlobs, isPathIgnoredByProject, } from "../file-utils.js";
|
|
14
|
+
import { STAGE_TMP_PATTERN } from "../atomic-write-staging.js";
|
|
14
15
|
import { DOTNET_CSHARP_ROOT_MARKERS, DOTNET_FSHARP_ROOT_MARKERS, KIND_EXTENSIONS, } from "../file-kinds.js";
|
|
15
|
-
import { direntsHaveMarkerGlobMatch, isAtOrAboveHomeDir, } from "../path-utils.js";
|
|
16
|
+
import { direntsHaveMarkerGlobMatch, isAtOrAboveHomeDir, isFullyQualified, } from "../path-utils.js";
|
|
16
17
|
import { ensureTool, getToolEnvironment, getToolPath, } from "../installer/index.js";
|
|
17
18
|
import { resolveOpengrepConfig } from "../opengrep-config.js";
|
|
18
19
|
import { isZizmorAuditTarget, resolveZizmorGitHubToken } from "../zizmor-config.js";
|
|
@@ -22,40 +23,198 @@ import { findLocalSgconfig, resolveBaselineSgconfig } from "../sgconfig.js";
|
|
|
22
23
|
import { findLocalTyposConfig } from "../typos-config.js";
|
|
23
24
|
import { resolvePackagePath } from "../package-root.js";
|
|
24
25
|
import { resolveAstGrepNativeExe } from "./wait-policy/index.js";
|
|
25
|
-
import { isCommandAvailableAsync, safeSpawnAsync } from "../safe-spawn.js";
|
|
26
|
+
import { hasSpawnFailureKind, isCommandAvailableAsync, safeSpawnAsync, } from "../safe-spawn.js";
|
|
26
27
|
import { launchLSP } from "./launch.js";
|
|
27
28
|
import { createLombokJdtlsArgs } from "./lombok.js";
|
|
28
29
|
import { resolveJavaRuntimeEnv } from "./jvm-runtime.js";
|
|
29
30
|
import { normalizeMapKey } from "./path-utils.js";
|
|
31
|
+
import { getRubyVersionDirNamesSync } from "./ruby-drive-dirs.js";
|
|
32
|
+
const FIXTURE_ROOT_SEGMENTS = new Set(["__fixtures__", "testdata"]);
|
|
33
|
+
const FALLBACK_PROJECT_MARKERS = [
|
|
34
|
+
".git",
|
|
35
|
+
"package.json",
|
|
36
|
+
"go.work",
|
|
37
|
+
"go.mod",
|
|
38
|
+
"Cargo.toml",
|
|
39
|
+
"pyproject.toml",
|
|
40
|
+
"pom.xml",
|
|
41
|
+
"build.gradle",
|
|
42
|
+
"build.gradle.kts",
|
|
43
|
+
"mix.exs",
|
|
44
|
+
"pubspec.yaml",
|
|
45
|
+
"Package.swift",
|
|
46
|
+
];
|
|
47
|
+
/**
|
|
48
|
+
* Markers that make a nested root an independently hosted project rather than
|
|
49
|
+
* a config-only directory that should share an ancestor's LSP client.
|
|
50
|
+
*/
|
|
51
|
+
const PROJECT_BOUNDARY_MARKERS = [
|
|
52
|
+
"package.json",
|
|
53
|
+
"package-lock.json",
|
|
54
|
+
"pnpm-lock.yaml",
|
|
55
|
+
"yarn.lock",
|
|
56
|
+
"bun.lock",
|
|
57
|
+
"bun.lockb",
|
|
58
|
+
"go.work",
|
|
59
|
+
"go.mod",
|
|
60
|
+
"Cargo.toml",
|
|
61
|
+
"Cargo.lock",
|
|
62
|
+
"pyproject.toml",
|
|
63
|
+
"uv.lock",
|
|
64
|
+
"poetry.lock",
|
|
65
|
+
"Pipfile",
|
|
66
|
+
"Pipfile.lock",
|
|
67
|
+
"pom.xml",
|
|
68
|
+
"build.gradle",
|
|
69
|
+
"build.gradle.kts",
|
|
70
|
+
"settings.gradle",
|
|
71
|
+
"settings.gradle.kts",
|
|
72
|
+
"mix.exs",
|
|
73
|
+
"pubspec.yaml",
|
|
74
|
+
"Package.swift",
|
|
75
|
+
];
|
|
76
|
+
// This is process-global state, so each candidate pair is logged once per
|
|
77
|
+
// process (not once per session). Keep the root-boundary marker below aligned
|
|
78
|
+
// with FALLBACK_PROJECT_MARKERS, the shared fallback root-policy marker set.
|
|
79
|
+
const loggedRootCeilingClamps = new Set();
|
|
80
|
+
function isSameOrWithin(ancestor, candidate) {
|
|
81
|
+
const relative = path.relative(path.resolve(ancestor), path.resolve(candidate));
|
|
82
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
83
|
+
}
|
|
84
|
+
/** Enforce the session cwd as the hard boundary for LSP client root selection. */
|
|
85
|
+
export function enforceLspRootCeiling(root, sessionCwd, filePath) {
|
|
86
|
+
const resolvedRoot = path.resolve(root);
|
|
87
|
+
const resolvedCwd = path.resolve(sessionCwd);
|
|
88
|
+
// Callers may explicitly inspect an out-of-session file (notably isolated
|
|
89
|
+
// tests and API consumers). The cwd ceiling governs roots for files that are
|
|
90
|
+
// actually inside the declared session project.
|
|
91
|
+
if (filePath && !isSameOrWithin(resolvedCwd, path.resolve(filePath))) {
|
|
92
|
+
return resolvedRoot;
|
|
93
|
+
}
|
|
94
|
+
if (isSameOrWithin(resolvedCwd, resolvedRoot))
|
|
95
|
+
return resolvedRoot;
|
|
96
|
+
const logKey = `${normalizeMapKey(resolvedCwd)}:${normalizeMapKey(resolvedRoot)}`;
|
|
97
|
+
if (!loggedRootCeilingClamps.has(logKey)) {
|
|
98
|
+
loggedRootCeilingClamps.add(logKey);
|
|
99
|
+
logSessionStart(`lsp root clamped to session cwd: candidate=${resolvedRoot} cwd=${resolvedCwd}`);
|
|
100
|
+
}
|
|
101
|
+
return resolvedCwd;
|
|
102
|
+
}
|
|
103
|
+
export async function hasProjectBoundaryMarker(dir) {
|
|
104
|
+
// A nested Git checkout is an independently hosted project even when it has
|
|
105
|
+
// no language manifest. Keep this directory boundary aligned with the shared
|
|
106
|
+
// FALLBACK_PROJECT_MARKERS policy used by nearestNonExcludedFallbackRoot.
|
|
107
|
+
if (await markerExists(dir, ".git"))
|
|
108
|
+
return true;
|
|
109
|
+
for (const marker of PROJECT_BOUNDARY_MARKERS) {
|
|
110
|
+
if (await markerExists(dir, marker))
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
function pathSegments(dir) {
|
|
116
|
+
const parsed = path.parse(path.resolve(dir));
|
|
117
|
+
return path
|
|
118
|
+
.relative(parsed.root, path.resolve(dir))
|
|
119
|
+
.split(path.sep)
|
|
120
|
+
.filter(Boolean)
|
|
121
|
+
.map((segment) => segment.toLowerCase());
|
|
122
|
+
}
|
|
123
|
+
function hasFixtureConvention(dir) {
|
|
124
|
+
const segments = pathSegments(dir);
|
|
125
|
+
// Go treats any directory named testdata as fixture data by convention. The
|
|
126
|
+
// exclusion is intentionally ancestor-wide so nested fixture projects cannot
|
|
127
|
+
// become independent LSP roots, but the segment match itself stays exact.
|
|
128
|
+
if (segments.some((segment) => FIXTURE_ROOT_SEGMENTS.has(segment)))
|
|
129
|
+
return true;
|
|
130
|
+
return segments.some((segment, index) => segment === "tests" && segments[index + 1] === "fixtures");
|
|
131
|
+
}
|
|
132
|
+
function hasAtomicStageSegment(dir) {
|
|
133
|
+
return pathSegments(dir).some((segment) => STAGE_TMP_PATTERN.test(segment));
|
|
134
|
+
}
|
|
135
|
+
async function findGitBoundary(dir) {
|
|
136
|
+
let current = path.resolve(dir);
|
|
137
|
+
const fsRoot = path.parse(current).root;
|
|
138
|
+
while (true) {
|
|
139
|
+
if (await markerExists(current, ".git"))
|
|
140
|
+
return current;
|
|
141
|
+
if (current === fsRoot)
|
|
142
|
+
return undefined;
|
|
143
|
+
current = path.dirname(current);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async function isExcludedLspRoot(dir) {
|
|
147
|
+
const candidate = path.resolve(dir);
|
|
148
|
+
if (hasFixtureConvention(candidate) || hasAtomicStageSegment(candidate))
|
|
149
|
+
return true;
|
|
150
|
+
const gitRoot = await findGitBoundary(candidate);
|
|
151
|
+
if (!gitRoot || gitRoot === candidate)
|
|
152
|
+
return false;
|
|
153
|
+
// Avoid constructing the matcher when the project has no positive ignore rules.
|
|
154
|
+
// The matcher remains authoritative (including anchored rules and directory form).
|
|
155
|
+
if (getProjectIgnoreGlobs(gitRoot).length === 0)
|
|
156
|
+
return false;
|
|
157
|
+
return isPathIgnoredByProject(candidate, gitRoot, true);
|
|
158
|
+
}
|
|
159
|
+
async function nearestNonExcludedFallbackRoot(candidate) {
|
|
160
|
+
if (!(await isExcludedLspRoot(candidate)))
|
|
161
|
+
return path.resolve(candidate);
|
|
162
|
+
let current = path.dirname(path.resolve(candidate));
|
|
163
|
+
const fsRoot = path.parse(current).root;
|
|
164
|
+
let nearestAllowed;
|
|
165
|
+
while (true) {
|
|
166
|
+
if (!(await isExcludedLspRoot(current))) {
|
|
167
|
+
nearestAllowed ??= current;
|
|
168
|
+
for (const marker of FALLBACK_PROJECT_MARKERS) {
|
|
169
|
+
if (await markerExists(current, marker))
|
|
170
|
+
return current;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (current === fsRoot)
|
|
174
|
+
break;
|
|
175
|
+
current = path.dirname(current);
|
|
176
|
+
}
|
|
177
|
+
// No project marker was available. Keep the file attached to a stable,
|
|
178
|
+
// non-excluded ancestor rather than minting a client inside the fixture/stage.
|
|
179
|
+
return nearestAllowed ?? fsRoot;
|
|
180
|
+
}
|
|
30
181
|
function isLspInstallDisabled() {
|
|
31
182
|
return process.env.PI_LENS_DISABLE_LSP_INSTALL === "1";
|
|
32
183
|
}
|
|
33
184
|
function canInstall(allowInstall) {
|
|
34
185
|
return allowInstall !== false && !isLspInstallDisabled();
|
|
35
186
|
}
|
|
36
|
-
function isCommandNotFoundError(error) {
|
|
37
|
-
const msg = String(error);
|
|
38
|
-
return (msg.includes("not found") ||
|
|
39
|
-
msg.includes("ENOENT") ||
|
|
40
|
-
msg.includes("not recognized"));
|
|
41
|
-
}
|
|
42
187
|
const DIRECT_LSP_NEGATIVE_TTL_MS = Math.max(30_000, Number.parseInt(process.env.PI_LENS_DIRECT_LSP_NEGATIVE_TTL_MS ?? "600000", 10) || 600_000);
|
|
43
188
|
const directLspCommandUnavailableUntil = new Map();
|
|
44
189
|
const directLspCommandSkipLoggedUntil = new Map();
|
|
190
|
+
function pruneExpiredDirectLspNegativeEntries(now = Date.now()) {
|
|
191
|
+
for (const [command, until] of directLspCommandUnavailableUntil) {
|
|
192
|
+
if (until <= now) {
|
|
193
|
+
directLspCommandUnavailableUntil.delete(command);
|
|
194
|
+
directLspCommandSkipLoggedUntil.delete(command);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
for (const [command, until] of directLspCommandSkipLoggedUntil) {
|
|
198
|
+
if (until <= now)
|
|
199
|
+
directLspCommandSkipLoggedUntil.delete(command);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
45
202
|
function isSimpleCommand(command) {
|
|
46
|
-
return (!
|
|
203
|
+
return (!isFullyQualified(command) &&
|
|
47
204
|
!command.includes("/") &&
|
|
48
205
|
!command.includes("\\"));
|
|
49
206
|
}
|
|
50
207
|
export function isDirectLspCommandTemporarilyUnavailable(command) {
|
|
208
|
+
const now = Date.now();
|
|
209
|
+
pruneExpiredDirectLspNegativeEntries(now);
|
|
51
210
|
const until = directLspCommandUnavailableUntil.get(command);
|
|
52
|
-
if (!until || until <=
|
|
211
|
+
if (!until || until <= now) {
|
|
53
212
|
directLspCommandUnavailableUntil.delete(command);
|
|
54
213
|
return false;
|
|
55
214
|
}
|
|
56
215
|
const loggedUntil = directLspCommandSkipLoggedUntil.get(command) ?? 0;
|
|
57
|
-
if (loggedUntil <=
|
|
58
|
-
logSessionStart(`lsp direct command ${command}: skipped by negative availability cache (${Math.max(0, until -
|
|
216
|
+
if (loggedUntil <= now) {
|
|
217
|
+
logSessionStart(`lsp direct command ${command}: skipped by negative availability cache (${Math.max(0, until - now)}ms remaining)`);
|
|
59
218
|
directLspCommandSkipLoggedUntil.set(command, until);
|
|
60
219
|
}
|
|
61
220
|
return true;
|
|
@@ -74,7 +233,7 @@ export async function resolveAndLaunch(spec, allowInstall) {
|
|
|
74
233
|
let lastRuntimeFailure;
|
|
75
234
|
const trackRuntimeFailure = (err) => {
|
|
76
235
|
const message = err instanceof Error ? err.message : String(err);
|
|
77
|
-
if (!
|
|
236
|
+
if (!hasSpawnFailureKind(err, "tool-not-found")) {
|
|
78
237
|
lastRuntimeFailure = err instanceof Error ? err : new Error(message);
|
|
79
238
|
}
|
|
80
239
|
};
|
|
@@ -145,6 +304,12 @@ export async function resolveAndLaunch(spec, allowInstall) {
|
|
|
145
304
|
logSessionStart(`lsp launch candidate failed tool=${toolLabel} idx=${failure.index} command=${failure.command} error=${failure.message}`);
|
|
146
305
|
trackRuntimeFailure(failure.err);
|
|
147
306
|
}
|
|
307
|
+
const hasOnlyRepairableCandidateFailures = candidateFailures.every((failure) => hasSpawnFailureKind(failure.err, "tool-not-found"));
|
|
308
|
+
if (!hasOnlyRepairableCandidateFailures) {
|
|
309
|
+
if (lastRuntimeFailure)
|
|
310
|
+
throw lastRuntimeFailure;
|
|
311
|
+
return undefined;
|
|
312
|
+
}
|
|
148
313
|
if (!canInstall(allowInstall)) {
|
|
149
314
|
logSessionStart(`lsp launch install blocked tool=${toolLabel} cwd=${spec.cwd} allowInstall=${allowInstall !== false} globalDisabled=${isLspInstallDisabled()}`);
|
|
150
315
|
logLatency({
|
|
@@ -214,7 +379,7 @@ export async function resolveAndLaunch(spec, allowInstall) {
|
|
|
214
379
|
// fails to launch (e.g. broken symlink, missing .dll), nuke the
|
|
215
380
|
// caches and download a managed copy from the registry.
|
|
216
381
|
const looksPathResolved = !installed.includes("/") && !installed.includes("\\");
|
|
217
|
-
if (looksPathResolved) {
|
|
382
|
+
if (looksPathResolved && hasSpawnFailureKind(err, "tool-not-found")) {
|
|
218
383
|
logSessionStart(`lsp launch managed retry force-reinstall tool=${spec.managedToolId}`);
|
|
219
384
|
const reinstalled = await ensureTool(spec.managedToolId, {
|
|
220
385
|
forceReinstall: true,
|
|
@@ -415,19 +580,13 @@ function rubyBinCandidates(baseName) {
|
|
|
415
580
|
candidates.push(path.join(home, ".asdf", "installs", "ruby", "bin", `${baseName}${ext}`));
|
|
416
581
|
if (isWin) {
|
|
417
582
|
// Ruby installer drops versioned dirs on C: by convention, but the drive
|
|
418
|
-
// and version suffix vary — scan what's actually present instead of
|
|
583
|
+
// and version suffix vary — scan what's actually present instead of
|
|
584
|
+
// hardcoding. Memoized once per process (#1137): a synchronous drive-root
|
|
585
|
+
// enumeration per LSP spawn was an event-loop offender.
|
|
419
586
|
const driveRoot = path.parse(home).root; // e.g. "C:\"
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
if (/^ruby\d/i.test(entry)) {
|
|
424
|
-
candidates.push(path.join(driveRoot, entry, "bin", `${baseName}.bat`));
|
|
425
|
-
candidates.push(path.join(driveRoot, entry, "bin", baseName));
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
catch {
|
|
430
|
-
// drive root not readable — skip
|
|
587
|
+
for (const entry of getRubyVersionDirNamesSync(driveRoot)) {
|
|
588
|
+
candidates.push(path.join(driveRoot, entry, "bin", `${baseName}.bat`));
|
|
589
|
+
candidates.push(path.join(driveRoot, entry, "bin", baseName));
|
|
431
590
|
}
|
|
432
591
|
}
|
|
433
592
|
return candidates;
|
|
@@ -465,7 +624,7 @@ function createInteractiveServer(spec) {
|
|
|
465
624
|
return { process: proc, source: "direct", initialization };
|
|
466
625
|
}
|
|
467
626
|
catch (err) {
|
|
468
|
-
if (
|
|
627
|
+
if (hasSpawnFailureKind(err, "tool-not-found")) {
|
|
469
628
|
markDirectLspCommandUnavailable(command);
|
|
470
629
|
}
|
|
471
630
|
return undefined;
|
|
@@ -484,7 +643,10 @@ export function PriorityRoot(markerGroups, excludePatterns, stopDir) {
|
|
|
484
643
|
return undefined;
|
|
485
644
|
};
|
|
486
645
|
}
|
|
487
|
-
export const FileDirRoot = async (file) =>
|
|
646
|
+
export const FileDirRoot = async (file) => {
|
|
647
|
+
const candidate = path.resolve(path.dirname(file));
|
|
648
|
+
return nearestNonExcludedFallbackRoot(candidate);
|
|
649
|
+
};
|
|
488
650
|
export function RootWithFallback(primary, fallback = FileDirRoot) {
|
|
489
651
|
return async (file) => {
|
|
490
652
|
const primaryRoot = await primary(file);
|
|
@@ -597,8 +759,10 @@ export function NearestRoot(includePatterns, excludePatterns, stopDir) {
|
|
|
597
759
|
// Check include patterns. Exact marker names stay cheap (`stat`), while
|
|
598
760
|
// glob markers like `*.csproj` match real project filenames (#201).
|
|
599
761
|
for (const pattern of includePatterns) {
|
|
600
|
-
if (await markerExists(currentDir, pattern))
|
|
601
|
-
|
|
762
|
+
if ((await markerExists(currentDir, pattern)) &&
|
|
763
|
+
!(await isExcludedLspRoot(currentDir))) {
|
|
764
|
+
return enforceLspRootCeiling(currentDir, process.cwd(), file);
|
|
765
|
+
}
|
|
602
766
|
}
|
|
603
767
|
if (currentDir === stop || currentDir === fsRoot) {
|
|
604
768
|
break;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { getProjectDataDir } from "../file-utils.js";
|
|
4
|
+
import { writeFileAtomic } from "../atomic-write.js";
|
|
4
5
|
import { readJsonCache } from "../json-cache-read.js";
|
|
5
6
|
import { normalizeMapKey } from "../path-utils.js";
|
|
6
7
|
import { loadReverseDependencyIndexFromSnapshot } from "../reverse-deps.js";
|
|
8
|
+
import { createDiskBindingCache, } from "./diagnostic-binding.js";
|
|
7
9
|
/**
|
|
8
10
|
* #671: per-file cache of the last CONFIRMED `runWorkspaceDiagnostics` sweep
|
|
9
11
|
* result, so a repeat `lens_diagnostics mode=full` with no intervening edits
|
|
@@ -24,7 +26,12 @@ import { loadReverseDependencyIndexFromSnapshot } from "../reverse-deps.js";
|
|
|
24
26
|
* `LSPWorkspaceDiagnosticResult.timedOut` in `./index.ts`) into
|
|
25
27
|
* `WorkspaceDiagnosticsCacheEntry`.
|
|
26
28
|
*/
|
|
27
|
-
|
|
29
|
+
// v2 (#1095): entries may carry an optional `contentHash` fingerprint of the
|
|
30
|
+
// file bytes the cached diagnostics were computed against, so a `lookup` can
|
|
31
|
+
// surface a content `binding` (boundToCurrentDisk) beyond the mtime proxy.
|
|
32
|
+
// Legacy v1 entries are rejected by the version guard and re-scanned — a
|
|
33
|
+
// deliberately clean break so no entry lacking the field is ever served.
|
|
34
|
+
export const WORKSPACE_DIAGNOSTICS_CACHE_VERSION = 2;
|
|
28
35
|
const CACHE_FILE = "lsp-workspace-diagnostics.json";
|
|
29
36
|
function cachePath(cwd) {
|
|
30
37
|
return path.join(getProjectDataDir(cwd), "cache", CACHE_FILE);
|
|
@@ -48,7 +55,11 @@ export function loadWorkspaceDiagnosticsCache(cwd) {
|
|
|
48
55
|
export function saveWorkspaceDiagnosticsCache(cwd, cache) {
|
|
49
56
|
const filePath = cachePath(cwd);
|
|
50
57
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
51
|
-
|
|
58
|
+
// bestEffort: false — persist()'s catch is the error policy here, and it
|
|
59
|
+
// must observe the failure so `dirty` stays set and the next sweep retries.
|
|
60
|
+
writeFileAtomic(filePath, JSON.stringify(cache, null, 2), {
|
|
61
|
+
bestEffort: false,
|
|
62
|
+
});
|
|
52
63
|
}
|
|
53
64
|
/**
|
|
54
65
|
* True when `filePath`'s cached entry is still trustworthy enough to reuse
|
|
@@ -133,6 +144,8 @@ export function createWorkspaceDiagnosticsCacheContext(cwd) {
|
|
|
133
144
|
return undefined;
|
|
134
145
|
return reverseDepsIndex.imports[cacheKeyFor(filePath)] ?? [];
|
|
135
146
|
};
|
|
147
|
+
// #1095: per-sweep disk-fingerprint memo (per file+mtime) for binding verify.
|
|
148
|
+
const diskBindingCache = createDiskBindingCache();
|
|
136
149
|
let dirty = false;
|
|
137
150
|
return {
|
|
138
151
|
lookup(filePath, scopeKey) {
|
|
@@ -141,15 +154,26 @@ export function createWorkspaceDiagnosticsCacheContext(cwd) {
|
|
|
141
154
|
return undefined;
|
|
142
155
|
if (!isEntryFresh(filePath, entry, getImports))
|
|
143
156
|
return undefined;
|
|
144
|
-
return {
|
|
157
|
+
return {
|
|
158
|
+
diagnostics: entry.diagnostics,
|
|
159
|
+
count: entry.count,
|
|
160
|
+
binding: {
|
|
161
|
+
contentHash: entry.contentHash,
|
|
162
|
+
boundToCurrentDisk: diskBindingCache.boundToCurrentDisk(filePath, {
|
|
163
|
+
contentHash: entry.contentHash,
|
|
164
|
+
}),
|
|
165
|
+
},
|
|
166
|
+
scannedAt: entry.scannedAt,
|
|
167
|
+
};
|
|
145
168
|
},
|
|
146
|
-
record(filePath, scopeKey, diagnostics, mtimeMs) {
|
|
169
|
+
record(filePath, scopeKey, diagnostics, mtimeMs, contentHash) {
|
|
147
170
|
entries[cacheKeyFor(filePath)] = {
|
|
148
171
|
diagnostics,
|
|
149
172
|
count: diagnostics.length,
|
|
150
173
|
mtimeMs,
|
|
151
174
|
scannedAt: Date.now(),
|
|
152
175
|
scopeKey,
|
|
176
|
+
...(contentHash !== undefined && { contentHash }),
|
|
153
177
|
};
|
|
154
178
|
dirty = true;
|
|
155
179
|
},
|