@herbertgao/pi-extensions 2026.8.4 → 2026.8.6
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 +11 -3
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +11 -3
- 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/feature/context.ts +74 -5
- 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 +53 -14
- 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 +18 -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 +20 -4
- 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/fleet-list.ts +15 -6
- 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/@herbertgao/pi-subagents/src/worktree.ts +9 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +2 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +20 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +6 -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 +32 -24
- package/node_modules/@narumitw/pi-btw/package.json +6 -6
- package/node_modules/@narumitw/pi-btw/src/btw.ts +4 -2
- package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -13
- package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
- package/node_modules/pi-lens/CHANGELOG.md +2535 -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/advisory-provenance.js +314 -0
- package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +21 -4
- package/node_modules/pi-lens/dist/clients/agent-nudge.js +14 -7
- 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 +136 -24
- 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-events-logger.js +62 -6
- package/node_modules/pi-lens/dist/clients/bus-publish.js +19 -4
- 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/cascade-format.js +57 -2
- 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 +26 -0
- package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +137 -31
- 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 +361 -77
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +65 -13
- package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +18 -4
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +176 -29
- 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 +1172 -683
- 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 +47 -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/availability-policy.js +226 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +69 -0
- 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 +473 -42
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +97 -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 +18 -4
- package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
- package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
- package/node_modules/pi-lens/dist/clients/extension-log.js +432 -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/fix-worklog.js +5 -1
- package/node_modules/pi-lens/dist/clients/format-events-publish.js +51 -7
- 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 +678 -11
- package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
- package/node_modules/pi-lens/dist/clients/go-client.js +23 -40
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +124 -7
- 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 +395 -100
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +27 -55
- package/node_modules/pi-lens/dist/clients/knip-client.js +58 -42
- 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 +72 -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 +63 -9
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +16 -0
- package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +73 -0
- package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +59 -14
- package/node_modules/pi-lens/dist/clients/lsp/client.js +662 -56
- package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +115 -0
- package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
- package/node_modules/pi-lens/dist/clients/lsp/index.js +2025 -800
- 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 +437 -86
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +96 -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 +18 -3
- 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 +127 -32
- package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
- package/node_modules/pi-lens/dist/clients/mcp/session.js +235 -14
- 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/model-provider.js +53 -0
- 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 +76 -15
- 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/fresh-fetch.js +1 -1
- 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/runner-adapters/runner-findings.js +23 -2
- 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/query.js +24 -0
- 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/run-duration.js +55 -0
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +257 -77
- package/node_modules/pi-lens/dist/clients/runtime-context.js +129 -12
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +242 -26
- package/node_modules/pi-lens/dist/clients/runtime-session.js +428 -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 +342 -33
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +619 -93
- package/node_modules/pi-lens/dist/clients/rust-client.js +23 -38
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +90 -6
- package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
- package/node_modules/pi-lens/dist/clients/sg-runner.js +424 -196
- package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +231 -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 +7 -1
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +469 -32
- package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
- package/node_modules/pi-lens/dist/clients/tool-policy.js +390 -39
- package/node_modules/pi-lens/dist/clients/tool-render.js +155 -0
- package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -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 +50 -13
- package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
- package/node_modules/pi-lens/dist/clients/word-index.js +680 -81
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
- package/node_modules/pi-lens/dist/index.js +51785 -36118
- package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
- package/node_modules/pi-lens/dist/mcp/server.js +219 -46
- 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/ast-grep-search.js +93 -27
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +5 -2
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +203 -38
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +210 -42
- 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/dist/tools/symbol-search.js +1 -1
- package/node_modules/pi-lens/docs/agent-guide.md +40 -15
- 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 +14 -3
- 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 +45 -3
- package/node_modules/pi-lens/docs/globalconfig.md +56 -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 +18 -1
- package/node_modules/pi-lens/docs/tools.md +4 -1
- package/node_modules/pi-lens/docs/usage.md +41 -5
- package/node_modules/pi-lens/docs/word-index.md +35 -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-chained-type-assertions-test.yml +8 -0
- 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-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-reflect-apply-js-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +8 -0
- 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-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-chained-type-assertions.yml +21 -0
- 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-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-reflect-apply-js.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +13 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +27 -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 +134 -2
- package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +10 -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 +8 -114
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +129 -0
- package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +38 -0
- package/node_modules/pi-mcp-adapter/README.md +38 -4
- package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
- package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
- package/node_modules/pi-mcp-adapter/cli.js +25 -4
- package/node_modules/pi-mcp-adapter/commands.ts +15 -7
- package/node_modules/pi-mcp-adapter/config.ts +4 -4
- package/node_modules/pi-mcp-adapter/direct-tools.ts +58 -8
- 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/mcp-setup-panel.ts +2 -1
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +37 -4
- package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
- package/node_modules/pi-mcp-adapter/package.json +4 -3
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +336 -0
- package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
- package/node_modules/pi-mcp-adapter/server-manager.ts +41 -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 +62 -4
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
- package/node_modules/pi-mcp-adapter/types.ts +176 -27
- package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
- package/node_modules/pi-web-access/CHANGELOG.md +14 -0
- package/node_modules/pi-web-access/README.md +18 -12
- package/node_modules/pi-web-access/auth-fetch.ts +148 -0
- package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
- package/node_modules/pi-web-access/curator-page.ts +5 -3
- package/node_modules/pi-web-access/curator-server.ts +2 -1
- package/node_modules/pi-web-access/extract.ts +106 -34
- 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 +18 -4
- package/node_modules/pi-web-access/index.ts +120 -48
- package/node_modules/pi-web-access/package.json +2 -2
- package/node_modules/pi-web-access/summary-review.ts +11 -5
- package/node_modules/pi-web-access/youtube-extract.ts +2 -2
- package/package.json +12 -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
|
@@ -9,12 +9,17 @@
|
|
|
9
9
|
import * as fs from "node:fs";
|
|
10
10
|
import * as path from "node:path";
|
|
11
11
|
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { logSessionStart } from "../../../sessionstart-logger.js";
|
|
12
13
|
import { getGlobalPiLensDir } from "../../../file-utils.js";
|
|
13
|
-
import {
|
|
14
|
+
import { PathKeyedMap } from "../../../path-keyed-map.js";
|
|
15
|
+
import { normalizeEphemeralMapKey, normalizeMapKey, } from "../../../path-utils.js";
|
|
16
|
+
import { ensureTool, isSpawnableCommand, resetPathWalkMemo, } from "../../../installer/index.js";
|
|
14
17
|
import { getServersForFileWithConfig, isServerDisabled, } from "../../../lsp/config.js";
|
|
15
18
|
import { findGlobalBinary } from "../../../package-manager.js";
|
|
16
19
|
import { safeSpawnAsync } from "../../../safe-spawn.js";
|
|
17
20
|
import { getToolCommandSpec, shouldAutoInstallTool, } from "../../../tool-policy.js";
|
|
21
|
+
import { classifyProbeFailure, createAvailabilityLatch, isLatchingOutcome, logAvailabilityDecision, startHostStallSampler, transientRetryDelayMs, } from "./availability-policy.js";
|
|
22
|
+
export { createAvailabilityLatch, classifyProbeFailure, describeUnavailability, isTransientDecision, logAvailabilityDecision, startHostStallSampler, } from "./availability-policy.js";
|
|
18
23
|
/**
|
|
19
24
|
* True when the LSP runner will cover `ctx.filePath` via the given PRIMARY server
|
|
20
25
|
* id. Used by CLI runners that duplicate a linter a warm LSP already wraps
|
|
@@ -61,6 +66,29 @@ if (typeof __dirname !== "undefined") {
|
|
|
61
66
|
}
|
|
62
67
|
// Managed tools directory (~/.pi-lens/tools) — where ensureTool() installs binaries
|
|
63
68
|
const _managedToolsDir = path.join(getGlobalPiLensDir(), "tools");
|
|
69
|
+
/**
|
|
70
|
+
* The managed shim for a Node CLI tool (`~/.pi-lens/tools/node_modules/.bin/<tool>`),
|
|
71
|
+
* or null when it is not on disk.
|
|
72
|
+
*
|
|
73
|
+
* When the shim exists the tool IS installed, so availability needs no spawn at
|
|
74
|
+
* all — and a spawn that cannot happen cannot time out (#1467). knip and jscpd
|
|
75
|
+
* each carried a line-for-line copy of this resolver; #1476 folds them into one
|
|
76
|
+
* definition so the next managed tool inherits the fast path instead of a
|
|
77
|
+
* fourth copy.
|
|
78
|
+
*
|
|
79
|
+
* The pi-lens dir is read per call, never memoized at module load, so tests that
|
|
80
|
+
* point `getGlobalPiLensDir` at a temp home still see their own tree.
|
|
81
|
+
*/
|
|
82
|
+
export function findManagedNodeToolBinary(tool) {
|
|
83
|
+
const base = path.join(getGlobalPiLensDir(), "tools", "node_modules", ".bin", tool);
|
|
84
|
+
const candidates = process.platform === "win32" ? [`${base}.cmd`, `${base}.exe`, base] : [base];
|
|
85
|
+
try {
|
|
86
|
+
return candidates.find((candidate) => fs.existsSync(candidate)) ?? null;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
64
92
|
/**
|
|
65
93
|
* Find a command in venv first, then fall back to global.
|
|
66
94
|
* Checks common venv locations (.venv, venv) before trying global.
|
|
@@ -83,6 +111,102 @@ export function createVenvFinder(command, windowsExt = "", quoteWindows = false)
|
|
|
83
111
|
return command;
|
|
84
112
|
};
|
|
85
113
|
}
|
|
114
|
+
/** Typed client-facing install seam for ordered/custom candidate probes. */
|
|
115
|
+
export async function resolveManagedToolClient(options) {
|
|
116
|
+
const probed = await options.probe();
|
|
117
|
+
if (probed.outcome !== "missing")
|
|
118
|
+
return probed;
|
|
119
|
+
if (!shouldAutoInstallTool(options.toolId))
|
|
120
|
+
return probed;
|
|
121
|
+
const state = installStateFor(options.cwd, options.toolId);
|
|
122
|
+
if (state.suppressed)
|
|
123
|
+
return probed;
|
|
124
|
+
const installed = await ensureTool(options.toolId);
|
|
125
|
+
if (!installed) {
|
|
126
|
+
noteInstallFailure(options.toolId, options.cwd);
|
|
127
|
+
return probed;
|
|
128
|
+
}
|
|
129
|
+
const value = await options.acceptInstalled(installed);
|
|
130
|
+
if (value === null) {
|
|
131
|
+
noteInstallFailure(options.toolId, options.cwd);
|
|
132
|
+
return { outcome: "non-installable" };
|
|
133
|
+
}
|
|
134
|
+
noteInstallSuccess(options.toolId, options.cwd);
|
|
135
|
+
return { outcome: "success", value };
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Child environment for managed-installable tools. Keeping this beside the
|
|
139
|
+
* probe/install seam makes managed npm shims visible to every standalone
|
|
140
|
+
* client, rather than only to Knip (#1289).
|
|
141
|
+
*/
|
|
142
|
+
export async function getManagedToolEnvironment(_toolId, cwd) {
|
|
143
|
+
let env;
|
|
144
|
+
try {
|
|
145
|
+
const { getToolEnvironment } = await import("../../../installer/index.js");
|
|
146
|
+
env = await getToolEnvironment();
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// Installer-isolated unit tests historically mock only ensureTool. The
|
|
150
|
+
// ambient fallback preserves that isolation; production always exports it.
|
|
151
|
+
env = { ...process.env };
|
|
152
|
+
}
|
|
153
|
+
if (!cwd)
|
|
154
|
+
return env;
|
|
155
|
+
const separator = process.platform === "win32" ? ";" : ":";
|
|
156
|
+
const currentPath = env.PATH || env.Path || process.env.PATH || "";
|
|
157
|
+
const localBin = path.join(cwd, "node_modules", ".bin");
|
|
158
|
+
const augmentedPath = `${localBin}${separator}${currentPath}`;
|
|
159
|
+
return {
|
|
160
|
+
...env,
|
|
161
|
+
PATH: augmentedPath,
|
|
162
|
+
...(process.platform === "win32" ? { Path: augmentedPath } : {}),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/** Read-only managed/PATH discovery for spawn-time resolution memos. */
|
|
166
|
+
export async function discoverManagedTool(toolId) {
|
|
167
|
+
return (await ensureTool(toolId, { allowInstall: false })) ?? null;
|
|
168
|
+
}
|
|
169
|
+
// This is session-scoped state, not a process-global tool/path cache. The cwd
|
|
170
|
+
// key is normalized by PathKeyedMap and is cleared at session_start. A failed
|
|
171
|
+
// install must not become an install attempt on every eligible file/turn.
|
|
172
|
+
const installAttemptsByCwd = new PathKeyedMap(normalizeMapKey);
|
|
173
|
+
const resolveInstallInFlightByCwd = new PathKeyedMap(normalizeEphemeralMapKey);
|
|
174
|
+
// Checkers are created by runner modules and may also be created dynamically.
|
|
175
|
+
// Keep the session reset as a generation rather than retaining every checker
|
|
176
|
+
// reset closure forever.
|
|
177
|
+
let availabilityStateGeneration = 0;
|
|
178
|
+
function installStateFor(cwd, toolId) {
|
|
179
|
+
let states = installAttemptsByCwd.get(cwd);
|
|
180
|
+
if (!states) {
|
|
181
|
+
states = new Map();
|
|
182
|
+
installAttemptsByCwd.set(cwd, states);
|
|
183
|
+
}
|
|
184
|
+
let state = states.get(toolId);
|
|
185
|
+
if (!state) {
|
|
186
|
+
state = { attempts: 0, suppressed: false };
|
|
187
|
+
states.set(toolId, state);
|
|
188
|
+
}
|
|
189
|
+
return state;
|
|
190
|
+
}
|
|
191
|
+
function noteInstallFailure(toolId, cwd) {
|
|
192
|
+
const state = installStateFor(cwd, toolId);
|
|
193
|
+
state.attempts += 1;
|
|
194
|
+
state.suppressed = true;
|
|
195
|
+
logSessionStart(`dispatch availability ${toolId}: install attempt ${state.attempts} failed; suppressing retries until the next session or a successful install`);
|
|
196
|
+
}
|
|
197
|
+
function noteInstallSuccess(toolId, cwd) {
|
|
198
|
+
const states = installAttemptsByCwd.get(cwd);
|
|
199
|
+
states?.delete(toolId);
|
|
200
|
+
if (states?.size === 0)
|
|
201
|
+
installAttemptsByCwd.delete(cwd);
|
|
202
|
+
}
|
|
203
|
+
/** Reset availability/install suppression at the session boundary. */
|
|
204
|
+
export function resetDispatchAvailabilityState() {
|
|
205
|
+
installAttemptsByCwd.clear();
|
|
206
|
+
resolveInstallInFlightByCwd.clear();
|
|
207
|
+
resetPathWalkMemo();
|
|
208
|
+
availabilityStateGeneration += 1;
|
|
209
|
+
}
|
|
86
210
|
/**
|
|
87
211
|
* Create a cached availability checker for a command.
|
|
88
212
|
* The checker will look for the command in venv first, then global.
|
|
@@ -92,50 +216,231 @@ export function createVenvFinder(command, windowsExt = "", quoteWindows = false)
|
|
|
92
216
|
* `zig --version`). Passing the wrong probe makes the runner silently skip on
|
|
93
217
|
* every machine, so toolchains with a non-standard version command must override
|
|
94
218
|
* this.
|
|
219
|
+
*
|
|
220
|
+
* ## Latch policy (#1467)
|
|
221
|
+
*
|
|
222
|
+
* A `missing` / `non-installable` verdict is durable and is cached for the
|
|
223
|
+
* session. A `transient` verdict — timeout, abort, EAGAIN — is NOT: it is
|
|
224
|
+
* cached only for a bounded cooldown, after which the next caller re-probes.
|
|
225
|
+
* An installed tool therefore recovers on its own, without a host restart.
|
|
95
226
|
*/
|
|
96
|
-
export function createAvailabilityChecker(command, windowsExt = "", versionArgs = ["--version"]) {
|
|
97
|
-
const cacheByCwd = new
|
|
98
|
-
const inFlightByCwd = new
|
|
227
|
+
export function createAvailabilityChecker(command, windowsExt = "", versionArgs = ["--version"], options = {}) {
|
|
228
|
+
const cacheByCwd = new PathKeyedMap(normalizeEphemeralMapKey);
|
|
229
|
+
const inFlightByCwd = new PathKeyedMap(normalizeEphemeralMapKey);
|
|
230
|
+
let checkerGeneration = availabilityStateGeneration;
|
|
99
231
|
const findCommand = createVenvFinder(command, windowsExt, true);
|
|
232
|
+
function ensureCurrentGeneration() {
|
|
233
|
+
if (checkerGeneration === availabilityStateGeneration)
|
|
234
|
+
return;
|
|
235
|
+
cacheByCwd.clear();
|
|
236
|
+
inFlightByCwd.clear();
|
|
237
|
+
checkerGeneration = availabilityStateGeneration;
|
|
238
|
+
}
|
|
239
|
+
const reset = () => {
|
|
240
|
+
cacheByCwd.clear();
|
|
241
|
+
inFlightByCwd.clear();
|
|
242
|
+
checkerGeneration = availabilityStateGeneration;
|
|
243
|
+
};
|
|
100
244
|
function getCache(cwd) {
|
|
245
|
+
ensureCurrentGeneration();
|
|
101
246
|
const key = path.resolve(cwd || process.cwd());
|
|
102
247
|
const existing = cacheByCwd.get(key);
|
|
103
248
|
if (existing)
|
|
104
249
|
return existing;
|
|
105
|
-
const created = {
|
|
250
|
+
const created = {
|
|
251
|
+
available: null,
|
|
252
|
+
command: null,
|
|
253
|
+
outcome: null,
|
|
254
|
+
cause: null,
|
|
255
|
+
elapsedMs: 0,
|
|
256
|
+
retryAtMs: 0,
|
|
257
|
+
transientAttempts: 0,
|
|
258
|
+
};
|
|
106
259
|
cacheByCwd.set(key, created);
|
|
107
260
|
return created;
|
|
108
261
|
}
|
|
262
|
+
/** Record a verdict on the cache and emit exactly one decision record. */
|
|
263
|
+
function noteDecision(cache, resolvedCwd, verdict) {
|
|
264
|
+
cache.available = verdict.available;
|
|
265
|
+
cache.outcome = verdict.outcome;
|
|
266
|
+
cache.cause = verdict.cause;
|
|
267
|
+
cache.elapsedMs = verdict.elapsedMs;
|
|
268
|
+
let retryAfterMs;
|
|
269
|
+
if (verdict.available) {
|
|
270
|
+
cache.retryAtMs = 0;
|
|
271
|
+
cache.transientAttempts = 0;
|
|
272
|
+
}
|
|
273
|
+
else if (isLatchingOutcome(verdict.outcome)) {
|
|
274
|
+
cache.retryAtMs = 0;
|
|
275
|
+
cache.transientAttempts = 0;
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
cache.transientAttempts += 1;
|
|
279
|
+
retryAfterMs = transientRetryDelayMs(cache.transientAttempts, verdict.cause);
|
|
280
|
+
cache.retryAtMs = Date.now() + retryAfterMs;
|
|
281
|
+
}
|
|
282
|
+
logAvailabilityDecision({
|
|
283
|
+
tool: command,
|
|
284
|
+
verdict: verdict.available ? "available" : "unavailable",
|
|
285
|
+
outcome: verdict.outcome,
|
|
286
|
+
cause: verdict.cause,
|
|
287
|
+
elapsedMs: verdict.elapsedMs,
|
|
288
|
+
latched: verdict.available || isLatchingOutcome(verdict.outcome),
|
|
289
|
+
...(verdict.hostStallMs !== undefined && {
|
|
290
|
+
hostStallMs: verdict.hostStallMs,
|
|
291
|
+
}),
|
|
292
|
+
...(retryAfterMs !== undefined && { retryAfterMs }),
|
|
293
|
+
budgetMs: options.probeTimeout ?? 5000,
|
|
294
|
+
}, resolvedCwd);
|
|
295
|
+
}
|
|
109
296
|
async function isAvailableAsync(cwd) {
|
|
297
|
+
ensureCurrentGeneration();
|
|
110
298
|
const resolvedCwd = cwd || process.cwd();
|
|
111
299
|
const cache = getCache(resolvedCwd);
|
|
112
|
-
if (cache.available
|
|
113
|
-
|
|
300
|
+
if (cache.available === false) {
|
|
301
|
+
// A durable "this machine does not have the tool" stays cached; a
|
|
302
|
+
// transient probe failure only holds until its cooldown expires, so an
|
|
303
|
+
// installed tool cannot be disabled for the life of the process by one
|
|
304
|
+
// slow second at warm-up (#1467).
|
|
305
|
+
if (cache.outcome !== "transient")
|
|
306
|
+
return false;
|
|
307
|
+
if (Date.now() < cache.retryAtMs)
|
|
308
|
+
return false;
|
|
309
|
+
cache.available = null;
|
|
310
|
+
}
|
|
311
|
+
if (cache.available === true && cache.command) {
|
|
312
|
+
if (await isSpawnableCommand(cache.command))
|
|
313
|
+
return true;
|
|
314
|
+
// Cached-positive spawn feedback: a removed absolute path or vanished
|
|
315
|
+
// PATH command must fall through to a fresh probe immediately.
|
|
316
|
+
cache.available = null;
|
|
317
|
+
cache.command = null;
|
|
318
|
+
cache.outcome = null;
|
|
319
|
+
cache.cause = null;
|
|
320
|
+
}
|
|
114
321
|
const key = path.resolve(resolvedCwd);
|
|
115
322
|
const existing = inFlightByCwd.get(key);
|
|
116
323
|
if (existing)
|
|
117
324
|
return existing;
|
|
118
|
-
const
|
|
325
|
+
const promiseGeneration = checkerGeneration;
|
|
326
|
+
let promise;
|
|
327
|
+
promise = (async () => {
|
|
328
|
+
const fastPath = options.fastPath?.();
|
|
329
|
+
if (fastPath) {
|
|
330
|
+
cache.command = fastPath;
|
|
331
|
+
noteDecision(cache, resolvedCwd, {
|
|
332
|
+
available: true,
|
|
333
|
+
outcome: "success",
|
|
334
|
+
cause: "fast-path",
|
|
335
|
+
elapsedMs: 0,
|
|
336
|
+
});
|
|
337
|
+
return true;
|
|
338
|
+
}
|
|
339
|
+
// A bad/removed workspace must not be mistaken for a missing tool and
|
|
340
|
+
// trigger an install. This async probe stays off the synchronous dispatch
|
|
341
|
+
// burst and makes the failure taxonomy explicit at the seam.
|
|
342
|
+
try {
|
|
343
|
+
const cwdStat = await fs.promises.stat(resolvedCwd);
|
|
344
|
+
if (!cwdStat.isDirectory()) {
|
|
345
|
+
noteDecision(cache, resolvedCwd, {
|
|
346
|
+
available: false,
|
|
347
|
+
outcome: "non-installable",
|
|
348
|
+
cause: "bad-cwd",
|
|
349
|
+
elapsedMs: 0,
|
|
350
|
+
});
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
noteDecision(cache, resolvedCwd, {
|
|
356
|
+
available: false,
|
|
357
|
+
outcome: "non-installable",
|
|
358
|
+
cause: "bad-cwd",
|
|
359
|
+
elapsedMs: 0,
|
|
360
|
+
});
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
119
363
|
const cmd = findCommand(resolvedCwd);
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
364
|
+
const env = await options.environment?.(resolvedCwd);
|
|
365
|
+
// The probe budget is enforced by a HOST-side timer, so host event-loop
|
|
366
|
+
// stalls are charged to the child. Measure the stall that overlapped the
|
|
367
|
+
// window and hand it to the classifier (#1467).
|
|
368
|
+
const stallSampler = startHostStallSampler();
|
|
369
|
+
const startedAt = Date.now();
|
|
370
|
+
let result;
|
|
371
|
+
let hostStallMs;
|
|
372
|
+
try {
|
|
373
|
+
result = await safeSpawnAsync(cmd, versionArgs, {
|
|
374
|
+
timeout: options.probeTimeout ?? 5000,
|
|
375
|
+
cwd: resolvedCwd,
|
|
376
|
+
env,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
finally {
|
|
380
|
+
hostStallMs = stallSampler.stop();
|
|
381
|
+
}
|
|
382
|
+
const elapsedMs = Date.now() - startedAt;
|
|
383
|
+
if (!result.error && result.status === 0) {
|
|
125
384
|
cache.command = cmd;
|
|
385
|
+
noteDecision(cache, resolvedCwd, {
|
|
386
|
+
available: true,
|
|
387
|
+
outcome: "success",
|
|
388
|
+
cause: "ok",
|
|
389
|
+
elapsedMs,
|
|
390
|
+
hostStallMs,
|
|
391
|
+
});
|
|
392
|
+
return true;
|
|
126
393
|
}
|
|
127
|
-
|
|
394
|
+
const { outcome, cause } = classifyProbeFailure(result, {
|
|
395
|
+
hostStallMs,
|
|
396
|
+
unclassifiedFailureOutcome: options.unclassifiedFailureOutcome,
|
|
397
|
+
});
|
|
398
|
+
// Only a TYPED tool-not-found invalidates the PATH walk memo; an
|
|
399
|
+
// `unclassifiedFailureOutcome: "missing"` compatibility verdict is a
|
|
400
|
+
// guess, not evidence that PATH changed.
|
|
401
|
+
if (result.spawnFailure?.kind === "tool-not-found")
|
|
402
|
+
resetPathWalkMemo();
|
|
403
|
+
noteDecision(cache, resolvedCwd, {
|
|
404
|
+
available: false,
|
|
405
|
+
outcome,
|
|
406
|
+
cause,
|
|
407
|
+
elapsedMs,
|
|
408
|
+
hostStallMs,
|
|
409
|
+
});
|
|
410
|
+
return false;
|
|
128
411
|
})().finally(() => {
|
|
129
|
-
|
|
412
|
+
// A session reset clears this map and a caller may immediately start a
|
|
413
|
+
// replacement probe for the same cwd. The old promise must not delete
|
|
414
|
+
// that newer-generation entry when it settles.
|
|
415
|
+
if (checkerGeneration === promiseGeneration &&
|
|
416
|
+
inFlightByCwd.get(key) === promise) {
|
|
417
|
+
inFlightByCwd.delete(key);
|
|
418
|
+
}
|
|
130
419
|
});
|
|
131
420
|
inFlightByCwd.set(key, promise);
|
|
132
421
|
return promise;
|
|
133
422
|
}
|
|
134
423
|
function getCommand(cwd) {
|
|
424
|
+
ensureCurrentGeneration();
|
|
135
425
|
const cache = getCache(cwd || process.cwd());
|
|
136
426
|
return cache.command;
|
|
137
427
|
}
|
|
138
|
-
|
|
428
|
+
function getOutcome(cwd) {
|
|
429
|
+
ensureCurrentGeneration();
|
|
430
|
+
return getCache(cwd || process.cwd()).outcome;
|
|
431
|
+
}
|
|
432
|
+
function getVerdict(cwd) {
|
|
433
|
+
ensureCurrentGeneration();
|
|
434
|
+
const cache = getCache(cwd || process.cwd());
|
|
435
|
+
return {
|
|
436
|
+
outcome: cache.outcome,
|
|
437
|
+
cause: cache.cause,
|
|
438
|
+
elapsedMs: cache.elapsedMs,
|
|
439
|
+
latched: cache.available !== false || isLatchingOutcome(cache.outcome ?? "missing"),
|
|
440
|
+
retryAtMs: cache.retryAtMs,
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
return { isAvailableAsync, getCommand, getOutcome, getVerdict, reset };
|
|
139
444
|
}
|
|
140
445
|
/**
|
|
141
446
|
* Per-cwd cached availability probe for spawn signatures that don't fit
|
|
@@ -150,8 +455,13 @@ export function createAvailabilityChecker(command, windowsExt = "", versionArgs
|
|
|
150
455
|
* fresh probe for retry-after-install flows.
|
|
151
456
|
*/
|
|
152
457
|
export function createCwdCachedProbe(probe) {
|
|
153
|
-
const cacheByCwd = new
|
|
458
|
+
const cacheByCwd = new PathKeyedMap(normalizeEphemeralMapKey);
|
|
459
|
+
let probeGeneration = availabilityStateGeneration;
|
|
154
460
|
return (cwd) => {
|
|
461
|
+
if (probeGeneration !== availabilityStateGeneration) {
|
|
462
|
+
cacheByCwd.clear();
|
|
463
|
+
probeGeneration = availabilityStateGeneration;
|
|
464
|
+
}
|
|
155
465
|
const key = path.resolve(cwd || process.cwd());
|
|
156
466
|
const existing = cacheByCwd.get(key);
|
|
157
467
|
if (existing)
|
|
@@ -209,7 +519,8 @@ export async function resolveToolCommandWithInstallFallback(cwd, toolId, timeout
|
|
|
209
519
|
async function verifyOrInstallCommand(command, toolId, cwd, versionArgs = ["--version"], timeout = 5000) {
|
|
210
520
|
// Skip the --version spawn when the command isn't even on disk — the ~μs
|
|
211
521
|
// stat/PATH walk beats a guaranteed-to-fail spawn round-trip.
|
|
212
|
-
|
|
522
|
+
const spawnable = await isSpawnableCommand(command);
|
|
523
|
+
if (spawnable) {
|
|
213
524
|
const versionCheck = await safeSpawnAsync(command, versionArgs, {
|
|
214
525
|
timeout,
|
|
215
526
|
cwd,
|
|
@@ -217,16 +528,22 @@ async function verifyOrInstallCommand(command, toolId, cwd, versionArgs = ["--ve
|
|
|
217
528
|
if (!versionCheck.error && versionCheck.status === 0) {
|
|
218
529
|
return command;
|
|
219
530
|
}
|
|
531
|
+
// A command that was found but rejected its probe is not fixed by a
|
|
532
|
+
// reinstall. This also covers permissions and malformed shims.
|
|
533
|
+
return null;
|
|
220
534
|
}
|
|
221
|
-
if (!shouldAutoInstallTool(toolId))
|
|
535
|
+
if (!shouldAutoInstallTool(toolId))
|
|
536
|
+
return null;
|
|
537
|
+
const state = installStateFor(cwd, toolId);
|
|
538
|
+
if (state.suppressed)
|
|
222
539
|
return null;
|
|
540
|
+
const installed = await ensureTool(toolId);
|
|
541
|
+
if (installed) {
|
|
542
|
+
noteInstallSuccess(toolId, cwd);
|
|
543
|
+
return installed;
|
|
223
544
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
// --version here booted the tool's node shim once per invocation for
|
|
227
|
-
// zero new information; a broken install now surfaces as the runner's
|
|
228
|
-
// own spawn error instead of a silent null.
|
|
229
|
-
return (await ensureTool(toolId)) ?? null;
|
|
545
|
+
noteInstallFailure(toolId, cwd);
|
|
546
|
+
return null;
|
|
230
547
|
}
|
|
231
548
|
export async function resolveCommandArgsWithInstallFallback(command, toolId, cwd, versionArgs = ["--version"], timeout = 5000) {
|
|
232
549
|
const versionCheck = await safeSpawnAsync(command.cmd, [...command.args, ...versionArgs], { timeout, cwd });
|
|
@@ -245,34 +562,99 @@ export async function resolveCommandArgsWithInstallFallback(command, toolId, cwd
|
|
|
245
562
|
export async function resolveCommandWithInstallFallback(command, toolId, cwd, versionArgs = ["--version"], timeout = 5000) {
|
|
246
563
|
return verifyOrInstallCommand(command, toolId, cwd, versionArgs, timeout);
|
|
247
564
|
}
|
|
248
|
-
|
|
565
|
+
async function resolveAvailableOrInstallUnshared(checker, toolId, cwd) {
|
|
249
566
|
const available = await checker.isAvailableAsync(cwd);
|
|
250
567
|
if (available) {
|
|
251
568
|
return checker.getCommand(cwd);
|
|
252
569
|
}
|
|
253
|
-
|
|
570
|
+
// Only a typed ENOENT/missing-command result is repairable by installing.
|
|
571
|
+
// Probe failures caused by bad cwd, permissions, rejected flags, aborts, and
|
|
572
|
+
// timeouts are unavailable/non-installable and must not enter an install loop.
|
|
573
|
+
if (checker.getOutcome?.(cwd) !== "missing")
|
|
574
|
+
return null;
|
|
575
|
+
if (!shouldAutoInstallTool(toolId))
|
|
576
|
+
return null;
|
|
577
|
+
const state = installStateFor(cwd, toolId);
|
|
578
|
+
if (state.suppressed) {
|
|
254
579
|
return null;
|
|
255
580
|
}
|
|
256
581
|
const installed = await ensureTool(toolId);
|
|
257
|
-
|
|
582
|
+
if (installed) {
|
|
583
|
+
noteInstallSuccess(toolId, cwd);
|
|
584
|
+
checker.reset?.();
|
|
585
|
+
return installed;
|
|
586
|
+
}
|
|
587
|
+
noteInstallFailure(toolId, cwd);
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
/** Share the complete probe/install transaction for each cwd/tool pair. */
|
|
591
|
+
export function resolveAvailableOrInstall(checker, toolId, cwd) {
|
|
592
|
+
const key = normalizeEphemeralMapKey(cwd);
|
|
593
|
+
let byTool = resolveInstallInFlightByCwd.get(key);
|
|
594
|
+
if (!byTool) {
|
|
595
|
+
byTool = new Map();
|
|
596
|
+
resolveInstallInFlightByCwd.set(key, byTool);
|
|
597
|
+
}
|
|
598
|
+
const existing = byTool.get(toolId);
|
|
599
|
+
if (existing)
|
|
600
|
+
return existing;
|
|
601
|
+
const generation = availabilityStateGeneration;
|
|
602
|
+
const promise = resolveAvailableOrInstallUnshared(checker, toolId, cwd).finally(() => {
|
|
603
|
+
if (generation !== availabilityStateGeneration)
|
|
604
|
+
return;
|
|
605
|
+
const current = resolveInstallInFlightByCwd.get(key);
|
|
606
|
+
if (current?.get(toolId) === promise) {
|
|
607
|
+
current.delete(toolId);
|
|
608
|
+
if (current.size === 0)
|
|
609
|
+
resolveInstallInFlightByCwd.delete(key);
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
byTool.set(toolId, promise);
|
|
613
|
+
return promise;
|
|
258
614
|
}
|
|
259
615
|
// =============================================================================
|
|
260
616
|
// SHARED AST-GREP AVAILABILITY
|
|
261
617
|
// =============================================================================
|
|
262
|
-
|
|
263
|
-
|
|
618
|
+
/**
|
|
619
|
+
* Shared ast-grep availability across all slop runners, behind the transient-
|
|
620
|
+
* aware latch (#1476). This module-level memo carried the same shape `SgRunner`
|
|
621
|
+
* did — one failed sweep, including a timeout, disabled ast-grep for every slop
|
|
622
|
+
* runner for the life of the process.
|
|
623
|
+
*/
|
|
624
|
+
const sgLatch = createAvailabilityLatch();
|
|
264
625
|
let sgCmd = null;
|
|
265
626
|
let sgCmdArgs = [];
|
|
627
|
+
/** Classification of the current sweep, accumulated across candidates. */
|
|
628
|
+
let sgSweepSawTransient = false;
|
|
629
|
+
let sgSweepTransientCause = "probe-timeout";
|
|
630
|
+
let sgSweepHostStallMs = 0;
|
|
266
631
|
function isAstGrepVersionOutput(output) {
|
|
267
632
|
return /\bast[- ]grep\b/i.test(output);
|
|
268
633
|
}
|
|
269
634
|
async function probeAstGrepCommandAsync(cmd, argsPrefix = []) {
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
635
|
+
const sampler = startHostStallSampler();
|
|
636
|
+
let check;
|
|
637
|
+
let hostStallMs;
|
|
638
|
+
try {
|
|
639
|
+
check = await safeSpawnAsync(cmd, [...argsPrefix, "--version"], {
|
|
640
|
+
timeout: 5000,
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
finally {
|
|
644
|
+
hostStallMs = sampler.stop();
|
|
645
|
+
sgSweepHostStallMs += hostStallMs;
|
|
646
|
+
}
|
|
647
|
+
if (!check.error &&
|
|
274
648
|
check.status === 0 &&
|
|
275
|
-
isAstGrepVersionOutput(`${check.stdout}\n${check.stderr}`))
|
|
649
|
+
isAstGrepVersionOutput(`${check.stdout}\n${check.stderr}`)) {
|
|
650
|
+
return true;
|
|
651
|
+
}
|
|
652
|
+
const { outcome, cause } = classifyProbeFailure(check, { hostStallMs });
|
|
653
|
+
if (outcome === "transient") {
|
|
654
|
+
sgSweepSawTransient = true;
|
|
655
|
+
sgSweepTransientCause = cause;
|
|
656
|
+
}
|
|
657
|
+
return false;
|
|
276
658
|
}
|
|
277
659
|
/** Pre-filter local node_modules/.bin candidates that actually exist on disk. */
|
|
278
660
|
function buildSgLocalBins() {
|
|
@@ -302,18 +684,36 @@ function buildSgLocalBins() {
|
|
|
302
684
|
return bins;
|
|
303
685
|
}
|
|
304
686
|
let sgAvailableInFlight = null;
|
|
687
|
+
let sgAvailabilityGeneration = availabilityStateGeneration;
|
|
688
|
+
function ensureCurrentSgGeneration() {
|
|
689
|
+
if (sgAvailabilityGeneration === availabilityStateGeneration)
|
|
690
|
+
return;
|
|
691
|
+
sgLatch.reset();
|
|
692
|
+
sgCmd = null;
|
|
693
|
+
sgCmdArgs = [];
|
|
694
|
+
sgAvailableInFlight = null;
|
|
695
|
+
sgAvailabilityGeneration = availabilityStateGeneration;
|
|
696
|
+
}
|
|
305
697
|
export async function isSgAvailableAsync() {
|
|
306
|
-
|
|
307
|
-
|
|
698
|
+
ensureCurrentSgGeneration();
|
|
699
|
+
// `read()` returns null when the last verdict was transient and its cooldown
|
|
700
|
+
// expired: re-probe rather than stay dead for the session (#1476).
|
|
701
|
+
const memo = sgLatch.read();
|
|
702
|
+
if (memo !== null)
|
|
703
|
+
return memo;
|
|
308
704
|
if (sgAvailableInFlight)
|
|
309
705
|
return sgAvailableInFlight;
|
|
310
706
|
sgAvailableInFlight = (async () => {
|
|
707
|
+
const startedAt = Date.now();
|
|
708
|
+
sgSweepSawTransient = false;
|
|
709
|
+
sgSweepTransientCause = "probe-timeout";
|
|
710
|
+
sgSweepHostStallMs = 0;
|
|
311
711
|
// 1. Local node_modules/.bin
|
|
312
712
|
for (const localBin of buildSgLocalBins()) {
|
|
313
713
|
if (await probeAstGrepCommandAsync(localBin)) {
|
|
314
714
|
sgCmd = localBin;
|
|
315
715
|
sgCmdArgs = [];
|
|
316
|
-
|
|
716
|
+
noteSgAvailable(startedAt);
|
|
317
717
|
return true;
|
|
318
718
|
}
|
|
319
719
|
}
|
|
@@ -322,7 +722,7 @@ export async function isSgAvailableAsync() {
|
|
|
322
722
|
if (await probeAstGrepCommandAsync(cmd)) {
|
|
323
723
|
sgCmd = cmd;
|
|
324
724
|
sgCmdArgs = [];
|
|
325
|
-
|
|
725
|
+
noteSgAvailable(startedAt);
|
|
326
726
|
return true;
|
|
327
727
|
}
|
|
328
728
|
}
|
|
@@ -333,7 +733,7 @@ export async function isSgAvailableAsync() {
|
|
|
333
733
|
if (globalBin && (await probeAstGrepCommandAsync(globalBin))) {
|
|
334
734
|
sgCmd = globalBin;
|
|
335
735
|
sgCmdArgs = [];
|
|
336
|
-
|
|
736
|
+
noteSgAvailable(startedAt);
|
|
337
737
|
return true;
|
|
338
738
|
}
|
|
339
739
|
}
|
|
@@ -341,17 +741,48 @@ export async function isSgAvailableAsync() {
|
|
|
341
741
|
if (await probeAstGrepCommandAsync("npx", ["--no", "--", "ast-grep"])) {
|
|
342
742
|
sgCmd = "npx";
|
|
343
743
|
sgCmdArgs = ["--no", "--", "ast-grep"];
|
|
344
|
-
|
|
744
|
+
noteSgAvailable(startedAt);
|
|
345
745
|
return true;
|
|
346
746
|
}
|
|
347
|
-
|
|
747
|
+
// A timeout on ANY candidate is evidence about the host, not the tool.
|
|
748
|
+
noteSgUnavailable(startedAt, sgSweepSawTransient ? "transient" : "missing", sgSweepSawTransient ? sgSweepTransientCause : "not-found");
|
|
348
749
|
return false;
|
|
349
750
|
})().finally(() => {
|
|
350
751
|
sgAvailableInFlight = null;
|
|
351
752
|
});
|
|
352
753
|
return sgAvailableInFlight;
|
|
353
754
|
}
|
|
755
|
+
/** Record a successful shared-ast-grep sweep, with one decision record. */
|
|
756
|
+
function noteSgAvailable(startedAt) {
|
|
757
|
+
sgLatch.noteAvailable();
|
|
758
|
+
logAvailabilityDecision({
|
|
759
|
+
tool: "ast-grep",
|
|
760
|
+
verdict: "available",
|
|
761
|
+
outcome: "success",
|
|
762
|
+
cause: "ok",
|
|
763
|
+
elapsedMs: Date.now() - startedAt,
|
|
764
|
+
latched: true,
|
|
765
|
+
hostStallMs: sgSweepHostStallMs,
|
|
766
|
+
budgetMs: 5000,
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
/** Record a failed shared-ast-grep sweep; a transient verdict expires. */
|
|
770
|
+
function noteSgUnavailable(startedAt, outcome, cause) {
|
|
771
|
+
const retryAfterMs = sgLatch.noteUnavailable(outcome, cause);
|
|
772
|
+
logAvailabilityDecision({
|
|
773
|
+
tool: "ast-grep",
|
|
774
|
+
verdict: "unavailable",
|
|
775
|
+
outcome,
|
|
776
|
+
cause,
|
|
777
|
+
elapsedMs: Date.now() - startedAt,
|
|
778
|
+
latched: outcome !== "transient",
|
|
779
|
+
hostStallMs: sgSweepHostStallMs,
|
|
780
|
+
...(retryAfterMs > 0 && { retryAfterMs }),
|
|
781
|
+
budgetMs: 5000,
|
|
782
|
+
});
|
|
783
|
+
}
|
|
354
784
|
export function getSgCommand() {
|
|
785
|
+
ensureCurrentSgGeneration();
|
|
355
786
|
return {
|
|
356
787
|
cmd: sgCmd ?? "npx",
|
|
357
788
|
args: sgCmdArgs.length ? sgCmdArgs : ["--no", "--", "ast-grep"],
|