@herbertgao/pi-extensions 2026.8.5 → 2026.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +19 -7
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +30 -9
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +272 -22
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +74 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +94 -77
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +48 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +14 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +55 -17
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +41 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +75 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +7 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +64 -1
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +16 -0
- package/node_modules/@herbertgao/pi-subagents/README.md +30 -22
- package/node_modules/@herbertgao/pi-subagents/examples/agent-tool-description.md +5 -5
- package/node_modules/@herbertgao/pi-subagents/package.json +7 -7
- package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +7 -1
- package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +48 -19
- package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +22 -15
- package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +26 -2
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +219 -40
- package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +101 -3
- package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +18 -4
- package/node_modules/@herbertgao/pi-subagents/src/settings.ts +99 -0
- package/node_modules/@herbertgao/pi-subagents/src/types.ts +22 -3
- package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +51 -3
- package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +20 -2
- package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +34 -10
- package/node_modules/@herbertgao/pi-subagents/src/usage.ts +129 -1
- package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +29 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +3 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +46 -10
- package/node_modules/@juicesharp/rpiv-ask-user-question/config.ts +25 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/configuration.md +13 -4
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +8 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +3 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/locales/zh.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +3 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +10 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +3 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +30 -7
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/state.ts +14 -6
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +30 -4
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +26 -5
- package/node_modules/@narumitw/pi-btw/README.md +119 -103
- package/node_modules/@narumitw/pi-btw/dist/index.ts +2898 -0
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +7 -0
- package/node_modules/@narumitw/pi-btw/package.json +13 -9
- package/node_modules/@narumitw/pi-btw/src/btw.ts +40 -42
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +55 -10
- package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +390 -0
- package/node_modules/@narumitw/pi-btw/src/menu.ts +78 -15
- package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
- package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +136 -45
- package/node_modules/pi-lens/CHANGELOG.md +1004 -0
- package/node_modules/pi-lens/README.md +11 -3
- package/node_modules/pi-lens/config/ruff/core.toml +42 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +130 -27
- package/node_modules/pi-lens/dist/clients/advisory-provenance.js +427 -0
- package/node_modules/pi-lens/dist/clients/agent-nudge.js +79 -16
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +9 -3
- package/node_modules/pi-lens/dist/clients/atomic-write.js +9 -3
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +285 -8
- package/node_modules/pi-lens/dist/clients/biome-client.js +176 -37
- package/node_modules/pi-lens/dist/clients/blocker-freshness.js +428 -0
- package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +119 -0
- package/node_modules/pi-lens/dist/clients/bootstrap.js +12 -1
- package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +12 -3
- package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +192 -0
- package/node_modules/pi-lens/dist/clients/bus-events-logger.js +62 -6
- package/node_modules/pi-lens/dist/clients/bus-publish.js +11 -3
- package/node_modules/pi-lens/dist/clients/cascade-budget.js +196 -0
- package/node_modules/pi-lens/dist/clients/cascade-format.js +76 -2
- package/node_modules/pi-lens/dist/clients/cascade-types.js +9 -1
- package/node_modules/pi-lens/dist/clients/child-unref.js +54 -6
- package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +76 -24
- package/node_modules/pi-lens/dist/clients/console-guard-install.js +16 -4
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +249 -52
- package/node_modules/pi-lens/dist/clients/deadline-utils.js +8 -1
- package/node_modules/pi-lens/dist/clients/debug-handles.js +5 -0
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +62 -13
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +43 -9
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +153 -37
- package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +298 -0
- package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +165 -11
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +325 -72
- package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +7 -4
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +5 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +234 -18
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +187 -19
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +6 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +11 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +398 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +11 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +12 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +18 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +895 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +4 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +16 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +81 -41
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +16 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +23 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +331 -55
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +38 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +97 -19
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +14 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +20 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +22 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +10 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +18 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +73 -12
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +488 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +84 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +260 -27
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +943 -93
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +86 -11
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +72 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +106 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +9 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +18 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +4 -2
- package/node_modules/pi-lens/dist/clients/disposition-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +28 -5
- package/node_modules/pi-lens/dist/clients/extension-log.js +317 -3
- package/node_modules/pi-lens/dist/clients/file-kinds.js +8 -2
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +294 -0
- package/node_modules/pi-lens/dist/clients/finding-identity.js +88 -0
- package/node_modules/pi-lens/dist/clients/fix-worklog.js +5 -1
- package/node_modules/pi-lens/dist/clients/format-events-publish.js +39 -8
- package/node_modules/pi-lens/dist/clients/formatters-lazy.js +3 -2
- package/node_modules/pi-lens/dist/clients/formatters.js +500 -115
- package/node_modules/pi-lens/dist/clients/generation-guard.js +216 -0
- package/node_modules/pi-lens/dist/clients/git-guard.js +49 -21
- package/node_modules/pi-lens/dist/clients/gitleaks-client.js +181 -8
- package/node_modules/pi-lens/dist/clients/go-client.js +21 -39
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +219 -10
- package/node_modules/pi-lens/dist/clients/grammar-source.js +333 -10
- package/node_modules/pi-lens/dist/clients/host-ports.js +9 -1
- package/node_modules/pi-lens/dist/clients/installer/index.js +1119 -101
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +58 -0
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +715 -0
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +643 -97
- package/node_modules/pi-lens/dist/clients/instance-registry.js +87 -17
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +86 -15
- package/node_modules/pi-lens/dist/clients/knip-client.js +361 -24
- package/node_modules/pi-lens/dist/clients/language-policy.js +8 -2
- package/node_modules/pi-lens/dist/clients/language-profile.js +2 -0
- package/node_modules/pi-lens/dist/clients/latency-logger.js +487 -9
- package/node_modules/pi-lens/dist/clients/lazy-import.js +21 -0
- package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -0
- package/node_modules/pi-lens/dist/clients/lens-events.js +56 -25
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +16 -0
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +45 -2
- package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +160 -20
- package/node_modules/pi-lens/dist/clients/lsp/client.js +1666 -148
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +111 -0
- package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +389 -0
- package/node_modules/pi-lens/dist/clients/lsp/index.js +2496 -171
- package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +215 -0
- package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +146 -14
- package/node_modules/pi-lens/dist/clients/lsp/language.js +48 -4
- package/node_modules/pi-lens/dist/clients/lsp/server.js +517 -69
- package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +22 -0
- package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +43 -0
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +172 -0
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +21 -5
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +61 -3
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +422 -28
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +30 -0
- package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +175 -0
- package/node_modules/pi-lens/dist/clients/lsp-lazy.js +3 -2
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +4 -0
- package/node_modules/pi-lens/dist/clients/mcp/session.js +39 -18
- package/node_modules/pi-lens/dist/clients/model-provider.js +53 -0
- package/node_modules/pi-lens/dist/clients/module-report.js +8 -0
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +15 -1
- package/node_modules/pi-lens/dist/clients/package-manager.js +170 -14
- package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +27 -0
- package/node_modules/pi-lens/dist/clients/path-utils.js +160 -0
- package/node_modules/pi-lens/dist/clients/pipeline.js +178 -30
- package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +37 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +17 -3
- package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +89 -22
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +85 -15
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +15 -3
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +23 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +50 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +5 -0
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +299 -0
- package/node_modules/pi-lens/dist/clients/quiet-window-config.js +47 -0
- package/node_modules/pi-lens/dist/clients/quiet-window.js +11 -25
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +1 -1
- package/node_modules/pi-lens/dist/clients/read-guard.js +184 -10
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +49 -17
- package/node_modules/pi-lens/dist/clients/reverse-deps.js +50 -11
- package/node_modules/pi-lens/dist/clients/review-graph/query.js +76 -4
- package/node_modules/pi-lens/dist/clients/run-duration.js +55 -0
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +248 -13
- package/node_modules/pi-lens/dist/clients/runtime-context.js +112 -12
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +472 -61
- package/node_modules/pi-lens/dist/clients/runtime-session.js +359 -13
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +217 -22
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +269 -22
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +948 -87
- package/node_modules/pi-lens/dist/clients/rust-client.js +21 -37
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +220 -7
- package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +161 -0
- package/node_modules/pi-lens/dist/clients/search-read-registration.js +34 -6
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +224 -8
- package/node_modules/pi-lens/dist/clients/sg-runner.js +249 -37
- package/node_modules/pi-lens/dist/clients/single-flight.js +158 -0
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +18 -11
- package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
- package/node_modules/pi-lens/dist/clients/startup-timing.js +26 -2
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +574 -32
- package/node_modules/pi-lens/dist/clients/tool-event.js +56 -5
- package/node_modules/pi-lens/dist/clients/tool-policy.js +127 -20
- package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +75 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +769 -64
- package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +11 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +90 -0
- package/node_modules/pi-lens/dist/clients/trivy-client.js +19 -1
- package/node_modules/pi-lens/dist/clients/warm-attach.js +17 -0
- package/node_modules/pi-lens/dist/clients/widget-state.js +336 -36
- package/node_modules/pi-lens/dist/clients/word-index.js +305 -33
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +214 -14
- package/node_modules/pi-lens/dist/index.js +64677 -49484
- package/node_modules/pi-lens/dist/mcp/server.js +8 -2
- package/node_modules/pi-lens/dist/tools/activate-tools.js +17 -5
- package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +9 -4
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +5 -2
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +469 -56
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +190 -46
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -7
- package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -1
- package/node_modules/pi-lens/docs/agent-guide.md +44 -14
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +518 -501
- package/node_modules/pi-lens/docs/dispositions.md +21 -0
- package/node_modules/pi-lens/docs/features.md +33 -6
- package/node_modules/pi-lens/docs/globalconfig.md +14 -0
- package/node_modules/pi-lens/docs/language-coverage.md +71 -5
- package/node_modules/pi-lens/docs/mcp.md +2 -2
- package/node_modules/pi-lens/docs/servercapabilities.md +4 -1
- package/node_modules/pi-lens/docs/settings.md +25 -1
- package/node_modules/pi-lens/docs/usage.md +24 -5
- package/node_modules/pi-lens/docs/word-index.md +35 -0
- package/node_modules/pi-lens/package.json +10 -6
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-js-snapshot.yml +23 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-snapshot.yml +23 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/ts-void-zero-snapshot.yml +23 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-object-param-test.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +22 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-compile-call-test.yml +29 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-js-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-dupe-class-members-test.yml +33 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-js-test.yml +7 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-test.yml +7 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-known-value-widening-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-non-null-assertion-test.yml +216 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-return-value-in-generator-test.yml +12 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-runtime-typeof-test.yml +33 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-server-bind-wildcard-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-shape-in-symbol-names-test.yml +13 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-js-test.yml +5 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-test.yml +5 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-laundering-test.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-parameters-test.yml +12 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-returns-test.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-any-test.yml +10 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-unknown-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/redundant-unsafe-function-test.yml +35 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/require-safety-comment-for-as-unknown-as-test.yml +177 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ts-void-zero-test.yml +1 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member-js.yml +13 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member.yml +13 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +2 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-object-param.yml +60 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +66 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-compile-call.yml +63 -5
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread-js.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread.yml +29 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-dupe-class-members.yml +52 -8
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html-js.yml +27 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html.yml +27 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-known-value-widening.yml +71 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-non-null-assertion.yml +316 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +29 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +30 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +41 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +42 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-return-value-in-generator.yml +22 -5
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-runtime-typeof.yml +121 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-server-bind-wildcard.yml +23 -7
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-shape-in-symbol-names.yml +68 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code-js.yml +41 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code.yml +41 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +32 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-parameters.yml +67 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-returns.yml +106 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-any.yml +50 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-unknown.yml +56 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +2 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/redundant-unsafe-function.yml +32 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/require-safety-comment-for-as-unknown-as.yml +184 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/ts-void-zero.yml +5 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/self-scan-baseline.json +5 -0
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +132 -1
- package/node_modules/pi-lens/scripts/download-grammars.js +25 -0
- package/node_modules/pi-lens/scripts/grammars.lock.json +9 -0
- package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +12 -11
- package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +22 -22
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +87 -116
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +170 -0
- package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
- package/node_modules/pi-lens/vendor/grammars/tree-sitter-cue.wasm +0 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +49 -0
- package/node_modules/pi-mcp-adapter/README.md +51 -6
- package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
- package/node_modules/pi-mcp-adapter/cli.js +188 -5
- package/node_modules/pi-mcp-adapter/commands.ts +128 -22
- package/node_modules/pi-mcp-adapter/config.ts +28 -10
- package/node_modules/pi-mcp-adapter/direct-tools.ts +23 -19
- package/node_modules/pi-mcp-adapter/index.ts +219 -32
- package/node_modules/pi-mcp-adapter/init.ts +36 -4
- package/node_modules/pi-mcp-adapter/lifecycle.ts +315 -24
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +39 -6
- package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +387 -0
- package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +27 -3
- package/node_modules/pi-mcp-adapter/mcp-code.ts +4 -0
- package/node_modules/pi-mcp-adapter/mcp-panel.ts +42 -13
- package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +2 -1
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +46 -25
- package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +168 -0
- package/node_modules/pi-mcp-adapter/package.json +4 -1
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +5 -4
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +330 -0
- package/node_modules/pi-mcp-adapter/search-ranking.ts +92 -22
- package/node_modules/pi-mcp-adapter/server-manager.ts +145 -2
- package/node_modules/pi-mcp-adapter/session-recovery.ts +7 -0
- package/node_modules/pi-mcp-adapter/tool-approval.ts +15 -2
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +38 -18
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +44 -4
- package/node_modules/pi-mcp-adapter/types.ts +95 -10
- package/node_modules/pi-mcp-adapter/ui-server.ts +18 -16
- package/node_modules/pi-mcp-adapter/utils.ts +70 -0
- package/node_modules/pi-web-access/CHANGELOG.md +37 -0
- package/node_modules/pi-web-access/README.md +38 -14
- package/node_modules/pi-web-access/anysearch.ts +4 -2
- package/node_modules/pi-web-access/auth-fetch.ts +148 -0
- package/node_modules/pi-web-access/brave.ts +16 -4
- package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
- package/node_modules/pi-web-access/curator-page.ts +8 -3
- package/node_modules/pi-web-access/curator-server.ts +5 -1
- package/node_modules/pi-web-access/exa.ts +18 -7
- package/node_modules/pi-web-access/extract.ts +134 -37
- package/node_modules/pi-web-access/fetch-params.ts +17 -3
- package/node_modules/pi-web-access/firecrawl.ts +172 -12
- package/node_modules/pi-web-access/gemini-search.ts +33 -7
- package/node_modules/pi-web-access/index.ts +160 -104
- package/node_modules/pi-web-access/package.json +2 -2
- package/node_modules/pi-web-access/page-query.ts +4 -2
- package/node_modules/pi-web-access/parallel-mcp.ts +249 -0
- package/node_modules/pi-web-access/parallel.ts +2 -2
- package/node_modules/pi-web-access/query-rewrite.ts +51 -0
- package/node_modules/pi-web-access/serper.ts +211 -0
- package/node_modules/pi-web-access/summary-model-scope.ts +12 -4
- package/node_modules/pi-web-access/summary-review.ts +51 -23
- package/node_modules/pi-web-access/tavily.ts +16 -4
- package/node_modules/pi-web-access/utils.ts +87 -0
- package/node_modules/pi-web-access/valyu.ts +199 -0
- package/node_modules/pi-web-access/youtube-extract.ts +2 -2
- package/package.json +13 -13
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capture-generation-before-await, check-after — as one primitive (#1754).
|
|
3
|
+
*
|
|
4
|
+
* The shape: a write that follows an `await` must prove the world it captured
|
|
5
|
+
* still exists. A session reset, a cache refresh, or a newer request for the
|
|
6
|
+
* same key can land while the write's producer is in flight, and the late
|
|
7
|
+
* write then publishes an answer the caller no longer wants — or, in the
|
|
8
|
+
* eviction direction, deletes an entry a SUCCESSOR already installed.
|
|
9
|
+
*
|
|
10
|
+
* This module was extracted after the pattern was hand-rolled four times and
|
|
11
|
+
* its absence was a review finding twice:
|
|
12
|
+
*
|
|
13
|
+
* - `clients/dispatch/runners/utils/runner-helpers.ts` held the guarded form
|
|
14
|
+
* at one site and the unguarded form at another IN THE SAME FILE; #1674's
|
|
15
|
+
* F5 round copied the guard by hand to three more write sites.
|
|
16
|
+
* - `clients/lsp/client.ts`'s per-(path, identifier) pull sequences (#1682),
|
|
17
|
+
* whose first guard was vacuous until a review round bound it.
|
|
18
|
+
* - `clients/lsp/workspace-diagnostics-cache.ts`'s per-cwd epochs (#1669),
|
|
19
|
+
* where the epoch guarded `persist()` but not `lookup()`.
|
|
20
|
+
* - `clients/review-graph/builder.ts`'s workspace-cache epoch, the shape the
|
|
21
|
+
* one above was modelled on.
|
|
22
|
+
*
|
|
23
|
+
* Two things every one of those needed and at least one got wrong:
|
|
24
|
+
*
|
|
25
|
+
* 1. The check must run at WRITE time, not only at capture time.
|
|
26
|
+
* 2. The stale branch must be OBSERVABLE. A silently dropped write looks
|
|
27
|
+
* exactly like a write that never happened, which is why the vacuous
|
|
28
|
+
* guards survived review. `guardedWrite` emits one bounded degradation
|
|
29
|
+
* record per (source, subject) with the count of repeats, so a dogfood
|
|
30
|
+
* session can tell "the guard is working" from "the guard never fires".
|
|
31
|
+
*
|
|
32
|
+
* ## Scope and lifetime
|
|
33
|
+
*
|
|
34
|
+
* A `GenerationSource` is a plain counter owned by the RESETTING SEAM — the
|
|
35
|
+
* function that already clears the state the generation protects. This module
|
|
36
|
+
* holds no session state of its own: sources live in the modules that create
|
|
37
|
+
* them, and their reset policy is that module's declaration in
|
|
38
|
+
* `tests/support/session-state-registry.ts`. The only module-level state here
|
|
39
|
+
* is the name registry below, which exists so the #1754 sweep can ask "which
|
|
40
|
+
* stores declare a generation" instead of guessing from identifier names.
|
|
41
|
+
*
|
|
42
|
+
* ## Cost
|
|
43
|
+
*
|
|
44
|
+
* `isCurrent()` on an unkeyed source is a field read and a compare. On a
|
|
45
|
+
* keyed map it is a compare while nothing has been invalidated, and a
|
|
46
|
+
* normalize plus one `Map.get` once something has. The hand-rolled form it
|
|
47
|
+
* replaces normalized on EVERY call, and this repo's normalizer runs
|
|
48
|
+
* `realpathSync.native` on Windows, so an uninterrupted sweep pays that
|
|
49
|
+
* syscall once rather than once per file. See
|
|
50
|
+
* `GenerationMapOptions.normalizeKey` for the correctness reason the slow
|
|
51
|
+
* path exists at all.
|
|
52
|
+
*/
|
|
53
|
+
import { incrementDegradationCount } from "./degradation-ledger.js";
|
|
54
|
+
/**
|
|
55
|
+
* Names of every generation-carrying store created through this module.
|
|
56
|
+
*
|
|
57
|
+
* Registration is by CONSTRUCTION: you cannot make a `GenerationSource`
|
|
58
|
+
* without appearing here. `tests/clients/generation-guard-sweep.test.ts`
|
|
59
|
+
* reads this to enforce the ratchet — a file that hand-rolls the pattern
|
|
60
|
+
* instead of declaring it here must carry an explicit exemption reason.
|
|
61
|
+
*
|
|
62
|
+
* Process-lifetime and bounded: names are compile-time literals from module
|
|
63
|
+
* initialization, so this cannot grow with workload.
|
|
64
|
+
*/
|
|
65
|
+
const declaredSources = new Set();
|
|
66
|
+
const MAX_DECLARED_SOURCES = 128;
|
|
67
|
+
function declare(name) {
|
|
68
|
+
if (!name.trim()) {
|
|
69
|
+
throw new Error("generation source needs a non-empty name");
|
|
70
|
+
}
|
|
71
|
+
if (declaredSources.size < MAX_DECLARED_SOURCES)
|
|
72
|
+
declaredSources.add(name);
|
|
73
|
+
return name;
|
|
74
|
+
}
|
|
75
|
+
/** Every generation-carrying store name declared this process. */
|
|
76
|
+
export function listDeclaredGenerationSources() {
|
|
77
|
+
return [...declaredSources].sort((a, b) => a.localeCompare(b));
|
|
78
|
+
}
|
|
79
|
+
function recordStaleWrite(sourceName, subject, captured, observed) {
|
|
80
|
+
incrementDegradationCount({
|
|
81
|
+
kind: "generation-guard-stale-write",
|
|
82
|
+
subject: `${sourceName}:${subject}`,
|
|
83
|
+
reason: `write dropped: captured generation ${captured}, current ${observed}`,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function makeHandle(sourceName, generation, read) {
|
|
87
|
+
return {
|
|
88
|
+
generation,
|
|
89
|
+
isCurrent() {
|
|
90
|
+
return read() === generation;
|
|
91
|
+
},
|
|
92
|
+
guardedWrite(subject, write) {
|
|
93
|
+
const observed = read();
|
|
94
|
+
if (observed !== generation) {
|
|
95
|
+
recordStaleWrite(sourceName, subject, generation, observed);
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
return write();
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Create a counter for a store invalidated as a whole.
|
|
104
|
+
*
|
|
105
|
+
* `name` appears in the stale-write ledger subject and in the sweep's
|
|
106
|
+
* declaration list, so it must name the STORE, not the module.
|
|
107
|
+
*/
|
|
108
|
+
export function createGenerationSource(name) {
|
|
109
|
+
declare(name);
|
|
110
|
+
let generation = 0;
|
|
111
|
+
const read = () => generation;
|
|
112
|
+
return {
|
|
113
|
+
name,
|
|
114
|
+
current: read,
|
|
115
|
+
bump() {
|
|
116
|
+
generation += 1;
|
|
117
|
+
return generation;
|
|
118
|
+
},
|
|
119
|
+
capture() {
|
|
120
|
+
return makeHandle(name, generation, read);
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
const DEFAULT_MAX_KEYS = 512;
|
|
125
|
+
/**
|
|
126
|
+
* Create a per-key family of counters.
|
|
127
|
+
*
|
|
128
|
+
* Use this when invalidation is scoped: a cache refresh for ONE cwd must not
|
|
129
|
+
* drop an in-flight write for another, and a newer pull request for one
|
|
130
|
+
* (path, source) pair must not invalidate a different pair's.
|
|
131
|
+
*/
|
|
132
|
+
export function createGenerationMap(name, options = {}) {
|
|
133
|
+
declare(name);
|
|
134
|
+
const normalize = options.normalizeKey ?? ((key) => key);
|
|
135
|
+
const maxKeys = Math.max(1, options.maxKeys ?? DEFAULT_MAX_KEYS);
|
|
136
|
+
// Insertion-ordered: the oldest key is evicted first. Re-stamping a key
|
|
137
|
+
// refreshes its position so a hot cwd is not evicted by a burst of
|
|
138
|
+
// one-shot ones.
|
|
139
|
+
const stamps = new Map();
|
|
140
|
+
// ONE ticket counter for the whole map. See GenerationMap's doc comment:
|
|
141
|
+
// per-key counters starting at 0 make eviction and forget() fail OPEN.
|
|
142
|
+
// 0 is reserved for "this key holds no stamp" and is never issued.
|
|
143
|
+
let nextTicket = 0;
|
|
144
|
+
// Counts every mutation that can change what a key's stamp reads: a bump,
|
|
145
|
+
// a forget, an eviction. A handle records this at capture and re-derives
|
|
146
|
+
// its key only when it has moved. See normalizeKey's doc comment.
|
|
147
|
+
let invalidations = 0;
|
|
148
|
+
const read = (key) => stamps.get(key) ?? 0;
|
|
149
|
+
function issue(key) {
|
|
150
|
+
nextTicket += 1;
|
|
151
|
+
stamps.delete(key);
|
|
152
|
+
stamps.set(key, nextTicket);
|
|
153
|
+
while (stamps.size > maxKeys) {
|
|
154
|
+
const oldest = stamps.keys().next().value;
|
|
155
|
+
if (oldest === undefined)
|
|
156
|
+
break;
|
|
157
|
+
stamps.delete(oldest);
|
|
158
|
+
invalidations += 1;
|
|
159
|
+
}
|
|
160
|
+
return nextTicket;
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
name,
|
|
164
|
+
current(key) {
|
|
165
|
+
return read(normalize(key));
|
|
166
|
+
},
|
|
167
|
+
bump(key) {
|
|
168
|
+
invalidations += 1;
|
|
169
|
+
return issue(normalize(key));
|
|
170
|
+
},
|
|
171
|
+
capture(key) {
|
|
172
|
+
const normalized = normalize(key);
|
|
173
|
+
// A first-use capture ISSUES a stamp rather than reading 0. A handle
|
|
174
|
+
// holding 0 would be indistinguishable from one whose key was later
|
|
175
|
+
// forgotten or evicted, which is the fail-open hole this closes.
|
|
176
|
+
const stamp = read(normalized) || issue(normalized);
|
|
177
|
+
const capturedInvalidations = invalidations;
|
|
178
|
+
return makeHandle(`${name}[${normalized}]`, stamp, () => {
|
|
179
|
+
// Fast path: nothing has been invalidated since capture, so no key
|
|
180
|
+
// can have changed stamps and the normalizer need not run again.
|
|
181
|
+
if (invalidations === capturedInvalidations)
|
|
182
|
+
return stamp;
|
|
183
|
+
// Something moved. Re-derive the key from the ORIGINAL string: a
|
|
184
|
+
// normalizer that consults the filesystem can resolve the same
|
|
185
|
+
// input to a different key once the path exists, and a stamp read
|
|
186
|
+
// under the stale spelling would falsely read current.
|
|
187
|
+
return read(normalize(key));
|
|
188
|
+
});
|
|
189
|
+
},
|
|
190
|
+
forget(key) {
|
|
191
|
+
invalidations += 1;
|
|
192
|
+
stamps.delete(normalize(key));
|
|
193
|
+
},
|
|
194
|
+
size() {
|
|
195
|
+
return stamps.size;
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Capture the generation, run `fn`, and hand it the handle.
|
|
201
|
+
*
|
|
202
|
+
* Sugar for the common `const handle = source.capture(); ... await ...;
|
|
203
|
+
* handle.guardedWrite(...)` sequence. Use `capture()` directly when the
|
|
204
|
+
* handle must outlive the function that made it — an eviction guard in a
|
|
205
|
+
* `.finally()` is the usual case, and it is what both #1754 migrations do.
|
|
206
|
+
*
|
|
207
|
+
* This ships ahead of its first production caller. Both migrated sites need
|
|
208
|
+
* the handle to outlive its producer, so neither uses this form; it exists
|
|
209
|
+
* because the issue specifies it and because the sites still to migrate
|
|
210
|
+
* (`runtime-coordinator.ts`, `runtime-turn.ts`) write inside the async
|
|
211
|
+
* function that captured. Said plainly rather than left for a reader to
|
|
212
|
+
* discover by grep.
|
|
213
|
+
*/
|
|
214
|
+
export async function withGeneration(source, fn) {
|
|
215
|
+
return fn(source.capture());
|
|
216
|
+
}
|
|
@@ -1,24 +1,17 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
1
|
import * as nodeFs from "node:fs";
|
|
3
2
|
import * as path from "node:path";
|
|
4
3
|
import { isPathIgnoredByProject } from "./file-utils.js";
|
|
5
4
|
import { tokenizeShellCommand } from "./bash-file-access.js";
|
|
6
|
-
import { normalizeMapKey } from "./path-utils.js";
|
|
7
5
|
import { logLatency } from "./latency-logger.js";
|
|
8
|
-
|
|
6
|
+
import { advisoryFileHash, advisoryPathKey, MAX_ADVISORY_AFFECTED_FILES, snapshotAdvisoryProvenance, } from "./advisory-provenance.js";
|
|
9
7
|
function resolveGuardPath(filePath, cwd) {
|
|
10
8
|
return path.resolve(cwd, filePath);
|
|
11
9
|
}
|
|
12
10
|
function guardPathKey(filePath, cwd) {
|
|
13
|
-
return
|
|
11
|
+
return advisoryPathKey(filePath, cwd);
|
|
14
12
|
}
|
|
15
13
|
function fileFingerprint(filePath) {
|
|
16
|
-
|
|
17
|
-
return createHash("sha256").update(nodeFs.readFileSync(filePath)).digest("hex");
|
|
18
|
-
}
|
|
19
|
-
catch (err) {
|
|
20
|
-
return `unreadable:${err.code ?? "unknown"}`;
|
|
21
|
-
}
|
|
14
|
+
return advisoryFileHash(filePath);
|
|
22
15
|
}
|
|
23
16
|
function currentFileFingerprint(filePath) {
|
|
24
17
|
try {
|
|
@@ -31,19 +24,11 @@ function currentFileFingerprint(filePath) {
|
|
|
31
24
|
: `unreadable:${err.code ?? "unknown"}`;
|
|
32
25
|
}
|
|
33
26
|
}
|
|
34
|
-
function snapshotFileHashes(files, cwd) {
|
|
35
|
-
const hashes = {};
|
|
36
|
-
for (const file of files) {
|
|
37
|
-
const resolved = resolveGuardPath(file, cwd);
|
|
38
|
-
hashes[guardPathKey(resolved, cwd)] = currentFileFingerprint(resolved);
|
|
39
|
-
}
|
|
40
|
-
return hashes;
|
|
41
|
-
}
|
|
42
27
|
function capAffectedFiles(files, cwd) {
|
|
43
28
|
const unique = [...new Set(files.map((file) => resolveGuardPath(file, cwd)))];
|
|
44
29
|
return {
|
|
45
|
-
files: unique.slice(0,
|
|
46
|
-
truncated: unique.length >
|
|
30
|
+
files: unique.slice(0, MAX_ADVISORY_AFFECTED_FILES),
|
|
31
|
+
truncated: unique.length > MAX_ADVISORY_AFFECTED_FILES,
|
|
47
32
|
};
|
|
48
33
|
}
|
|
49
34
|
/** Recovery is safe only when blocker content and its file provenance agree. */
|
|
@@ -424,6 +409,17 @@ export function writeGitGuardRecord(cacheManager, runtime, cwd, record) {
|
|
|
424
409
|
fileSeqByPath[key] = runtime.getFileSeq(resolveGuardPath(file, cwd));
|
|
425
410
|
}
|
|
426
411
|
}
|
|
412
|
+
const currentProvenance = snapshotAdvisoryProvenance({
|
|
413
|
+
cwd,
|
|
414
|
+
runtime,
|
|
415
|
+
generation: 0,
|
|
416
|
+
files: capped.files.map((file) => ({ path: file, role: "affected" })),
|
|
417
|
+
truncated: capped.truncated,
|
|
418
|
+
});
|
|
419
|
+
const fileContentHashes = Object.fromEntries(currentProvenance.files.map((file) => [
|
|
420
|
+
guardPathKey(file.path, cwd),
|
|
421
|
+
file.sha256,
|
|
422
|
+
]));
|
|
427
423
|
const data = {
|
|
428
424
|
...record,
|
|
429
425
|
affectedFiles: capped.files,
|
|
@@ -432,7 +428,8 @@ export function writeGitGuardRecord(cacheManager, runtime, cwd, record) {
|
|
|
432
428
|
blockingFiles: Array.isArray(record.blockingFiles)
|
|
433
429
|
? capAffectedFiles(record.blockingFiles, cwd).files
|
|
434
430
|
: undefined,
|
|
435
|
-
fileContentHashes
|
|
431
|
+
fileContentHashes,
|
|
432
|
+
provenance: record.provenance ?? currentProvenance,
|
|
436
433
|
};
|
|
437
434
|
try {
|
|
438
435
|
cacheManager.writeCache("turn-end-findings", data, cwd);
|
|
@@ -468,6 +465,37 @@ function unknown(cwd, reasonCategory, metadata = {}) {
|
|
|
468
465
|
* on tool_result, never tool_call, and therefore does not add disk I/O to the
|
|
469
466
|
* edit preflight path.
|
|
470
467
|
*/
|
|
468
|
+
/**
|
|
469
|
+
* Retire one file's inline blocker on a confirmed-clean verdict, then bring the
|
|
470
|
+
* commit gate back in line with the map (#1561 F2).
|
|
471
|
+
*
|
|
472
|
+
* Retiring the map entry alone is not enough and claiming otherwise was wrong:
|
|
473
|
+
* the gate reads a LATCH (`gitGuardHasBlockers`) and a PERSISTED record, and
|
|
474
|
+
* `evaluateGitGuard` short-circuits on the latch before it ever looks at the
|
|
475
|
+
* record. A retire that touched neither left the commit blocked and quoted the
|
|
476
|
+
* retired blocker back as the reason.
|
|
477
|
+
*
|
|
478
|
+
* Both are recomputed exactly the way a clean dispatch recomputes them:
|
|
479
|
+
* `updateGitGuardStatus(false, "")` re-derives the latch from the blocker map
|
|
480
|
+
* — it cannot clear a latch another file's live blocker still justifies,
|
|
481
|
+
* because that file's entry is still in the map — and `syncGitGuardRecord`
|
|
482
|
+
* rewrites the persisted record from the same source of truth.
|
|
483
|
+
*
|
|
484
|
+
* Colocated with the gate rather than inlined at the wiring site so the claim
|
|
485
|
+
* is testable without booting the extension.
|
|
486
|
+
*
|
|
487
|
+
* @returns true when an entry was retired (the caller logs it — #1432 Gap 1).
|
|
488
|
+
*/
|
|
489
|
+
export function retireInlineBlockerAndResyncGuard(args) {
|
|
490
|
+
const retired = args.runtime.retireInlineBlockerOnConfirmedClean(args.filePath, args.writeIndex, args.coveredSources);
|
|
491
|
+
if (!retired)
|
|
492
|
+
return false;
|
|
493
|
+
args.runtime.updateGitGuardStatus(false, "");
|
|
494
|
+
if (args.lensGuardEnabled) {
|
|
495
|
+
syncGitGuardRecord(args.runtime, args.cacheManager, args.cwd, args.filePath);
|
|
496
|
+
}
|
|
497
|
+
return true;
|
|
498
|
+
}
|
|
471
499
|
export function syncGitGuardRecord(runtime, cacheManager, cwd, editedFilePath) {
|
|
472
500
|
const entries = runtime.getInlineBlockersSnapshot?.() ?? [];
|
|
473
501
|
const inspection = cacheManager.inspectCache("turn-end-findings", cwd);
|
|
@@ -28,19 +28,133 @@
|
|
|
28
28
|
* (installer entry registered in clients/installer/index.ts) and runs
|
|
29
29
|
* `gitleaks detect --no-git --report-format json` against the analysis root.
|
|
30
30
|
*
|
|
31
|
-
*
|
|
31
|
+
* Scope policy (#1562): gitleaks is handed a `--config` generated per scan
|
|
32
|
+
* (`writeScopedGitleaksConfig`) that EXTENDS the project's own `.gitleaks.toml`
|
|
33
|
+
* (or gitleaks's built-in defaults, if none) with two additions — an
|
|
34
|
+
* `[allowlist] paths` entry per NARROW secrets-lane scratch tree
|
|
35
|
+
* (`scratch-tree-policy.ts`'s `SECRETS_LANE_SCRATCH_DIR_NAMES` — pi-ecosystem/
|
|
36
|
+
* agent data dirs, `node_modules`, `.git`, generic build caches; deliberately
|
|
37
|
+
* NOT the broader walker-parity `EXCLUDED_DIRS` list knip/jscpd use, which
|
|
38
|
+
* also drops real leak surfaces like `dist/`, `vendor/`, `.vscode/` — see that
|
|
39
|
+
* module's doc for the review-round probe that caught the too-broad first
|
|
40
|
+
* cut) and an `[allowlist] regexes` entry per known placeholder-secret shape
|
|
41
|
+
* (`YOUR_API_KEY`, `<your-key>`, `xxxxxxxx`, …). An allowlist entry is a
|
|
42
|
+
* POST-DETECTION filter, not a walk exclusion — gitleaks still visits every
|
|
43
|
+
* file; the allowlist only decides which raw hits get suppressed from the
|
|
44
|
+
* report. Deliberately NOT `.gitignore`-based — see `scratch-tree-policy.ts`'s
|
|
45
|
+
* module doc for why blanket gitignore-respect is wrong for a secrets scanner
|
|
46
|
+
* (an untracked `.env` with a real credential is exactly the case gitleaks
|
|
47
|
+
* exists to catch, and `.env` is commonly gitignored). `classifyAndFilterFindings`
|
|
48
|
+
* backstops the config with a TS-side re-classification: a finding under the
|
|
49
|
+
* secrets-lane scratch tier is DEMOTED (not deleted) to `severity: "info"`,
|
|
50
|
+
* `semantic: "none"`, `pathStatus: "scratch"` — visible for an audit read,
|
|
51
|
+
* but it can never surface as a blocking "leaked secret" alarm. Every other
|
|
52
|
+
* finding gets `pathStatus` from git's tracked/ignored view.
|
|
53
|
+
*
|
|
54
|
+
* Refs: #130, #1562
|
|
32
55
|
*/
|
|
33
56
|
import * as fs from "node:fs";
|
|
34
57
|
import * as os from "node:os";
|
|
35
58
|
import * as path from "node:path";
|
|
36
59
|
import { mkdtempSync } from "node:fs";
|
|
60
|
+
import { collectTrackedFiles, collectUntrackedIgnoredIds, } from "./git-tracked-ignore.js";
|
|
61
|
+
import { normalizeEphemeralMapKey, normalizeMapKey } from "./path-utils.js";
|
|
37
62
|
import { safeSpawnAsync } from "./safe-spawn.js";
|
|
38
63
|
import { SecurityScanClient } from "./security-scan-client.js";
|
|
64
|
+
import { getSecretsLaneAllowlistPaths, isUnderSecretsLaneScratchTree, } from "./scratch-tree-policy.js";
|
|
39
65
|
const EMPTY_RESULT = {
|
|
40
66
|
success: false,
|
|
41
67
|
findings: [],
|
|
42
68
|
};
|
|
43
69
|
const SCAN_TIMEOUT_MS = 120_000;
|
|
70
|
+
// gitleaks discovers its own config at `<source>/.gitleaks.toml` (or `.yaml`/
|
|
71
|
+
// `.yml`) when no `--config` is passed. Kept as a named constant so the
|
|
72
|
+
// generated scoped config (below) can EXTEND whichever of these the project
|
|
73
|
+
// already has, instead of silently replacing the project's own rules.
|
|
74
|
+
const LOCAL_GITLEAKS_CONFIG_NAMES = [
|
|
75
|
+
".gitleaks.toml",
|
|
76
|
+
".gitleaks.yaml",
|
|
77
|
+
".gitleaks.yml",
|
|
78
|
+
];
|
|
79
|
+
function findLocalGitleaksConfig(cwd) {
|
|
80
|
+
for (const name of LOCAL_GITLEAKS_CONFIG_NAMES) {
|
|
81
|
+
const candidate = path.join(cwd, name);
|
|
82
|
+
try {
|
|
83
|
+
if (fs.existsSync(candidate))
|
|
84
|
+
return candidate;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// non-fatal
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
// The #1562 incident: a curl doc example's `-H "Authorization: Bearer
|
|
93
|
+
// YOUR_API_KEY"` (cached under gitignored scratch, see the secrets-lane
|
|
94
|
+
// exclusion below) read as a leaked secret. These match gitleaks's own
|
|
95
|
+
// `Secret`/`Match` value case-insensitively, catching the canonical
|
|
96
|
+
// placeholder shapes: `YOUR_API_KEY`-style tokens, `<your-key>`-style angle
|
|
97
|
+
// brackets, run-of-`x` filler, and the common changeme/example/dummy family.
|
|
98
|
+
// Scoped to the whole matched value (`^...$`) so a REAL secret that merely
|
|
99
|
+
// contains the substring "key" is never caught by accident.
|
|
100
|
+
//
|
|
101
|
+
// #1562 review-round F3: regex 2's alternation required "key" to be prefixed
|
|
102
|
+
// (`api[-_ ]?key`) — the issue's own literal acceptance example, `<your-key>`
|
|
103
|
+
// (bare "key", no "api" prefix), didn't match. Added a bare `key` alternative;
|
|
104
|
+
// still safe because the match is scoped to the WHOLE bracketed value
|
|
105
|
+
// (`^<...>$`), so a real secret that happens to be wrapped in angle brackets
|
|
106
|
+
// AND merely contain the substring "key" still wouldn't collide with this —
|
|
107
|
+
// only a value that is ENTIRELY `<...key...>`-shaped does.
|
|
108
|
+
export const PLACEHOLDER_SECRET_REGEXES = [
|
|
109
|
+
String.raw `(?i)^(your|my|insert|replace|enter|add)[-_ ]?(the[-_ ]?)?(api[-_ ]?)?(key|token|secret|password|credential)s?$`,
|
|
110
|
+
String.raw `(?i)^<[^<>]*(api[-_ ]?key|token|secret|password|credential|key)[^<>]*>$`,
|
|
111
|
+
String.raw `(?i)^x{3,}$`,
|
|
112
|
+
String.raw `(?i)^(changeme|change[-_]me|example|placeholder|dummy|fake|redacted|xxxxxxxx)$`,
|
|
113
|
+
];
|
|
114
|
+
/**
|
|
115
|
+
* Build a temp gitleaks config that EXTENDS the project's own config (if any,
|
|
116
|
+
* else gitleaks's built-in defaults) with two additions:
|
|
117
|
+
* 1. `[allowlist] paths` — the NARROW secrets-lane scratch exclusion
|
|
118
|
+
* (`scratch-tree-policy.ts`'s `getSecretsLaneAllowlistPaths`, NOT the
|
|
119
|
+
* broader walker-parity `EXCLUDED_DIRS` list — #1562 review-round F1).
|
|
120
|
+
* 2. `[allowlist] regexes` — the placeholder-secret class above.
|
|
121
|
+
*
|
|
122
|
+
* An `[allowlist]` entry is a POST-DETECTION filter (#1562 review-round F4):
|
|
123
|
+
* gitleaks still visits every file under `--source`; the allowlist decides
|
|
124
|
+
* which of the raw hits it reports, it does not shrink the walk itself.
|
|
125
|
+
*
|
|
126
|
+
* gitleaks's `[extend]` allowlist merge is additive (append, not replace —
|
|
127
|
+
* see gitleaks/config/config.go), so a project's own curated allowlist keeps
|
|
128
|
+
* working unchanged; this only ever ADDS exclusions, never removes one the
|
|
129
|
+
* project opted into.
|
|
130
|
+
*
|
|
131
|
+
* Returns the generated file's path; caller owns cleanup (same temp dir as
|
|
132
|
+
* the report, removed together in `runScan`'s `finally`).
|
|
133
|
+
*/
|
|
134
|
+
export function writeScopedGitleaksConfig(outDir, cwd) {
|
|
135
|
+
const localConfig = findLocalGitleaksConfig(cwd);
|
|
136
|
+
const extendLine = localConfig
|
|
137
|
+
? `path = ${JSON.stringify(localConfig)}`
|
|
138
|
+
: "useDefault = true";
|
|
139
|
+
const pathPatterns = getSecretsLaneAllowlistPaths()
|
|
140
|
+
.map((p) => ` ${JSON.stringify(p)},`)
|
|
141
|
+
.join("\n");
|
|
142
|
+
const regexPatterns = PLACEHOLDER_SECRET_REGEXES.map((r) => ` ${JSON.stringify(r)},`).join("\n");
|
|
143
|
+
const toml = `[extend]
|
|
144
|
+
${extendLine}
|
|
145
|
+
|
|
146
|
+
[allowlist]
|
|
147
|
+
paths = [
|
|
148
|
+
${pathPatterns}
|
|
149
|
+
]
|
|
150
|
+
regexes = [
|
|
151
|
+
${regexPatterns}
|
|
152
|
+
]
|
|
153
|
+
`;
|
|
154
|
+
const configPath = path.join(outDir, "gitleaks-scoped-config.toml");
|
|
155
|
+
fs.writeFileSync(configPath, toml, "utf-8");
|
|
156
|
+
return configPath;
|
|
157
|
+
}
|
|
44
158
|
// --- Detection ---
|
|
45
159
|
/**
|
|
46
160
|
* Detect whether the project root has opted in to gitleaks via any of the
|
|
@@ -51,12 +165,7 @@ const SCAN_TIMEOUT_MS = 120_000;
|
|
|
51
165
|
* the client.
|
|
52
166
|
*/
|
|
53
167
|
export function hasGitleaksSignal(cwd) {
|
|
54
|
-
const candidates = [
|
|
55
|
-
".gitleaks.toml",
|
|
56
|
-
".gitleaks.yaml",
|
|
57
|
-
".gitleaks.yml",
|
|
58
|
-
".gitleaksignore",
|
|
59
|
-
];
|
|
168
|
+
const candidates = [...LOCAL_GITLEAKS_CONFIG_NAMES, ".gitleaksignore"];
|
|
60
169
|
for (const candidate of candidates) {
|
|
61
170
|
try {
|
|
62
171
|
if (fs.existsSync(path.join(cwd, candidate)))
|
|
@@ -187,11 +296,14 @@ export class GitleaksClient extends SecurityScanClient {
|
|
|
187
296
|
const outDir = mkdtempSync(path.join(os.tmpdir(), "pi-lens-gitleaks-"));
|
|
188
297
|
const reportPath = path.join(outDir, "gitleaks-report.json");
|
|
189
298
|
try {
|
|
299
|
+
const configPath = writeScopedGitleaksConfig(outDir, cwd);
|
|
190
300
|
const result = await safeSpawnAsync(bin, [
|
|
191
301
|
"detect",
|
|
192
302
|
"--no-git",
|
|
193
303
|
"--source",
|
|
194
304
|
cwd,
|
|
305
|
+
"--config",
|
|
306
|
+
configPath,
|
|
195
307
|
"--report-format",
|
|
196
308
|
"json",
|
|
197
309
|
"--report-path",
|
|
@@ -219,7 +331,8 @@ export class GitleaksClient extends SecurityScanClient {
|
|
|
219
331
|
summary: (result.stderr ?? "").trim().split("\n")[0] || "no report produced",
|
|
220
332
|
};
|
|
221
333
|
}
|
|
222
|
-
const
|
|
334
|
+
const rawFindings = parseGitleaksReport(fs.readFileSync(reportPath, "utf-8"));
|
|
335
|
+
const findings = await classifyAndFilterFindings(rawFindings, cwd);
|
|
223
336
|
return {
|
|
224
337
|
success: true,
|
|
225
338
|
findings,
|
|
@@ -243,6 +356,66 @@ export class GitleaksClient extends SecurityScanClient {
|
|
|
243
356
|
}
|
|
244
357
|
}
|
|
245
358
|
}
|
|
359
|
+
// --- Post-scan classification / filter ---
|
|
360
|
+
/**
|
|
361
|
+
* Backstop for the scoped-config secrets-lane exclusion above, plus the
|
|
362
|
+
* `pathStatus` observability field (#1562).
|
|
363
|
+
*
|
|
364
|
+
* Two things happen here, both defense-in-depth relative to the generated
|
|
365
|
+
* gitleaks config:
|
|
366
|
+
* 1. Any finding still under the NARROW secrets-lane scratch tier
|
|
367
|
+
* (`isUnderSecretsLaneScratchTree` — NOT the broader walker-parity
|
|
368
|
+
* `EXCLUDED_DIRS` list) is DEMOTED, never DROPPED (#1562 review-round
|
|
369
|
+
* F2: the observability criterion requires this value to actually be
|
|
370
|
+
* reachable, so a scratch finding survives with `pathStatus: "scratch"`
|
|
371
|
+
* and the adapter (`gitleaksFindingToProjectDiagnostic`) reports it as
|
|
372
|
+
* `severity: "info"`/`semantic: "none"` — visible for an audit read,
|
|
373
|
+
* but it can never surface as a blocking "leaked secret" alarm). We
|
|
374
|
+
* can't exercise gitleaks's own TOML-regex engine in unit tests, only
|
|
375
|
+
* assert the config we hand it, so this backstop guarantees the demotion
|
|
376
|
+
* even if the generated regex ever mismatches gitleaks's own path
|
|
377
|
+
* normalization.
|
|
378
|
+
* 2. Every OTHER finding gets `pathStatus` set from git's tracked/ignored
|
|
379
|
+
* view, so a future false-positive triage is a log read.
|
|
380
|
+
*
|
|
381
|
+
* `collectTrackedFiles`/`collectUntrackedIgnoredIds` degrade to `undefined`
|
|
382
|
+
* when git itself is unavailable (no repo, spawn failure) — findings just
|
|
383
|
+
* keep `pathStatus: undefined` rather than a guessed value (fail-open,
|
|
384
|
+
* matching `git-tracked-ignore.ts`'s own contract). Exported for unit tests.
|
|
385
|
+
*/
|
|
386
|
+
export async function classifyAndFilterFindings(findings, cwd) {
|
|
387
|
+
if (findings.length === 0)
|
|
388
|
+
return findings;
|
|
389
|
+
const [trackedIds, untrackedIgnoredIds] = await Promise.all([
|
|
390
|
+
collectTrackedFiles(cwd),
|
|
391
|
+
collectUntrackedIgnoredIds(cwd),
|
|
392
|
+
]);
|
|
393
|
+
const classified = [];
|
|
394
|
+
for (const finding of findings) {
|
|
395
|
+
if (isUnderSecretsLaneScratchTree(finding.file)) {
|
|
396
|
+
classified.push({ ...finding, pathStatus: "scratch" });
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
const absPath = path.isAbsolute(finding.file)
|
|
400
|
+
? finding.file
|
|
401
|
+
: path.resolve(cwd, finding.file);
|
|
402
|
+
let pathStatus;
|
|
403
|
+
if (trackedIds?.has(normalizeEphemeralMapKey(absPath))) {
|
|
404
|
+
pathStatus = "tracked";
|
|
405
|
+
}
|
|
406
|
+
else if (untrackedIgnoredIds?.has(normalizeMapKey(absPath))) {
|
|
407
|
+
pathStatus = "ignored";
|
|
408
|
+
}
|
|
409
|
+
else if (trackedIds !== undefined || untrackedIgnoredIds !== undefined) {
|
|
410
|
+
// At least one of the two git-backed sets resolved, so the absence
|
|
411
|
+
// from both is a real answer ("untracked, not gitignored") rather
|
|
412
|
+
// than an unresolved probe.
|
|
413
|
+
pathStatus = "untracked";
|
|
414
|
+
}
|
|
415
|
+
classified.push(pathStatus ? { ...finding, pathStatus } : finding);
|
|
416
|
+
}
|
|
417
|
+
return classified;
|
|
418
|
+
}
|
|
246
419
|
// --- Parser ---
|
|
247
420
|
/**
|
|
248
421
|
* Map gitleaks's JSON report (a flat array of finding objects) to our
|
|
@@ -7,15 +7,16 @@
|
|
|
7
7
|
* Docs: https://pkg.go.dev/golang.org/x/tools/gopls
|
|
8
8
|
*/
|
|
9
9
|
import { createSubsystemLogger } from "./extension-log.js";
|
|
10
|
-
import * as fs from "node:fs";
|
|
11
10
|
import * as path from "node:path";
|
|
12
|
-
import {
|
|
11
|
+
import { createToolchainAvailability, } from "./dispatch/runners/utils/toolchain-availability.js";
|
|
13
12
|
// --- Common install paths ---
|
|
14
13
|
const GO_WINDOWS_PATHS = [
|
|
15
14
|
"C:\\Program Files\\Go\\bin\\go.exe",
|
|
16
15
|
"C:\\Go\\bin\\go.exe",
|
|
17
16
|
"go.exe", // PATH
|
|
18
17
|
];
|
|
18
|
+
/** Budget for the PATH candidate's `go version` probe, ms. */
|
|
19
|
+
const PROBE_TIMEOUT_MS = 3_000;
|
|
19
20
|
const GO_UNIX_PATHS = [
|
|
20
21
|
"/usr/local/go/bin/go",
|
|
21
22
|
"/usr/bin/go",
|
|
@@ -23,58 +24,39 @@ const GO_UNIX_PATHS = [
|
|
|
23
24
|
];
|
|
24
25
|
// --- Client ---
|
|
25
26
|
export class GoClient {
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Availability lifecycle, behind the shared transient-aware latch (#1476).
|
|
29
|
+
* The PATH candidate is resolved by spawning `go version` with a 3 s budget,
|
|
30
|
+
* so a host stall could latch "no Go toolchain" for the whole session and
|
|
31
|
+
* silently disable every Go diagnostic until restart.
|
|
32
|
+
*/
|
|
33
|
+
availability;
|
|
28
34
|
log;
|
|
29
35
|
constructor(verbose = false) {
|
|
30
36
|
this.log = verbose
|
|
31
37
|
? createSubsystemLogger("go")
|
|
32
38
|
: () => { };
|
|
39
|
+
this.availability = createToolchainAvailability({
|
|
40
|
+
tool: "go",
|
|
41
|
+
label: "Go",
|
|
42
|
+
windowsPaths: GO_WINDOWS_PATHS,
|
|
43
|
+
unixPaths: GO_UNIX_PATHS,
|
|
44
|
+
probeArgs: ["version"],
|
|
45
|
+
budgetMs: PROBE_TIMEOUT_MS,
|
|
46
|
+
log: (msg) => this.log(msg),
|
|
47
|
+
});
|
|
33
48
|
}
|
|
34
49
|
/**
|
|
35
50
|
* Find go executable path (async — probes PATH candidates off the event loop).
|
|
36
51
|
*/
|
|
37
52
|
async findGoPathAsync() {
|
|
38
|
-
|
|
39
|
-
return this.goPath;
|
|
40
|
-
const paths = process.platform === "win32" ? GO_WINDOWS_PATHS : GO_UNIX_PATHS;
|
|
41
|
-
for (const p of paths) {
|
|
42
|
-
try {
|
|
43
|
-
if (p.includes("\\") || p.includes("/")) {
|
|
44
|
-
// Absolute path - check if exists
|
|
45
|
-
if (fs.existsSync(p)) {
|
|
46
|
-
this.goPath = p;
|
|
47
|
-
return p;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
// Relative (PATH) - try running it
|
|
52
|
-
const result = await safeSpawnAsync(p, ["version"], {
|
|
53
|
-
timeout: 3000,
|
|
54
|
-
});
|
|
55
|
-
if (!result.error && result.status === 0) {
|
|
56
|
-
this.goPath = p;
|
|
57
|
-
return p;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
catch (err) {
|
|
62
|
-
void err;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return null;
|
|
53
|
+
return this.availability.findPath();
|
|
66
54
|
}
|
|
67
55
|
/**
|
|
68
56
|
* Check if Go is installed (cached)
|
|
69
57
|
*/
|
|
70
58
|
async isGoAvailableAsync() {
|
|
71
|
-
|
|
72
|
-
return this.goAvailable;
|
|
73
|
-
this.goAvailable = (await this.findGoPathAsync()) !== null;
|
|
74
|
-
if (this.goAvailable) {
|
|
75
|
-
this.log(`Go found: ${this.goPath}`);
|
|
76
|
-
}
|
|
77
|
-
return this.goAvailable;
|
|
59
|
+
return this.availability.isAvailable();
|
|
78
60
|
}
|
|
79
61
|
/**
|
|
80
62
|
* Check if a file is a Go file
|