@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
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
* - pip packages
|
|
46
46
|
* - GitHub releases (platform-specific binaries → ~/.pi-lens/bin/)
|
|
47
47
|
*/
|
|
48
|
+
import { logExtension } from "../extension-log.js";
|
|
48
49
|
import { spawn } from "node:child_process";
|
|
49
50
|
import { existsSync, statSync, unlinkSync } from "node:fs";
|
|
50
51
|
import fs from "node:fs/promises";
|
|
@@ -52,17 +53,29 @@ import https from "node:https";
|
|
|
52
53
|
import { createRequire } from "node:module";
|
|
53
54
|
import os from "node:os";
|
|
54
55
|
import path from "node:path";
|
|
56
|
+
import { BoundedLruCache } from "../bounded-cache.js";
|
|
57
|
+
import { isFullyQualified } from "../path-utils.js";
|
|
58
|
+
import { assertInstallAllowed, projectTrustDenialReason, } from "../project-trust.js";
|
|
55
59
|
const _installerRequire = createRequire(import.meta.url);
|
|
56
60
|
import { createGunzip } from "node:zlib";
|
|
57
61
|
import { logSessionStart } from "../sessionstart-logger.js";
|
|
62
|
+
import { commitDurableStoreAsync } from "../durable-store.js";
|
|
58
63
|
import { getGlobalPiLensDir } from "../file-utils.js";
|
|
59
64
|
import { allAvailableGlobalBinDirs, installArgs, pmBinary, resolveNodePackageManager, } from "../package-manager.js";
|
|
60
|
-
import { safeSpawnAsync } from "../safe-spawn.js";
|
|
65
|
+
import { resetSafeSpawnWindowsCommandCache, safeSpawnAsync, } from "../safe-spawn.js";
|
|
61
66
|
// Global installation directory for pi-lens tools
|
|
62
67
|
const TOOLS_DIR = path.join(getGlobalPiLensDir(), "tools");
|
|
63
68
|
const INSTALL_LOCK_PATH = path.join(TOOLS_DIR, ".install.lock");
|
|
64
69
|
const activeInstallLocks = new Set();
|
|
65
70
|
let installLockExitCleanupRegistered = false;
|
|
71
|
+
/**
|
|
72
|
+
* The managed tools tree, for callers that need to tell a path `getToolPath()`
|
|
73
|
+
* returned from the managed install apart from a global/PATH hit (the tool
|
|
74
|
+
* registry is this module's business — don't re-derive `<pi-lens home>/tools`).
|
|
75
|
+
*/
|
|
76
|
+
export function getManagedToolsDir() {
|
|
77
|
+
return TOOLS_DIR;
|
|
78
|
+
}
|
|
66
79
|
function isProcessAlive(pid) {
|
|
67
80
|
try {
|
|
68
81
|
process.kill(pid, 0);
|
|
@@ -164,12 +177,27 @@ async function acquireInstallLock() {
|
|
|
164
177
|
const GITHUB_BIN_DIR = path.join(getGlobalPiLensDir(), "bin");
|
|
165
178
|
// Debug flag - set via PI_LENS_DEBUG=1 or --debug
|
|
166
179
|
const DEBUG = process.env.PI_LENS_DEBUG === "1" || process.argv.includes("--debug");
|
|
180
|
+
/** Test-only platform seam for Windows resource-layout coverage on Linux CI. */
|
|
181
|
+
function installerPlatform() {
|
|
182
|
+
const override = process.env.PI_LENS_TEST_PLATFORM;
|
|
183
|
+
if (override === "win32" || override === "linux") {
|
|
184
|
+
return override;
|
|
185
|
+
}
|
|
186
|
+
return process.platform;
|
|
187
|
+
}
|
|
167
188
|
/**
|
|
168
189
|
* Log debug messages only when DEBUG is enabled
|
|
169
190
|
*/
|
|
170
191
|
function debugLog(...args) {
|
|
171
192
|
if (DEBUG) {
|
|
172
|
-
|
|
193
|
+
// #1333: DEBUG gate preserved; sink is extension.log, never the TUI.
|
|
194
|
+
logExtension({
|
|
195
|
+
subsystem: "auto-install",
|
|
196
|
+
level: "debug",
|
|
197
|
+
message: args
|
|
198
|
+
.map((arg) => (typeof arg === "string" ? arg : JSON.stringify(arg)))
|
|
199
|
+
.join(" "),
|
|
200
|
+
});
|
|
173
201
|
}
|
|
174
202
|
}
|
|
175
203
|
/**
|
|
@@ -177,16 +205,25 @@ function debugLog(...args) {
|
|
|
177
205
|
* the copy-pasted `if (platform === "linux") return arch === "arm64" ? … : …`
|
|
178
206
|
* ladder that several release entries repeat verbatim. Each platform maps to its
|
|
179
207
|
* `x64` (default) and optional `arm64` asset substring; a missing platform or
|
|
180
|
-
* arch ⇒ unsupported (`undefined`)
|
|
208
|
+
* arch ⇒ unsupported (`undefined`). Arches outside x64/arm64 (ia32, ppc64, …)
|
|
209
|
+
* are unsupported too — no release entry ships a 32-bit or exotic asset, and
|
|
210
|
+
* handing back the x64 substring there would install an unrunnable binary.
|
|
181
211
|
*/
|
|
182
212
|
function archAssetMatch(table) {
|
|
183
213
|
return (platform, arch) => {
|
|
184
|
-
|
|
185
|
-
if (!entry)
|
|
214
|
+
if (arch !== "x64" && arch !== "arm64")
|
|
186
215
|
return undefined;
|
|
187
|
-
return arch
|
|
216
|
+
return table[platform]?.[arch];
|
|
188
217
|
};
|
|
189
218
|
}
|
|
219
|
+
// Go-style `<os>_<arch>.zip` release assets, shared verbatim by tflint and
|
|
220
|
+
// terraform-ls — both entries carried byte-identical ladders, down to the asset
|
|
221
|
+
// strings themselves.
|
|
222
|
+
const OS_ARCH_ZIP_ASSETS = {
|
|
223
|
+
linux: { x64: "linux_amd64.zip", arm64: "linux_arm64.zip" },
|
|
224
|
+
darwin: { x64: "darwin_amd64.zip", arm64: "darwin_arm64.zip" },
|
|
225
|
+
win32: { x64: "windows_amd64.zip", arm64: "windows_arm64.zip" },
|
|
226
|
+
};
|
|
190
227
|
export const TOOLS = [
|
|
191
228
|
// Core LSP servers
|
|
192
229
|
{
|
|
@@ -204,7 +241,11 @@ export const TOOLS = [
|
|
|
204
241
|
checkCommand: "tsc",
|
|
205
242
|
checkArgs: ["--version"],
|
|
206
243
|
installStrategy: "npm",
|
|
207
|
-
|
|
244
|
+
// The managed compiler serves the classic typescript-language-server
|
|
245
|
+
// fallback. TypeScript 7 removed lib/tsserver.js and is selected only from
|
|
246
|
+
// project-local installs through the native `tsc --lsp --stdio` path.
|
|
247
|
+
// Revisit when typescript-language-server supports TS 7 — refs #1436.
|
|
248
|
+
packageName: "typescript@5.9.3",
|
|
208
249
|
binaryName: "tsc",
|
|
209
250
|
},
|
|
210
251
|
{
|
|
@@ -418,6 +459,31 @@ export const TOOLS = [
|
|
|
418
459
|
},
|
|
419
460
|
},
|
|
420
461
|
},
|
|
462
|
+
{
|
|
463
|
+
id: "helm",
|
|
464
|
+
name: "Helm",
|
|
465
|
+
checkCommand: "helm",
|
|
466
|
+
checkArgs: ["version", "--short"],
|
|
467
|
+
installStrategy: "github",
|
|
468
|
+
binaryName: "helm",
|
|
469
|
+
github: {
|
|
470
|
+
repo: "helm/helm",
|
|
471
|
+
assetMatch: (platform, arch) => {
|
|
472
|
+
// helm publishes per-OS archives: tar.gz for POSIX, zip for Windows.
|
|
473
|
+
const cpu = arch === "arm64" ? "arm64" : "amd64";
|
|
474
|
+
if (platform === "linux")
|
|
475
|
+
return `linux-${cpu}.tar.gz`;
|
|
476
|
+
if (platform === "darwin")
|
|
477
|
+
return `darwin-${cpu}.tar.gz`;
|
|
478
|
+
if (platform === "win32")
|
|
479
|
+
return `windows-${cpu}.zip`;
|
|
480
|
+
return undefined;
|
|
481
|
+
},
|
|
482
|
+
// Release archives nest the executable under an OS/arch directory;
|
|
483
|
+
// the installer searches recursively and adds the Windows suffix.
|
|
484
|
+
binaryInArchive: "helm",
|
|
485
|
+
},
|
|
486
|
+
},
|
|
421
487
|
{
|
|
422
488
|
// Opengrep: a single standalone binary per platform on GitHub releases —
|
|
423
489
|
// no login, no telemetry (the reason for switching off Semgrep, #111).
|
|
@@ -919,18 +985,39 @@ export const TOOLS = [
|
|
|
919
985
|
binaryName: "tflint",
|
|
920
986
|
github: {
|
|
921
987
|
repo: "terraform-linters/tflint",
|
|
922
|
-
assetMatch: (
|
|
923
|
-
if (platform === "linux")
|
|
924
|
-
return arch === "arm64" ? "linux_arm64.zip" : "linux_amd64.zip";
|
|
925
|
-
if (platform === "darwin")
|
|
926
|
-
return arch === "arm64" ? "darwin_arm64.zip" : "darwin_amd64.zip";
|
|
927
|
-
if (platform === "win32")
|
|
928
|
-
return arch === "arm64" ? "windows_arm64.zip" : "windows_amd64.zip";
|
|
929
|
-
return undefined;
|
|
930
|
-
},
|
|
988
|
+
assetMatch: archAssetMatch(OS_ARCH_ZIP_ASSETS),
|
|
931
989
|
binaryInArchive: "tflint",
|
|
932
990
|
},
|
|
933
991
|
},
|
|
992
|
+
{
|
|
993
|
+
// Terragrunt ships a bare native binary per platform on GitHub releases.
|
|
994
|
+
// Windows arm64 uses the x64 binary through Windows' built-in emulation —
|
|
995
|
+
// there is no terragrunt_windows_arm64.exe upstream.
|
|
996
|
+
id: "terragrunt",
|
|
997
|
+
name: "terragrunt",
|
|
998
|
+
checkCommand: "terragrunt",
|
|
999
|
+
checkArgs: ["--version"],
|
|
1000
|
+
installStrategy: "github",
|
|
1001
|
+
binaryName: "terragrunt",
|
|
1002
|
+
github: {
|
|
1003
|
+
repo: "gruntwork-io/terragrunt",
|
|
1004
|
+
assetMatch: archAssetMatch({
|
|
1005
|
+
linux: {
|
|
1006
|
+
x64: "terragrunt_linux_amd64",
|
|
1007
|
+
arm64: "terragrunt_linux_arm64",
|
|
1008
|
+
},
|
|
1009
|
+
darwin: {
|
|
1010
|
+
x64: "terragrunt_darwin_amd64",
|
|
1011
|
+
arm64: "terragrunt_darwin_arm64",
|
|
1012
|
+
},
|
|
1013
|
+
win32: {
|
|
1014
|
+
x64: "terragrunt_windows_amd64.exe",
|
|
1015
|
+
arm64: "terragrunt_windows_amd64.exe",
|
|
1016
|
+
},
|
|
1017
|
+
}),
|
|
1018
|
+
// bare binary — no binaryInArchive
|
|
1019
|
+
},
|
|
1020
|
+
},
|
|
934
1021
|
{
|
|
935
1022
|
id: "gitleaks",
|
|
936
1023
|
name: "gitleaks",
|
|
@@ -1055,15 +1142,7 @@ export const TOOLS = [
|
|
|
1055
1142
|
github: {
|
|
1056
1143
|
repo: "hashicorp/terraform-ls",
|
|
1057
1144
|
hashiCorpReleaseProduct: "terraform-ls",
|
|
1058
|
-
assetMatch: (
|
|
1059
|
-
if (platform === "linux")
|
|
1060
|
-
return arch === "arm64" ? "linux_arm64.zip" : "linux_amd64.zip";
|
|
1061
|
-
if (platform === "darwin")
|
|
1062
|
-
return arch === "arm64" ? "darwin_arm64.zip" : "darwin_amd64.zip";
|
|
1063
|
-
if (platform === "win32")
|
|
1064
|
-
return arch === "arm64" ? "windows_arm64.zip" : "windows_amd64.zip";
|
|
1065
|
-
return undefined;
|
|
1066
|
-
},
|
|
1145
|
+
assetMatch: archAssetMatch(OS_ARCH_ZIP_ASSETS),
|
|
1067
1146
|
binaryInArchive: "terraform-ls",
|
|
1068
1147
|
},
|
|
1069
1148
|
},
|
|
@@ -1221,33 +1300,164 @@ export function getInstallFailureReason(toolId) {
|
|
|
1221
1300
|
return installFailureReasons.get(toolId);
|
|
1222
1301
|
}
|
|
1223
1302
|
// Session-lifetime cache: once a tool path is resolved, skip the process-spawn check on subsequent calls.
|
|
1224
|
-
const resolvedPathCache = new
|
|
1303
|
+
const resolvedPathCache = new BoundedLruCache(256);
|
|
1225
1304
|
const PROBE_CACHE_PATH = path.join(getGlobalPiLensDir(), "probe-cache.json");
|
|
1305
|
+
const PROBE_CACHE_LOCK_STALE_MS = 180_000;
|
|
1226
1306
|
const PROBE_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
1307
|
+
const PROBE_CACHE_FLUSH_LOCK_WAIT_MS = 250;
|
|
1308
|
+
const PROBE_CACHE_FLUSH_RETRY_DELAY_MS = 300;
|
|
1309
|
+
const PROBE_CACHE_FLUSH_RETRY_MAX_DELAY_MS = 30_000;
|
|
1227
1310
|
let _probeCache = null;
|
|
1228
1311
|
let _probeCacheDirty = false;
|
|
1229
1312
|
let _probeCacheFlushTimer = null;
|
|
1313
|
+
let _probeCacheWriteInFlight = null;
|
|
1314
|
+
let _probeCacheRetryAttempt = 0;
|
|
1315
|
+
let _probeCacheChangeGeneration = 0;
|
|
1316
|
+
// Read-modify-write deltas let a long-lived process merge with entries another
|
|
1317
|
+
// process discovered after this process's initial read. `null` is a deliberate
|
|
1318
|
+
// deletion (TTL/stale-path eviction), not an absent map value.
|
|
1319
|
+
const _probeCacheChanges = new Map();
|
|
1320
|
+
const _probeCacheChangeVersions = new Map();
|
|
1230
1321
|
async function readProbeCache() {
|
|
1231
1322
|
if (_probeCache !== null)
|
|
1232
1323
|
return _probeCache;
|
|
1233
1324
|
try {
|
|
1234
1325
|
const raw = await fs.readFile(PROBE_CACHE_PATH, "utf-8");
|
|
1235
|
-
|
|
1326
|
+
const parsed = JSON.parse(raw);
|
|
1327
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1328
|
+
throw new Error("probe-cache root is not an object");
|
|
1329
|
+
}
|
|
1330
|
+
_probeCache = parsed;
|
|
1236
1331
|
}
|
|
1237
|
-
catch {
|
|
1332
|
+
catch (err) {
|
|
1333
|
+
const code = err?.code;
|
|
1334
|
+
if (code !== "ENOENT") {
|
|
1335
|
+
logSessionStart(`auto-install probe-cache: read failed (${code ?? "invalid"}); treating cache as unavailable`);
|
|
1336
|
+
}
|
|
1238
1337
|
_probeCache = {};
|
|
1239
1338
|
}
|
|
1240
1339
|
return _probeCache;
|
|
1241
1340
|
}
|
|
1242
|
-
function
|
|
1341
|
+
function markProbeCacheChange(toolId, entry) {
|
|
1342
|
+
_probeCacheChanges.set(toolId, entry);
|
|
1343
|
+
_probeCacheChangeVersions.set(toolId, ++_probeCacheChangeGeneration);
|
|
1344
|
+
_probeCacheDirty = true;
|
|
1345
|
+
scheduleProbeFlush();
|
|
1346
|
+
}
|
|
1347
|
+
function scheduleProbeFlush(delayMs = PROBE_CACHE_FLUSH_RETRY_DELAY_MS) {
|
|
1243
1348
|
if (_probeCacheFlushTimer !== null)
|
|
1244
1349
|
return;
|
|
1245
1350
|
_probeCacheFlushTimer = setTimeout(() => {
|
|
1246
1351
|
void flushProbeCache();
|
|
1247
|
-
},
|
|
1352
|
+
}, delayMs);
|
|
1248
1353
|
_probeCacheFlushTimer.unref?.();
|
|
1249
1354
|
}
|
|
1250
|
-
|
|
1355
|
+
function scheduleProbeFlushRetry() {
|
|
1356
|
+
const delay = Math.min(PROBE_CACHE_FLUSH_RETRY_MAX_DELAY_MS, PROBE_CACHE_FLUSH_RETRY_DELAY_MS * 2 ** Math.min(_probeCacheRetryAttempt, 6));
|
|
1357
|
+
_probeCacheRetryAttempt += 1;
|
|
1358
|
+
scheduleProbeFlush(delay);
|
|
1359
|
+
}
|
|
1360
|
+
function snapshotProbeCacheChanges() {
|
|
1361
|
+
return {
|
|
1362
|
+
changes: new Map(_probeCacheChanges),
|
|
1363
|
+
versions: new Map(_probeCacheChangeVersions),
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
function deserializeProbeCache(contents) {
|
|
1367
|
+
if (contents === undefined)
|
|
1368
|
+
return {};
|
|
1369
|
+
const parsed = JSON.parse(contents);
|
|
1370
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1371
|
+
throw new Error("probe-cache root is not an object");
|
|
1372
|
+
}
|
|
1373
|
+
return parsed;
|
|
1374
|
+
}
|
|
1375
|
+
function applyProbeCacheChanges(disk, changes) {
|
|
1376
|
+
for (const [toolId, entry] of changes) {
|
|
1377
|
+
if (entry === null)
|
|
1378
|
+
delete disk[toolId];
|
|
1379
|
+
else
|
|
1380
|
+
disk[toolId] = entry;
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
/**
|
|
1384
|
+
* Install-lifetime ageing belongs to the authoritative merge, not only to the
|
|
1385
|
+
* lookup path: a sibling process may have left expired entries on disk since
|
|
1386
|
+
* this process loaded its in-memory snapshot. The async durable-store lock
|
|
1387
|
+
* supplies the probe cache's former quarantine/stale-owner recovery.
|
|
1388
|
+
*/
|
|
1389
|
+
function ageProbeCache(disk) {
|
|
1390
|
+
const cutoff = Date.now() - PROBE_CACHE_TTL_MS;
|
|
1391
|
+
for (const [toolId, entry] of Object.entries(disk)) {
|
|
1392
|
+
if (!Number.isFinite(entry.cachedAt) || entry.cachedAt < cutoff) {
|
|
1393
|
+
delete disk[toolId];
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
function publishProbeCacheWrite(disk, snapshotVersions) {
|
|
1398
|
+
// Publish the sibling-process merge plus any updates that arrived during
|
|
1399
|
+
// the write. Only an unchanged snapshot entry is retired.
|
|
1400
|
+
const pendingAfterWrite = new Map(_probeCacheChanges);
|
|
1401
|
+
_probeCache = disk;
|
|
1402
|
+
for (const [toolId, entry] of pendingAfterWrite) {
|
|
1403
|
+
if (entry === null)
|
|
1404
|
+
delete _probeCache[toolId];
|
|
1405
|
+
else
|
|
1406
|
+
_probeCache[toolId] = entry;
|
|
1407
|
+
}
|
|
1408
|
+
for (const [toolId, version] of snapshotVersions) {
|
|
1409
|
+
if (_probeCacheChangeVersions.get(toolId) === version) {
|
|
1410
|
+
_probeCacheChanges.delete(toolId);
|
|
1411
|
+
_probeCacheChangeVersions.delete(toolId);
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
_probeCacheDirty = _probeCacheChanges.size > 0;
|
|
1415
|
+
_probeCacheRetryAttempt = 0;
|
|
1416
|
+
return _probeCacheDirty ? "written-with-pending" : "written";
|
|
1417
|
+
}
|
|
1418
|
+
async function writeProbeCache() {
|
|
1419
|
+
try {
|
|
1420
|
+
// Snapshot versions before the awaited disk read/write. A new update for
|
|
1421
|
+
// the same tool may arrive while the atomic write is in flight; its newer
|
|
1422
|
+
// version must remain pending for the next flush.
|
|
1423
|
+
const { changes, versions } = snapshotProbeCacheChanges();
|
|
1424
|
+
let result = "written";
|
|
1425
|
+
const committed = await commitDurableStoreAsync({
|
|
1426
|
+
path: PROBE_CACHE_PATH,
|
|
1427
|
+
deserialize: deserializeProbeCache,
|
|
1428
|
+
merge: (disk) => {
|
|
1429
|
+
ageProbeCache(disk);
|
|
1430
|
+
applyProbeCacheChanges(disk, changes);
|
|
1431
|
+
return disk;
|
|
1432
|
+
},
|
|
1433
|
+
serialize: (disk) => JSON.stringify(disk, null, 2),
|
|
1434
|
+
waitMs: PROBE_CACHE_FLUSH_LOCK_WAIT_MS,
|
|
1435
|
+
retryMs: 25,
|
|
1436
|
+
staleMs: PROBE_CACHE_LOCK_STALE_MS,
|
|
1437
|
+
timeoutMessage: "Timed out waiting for probe-cache lock",
|
|
1438
|
+
onContention: "skip-log",
|
|
1439
|
+
logContention: () => {
|
|
1440
|
+
logSessionStart("auto-install probe-cache: flush deferred because another process owns the lock");
|
|
1441
|
+
},
|
|
1442
|
+
afterWriteLocked: (disk) => {
|
|
1443
|
+
result = publishProbeCacheWrite(disk, versions);
|
|
1444
|
+
},
|
|
1445
|
+
});
|
|
1446
|
+
if (committed === undefined) {
|
|
1447
|
+
scheduleProbeFlushRetry();
|
|
1448
|
+
return "deferred";
|
|
1449
|
+
}
|
|
1450
|
+
return result;
|
|
1451
|
+
}
|
|
1452
|
+
catch (err) {
|
|
1453
|
+
// Keep dirty state so a later timer/explicit flush can retry. The error
|
|
1454
|
+
// is logged without paths, source, or command text; an unavailable cache
|
|
1455
|
+
// must never look like a clean empty cache to operators.
|
|
1456
|
+
logSessionStart(`auto-install probe-cache: flush failed (${err?.code ?? "write error"}); pending update retained`);
|
|
1457
|
+
scheduleProbeFlushRetry();
|
|
1458
|
+
return "failed";
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1251
1461
|
/** Await pending probe-cache persistence before a one-shot process exits. */
|
|
1252
1462
|
export async function flushProbeCache() {
|
|
1253
1463
|
if (_probeCacheFlushTimer !== null) {
|
|
@@ -1257,22 +1467,17 @@ export async function flushProbeCache() {
|
|
|
1257
1467
|
// #946 review F4: if the 300ms timer's write already started, the dirty
|
|
1258
1468
|
// flag is false but the write may still be in flight — an immediate
|
|
1259
1469
|
// process.exit would truncate it. Always await the in-flight write.
|
|
1260
|
-
if (
|
|
1470
|
+
if (_probeCacheWriteInFlight) {
|
|
1261
1471
|
await _probeCacheWriteInFlight;
|
|
1262
|
-
|
|
1472
|
+
if (!_probeCacheDirty)
|
|
1473
|
+
return "written";
|
|
1263
1474
|
}
|
|
1264
|
-
_probeCacheDirty
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
await fs.writeFile(PROBE_CACHE_PATH, JSON.stringify(_probeCache, null, 2));
|
|
1268
|
-
}
|
|
1269
|
-
catch {
|
|
1270
|
-
// Cache persistence is best effort; discovery remains authoritative.
|
|
1271
|
-
}
|
|
1272
|
-
})();
|
|
1475
|
+
if (!_probeCacheDirty || _probeCache === null)
|
|
1476
|
+
return "idle";
|
|
1477
|
+
const write = writeProbeCache();
|
|
1273
1478
|
_probeCacheWriteInFlight = write;
|
|
1274
1479
|
try {
|
|
1275
|
-
await write;
|
|
1480
|
+
return await write;
|
|
1276
1481
|
}
|
|
1277
1482
|
finally {
|
|
1278
1483
|
if (_probeCacheWriteInFlight === write)
|
|
@@ -1316,8 +1521,7 @@ export async function checkProbeCache(toolId) {
|
|
|
1316
1521
|
if (Date.now() - entry.cachedAt > PROBE_CACHE_TTL_MS) {
|
|
1317
1522
|
logSessionStart(`auto-install probe-cache ${toolId}: miss (ttl expired)`);
|
|
1318
1523
|
delete cache[toolId];
|
|
1319
|
-
|
|
1320
|
-
scheduleProbeFlush();
|
|
1524
|
+
markProbeCacheChange(toolId, null);
|
|
1321
1525
|
return undefined;
|
|
1322
1526
|
}
|
|
1323
1527
|
try {
|
|
@@ -1326,15 +1530,13 @@ export async function checkProbeCache(toolId) {
|
|
|
1326
1530
|
if (stat.mtimeMs !== entry.mtimeMs) {
|
|
1327
1531
|
logSessionStart(`auto-install probe-cache ${toolId}: miss (mtime changed)`);
|
|
1328
1532
|
delete cache[toolId];
|
|
1329
|
-
|
|
1330
|
-
scheduleProbeFlush();
|
|
1533
|
+
markProbeCacheChange(toolId, null);
|
|
1331
1534
|
return undefined;
|
|
1332
1535
|
}
|
|
1333
1536
|
if (toolId === "ast-grep" && !(await verifyAstGrepProbePath(entry.path))) {
|
|
1334
1537
|
logSessionStart(`auto-install probe-cache ${toolId}: miss (not ast-grep: ${entry.path})`);
|
|
1335
1538
|
delete cache[toolId];
|
|
1336
|
-
|
|
1337
|
-
scheduleProbeFlush();
|
|
1539
|
+
markProbeCacheChange(toolId, null);
|
|
1338
1540
|
return undefined;
|
|
1339
1541
|
}
|
|
1340
1542
|
return entry.path;
|
|
@@ -1342,8 +1544,7 @@ export async function checkProbeCache(toolId) {
|
|
|
1342
1544
|
catch {
|
|
1343
1545
|
logSessionStart(`auto-install probe-cache ${toolId}: miss (gone: ${entry.path})`);
|
|
1344
1546
|
delete cache[toolId];
|
|
1345
|
-
|
|
1346
|
-
scheduleProbeFlush();
|
|
1547
|
+
markProbeCacheChange(toolId, null);
|
|
1347
1548
|
return undefined;
|
|
1348
1549
|
}
|
|
1349
1550
|
}
|
|
@@ -1352,13 +1553,13 @@ export async function updateProbeCache(toolId, resolvedPath) {
|
|
|
1352
1553
|
try {
|
|
1353
1554
|
const stat = await fs.stat(resolvedPath);
|
|
1354
1555
|
const cache = await readProbeCache();
|
|
1355
|
-
|
|
1556
|
+
const entry = {
|
|
1356
1557
|
path: resolvedPath,
|
|
1357
1558
|
mtimeMs: stat.mtimeMs,
|
|
1358
1559
|
cachedAt: Date.now(),
|
|
1359
1560
|
};
|
|
1360
|
-
|
|
1361
|
-
|
|
1561
|
+
cache[toolId] = entry;
|
|
1562
|
+
markProbeCacheChange(toolId, entry);
|
|
1362
1563
|
}
|
|
1363
1564
|
catch {
|
|
1364
1565
|
// best-effort
|
|
@@ -1368,16 +1569,33 @@ export async function updateProbeCache(toolId, resolvedPath) {
|
|
|
1368
1569
|
export function resetProbeCacheStateForTesting() {
|
|
1369
1570
|
_probeCache = null;
|
|
1370
1571
|
_probeCacheDirty = false;
|
|
1572
|
+
_probeCacheChanges.clear();
|
|
1573
|
+
_probeCacheChangeVersions.clear();
|
|
1574
|
+
_probeCacheChangeGeneration = 0;
|
|
1575
|
+
_probeCacheRetryAttempt = 0;
|
|
1371
1576
|
resolvedPathCache.clear();
|
|
1372
1577
|
ensureInFlight.clear();
|
|
1373
1578
|
installFailureReasons.clear();
|
|
1374
1579
|
lastManagedInstallVersion.clear();
|
|
1580
|
+
resetPathWalkMemo();
|
|
1375
1581
|
if (_probeCacheFlushTimer !== null) {
|
|
1376
1582
|
clearTimeout(_probeCacheFlushTimer);
|
|
1377
1583
|
_probeCacheFlushTimer = null;
|
|
1378
1584
|
}
|
|
1379
1585
|
}
|
|
1380
1586
|
// --- Check Functions ---
|
|
1587
|
+
const pathWalkMemo = new Map();
|
|
1588
|
+
function hashSync(value) {
|
|
1589
|
+
let hash = 0x811c9dc5;
|
|
1590
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
1591
|
+
hash ^= value.charCodeAt(i);
|
|
1592
|
+
hash = Math.imul(hash, 0x01000193);
|
|
1593
|
+
}
|
|
1594
|
+
return (hash >>> 0).toString(16);
|
|
1595
|
+
}
|
|
1596
|
+
export function resetPathWalkMemo() {
|
|
1597
|
+
pathWalkMemo.clear();
|
|
1598
|
+
}
|
|
1381
1599
|
/**
|
|
1382
1600
|
* Check if a command is available in PATH by walking PATH entries and
|
|
1383
1601
|
* verifying each candidate is a real file with non-zero size.
|
|
@@ -1385,7 +1603,7 @@ export function resetProbeCacheStateForTesting() {
|
|
|
1385
1603
|
* spawning a process — ~μs per candidate vs ~50ms for which/where.
|
|
1386
1604
|
*/
|
|
1387
1605
|
export async function isCommandAvailable(command, _args) {
|
|
1388
|
-
const isWindows =
|
|
1606
|
+
const isWindows = installerPlatform() === "win32";
|
|
1389
1607
|
const pathEnv = process.env.PATH || process.env.Path || process.env.path || "";
|
|
1390
1608
|
const dirs = pathEnv.split(path.delimiter);
|
|
1391
1609
|
// On Windows, probe .exe, .cmd, and .bat extensions in addition to bare name.
|
|
@@ -1427,7 +1645,12 @@ export async function isSpawnableCommand(command) {
|
|
|
1427
1645
|
return false;
|
|
1428
1646
|
}
|
|
1429
1647
|
}
|
|
1430
|
-
|
|
1648
|
+
const memoKey = `${command}:${hashSync(process.env.PATH || "")}`;
|
|
1649
|
+
if (pathWalkMemo.has(memoKey))
|
|
1650
|
+
return pathWalkMemo.get(memoKey) ?? false;
|
|
1651
|
+
const isSpawnable = await isCommandAvailable(command);
|
|
1652
|
+
pathWalkMemo.set(memoKey, isSpawnable);
|
|
1653
|
+
return isSpawnable;
|
|
1431
1654
|
}
|
|
1432
1655
|
// --- Verification Functions
|
|
1433
1656
|
/**
|
|
@@ -1483,7 +1706,7 @@ const lastManagedInstallVersion = new Map();
|
|
|
1483
1706
|
*/
|
|
1484
1707
|
async function verifyToolBinary(binPath, onVersionOutput) {
|
|
1485
1708
|
return new Promise((resolve) => {
|
|
1486
|
-
const isWindows =
|
|
1709
|
+
const isWindows = installerPlatform() === "win32";
|
|
1487
1710
|
const hasKnownWindowsExt = /\.(cmd|exe|ps1)$/i.test(binPath);
|
|
1488
1711
|
// On Windows, resolve the best executable path:
|
|
1489
1712
|
// - extensionless → prefer .cmd (cmd.exe-safe)
|
|
@@ -1644,7 +1867,7 @@ export async function getAllToolStatuses() {
|
|
|
1644
1867
|
}
|
|
1645
1868
|
// 5. Check pi-lens auto-install (~/.pi-lens/tools/)
|
|
1646
1869
|
const localBase = path.join(TOOLS_DIR, "node_modules", ".bin", tool.binaryName || tool.id);
|
|
1647
|
-
const localPath =
|
|
1870
|
+
const localPath = installerPlatform() === "win32" ? `${localBase}.cmd` : localBase;
|
|
1648
1871
|
try {
|
|
1649
1872
|
await fs.access(localPath);
|
|
1650
1873
|
if (await verifyToolBinary(localPath)) {
|
|
@@ -1709,7 +1932,7 @@ export function resolvePlatformPackageBinary(tool) {
|
|
|
1709
1932
|
const spec = tool.platformPackage;
|
|
1710
1933
|
if (!spec || !tool.packageName)
|
|
1711
1934
|
return undefined;
|
|
1712
|
-
const suffix = spec.suffixes[`${
|
|
1935
|
+
const suffix = spec.suffixes[`${installerPlatform()}-${process.arch}`];
|
|
1713
1936
|
if (!suffix)
|
|
1714
1937
|
return undefined;
|
|
1715
1938
|
const platformPkg = `${spec.base ?? tool.packageName}-${suffix}`;
|
|
@@ -1725,7 +1948,7 @@ export function resolvePlatformPackageBinary(tool) {
|
|
|
1725
1948
|
catch {
|
|
1726
1949
|
pkgDir = path.dirname(_installerRequire.resolve(`${platformPkg}/package.json`));
|
|
1727
1950
|
}
|
|
1728
|
-
const isWin =
|
|
1951
|
+
const isWin = installerPlatform() === "win32";
|
|
1729
1952
|
for (const bin of spec.binaries) {
|
|
1730
1953
|
for (const name of isWin ? [`${bin}.exe`, bin] : [bin]) {
|
|
1731
1954
|
const candidate = path.join(pkgDir, name);
|
|
@@ -1771,7 +1994,7 @@ export async function getToolPath(toolId) {
|
|
|
1771
1994
|
// This avoids the ~2-5s overhead of spawning npm global probes and PATH
|
|
1772
1995
|
// searches for tools we already manage locally.
|
|
1773
1996
|
const localBase = path.join(TOOLS_DIR, "node_modules", ".bin", tool.binaryName || tool.id);
|
|
1774
|
-
if (
|
|
1997
|
+
if (installerPlatform() === "win32") {
|
|
1775
1998
|
// Prefer .cmd over extensionless — Node.js can't execute POSIX shell scripts on Windows
|
|
1776
1999
|
const cmdPath = `${localBase}.cmd`;
|
|
1777
2000
|
try {
|
|
@@ -1798,15 +2021,17 @@ export async function getToolPath(toolId) {
|
|
|
1798
2021
|
// fall through to extensionless
|
|
1799
2022
|
}
|
|
1800
2023
|
}
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
2024
|
+
if (installerPlatform() !== "win32") {
|
|
2025
|
+
try {
|
|
2026
|
+
await fs.access(localBase);
|
|
2027
|
+
if (await verifyToolBinary(localBase, recordVersion)) {
|
|
2028
|
+
return localBase;
|
|
2029
|
+
}
|
|
2030
|
+
logSessionStart(`auto-install verify: ${localBase} exists but is broken, will reinstall`);
|
|
2031
|
+
}
|
|
2032
|
+
catch {
|
|
2033
|
+
// fall through to global checks
|
|
1805
2034
|
}
|
|
1806
|
-
logSessionStart(`auto-install verify: ${localBase} exists but is broken, will reinstall`);
|
|
1807
|
-
}
|
|
1808
|
-
catch {
|
|
1809
|
-
// fall through to global checks
|
|
1810
2035
|
}
|
|
1811
2036
|
// npm/pnpm/bun: prefer the native per-platform binary directly. The main
|
|
1812
2037
|
// package's launcher often can't find it under a symlink store / after a
|
|
@@ -1909,7 +2134,6 @@ async function findNpmGlobalToolPath(binaryName) {
|
|
|
1909
2134
|
? [
|
|
1910
2135
|
path.join(dir, `${binaryName}.cmd`),
|
|
1911
2136
|
path.join(dir, `${binaryName}.exe`),
|
|
1912
|
-
path.join(dir, binaryName),
|
|
1913
2137
|
]
|
|
1914
2138
|
: [path.join(dir, binaryName)];
|
|
1915
2139
|
for (const candidate of candidates) {
|
|
@@ -2160,7 +2384,7 @@ async function installGitHubTool(tool) {
|
|
|
2160
2384
|
logSessionStart(`github-install ${tool.id}: release fetch failed: ${err.message}`);
|
|
2161
2385
|
return undefined;
|
|
2162
2386
|
}
|
|
2163
|
-
const asset = releaseJson.assets
|
|
2387
|
+
const asset = pickReleaseAsset(releaseJson.assets, assetSubstring) ??
|
|
2164
2388
|
deriveHashiCorpReleaseAsset(tool, releaseJson.tag_name, assetSubstring);
|
|
2165
2389
|
if (!asset) {
|
|
2166
2390
|
logSessionStart(`github-install ${tool.id}: no asset matched "${assetSubstring}"`);
|
|
@@ -2537,7 +2761,7 @@ async function installNpmTool(packageName, binaryName) {
|
|
|
2537
2761
|
await fs.writeFile(packageJsonPath, JSON.stringify({ name: "pi-lens-tools", version: "1.0.0" }, null, 2));
|
|
2538
2762
|
}
|
|
2539
2763
|
// Resolve the package manager for the tools dir and build install args.
|
|
2540
|
-
const isWindows =
|
|
2764
|
+
const isWindows = installerPlatform() === "win32";
|
|
2541
2765
|
const pm = await resolveNodePackageManager(TOOLS_DIR);
|
|
2542
2766
|
const testNpmScript = process.env.PI_LENS_TEST_MODE === "1"
|
|
2543
2767
|
? process.env.PI_LENS_TEST_NPM_SCRIPT
|
|
@@ -2583,9 +2807,12 @@ async function installNpmTool(packageName, binaryName) {
|
|
|
2583
2807
|
if (!outcome.ok) {
|
|
2584
2808
|
throw new Error(`Failed to install ${packageName}: ${outcome.stderr}`);
|
|
2585
2809
|
}
|
|
2586
|
-
|
|
2810
|
+
// npm creates a command shim on Windows; retain that actual executable path
|
|
2811
|
+
// rather than probing/storing the extensionless POSIX sibling.
|
|
2812
|
+
const binBase = path.join(TOOLS_DIR, "node_modules", ".bin", binaryName);
|
|
2813
|
+
const binPath = installerPlatform() === "win32" ? `${binBase}.cmd` : binBase;
|
|
2587
2814
|
// Make executable on Unix
|
|
2588
|
-
if (
|
|
2815
|
+
if (installerPlatform() !== "win32") {
|
|
2589
2816
|
try {
|
|
2590
2817
|
await fs.chmod(binPath, 0o750);
|
|
2591
2818
|
}
|
|
@@ -2615,10 +2842,10 @@ async function installNpmTool(packageName, binaryName) {
|
|
|
2615
2842
|
try {
|
|
2616
2843
|
const packagePath = path.join(TOOLS_DIR, "node_modules", packageName);
|
|
2617
2844
|
await fs.rm(packagePath, { recursive: true, force: true });
|
|
2618
|
-
await fs.rm(
|
|
2845
|
+
await fs.rm(binBase, { force: true });
|
|
2619
2846
|
if (isWindows) {
|
|
2620
|
-
await fs.rm(`${
|
|
2621
|
-
await fs.rm(`${
|
|
2847
|
+
await fs.rm(`${binBase}.cmd`, { force: true });
|
|
2848
|
+
await fs.rm(`${binBase}.ps1`, { force: true });
|
|
2622
2849
|
}
|
|
2623
2850
|
}
|
|
2624
2851
|
catch {
|
|
@@ -2784,6 +3011,33 @@ async function installGemTool(packageName) {
|
|
|
2784
3011
|
return undefined;
|
|
2785
3012
|
}
|
|
2786
3013
|
}
|
|
3014
|
+
async function finishInstallAttempt(toolId, ok, startedAt) {
|
|
3015
|
+
logSessionStart(`auto-install ${toolId}: ${ok ? "success" : "failed"} (${Date.now() - startedAt}ms)`);
|
|
3016
|
+
if (ok) {
|
|
3017
|
+
// A prior availability probe may have cached ENOENT for this exact child
|
|
3018
|
+
// PATH. Make a successful mutation visible immediately rather than waiting
|
|
3019
|
+
// for the bounded negative-cache TTL or the next session reset (#1199).
|
|
3020
|
+
resetSafeSpawnWindowsCommandCache();
|
|
3021
|
+
// #1276: the madge managed-path memo is keyed only by projectRoot, but
|
|
3022
|
+
// reads PATH/discovery/install state that a completed install can just
|
|
3023
|
+
// have changed — drop it here too, right alongside the safe-spawn reset.
|
|
3024
|
+
// AWAITED (not fire-and-forget): a caller that starts the next madge
|
|
3025
|
+
// resolution the instant `installTool`/`ensureTool` resolves must observe
|
|
3026
|
+
// the reset memo, not a stale pre-install one, and a rejected dynamic
|
|
3027
|
+
// import must not become a silent unhandled rejection. Dynamic import
|
|
3028
|
+
// avoids a static dependency-checker.js <-> installer/index.js cycle
|
|
3029
|
+
// (dependency-checker.js already imports this module dynamically for the
|
|
3030
|
+
// same reason).
|
|
3031
|
+
try {
|
|
3032
|
+
const { resetMadgeManagedPathMemo } = await import("../dependency-checker.js");
|
|
3033
|
+
resetMadgeManagedPathMemo();
|
|
3034
|
+
}
|
|
3035
|
+
catch (err) {
|
|
3036
|
+
logSessionStart(`auto-install ${toolId}: madge memo reset failed: ${err.message}`);
|
|
3037
|
+
}
|
|
3038
|
+
}
|
|
3039
|
+
return ok;
|
|
3040
|
+
}
|
|
2787
3041
|
/**
|
|
2788
3042
|
* Install a tool by ID
|
|
2789
3043
|
*/
|
|
@@ -2806,49 +3060,37 @@ export async function installTool(toolId) {
|
|
|
2806
3060
|
if (!tool.packageName || !tool.binaryName)
|
|
2807
3061
|
return false;
|
|
2808
3062
|
const npmPath = await installNpmTool(tool.packageName, tool.binaryName);
|
|
2809
|
-
|
|
2810
|
-
logSessionStart(`auto-install ${tool.id}: ${ok ? "success" : "failed"} (${Date.now() - startedAt}ms)`);
|
|
2811
|
-
return ok;
|
|
3063
|
+
return finishInstallAttempt(tool.id, npmPath !== undefined, startedAt);
|
|
2812
3064
|
}
|
|
2813
3065
|
case "pip": {
|
|
2814
3066
|
if (!tool.packageName)
|
|
2815
3067
|
return false;
|
|
2816
3068
|
const pipPath = await installPipTool(tool.packageName);
|
|
2817
|
-
|
|
2818
|
-
logSessionStart(`auto-install ${tool.id}: ${ok ? "success" : "failed"} (${Date.now() - startedAt}ms)`);
|
|
2819
|
-
return ok;
|
|
3069
|
+
return finishInstallAttempt(tool.id, pipPath !== undefined, startedAt);
|
|
2820
3070
|
}
|
|
2821
3071
|
case "gem": {
|
|
2822
3072
|
if (!tool.packageName)
|
|
2823
3073
|
return false;
|
|
2824
3074
|
const gemPath = await installGemTool(tool.packageName);
|
|
2825
|
-
|
|
2826
|
-
logSessionStart(`auto-install ${tool.id}: ${ok ? "success" : "failed"} (${Date.now() - startedAt}ms)`);
|
|
2827
|
-
return ok;
|
|
3075
|
+
return finishInstallAttempt(tool.id, gemPath !== undefined, startedAt);
|
|
2828
3076
|
}
|
|
2829
3077
|
case "github": {
|
|
2830
3078
|
if (!tool.github)
|
|
2831
3079
|
return false;
|
|
2832
3080
|
const ghPath = await installGitHubTool(tool);
|
|
2833
|
-
|
|
2834
|
-
logSessionStart(`auto-install ${tool.id}: ${ok ? "success" : "failed"} (${Date.now() - startedAt}ms)`);
|
|
2835
|
-
return ok;
|
|
3081
|
+
return finishInstallAttempt(tool.id, ghPath !== undefined, startedAt);
|
|
2836
3082
|
}
|
|
2837
3083
|
case "maven": {
|
|
2838
3084
|
if (!tool.maven)
|
|
2839
3085
|
return false;
|
|
2840
3086
|
const mavenPath = await installMavenTool(tool);
|
|
2841
|
-
|
|
2842
|
-
logSessionStart(`auto-install ${tool.id}: ${ok ? "success" : "failed"} (${Date.now() - startedAt}ms)`);
|
|
2843
|
-
return ok;
|
|
3087
|
+
return finishInstallAttempt(tool.id, mavenPath !== undefined, startedAt);
|
|
2844
3088
|
}
|
|
2845
3089
|
case "archive": {
|
|
2846
3090
|
if (!tool.archive)
|
|
2847
3091
|
return false;
|
|
2848
3092
|
const archivePath = await installArchiveTool(tool);
|
|
2849
|
-
|
|
2850
|
-
logSessionStart(`auto-install ${tool.id}: ${ok ? "success" : "failed"} (${Date.now() - startedAt}ms)`);
|
|
2851
|
-
return ok;
|
|
3093
|
+
return finishInstallAttempt(tool.id, archivePath !== undefined, startedAt);
|
|
2852
3094
|
}
|
|
2853
3095
|
default:
|
|
2854
3096
|
logSessionStart(`auto-install ${tool.id}: unsupported strategy`);
|
|
@@ -2861,9 +3103,24 @@ export async function installTool(toolId) {
|
|
|
2861
3103
|
}
|
|
2862
3104
|
}
|
|
2863
3105
|
/**
|
|
2864
|
-
* Ensure a tool is installed (check first, install if missing)
|
|
3106
|
+
* Ensure a tool is installed (check first, install if missing).
|
|
3107
|
+
*
|
|
3108
|
+
* #1334 S5: when the pi host has actively denied project trust, the INSTALL
|
|
3109
|
+
* half is switched off here — the request degrades to the existing
|
|
3110
|
+
* `allowInstall:false` discovery-only path rather than failing outright, so an
|
|
3111
|
+
* already-present binary keeps working while nothing is downloaded or executed
|
|
3112
|
+
* on behalf of an untrusted project. A host with no trust surface at all
|
|
3113
|
+
* (`"unknown"`) is unaffected.
|
|
2865
3114
|
*/
|
|
2866
3115
|
export async function ensureTool(toolId, opts) {
|
|
3116
|
+
if (opts?.allowInstall !== false &&
|
|
3117
|
+
!assertInstallAllowed(`managed tool ensure: ${toolId}`)) {
|
|
3118
|
+
logSessionStart(`auto-install ensure ${toolId}: install gated — ${projectTrustDenialReason()}; discovery only`);
|
|
3119
|
+
return ensureToolResolved(toolId, { ...opts, allowInstall: false });
|
|
3120
|
+
}
|
|
3121
|
+
return ensureToolResolved(toolId, opts);
|
|
3122
|
+
}
|
|
3123
|
+
async function ensureToolResolved(toolId, opts) {
|
|
2867
3124
|
installFailureReasons.delete(toolId);
|
|
2868
3125
|
const cacheResolvedPath = (result) => {
|
|
2869
3126
|
if (result) {
|
|
@@ -2885,8 +3142,7 @@ export async function ensureTool(toolId, opts) {
|
|
|
2885
3142
|
try {
|
|
2886
3143
|
const probeCache = await readProbeCache();
|
|
2887
3144
|
delete probeCache[toolId];
|
|
2888
|
-
|
|
2889
|
-
scheduleProbeFlush();
|
|
3145
|
+
markProbeCacheChange(toolId, null);
|
|
2890
3146
|
}
|
|
2891
3147
|
catch {
|
|
2892
3148
|
// best-effort
|
|
@@ -2925,8 +3181,23 @@ export async function ensureTool(toolId, opts) {
|
|
|
2925
3181
|
}
|
|
2926
3182
|
// Fast path 1: in-memory session cache — no I/O.
|
|
2927
3183
|
const cached = resolvedPathCache.get(toolId);
|
|
2928
|
-
if (cached)
|
|
2929
|
-
|
|
3184
|
+
if (cached) {
|
|
3185
|
+
if (!isFullyQualified(cached))
|
|
3186
|
+
return cached;
|
|
3187
|
+
try {
|
|
3188
|
+
await fs.access(cached);
|
|
3189
|
+
return cached;
|
|
3190
|
+
}
|
|
3191
|
+
catch {
|
|
3192
|
+
// The executor would report ENOENT for this cached positive. Evict it
|
|
3193
|
+
// before discovery so the failure heals on this call, not after restart.
|
|
3194
|
+
}
|
|
3195
|
+
resolvedPathCache.delete(toolId);
|
|
3196
|
+
const probeCache = await readProbeCache();
|
|
3197
|
+
delete probeCache[toolId];
|
|
3198
|
+
markProbeCacheChange(toolId, null);
|
|
3199
|
+
logSessionStart(`auto-install ensure ${toolId}: cached path disappeared; re-probing`);
|
|
3200
|
+
}
|
|
2930
3201
|
// Fast path 2: persistent probe cache — fs.access + stat, no process spawn.
|
|
2931
3202
|
const diskCached = await checkProbeCache(toolId);
|
|
2932
3203
|
if (diskCached) {
|
|
@@ -3100,9 +3371,11 @@ export const GITHUB_TOOLS = [
|
|
|
3100
3371
|
"zizmor",
|
|
3101
3372
|
"typos-lsp",
|
|
3102
3373
|
"tflint",
|
|
3374
|
+
"terragrunt",
|
|
3103
3375
|
"terraform-ls",
|
|
3104
3376
|
"zls",
|
|
3105
3377
|
"hadolint",
|
|
3378
|
+
"helm",
|
|
3106
3379
|
"gitleaks",
|
|
3107
3380
|
"taplo",
|
|
3108
3381
|
"vale",
|
|
@@ -3135,6 +3408,28 @@ export function resolveGitHubArchiveBinaryCandidates(toolId, platform, assetName
|
|
|
3135
3408
|
const binaryName = tool.github?.binaryInArchive ?? tool.binaryName ?? tool.id;
|
|
3136
3409
|
return getArchiveBinaryCandidates(binaryName, platform, assetName);
|
|
3137
3410
|
}
|
|
3411
|
+
// Signature/checksum siblings a release publishes next to the real asset. A
|
|
3412
|
+
// bare-binary tool's substring IS the whole asset name, so `includes` alone
|
|
3413
|
+
// matches `<asset>.asc` too and would install a signature file as the binary.
|
|
3414
|
+
const ASSET_SIDECAR_SUFFIXES = [
|
|
3415
|
+
".asc",
|
|
3416
|
+
".sig",
|
|
3417
|
+
".minisig",
|
|
3418
|
+
".pem",
|
|
3419
|
+
".cert",
|
|
3420
|
+
".sbom",
|
|
3421
|
+
".sha256",
|
|
3422
|
+
".sha256sum",
|
|
3423
|
+
".md5",
|
|
3424
|
+
];
|
|
3425
|
+
function isAssetSidecar(name) {
|
|
3426
|
+
const lower = name.toLowerCase();
|
|
3427
|
+
return ASSET_SIDECAR_SUFFIXES.some((suffix) => lower.endsWith(suffix));
|
|
3428
|
+
}
|
|
3429
|
+
export function pickReleaseAsset(assets, assetSubstring) {
|
|
3430
|
+
return (assets.find((a) => a.name === assetSubstring) ??
|
|
3431
|
+
assets.find((a) => a.name.includes(assetSubstring) && !isAssetSidecar(a.name)));
|
|
3432
|
+
}
|
|
3138
3433
|
function deriveHashiCorpReleaseAsset(tool, tagName, assetSubstring) {
|
|
3139
3434
|
const product = tool.github?.hashiCorpReleaseProduct;
|
|
3140
3435
|
if (!product || !tagName)
|