@herbertgao/pi-extensions 2026.8.6 → 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 +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -1
- 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/renderer/compact-mode.ts +94 -77
- 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 +6 -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 +17 -13
- 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 +21 -6
- package/node_modules/@herbertgao/pi-subagents/src/usage.ts +129 -1
- package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +20 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +1 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +26 -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 +2 -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 +120 -111
- 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 +10 -6
- package/node_modules/@narumitw/pi-btw/src/btw.ts +36 -40
- 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 +45 -2
- package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +136 -45
- package/node_modules/pi-lens/CHANGELOG.md +909 -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 +126 -13
- package/node_modules/pi-lens/dist/clients/agent-nudge.js +67 -11
- 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 +61 -30
- 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/cascade-budget.js +196 -0
- package/node_modules/pi-lens/dist/clients/cascade-format.js +20 -1
- 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/dead-code-client.js +123 -31
- 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 +25 -3
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +147 -33
- package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +298 -0
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +52 -2
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +256 -88
- 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 +60 -53
- 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 +275 -13
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +21 -6
- 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 +734 -64
- 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 +9 -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/event-loop-monitor.js +28 -5
- package/node_modules/pi-lens/dist/clients/extension-log.js +22 -1
- 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/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 +31 -0
- package/node_modules/pi-lens/dist/clients/gitleaks-client.js +181 -8
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +109 -9
- package/node_modules/pi-lens/dist/clients/grammar-source.js +333 -10
- package/node_modules/pi-lens/dist/clients/host-ports.js +8 -0
- package/node_modules/pi-lens/dist/clients/installer/index.js +1114 -100
- 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 +82 -6
- package/node_modules/pi-lens/dist/clients/knip-client.js +310 -10
- 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 +440 -11
- 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-flag-registry.js +8 -0
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +102 -7
- package/node_modules/pi-lens/dist/clients/lsp/client.js +1516 -159
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +83 -1
- package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +389 -0
- package/node_modules/pi-lens/dist/clients/lsp/index.js +2274 -147
- 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 +277 -16
- 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 +79 -3
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +43 -0
- 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/session.js +10 -2
- 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 +154 -33
- 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 +84 -14
- 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/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 +52 -4
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +96 -7
- package/node_modules/pi-lens/dist/clients/runtime-context.js +8 -1
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +308 -45
- package/node_modules/pi-lens/dist/clients/runtime-session.js +332 -9
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +217 -22
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +212 -79
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +575 -67
- 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 +157 -24
- package/node_modules/pi-lens/dist/clients/sg-runner.js +116 -22
- package/node_modules/pi-lens/dist/clients/single-flight.js +158 -0
- package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
- package/node_modules/pi-lens/dist/clients/startup-timing.js +19 -1
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +151 -16
- package/node_modules/pi-lens/dist/clients/tool-event.js +56 -5
- package/node_modules/pi-lens/dist/clients/tool-policy.js +125 -20
- 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/widget-state.js +336 -36
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +214 -14
- package/node_modules/pi-lens/dist/index.js +67738 -55739
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +466 -54
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +132 -43
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -7
- package/node_modules/pi-lens/docs/agent-guide.md +6 -1
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +515 -506
- package/node_modules/pi-lens/docs/dispositions.md +21 -0
- package/node_modules/pi-lens/docs/features.md +19 -5
- package/node_modules/pi-lens/docs/globalconfig.md +3 -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 +19 -1
- package/node_modules/pi-lens/docs/usage.md +1 -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 +15 -1
- 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-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 +2 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +2 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +2 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +2 -1
- 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-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 +57 -12
- 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-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 +22 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +23 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +33 -5
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +34 -8
- 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 +6 -1
- 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 +53 -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 +2 -0
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +80 -3
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +41 -0
- package/node_modules/pi-lens/vendor/grammars/tree-sitter-cue.wasm +0 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +36 -0
- package/node_modules/pi-mcp-adapter/README.md +47 -5
- package/node_modules/pi-mcp-adapter/cli.js +163 -1
- package/node_modules/pi-mcp-adapter/commands.ts +128 -22
- package/node_modules/pi-mcp-adapter/config.ts +25 -7
- package/node_modules/pi-mcp-adapter/direct-tools.ts +5 -4
- 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/metadata-cache.ts +32 -21
- package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +168 -0
- package/node_modules/pi-mcp-adapter/package.json +3 -1
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +5 -4
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +31 -37
- package/node_modules/pi-mcp-adapter/search-ranking.ts +92 -22
- package/node_modules/pi-mcp-adapter/server-manager.ts +140 -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-result-renderer.ts +44 -4
- package/node_modules/pi-mcp-adapter/types.ts +2 -0
- 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 +23 -0
- package/node_modules/pi-web-access/README.md +27 -9
- package/node_modules/pi-web-access/anysearch.ts +4 -2
- package/node_modules/pi-web-access/brave.ts +16 -4
- package/node_modules/pi-web-access/curator-page.ts +6 -3
- package/node_modules/pi-web-access/curator-server.ts +4 -1
- package/node_modules/pi-web-access/exa.ts +18 -7
- package/node_modules/pi-web-access/extract.ts +28 -3
- package/node_modules/pi-web-access/gemini-search.ts +16 -4
- package/node_modules/pi-web-access/index.ts +41 -57
- package/node_modules/pi-web-access/package.json +1 -1
- 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 +44 -22
- 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/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
|
+
}
|
|
@@ -465,6 +465,37 @@ function unknown(cwd, reasonCategory, metadata = {}) {
|
|
|
465
465
|
* on tool_result, never tool_call, and therefore does not add disk I/O to the
|
|
466
466
|
* edit preflight path.
|
|
467
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
|
+
}
|
|
468
499
|
export function syncGitGuardRecord(runtime, cacheManager, cwd, editedFilePath) {
|
|
469
500
|
const entries = runtime.getInlineBlockersSnapshot?.() ?? [];
|
|
470
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
|
|
@@ -22,7 +22,8 @@ import * as path from "node:path";
|
|
|
22
22
|
import { safeSpawnAsync } from "./safe-spawn.js";
|
|
23
23
|
import { assertInstallAllowed } from "./project-trust.js";
|
|
24
24
|
import { SecurityScanClient } from "./security-scan-client.js";
|
|
25
|
-
import { classifyProbeFailure, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
|
|
25
|
+
import { classifyProbeFailure, describeProbeEvidence, INSTALL_TRANSIENT_MAX_ATTEMPTS, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
|
|
26
|
+
import { recordDegradationOnce } from "./degradation-ledger.js";
|
|
26
27
|
const EMPTY_RESULT = {
|
|
27
28
|
success: false,
|
|
28
29
|
findings: [],
|
|
@@ -61,9 +62,10 @@ export class GovulncheckClient extends SecurityScanClient {
|
|
|
61
62
|
// #1467: a timed-out/killed probe says nothing about whether
|
|
62
63
|
// govulncheck is on PATH. Latching `false` here disabled it for the
|
|
63
64
|
// life of the process; a `go install` here would be a heavyweight
|
|
64
|
-
// reaction to a host hiccup.
|
|
65
|
+
// reaction to a host hiccup. probeVersion has already recorded the
|
|
66
|
+
// expiring verdict and its retry schedule (#1501) — re-marking it
|
|
67
|
+
// here would double-escalate the cooldown. Retry later.
|
|
65
68
|
this.log("govulncheck probe timed out; retrying later (not installing)");
|
|
66
|
-
this.markTransientlyUnavailable(this.lastProbeCause ?? "probe-timeout");
|
|
67
69
|
return false;
|
|
68
70
|
}
|
|
69
71
|
if (!assertInstallAllowed("govulncheck go install")) {
|
|
@@ -112,7 +114,15 @@ export class GovulncheckClient extends SecurityScanClient {
|
|
|
112
114
|
return false;
|
|
113
115
|
}
|
|
114
116
|
this.log("go binary not on PATH — cannot auto-install govulncheck");
|
|
115
|
-
|
|
117
|
+
// Derived from the `go version` probe above, and recorded with it: a
|
|
118
|
+
// reader can see WHY govulncheck went quiet without re-running it (#1500).
|
|
119
|
+
// The evidence names `go`, because that is what was spawned — a row that
|
|
120
|
+
// carried go's errno unlabelled under `tool: "govulncheck"` invited the
|
|
121
|
+
// exact misreading the field exists to prevent.
|
|
122
|
+
this.noteDurableAbsence({
|
|
123
|
+
...describeProbeEvidence(goOnPath, "go"),
|
|
124
|
+
install: "not-attempted",
|
|
125
|
+
});
|
|
116
126
|
return false;
|
|
117
127
|
}
|
|
118
128
|
this.log("govulncheck not found, attempting auto-install via go install");
|
|
@@ -139,23 +149,63 @@ export class GovulncheckClient extends SecurityScanClient {
|
|
|
139
149
|
hostStallMs: installHostStallMs,
|
|
140
150
|
});
|
|
141
151
|
if (outcome === "transient") {
|
|
142
|
-
|
|
152
|
+
// #1497: the retried operation here is a ≤60s network compile,
|
|
153
|
+
// not a cheap probe, so it escalates on the install-class
|
|
154
|
+
// schedule and latches at the attempt ceiling instead of
|
|
155
|
+
// re-compiling every few minutes forever.
|
|
156
|
+
const retryAfterMs = this.markTransientlyUnavailable(cause, {
|
|
157
|
+
operationClass: "install",
|
|
158
|
+
});
|
|
159
|
+
const exhausted = retryAfterMs === 0;
|
|
160
|
+
const ceilingReason = `go install timed out ${INSTALL_TRANSIENT_MAX_ATTEMPTS} times; install retries disabled until the next session`;
|
|
161
|
+
if (exhausted) {
|
|
162
|
+
// The symptom a user notices is an unexplained busy core, not a
|
|
163
|
+
// missing tool — so the terminal verdict gets the louder record.
|
|
164
|
+
// Once per session, matching the latch's own lifetime: both re-arm
|
|
165
|
+
// at `session_start` (review F3).
|
|
166
|
+
recordDegradationOnce({
|
|
167
|
+
kind: "install-retry-exhausted",
|
|
168
|
+
subject: "govulncheck",
|
|
169
|
+
reason: ceilingReason,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
143
172
|
// One record per decision, so an install that keeps timing out is
|
|
144
173
|
// readable in latency.log the same day (#1467's forensic trail).
|
|
145
174
|
logAvailabilityDecision({
|
|
146
175
|
tool: "govulncheck",
|
|
147
176
|
verdict: "unavailable",
|
|
148
177
|
outcome,
|
|
149
|
-
cause
|
|
178
|
+
// At the ceiling the latch rewrote the cause; a row still saying
|
|
179
|
+
// `probe-timeout` would read as "cooling down" (#1497 review F5).
|
|
180
|
+
cause: exhausted ? (this.latchedCause() ?? cause) : cause,
|
|
150
181
|
elapsedMs: Date.now() - installStartedAt,
|
|
151
|
-
latched:
|
|
182
|
+
latched: exhausted,
|
|
152
183
|
hostStallMs: installHostStallMs,
|
|
153
184
|
...(retryAfterMs > 0 && { retryAfterMs }),
|
|
154
185
|
budgetMs: INSTALL_TIMEOUT_MS,
|
|
186
|
+
// The ceiling verdict is an ASSERTION by this call site, not a
|
|
187
|
+
// classification of one spawn, and #1534's convention is that such a
|
|
188
|
+
// row says so and carries the install facts behind it. Every retry
|
|
189
|
+
// DID run a `go install` that failed, so `install: "failed"` is
|
|
190
|
+
// earned, and the reason names the ceiling rather than the spawn.
|
|
191
|
+
...(exhausted && {
|
|
192
|
+
classifiedBy: "caller",
|
|
193
|
+
evidence: {
|
|
194
|
+
...describeProbeEvidence(install, "go install"),
|
|
195
|
+
install: "failed",
|
|
196
|
+
installReason: ceilingReason,
|
|
197
|
+
},
|
|
198
|
+
}),
|
|
155
199
|
});
|
|
156
200
|
return false;
|
|
157
201
|
}
|
|
158
|
-
|
|
202
|
+
// A non-transient install failure (module not found, compile error) IS
|
|
203
|
+
// evidence about this machine, so it latches — but the row says the
|
|
204
|
+
// install was tried and failed, which a plain absence never does (#1500).
|
|
205
|
+
this.noteDurableAbsence({
|
|
206
|
+
...describeProbeEvidence(install, "go install"),
|
|
207
|
+
install: "failed",
|
|
208
|
+
}, { elapsedMs: Date.now() - installStartedAt });
|
|
159
209
|
return false;
|
|
160
210
|
}
|
|
161
211
|
// `go install` writes to `$GOBIN` or `$GOPATH/bin`. The user may not
|
|
@@ -176,6 +226,27 @@ export class GovulncheckClient extends SecurityScanClient {
|
|
|
176
226
|
if (!reprobe.error && reprobe.status === 0) {
|
|
177
227
|
this.log("govulncheck auto-installed and found on PATH");
|
|
178
228
|
this.available = true;
|
|
229
|
+
// The initial PATH probe already wrote a latched `unavailable` row
|
|
230
|
+
// (#1500's own assertion doesn't fire here — the reprobe SUCCEEDED —
|
|
231
|
+
// but that first probe's verdict still logged, before `go install`
|
|
232
|
+
// ever ran). Without a compensating row here, the durable record says
|
|
233
|
+
// govulncheck is off when `go install` just put it on PATH (#1606).
|
|
234
|
+
logAvailabilityDecision({
|
|
235
|
+
tool: "govulncheck",
|
|
236
|
+
verdict: "available",
|
|
237
|
+
outcome: "success",
|
|
238
|
+
cause: "ok",
|
|
239
|
+
elapsedMs: Date.now() - reprobeStartedAt,
|
|
240
|
+
latched: true,
|
|
241
|
+
hostStallMs: reprobeHostStallMs,
|
|
242
|
+
budgetMs: 5000,
|
|
243
|
+
classifiedBy: "caller",
|
|
244
|
+
evidence: {
|
|
245
|
+
install: "succeeded",
|
|
246
|
+
binary: "govulncheck",
|
|
247
|
+
source: "go-install",
|
|
248
|
+
},
|
|
249
|
+
});
|
|
179
250
|
return true;
|
|
180
251
|
}
|
|
181
252
|
// Look in the canonical install locations and remember the absolute
|
|
@@ -196,6 +267,24 @@ export class GovulncheckClient extends SecurityScanClient {
|
|
|
196
267
|
this.binaryPath = candidate;
|
|
197
268
|
this.available = true;
|
|
198
269
|
this.log(`govulncheck auto-installed at ${candidate}`);
|
|
270
|
+
// Same compensating row as the on-PATH reprobe arm above (#1606):
|
|
271
|
+
// the initial probe already latched `unavailable`, and this arm
|
|
272
|
+
// is the OTHER way to recover — the canonical $GOBIN/$GOPATH walk
|
|
273
|
+
// found it after the on-PATH reprobe missed.
|
|
274
|
+
logAvailabilityDecision({
|
|
275
|
+
tool: "govulncheck",
|
|
276
|
+
verdict: "available",
|
|
277
|
+
outcome: "success",
|
|
278
|
+
cause: "ok",
|
|
279
|
+
elapsedMs: Date.now() - installStartedAt,
|
|
280
|
+
latched: true,
|
|
281
|
+
classifiedBy: "caller",
|
|
282
|
+
evidence: {
|
|
283
|
+
install: "succeeded",
|
|
284
|
+
binary: path.basename(candidate),
|
|
285
|
+
source: "go-install",
|
|
286
|
+
},
|
|
287
|
+
});
|
|
199
288
|
return true;
|
|
200
289
|
}
|
|
201
290
|
}
|
|
@@ -227,8 +316,19 @@ export class GovulncheckClient extends SecurityScanClient {
|
|
|
227
316
|
});
|
|
228
317
|
return false;
|
|
229
318
|
}
|
|
319
|
+
// The third silent arm (#1500 review): the install SUCCEEDED and the binary
|
|
320
|
+
// is nowhere the re-probe or the canonical bin dirs could find it. That is a
|
|
321
|
+
// durable, actionable fact — and until now it latched with no record at all,
|
|
322
|
+
// so a $GOBIN misconfiguration was indistinguishable from govulncheck simply
|
|
323
|
+
// not being installed. `install: "succeeded"` also appears on the two success
|
|
324
|
+
// arms above (#1606's compensating `available` rows) — this is the durable
|
|
325
|
+
// FAILURE counterpart, an install that worked but left nothing locatable.
|
|
230
326
|
this.log("govulncheck auto-install succeeded but binary not locatable — check $GOBIN / $GOPATH");
|
|
231
|
-
this.
|
|
327
|
+
this.noteDurableAbsence({
|
|
328
|
+
...describeProbeEvidence(reprobe, "govulncheck"),
|
|
329
|
+
install: "succeeded",
|
|
330
|
+
installReason: "installed binary not found on PATH, $GOBIN or $GOPATH/bin",
|
|
331
|
+
}, { elapsedMs: Date.now() - reprobeStartedAt });
|
|
232
332
|
return false;
|
|
233
333
|
}
|
|
234
334
|
/**
|