@herbertgao/pi-extensions 2026.8.6 → 2026.8.8
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 +8 -6
- 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 +320 -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 +79 -17
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +27 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +66 -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 +5 -4
- package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +174 -100
- 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 +5 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +10 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +10 -6
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/tool-schema.md +11 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +5 -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 +140 -97
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +63 -28
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/selectors/projections.ts +22 -15
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/state-reducer.ts +11 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/state.ts +14 -6
- package/node_modules/@juicesharp/rpiv-ask-user-question/tool/response-envelope.ts +11 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/tool/types.ts +11 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/multi-select-view.ts +31 -17
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/preview/preview-block-renderer.ts +15 -6
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/wrapping-select.ts +15 -6
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +86 -38
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +76 -9
- 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-plugins/fast-mode/dist/index.mjs +44 -1856
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
- package/node_modules/@pi-plugins/fast-mode/package.json +4 -6
- package/node_modules/pi-lens/CHANGELOG.md +999 -0
- package/node_modules/pi-lens/README.md +117 -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 +381 -13
- 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 +129 -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 +206 -0
- package/node_modules/pi-lens/dist/clients/cache-observability.js +353 -76
- 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 +84 -14
- package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +155 -0
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +25 -3
- package/node_modules/pi-lens/dist/clients/deps/pi-tui.js +8 -2
- package/node_modules/pi-lens/dist/clients/deps/typebox.js +8 -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/detekt.js +9 -6
- 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/hadolint.js +9 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +895 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +8 -6
- 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 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +25 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +20 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +44 -11
- 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 +17 -8
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +32 -4
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +47 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +14 -4
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +127 -28
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +12 -7
- 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 +188 -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 +21 -10
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +18 -2
- 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 +316 -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 +87 -8
- 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 +9 -2
- 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 +128 -8
- package/node_modules/pi-lens/dist/clients/lsp/client.js +1660 -164
- 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 +2423 -151
- 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 +22 -1
- package/node_modules/pi-lens/dist/clients/ndjson-logger.js +114 -23
- 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 +23 -9
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
- package/node_modules/pi-lens/dist/clients/project-report.js +62 -27
- 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 +8 -3
- package/node_modules/pi-lens/dist/clients/read-guard.js +253 -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/builder.js +194 -31
- package/node_modules/pi-lens/dist/clients/review-graph/query.js +52 -4
- package/node_modules/pi-lens/dist/clients/review-graph/revision-drift.js +21 -0
- package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +6 -3
- 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 +352 -45
- package/node_modules/pi-lens/dist/clients/runtime-session.js +373 -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 +620 -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/session-event-guard.js +184 -0
- 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/source-filter.js +6 -2
- package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
- package/node_modules/pi-lens/dist/clients/startup-scan.js +5 -2
- 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-logger.js +1 -0
- 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 +61407 -48577
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +476 -57
- 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 +27 -10
- 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/scripts/install-selftest.mjs +99 -11
- package/node_modules/pi-lens/scripts/lib/host-provided-deps.mjs +75 -0
- package/node_modules/pi-lens/scripts/lib/warm-loader-cache.mjs +285 -0
- package/node_modules/pi-lens/scripts/warm-loader-cache.mjs +233 -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 +37 -0
- package/node_modules/pi-web-access/README.md +32 -13
- 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/chrome-cookies.ts +86 -15
- 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/data-uri-sanitize.ts +406 -0
- package/node_modules/pi-web-access/exa.ts +18 -7
- package/node_modules/pi-web-access/extract.ts +40 -5
- package/node_modules/pi-web-access/firecrawl.ts +18 -2
- package/node_modules/pi-web-access/gemini-search.ts +16 -4
- package/node_modules/pi-web-access/github-extract.ts +65 -36
- package/node_modules/pi-web-access/index.ts +60 -61
- package/node_modules/pi-web-access/openai-search.ts +17 -6
- 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/pdf-extract.ts +18 -5
- 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/ssrf-protection.ts +12 -2
- 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 +14 -14
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
*
|
|
5
5
|
* - VIEW commands (cat/head/tail/sed -n) → reads, recorded with the exact line
|
|
6
6
|
* range shown (like the Read tool's delivered range).
|
|
7
|
-
* - WRITE commands (redirects, tee, sed -i, cp/mv dest, touch
|
|
7
|
+
* - WRITE commands (redirects, tee, sed -i, cp/mv dest, touch, and explicit
|
|
8
|
+
* in-place formatter/fixer targets) → the agent
|
|
8
9
|
* authored/owns the resulting file, exactly like the Write tool — these are
|
|
9
10
|
* registered via noteCreatedFile + recordWritten so a follow-up edit is not
|
|
10
11
|
* blocked.
|
|
@@ -44,10 +45,10 @@ export function tokenizeShellCommand(command) {
|
|
|
44
45
|
word = "";
|
|
45
46
|
atTokenStart = true;
|
|
46
47
|
};
|
|
47
|
-
const flushSegment = () => {
|
|
48
|
+
const flushSegment = (terminator) => {
|
|
48
49
|
flushWord();
|
|
49
50
|
if (tokens.length > 0 || unsupported)
|
|
50
|
-
segments.push({ tokens, unsupported });
|
|
51
|
+
segments.push({ tokens, unsupported, terminator });
|
|
51
52
|
tokens = [];
|
|
52
53
|
unsupported = false;
|
|
53
54
|
atTokenStart = true;
|
|
@@ -90,7 +91,7 @@ export function tokenizeShellCommand(command) {
|
|
|
90
91
|
// Windows routinely contain native paths (C:\\src\\file.ts). Preserve
|
|
91
92
|
// backslashes before ordinary path characters while still honoring
|
|
92
93
|
// shell escapes and line continuations.
|
|
93
|
-
if (next === "\n" || /[\s\\'
|
|
94
|
+
if (next === "\n" || /[\s\\'";$|&<>]/.test(next ?? "")) {
|
|
94
95
|
escaped = true;
|
|
95
96
|
}
|
|
96
97
|
else {
|
|
@@ -121,13 +122,18 @@ export function tokenizeShellCommand(command) {
|
|
|
121
122
|
}
|
|
122
123
|
if (ch === ";" || ch === "\n" || ch === "|" || ch === "&") {
|
|
123
124
|
flushWord();
|
|
125
|
+
let terminator;
|
|
124
126
|
if (ch === "|" && next === "|")
|
|
125
127
|
i++;
|
|
126
128
|
else if (ch === "&" && next === "&")
|
|
127
129
|
i++;
|
|
128
|
-
else if (ch === "|"
|
|
130
|
+
else if (ch === "|") {
|
|
129
131
|
unsupported = true;
|
|
130
|
-
|
|
132
|
+
terminator = "pipe";
|
|
133
|
+
}
|
|
134
|
+
else if (ch === "&")
|
|
135
|
+
unsupported = true;
|
|
136
|
+
flushSegment(terminator);
|
|
131
137
|
continue;
|
|
132
138
|
}
|
|
133
139
|
if (ch === "<" || ch === ">") {
|
|
@@ -162,6 +168,37 @@ function parseCountFlag(token) {
|
|
|
162
168
|
function commandSegments(command) {
|
|
163
169
|
return tokenizeShellCommand(command).map((segment) => segment.tokens);
|
|
164
170
|
}
|
|
171
|
+
/** Unwrap the common agent-authored `npx <tool>` launcher form. */
|
|
172
|
+
function unwrapCommand(tokens) {
|
|
173
|
+
let verb = path.basename(tokens[0] ?? "");
|
|
174
|
+
let args = tokens.slice(1);
|
|
175
|
+
if (verb === "npx") {
|
|
176
|
+
while (args[0] === "-y" || args[0] === "--yes")
|
|
177
|
+
args = args.slice(1);
|
|
178
|
+
verb = path.basename(args[0] ?? "");
|
|
179
|
+
args = args.slice(1);
|
|
180
|
+
}
|
|
181
|
+
return { verb, args };
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Return source-file operands while excluding values consumed by known options.
|
|
185
|
+
* This stays deliberately narrower than a general CLI parser: only formatter
|
|
186
|
+
* options whose values can themselves look like source files belong here.
|
|
187
|
+
*/
|
|
188
|
+
function formatterFileArgs(args, start = 0, valueOptions = new Set()) {
|
|
189
|
+
const files = [];
|
|
190
|
+
for (let i = start; i < args.length; i += 1) {
|
|
191
|
+
const arg = args[i] ?? "";
|
|
192
|
+
if (valueOptions.has(arg)) {
|
|
193
|
+
i += 1;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (arg === "--" || arg.startsWith("-"))
|
|
197
|
+
continue;
|
|
198
|
+
files.push(arg);
|
|
199
|
+
}
|
|
200
|
+
return files;
|
|
201
|
+
}
|
|
165
202
|
/** Find redirect targets without treating quoted `>` characters as syntax. */
|
|
166
203
|
function extractRedirectTargets(command) {
|
|
167
204
|
const targets = [];
|
|
@@ -344,6 +381,80 @@ const GREP_OPTIONS_WITH_VALUE = new Set([
|
|
|
344
381
|
"--before-context",
|
|
345
382
|
"--context",
|
|
346
383
|
]);
|
|
384
|
+
function parseContextValue(inline, next) {
|
|
385
|
+
const raw = inline !== "" ? inline : next;
|
|
386
|
+
if (raw === undefined)
|
|
387
|
+
return undefined;
|
|
388
|
+
const n = Number.parseInt(raw, 10);
|
|
389
|
+
if (!Number.isFinite(n) || n < 0)
|
|
390
|
+
return undefined;
|
|
391
|
+
// Bound the credit: a huge -C would credit a whole file from one hit.
|
|
392
|
+
return Math.min(n, 100);
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Read the context lines a `grep` invocation actually PRINTS around each hit
|
|
396
|
+
* (#1904 item 2). Only these lines were delivered to the model, so only these
|
|
397
|
+
* may be credited as read. Handles `-A N`, `-A3`, clustered short flags such as
|
|
398
|
+
* `-rnA3`, `--after-context=N`, the `-B`/`--before-context` mirror, `-C`
|
|
399
|
+
* (both sides), and the bare `-NUM` form.
|
|
400
|
+
*/
|
|
401
|
+
export function parseGrepContextLines(args) {
|
|
402
|
+
let before = 0;
|
|
403
|
+
let after = 0;
|
|
404
|
+
for (let i = 0; i < args.length; i++) {
|
|
405
|
+
const token = stripQuotes(args[i]);
|
|
406
|
+
if (token === "--")
|
|
407
|
+
break;
|
|
408
|
+
const nextToken = args[i + 1] === undefined ? undefined : stripQuotes(args[i + 1]);
|
|
409
|
+
const long = /^--(after|before)-context(?:=(\d+))?$/.exec(token);
|
|
410
|
+
if (long) {
|
|
411
|
+
const value = parseContextValue(long[2] ?? "", nextToken);
|
|
412
|
+
if (value === undefined)
|
|
413
|
+
continue;
|
|
414
|
+
if (long[1] === "after")
|
|
415
|
+
after = Math.max(after, value);
|
|
416
|
+
else
|
|
417
|
+
before = Math.max(before, value);
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
const longBoth = /^--context(?:=(\d+))?$/.exec(token);
|
|
421
|
+
if (longBoth) {
|
|
422
|
+
const value = parseContextValue(longBoth[1] ?? "", nextToken);
|
|
423
|
+
if (value === undefined)
|
|
424
|
+
continue;
|
|
425
|
+
before = Math.max(before, value);
|
|
426
|
+
after = Math.max(after, value);
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
// Bare numeric context form: `grep -3 pattern file`.
|
|
430
|
+
const bare = /^-(\d+)$/.exec(token);
|
|
431
|
+
if (bare) {
|
|
432
|
+
const value = parseContextValue(bare[1], undefined);
|
|
433
|
+
if (value === undefined)
|
|
434
|
+
continue;
|
|
435
|
+
before = Math.max(before, value);
|
|
436
|
+
after = Math.max(after, value);
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
// Short flags, possibly clustered: the context flag must be LAST in the
|
|
440
|
+
// cluster because it consumes the remaining characters as its value.
|
|
441
|
+
const short = /^-[A-Za-z]*([ABC])(\d*)$/.exec(token);
|
|
442
|
+
if (!short)
|
|
443
|
+
continue;
|
|
444
|
+
const value = parseContextValue(short[2], nextToken);
|
|
445
|
+
if (value === undefined)
|
|
446
|
+
continue;
|
|
447
|
+
if (short[1] === "A")
|
|
448
|
+
after = Math.max(after, value);
|
|
449
|
+
else if (short[1] === "B")
|
|
450
|
+
before = Math.max(before, value);
|
|
451
|
+
else {
|
|
452
|
+
before = Math.max(before, value);
|
|
453
|
+
after = Math.max(after, value);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return { before, after };
|
|
457
|
+
}
|
|
347
458
|
function extractGrepSearchFiles(args, cwd) {
|
|
348
459
|
const files = [];
|
|
349
460
|
let patternSeen = false;
|
|
@@ -408,10 +519,99 @@ function parseGrepLineWithoutFile(line, file) {
|
|
|
408
519
|
return undefined;
|
|
409
520
|
return { file, startLine: lineNumber, endLine: lineNumber };
|
|
410
521
|
}
|
|
522
|
+
/**
|
|
523
|
+
* Verbs that DROP printed lines from a piped stream (#1908, review F4): a
|
|
524
|
+
* hit that survives such a tail cannot be trusted to still carry the
|
|
525
|
+
* -A/-B/-C context grep's flags declared, because that context may have
|
|
526
|
+
* been among the dropped lines. `head`/`tail` drop by position (even bare,
|
|
527
|
+
* since both default to a 10-line window); `sed` with a `q` command drops
|
|
528
|
+
* everything after the addressed line; `uniq` drops adjacent duplicate
|
|
529
|
+
* lines, which can include a context line that happens to repeat.
|
|
530
|
+
*
|
|
531
|
+
* The criterion is DROPPING, not "changes grep's output shape" — a review
|
|
532
|
+
* finding (#1913 F4) caught this file previously conflating the two:
|
|
533
|
+
* - `head`/`tail`/`sed q`/`uniq` downstream of a matching grep → some
|
|
534
|
+
* printed lines are genuinely gone, so credit falls back to
|
|
535
|
+
* match-line-only (the same conservative default already used for
|
|
536
|
+
* unparseable commands) rather than trusting the declared flags.
|
|
537
|
+
* - `sort` and other REORDER-ONLY filters keep every line grep printed,
|
|
538
|
+
* just in a different order — a surviving `file:line:` match still
|
|
539
|
+
* carries the full context grep actually printed, so no special case is
|
|
540
|
+
* needed (proven by the "still credits full context through a
|
|
541
|
+
* non-truncating pipe tail" test below).
|
|
542
|
+
* - `wc` and similar filters that REPLACE grep's output entirely (with a
|
|
543
|
+
* count, say) leave no `file:line:`-shaped line at all, so
|
|
544
|
+
* `parseGrepOutputSearchReads` already finds zero matches in the real
|
|
545
|
+
* captured stdout. Also no special case needed, but for the OPPOSITE
|
|
546
|
+
* reason from `sort`: nothing survives to credit, rather than
|
|
547
|
+
* everything surviving intact.
|
|
548
|
+
* - A pass-through filter (`cat`, `grep -v`, ...) between grep and a
|
|
549
|
+
* dropping tail still counts: the walk below follows the whole pipe
|
|
550
|
+
* chain, not just the immediate next segment.
|
|
551
|
+
* - Out of scope, deliberately: `sed -n 'Np'` range-address filtering
|
|
552
|
+
* without `q`, and `sort -u`, also drop lines but aren't in #1908's
|
|
553
|
+
* stated scope — revisit if they recur in the field. Also out of
|
|
554
|
+
* scope: a truncating tail hidden inside a subshell/brace-group
|
|
555
|
+
* (`( ... )`, `{ ...; }`) or reimplemented in `awk`/`perl` — the shared
|
|
556
|
+
* tokenizer this file uses doesn't parse inside those, matching its
|
|
557
|
+
* documented "small conservative shell lexer" scope.
|
|
558
|
+
*/
|
|
559
|
+
const LINE_DROPPING_TAIL_VERBS = new Set(["head", "tail", "uniq"]);
|
|
560
|
+
function isSedQuitCommand(args) {
|
|
561
|
+
for (const arg of args) {
|
|
562
|
+
const token = stripQuotes(arg);
|
|
563
|
+
if (token.startsWith("-"))
|
|
564
|
+
continue;
|
|
565
|
+
// A `q` command anchored at the start of the script or after a `;`/
|
|
566
|
+
// newline separator, optionally preceded by a numeric/`$` address
|
|
567
|
+
// (`q`, `1q`, `$q`, `1,3p;q`). Deliberately not a full sed parser.
|
|
568
|
+
if (/(^|[;\n])\s*(\d+|\$)?\s*q\b/.test(token))
|
|
569
|
+
return true;
|
|
570
|
+
}
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
function isLineDroppingTailCommand(tokens) {
|
|
574
|
+
const verb = path.basename(stripQuotes(tokens[0] ?? ""));
|
|
575
|
+
if (LINE_DROPPING_TAIL_VERBS.has(verb))
|
|
576
|
+
return true;
|
|
577
|
+
if (verb === "sed")
|
|
578
|
+
return isSedQuitCommand(tokens.slice(1));
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* True when the grep segment starting at `index` feeds — directly or through
|
|
583
|
+
* a chain of pipes — into a line-dropping tail (#1908). Only an unquoted `|`
|
|
584
|
+
* counts: a `;`/`&&`/`&`-separated command downstream never receives grep's
|
|
585
|
+
* stdout, so it must not downgrade credit (#1913 F2 — this was previously
|
|
586
|
+
* unverified by any test that could tell a real pipe from mere adjacency).
|
|
587
|
+
*/
|
|
588
|
+
function grepPipesIntoTruncatingTail(segments, index) {
|
|
589
|
+
if (segments[index]?.terminator !== "pipe")
|
|
590
|
+
return false;
|
|
591
|
+
for (let j = index + 1; j < segments.length; j++) {
|
|
592
|
+
if (isLineDroppingTailCommand(segments[j].tokens))
|
|
593
|
+
return true;
|
|
594
|
+
if (segments[j].terminator !== "pipe")
|
|
595
|
+
return false;
|
|
596
|
+
}
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
411
599
|
function collectGrepCommandFiles(command, cwd) {
|
|
412
600
|
const files = new Set();
|
|
413
601
|
let hasLineNumberGrep = false;
|
|
414
|
-
|
|
602
|
+
// A command can chain several greps whose output all reaches the model, but
|
|
603
|
+
// the parsed hits carry no segment identity. Credit the SMALLEST context any
|
|
604
|
+
// contributing grep printed, so no line is credited that a hit from the
|
|
605
|
+
// narrowest grep never delivered.
|
|
606
|
+
let context;
|
|
607
|
+
// #1908: if ANY contributing grep pipes into a truncating tail, fall back
|
|
608
|
+
// to match-line-only credit for the whole command — the parsed hits carry
|
|
609
|
+
// no segment identity, so a per-segment override isn't representable in
|
|
610
|
+
// the aggregate `context` this function returns.
|
|
611
|
+
let truncated = false;
|
|
612
|
+
const segments = tokenizeShellCommand(command);
|
|
613
|
+
for (let i = 0; i < segments.length; i++) {
|
|
614
|
+
const tokens = segments[i].tokens;
|
|
415
615
|
const verb = path.basename(stripQuotes(tokens[0] ?? ""));
|
|
416
616
|
if (verb !== "grep" && verb !== "egrep" && verb !== "fgrep")
|
|
417
617
|
continue;
|
|
@@ -419,10 +619,23 @@ function collectGrepCommandFiles(command, cwd) {
|
|
|
419
619
|
if (!grepHasLineNumbers(args))
|
|
420
620
|
continue;
|
|
421
621
|
hasLineNumberGrep = true;
|
|
622
|
+
const segmentContext = parseGrepContextLines(args);
|
|
623
|
+
context = context
|
|
624
|
+
? {
|
|
625
|
+
before: Math.min(context.before, segmentContext.before),
|
|
626
|
+
after: Math.min(context.after, segmentContext.after),
|
|
627
|
+
}
|
|
628
|
+
: segmentContext;
|
|
422
629
|
for (const file of extractGrepSearchFiles(args, cwd))
|
|
423
630
|
files.add(file);
|
|
631
|
+
if (grepPipesIntoTruncatingTail(segments, i))
|
|
632
|
+
truncated = true;
|
|
424
633
|
}
|
|
425
|
-
return {
|
|
634
|
+
return {
|
|
635
|
+
hasLineNumberGrep,
|
|
636
|
+
files,
|
|
637
|
+
context: truncated ? { before: 0, after: 0 } : (context ?? { before: 0, after: 0 }),
|
|
638
|
+
};
|
|
426
639
|
}
|
|
427
640
|
function dedupePushSearchRead(out, seen, loc) {
|
|
428
641
|
if (!loc)
|
|
@@ -452,17 +665,25 @@ function parseGrepOutputSearchReads(output, cwd, singleFile) {
|
|
|
452
665
|
* so the latter is only accepted when the command names exactly one source file.
|
|
453
666
|
*/
|
|
454
667
|
export function extractGrepSearchReadsFromOutput(command, cwd, output) {
|
|
455
|
-
const { hasLineNumberGrep, files } = collectGrepCommandFiles(command, cwd);
|
|
668
|
+
const { hasLineNumberGrep, files, context } = collectGrepCommandFiles(command, cwd);
|
|
456
669
|
if (!hasLineNumberGrep)
|
|
457
670
|
return [];
|
|
458
671
|
const singleFile = files.size === 1 ? [...files][0] : undefined;
|
|
459
|
-
|
|
672
|
+
const locations = parseGrepOutputSearchReads(output, cwd, singleFile);
|
|
673
|
+
// Only the printed context lines were delivered to the model, so only they
|
|
674
|
+
// are credited. A bare grep credits its match line alone (#1904 item 2).
|
|
675
|
+
for (const loc of locations) {
|
|
676
|
+
loc.contextBefore = context.before;
|
|
677
|
+
loc.contextAfter = context.after;
|
|
678
|
+
}
|
|
679
|
+
return locations;
|
|
460
680
|
}
|
|
461
681
|
/**
|
|
462
682
|
* Extract files a bash command WROTE/created, so the read-guard can treat them
|
|
463
683
|
* as authored by the agent (mirrors the Write tool). Handles:
|
|
464
684
|
* redirects: `> FILE`, `>> FILE`, `N> FILE`, `&> FILE` (with or without space)
|
|
465
|
-
* tee [-a] FILE...,
|
|
685
|
+
* tee [-a] FILE..., sed -i ... FILE, cp/mv/install ... DEST, touch FILE...,
|
|
686
|
+
* and common in-place formatter/fixer invocations with explicit file targets.
|
|
466
687
|
*
|
|
467
688
|
* Returns absolute paths. The file need not exist yet (it may be created) —
|
|
468
689
|
* existence is confirmed later by recordWritten at tool_result time.
|
|
@@ -490,8 +711,7 @@ export function extractWrittenPathsFromCommand(command, cwd) {
|
|
|
490
711
|
for (const tokens of commandSegments(command)) {
|
|
491
712
|
if (tokens.length === 0)
|
|
492
713
|
continue;
|
|
493
|
-
const verb =
|
|
494
|
-
const args = tokens.slice(1);
|
|
714
|
+
const { verb, args } = unwrapCommand(tokens);
|
|
495
715
|
if (verb === "tee" || verb === "touch") {
|
|
496
716
|
for (const a of args)
|
|
497
717
|
if (!a.startsWith("-"))
|
|
@@ -502,10 +722,89 @@ export function extractWrittenPathsFromCommand(command, cwd) {
|
|
|
502
722
|
add(a);
|
|
503
723
|
}
|
|
504
724
|
else if (verb === "cp" || verb === "mv" || verb === "install") {
|
|
725
|
+
// Known miss (#1668 review F3): the GNU `-t DEST`/`--target-directory`
|
|
726
|
+
// form puts the destination BEFORE the sources — this always treats
|
|
727
|
+
// the LAST non-flag argument as the destination, so `-t DEST SRC` and
|
|
728
|
+
// multi-source `-t DEST SRC1 SRC2...` are misread. Rare in agent-
|
|
729
|
+
// authored bash (source-before-dest is the common form); documented
|
|
730
|
+
// rather than fixed, since correctly parsing `-t` means recognizing it
|
|
731
|
+
// takes a value while telling it apart from every other single-letter
|
|
732
|
+
// flag this same branch already ignores.
|
|
505
733
|
const files = args.filter((a) => !a.startsWith("-"));
|
|
506
734
|
if (files.length >= 1)
|
|
507
735
|
add(files[files.length - 1]); // destination
|
|
508
736
|
}
|
|
737
|
+
else if (verb === "biome") {
|
|
738
|
+
const sub = args[0];
|
|
739
|
+
if ((sub === "format" || sub === "check") && args.includes("--write")) {
|
|
740
|
+
for (const file of formatterFileArgs(args, 1, new Set(["--config-path"])))
|
|
741
|
+
add(file);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
else if (verb === "prettier" && args.includes("--write")) {
|
|
745
|
+
for (const file of formatterFileArgs(args, 0, new Set(["--config", "--ignore-path", "--parser", "--plugin"])))
|
|
746
|
+
add(file);
|
|
747
|
+
}
|
|
748
|
+
else if (verb === "eslint" && args.includes("--fix")) {
|
|
749
|
+
for (const file of formatterFileArgs(args, 0, new Set(["--config", "--ignore-path", "--parser", "--plugin"])))
|
|
750
|
+
add(file);
|
|
751
|
+
}
|
|
752
|
+
else if (verb === "ruff") {
|
|
753
|
+
const sub = args[0];
|
|
754
|
+
if (sub === "format" || args.includes("--fix")) {
|
|
755
|
+
const start = sub === "format" || sub === "check" ? 1 : 0;
|
|
756
|
+
for (const file of formatterFileArgs(args, start, new Set(["--config"])))
|
|
757
|
+
add(file);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
else if (verb === "gofmt" && args.includes("-w")) {
|
|
761
|
+
for (const file of formatterFileArgs(args))
|
|
762
|
+
add(file);
|
|
763
|
+
}
|
|
764
|
+
else if (verb === "cargo" && args[0] === "fmt") {
|
|
765
|
+
// Bare cargo fmt is project-scoped and cannot be enumerated without a
|
|
766
|
+
// filesystem walk. Only explicit rustfmt operands after `--` are safe.
|
|
767
|
+
const separator = args.indexOf("--");
|
|
768
|
+
if (separator >= 0) {
|
|
769
|
+
for (const file of formatterFileArgs(args, separator + 1, new Set(["--edition", "--config-path"])))
|
|
770
|
+
add(file);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
else if (verb === "rustfmt") {
|
|
774
|
+
for (const file of formatterFileArgs(args, 0, new Set(["--edition", "--config-path"])))
|
|
775
|
+
add(file);
|
|
776
|
+
}
|
|
777
|
+
else if (verb === "black") {
|
|
778
|
+
for (const file of formatterFileArgs(args, 0, new Set([
|
|
779
|
+
"--config",
|
|
780
|
+
"--exclude",
|
|
781
|
+
"--include",
|
|
782
|
+
"--line-length",
|
|
783
|
+
"--target-version",
|
|
784
|
+
])))
|
|
785
|
+
add(file);
|
|
786
|
+
}
|
|
787
|
+
else if (verb === "clang-format" && args.includes("-i")) {
|
|
788
|
+
for (const file of formatterFileArgs(args, 0, new Set(["--style", "--fallback-style", "--assume-filename"])))
|
|
789
|
+
add(file);
|
|
790
|
+
}
|
|
791
|
+
else if (verb === "dotnet" && args[0] === "format") {
|
|
792
|
+
// dotnet format is normally solution-scoped. `--include` is the one
|
|
793
|
+
// invocation form that provides attributable source paths.
|
|
794
|
+
for (let i = 1; i < args.length; i += 1) {
|
|
795
|
+
if (args[i] === "--include" && args[i + 1]) {
|
|
796
|
+
for (const file of (args[i + 1] ?? "").split(","))
|
|
797
|
+
add(file);
|
|
798
|
+
i += 1;
|
|
799
|
+
}
|
|
800
|
+
else if (args[i]?.startsWith("--include=")) {
|
|
801
|
+
for (const file of (args[i] ?? "")
|
|
802
|
+
.slice("--include=".length)
|
|
803
|
+
.split(","))
|
|
804
|
+
add(file);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
509
808
|
else if (verb === "git") {
|
|
510
809
|
// git ops that REWRITE working-tree files with explicit paths:
|
|
511
810
|
// git checkout [<ref>] -- <files> git restore [opts] <files>
|
|
@@ -524,6 +823,75 @@ export function extractWrittenPathsFromCommand(command, cwd) {
|
|
|
524
823
|
for (const a of fileArgs)
|
|
525
824
|
add(a);
|
|
526
825
|
}
|
|
826
|
+
else if (sub === "mv") {
|
|
827
|
+
// git mv SRC... DEST — the destination is a write, exactly like
|
|
828
|
+
// plain `mv` above (#1668 review F2). The source side is a delete,
|
|
829
|
+
// handled by extractDeletedPathsFromCommand.
|
|
830
|
+
const files = args.slice(1).filter((a) => !a.startsWith("-"));
|
|
831
|
+
if (files.length >= 1)
|
|
832
|
+
add(files[files.length - 1]);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return Array.from(out);
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Extract files a bash command likely DELETED, for the type-3 (Deleted)
|
|
840
|
+
* watched-files gap (#1668): `rm FILE...`, `git rm FILE...`, and the SOURCE
|
|
841
|
+
* side of `mv SRC DEST` / `git mv SRC DEST` (the destination is a write,
|
|
842
|
+
* covered by `extractWrittenPathsFromCommand` — including for `git mv`,
|
|
843
|
+
* #1668 review F2).
|
|
844
|
+
*
|
|
845
|
+
* Deliberately narrow: only commands naming an explicit file target are
|
|
846
|
+
* handled. A bare directory op (`rm -rf dir/`, `git clean`) is skipped —
|
|
847
|
+
* resolving what vanished inside a directory would mean listing it before
|
|
848
|
+
* and after, the "stat the world" cost this module exists to avoid. The
|
|
849
|
+
* caller confirms each candidate by checking it no longer exists on disk;
|
|
850
|
+
* this function only proposes what the command named.
|
|
851
|
+
*/
|
|
852
|
+
export function extractDeletedPathsFromCommand(command, cwd) {
|
|
853
|
+
const out = new Set();
|
|
854
|
+
const add = (token) => {
|
|
855
|
+
const abs = resolveCandidate(token, cwd);
|
|
856
|
+
if (abs)
|
|
857
|
+
out.add(abs);
|
|
858
|
+
};
|
|
859
|
+
// mv SRC... DEST / git mv SRC... DEST — every argument except the last is
|
|
860
|
+
// a source that vanishes from its original path once the move lands.
|
|
861
|
+
// Known miss (#1668 review F3): the same `-t DEST`/`--target-directory`
|
|
862
|
+
// form documented in `extractWrittenPathsFromCommand` misreads here too —
|
|
863
|
+
// this assumes source-before-destination order.
|
|
864
|
+
const addMoveSources = (args) => {
|
|
865
|
+
const files = args.filter((a) => !a.startsWith("-"));
|
|
866
|
+
if (files.length >= 2) {
|
|
867
|
+
for (const src of files.slice(0, -1))
|
|
868
|
+
add(src);
|
|
869
|
+
}
|
|
870
|
+
};
|
|
871
|
+
for (const tokens of commandSegments(command)) {
|
|
872
|
+
if (tokens.length === 0)
|
|
873
|
+
continue;
|
|
874
|
+
const verb = path.basename(tokens[0] ?? "");
|
|
875
|
+
const args = tokens.slice(1);
|
|
876
|
+
if (verb === "rm") {
|
|
877
|
+
for (const a of args)
|
|
878
|
+
if (!a.startsWith("-"))
|
|
879
|
+
add(a);
|
|
880
|
+
}
|
|
881
|
+
else if (verb === "git" && args[0] === "rm") {
|
|
882
|
+
const rmArgs = args.slice(1);
|
|
883
|
+
const dashDash = rmArgs.indexOf("--");
|
|
884
|
+
const fileArgs = dashDash >= 0
|
|
885
|
+
? rmArgs.slice(dashDash + 1)
|
|
886
|
+
: rmArgs.filter((a) => !a.startsWith("-"));
|
|
887
|
+
for (const a of fileArgs)
|
|
888
|
+
add(a);
|
|
889
|
+
}
|
|
890
|
+
else if (verb === "git" && args[0] === "mv") {
|
|
891
|
+
addMoveSources(args.slice(1));
|
|
892
|
+
}
|
|
893
|
+
else if (verb === "mv") {
|
|
894
|
+
addMoveSources(args);
|
|
527
895
|
}
|
|
528
896
|
}
|
|
529
897
|
return Array.from(out);
|
|
@@ -14,9 +14,10 @@ import { isFileKind } from "./file-kinds.js";
|
|
|
14
14
|
import { getGlobalPiLensDir } from "./file-utils.js";
|
|
15
15
|
import { findGlobalBinary } from "./package-manager.js";
|
|
16
16
|
import { safeSpawnAsync } from "./safe-spawn.js";
|
|
17
|
+
import { createSingleFlight } from "./single-flight.js";
|
|
17
18
|
import { biomeConfigArgs } from "./tool-policy.js";
|
|
18
19
|
import { resolveManagedToolClient, } from "./dispatch/runners/utils/runner-helpers.js";
|
|
19
|
-
import { classifyProbeFailure, createAvailabilityLatch, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
|
|
20
|
+
import { classifyProbeFailure, describeInstallAttempt, createAvailabilityLatch, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
|
|
20
21
|
// --- Client ---
|
|
21
22
|
const PROBE_TIMEOUT_MS = 10_000;
|
|
22
23
|
export class BiomeClient {
|
|
@@ -39,6 +40,8 @@ export class BiomeClient {
|
|
|
39
40
|
*/
|
|
40
41
|
lastProbeElapsedMs = 0;
|
|
41
42
|
lastProbeHostStallMs = 0;
|
|
43
|
+
/** Raw facts from the last probe, carried into the decision record (#1500). */
|
|
44
|
+
lastProbeEvidence;
|
|
42
45
|
// Per-cwd cache of the resolved biome binary. Keying by cwd matters in
|
|
43
46
|
// monorepos where different sub-packages each ship their own biome
|
|
44
47
|
// installation; sharing one slot across the whole client would cause
|
|
@@ -48,7 +51,11 @@ export class BiomeClient {
|
|
|
48
51
|
// (lives under ~/.pi-lens/tools), so it's stored separately from the
|
|
49
52
|
// per-cwd cache and used as a final fallback before npx.
|
|
50
53
|
autoInstalledBinaryPath = null;
|
|
51
|
-
|
|
54
|
+
/**
|
|
55
|
+
* At-most-one probe/auto-install in flight, via the shared primitive
|
|
56
|
+
* (#1753). One instance owns one question, so the key is a constant.
|
|
57
|
+
*/
|
|
58
|
+
ensureFlight = createSingleFlight();
|
|
52
59
|
log;
|
|
53
60
|
constructor(verbose = false) {
|
|
54
61
|
this.log = verbose
|
|
@@ -70,13 +77,14 @@ export class BiomeClient {
|
|
|
70
77
|
const cached = this.localBinaryByCwd.get(resolveCwd);
|
|
71
78
|
if (cached)
|
|
72
79
|
return { cmd: cached, args: [] };
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
+
// Walk up from cwd looking for node_modules/.bin/biome BEFORE trusting
|
|
81
|
+
// `autoInstalledBinaryPath` (#1731). That field is set once, the first
|
|
82
|
+
// time `ensureAvailable()` auto-installs for ANY cwd this session, and
|
|
83
|
+
// every later call for every OTHER cwd short-circuited on it — so a
|
|
84
|
+
// project that ships its own biome never won once the session's first
|
|
85
|
+
// managed install had already happened. Project-local-first (discipline
|
|
86
|
+
// B, #1721) means the project's pinned version and config resolution
|
|
87
|
+
// always outrank a managed copy, autoinstalled or not.
|
|
80
88
|
// On Windows prefer .cmd (native batch) over the sh wrapper — 2x faster.
|
|
81
89
|
const isWin = process.platform === "win32";
|
|
82
90
|
const piLensBin = path.join(getGlobalPiLensDir(), "tools", "node_modules", ".bin");
|
|
@@ -84,20 +92,29 @@ export class BiomeClient {
|
|
|
84
92
|
? [
|
|
85
93
|
path.join(resolveCwd, "node_modules", ".bin", "biome.cmd"),
|
|
86
94
|
path.join(resolveCwd, "node_modules", ".bin", "biome"),
|
|
87
|
-
path.join(piLensBin, "biome.cmd"),
|
|
88
|
-
path.join(piLensBin, "biome"),
|
|
89
95
|
]
|
|
90
|
-
: [
|
|
91
|
-
path.join(resolveCwd, "node_modules", ".bin", "biome"),
|
|
92
|
-
path.join(resolveCwd, "node_modules", ".bin", "biome.cmd"),
|
|
93
|
-
path.join(piLensBin, "biome"),
|
|
94
|
-
];
|
|
96
|
+
: [path.join(resolveCwd, "node_modules", ".bin", "biome")];
|
|
95
97
|
for (const p of candidates) {
|
|
96
98
|
if (fs.existsSync(p)) {
|
|
97
99
|
this.localBinaryByCwd.set(resolveCwd, p);
|
|
98
100
|
return { cmd: p, args: [] };
|
|
99
101
|
}
|
|
100
102
|
}
|
|
103
|
+
if (this.autoInstalledBinaryPath) {
|
|
104
|
+
return { cmd: this.autoInstalledBinaryPath, args: [] };
|
|
105
|
+
}
|
|
106
|
+
// Also check ~/.pi-lens/tools (where ensureTool("biome") auto-installs),
|
|
107
|
+
// so we avoid the ~1.5s `npx @biomejs/biome --version` fallback when the
|
|
108
|
+
// tool is already installed but not in the project's node_modules.
|
|
109
|
+
const managedCandidates = isWin
|
|
110
|
+
? [path.join(piLensBin, "biome.cmd"), path.join(piLensBin, "biome")]
|
|
111
|
+
: [path.join(piLensBin, "biome")];
|
|
112
|
+
for (const p of managedCandidates) {
|
|
113
|
+
if (fs.existsSync(p)) {
|
|
114
|
+
this.localBinaryByCwd.set(resolveCwd, p);
|
|
115
|
+
return { cmd: p, args: [] };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
101
118
|
// Any package manager's global bin dir (npm/pnpm/yarn/bun) before npx —
|
|
102
119
|
// catches `pnpm add -g @biomejs/biome` installs that PATH misses (#375).
|
|
103
120
|
const global = await findGlobalBinary("biome");
|
|
@@ -116,10 +133,11 @@ export class BiomeClient {
|
|
|
116
133
|
* Ensure Biome is available, auto-installing if necessary.
|
|
117
134
|
* Prefer this over isAvailable() for auto-install behavior.
|
|
118
135
|
*
|
|
119
|
-
* Re-entrancy safe: concurrent first-time callers share
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
136
|
+
* Re-entrancy safe: concurrent first-time callers share one flight, so
|
|
137
|
+
* probing/auto-install isn't duplicated. The share and the clear-in-finally
|
|
138
|
+
* belong to `singleFlight` (#1753); this method owns only the latch
|
|
139
|
+
* short-circuit above it. #1690 exists because the hand-rolled clear here
|
|
140
|
+
* was never exercised by a test.
|
|
123
141
|
*
|
|
124
142
|
* The memo returns `null` when the last verdict was transient and its
|
|
125
143
|
* cooldown expired, which re-enters the probe: "biome is not installed" is a
|
|
@@ -129,15 +147,7 @@ export class BiomeClient {
|
|
|
129
147
|
const memo = this.availabilityLatch.read();
|
|
130
148
|
if (memo !== null)
|
|
131
149
|
return memo;
|
|
132
|
-
|
|
133
|
-
return this.ensureInFlight;
|
|
134
|
-
this.ensureInFlight = this.doEnsureAvailable();
|
|
135
|
-
try {
|
|
136
|
-
return await this.ensureInFlight;
|
|
137
|
-
}
|
|
138
|
-
finally {
|
|
139
|
-
this.ensureInFlight = null;
|
|
140
|
-
}
|
|
150
|
+
return this.ensureFlight.run("biome", () => this.doEnsureAvailable());
|
|
141
151
|
}
|
|
142
152
|
/**
|
|
143
153
|
* Probe `biome --version` and classify the failure through the shared
|
|
@@ -177,7 +187,7 @@ export class BiomeClient {
|
|
|
177
187
|
// `unclassifiedFailureOutcome: "missing"` preserves the pre-#1476
|
|
178
188
|
// meaning of a plain non-zero exit (npx reporting no biome package):
|
|
179
189
|
// still "missing", still installable. Only the timeout/abort arm changes.
|
|
180
|
-
const { outcome, cause } = classifyProbeFailure(result, {
|
|
190
|
+
const { outcome, cause, evidence } = classifyProbeFailure(result, {
|
|
181
191
|
hostStallMs,
|
|
182
192
|
unclassifiedFailureOutcome: "missing",
|
|
183
193
|
});
|
|
@@ -187,6 +197,7 @@ export class BiomeClient {
|
|
|
187
197
|
this.lastProbeCause = cause;
|
|
188
198
|
this.lastProbeElapsedMs = elapsedMs;
|
|
189
199
|
this.lastProbeHostStallMs = hostStallMs;
|
|
200
|
+
this.lastProbeEvidence = evidence;
|
|
190
201
|
// The record is emitted by `doEnsureAvailable`, once the latch has said how
|
|
191
202
|
// long this verdict holds.
|
|
192
203
|
return { outcome: failureOutcome };
|
|
@@ -224,6 +235,19 @@ export class BiomeClient {
|
|
|
224
235
|
// session exactly as before.
|
|
225
236
|
const cause = this.lastProbeCause ?? "not-found";
|
|
226
237
|
const retryAfterMs = this.availabilityLatch.noteUnavailable(resolved.outcome, cause);
|
|
238
|
+
// `missing` IS the install-failure arm (#1500 review). `resolveManagedToolClient`
|
|
239
|
+
// only reaches the installer when the probe said `missing`, so every `missing`
|
|
240
|
+
// verdict here has already been through it — declined, suppressed, or tried
|
|
241
|
+
// and failed, which is what `describeInstallAttempt` separates. The earlier
|
|
242
|
+
// marker sat on the `non-installable` arm instead, and that arm cannot happen
|
|
243
|
+
// for biome at all: its `acceptInstalled` always accepts. So the one row that
|
|
244
|
+
// needed the fact was the one shipping without it.
|
|
245
|
+
let installEvidence;
|
|
246
|
+
if (resolved.outcome === "missing") {
|
|
247
|
+
const { getInstallAttempt } = await import("./installer/index.js");
|
|
248
|
+
installEvidence = describeInstallAttempt(getInstallAttempt("biome"));
|
|
249
|
+
}
|
|
250
|
+
const evidence = { ...this.lastProbeEvidence, ...installEvidence };
|
|
227
251
|
logAvailabilityDecision({
|
|
228
252
|
tool: "biome",
|
|
229
253
|
verdict: "unavailable",
|
|
@@ -234,6 +258,13 @@ export class BiomeClient {
|
|
|
234
258
|
hostStallMs: this.lastProbeHostStallMs,
|
|
235
259
|
...(retryAfterMs > 0 && { retryAfterMs }),
|
|
236
260
|
budgetMs: PROBE_TIMEOUT_MS,
|
|
261
|
+
// Per arm: a probe-derived verdict says `probe`, while a verdict asserted
|
|
262
|
+
// by the install seam — or a cause that fell back because no probe ran —
|
|
263
|
+
// says `caller`.
|
|
264
|
+
classifiedBy: this.lastProbeCause === null || resolved.outcome === "non-installable"
|
|
265
|
+
? "caller"
|
|
266
|
+
: "probe",
|
|
267
|
+
...(Object.keys(evidence).length > 0 && { evidence }),
|
|
237
268
|
});
|
|
238
269
|
if (resolved.outcome === "transient") {
|
|
239
270
|
this.log("biome availability probe timed out; will retry (not installing)");
|