@herbertgao/pi-extensions 2026.8.5 → 2026.8.7
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 +5 -5
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +19 -7
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +30 -9
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +272 -22
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +74 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +94 -77
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +48 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +14 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +55 -17
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +41 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +75 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +7 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +64 -1
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +16 -0
- package/node_modules/@herbertgao/pi-subagents/README.md +30 -22
- package/node_modules/@herbertgao/pi-subagents/examples/agent-tool-description.md +5 -5
- package/node_modules/@herbertgao/pi-subagents/package.json +7 -7
- package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +7 -1
- package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +48 -19
- package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +22 -15
- package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +26 -2
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +219 -40
- package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +101 -3
- package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +18 -4
- package/node_modules/@herbertgao/pi-subagents/src/settings.ts +99 -0
- package/node_modules/@herbertgao/pi-subagents/src/types.ts +22 -3
- package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +51 -3
- package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +20 -2
- package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +34 -10
- package/node_modules/@herbertgao/pi-subagents/src/usage.ts +129 -1
- package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +29 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +3 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +46 -10
- package/node_modules/@juicesharp/rpiv-ask-user-question/config.ts +25 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/configuration.md +13 -4
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +8 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +3 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/locales/zh.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +3 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +10 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +3 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +30 -7
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/state.ts +14 -6
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +30 -4
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +26 -5
- package/node_modules/@narumitw/pi-btw/README.md +119 -103
- package/node_modules/@narumitw/pi-btw/dist/index.ts +2898 -0
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +7 -0
- package/node_modules/@narumitw/pi-btw/package.json +13 -9
- package/node_modules/@narumitw/pi-btw/src/btw.ts +40 -42
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +55 -10
- package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +390 -0
- package/node_modules/@narumitw/pi-btw/src/menu.ts +78 -15
- package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
- package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +136 -45
- package/node_modules/pi-lens/CHANGELOG.md +1004 -0
- package/node_modules/pi-lens/README.md +11 -3
- package/node_modules/pi-lens/config/ruff/core.toml +42 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +130 -27
- package/node_modules/pi-lens/dist/clients/advisory-provenance.js +427 -0
- package/node_modules/pi-lens/dist/clients/agent-nudge.js +79 -16
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +9 -3
- package/node_modules/pi-lens/dist/clients/atomic-write.js +9 -3
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +285 -8
- package/node_modules/pi-lens/dist/clients/biome-client.js +176 -37
- package/node_modules/pi-lens/dist/clients/blocker-freshness.js +428 -0
- package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +119 -0
- package/node_modules/pi-lens/dist/clients/bootstrap.js +12 -1
- package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +12 -3
- package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +192 -0
- package/node_modules/pi-lens/dist/clients/bus-events-logger.js +62 -6
- package/node_modules/pi-lens/dist/clients/bus-publish.js +11 -3
- package/node_modules/pi-lens/dist/clients/cascade-budget.js +196 -0
- package/node_modules/pi-lens/dist/clients/cascade-format.js +76 -2
- package/node_modules/pi-lens/dist/clients/cascade-types.js +9 -1
- package/node_modules/pi-lens/dist/clients/child-unref.js +54 -6
- package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +76 -24
- package/node_modules/pi-lens/dist/clients/console-guard-install.js +16 -4
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +249 -52
- package/node_modules/pi-lens/dist/clients/deadline-utils.js +8 -1
- package/node_modules/pi-lens/dist/clients/debug-handles.js +5 -0
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +62 -13
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +43 -9
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +153 -37
- package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +298 -0
- package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +165 -11
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +325 -72
- package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +7 -4
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +5 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +234 -18
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +187 -19
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +6 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +11 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +398 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +11 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +12 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +18 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +895 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +4 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +16 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +81 -41
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +16 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +23 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +331 -55
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +38 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +97 -19
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +14 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +20 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +22 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +10 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +18 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +73 -12
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +488 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +84 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +260 -27
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +943 -93
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +86 -11
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +72 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +106 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +9 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +18 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +4 -2
- package/node_modules/pi-lens/dist/clients/disposition-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +28 -5
- package/node_modules/pi-lens/dist/clients/extension-log.js +317 -3
- package/node_modules/pi-lens/dist/clients/file-kinds.js +8 -2
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +294 -0
- package/node_modules/pi-lens/dist/clients/finding-identity.js +88 -0
- package/node_modules/pi-lens/dist/clients/fix-worklog.js +5 -1
- package/node_modules/pi-lens/dist/clients/format-events-publish.js +39 -8
- package/node_modules/pi-lens/dist/clients/formatters-lazy.js +3 -2
- package/node_modules/pi-lens/dist/clients/formatters.js +500 -115
- package/node_modules/pi-lens/dist/clients/generation-guard.js +216 -0
- package/node_modules/pi-lens/dist/clients/git-guard.js +49 -21
- package/node_modules/pi-lens/dist/clients/gitleaks-client.js +181 -8
- package/node_modules/pi-lens/dist/clients/go-client.js +21 -39
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +219 -10
- package/node_modules/pi-lens/dist/clients/grammar-source.js +333 -10
- package/node_modules/pi-lens/dist/clients/host-ports.js +9 -1
- package/node_modules/pi-lens/dist/clients/installer/index.js +1119 -101
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +58 -0
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +715 -0
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +643 -97
- package/node_modules/pi-lens/dist/clients/instance-registry.js +87 -17
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +86 -15
- package/node_modules/pi-lens/dist/clients/knip-client.js +361 -24
- package/node_modules/pi-lens/dist/clients/language-policy.js +8 -2
- package/node_modules/pi-lens/dist/clients/language-profile.js +2 -0
- package/node_modules/pi-lens/dist/clients/latency-logger.js +487 -9
- package/node_modules/pi-lens/dist/clients/lazy-import.js +21 -0
- package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -0
- package/node_modules/pi-lens/dist/clients/lens-events.js +56 -25
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +16 -0
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +45 -2
- package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +160 -20
- package/node_modules/pi-lens/dist/clients/lsp/client.js +1666 -148
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +111 -0
- package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +389 -0
- package/node_modules/pi-lens/dist/clients/lsp/index.js +2496 -171
- package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +215 -0
- package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +146 -14
- package/node_modules/pi-lens/dist/clients/lsp/language.js +48 -4
- package/node_modules/pi-lens/dist/clients/lsp/server.js +517 -69
- package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +22 -0
- package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +43 -0
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +172 -0
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +21 -5
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +61 -3
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +422 -28
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +30 -0
- package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +175 -0
- package/node_modules/pi-lens/dist/clients/lsp-lazy.js +3 -2
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +4 -0
- package/node_modules/pi-lens/dist/clients/mcp/session.js +39 -18
- package/node_modules/pi-lens/dist/clients/model-provider.js +53 -0
- package/node_modules/pi-lens/dist/clients/module-report.js +8 -0
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +15 -1
- package/node_modules/pi-lens/dist/clients/package-manager.js +170 -14
- package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +27 -0
- package/node_modules/pi-lens/dist/clients/path-utils.js +160 -0
- package/node_modules/pi-lens/dist/clients/pipeline.js +178 -30
- package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +37 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +17 -3
- package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +89 -22
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +85 -15
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +15 -3
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +23 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +50 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +5 -0
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +299 -0
- package/node_modules/pi-lens/dist/clients/quiet-window-config.js +47 -0
- package/node_modules/pi-lens/dist/clients/quiet-window.js +11 -25
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +1 -1
- package/node_modules/pi-lens/dist/clients/read-guard.js +184 -10
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +49 -17
- package/node_modules/pi-lens/dist/clients/reverse-deps.js +50 -11
- package/node_modules/pi-lens/dist/clients/review-graph/query.js +76 -4
- package/node_modules/pi-lens/dist/clients/run-duration.js +55 -0
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +248 -13
- package/node_modules/pi-lens/dist/clients/runtime-context.js +112 -12
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +472 -61
- package/node_modules/pi-lens/dist/clients/runtime-session.js +359 -13
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +217 -22
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +269 -22
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +948 -87
- package/node_modules/pi-lens/dist/clients/rust-client.js +21 -37
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +220 -7
- package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +161 -0
- package/node_modules/pi-lens/dist/clients/search-read-registration.js +34 -6
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +224 -8
- package/node_modules/pi-lens/dist/clients/sg-runner.js +249 -37
- package/node_modules/pi-lens/dist/clients/single-flight.js +158 -0
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +18 -11
- package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
- package/node_modules/pi-lens/dist/clients/startup-timing.js +26 -2
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +574 -32
- package/node_modules/pi-lens/dist/clients/tool-event.js +56 -5
- package/node_modules/pi-lens/dist/clients/tool-policy.js +127 -20
- package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +75 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +769 -64
- package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +11 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +90 -0
- package/node_modules/pi-lens/dist/clients/trivy-client.js +19 -1
- package/node_modules/pi-lens/dist/clients/warm-attach.js +17 -0
- package/node_modules/pi-lens/dist/clients/widget-state.js +336 -36
- package/node_modules/pi-lens/dist/clients/word-index.js +305 -33
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +214 -14
- package/node_modules/pi-lens/dist/index.js +64677 -49484
- package/node_modules/pi-lens/dist/mcp/server.js +8 -2
- package/node_modules/pi-lens/dist/tools/activate-tools.js +17 -5
- package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +9 -4
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +5 -2
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +469 -56
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +190 -46
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -7
- package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -1
- package/node_modules/pi-lens/docs/agent-guide.md +44 -14
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +518 -501
- package/node_modules/pi-lens/docs/dispositions.md +21 -0
- package/node_modules/pi-lens/docs/features.md +33 -6
- package/node_modules/pi-lens/docs/globalconfig.md +14 -0
- package/node_modules/pi-lens/docs/language-coverage.md +71 -5
- package/node_modules/pi-lens/docs/mcp.md +2 -2
- package/node_modules/pi-lens/docs/servercapabilities.md +4 -1
- package/node_modules/pi-lens/docs/settings.md +25 -1
- package/node_modules/pi-lens/docs/usage.md +24 -5
- package/node_modules/pi-lens/docs/word-index.md +35 -0
- package/node_modules/pi-lens/package.json +10 -6
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-js-snapshot.yml +23 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-snapshot.yml +23 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/ts-void-zero-snapshot.yml +23 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-object-param-test.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +22 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-compile-call-test.yml +29 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-js-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-dupe-class-members-test.yml +33 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-js-test.yml +7 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-test.yml +7 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-known-value-widening-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-non-null-assertion-test.yml +216 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-return-value-in-generator-test.yml +12 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-runtime-typeof-test.yml +33 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-server-bind-wildcard-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-shape-in-symbol-names-test.yml +13 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-js-test.yml +5 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-test.yml +5 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-laundering-test.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-parameters-test.yml +12 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-returns-test.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-any-test.yml +10 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-unknown-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/redundant-unsafe-function-test.yml +35 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/require-safety-comment-for-as-unknown-as-test.yml +177 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ts-void-zero-test.yml +1 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member-js.yml +13 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member.yml +13 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +2 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-object-param.yml +60 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +66 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-compile-call.yml +63 -5
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread-js.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread.yml +29 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-dupe-class-members.yml +52 -8
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html-js.yml +27 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html.yml +27 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-known-value-widening.yml +71 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-non-null-assertion.yml +316 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +29 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +30 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +41 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +42 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-return-value-in-generator.yml +22 -5
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-runtime-typeof.yml +121 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-server-bind-wildcard.yml +23 -7
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-shape-in-symbol-names.yml +68 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code-js.yml +41 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code.yml +41 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +32 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-parameters.yml +67 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-returns.yml +106 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-any.yml +50 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-unknown.yml +56 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +2 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/redundant-unsafe-function.yml +32 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/require-safety-comment-for-as-unknown-as.yml +184 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/ts-void-zero.yml +5 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/self-scan-baseline.json +5 -0
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +132 -1
- package/node_modules/pi-lens/scripts/download-grammars.js +25 -0
- package/node_modules/pi-lens/scripts/grammars.lock.json +9 -0
- package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +12 -11
- package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +22 -22
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +87 -116
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +170 -0
- package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
- package/node_modules/pi-lens/vendor/grammars/tree-sitter-cue.wasm +0 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +49 -0
- package/node_modules/pi-mcp-adapter/README.md +51 -6
- package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
- package/node_modules/pi-mcp-adapter/cli.js +188 -5
- package/node_modules/pi-mcp-adapter/commands.ts +128 -22
- package/node_modules/pi-mcp-adapter/config.ts +28 -10
- package/node_modules/pi-mcp-adapter/direct-tools.ts +23 -19
- package/node_modules/pi-mcp-adapter/index.ts +219 -32
- package/node_modules/pi-mcp-adapter/init.ts +36 -4
- package/node_modules/pi-mcp-adapter/lifecycle.ts +315 -24
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +39 -6
- package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +387 -0
- package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +27 -3
- package/node_modules/pi-mcp-adapter/mcp-code.ts +4 -0
- package/node_modules/pi-mcp-adapter/mcp-panel.ts +42 -13
- package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +2 -1
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +46 -25
- package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +168 -0
- package/node_modules/pi-mcp-adapter/package.json +4 -1
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +5 -4
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +330 -0
- package/node_modules/pi-mcp-adapter/search-ranking.ts +92 -22
- package/node_modules/pi-mcp-adapter/server-manager.ts +145 -2
- package/node_modules/pi-mcp-adapter/session-recovery.ts +7 -0
- package/node_modules/pi-mcp-adapter/tool-approval.ts +15 -2
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +38 -18
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +44 -4
- package/node_modules/pi-mcp-adapter/types.ts +95 -10
- package/node_modules/pi-mcp-adapter/ui-server.ts +18 -16
- package/node_modules/pi-mcp-adapter/utils.ts +70 -0
- package/node_modules/pi-web-access/CHANGELOG.md +37 -0
- package/node_modules/pi-web-access/README.md +38 -14
- package/node_modules/pi-web-access/anysearch.ts +4 -2
- package/node_modules/pi-web-access/auth-fetch.ts +148 -0
- package/node_modules/pi-web-access/brave.ts +16 -4
- package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
- package/node_modules/pi-web-access/curator-page.ts +8 -3
- package/node_modules/pi-web-access/curator-server.ts +5 -1
- package/node_modules/pi-web-access/exa.ts +18 -7
- package/node_modules/pi-web-access/extract.ts +134 -37
- package/node_modules/pi-web-access/fetch-params.ts +17 -3
- package/node_modules/pi-web-access/firecrawl.ts +172 -12
- package/node_modules/pi-web-access/gemini-search.ts +33 -7
- package/node_modules/pi-web-access/index.ts +160 -104
- package/node_modules/pi-web-access/package.json +2 -2
- package/node_modules/pi-web-access/page-query.ts +4 -2
- package/node_modules/pi-web-access/parallel-mcp.ts +249 -0
- package/node_modules/pi-web-access/parallel.ts +2 -2
- package/node_modules/pi-web-access/query-rewrite.ts +51 -0
- package/node_modules/pi-web-access/serper.ts +211 -0
- package/node_modules/pi-web-access/summary-model-scope.ts +12 -4
- package/node_modules/pi-web-access/summary-review.ts +51 -23
- package/node_modules/pi-web-access/tavily.ts +16 -4
- package/node_modules/pi-web-access/utils.ts +87 -0
- package/node_modules/pi-web-access/valyu.ts +199 -0
- package/node_modules/pi-web-access/youtube-extract.ts +2 -2
- package/package.json +13 -13
|
@@ -13,7 +13,7 @@ import path from "node:path";
|
|
|
13
13
|
import { getGlobalPiLensDir, getProjectIgnoreGlobs, isPathIgnoredByProject, } from "../file-utils.js";
|
|
14
14
|
import { STAGE_TMP_PATTERN } from "../atomic-write-staging.js";
|
|
15
15
|
import { DOTNET_CSHARP_ROOT_MARKERS, DOTNET_FSHARP_ROOT_MARKERS, KIND_EXTENSIONS, } from "../file-kinds.js";
|
|
16
|
-
import { direntsHaveMarkerGlobMatch, isAtOrAboveHomeDir, isFullyQualified, } from "../path-utils.js";
|
|
16
|
+
import { direntsHaveMarkerGlobMatch, isAtOrAboveHomeDir, isFullyQualified, isWindowsPath, pathsEqual, } from "../path-utils.js";
|
|
17
17
|
import { ensureTool, getToolEnvironment, getToolPath, } from "../installer/index.js";
|
|
18
18
|
import { resolveOpengrepConfig } from "../opengrep-config.js";
|
|
19
19
|
import { isZizmorAuditTarget, resolveZizmorGitHubToken } from "../zizmor-config.js";
|
|
@@ -78,17 +78,22 @@ const PROJECT_BOUNDARY_MARKERS = [
|
|
|
78
78
|
// with FALLBACK_PROJECT_MARKERS, the shared fallback root-policy marker set.
|
|
79
79
|
const loggedRootCeilingClamps = new Set();
|
|
80
80
|
function isSameOrWithin(ancestor, candidate) {
|
|
81
|
-
const
|
|
82
|
-
|
|
81
|
+
const windowsShaped = isWindowsPath(ancestor) || isWindowsPath(candidate);
|
|
82
|
+
const pathApi = windowsShaped ? path.win32 : path;
|
|
83
|
+
const relative = pathApi.relative(pathApi.resolve(ancestor), pathApi.resolve(candidate));
|
|
84
|
+
return (relative === "" ||
|
|
85
|
+
(!relative.startsWith("..") && !pathApi.isAbsolute(relative)));
|
|
83
86
|
}
|
|
84
87
|
/** Enforce the session cwd as the hard boundary for LSP client root selection. */
|
|
85
88
|
export function enforceLspRootCeiling(root, sessionCwd, filePath) {
|
|
86
|
-
const
|
|
87
|
-
const
|
|
89
|
+
const windowsShaped = isWindowsPath(root) || isWindowsPath(sessionCwd);
|
|
90
|
+
const pathApi = windowsShaped ? path.win32 : path;
|
|
91
|
+
const resolvedRoot = pathApi.resolve(root);
|
|
92
|
+
const resolvedCwd = pathApi.resolve(sessionCwd);
|
|
88
93
|
// Callers may explicitly inspect an out-of-session file (notably isolated
|
|
89
94
|
// tests and API consumers). The cwd ceiling governs roots for files that are
|
|
90
95
|
// actually inside the declared session project.
|
|
91
|
-
if (filePath && !isSameOrWithin(resolvedCwd,
|
|
96
|
+
if (filePath && !isSameOrWithin(resolvedCwd, pathApi.resolve(filePath))) {
|
|
92
97
|
return resolvedRoot;
|
|
93
98
|
}
|
|
94
99
|
if (isSameOrWithin(resolvedCwd, resolvedRoot))
|
|
@@ -187,6 +192,15 @@ function canInstall(allowInstall) {
|
|
|
187
192
|
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);
|
|
188
193
|
const directLspCommandUnavailableUntil = new Map();
|
|
189
194
|
const directLspCommandSkipLoggedUntil = new Map();
|
|
195
|
+
/** Re-arm direct-command availability for the next session. */
|
|
196
|
+
export function resetDirectLspCommandAvailability() {
|
|
197
|
+
directLspCommandUnavailableUntil.clear();
|
|
198
|
+
directLspCommandSkipLoggedUntil.clear();
|
|
199
|
+
}
|
|
200
|
+
/** Test seam for seeding the real negative-cache path. */
|
|
201
|
+
export function _markDirectLspCommandUnavailableForTests(command) {
|
|
202
|
+
markDirectLspCommandUnavailable(command);
|
|
203
|
+
}
|
|
190
204
|
function pruneExpiredDirectLspNegativeEntries(now = Date.now()) {
|
|
191
205
|
for (const [command, until] of directLspCommandUnavailableUntil) {
|
|
192
206
|
if (until <= now) {
|
|
@@ -707,7 +721,10 @@ async function markerExists(dir, pattern) {
|
|
|
707
721
|
* NearestRoot(includePatterns, excludePatterns?) → RootFunction
|
|
708
722
|
*
|
|
709
723
|
* - includePatterns: file/dir names that signal the project root (e.g. ["package.json"])
|
|
710
|
-
* - excludePatterns: if any of these exist in a directory,
|
|
724
|
+
* - excludePatterns: if any of these exist in a directory, SKIP that directory and
|
|
725
|
+
* keep walking up toward stopDir — it does not abort resolution. A directory that
|
|
726
|
+
* matches an exclude pattern is simply never returned as a root; the walk still
|
|
727
|
+
* continues past it looking for an include-pattern hit higher up (#1671).
|
|
711
728
|
* - stopDir: walk stops here (defaults to filesystem root; set to project cwd for safety)
|
|
712
729
|
*
|
|
713
730
|
* Equivalent to createRootDetector; exported under both names for clarity.
|
|
@@ -717,6 +734,12 @@ export function NearestRoot(includePatterns, excludePatterns, stopDir) {
|
|
|
717
734
|
// different servers (e.g. TypeScript vs Go) with different marker sets never
|
|
718
735
|
// share entries. vi.resetModules() in tests resets module state between cases.
|
|
719
736
|
const cache = new Map();
|
|
737
|
+
// Only cache successful hits. Undefined results are NOT cached so that a
|
|
738
|
+
// newly-created root marker (e.g. package.json or tsconfig.json scaffolded
|
|
739
|
+
// mid-session by the agent) is detected on the next call — the absent →
|
|
740
|
+
// present transition must work without a process restart. The uncached
|
|
741
|
+
// re-walk cost for configless repos is a known trade-off; bounding the
|
|
742
|
+
// walk with stopDir for in-cwd files is the tracked optimization (#1412).
|
|
720
743
|
const inFlight = new Map();
|
|
721
744
|
return async (file) => {
|
|
722
745
|
// Cache key is the resolved directory — all files in the same dir share a root.
|
|
@@ -774,9 +797,6 @@ export function NearestRoot(includePatterns, excludePatterns, stopDir) {
|
|
|
774
797
|
inFlight.set(dirKey, promise);
|
|
775
798
|
try {
|
|
776
799
|
const result = await promise;
|
|
777
|
-
// Only cache successful hits. Undefined results are not cached so that
|
|
778
|
-
// a newly-created root marker (e.g. package.json added mid-session) is
|
|
779
|
-
// detected on the next call.
|
|
780
800
|
if (result !== undefined)
|
|
781
801
|
cache.set(dirKey, result);
|
|
782
802
|
return result;
|
|
@@ -829,44 +849,185 @@ export async function tryDotnetToolInstall(tool) {
|
|
|
829
849
|
return !updateResult.error && updateResult.status === 0;
|
|
830
850
|
}
|
|
831
851
|
/**
|
|
832
|
-
*
|
|
833
|
-
*
|
|
852
|
+
* #1412 M1: walk up from `startDir` (inclusive) looking for a file at
|
|
853
|
+
* `startDir/<relativeSegments>`, `dirname(startDir)/<relativeSegments>`, and so
|
|
854
|
+
* on — the same ancestor walk `findNativeTypeScriptLsp` uses, bounded the same
|
|
855
|
+
* way (`isAtOrAboveHomeDir`). A nested config root (e.g. a `cypress/tsconfig.json`
|
|
856
|
+
* LSP root inside a repo whose `node_modules` only exists at the repo root) must
|
|
857
|
+
* still resolve tooling installed at an ancestor, not just directly under the
|
|
858
|
+
* LSP root — mirrors how node module resolution itself walks up.
|
|
834
859
|
*/
|
|
835
|
-
async function
|
|
860
|
+
async function findAncestorFile(startDir, ...relativeSegments) {
|
|
861
|
+
return findAncestorFileAmong(startDir, [relativeSegments]);
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* Same ancestor walk as `findAncestorFile`, but checks every candidate
|
|
865
|
+
* relative-path in `candidateSegmentLists` AT EACH LEVEL before moving up —
|
|
866
|
+
* so the nearest ancestor wins regardless of which candidate name matched
|
|
867
|
+
* there, matching normal node_modules resolution priority (nearest install
|
|
868
|
+
* shadows a further one, never the reverse).
|
|
869
|
+
*/
|
|
870
|
+
async function findAncestorFileAmong(startDir, candidateSegmentLists) {
|
|
836
871
|
const fs = await import("node:fs/promises");
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
872
|
+
let currentDir = path.resolve(startDir);
|
|
873
|
+
while (!isAtOrAboveHomeDir(currentDir)) {
|
|
874
|
+
for (const segments of candidateSegmentLists) {
|
|
875
|
+
const candidate = path.join(currentDir, ...segments);
|
|
876
|
+
try {
|
|
877
|
+
await fs.access(candidate);
|
|
878
|
+
return candidate;
|
|
879
|
+
}
|
|
880
|
+
catch {
|
|
881
|
+
/* not found at this level */
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
const parent = path.dirname(currentDir);
|
|
885
|
+
if (parent === currentDir)
|
|
886
|
+
break;
|
|
887
|
+
currentDir = parent;
|
|
888
|
+
}
|
|
889
|
+
return undefined;
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* A failed classic-compiler repair must not repeat within a session.
|
|
893
|
+
* `ensureTool` caches successful installs, so a repair that works
|
|
894
|
+
* short-circuits later calls on its own. A repair that fails leaves nothing
|
|
895
|
+
* behind, and `findTsserverPath` has three call sites (TypeScript, Vue,
|
|
896
|
+
* Svelte). Without this guard an offline or partial install re-runs a 120 s
|
|
897
|
+
* forced reinstall on every spawn.
|
|
898
|
+
*
|
|
899
|
+
* The guard is a plain module-level flag, so without an explicit re-arm it
|
|
900
|
+
* would latch for the whole extension-host process — a repair that failed
|
|
901
|
+
* once (transient registry hiccup) would stay unrepairable for every later
|
|
902
|
+
* session in that process. `resetClassicTsRepairGuard` re-arms it; callers
|
|
903
|
+
* wire that into `session_start` alongside the other per-session resets
|
|
904
|
+
* (#1570).
|
|
905
|
+
*/
|
|
906
|
+
let classicTsRepairAttempted = false;
|
|
907
|
+
/** Re-arm the classic-repair guard so a new session gets its own attempt. */
|
|
908
|
+
export function resetClassicTsRepairGuard() {
|
|
909
|
+
classicTsRepairAttempted = false;
|
|
910
|
+
}
|
|
911
|
+
/** Test hook — clears the per-process classic-repair guard. */
|
|
912
|
+
export function _resetClassicTsRepairForTests() {
|
|
913
|
+
resetClassicTsRepairGuard();
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* Directories that may hold the TypeScript package next to a resolved `tsc`
|
|
917
|
+
* binary: `<bin>/../typescript` (npm-global layout) and
|
|
918
|
+
* `<bin>/../../typescript` (managed `node_modules/.bin` layout).
|
|
919
|
+
*/
|
|
920
|
+
function typescriptDirsForTsc(tscPath) {
|
|
921
|
+
const binDir = path.dirname(tscPath);
|
|
922
|
+
return [
|
|
923
|
+
path.join(binDir, "..", "typescript"),
|
|
924
|
+
path.join(binDir, "..", "..", "typescript"),
|
|
840
925
|
];
|
|
841
|
-
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Read the major version of the TypeScript package that backs a resolved
|
|
929
|
+
* `tsc` binary. Returns undefined when the version is unknowable: `ensureTool`
|
|
930
|
+
* returns the bare string `"tsc"` for a PATH hit, and `path.dirname("tsc")` is
|
|
931
|
+
* `"."`, so the candidates would go cwd-relative. Callers must not repair on
|
|
932
|
+
* an unknown version — a healthy global TypeScript 5.x would be reinstalled
|
|
933
|
+
* for nothing.
|
|
934
|
+
*/
|
|
935
|
+
async function typescriptVersionForTsc(tscPath) {
|
|
936
|
+
if (!path.isAbsolute(tscPath))
|
|
937
|
+
return undefined;
|
|
938
|
+
for (const dir of typescriptDirsForTsc(tscPath)) {
|
|
939
|
+
let manifest;
|
|
842
940
|
try {
|
|
843
|
-
await
|
|
844
|
-
return p;
|
|
941
|
+
manifest = await readFile(path.join(dir, "package.json"), "utf8");
|
|
845
942
|
}
|
|
846
943
|
catch {
|
|
847
|
-
|
|
944
|
+
continue;
|
|
848
945
|
}
|
|
946
|
+
let version;
|
|
947
|
+
try {
|
|
948
|
+
const parsed = JSON.parse(manifest);
|
|
949
|
+
if (typeof parsed !== "object" ||
|
|
950
|
+
parsed === null ||
|
|
951
|
+
!("version" in parsed) ||
|
|
952
|
+
typeof parsed.version !== "string") {
|
|
953
|
+
return undefined;
|
|
954
|
+
}
|
|
955
|
+
version = parsed.version;
|
|
956
|
+
}
|
|
957
|
+
catch {
|
|
958
|
+
return undefined;
|
|
959
|
+
}
|
|
960
|
+
const majorText = version.split(".", 1)[0] ?? "";
|
|
961
|
+
const major = /^\d+$/.test(majorText) ? Number(majorText) : Number.NaN;
|
|
962
|
+
if (!Number.isFinite(major))
|
|
963
|
+
return undefined;
|
|
964
|
+
return { version, major };
|
|
849
965
|
}
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
966
|
+
return undefined;
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Locate tsserver.js — tries local project (walking up from root, #1412 M1),
|
|
970
|
+
* then process.cwd() as a last-resort fallback, then pi-lens managed
|
|
971
|
+
* TypeScript. Returns the path to tsserver.js, or undefined if not found.
|
|
972
|
+
*/
|
|
973
|
+
async function findTsserverPath(root, allowInstall) {
|
|
974
|
+
const fs = await import("node:fs/promises");
|
|
975
|
+
const ancestorHit = await findAncestorFile(root, "node_modules", "typescript", "lib", "tsserver.js");
|
|
976
|
+
if (ancestorHit)
|
|
977
|
+
return ancestorHit;
|
|
978
|
+
const cwdCandidate = path.join(process.cwd(), "node_modules", "typescript", "lib", "tsserver.js");
|
|
979
|
+
try {
|
|
980
|
+
await fs.access(cwdCandidate);
|
|
981
|
+
return cwdCandidate;
|
|
982
|
+
}
|
|
983
|
+
catch {
|
|
984
|
+
/* not found */
|
|
985
|
+
}
|
|
986
|
+
const tsserverForTsc = async (tscPath) => {
|
|
987
|
+
if (!tscPath)
|
|
988
|
+
return undefined;
|
|
989
|
+
for (const dir of typescriptDirsForTsc(tscPath)) {
|
|
990
|
+
const candidate = path.join(dir, "lib", "tsserver.js");
|
|
860
991
|
try {
|
|
861
|
-
await fs.access(
|
|
862
|
-
return
|
|
992
|
+
await fs.access(candidate);
|
|
993
|
+
return candidate;
|
|
863
994
|
}
|
|
864
995
|
catch {
|
|
865
996
|
/* not found */
|
|
866
997
|
}
|
|
867
998
|
}
|
|
999
|
+
return undefined;
|
|
1000
|
+
};
|
|
1001
|
+
// Discover the TypeScript install (PATH / npm-global) even when installation
|
|
1002
|
+
// is disabled; only the download is gated by allowInstall.
|
|
1003
|
+
const installAllowed = canInstall(allowInstall);
|
|
1004
|
+
const discoveredTsc = await ensureTool("typescript", {
|
|
1005
|
+
allowInstall: installAllowed,
|
|
1006
|
+
});
|
|
1007
|
+
const discoveredTsserver = await tsserverForTsc(discoveredTsc);
|
|
1008
|
+
if (discoveredTsserver ||
|
|
1009
|
+
!discoveredTsc ||
|
|
1010
|
+
!installAllowed ||
|
|
1011
|
+
classicTsRepairAttempted) {
|
|
1012
|
+
return discoveredTsserver;
|
|
868
1013
|
}
|
|
869
|
-
|
|
1014
|
+
// Repair only a compiler we can prove is TypeScript 7+. TypeScript 7 dropped
|
|
1015
|
+
// lib/tsserver.js, so the classic wrapper cannot start against it. Any other
|
|
1016
|
+
// version — or a version we cannot read, such as a bare PATH `tsc` — is left
|
|
1017
|
+
// alone rather than force-reinstalled.
|
|
1018
|
+
const discoveredVersion = await typescriptVersionForTsc(discoveredTsc);
|
|
1019
|
+
if (!discoveredVersion || discoveredVersion.major < 7)
|
|
1020
|
+
return undefined;
|
|
1021
|
+
// An older managed tree took `latest` before the registry pinned the classic
|
|
1022
|
+
// compiler. Reinstall the pinned version once so that tree self-heals,
|
|
1023
|
+
// without deleting user or project-local TypeScript installations.
|
|
1024
|
+
classicTsRepairAttempted = true;
|
|
1025
|
+
logSessionStart(`lsp typescript: managed compiler resolved to TypeScript ${discoveredVersion.version}, which ships no tsserver.js; reinstalling pinned classic fallback`);
|
|
1026
|
+
const repairedTsc = await ensureTool("typescript", {
|
|
1027
|
+
allowInstall: true,
|
|
1028
|
+
forceReinstall: true,
|
|
1029
|
+
});
|
|
1030
|
+
return tsserverForTsc(repairedTsc);
|
|
870
1031
|
}
|
|
871
1032
|
/**
|
|
872
1033
|
* TypeScript 7+ ships the native typescript-go language server through the
|
|
@@ -1046,10 +1207,17 @@ export async function detectPythonVenv(root) {
|
|
|
1046
1207
|
}
|
|
1047
1208
|
// --- Server Definitions ---
|
|
1048
1209
|
const JS_TS_LSP_EXTENSIONS = KIND_EXTENSIONS["jsts"].filter((ext) => ext !== ".svelte" && ext !== ".vue");
|
|
1049
|
-
//
|
|
1050
|
-
//
|
|
1051
|
-
//
|
|
1052
|
-
|
|
1210
|
+
// TypeScript identity and tooling discovery deliberately use separate marker
|
|
1211
|
+
// families. A governing config wins even when a package directory supplies
|
|
1212
|
+
// hoisted binaries. Keep configs out of PROJECT_BOUNDARY_MARKERS: #1373 still
|
|
1213
|
+
// coalesces a config-only nested root when an ancestor client was hosted first
|
|
1214
|
+
// (nested-config-first remains intentionally open-order-sensitive). #1412
|
|
1215
|
+
// accepted risk (M2, not fixed here): honoring nested config roots at all
|
|
1216
|
+
// enlarges the population of directories that can independently coalesce or
|
|
1217
|
+
// diverge under #1373's open-order sensitivity — the same pre-existing
|
|
1218
|
+
// blast-radius, just triggered by more roots than before.
|
|
1219
|
+
const TS_CONFIG_MARKERS = ["tsconfig.json", "jsconfig.json"];
|
|
1220
|
+
const TS_TOOLING_MARKERS = [
|
|
1053
1221
|
"package-lock.json",
|
|
1054
1222
|
"bun.lockb",
|
|
1055
1223
|
"bun.lock",
|
|
@@ -1057,21 +1225,71 @@ const TS_PROJECT_MARKERS = [
|
|
|
1057
1225
|
"yarn.lock",
|
|
1058
1226
|
"package.json",
|
|
1059
1227
|
];
|
|
1060
|
-
|
|
1228
|
+
// #1412 M3: tsserver associates jsconfig.json with JS files only (its identity
|
|
1229
|
+
// probe reports a jsconfig-governed .ts file as unassociated) — so a TS-family
|
|
1230
|
+
// file under a jsconfig-only directory must NOT root there; keep walking up
|
|
1231
|
+
// for a real tsconfig.json. A JS-family file accepts either: tsconfig also
|
|
1232
|
+
// governs plain JS via `allowJs`, so accepting tsconfig for a .js file is
|
|
1233
|
+
// correct, and jsconfig obviously is too.
|
|
1234
|
+
const TS_FAMILY_EXTENSIONS = new Set([".ts", ".tsx", ".mts", ".cts"]);
|
|
1235
|
+
function isTsFamilyFile(file) {
|
|
1236
|
+
return TS_FAMILY_EXTENSIONS.has(path.extname(file).toLowerCase());
|
|
1237
|
+
}
|
|
1238
|
+
function tsConfigMarkersForFile(file) {
|
|
1239
|
+
return isTsFamilyFile(file) ? ["tsconfig.json"] : TS_CONFIG_MARKERS;
|
|
1240
|
+
}
|
|
1241
|
+
// Two detector instances (not one parameterized by file) so each keeps its own
|
|
1242
|
+
// per-directory NearestRoot cache valid for its fixed marker set — a shared
|
|
1243
|
+
// cache keyed only by directory would conflate the TS-only and either-config
|
|
1244
|
+
// answers for the same directory.
|
|
1245
|
+
const TypeScriptConfigRootTsOnly = IgnoreHomeRoot(createRootDetector(["tsconfig.json"]));
|
|
1246
|
+
const TypeScriptConfigRootEither = IgnoreHomeRoot(createRootDetector([...TS_CONFIG_MARKERS]));
|
|
1247
|
+
const TypeScriptToolingRoot = IgnoreHomeRoot(createRootDetector([...TS_TOOLING_MARKERS]));
|
|
1248
|
+
async function findTypeScriptProjectRoot(file) {
|
|
1249
|
+
const configDetector = isTsFamilyFile(file)
|
|
1250
|
+
? TypeScriptConfigRootTsOnly
|
|
1251
|
+
: TypeScriptConfigRootEither;
|
|
1252
|
+
const [configRoot, toolingRoot] = await Promise.all([
|
|
1253
|
+
configDetector(file),
|
|
1254
|
+
TypeScriptToolingRoot(file),
|
|
1255
|
+
]);
|
|
1256
|
+
if (!configRoot)
|
|
1257
|
+
return toolingRoot;
|
|
1258
|
+
if (!toolingRoot)
|
|
1259
|
+
return configRoot;
|
|
1260
|
+
// A config inside (or beside) the nearest package governs its files. A
|
|
1261
|
+
// config above a nearer package must not erase that topology boundary.
|
|
1262
|
+
return isSameOrWithin(toolingRoot, configRoot) ? configRoot : toolingRoot;
|
|
1263
|
+
}
|
|
1061
1264
|
/**
|
|
1062
|
-
* Walk up from the file's directory looking for a TypeScript project marker
|
|
1063
|
-
*
|
|
1064
|
-
*
|
|
1065
|
-
*
|
|
1265
|
+
* Walk up from the file's directory looking for a TypeScript project marker
|
|
1266
|
+
* (a governing config first, per-directory, per #1412 M3's extension-family
|
|
1267
|
+
* filter; else a tooling/lockfile marker), but stop at `extensionRootKey` so
|
|
1268
|
+
* we never escape the .pi/agent/extensions boundary into a higher-up project
|
|
1269
|
+
* (e.g. ~/.pi/agent/package.json which would pull every extension in the
|
|
1270
|
+
* directory into one LSP workspace).
|
|
1066
1271
|
*
|
|
1067
|
-
*
|
|
1068
|
-
*
|
|
1272
|
+
* #1412 L4: each directory returns immediately on its first match (config or
|
|
1273
|
+
* tooling) — there is no cross-level "nearest tooling root" to carry forward,
|
|
1274
|
+
* since a match always wins on the spot. Returns the nearest directory
|
|
1275
|
+
* containing a marker, or undefined if none is found between the file and the
|
|
1276
|
+
* extensions root inclusive.
|
|
1069
1277
|
*/
|
|
1070
1278
|
async function findExtensionBoundedRoot(file, extensionRootKey) {
|
|
1071
1279
|
const startDir = path.resolve(path.dirname(file));
|
|
1072
1280
|
let currentDir = startDir;
|
|
1281
|
+
const configMarkers = tsConfigMarkersForFile(file);
|
|
1073
1282
|
while (true) {
|
|
1074
|
-
for (const pattern of
|
|
1283
|
+
for (const pattern of configMarkers) {
|
|
1284
|
+
try {
|
|
1285
|
+
await stat(path.join(currentDir, pattern));
|
|
1286
|
+
return currentDir;
|
|
1287
|
+
}
|
|
1288
|
+
catch {
|
|
1289
|
+
/* not found, try next marker */
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
for (const pattern of TS_TOOLING_MARKERS) {
|
|
1075
1293
|
try {
|
|
1076
1294
|
await stat(path.join(currentDir, pattern));
|
|
1077
1295
|
return currentDir;
|
|
@@ -1102,7 +1320,7 @@ async function hasAgentLevelProjectMarker(extensionRootKey) {
|
|
|
1102
1320
|
const agentDir = path.dirname(extensionRootKey);
|
|
1103
1321
|
if (!agentDir || agentDir === extensionRootKey)
|
|
1104
1322
|
return false;
|
|
1105
|
-
for (const pattern of
|
|
1323
|
+
for (const pattern of [...TS_CONFIG_MARKERS, ...TS_TOOLING_MARKERS]) {
|
|
1106
1324
|
try {
|
|
1107
1325
|
await stat(path.join(agentDir, pattern));
|
|
1108
1326
|
return true;
|
|
@@ -1133,7 +1351,7 @@ const TypeScriptRoot = DenoExcludeRoot(async (file) => {
|
|
|
1133
1351
|
// analyze a lone .ts file with no package.json above or below).
|
|
1134
1352
|
return undefined;
|
|
1135
1353
|
}
|
|
1136
|
-
const projectRoot = await
|
|
1354
|
+
const projectRoot = await findTypeScriptProjectRoot(file);
|
|
1137
1355
|
if (projectRoot)
|
|
1138
1356
|
return projectRoot;
|
|
1139
1357
|
return FileDirRoot(file);
|
|
@@ -1159,20 +1377,13 @@ export const TypeScriptServer = {
|
|
|
1159
1377
|
let source = "direct";
|
|
1160
1378
|
// TypeScript <=6 uses typescript-language-server + tsserver.js. Prefer a
|
|
1161
1379
|
// project-local wrapper, then fall back to discovered/managed tooling.
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
lspPath = checkPath;
|
|
1170
|
-
break;
|
|
1171
|
-
}
|
|
1172
|
-
catch {
|
|
1173
|
-
/* not found */
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1380
|
+
// #1412 M1: walk up from root (Windows .cmd first, then Unix at each
|
|
1381
|
+
// level) — a nested config root's node_modules/.bin lives at an ancestor,
|
|
1382
|
+
// not necessarily directly under the LSP root.
|
|
1383
|
+
let lspPath = await findAncestorFileAmong(root, [
|
|
1384
|
+
["node_modules", ".bin", "typescript-language-server.cmd"],
|
|
1385
|
+
["node_modules", ".bin", "typescript-language-server"],
|
|
1386
|
+
]);
|
|
1176
1387
|
// Fall back to a discovered or managed install. ensureTool() runs PATH /
|
|
1177
1388
|
// npm-global discovery even when install is disabled (only the download is
|
|
1178
1389
|
// gated by canInstall), so a globally-installed typescript-language-server
|
|
@@ -1372,15 +1583,120 @@ export const GoServer = {
|
|
|
1372
1583
|
return { ...result, initialization: { ui: { semanticTokens: true } } };
|
|
1373
1584
|
},
|
|
1374
1585
|
};
|
|
1375
|
-
async function
|
|
1586
|
+
async function readTextFileOrUndefined(filePath) {
|
|
1376
1587
|
try {
|
|
1377
|
-
|
|
1378
|
-
const content = await readFile(cargoPath, "utf-8");
|
|
1379
|
-
return /^\s*\[workspace\]/m.test(content);
|
|
1588
|
+
return await readFile(filePath, "utf-8");
|
|
1380
1589
|
}
|
|
1381
1590
|
catch {
|
|
1591
|
+
return undefined;
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Slice out ONE top-level TOML table's raw body — from its `[name]` heading
|
|
1596
|
+
* to the next top-level `[...]`/`[[...]]` heading or EOF. `members`/`exclude`
|
|
1597
|
+
* must be read from the `[workspace]` table specifically: `[package]` has its
|
|
1598
|
+
* OWN `exclude` key (the standard cargo-publish exclude list, conventionally
|
|
1599
|
+
* written above `[workspace]` in a virtual-manifest-less root crate), and a
|
|
1600
|
+
* whole-file regex would misread it as workspace membership (#1671 F4).
|
|
1601
|
+
*/
|
|
1602
|
+
function extractTomlTableSection(content, tableName) {
|
|
1603
|
+
const heading = new RegExp(`^\\[${tableName}\\][ \\t]*(?:#.*)?$`, "m");
|
|
1604
|
+
const match = heading.exec(content);
|
|
1605
|
+
if (!match)
|
|
1606
|
+
return "";
|
|
1607
|
+
const rest = content.slice(match.index + match[0].length);
|
|
1608
|
+
const nextHeading = rest.match(/^\[{1,2}[^\]]+\]{1,2}[ \t]*(?:#.*)?$/m);
|
|
1609
|
+
return nextHeading?.index !== undefined ? rest.slice(0, nextHeading.index) : rest;
|
|
1610
|
+
}
|
|
1611
|
+
function parseTomlStringArray(content, key) {
|
|
1612
|
+
const match = content.match(new RegExp(`^[ \\t]*${key}[ \\t]*=[ \\t]*\\[([\\s\\S]*?)\\]`, "m"));
|
|
1613
|
+
if (!match)
|
|
1614
|
+
return [];
|
|
1615
|
+
return [...match[1].matchAll(/"([^"]*)"|'([^']*)'/g)].map((m) => (m[1] ?? m[2] ?? "").trim());
|
|
1616
|
+
}
|
|
1617
|
+
/** Turn one `/`-delimited glob SEGMENT into a regex source: `*` matches any
|
|
1618
|
+
* run of characters within the segment, `?` matches exactly one character,
|
|
1619
|
+
* everything else is escaped and literal. */
|
|
1620
|
+
function segmentGlobToRegExpSource(segment) {
|
|
1621
|
+
let out = "";
|
|
1622
|
+
for (const ch of segment) {
|
|
1623
|
+
if (ch === "*")
|
|
1624
|
+
out += "[^/]*";
|
|
1625
|
+
else if (ch === "?")
|
|
1626
|
+
out += "[^/]";
|
|
1627
|
+
else
|
|
1628
|
+
out += ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
1629
|
+
}
|
|
1630
|
+
return out;
|
|
1631
|
+
}
|
|
1632
|
+
/**
|
|
1633
|
+
* A `members`/`exclude` entry names a path exactly, or globs it segment by
|
|
1634
|
+
* segment: `*` and `?` inside a segment work at any depth — one wildcard
|
|
1635
|
+
* segment (`crates/*`), a bare `*`, or several chained together for a
|
|
1636
|
+
* deeper fixed-depth layout — matching cargo's own `glob`-crate semantics
|
|
1637
|
+
* for a fixed number of path components.
|
|
1638
|
+
*
|
|
1639
|
+
* KNOWN LIMITATION (#1671 F6, documented rather than implemented): a
|
|
1640
|
+
* recursive `**` segment (matching a variable number of path components) is
|
|
1641
|
+
* NOT supported and never matches — cargo workspaces that rely on `**` to
|
|
1642
|
+
* pull in an arbitrarily-nested crate tree will under-hoist (the crate stays
|
|
1643
|
+
* independently rooted instead of joining the workspace). This is
|
|
1644
|
+
* deliberately out of #1671's scope: the common, and the issue's fixture,
|
|
1645
|
+
* shape is an explicit fixed-depth `members` list.
|
|
1646
|
+
*/
|
|
1647
|
+
function matchesCargoWorkspacePattern(pattern, relativePath) {
|
|
1648
|
+
const normalized = pattern.replace(/\/+$/, "");
|
|
1649
|
+
if (normalized.includes("**"))
|
|
1650
|
+
return false;
|
|
1651
|
+
const patternSegments = normalized.split("/");
|
|
1652
|
+
const pathSegments = relativePath.split("/");
|
|
1653
|
+
if (patternSegments.length !== pathSegments.length)
|
|
1654
|
+
return false;
|
|
1655
|
+
return patternSegments.every((segment, i) => new RegExp(`^${segmentGlobToRegExpSource(segment)}$`).test(pathSegments[i]));
|
|
1656
|
+
}
|
|
1657
|
+
/**
|
|
1658
|
+
* Given an ancestor Cargo.toml's raw contents that already contains a
|
|
1659
|
+
* `[workspace]` table, decide whether it actually claims `childDir` as a
|
|
1660
|
+
* member. Cargo's own default is that a bare `[workspace]` with no `members`
|
|
1661
|
+
* key claims only the manifest's own package — a nested crate is NOT
|
|
1662
|
+
* implicitly swept in just because it sits underneath the workspace root on
|
|
1663
|
+
* disk. Without this check any Cargo.toml with a `[workspace]` table would
|
|
1664
|
+
* hoist every crate below it, including ones the workspace never declared
|
|
1665
|
+
* (#1671) — the same defect shape as an undeclared Maven sibling module.
|
|
1666
|
+
*
|
|
1667
|
+
* KNOWN LIMITATION (#1671 F7, documented rather than implemented): cargo also
|
|
1668
|
+
* treats a crate as an implicit workspace member when the workspace root's
|
|
1669
|
+
* OWN `[package]` has a `path = "..."` dependency on it and no explicit
|
|
1670
|
+
* `members` key is present at all. That dependency-graph inference is not
|
|
1671
|
+
* evaluated here — a workspace relying on it alone (no `members` key) reads
|
|
1672
|
+
* as claiming nothing, so its crates stay independently rooted rather than
|
|
1673
|
+
* being (correctly, per cargo) swept in. An explicit `members` list — the
|
|
1674
|
+
* common case and the one #1671's fixtures exercise — is unaffected.
|
|
1675
|
+
*/
|
|
1676
|
+
function cargoWorkspaceDeclaresMember(workspaceContent, parentDir, childDir) {
|
|
1677
|
+
const relativePath = path.relative(parentDir, childDir).split(path.sep).join("/");
|
|
1678
|
+
if (relativePath === "" || relativePath.startsWith(".."))
|
|
1679
|
+
return false;
|
|
1680
|
+
const workspaceSection = extractTomlTableSection(workspaceContent, "workspace");
|
|
1681
|
+
const excluded = parseTomlStringArray(workspaceSection, "exclude");
|
|
1682
|
+
if (excluded.some((pattern) => matchesCargoWorkspacePattern(pattern, relativePath))) {
|
|
1382
1683
|
return false;
|
|
1383
1684
|
}
|
|
1685
|
+
const members = parseTomlStringArray(workspaceSection, "members");
|
|
1686
|
+
return members.some((pattern) => matchesCargoWorkspacePattern(pattern, relativePath));
|
|
1687
|
+
}
|
|
1688
|
+
/**
|
|
1689
|
+
* Bound a monorepo-hoist walk-up by the session ceiling: when `file` is inside
|
|
1690
|
+
* the session cwd, the walk must stop AT the cwd rather than climb past it —
|
|
1691
|
+
* matching enforceLspRootCeiling's clamp semantics but avoiding the wasted stat
|
|
1692
|
+
* calls of walking past a boundary we would clamp back down anyway. When
|
|
1693
|
+
* `file` is outside the session (isolated tests, out-of-session API callers),
|
|
1694
|
+
* enforceLspRootCeiling is a no-op, so the walk is left unbounded (`undefined`).
|
|
1695
|
+
*/
|
|
1696
|
+
function sessionHoistCeiling(sessionCwd, file) {
|
|
1697
|
+
return isSameOrWithin(path.resolve(sessionCwd), path.resolve(file))
|
|
1698
|
+
? path.resolve(sessionCwd)
|
|
1699
|
+
: undefined;
|
|
1384
1700
|
}
|
|
1385
1701
|
function RustWorkspaceRoot() {
|
|
1386
1702
|
const crateRoot = createRootDetector(["Cargo.toml", "Cargo.lock"]);
|
|
@@ -1388,21 +1704,131 @@ function RustWorkspaceRoot() {
|
|
|
1388
1704
|
const root = await crateRoot(file);
|
|
1389
1705
|
if (!root)
|
|
1390
1706
|
return undefined;
|
|
1707
|
+
const sessionCwd = process.cwd();
|
|
1708
|
+
// The walk-up for an ancestor Cargo.toml with a [workspace] table stays
|
|
1709
|
+
// bounded by the same session ceiling the crate-root lookup above already
|
|
1710
|
+
// enforces (enforceLspRootCeiling) — a monorepo hoist must never cross the
|
|
1711
|
+
// session boundary just because a workspace manifest happens to sit above
|
|
1712
|
+
// it (#1671).
|
|
1713
|
+
const stop = sessionHoistCeiling(sessionCwd, file);
|
|
1391
1714
|
let current = root;
|
|
1392
1715
|
const fsRoot = path.parse(current).root;
|
|
1393
1716
|
while (true) {
|
|
1717
|
+
if (stop !== undefined && current === stop)
|
|
1718
|
+
break;
|
|
1394
1719
|
const parent = path.dirname(current);
|
|
1395
1720
|
if (parent === current || parent === fsRoot)
|
|
1396
1721
|
break;
|
|
1397
|
-
const
|
|
1398
|
-
|
|
1399
|
-
|
|
1722
|
+
const parentCargoPath = path.join(parent, "Cargo.toml");
|
|
1723
|
+
const parentCargoContent = await readTextFileOrUndefined(parentCargoPath);
|
|
1724
|
+
if (parentCargoContent !== undefined && /^\s*\[workspace\]/m.test(parentCargoContent)) {
|
|
1725
|
+
// Test membership against the ORIGINAL crate dir (`root`), never the
|
|
1726
|
+
// walk cursor (`current`, which may already have climbed through
|
|
1727
|
+
// gap directories that have no Cargo.toml of their own) — a
|
|
1728
|
+
// `members = ["crates/foo"]` or `["crates/*"]` entry is relative to
|
|
1729
|
+
// the workspace root, spanning the whole gap in one hop (#1671 F1).
|
|
1730
|
+
if (cargoWorkspaceDeclaresMember(parentCargoContent, parent, root)) {
|
|
1731
|
+
return enforceLspRootCeiling(parent, sessionCwd, file);
|
|
1732
|
+
}
|
|
1733
|
+
// A workspace root exists here but its `members`/`exclude` tables do
|
|
1734
|
+
// not claim this crate — stop climbing; the crate stays independently
|
|
1735
|
+
// rooted rather than being swept into a workspace it opted out of.
|
|
1736
|
+
break;
|
|
1400
1737
|
}
|
|
1401
1738
|
current = parent;
|
|
1402
1739
|
}
|
|
1403
1740
|
return root;
|
|
1404
1741
|
};
|
|
1405
1742
|
}
|
|
1743
|
+
/**
|
|
1744
|
+
* Resolve whether `parentPomPath`'s <modules> block declares `childDir` as one
|
|
1745
|
+
* of its member modules. Maven multi-module hoisting must only chain through
|
|
1746
|
+
* poms that actually declare the child (an undeclared sibling directory that
|
|
1747
|
+
* merely happens to sit next to a parent pom stays independently rooted) —
|
|
1748
|
+
* this is the "Maven module-chain verification" #1671 asks for, as opposed to
|
|
1749
|
+
* rust-analyzer's simpler "any ancestor [workspace] wins" hoist.
|
|
1750
|
+
*/
|
|
1751
|
+
/**
|
|
1752
|
+
* Strip XML comments to a fixed point rather than in one pass: a single
|
|
1753
|
+
* `.replace()` can leave a residual `<!--` behind on adversarially-nested
|
|
1754
|
+
* input (CodeQL flags this class as "incomplete multi-character
|
|
1755
|
+
* sanitization" — the removal of one comment can expose a delimiter that
|
|
1756
|
+
* was itself inside another). Looping until nothing changes closes that gap.
|
|
1757
|
+
*/
|
|
1758
|
+
function stripXmlComments(content) {
|
|
1759
|
+
let result = content;
|
|
1760
|
+
let previous;
|
|
1761
|
+
do {
|
|
1762
|
+
previous = result;
|
|
1763
|
+
result = result.replace(/<!--[\s\S]*?-->/g, "");
|
|
1764
|
+
} while (result !== previous);
|
|
1765
|
+
return result;
|
|
1766
|
+
}
|
|
1767
|
+
async function declaresMavenModule(parentDir, parentPomPath, childDir) {
|
|
1768
|
+
try {
|
|
1769
|
+
const content = stripXmlComments(await readFile(parentPomPath, "utf-8"));
|
|
1770
|
+
const modulesBlock = content.match(/<modules>([\s\S]*?)<\/modules>/);
|
|
1771
|
+
if (!modulesBlock)
|
|
1772
|
+
return false;
|
|
1773
|
+
const resolvedChild = path.resolve(childDir);
|
|
1774
|
+
for (const match of modulesBlock[1].matchAll(/<module>\s*([^<]+?)\s*<\/module>/g)) {
|
|
1775
|
+
// Case-insensitive / realpath-aware compare (#1671 F8): a declared
|
|
1776
|
+
// `<module>Foo</module>` must still match a directory actually named
|
|
1777
|
+
// `foo` on a case-insensitive filesystem, the same class of check the
|
|
1778
|
+
// rest of this codebase does via `pathsEqual` (#1139/#1150).
|
|
1779
|
+
if (pathsEqual(path.resolve(parentDir, match[1]), resolvedChild))
|
|
1780
|
+
return true;
|
|
1781
|
+
}
|
|
1782
|
+
return false;
|
|
1783
|
+
}
|
|
1784
|
+
catch {
|
|
1785
|
+
return false;
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
function JavaWorkspaceRoot() {
|
|
1789
|
+
const moduleRoot = createRootDetector(["pom.xml", "build.gradle", ".classpath"]);
|
|
1790
|
+
return async (file) => {
|
|
1791
|
+
const root = await moduleRoot(file);
|
|
1792
|
+
if (!root)
|
|
1793
|
+
return undefined;
|
|
1794
|
+
// Only a Maven (pom.xml) module chain-hoists here — Gradle/.classpath module
|
|
1795
|
+
// roots are returned as found; multi-module Gradle wiring (settings.gradle)
|
|
1796
|
+
// is a different shape and out of scope for #1671.
|
|
1797
|
+
if (!(await markerExists(root, "pom.xml")))
|
|
1798
|
+
return root;
|
|
1799
|
+
const sessionCwd = process.cwd();
|
|
1800
|
+
const stop = sessionHoistCeiling(sessionCwd, file);
|
|
1801
|
+
// `current` is the walk CURSOR — it climbs over gap directories (ones
|
|
1802
|
+
// with no pom.xml of their own, e.g. a `<module>sub/dir</module>` entry
|
|
1803
|
+
// spanning more than one filesystem level). `lastHop` is the last
|
|
1804
|
+
// CONFIRMED module boundary; it is what the next ancestor pom's
|
|
1805
|
+
// <modules> must declare, and it is what actually gets returned — never
|
|
1806
|
+
// the cursor, which can land on a bare gap directory that is not itself
|
|
1807
|
+
// a valid module root (#1671 F2).
|
|
1808
|
+
let current = root;
|
|
1809
|
+
let lastHop = root;
|
|
1810
|
+
const fsRoot = path.parse(current).root;
|
|
1811
|
+
while (true) {
|
|
1812
|
+
if (stop !== undefined && current === stop)
|
|
1813
|
+
break;
|
|
1814
|
+
const parent = path.dirname(current);
|
|
1815
|
+
if (parent === current || parent === fsRoot)
|
|
1816
|
+
break;
|
|
1817
|
+
if (await markerExists(parent, "pom.xml")) {
|
|
1818
|
+
const parentPom = path.join(parent, "pom.xml");
|
|
1819
|
+
if (!(await declaresMavenModule(parent, parentPom, lastHop)))
|
|
1820
|
+
break;
|
|
1821
|
+
current = parent;
|
|
1822
|
+
lastHop = parent;
|
|
1823
|
+
continue;
|
|
1824
|
+
}
|
|
1825
|
+
// No pom.xml here — a gap directory, not a module boundary. Keep
|
|
1826
|
+
// climbing without advancing `lastHop`.
|
|
1827
|
+
current = parent;
|
|
1828
|
+
}
|
|
1829
|
+
return enforceLspRootCeiling(lastHop, sessionCwd, file);
|
|
1830
|
+
};
|
|
1831
|
+
}
|
|
1406
1832
|
export const RustServer = {
|
|
1407
1833
|
id: "rust",
|
|
1408
1834
|
name: "rust-analyzer",
|
|
@@ -1632,7 +2058,7 @@ export const JavaServer = createInteractiveServer({
|
|
|
1632
2058
|
id: "java",
|
|
1633
2059
|
name: "JDT Language Server",
|
|
1634
2060
|
extensions: KIND_EXTENSIONS["java"],
|
|
1635
|
-
root: RootWithFallback(
|
|
2061
|
+
root: RootWithFallback(JavaWorkspaceRoot()),
|
|
1636
2062
|
language: "java",
|
|
1637
2063
|
command: () => process.env.JDTLS_PATH || "jdtls",
|
|
1638
2064
|
args: (root) => createLombokJdtlsArgs(root),
|
|
@@ -1837,6 +2263,20 @@ export const ClojureServer = {
|
|
|
1837
2263
|
}, options?.allowInstall);
|
|
1838
2264
|
},
|
|
1839
2265
|
};
|
|
2266
|
+
export const CueServer = {
|
|
2267
|
+
id: "cue",
|
|
2268
|
+
name: "CUE Language Server",
|
|
2269
|
+
extensions: KIND_EXTENSIONS["cue"],
|
|
2270
|
+
root: RootWithFallback(createRootDetector(["cue.mod", ".git"])),
|
|
2271
|
+
async spawn(root, options) {
|
|
2272
|
+
return resolveAndLaunch({
|
|
2273
|
+
candidates: ["cue"],
|
|
2274
|
+
args: ["lsp", "serve"],
|
|
2275
|
+
cwd: root,
|
|
2276
|
+
managedToolId: "cue",
|
|
2277
|
+
}, options?.allowInstall);
|
|
2278
|
+
},
|
|
2279
|
+
};
|
|
1840
2280
|
export const TerraformServer = {
|
|
1841
2281
|
id: "terraform",
|
|
1842
2282
|
name: "Terraform LSP",
|
|
@@ -2215,6 +2655,13 @@ export const AstGrepServer = {
|
|
|
2215
2655
|
// (it falls back to napi when the ast-grep binary is absent — Gate B).
|
|
2216
2656
|
root: RootWithFallback(createRootDetector(["sgconfig.yml", "sgconfig.yaml"]), RootWithFallback(NearestRoot([".git"]), async () => process.cwd())),
|
|
2217
2657
|
availabilityKey: "ast-grep",
|
|
2658
|
+
// #1714: the one auxiliary with a measured wedge ceiling. A `lens_diagnostics
|
|
2659
|
+
// mode=full` sweep of 225 files drove this server into an unrecoverable stall
|
|
2660
|
+
// twice in two exposures (writes outstanding 5.9 s and 9.4 s, then a forced
|
|
2661
|
+
// shutdown that timed out both the request and the exit notify). It re-parses
|
|
2662
|
+
// the whole file on every didOpen, so it absorbs a sweep more slowly than the
|
|
2663
|
+
// other scanners — hold it to half the shared default.
|
|
2664
|
+
notifyInflightLimit: 4,
|
|
2218
2665
|
// First scan of a session compiles the rules.
|
|
2219
2666
|
initializeTimeoutMs: 15000,
|
|
2220
2667
|
async spawn(root, options) {
|
|
@@ -2377,6 +2824,7 @@ export const LSP_SERVERS = [
|
|
|
2377
2824
|
MarksmanServer,
|
|
2378
2825
|
OCamlServer,
|
|
2379
2826
|
ClojureServer,
|
|
2827
|
+
CueServer,
|
|
2380
2828
|
TerraformServer,
|
|
2381
2829
|
NixServer,
|
|
2382
2830
|
BashServer,
|