@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
|
@@ -60,13 +60,24 @@ export function unrefChildAndPipes(child) {
|
|
|
60
60
|
* here already has).
|
|
61
61
|
*/
|
|
62
62
|
export function spawnCollectStdout(command, args, options) {
|
|
63
|
+
return spawnCollectStdoutResult(command, args, options).then((result) => result.stdout);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* `spawnCollectStdout` plus the reason the output is what it is. Same spawn,
|
|
67
|
+
* unref, collect, settle plumbing; the only additions are an optional hard
|
|
68
|
+
* timeout and a status discriminator. Never rejects.
|
|
69
|
+
*/
|
|
70
|
+
export function spawnCollectStdoutResult(command, args, options, collectOptions = {}) {
|
|
63
71
|
return new Promise((resolve) => {
|
|
64
72
|
let settled = false;
|
|
65
|
-
|
|
73
|
+
let timer;
|
|
74
|
+
const settle = (result) => {
|
|
66
75
|
if (settled)
|
|
67
76
|
return;
|
|
68
77
|
settled = true;
|
|
69
|
-
|
|
78
|
+
if (timer)
|
|
79
|
+
clearTimeout(timer);
|
|
80
|
+
resolve(result);
|
|
70
81
|
};
|
|
71
82
|
try {
|
|
72
83
|
const child = nodeSpawn(command, args, options);
|
|
@@ -75,11 +86,48 @@ export function spawnCollectStdout(command, args, options) {
|
|
|
75
86
|
child.stdout?.on("data", (chunk) => {
|
|
76
87
|
out += chunk.toString();
|
|
77
88
|
});
|
|
78
|
-
child.once("error", () => settle(""));
|
|
79
|
-
child.once("close", () => settle(
|
|
89
|
+
child.once("error", (error) => settle({ stdout: "", status: "spawn-error", error }));
|
|
90
|
+
child.once("close", (code, signal) => settle(code === 0 && signal === null
|
|
91
|
+
? { stdout: out, status: "ok" }
|
|
92
|
+
: {
|
|
93
|
+
stdout: "",
|
|
94
|
+
status: "exit-error",
|
|
95
|
+
exitCode: code,
|
|
96
|
+
exitSignal: signal,
|
|
97
|
+
}));
|
|
98
|
+
const timeoutMs = collectOptions.timeoutMs;
|
|
99
|
+
if (typeof timeoutMs === "number" && timeoutMs > 0) {
|
|
100
|
+
timer = setTimeout(() => {
|
|
101
|
+
// Settle only AFTER the child's fate is known. Killing and
|
|
102
|
+
// resolving in the same tick bounds the caller and abandons the
|
|
103
|
+
// child, which is the leak this timeout exists to prevent.
|
|
104
|
+
void terminateTimedOutChild(child, collectOptions.onTimeout).then((timeoutKill) => settle({ stdout: out, status: "timeout", timeoutKill }));
|
|
105
|
+
}, timeoutMs);
|
|
106
|
+
timer.unref();
|
|
107
|
+
}
|
|
80
108
|
}
|
|
81
|
-
catch {
|
|
82
|
-
settle("");
|
|
109
|
+
catch (error) {
|
|
110
|
+
settle({ stdout: "", status: "spawn-error", error });
|
|
83
111
|
}
|
|
84
112
|
});
|
|
85
113
|
}
|
|
114
|
+
/** Run the caller's termination handler, or the bare-signal default, and
|
|
115
|
+
* never let either throw out of the timeout path. */
|
|
116
|
+
async function terminateTimedOutChild(child, onTimeout) {
|
|
117
|
+
if (onTimeout) {
|
|
118
|
+
try {
|
|
119
|
+
return await onTimeout(child);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// A handler that threw did not verify anything.
|
|
123
|
+
return "unverified";
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
child.kill();
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
// best-effort — the child may already be gone
|
|
131
|
+
}
|
|
132
|
+
return "unverified";
|
|
133
|
+
}
|
|
@@ -1,29 +1,47 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
1
|
import * as fs from "node:fs";
|
|
3
2
|
import * as path from "node:path";
|
|
4
3
|
import { toRunnerDisplayPath } from "./dispatch/runner-context.js";
|
|
5
4
|
import { getProjectDataDir } from "./file-utils.js";
|
|
5
|
+
import { normalizeMessage, stableFindingId } from "./finding-identity.js";
|
|
6
6
|
import { normalizeMapKey } from "./path-utils.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Report-budget precedence (#1777). A hint is an opinion; a warning is a
|
|
9
|
+
* finding. When the report is capped, warnings must be spent first, or an
|
|
10
|
+
* alphabetically-early file's hints can push every real warning out of the
|
|
11
|
+
* agent's view — the exact "hints dominate the stream" failure the hint tier
|
|
12
|
+
* exists to prevent.
|
|
13
|
+
*/
|
|
14
|
+
const TIER_BUDGET_ORDER = [
|
|
15
|
+
"warning",
|
|
16
|
+
"info",
|
|
17
|
+
"hint",
|
|
18
|
+
];
|
|
19
|
+
/** #1816: was a local `relativeFile|tool|rule|code|normalizedMessage|line`
|
|
20
|
+
* hash (raw, non-canonicalized `relativeFile`, 10-char hash), independently
|
|
21
|
+
* hand-rolled from `actionable-warnings.ts`'s twin and
|
|
22
|
+
* `diagnostic-dispositions.ts`'s canonicalizing original. Now the shared
|
|
23
|
+
* `finding-identity.js` builder (canonicalizes `cwd`/`filePath` through
|
|
24
|
+
* `normalizeMapKey`, hashes to 12 chars). No back-compat migration needed
|
|
25
|
+
* here: `cq:` ids are never a keyed lookup — `buildCodeQualityWarningsReport`
|
|
26
|
+
* regenerates the whole report fresh every turn, callers only ever cache and
|
|
27
|
+
* replay the report wholesale (`tools/lens-diagnostics.ts` reads it via
|
|
28
|
+
* `cacheManager.readCache<CodeQualityWarningsReport>`, never by `id`), and
|
|
29
|
+
* `appendCodeQualityWarningsHistory` just append-only-logs the id for
|
|
30
|
+
* observability, never look it back up. Contrast `actionable-warnings.ts`,
|
|
31
|
+
* whose `actionable-warning-state.json` DOES key a persisted suppression
|
|
32
|
+
* store on `aw:` — that one migrates. */
|
|
17
33
|
function createCodeQualityWarningId(args) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
args.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
34
|
+
return stableFindingId("cq:", {
|
|
35
|
+
cwd: args.cwd,
|
|
36
|
+
filePath: args.filePath,
|
|
37
|
+
parts: [
|
|
38
|
+
args.tool,
|
|
39
|
+
args.rule,
|
|
40
|
+
args.code,
|
|
41
|
+
normalizeMessage(args.message),
|
|
42
|
+
args.line,
|
|
43
|
+
],
|
|
44
|
+
});
|
|
27
45
|
}
|
|
28
46
|
function categorize(diagnostic) {
|
|
29
47
|
const haystack = `${diagnostic.tool} ${diagnostic.rule ?? ""} ${diagnostic.code ?? ""} ${diagnostic.message}`.toLowerCase();
|
|
@@ -93,11 +111,22 @@ export function buildCodeQualityWarningsReport(args) {
|
|
|
93
111
|
continue;
|
|
94
112
|
byId.set(warning.id, warning);
|
|
95
113
|
}
|
|
96
|
-
const
|
|
97
|
-
.sort((a, b) => a.displayPath.localeCompare(b.displayPath) ||
|
|
114
|
+
const byDisplayOrder = (a, b) => a.displayPath.localeCompare(b.displayPath) ||
|
|
98
115
|
(a.line ?? 0) - (b.line ?? 0) ||
|
|
99
|
-
a.message.localeCompare(b.message)
|
|
100
|
-
|
|
116
|
+
a.message.localeCompare(b.message);
|
|
117
|
+
// Spend the budget tier by tier (see TIER_BUDGET_ORDER), then restore
|
|
118
|
+
// display order so the report still reads file by file, line by line.
|
|
119
|
+
const candidates = [...byId.values()].sort(byDisplayOrder);
|
|
120
|
+
const merged = [];
|
|
121
|
+
for (const tier of TIER_BUDGET_ORDER) {
|
|
122
|
+
for (const warning of candidates) {
|
|
123
|
+
if (merged.length >= maxWarnings)
|
|
124
|
+
break;
|
|
125
|
+
if (warning.severity === tier)
|
|
126
|
+
merged.push(warning);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
merged.sort(byDisplayOrder);
|
|
101
130
|
const byFile = new Map();
|
|
102
131
|
for (const warning of merged) {
|
|
103
132
|
const arr = byFile.get(warning.filePath) ?? [];
|
|
@@ -131,6 +160,11 @@ export function buildCodeQualityWarningsReport(args) {
|
|
|
131
160
|
summary: {
|
|
132
161
|
warnings: merged.length,
|
|
133
162
|
files: files.length,
|
|
163
|
+
byTier: {
|
|
164
|
+
warning: merged.filter((w) => w.severity === "warning").length,
|
|
165
|
+
info: merged.filter((w) => w.severity === "info").length,
|
|
166
|
+
hint: merged.filter((w) => w.severity === "hint").length,
|
|
167
|
+
},
|
|
134
168
|
topRules,
|
|
135
169
|
},
|
|
136
170
|
};
|
|
@@ -205,8 +239,26 @@ export function formatCodeQualityWarningsAdvisory(report) {
|
|
|
205
239
|
.slice(0, 3)
|
|
206
240
|
.map((entry) => `${entry.rule}×${entry.count}`)
|
|
207
241
|
.join(", ");
|
|
242
|
+
// #1777: name the tiers, in the same descending-loudness order the on-demand
|
|
243
|
+
// ast-grep surface uses (`formatDiagnostics`, clients/ast-grep-client.ts).
|
|
244
|
+
// A tier with no findings is omitted rather than printed as zero.
|
|
245
|
+
const byTier = report.summary.byTier ?? { warning: 0, info: 0, hint: 0 };
|
|
246
|
+
// The line is worth its space only when a quiet tier is present; an
|
|
247
|
+
// all-warning turn already says everything in the count above.
|
|
248
|
+
const tiers = byTier.hint || byTier.info
|
|
249
|
+
? [
|
|
250
|
+
byTier.warning ? `${byTier.warning} warning` : undefined,
|
|
251
|
+
byTier.info ? `${byTier.info} info` : undefined,
|
|
252
|
+
byTier.hint ? `${byTier.hint} hint` : undefined,
|
|
253
|
+
]
|
|
254
|
+
.filter(Boolean)
|
|
255
|
+
.join(", ")
|
|
256
|
+
: "";
|
|
208
257
|
return [
|
|
209
258
|
`Code-quality warnings introduced/touched this turn: ${report.summary.warnings} across ${report.summary.files} file(s).`,
|
|
259
|
+
tiers
|
|
260
|
+
? `By tier: ${tiers}. Hint and info are style opinions, not defects.`
|
|
261
|
+
: undefined,
|
|
210
262
|
topRules ? `Top rules: ${topRules}` : undefined,
|
|
211
263
|
"Details written to .pi-lens/cache/code-quality-warnings.json",
|
|
212
264
|
"No action required unless you are already refactoring these areas.",
|
|
@@ -12,10 +12,15 @@
|
|
|
12
12
|
* implementing DeadCodeClient and adding to getDeadCodeClients().
|
|
13
13
|
*/
|
|
14
14
|
import { createSubsystemLogger } from "./extension-log.js";
|
|
15
|
+
import { incrementDegradationCount } from "./degradation-ledger.js";
|
|
16
|
+
import * as fs from "node:fs";
|
|
15
17
|
import * as path from "node:path";
|
|
16
18
|
import { findNearestMarkerRoot } from "./path-utils.js";
|
|
19
|
+
import { getScratchTreeFnmatchPatterns } from "./scratch-tree-policy.js";
|
|
17
20
|
import { safeSpawnAsync } from "./safe-spawn.js";
|
|
18
21
|
import { classifyProbeFailure, createAvailabilityLatch, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
|
|
22
|
+
import { firstOutputLine, spawnFailedWithNoOutput, } from "./dispatch/runners/utils/spawn-outcome.js";
|
|
23
|
+
import { formatToolFailure } from "./dispatch/runners/utils/tool-failure.js";
|
|
19
24
|
function emptyResult(language) {
|
|
20
25
|
return {
|
|
21
26
|
success: false,
|
|
@@ -28,17 +33,18 @@ function emptyResult(language) {
|
|
|
28
33
|
}
|
|
29
34
|
const ANALYSIS_TIMEOUT_MS = 30_000;
|
|
30
35
|
// Directories never worth scanning for the user's own dead code.
|
|
36
|
+
//
|
|
37
|
+
// Single-sourced from `scratch-tree-policy.ts`'s `EXCLUDED_DIRS`-derived list
|
|
38
|
+
// (#1562 sweep finding: this array had hand-drifted from `EXCLUDED_DIRS` —
|
|
39
|
+
// missing `.pi`/`.claude`/`.next`/`.turbo`/etc, the same single-source-of-
|
|
40
|
+
// truth defect #883 named for per-language lists). `site-packages`/`.eggs`
|
|
41
|
+
// are Python-packaging-specific and have no `EXCLUDED_DIRS` counterpart (no
|
|
42
|
+
// other scanner needs them), so they stay as a small local addition rather
|
|
43
|
+
// than polluting the shared cross-tool list.
|
|
44
|
+
const VULTURE_PYTHON_ONLY_EXCLUDES = ["*/site-packages/*", "*/.eggs/*"];
|
|
31
45
|
const VULTURE_EXCLUDES = [
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"*/.tox/*",
|
|
35
|
-
"*/build/*",
|
|
36
|
-
"*/dist/*",
|
|
37
|
-
"*/node_modules/*",
|
|
38
|
-
"*/.git/*",
|
|
39
|
-
"*/site-packages/*",
|
|
40
|
-
"*/__pycache__/*",
|
|
41
|
-
"*/.eggs/*",
|
|
46
|
+
...getScratchTreeFnmatchPatterns(),
|
|
47
|
+
...VULTURE_PYTHON_ONLY_EXCLUDES,
|
|
42
48
|
];
|
|
43
49
|
// Decorators whose target is called by a framework, never by name in the tree.
|
|
44
50
|
// vulture cannot see those call sites, so every such symbol is a permanent
|
|
@@ -145,13 +151,13 @@ export class PythonDeadCodeClient {
|
|
|
145
151
|
"tox.ini",
|
|
146
152
|
], { boundaries: [".git", ".hg", ".svn"], homeDir: homeDirOverride });
|
|
147
153
|
}
|
|
148
|
-
async ensureAvailable() {
|
|
154
|
+
async ensureAvailable(root) {
|
|
149
155
|
const memo = this.availabilityLatch.read();
|
|
150
156
|
if (memo !== null)
|
|
151
157
|
return memo;
|
|
152
158
|
if (this.ensureInFlight)
|
|
153
159
|
return this.ensureInFlight;
|
|
154
|
-
this.ensureInFlight = this.doEnsureAvailable();
|
|
160
|
+
this.ensureInFlight = this.doEnsureAvailable(root);
|
|
155
161
|
try {
|
|
156
162
|
return await this.ensureInFlight;
|
|
157
163
|
}
|
|
@@ -159,15 +165,39 @@ export class PythonDeadCodeClient {
|
|
|
159
165
|
this.ensureInFlight = null;
|
|
160
166
|
}
|
|
161
167
|
}
|
|
162
|
-
|
|
168
|
+
/**
|
|
169
|
+
* `<root>/.venv/bin/vulture` (and `venv`/Windows `Scripts` equivalents),
|
|
170
|
+
* checked by existence only — no spawn. A project's own venv is the vulture
|
|
171
|
+
* that project's own config and dependency set were written against; the
|
|
172
|
+
* bare `vulture`/`python -m vulture` PATH probes below only ever see
|
|
173
|
+
* whatever happens to be active in the CALLING shell, not the project's
|
|
174
|
+
* (#1731, discipline B — the same venv-first shape sqlfluff's binary
|
|
175
|
+
* resolution already uses, `runner-helpers.ts` `createVenvFinder`).
|
|
176
|
+
*/
|
|
177
|
+
venvCandidates(root) {
|
|
178
|
+
const isWin = process.platform === "win32";
|
|
179
|
+
const relPaths = isWin
|
|
180
|
+
? [
|
|
181
|
+
path.join(".venv", "Scripts", "vulture.exe"),
|
|
182
|
+
path.join("venv", "Scripts", "vulture.exe"),
|
|
183
|
+
]
|
|
184
|
+
: [path.join(".venv", "bin", "vulture"), path.join("venv", "bin", "vulture")];
|
|
185
|
+
return relPaths
|
|
186
|
+
.map((rel) => path.join(root, rel))
|
|
187
|
+
.filter((full) => fs.existsSync(full))
|
|
188
|
+
.map((full) => ({ cmd: full, prefix: [] }));
|
|
189
|
+
}
|
|
190
|
+
async doEnsureAvailable(root) {
|
|
163
191
|
// Presence-gated, NOT auto-installed. vulture is a pure-Python package
|
|
164
192
|
// with no standalone binary, so "auto-install" would mean `pip install`
|
|
165
193
|
// into whatever Python environment happens to be active — wrong and
|
|
166
194
|
// intrusive for uv / poetry / conda / pipx users. So we only use vulture
|
|
167
|
-
// when the user already has it, probing
|
|
168
|
-
//
|
|
169
|
-
//
|
|
195
|
+
// when the user already has it, probing the project's own venv first
|
|
196
|
+
// (#1731), then the `vulture` console script and `python -m vulture` on
|
|
197
|
+
// PATH (the script dir is frequently not on PATH even when the package
|
|
198
|
+
// is installed). Mirrors govulncheck's no-install gating.
|
|
170
199
|
const candidates = [
|
|
200
|
+
...(root ? this.venvCandidates(root) : []),
|
|
171
201
|
{ cmd: "vulture", prefix: [] },
|
|
172
202
|
{ cmd: "python", prefix: ["-m", "vulture"] },
|
|
173
203
|
{ cmd: "python3", prefix: ["-m", "vulture"] },
|
|
@@ -183,6 +213,10 @@ export class PythonDeadCodeClient {
|
|
|
183
213
|
// numbers.
|
|
184
214
|
const sweepStartedAt = Date.now();
|
|
185
215
|
let sweepHostStallMs = 0;
|
|
216
|
+
/** What the last classified candidate returned (#1500 review): this was
|
|
217
|
+
* computed and thrown away, so every vulture row said "missing" with no
|
|
218
|
+
* trace of which candidate reported what. */
|
|
219
|
+
let sweepEvidence;
|
|
186
220
|
for (const c of candidates) {
|
|
187
221
|
const sampler = startHostStallSampler();
|
|
188
222
|
const startedAt = Date.now();
|
|
@@ -213,7 +247,11 @@ export class PythonDeadCodeClient {
|
|
|
213
247
|
});
|
|
214
248
|
return true;
|
|
215
249
|
}
|
|
216
|
-
const classified = classifyProbeFailure(probe, {
|
|
250
|
+
const classified = classifyProbeFailure(probe, {
|
|
251
|
+
hostStallMs,
|
|
252
|
+
command: c.cmd,
|
|
253
|
+
});
|
|
254
|
+
sweepEvidence = classified.evidence;
|
|
217
255
|
if (classified.outcome === "transient") {
|
|
218
256
|
sawTransient = true;
|
|
219
257
|
transientCause = classified.cause;
|
|
@@ -232,6 +270,8 @@ export class PythonDeadCodeClient {
|
|
|
232
270
|
latched: false,
|
|
233
271
|
retryAfterMs,
|
|
234
272
|
budgetMs: 5000,
|
|
273
|
+
classifiedBy: "probe",
|
|
274
|
+
...(sweepEvidence !== undefined && { evidence: sweepEvidence }),
|
|
235
275
|
});
|
|
236
276
|
return false;
|
|
237
277
|
}
|
|
@@ -246,6 +286,9 @@ export class PythonDeadCodeClient {
|
|
|
246
286
|
hostStallMs: sweepHostStallMs,
|
|
247
287
|
latched: true,
|
|
248
288
|
budgetMs: 5000,
|
|
289
|
+
// Every candidate probe was classified, and none was transient (#1500).
|
|
290
|
+
classifiedBy: "probe",
|
|
291
|
+
...(sweepEvidence !== undefined && { evidence: sweepEvidence }),
|
|
249
292
|
});
|
|
250
293
|
return false;
|
|
251
294
|
}
|
|
@@ -258,7 +301,7 @@ export class PythonDeadCodeClient {
|
|
|
258
301
|
summary: "No Python project root found; vulture skipped",
|
|
259
302
|
};
|
|
260
303
|
}
|
|
261
|
-
if (!(await this.ensureAvailable())) {
|
|
304
|
+
if (!(await this.ensureAvailable(root))) {
|
|
262
305
|
return {
|
|
263
306
|
...emptyResult(this.language),
|
|
264
307
|
success: true,
|
|
@@ -273,14 +316,35 @@ export class PythonDeadCodeClient {
|
|
|
273
316
|
this.inFlight.set(key, promise);
|
|
274
317
|
return promise;
|
|
275
318
|
}
|
|
319
|
+
/**
|
|
320
|
+
* True when `<root>/pyproject.toml` carries a `[tool.vulture]` table.
|
|
321
|
+
* vulture's own config discovery reads that table, but CLI flags override
|
|
322
|
+
* it — passing `--min-confidence`/`--exclude` unconditionally silently
|
|
323
|
+
* overrode a project's own thresholds and ignore list (#1731, discipline A).
|
|
324
|
+
* Same shape as `hasSqlfluffConfig`/`hasMypyConfig` in `tool-policy.ts`: a
|
|
325
|
+
* plain string search, no TOML parser, matching the section header only.
|
|
326
|
+
*/
|
|
327
|
+
hasProjectVultureConfig(root) {
|
|
328
|
+
try {
|
|
329
|
+
const content = fs.readFileSync(path.join(root, "pyproject.toml"), "utf-8");
|
|
330
|
+
return content.includes("[tool.vulture]");
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
276
336
|
async runAnalyze(root) {
|
|
277
337
|
const startMs = Date.now();
|
|
278
338
|
const invocation = this.resolved ?? { cmd: "vulture", prefix: [] };
|
|
339
|
+
// Let the project's own [tool.vulture] config win (#1731, discipline A):
|
|
340
|
+
// vulture discovers it unaided, but these two flags override it when
|
|
341
|
+
// passed, so they are omitted whenever the project ships that table.
|
|
342
|
+
const hasConfig = this.hasProjectVultureConfig(root);
|
|
279
343
|
const args = [
|
|
280
344
|
...invocation.prefix,
|
|
281
345
|
".",
|
|
282
|
-
`--min-confidence=${this.minConfidence}
|
|
283
|
-
`--exclude=${VULTURE_EXCLUDES.join(",")}
|
|
346
|
+
...(hasConfig ? [] : [`--min-confidence=${this.minConfidence}`]),
|
|
347
|
+
...(hasConfig ? [] : [`--exclude=${VULTURE_EXCLUDES.join(",")}`]),
|
|
284
348
|
`--ignore-decorators=${VULTURE_IGNORE_DECORATORS.join(",")}`,
|
|
285
349
|
];
|
|
286
350
|
const result = await safeSpawnAsync(invocation.cmd, args, {
|
|
@@ -299,14 +363,36 @@ export class PythonDeadCodeClient {
|
|
|
299
363
|
}
|
|
300
364
|
// vulture writes parse/usage errors to stderr and exits 1 with no
|
|
301
365
|
// stdout findings; distinguish that from "found dead code" (exit 1 WITH
|
|
302
|
-
// findings on stdout).
|
|
366
|
+
// findings on stdout). #1736 sweep: the ORIGINAL guard here required
|
|
367
|
+
// non-empty stderr to call it an error, so a nonzero exit with BOTH
|
|
368
|
+
// empty stdout and empty stderr (a silent crash) still fell through to
|
|
369
|
+
// "No dead code found" -- the same empty-distinguishes-clean-from-errored
|
|
370
|
+
// gap the knip fix closes. A nonzero exit with no findings on stdout is
|
|
371
|
+
// never clean now, regardless of whether stderr said anything.
|
|
303
372
|
const output = result.stdout || "";
|
|
304
373
|
if (!output.trim()) {
|
|
305
374
|
const stderr = (result.stderr || "").trim();
|
|
306
|
-
|
|
375
|
+
// Same discriminator every dispatch/runners linter uses
|
|
376
|
+
// (`spawnFailedWithNoOutput`) rather than a parallel hand-rolled
|
|
377
|
+
// check (result.error is already handled above, so this reduces to
|
|
378
|
+
// the `status !== 0` half).
|
|
379
|
+
if (spawnFailedWithNoOutput(result, output)) {
|
|
380
|
+
// #1816: one shared wording, one truncation, signal named.
|
|
381
|
+
const reason = formatToolFailure({
|
|
382
|
+
tool: "vulture",
|
|
383
|
+
status: result.status,
|
|
384
|
+
signal: result.signal,
|
|
385
|
+
stderr: result.stderr,
|
|
386
|
+
});
|
|
387
|
+
this.log(reason);
|
|
388
|
+
incrementDegradationCount({
|
|
389
|
+
kind: "runner-empty-result",
|
|
390
|
+
subject: "vulture",
|
|
391
|
+
reason,
|
|
392
|
+
});
|
|
307
393
|
return {
|
|
308
394
|
...emptyResult(this.language),
|
|
309
|
-
summary: `vulture error: ${stderr
|
|
395
|
+
summary: stderr ? `vulture error: ${firstOutputLine(stderr)}` : reason,
|
|
310
396
|
durationMs,
|
|
311
397
|
};
|
|
312
398
|
}
|
|
@@ -357,16 +443,22 @@ export function deadCodeIssues(result) {
|
|
|
357
443
|
];
|
|
358
444
|
}
|
|
359
445
|
/**
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
* and
|
|
364
|
-
* line in the key turns each shifted
|
|
365
|
-
*
|
|
366
|
-
*
|
|
446
|
+
* Joins finding-identity parts into a stable key for diffing one scan
|
|
447
|
+
* against the previous one. The string-only signature is load-bearing: a
|
|
448
|
+
* line/column number cannot be passed without a compile error. An edit
|
|
449
|
+
* shifts every line below it, and a scan delta is then filtered to exactly
|
|
450
|
+
* the files the edit touched — so a line in the key turns each shifted
|
|
451
|
+
* pre-existing finding into a "newly unused" report under a heading that
|
|
452
|
+
* blames the agent's own edit for orphaning it. Inserting four lines above
|
|
453
|
+
* one real finding produced four false ones (#1477, #1483). Shared by
|
|
454
|
+
* knip's per-turn delta in runtime-turn.ts so the rule lives in one place.
|
|
367
455
|
*/
|
|
456
|
+
export function stableFindingKey(...parts) {
|
|
457
|
+
return parts.map((part) => part ?? "").join(":");
|
|
458
|
+
}
|
|
459
|
+
/** Stable identity for diffing one scan against the previous one. */
|
|
368
460
|
export function deadCodeIssueKey(issue) {
|
|
369
|
-
return
|
|
461
|
+
return stableFindingKey(issue.category, issue.file, issue.name);
|
|
370
462
|
}
|
|
371
463
|
/**
|
|
372
464
|
* Format this turn's ATTRIBUTABLE delta — symbols that became unused in files
|
|
@@ -38,8 +38,15 @@ export function withDeadline(promise, options) {
|
|
|
38
38
|
const onReject = options.onReject ?? "propagate";
|
|
39
39
|
const ms = options.ms ??
|
|
40
40
|
(options.deadlineAt !== undefined ? options.deadlineAt - Date.now() : 0);
|
|
41
|
-
// Past deadline / non-positive budget: settle immediately, no timer.
|
|
41
|
+
// Past deadline / non-positive budget: settle immediately, no timer. This
|
|
42
|
+
// branch returns before the race below ever runs, so — same reason as the
|
|
43
|
+
// loser-leg catch a few lines down — `promise` needs its own no-op catch
|
|
44
|
+
// here too: without it, a `promise` that eventually rejects (the caller
|
|
45
|
+
// already invoked it; this function only decides how long to wait on it)
|
|
46
|
+
// surfaces as an unhandled rejection instead of being silently superseded
|
|
47
|
+
// by the immediate timeout/undefined settlement.
|
|
42
48
|
if (ms <= 0) {
|
|
49
|
+
promise.catch(() => { });
|
|
43
50
|
return onTimeout === "undefined"
|
|
44
51
|
? Promise.resolve(undefined)
|
|
45
52
|
: Promise.reject(new Error(`Timeout after ${Math.max(0, ms)}ms`));
|
|
@@ -140,6 +140,11 @@ export function flushDebugHandlesLog() {
|
|
|
140
140
|
}
|
|
141
141
|
function safeGetActiveResourcesInfo() {
|
|
142
142
|
try {
|
|
143
|
+
// SAFETY: `process.getActiveResourcesInfo` is a real Node API (>=17.3)
|
|
144
|
+
// that @types/node does not declare on `Process`. The cast only widens
|
|
145
|
+
// the type to say the method MIGHT be there; the `typeof fn ===
|
|
146
|
+
// "function"` guard on the next line is what actually decides, so a
|
|
147
|
+
// runtime without it returns [] rather than throwing.
|
|
143
148
|
const fn = process.getActiveResourcesInfo;
|
|
144
149
|
return typeof fn === "function" ? fn.call(process) : [];
|
|
145
150
|
}
|
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
/** Bounded, process-local telemetry for behavior degraded during one session. */
|
|
2
2
|
import { logExtension } from "./extension-log.js";
|
|
3
|
+
import { LEDGER_FIELD_MAX, truncateForLedger } from "./ledger-bounds.js";
|
|
4
|
+
import { logLatency } from "./latency-logger.js";
|
|
5
|
+
// Re-exported so existing importers keep one name for the ledger's bound.
|
|
6
|
+
export { LEDGER_FIELD_MAX, truncateForLedger };
|
|
3
7
|
const ENTRIES_PER_KIND = 20;
|
|
4
8
|
const MAX_DISTINCT_KINDS = 32;
|
|
5
9
|
const OVERFLOW_KIND = "other";
|
|
6
10
|
const groups = new Map();
|
|
7
11
|
const onceKeys = new Set();
|
|
8
12
|
const tallies = new Map();
|
|
13
|
+
// Monotonic session-boundary counter (#1536 review F5): callers that keep
|
|
14
|
+
// their OWN once-per-session latch outside the ledger (a per-instance Set
|
|
15
|
+
// the ledger itself doesn't own) can compare this lazily at use time and
|
|
16
|
+
// clear their latch on a mismatch — the same clear-on-transition shape as
|
|
17
|
+
// project-trust.ts's trustGeneration, but keyed to the ledger's own reset
|
|
18
|
+
// (resetDegradationLedger, wired into handleSessionStart) rather than a
|
|
19
|
+
// trust change.
|
|
20
|
+
let ledgerGeneration = 0;
|
|
21
|
+
/** Current session generation. Bump on every `resetDegradationLedger()`. */
|
|
22
|
+
export function getDegradationLedgerGeneration() {
|
|
23
|
+
return ledgerGeneration;
|
|
24
|
+
}
|
|
9
25
|
export function recordDegradation(record) {
|
|
10
26
|
try {
|
|
11
27
|
const kind = boundedKind(record.kind);
|
|
@@ -16,6 +32,7 @@ export function recordDegradation(record) {
|
|
|
16
32
|
group = { count: 0, entries: [] };
|
|
17
33
|
groups.set(kind, group);
|
|
18
34
|
}
|
|
35
|
+
const admitted = group.entries.length < ENTRIES_PER_KIND;
|
|
19
36
|
group.count += 1;
|
|
20
37
|
// Bounded at RECORD time (#1366 review): reasons carry arbitrary error
|
|
21
38
|
// text; a 10KB message must never become a 10KB health line or a 10KB
|
|
@@ -23,10 +40,12 @@ export function recordDegradation(record) {
|
|
|
23
40
|
group.entries.push({ subject, reason });
|
|
24
41
|
if (group.entries.length > ENTRIES_PER_KIND)
|
|
25
42
|
group.entries.shift();
|
|
43
|
+
return admitted;
|
|
26
44
|
}
|
|
27
45
|
catch (error) {
|
|
28
46
|
debugLedgerFailure("record", error);
|
|
29
47
|
// Telemetry must never break the observed path.
|
|
48
|
+
return false;
|
|
30
49
|
}
|
|
31
50
|
}
|
|
32
51
|
/** Record at most once per kind/subject during the current session. */
|
|
@@ -38,7 +57,9 @@ export function recordDegradationOnce(record) {
|
|
|
38
57
|
if (onceKeys.has(key))
|
|
39
58
|
return;
|
|
40
59
|
onceKeys.add(key);
|
|
41
|
-
recordDegradation({ kind, subject, reason: record.reason })
|
|
60
|
+
if (recordDegradation({ kind, subject, reason: record.reason })) {
|
|
61
|
+
logDurableDegradation(kind, subject, 1);
|
|
62
|
+
}
|
|
42
63
|
}
|
|
43
64
|
catch (error) {
|
|
44
65
|
debugLedgerFailure("record-once", error);
|
|
@@ -48,6 +69,14 @@ export function recordDegradationOnce(record) {
|
|
|
48
69
|
/**
|
|
49
70
|
* Count a repeated degradation while retaining one latest-reason entry per
|
|
50
71
|
* kind/subject. The group count remains the exact event total.
|
|
72
|
+
*
|
|
73
|
+
* Returns `true` when this call is the FIRST occurrence recorded for this
|
|
74
|
+
* kind/subject pair (the ledger is the single source of truth for that
|
|
75
|
+
* tally already — via `tallies` — so callers that need a once-per-subject
|
|
76
|
+
* "rising edge" signal, e.g. to gate a verbose one-time log line before
|
|
77
|
+
* falling back to the bounded count, read it off this return value instead
|
|
78
|
+
* of hand-rolling their own parallel `Set`/latch). #1716 reuses this same
|
|
79
|
+
* signal to gate `navRequest`'s detailed timeout/late-answer log writes.
|
|
51
80
|
*/
|
|
52
81
|
export function incrementDegradationCount(record) {
|
|
53
82
|
try {
|
|
@@ -62,24 +91,49 @@ export function incrementDegradationCount(record) {
|
|
|
62
91
|
group = { count: 0, entries: [] };
|
|
63
92
|
groups.set(kind, group);
|
|
64
93
|
}
|
|
65
|
-
group.count += 1;
|
|
66
|
-
const entry = { subject, reason: truncateForLedger(`${reason} (count: ${count})`) };
|
|
67
94
|
const existing = group.entries.findIndex((candidate) => candidate.subject === subject);
|
|
95
|
+
const admitted = existing >= 0 || group.entries.length < ENTRIES_PER_KIND;
|
|
96
|
+
group.count += 1;
|
|
97
|
+
// #1816: append the count AFTER truncation, never before. `reason` is
|
|
98
|
+
// already bounded above, so re-truncating the concatenation pushed the
|
|
99
|
+
// suffix past LEDGER_FIELD_MAX and silently ate it — a 200-char reason
|
|
100
|
+
// lost the one field that says how often the degradation fired.
|
|
101
|
+
const entry = { subject, reason: `${reason} (count: ${count})` };
|
|
68
102
|
if (existing >= 0)
|
|
69
103
|
group.entries.splice(existing, 1);
|
|
70
104
|
group.entries.push(entry);
|
|
71
105
|
if (group.entries.length > ENTRIES_PER_KIND)
|
|
72
106
|
group.entries.shift();
|
|
107
|
+
// Durable rows use the summary's admission and emit the first event and
|
|
108
|
+
// power-of-two milestones only, keeping the sink bounded.
|
|
109
|
+
if (admitted && isPowerOfTwo(count)) {
|
|
110
|
+
logDurableDegradation(kind, subject, count);
|
|
111
|
+
}
|
|
112
|
+
return count === 1;
|
|
73
113
|
}
|
|
74
114
|
catch (error) {
|
|
75
115
|
debugLedgerFailure("increment", error);
|
|
76
116
|
// Telemetry must never break the observed path.
|
|
117
|
+
return false;
|
|
77
118
|
}
|
|
78
119
|
}
|
|
79
|
-
/**
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
120
|
+
/**
|
|
121
|
+
* Persist the accepted ledger mutation through the existing rotated NDJSON
|
|
122
|
+
* latency stream. `logLatency` owns the timestamp, PID, serialization, secret
|
|
123
|
+
* redaction, and write queue. The subject and kind were already bounded by the
|
|
124
|
+
* ledger before reaching this seam.
|
|
125
|
+
*/
|
|
126
|
+
function logDurableDegradation(kind, subject, count) {
|
|
127
|
+
logLatency({
|
|
128
|
+
type: "phase",
|
|
129
|
+
phase: "degradation_ledger",
|
|
130
|
+
filePath: subject,
|
|
131
|
+
durationMs: 0,
|
|
132
|
+
metadata: { kind, subject, count, ledgerGeneration },
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
function isPowerOfTwo(value) {
|
|
136
|
+
return value > 0 && (value & (value - 1)) === 0;
|
|
83
137
|
}
|
|
84
138
|
function boundedKind(value) {
|
|
85
139
|
const kind = truncateForLedger(value);
|
|
@@ -88,12 +142,6 @@ function boundedKind(value) {
|
|
|
88
142
|
// Keep one slot available for all kinds beyond the cardinality bound.
|
|
89
143
|
return groups.size < MAX_DISTINCT_KINDS - 1 ? kind : OVERFLOW_KIND;
|
|
90
144
|
}
|
|
91
|
-
function truncateForLedger(value) {
|
|
92
|
-
const text = normalizeForLedger(value);
|
|
93
|
-
return text.length > LEDGER_FIELD_MAX
|
|
94
|
-
? `${text.slice(0, LEDGER_FIELD_MAX)}…`
|
|
95
|
-
: text;
|
|
96
|
-
}
|
|
97
145
|
export function getDegradationSummary() {
|
|
98
146
|
return [...groups.entries()].map(([kind, group]) => ({
|
|
99
147
|
kind,
|
|
@@ -148,6 +196,7 @@ export function resetDegradationLedger() {
|
|
|
148
196
|
groups.clear();
|
|
149
197
|
onceKeys.clear();
|
|
150
198
|
tallies.clear();
|
|
199
|
+
ledgerGeneration++;
|
|
151
200
|
}
|
|
152
201
|
export const DEGRADATION_ENTRIES_PER_KIND = ENTRIES_PER_KIND;
|
|
153
202
|
export const DEGRADATION_MAX_DISTINCT_KINDS = MAX_DISTINCT_KINDS;
|