@herbertgao/pi-extensions 2026.8.4 → 2026.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +11 -3
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +11 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +24 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +91 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +74 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +7 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +21 -12
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +4 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +53 -14
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +22 -12
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +2 -4
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +18 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +8 -4
- package/node_modules/@herbertgao/pi-subagents/src/agent-color.ts +72 -67
- package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +255 -0
- package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +191 -3
- package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +20 -4
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +289 -113
- package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +24 -1
- package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +24 -2
- package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +15 -6
- package/node_modules/@herbertgao/pi-subagents/src/ui/schedule-menu.ts +9 -8
- package/node_modules/@herbertgao/pi-subagents/src/ui/select-item.ts +48 -0
- package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +9 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +2 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +20 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +6 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +7 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +7 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +16 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +4 -2
- package/node_modules/@narumitw/pi-btw/README.md +32 -24
- package/node_modules/@narumitw/pi-btw/package.json +6 -6
- package/node_modules/@narumitw/pi-btw/src/btw.ts +4 -2
- package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -13
- package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
- package/node_modules/pi-lens/CHANGELOG.md +2535 -954
- package/node_modules/pi-lens/README.md +13 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +73 -18
- package/node_modules/pi-lens/dist/clients/advisory-provenance.js +314 -0
- package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +21 -4
- package/node_modules/pi-lens/dist/clients/agent-nudge.js +14 -7
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +68 -16
- package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -1
- package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +37 -10
- package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +63 -0
- package/node_modules/pi-lens/dist/clients/atomic-write.js +114 -14
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +193 -23
- package/node_modules/pi-lens/dist/clients/biome-client.js +136 -24
- package/node_modules/pi-lens/dist/clients/bootstrap.js +11 -2
- package/node_modules/pi-lens/dist/clients/bounded-cache.js +34 -0
- package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +192 -0
- package/node_modules/pi-lens/dist/clients/bus-events-logger.js +62 -6
- package/node_modules/pi-lens/dist/clients/bus-publish.js +19 -4
- package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +47 -5
- package/node_modules/pi-lens/dist/clients/cache-manager.js +97 -9
- package/node_modules/pi-lens/dist/clients/cache-observability.js +301 -14
- package/node_modules/pi-lens/dist/clients/call-graph.js +449 -117
- package/node_modules/pi-lens/dist/clients/cascade-format.js +57 -2
- package/node_modules/pi-lens/dist/clients/child-unref.js +85 -0
- package/node_modules/pi-lens/dist/clients/codebase-model.js +52 -23
- package/node_modules/pi-lens/dist/clients/collateral-test-role.js +40 -0
- package/node_modules/pi-lens/dist/clients/complexity-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/console-guard-install.js +26 -0
- package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +137 -31
- package/node_modules/pi-lens/dist/clients/debug-handles.js +206 -0
- package/node_modules/pi-lens/dist/clients/debug-heap.js +167 -0
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +153 -0
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +361 -77
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +65 -13
- package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +18 -4
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +176 -29
- package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +23 -7
- package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +8 -18
- package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +8 -5
- package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +35 -0
- package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +17 -8
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +1172 -683
- package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +14 -0
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +11 -1
- package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +50 -0
- package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +135 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +23 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +5 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +154 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +5 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +47 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +9 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +4 -8
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +163 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +37 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +3 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +13 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +226 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +69 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +473 -42
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +97 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
- package/node_modules/pi-lens/dist/clients/disposition-publish.js +18 -4
- package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
- package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
- package/node_modules/pi-lens/dist/clients/extension-log.js +432 -0
- package/node_modules/pi-lens/dist/clients/extension-mode.js +73 -0
- package/node_modules/pi-lens/dist/clients/file-kinds.js +57 -0
- package/node_modules/pi-lens/dist/clients/file-role.js +18 -3
- package/node_modules/pi-lens/dist/clients/file-utils.js +56 -34
- package/node_modules/pi-lens/dist/clients/fix-worklog.js +5 -1
- package/node_modules/pi-lens/dist/clients/format-events-publish.js +51 -7
- package/node_modules/pi-lens/dist/clients/format-service.js +24 -8
- package/node_modules/pi-lens/dist/clients/formatters-lazy.js +8 -0
- package/node_modules/pi-lens/dist/clients/formatters.js +358 -61
- package/node_modules/pi-lens/dist/clients/generated-artifacts.js +148 -15
- package/node_modules/pi-lens/dist/clients/git-guard.js +678 -11
- package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
- package/node_modules/pi-lens/dist/clients/go-client.js +23 -40
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +124 -7
- package/node_modules/pi-lens/dist/clients/grammar-source.js +12 -4
- package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +21 -3
- package/node_modules/pi-lens/dist/clients/host-ports.js +31 -0
- package/node_modules/pi-lens/dist/clients/installer/index.js +395 -100
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +27 -55
- package/node_modules/pi-lens/dist/clients/knip-client.js +58 -42
- package/node_modules/pi-lens/dist/clients/language-policy.js +14 -2
- package/node_modules/pi-lens/dist/clients/language-profile.js +13 -7
- package/node_modules/pi-lens/dist/clients/latency-logger.js +72 -1
- package/node_modules/pi-lens/dist/clients/lens-config.js +12 -1
- package/node_modules/pi-lens/dist/clients/lens-engine.js +86 -17
- package/node_modules/pi-lens/dist/clients/lens-events.js +63 -9
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +16 -0
- package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +73 -0
- package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +59 -14
- package/node_modules/pi-lens/dist/clients/lsp/client.js +662 -56
- package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +115 -0
- package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
- package/node_modules/pi-lens/dist/clients/lsp/index.js +2025 -800
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +32 -26
- package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +83 -0
- package/node_modules/pi-lens/dist/clients/lsp/server.js +437 -86
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +96 -0
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +21 -5
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +18 -3
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
- package/node_modules/pi-lens/dist/clients/lsp-lazy.js +8 -0
- package/node_modules/pi-lens/dist/clients/lsp-mutation.js +248 -0
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +127 -32
- package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
- package/node_modules/pi-lens/dist/clients/mcp/session.js +235 -14
- package/node_modules/pi-lens/dist/clients/memory-sampler.js +115 -0
- package/node_modules/pi-lens/dist/clients/metrics-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/metrics-history.js +2 -1
- package/node_modules/pi-lens/dist/clients/model-provider.js +53 -0
- package/node_modules/pi-lens/dist/clients/module-report-lsp.js +5 -0
- package/node_modules/pi-lens/dist/clients/module-report.js +168 -3
- package/node_modules/pi-lens/dist/clients/ndjson-logger.js +319 -118
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +1 -1
- package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +120 -5
- package/node_modules/pi-lens/dist/clients/path-utils.js +110 -9
- package/node_modules/pi-lens/dist/clients/pipeline.js +76 -15
- package/node_modules/pi-lens/dist/clients/print-mode.js +21 -0
- package/node_modules/pi-lens/dist/clients/project-changes.js +40 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +3 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +8 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +23 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +21 -1
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +86 -9
- package/node_modules/pi-lens/dist/clients/project-report.js +27 -13
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +175 -20
- package/node_modules/pi-lens/dist/clients/project-trust.js +181 -0
- package/node_modules/pi-lens/dist/clients/quiet-window.js +18 -4
- package/node_modules/pi-lens/dist/clients/read-bridge.js +147 -0
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +159 -1
- package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +150 -29
- package/node_modules/pi-lens/dist/clients/read-guard.js +185 -4
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +70 -84
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +1122 -221
- package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +2 -1
- package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +21 -10
- package/node_modules/pi-lens/dist/clients/review-graph/query.js +24 -0
- package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +46 -0
- package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +57 -6
- package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +51 -3
- package/node_modules/pi-lens/dist/clients/review-graph-logger.js +30 -1
- package/node_modules/pi-lens/dist/clients/ruff-client.js +27 -44
- package/node_modules/pi-lens/dist/clients/run-duration.js +55 -0
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +257 -77
- package/node_modules/pi-lens/dist/clients/runtime-context.js +129 -12
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +242 -26
- package/node_modules/pi-lens/dist/clients/runtime-session.js +428 -117
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +71 -48
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +342 -33
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +619 -93
- package/node_modules/pi-lens/dist/clients/rust-client.js +23 -38
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +90 -6
- package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
- package/node_modules/pi-lens/dist/clients/sg-runner.js +424 -196
- package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +231 -0
- package/node_modules/pi-lens/dist/clients/source-filter.js +184 -20
- package/node_modules/pi-lens/dist/clients/source-walker.js +66 -10
- package/node_modules/pi-lens/dist/clients/startup-scan.js +15 -16
- package/node_modules/pi-lens/dist/clients/startup-timing.js +7 -1
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +469 -32
- package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
- package/node_modules/pi-lens/dist/clients/tool-policy.js +390 -39
- package/node_modules/pi-lens/dist/clients/tool-render.js +155 -0
- package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +6 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +419 -50
- package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +21 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +7 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +7 -2
- package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +44 -7
- package/node_modules/pi-lens/dist/clients/user-notify.js +51 -0
- package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +57 -0
- package/node_modules/pi-lens/dist/clients/warm-attach.js +50 -13
- package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
- package/node_modules/pi-lens/dist/clients/word-index.js +680 -81
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
- package/node_modules/pi-lens/dist/index.js +51785 -36118
- package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
- package/node_modules/pi-lens/dist/mcp/server.js +219 -46
- package/node_modules/pi-lens/dist/tools/activate-tools.js +17 -5
- package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +9 -4
- package/node_modules/pi-lens/dist/tools/ast-grep-search.js +93 -27
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +5 -2
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +203 -38
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +210 -42
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -3
- package/node_modules/pi-lens/dist/tools/module-report.js +9 -0
- package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -1
- package/node_modules/pi-lens/docs/agent-guide.md +40 -15
- package/node_modules/pi-lens/docs/agent-tools.md +21 -10
- package/node_modules/pi-lens/docs/analysisall.md +192 -0
- package/node_modules/pi-lens/docs/api-ports-inventory.md +165 -0
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +14 -3
- package/node_modules/pi-lens/docs/durable-store-audit-1202.md +32 -0
- package/node_modules/pi-lens/docs/environment-variables.md +44 -0
- package/node_modules/pi-lens/docs/fable.md +27 -0
- package/node_modules/pi-lens/docs/features.md +45 -3
- package/node_modules/pi-lens/docs/globalconfig.md +56 -1
- package/node_modules/pi-lens/docs/language-coverage.md +4 -3
- package/node_modules/pi-lens/docs/lsp-capability-matrix.md +2 -2
- package/node_modules/pi-lens/docs/mcp.md +37 -4
- package/node_modules/pi-lens/docs/servercapabilities.md +3 -3
- package/node_modules/pi-lens/docs/settings.md +18 -1
- package/node_modules/pi-lens/docs/tools.md +4 -1
- package/node_modules/pi-lens/docs/usage.md +41 -5
- package/node_modules/pi-lens/docs/word-index.md +35 -0
- package/node_modules/pi-lens/package.json +10 -10
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-js-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-laundering-test.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread-js.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread.yml +29 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +13 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +27 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
- package/node_modules/pi-lens/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
- package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +2 -2
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +134 -2
- package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +10 -11
- package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +22 -22
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +8 -114
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +129 -0
- package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +38 -0
- package/node_modules/pi-mcp-adapter/README.md +38 -4
- package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
- package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
- package/node_modules/pi-mcp-adapter/cli.js +25 -4
- package/node_modules/pi-mcp-adapter/commands.ts +15 -7
- package/node_modules/pi-mcp-adapter/config.ts +4 -4
- package/node_modules/pi-mcp-adapter/direct-tools.ts +58 -8
- package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -8
- package/node_modules/pi-mcp-adapter/index.ts +8 -3
- package/node_modules/pi-mcp-adapter/init.ts +29 -5
- package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -4
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
- package/node_modules/pi-mcp-adapter/mcp-auth.ts +60 -2
- package/node_modules/pi-mcp-adapter/mcp-panel.ts +45 -9
- package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +22 -20
- package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +2 -1
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +37 -4
- package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
- package/node_modules/pi-mcp-adapter/package.json +4 -3
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +336 -0
- package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
- package/node_modules/pi-mcp-adapter/server-manager.ts +41 -7
- package/node_modules/pi-mcp-adapter/session-recovery.ts +8 -2
- package/node_modules/pi-mcp-adapter/tool-approval.ts +48 -7
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +62 -4
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
- package/node_modules/pi-mcp-adapter/types.ts +176 -27
- package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
- package/node_modules/pi-web-access/CHANGELOG.md +14 -0
- package/node_modules/pi-web-access/README.md +18 -12
- package/node_modules/pi-web-access/auth-fetch.ts +148 -0
- package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
- package/node_modules/pi-web-access/curator-page.ts +5 -3
- package/node_modules/pi-web-access/curator-server.ts +2 -1
- package/node_modules/pi-web-access/extract.ts +106 -34
- package/node_modules/pi-web-access/fetch-params.ts +17 -3
- package/node_modules/pi-web-access/firecrawl.ts +172 -12
- package/node_modules/pi-web-access/gemini-search.ts +18 -4
- package/node_modules/pi-web-access/index.ts +120 -48
- package/node_modules/pi-web-access/package.json +2 -2
- package/node_modules/pi-web-access/summary-review.ts +11 -5
- package/node_modules/pi-web-access/youtube-extract.ts +2 -2
- package/package.json +12 -13
- package/node_modules/pi-lens/dist/clients/ast-grep-parser.js +0 -86
- package/node_modules/pi-lens/dist/clients/lsp/interactive-install.js +0 -367
- package/node_modules/pi-lens/dist/clients/lsp/lsp-index.js +0 -10
- package/node_modules/pi-lens/dist/clients/lsp/server-strategies.js +0 -5
- package/node_modules/pi-lens/dist/clients/project-metadata.js +0 -690
- package/node_modules/pi-lens/dist/clients/source-groups.js +0 -96
- package/node_modules/pi-lens/dist/clients/startup-marker.js +0 -6
- package/node_modules/pi-lens/dist/clients/types.js +0 -11
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import { convertLspDiagnostics } from "./dispatch/utils/lsp-diagnostics.js";
|
|
1
2
|
import { toRunnerDisplayPath } from "./dispatch/runner-context.js";
|
|
2
3
|
export function formatCascadeNeighborDiagnostics(cwd, neighbors, options = {}) {
|
|
3
4
|
const withErrors = neighbors.filter((n) => n.diagnostics.length > 0);
|
|
4
|
-
|
|
5
|
+
const inconclusive = neighbors.filter((n) => n.inconclusive === true && n.diagnostics.length === 0);
|
|
6
|
+
if (withErrors.length === 0 && inconclusive.length === 0)
|
|
5
7
|
return "";
|
|
6
8
|
const noun = options.noun ?? "neighbor";
|
|
7
|
-
let out =
|
|
9
|
+
let out = withErrors.length > 0
|
|
10
|
+
? `📐 Cascade errors in ${withErrors.length} ${noun} file(s) — fix before finishing turn:`
|
|
11
|
+
: "";
|
|
8
12
|
for (const neighbor of withErrors) {
|
|
9
13
|
const display = toRunnerDisplayPath(cwd, neighbor.filePath);
|
|
10
14
|
const reason = options.includeReason ? ` reason="${neighbor.reason}"` : "";
|
|
@@ -17,5 +21,56 @@ export function formatCascadeNeighborDiagnostics(cwd, neighbors, options = {}) {
|
|
|
17
21
|
}
|
|
18
22
|
out += "\n</diagnostics>";
|
|
19
23
|
}
|
|
24
|
+
if (inconclusive.length > 0) {
|
|
25
|
+
if (out)
|
|
26
|
+
out += "\n";
|
|
27
|
+
out += `⚠️ Cascade diagnostics inconclusive for ${inconclusive.length} ${noun} file(s) — no clean result was confirmed:`;
|
|
28
|
+
for (const neighbor of inconclusive) {
|
|
29
|
+
out += `\n ${toRunnerDisplayPath(cwd, neighbor.filePath)}`;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
20
32
|
return out;
|
|
21
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Build the turn-end `CascadeRun` for a neighbour whose diagnostics landed only
|
|
36
|
+
* AFTER its cascade touch skipped the in-lane wait (#1023's `resolved-found`
|
|
37
|
+
* quiet-window outcome; #1444 made native TS7 take that same path). Returns
|
|
38
|
+
* `undefined` when there is nothing agent-facing to say — no ERROR-severity
|
|
39
|
+
* diagnostics, or nothing the formatter renders.
|
|
40
|
+
*
|
|
41
|
+
* Lives here rather than inline in the quiet-window callback so the delivery
|
|
42
|
+
* path (reconcile → run → turn_end) is testable end to end; index.ts only wires
|
|
43
|
+
* it to `runtime.appendCascadeRun`.
|
|
44
|
+
*/
|
|
45
|
+
export function buildResolvedFoundCascadeRun(cwd, neighbor) {
|
|
46
|
+
const { filePath } = neighbor;
|
|
47
|
+
const diagnostics = convertLspDiagnostics(neighbor.diagnostics.filter((d) => d.severity === 1), filePath, { tool: "lsp" });
|
|
48
|
+
if (diagnostics.length === 0)
|
|
49
|
+
return undefined;
|
|
50
|
+
const neighbors = [
|
|
51
|
+
{ filePath, reason: "references", diagnostics, lspTouched: true },
|
|
52
|
+
];
|
|
53
|
+
const formatted = formatCascadeNeighborDiagnostics(cwd, neighbors, {
|
|
54
|
+
noun: "cold neighbor",
|
|
55
|
+
});
|
|
56
|
+
if (!formatted)
|
|
57
|
+
return undefined;
|
|
58
|
+
return {
|
|
59
|
+
filePath,
|
|
60
|
+
result: {
|
|
61
|
+
filePath,
|
|
62
|
+
impact: {
|
|
63
|
+
filePath,
|
|
64
|
+
changedSymbols: [],
|
|
65
|
+
directImporters: [],
|
|
66
|
+
directCallers: [],
|
|
67
|
+
neighborFiles: [filePath],
|
|
68
|
+
riskFlags: [],
|
|
69
|
+
},
|
|
70
|
+
neighbors,
|
|
71
|
+
formatted,
|
|
72
|
+
},
|
|
73
|
+
neighborCount: 1,
|
|
74
|
+
diagnosticCount: diagnostics.length,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared plumbing for best-effort, fire-and-forget child-process spawns
|
|
3
|
+
* (shape 4 of the recurring-defect catalog in AGENTS.md: "a timer / promise /
|
|
4
|
+
* worker / child that outlives its one-shot settle"). Extracted from the
|
|
5
|
+
* orphan reaper's `unrefReaperChild`/`spawnCollectStdout` (#1153/#1160) into a
|
|
6
|
+
* shared, dependency-free module so every one-shot
|
|
7
|
+
* `spawn(..., { stdio: ["ignore","pipe",...] })` call site in the codebase —
|
|
8
|
+
* the reaper's enumeration/kill spawns AND the resource sampler's Windows
|
|
9
|
+
* CIM/powershell spawns (#1155) — uses the exact same unref+collect shape
|
|
10
|
+
* instead of re-deriving it (also closes the #1155 PR's SonarCloud
|
|
11
|
+
* new-code-duplication finding: two near-identical spawn→collect-stdout
|
|
12
|
+
* blocks in `clients/resource-sampler.ts` collapsed to one shared helper).
|
|
13
|
+
*
|
|
14
|
+
* Deliberately has NO imports beyond `node:child_process` types, so both
|
|
15
|
+
* `clients/instance-reaper.ts` and `clients/resource-sampler.ts` (which
|
|
16
|
+
* `clients/safe-spawn.ts` itself depends on) can import this without risking
|
|
17
|
+
* a circular-import chain.
|
|
18
|
+
*/
|
|
19
|
+
import { spawn as nodeSpawn } from "node:child_process";
|
|
20
|
+
/**
|
|
21
|
+
* Detach a best-effort, fire-and-forget child process from the event loop.
|
|
22
|
+
*
|
|
23
|
+
* A piped, `data`-listener-attached stdout/stderr/stdin stream keeps the libuv
|
|
24
|
+
* loop REFERENCED even after `child.unref()` — the child handle and every
|
|
25
|
+
* live stdio stream must all be unref'd, or a settled one-shot process (e.g.
|
|
26
|
+
* `pi --print`) cannot exit until the child `close`s. Unref only means "do
|
|
27
|
+
* not keep the process alive FOR this best-effort work": in an interactive/
|
|
28
|
+
* long-lived session the loop stays referenced for other reasons, so the
|
|
29
|
+
* child's `data`/`close` events still fire normally and callers still collect
|
|
30
|
+
* output/usage; only a genuinely-settled one-shot is allowed to exit without
|
|
31
|
+
* waiting for it. Never throws.
|
|
32
|
+
*/
|
|
33
|
+
export function unrefChildAndPipes(child) {
|
|
34
|
+
try {
|
|
35
|
+
child.unref();
|
|
36
|
+
// Child stdio pipes are `net.Socket`s at runtime (which expose `unref`),
|
|
37
|
+
// but are typed as `Readable`/`Writable` (which do not) — cast to the
|
|
38
|
+
// optional-`unref` shape and guard, so an un-piped ("ignore") stream is a
|
|
39
|
+
// no-op rather than a crash.
|
|
40
|
+
for (const stream of [child.stdout, child.stderr, child.stdin]) {
|
|
41
|
+
stream?.unref?.();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// best-effort — unref must never throw out of a fire-and-forget spawn
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Spawn a best-effort, fire-and-forget child, accumulate its full stdout, and
|
|
50
|
+
* resolve with the collected text (empty string on a synchronous spawn
|
|
51
|
+
* failure or an `error` event). Consolidates the spawn → unref →
|
|
52
|
+
* pipe-stdout → `close` plumbing shared by every one-shot OS-process-table
|
|
53
|
+
* query in the codebase — each caller supplies only its command/args/options
|
|
54
|
+
* and does its own output parse. The child + its stdio pipes are `unref`'d
|
|
55
|
+
* here (via `unrefChildAndPipes`) so a settled one-shot `pi --print` process
|
|
56
|
+
* can exit without waiting, and both the unref and the collect plumbing live
|
|
57
|
+
* in exactly ONE place rather than being re-derived at each spawn site.
|
|
58
|
+
* Never rejects — any failure resolves to `""`, which every caller's parse
|
|
59
|
+
* turns into an empty/absent result (the best-effort contract every caller
|
|
60
|
+
* here already has).
|
|
61
|
+
*/
|
|
62
|
+
export function spawnCollectStdout(command, args, options) {
|
|
63
|
+
return new Promise((resolve) => {
|
|
64
|
+
let settled = false;
|
|
65
|
+
const settle = (value) => {
|
|
66
|
+
if (settled)
|
|
67
|
+
return;
|
|
68
|
+
settled = true;
|
|
69
|
+
resolve(value);
|
|
70
|
+
};
|
|
71
|
+
try {
|
|
72
|
+
const child = nodeSpawn(command, args, options);
|
|
73
|
+
unrefChildAndPipes(child);
|
|
74
|
+
let out = "";
|
|
75
|
+
child.stdout?.on("data", (chunk) => {
|
|
76
|
+
out += chunk.toString();
|
|
77
|
+
});
|
|
78
|
+
child.once("error", () => settle(""));
|
|
79
|
+
child.once("close", () => settle(out));
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
settle("");
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
@@ -13,18 +13,25 @@
|
|
|
13
13
|
import * as fs from "node:fs";
|
|
14
14
|
import * as path from "node:path";
|
|
15
15
|
import { getProjectDataDir } from "./file-utils.js";
|
|
16
|
+
import { writeFileAtomic } from "./atomic-write.js";
|
|
17
|
+
import { parseSymbolKey } from "./call-graph.js";
|
|
18
|
+
import { detectFileRole } from "./file-role.js";
|
|
19
|
+
import { isExternalOrVendorFile } from "./path-utils.js";
|
|
20
|
+
import { isBuildArtifact } from "./source-filter.js";
|
|
16
21
|
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
17
|
-
const
|
|
22
|
+
export const DEFAULT_CODEBASE_MODEL_TOKEN_BUDGET = 1500;
|
|
18
23
|
const MAX_CALLS_PER_SYMBOL = 10;
|
|
19
24
|
const MIN_IN_DEGREE = 0.5; // skip symbols with low centrality (avoids noise)
|
|
25
|
+
/** Persisted model schema version. Bump when the persisted shape changes. */
|
|
26
|
+
export const CODEBASE_MODEL_VERSION = 1;
|
|
20
27
|
// ── Builder ───────────────────────────────────────────────────────────────────
|
|
21
28
|
function inferKind(symbolKey) {
|
|
22
|
-
const
|
|
23
|
-
if (
|
|
29
|
+
const parsed = parseSymbolKey(symbolKey);
|
|
30
|
+
if (parsed.kind === "class")
|
|
24
31
|
return "class";
|
|
25
|
-
if (
|
|
32
|
+
if (parsed.kind === "method" || parsed.symbolName?.includes("."))
|
|
26
33
|
return "method";
|
|
27
|
-
return "function";
|
|
34
|
+
return /^[A-Z]/.test(parsed.symbolName ?? "") ? "class" : "function";
|
|
28
35
|
}
|
|
29
36
|
function estimateTokens(entry) {
|
|
30
37
|
const text = [
|
|
@@ -45,7 +52,15 @@ function estimateTokens(entry) {
|
|
|
45
52
|
* @param cwd Project root — used to compute relative file paths.
|
|
46
53
|
* @param budget Maximum total token budget (default 1500).
|
|
47
54
|
*/
|
|
48
|
-
export function buildCodebaseModel(graph, cwd, budget =
|
|
55
|
+
export function buildCodebaseModel(graph, cwd, budget = DEFAULT_CODEBASE_MODEL_TOKEN_BUDGET, identity) {
|
|
56
|
+
// Standalone callers may build an unpersisted projection without the
|
|
57
|
+
// review-graph coordinator. Persisted/session models pass the canonical
|
|
58
|
+
// review-graph identity explicitly; this fallback is only a local call-graph
|
|
59
|
+
// identity for builder-only use.
|
|
60
|
+
const modelIdentity = identity ?? {
|
|
61
|
+
reviewGraphVersion: graph.builtAt || "call-graph-unknown",
|
|
62
|
+
reviewGraphSignature: graph.builtAt || "call-graph-unknown",
|
|
63
|
+
};
|
|
49
64
|
// Sort all callee keys by in-degree descending
|
|
50
65
|
const ranked = [...graph.inDegree.entries()]
|
|
51
66
|
.filter(([, score]) => score >= MIN_IN_DEGREE)
|
|
@@ -56,16 +71,16 @@ export function buildCodebaseModel(graph, cwd, budget = DEFAULT_TOKEN_BUDGET) {
|
|
|
56
71
|
for (const [calleeKey, inDegree] of ranked) {
|
|
57
72
|
if (totalTokens >= budget)
|
|
58
73
|
break;
|
|
59
|
-
const
|
|
60
|
-
const name =
|
|
61
|
-
const filePath =
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
filePath
|
|
74
|
+
const parsedCallee = parseSymbolKey(calleeKey);
|
|
75
|
+
const name = parsedCallee.symbolName ?? calleeKey;
|
|
76
|
+
const filePath = parsedCallee.filePath;
|
|
77
|
+
// Keep this projection aligned with the shared file-role policy. The
|
|
78
|
+
// call graph is derived from the canonical review graph, so the model
|
|
79
|
+
// carries that graph's identity rather than inventing a second freshness
|
|
80
|
+
// policy.
|
|
81
|
+
const fileRole = detectFileRole(filePath);
|
|
82
|
+
if (fileRole === "test" || fileRole === "generated" ||
|
|
83
|
+
isExternalOrVendorFile(filePath, cwd) || isBuildArtifact(filePath)) {
|
|
69
84
|
continue;
|
|
70
85
|
}
|
|
71
86
|
// Deduplicate by name when the same function appears in multiple files
|
|
@@ -73,11 +88,11 @@ export function buildCodebaseModel(graph, cwd, budget = DEFAULT_TOKEN_BUDGET) {
|
|
|
73
88
|
continue;
|
|
74
89
|
seenNames.add(name);
|
|
75
90
|
const calls = [...(graph.callees.get(calleeKey) ?? new Set())]
|
|
76
|
-
.map((k) => k
|
|
91
|
+
.map((k) => parseSymbolKey(k).symbolName ?? k)
|
|
77
92
|
.filter(Boolean)
|
|
78
93
|
.slice(0, MAX_CALLS_PER_SYMBOL);
|
|
79
94
|
const calledBy = [...(graph.callers.get(calleeKey) ?? new Set())]
|
|
80
|
-
.map((k) => k
|
|
95
|
+
.map((k) => parseSymbolKey(k).symbolName ?? k)
|
|
81
96
|
.filter((n) => !n.startsWith("file:"))
|
|
82
97
|
.slice(0, MAX_CALLS_PER_SYMBOL);
|
|
83
98
|
const file = filePath
|
|
@@ -98,9 +113,12 @@ export function buildCodebaseModel(graph, cwd, budget = DEFAULT_TOKEN_BUDGET) {
|
|
|
98
113
|
totalTokens += tokens;
|
|
99
114
|
}
|
|
100
115
|
const allFiles = new Set([...graph.callers.keys(), ...graph.callees.keys()]
|
|
101
|
-
.map((k) => k
|
|
116
|
+
.map((k) => parseSymbolKey(k).filePath)
|
|
102
117
|
.filter(Boolean));
|
|
103
118
|
return {
|
|
119
|
+
version: CODEBASE_MODEL_VERSION,
|
|
120
|
+
reviewGraphVersion: modelIdentity.reviewGraphVersion,
|
|
121
|
+
reviewGraphSignature: modelIdentity.reviewGraphSignature,
|
|
104
122
|
generatedAt: new Date().toISOString(),
|
|
105
123
|
totalSymbols: graph.inDegree.size,
|
|
106
124
|
totalFiles: allFiles.size,
|
|
@@ -119,16 +137,27 @@ export function saveCodebaseModel(cwd, model) {
|
|
|
119
137
|
const cacheFile = cacheFilePath(cwd);
|
|
120
138
|
try {
|
|
121
139
|
fs.mkdirSync(path.dirname(cacheFile), { recursive: true });
|
|
122
|
-
|
|
123
|
-
|
|
140
|
+
writeFileAtomic(cacheFile, JSON.stringify(model));
|
|
141
|
+
writeFileAtomic(metaFilePath(cwd), JSON.stringify({ savedAt: new Date().toISOString(), entryCount: model.entries.length, totalTokens: model.totalTokens }));
|
|
124
142
|
}
|
|
125
143
|
catch {
|
|
126
144
|
// Non-fatal — next session rebuilds.
|
|
127
145
|
}
|
|
128
146
|
}
|
|
129
|
-
|
|
147
|
+
/** Load only a model matching the canonical identity it was derived from. */
|
|
148
|
+
export function loadCodebaseModel(cwd, expectedIdentity) {
|
|
130
149
|
try {
|
|
131
|
-
|
|
150
|
+
const raw = JSON.parse(fs.readFileSync(cacheFilePath(cwd), "utf-8"));
|
|
151
|
+
if (raw.version !== CODEBASE_MODEL_VERSION ||
|
|
152
|
+
typeof raw.reviewGraphVersion !== "string" || raw.reviewGraphVersion.length === 0 ||
|
|
153
|
+
typeof raw.reviewGraphSignature !== "string" || raw.reviewGraphSignature.length === 0 ||
|
|
154
|
+
typeof raw.generatedAt !== "string" ||
|
|
155
|
+
!Array.isArray(raw.entries))
|
|
156
|
+
return undefined;
|
|
157
|
+
if (raw.reviewGraphVersion !== expectedIdentity.reviewGraphVersion ||
|
|
158
|
+
raw.reviewGraphSignature !== expectedIdentity.reviewGraphSignature)
|
|
159
|
+
return undefined;
|
|
160
|
+
return raw;
|
|
132
161
|
}
|
|
133
162
|
catch {
|
|
134
163
|
return undefined;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared test-role predicate for COLLATERAL cascade/impact surfaces (#1080).
|
|
3
|
+
*
|
|
4
|
+
* The review graph is already tests-free (`review-graph/builder.ts` applies
|
|
5
|
+
* `detectFileRole(file) !== "test"` at its walk/incremental chokepoints), but
|
|
6
|
+
* several collateral surfaces re-derive neighbors from OTHER sources that never
|
|
7
|
+
* saw that filter — LSP reference expansion, module-level downstream expansion,
|
|
8
|
+
* the reverse-dependency index, and the passive-diagnostics fallback — so an
|
|
9
|
+
* unignored `*.test.*` / `tests/` file can still leak into cascade neighbor
|
|
10
|
+
* output, the formatted impact header/counts, the turn-end call-graph advisory,
|
|
11
|
+
* and the persisted call-graph-impact diagnostics.
|
|
12
|
+
*
|
|
13
|
+
* This composes the SINGLE existing structural classifier (`detectFileRole`) —
|
|
14
|
+
* no second matcher, no private test-filename list, no duplicated patterns. The
|
|
15
|
+
* project ignore matcher (`getProjectIgnoreMatcher`) is applied separately and
|
|
16
|
+
* unchanged at each surface; this predicate only adds the test-ROLE half of the
|
|
17
|
+
* display policy the review graph already enforces.
|
|
18
|
+
*
|
|
19
|
+
* Honesty (#1080 acceptance criteria):
|
|
20
|
+
* - A KNOWN `"test"` role is filtered from the collateral surface.
|
|
21
|
+
* - If role classification cannot be obtained (an unexpected throw), the
|
|
22
|
+
* candidate is RETAINED (`false`) rather than dropped — filtering must never
|
|
23
|
+
* invent a heuristic or silently drop a potentially actionable file, and must
|
|
24
|
+
* never convert an incomplete/indeterminate computation into a clean result.
|
|
25
|
+
*/
|
|
26
|
+
import { detectFileRole } from "./file-role.js";
|
|
27
|
+
/**
|
|
28
|
+
* True when `filePath` is a KNOWN test-role file that must not surface as a
|
|
29
|
+
* collateral cascade/impact neighbor. Path-based only (no content needed for
|
|
30
|
+
* the test-role branch of `detectFileRole`). Fail-open: any classifier error
|
|
31
|
+
* returns `false` (retain the candidate).
|
|
32
|
+
*/
|
|
33
|
+
export function isTestRoleCollateral(filePath) {
|
|
34
|
+
try {
|
|
35
|
+
return detectFileRole(filePath) === "test";
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* maintainability index (Halstead-free), LOC/comments, code entropy, and AI-slop
|
|
11
11
|
* indicators. These are silent metrics surfaced in the session summary.
|
|
12
12
|
*/
|
|
13
|
+
import { createSubsystemLogger } from "./extension-log.js";
|
|
13
14
|
import * as fs from "node:fs";
|
|
14
15
|
import * as path from "node:path";
|
|
15
16
|
import { firstChildOfType, withTreeSitterRoot, resolveTreeSitterLanguage, walk, } from "./tree-sitter-shared.js";
|
|
@@ -371,7 +372,7 @@ export class ComplexityClient {
|
|
|
371
372
|
log;
|
|
372
373
|
constructor(verbose = false) {
|
|
373
374
|
this.log = verbose
|
|
374
|
-
? (
|
|
375
|
+
? createSubsystemLogger("complexity")
|
|
375
376
|
: () => { };
|
|
376
377
|
}
|
|
377
378
|
/** True if the file's grammar has a complexity node mapping. */
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Capture this before the guard's other module initialization so host boot is
|
|
2
|
+
// not charged to extension evaluation (#1374). Lives in its own
|
|
3
|
+
// side-effect-free module (#1434 S3d) — see eval-timestamp.ts's docstring for
|
|
4
|
+
// why: `startup-timing.ts` needs this constant but must never trigger a
|
|
5
|
+
// console-guard install merely by importing it.
|
|
6
|
+
import { PI_LENS_EVAL_STARTED_MS } from "./eval-timestamp.js";
|
|
7
|
+
// #1333: install the console guard as an import side-effect so it runs before
|
|
8
|
+
// any other module's initialization code. Installing inside the extension
|
|
9
|
+
// factory is too late — by the time the factory runs, every static/transitive
|
|
10
|
+
// import has already evaluated, so a dependency writing to console during
|
|
11
|
+
// module init would still hit the TUI raw (#1338 review finding). This module
|
|
12
|
+
// MUST remain the first import of index.ts; the enforcement test pins that.
|
|
13
|
+
// installConsoleGuard() itself is idempotent and no-ops under test mode and
|
|
14
|
+
// PI_LENS_CONSOLE_GUARD=0.
|
|
15
|
+
// #1434: the guard captures only while pi-lens owns execution, so the module
|
|
16
|
+
// window must be open for the rest of the import graph. index.ts closes it on
|
|
17
|
+
// its last line; an unref'd backstop closes it if that never runs.
|
|
18
|
+
import { installConsoleGuard, openModuleLoadConsoleWindow, } from "./extension-log.js";
|
|
19
|
+
installConsoleGuard();
|
|
20
|
+
openModuleLoadConsoleWindow();
|
|
21
|
+
// Not re-exported: nothing should import the timestamp from this module.
|
|
22
|
+
// `clients/startup-timing.ts` imports it straight from `eval-timestamp.ts`,
|
|
23
|
+
// which is what keeps that import side-effect-free (S3d). The `void` keeps
|
|
24
|
+
// the import from looking unused to lint while documenting that this module
|
|
25
|
+
// only needs the ordering (evaluate eval-timestamp.ts first), not the value.
|
|
26
|
+
void PI_LENS_EVAL_STARTED_MS;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** Monotonic cooperative-work budgeting for event-loop-sensitive bulk work. */
|
|
2
|
+
export function createDeadline(budgetMs) {
|
|
3
|
+
const boundedBudget = Math.max(0, budgetMs);
|
|
4
|
+
let startedAt = performance.now();
|
|
5
|
+
return {
|
|
6
|
+
expired: () => performance.now() - startedAt >= boundedBudget,
|
|
7
|
+
reset: () => {
|
|
8
|
+
startedAt = performance.now();
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export async function yieldIfOverBudget(deadline) {
|
|
13
|
+
if (!deadline.expired())
|
|
14
|
+
return false;
|
|
15
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
16
|
+
deadline.reset();
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
export async function forEachCooperatively(items, fn, options) {
|
|
20
|
+
const deadline = createDeadline(options.budgetMs);
|
|
21
|
+
const assertContinuing = () => {
|
|
22
|
+
if (options.shouldContinue?.() === false) {
|
|
23
|
+
throw new Error(options.abortMessage ?? "cooperative work superseded");
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
let index = 0;
|
|
27
|
+
for (const item of items) {
|
|
28
|
+
// Check before each unit: this per-unit check is what bounds abort
|
|
29
|
+
// latency (one work unit, not an iteration checkpoint). The post-yield
|
|
30
|
+
// check below only matters after the final unit's yield, so superseded
|
|
31
|
+
// work is reported aborted rather than completed.
|
|
32
|
+
assertContinuing();
|
|
33
|
+
const result = fn(item, index++);
|
|
34
|
+
if (result !== undefined &&
|
|
35
|
+
typeof result.then === "function") {
|
|
36
|
+
await result;
|
|
37
|
+
}
|
|
38
|
+
if (deadline.expired() && (await yieldIfOverBudget(deadline))) {
|
|
39
|
+
assertContinuing();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -11,9 +11,11 @@
|
|
|
11
11
|
* Phase 1 ships Python via `vulture`. Future phases add Go/Rust/etc. by
|
|
12
12
|
* implementing DeadCodeClient and adding to getDeadCodeClients().
|
|
13
13
|
*/
|
|
14
|
+
import { createSubsystemLogger } from "./extension-log.js";
|
|
14
15
|
import * as path from "node:path";
|
|
15
16
|
import { findNearestMarkerRoot } from "./path-utils.js";
|
|
16
17
|
import { safeSpawnAsync } from "./safe-spawn.js";
|
|
18
|
+
import { classifyProbeFailure, createAvailabilityLatch, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
|
|
17
19
|
function emptyResult(language) {
|
|
18
20
|
return {
|
|
19
21
|
success: false,
|
|
@@ -38,6 +40,21 @@ const VULTURE_EXCLUDES = [
|
|
|
38
40
|
"*/__pycache__/*",
|
|
39
41
|
"*/.eggs/*",
|
|
40
42
|
];
|
|
43
|
+
// Decorators whose target is called by a framework, never by name in the tree.
|
|
44
|
+
// vulture cannot see those call sites, so every such symbol is a permanent
|
|
45
|
+
// false positive that reappears in every scan — the fastest way to teach a
|
|
46
|
+
// reader to stop reading the advisory. Glob-matched by vulture itself.
|
|
47
|
+
const VULTURE_IGNORE_DECORATORS = [
|
|
48
|
+
"@pytest.fixture",
|
|
49
|
+
"@pytest.fixture(*",
|
|
50
|
+
"@fixture",
|
|
51
|
+
"@fixture(*",
|
|
52
|
+
"@app.*",
|
|
53
|
+
"@router.*",
|
|
54
|
+
"@celery.task*",
|
|
55
|
+
"@task",
|
|
56
|
+
"@shared_task*",
|
|
57
|
+
];
|
|
41
58
|
// vulture line: `path/to/file.py:12: unused function 'foo' (60% confidence)`
|
|
42
59
|
const VULTURE_LINE = /^(.*?):(\d+): unused (\w[\w ]*?) '([^']+)' \((\d+)% confidence\)\s*$/;
|
|
43
60
|
/**
|
|
@@ -85,14 +102,19 @@ export function parseVultureOutput(output, root) {
|
|
|
85
102
|
export class PythonDeadCodeClient {
|
|
86
103
|
id = "python";
|
|
87
104
|
language = "Python";
|
|
88
|
-
|
|
105
|
+
/**
|
|
106
|
+
* Transient-aware memo (#1467): only a durable "vulture is not installed"
|
|
107
|
+
* verdict is remembered for the session. A probe that timed out expires and
|
|
108
|
+
* is re-probed, so vulture cannot be disabled for the process by one stall.
|
|
109
|
+
*/
|
|
110
|
+
availabilityLatch = createAvailabilityLatch();
|
|
89
111
|
resolved = null;
|
|
90
112
|
ensureInFlight = null;
|
|
91
113
|
inFlight = new Map();
|
|
92
114
|
log;
|
|
93
115
|
constructor(verbose = false) {
|
|
94
116
|
this.log = verbose
|
|
95
|
-
? (
|
|
117
|
+
? createSubsystemLogger("dead-code:python")
|
|
96
118
|
: () => { };
|
|
97
119
|
}
|
|
98
120
|
get minConfidence() {
|
|
@@ -102,6 +124,10 @@ export class PythonDeadCodeClient {
|
|
|
102
124
|
detect(cwd) {
|
|
103
125
|
return this.resolveProjectRoot(cwd) !== null;
|
|
104
126
|
}
|
|
127
|
+
owns(filePath) {
|
|
128
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
129
|
+
return ext === ".py" || ext === ".pyi";
|
|
130
|
+
}
|
|
105
131
|
/**
|
|
106
132
|
* Nearest dir with a Python project marker, never at/above $HOME and never
|
|
107
133
|
* escaping a VCS boundary — same containment rules as KnipClient so a scan
|
|
@@ -120,8 +146,9 @@ export class PythonDeadCodeClient {
|
|
|
120
146
|
], { boundaries: [".git", ".hg", ".svn"], homeDir: homeDirOverride });
|
|
121
147
|
}
|
|
122
148
|
async ensureAvailable() {
|
|
123
|
-
|
|
124
|
-
|
|
149
|
+
const memo = this.availabilityLatch.read();
|
|
150
|
+
if (memo !== null)
|
|
151
|
+
return memo;
|
|
125
152
|
if (this.ensureInFlight)
|
|
126
153
|
return this.ensureInFlight;
|
|
127
154
|
this.ensureInFlight = this.doEnsureAvailable();
|
|
@@ -145,19 +172,81 @@ export class PythonDeadCodeClient {
|
|
|
145
172
|
{ cmd: "python", prefix: ["-m", "vulture"] },
|
|
146
173
|
{ cmd: "python3", prefix: ["-m", "vulture"] },
|
|
147
174
|
];
|
|
175
|
+
// A timeout on ANY candidate means the machine, not the tool, answered —
|
|
176
|
+
// the run gets a bounded retry instead of a permanent skip (#1467).
|
|
177
|
+
let sawTransient = false;
|
|
178
|
+
let transientCause = "probe-timeout";
|
|
179
|
+
// Accumulated across ALL candidates, because the failure verdicts below
|
|
180
|
+
// are about the whole sweep rather than any one probe. Reporting zero
|
|
181
|
+
// here would erase the evidence that cracked #1467: four 5s probes and
|
|
182
|
+
// a stalled host look identical to an absent tool without these two
|
|
183
|
+
// numbers.
|
|
184
|
+
const sweepStartedAt = Date.now();
|
|
185
|
+
let sweepHostStallMs = 0;
|
|
148
186
|
for (const c of candidates) {
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
187
|
+
const sampler = startHostStallSampler();
|
|
188
|
+
const startedAt = Date.now();
|
|
189
|
+
let probe;
|
|
190
|
+
let hostStallMs;
|
|
191
|
+
try {
|
|
192
|
+
probe = await safeSpawnAsync(c.cmd, [...c.prefix, "--version"], {
|
|
193
|
+
timeout: 5000,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
finally {
|
|
197
|
+
hostStallMs = sampler.stop();
|
|
198
|
+
sweepHostStallMs += hostStallMs;
|
|
199
|
+
}
|
|
152
200
|
if (!probe.error && probe.status === 0) {
|
|
153
201
|
this.resolved = c;
|
|
154
|
-
this.
|
|
202
|
+
this.availabilityLatch.noteAvailable();
|
|
155
203
|
this.log(`vulture found: ${[c.cmd, ...c.prefix].join(" ")}`);
|
|
204
|
+
logAvailabilityDecision({
|
|
205
|
+
tool: "vulture",
|
|
206
|
+
verdict: "available",
|
|
207
|
+
outcome: "success",
|
|
208
|
+
cause: "ok",
|
|
209
|
+
elapsedMs: Date.now() - startedAt,
|
|
210
|
+
latched: true,
|
|
211
|
+
hostStallMs,
|
|
212
|
+
budgetMs: 5000,
|
|
213
|
+
});
|
|
156
214
|
return true;
|
|
157
215
|
}
|
|
216
|
+
const classified = classifyProbeFailure(probe, { hostStallMs });
|
|
217
|
+
if (classified.outcome === "transient") {
|
|
218
|
+
sawTransient = true;
|
|
219
|
+
transientCause = classified.cause;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (sawTransient) {
|
|
223
|
+
const retryAfterMs = this.availabilityLatch.noteUnavailable("transient", transientCause);
|
|
224
|
+
this.log("vulture probe timed out; will retry (not treated as missing)");
|
|
225
|
+
logAvailabilityDecision({
|
|
226
|
+
tool: "vulture",
|
|
227
|
+
verdict: "unavailable",
|
|
228
|
+
outcome: "transient",
|
|
229
|
+
cause: transientCause,
|
|
230
|
+
elapsedMs: Date.now() - sweepStartedAt,
|
|
231
|
+
hostStallMs: sweepHostStallMs,
|
|
232
|
+
latched: false,
|
|
233
|
+
retryAfterMs,
|
|
234
|
+
budgetMs: 5000,
|
|
235
|
+
});
|
|
236
|
+
return false;
|
|
158
237
|
}
|
|
159
|
-
this.
|
|
238
|
+
this.availabilityLatch.noteUnavailable("missing", "not-found");
|
|
160
239
|
this.log("vulture not installed; skipping (no auto-install)");
|
|
240
|
+
logAvailabilityDecision({
|
|
241
|
+
tool: "vulture",
|
|
242
|
+
verdict: "unavailable",
|
|
243
|
+
outcome: "missing",
|
|
244
|
+
cause: "not-found",
|
|
245
|
+
elapsedMs: Date.now() - sweepStartedAt,
|
|
246
|
+
hostStallMs: sweepHostStallMs,
|
|
247
|
+
latched: true,
|
|
248
|
+
budgetMs: 5000,
|
|
249
|
+
});
|
|
161
250
|
return false;
|
|
162
251
|
}
|
|
163
252
|
async analyze(cwd) {
|
|
@@ -192,6 +281,7 @@ export class PythonDeadCodeClient {
|
|
|
192
281
|
".",
|
|
193
282
|
`--min-confidence=${this.minConfidence}`,
|
|
194
283
|
`--exclude=${VULTURE_EXCLUDES.join(",")}`,
|
|
284
|
+
`--ignore-decorators=${VULTURE_IGNORE_DECORATORS.join(",")}`,
|
|
195
285
|
];
|
|
196
286
|
const result = await safeSpawnAsync(invocation.cmd, args, {
|
|
197
287
|
timeout: ANALYSIS_TIMEOUT_MS,
|
|
@@ -257,30 +347,46 @@ export function deadCodeIssueCount(result) {
|
|
|
257
347
|
result.unusedDeps.length +
|
|
258
348
|
result.unlistedDeps.length);
|
|
259
349
|
}
|
|
350
|
+
/** Every bucket flattened — delta diffing and diagnostic mapping want one list. */
|
|
351
|
+
export function deadCodeIssues(result) {
|
|
352
|
+
return [
|
|
353
|
+
...result.unusedExports,
|
|
354
|
+
...result.unusedFiles,
|
|
355
|
+
...result.unusedDeps,
|
|
356
|
+
...result.unlistedDeps,
|
|
357
|
+
];
|
|
358
|
+
}
|
|
260
359
|
/**
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
360
|
+
* Stable identity for diffing one scan against the previous one.
|
|
361
|
+
*
|
|
362
|
+
* Deliberately excludes the line number. An edit shifts every line below it,
|
|
363
|
+
* and the delta is then filtered to exactly the files the edit touched — so a
|
|
364
|
+
* line in the key turns each shifted pre-existing finding into a "newly unused"
|
|
365
|
+
* report under a heading that blames the agent's own edit for orphaning it.
|
|
366
|
+
* Inserting four lines above one real finding produced four false ones.
|
|
265
367
|
*/
|
|
266
|
-
export function
|
|
267
|
-
|
|
268
|
-
|
|
368
|
+
export function deadCodeIssueKey(issue) {
|
|
369
|
+
return `${issue.category}:${issue.file ?? ""}:${issue.name}`;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Format this turn's ATTRIBUTABLE delta — symbols that became unused in files
|
|
373
|
+
* the agent just edited — or "" when there is nothing to report. Mirrors the
|
|
374
|
+
* Knip advisory's contract deliberately: the project-wide list is not injected
|
|
375
|
+
* per turn (hundreds of pre-existing findings would drown the blockers and burn
|
|
376
|
+
* context every turn), it stays available on demand via lens_diagnostics.
|
|
377
|
+
*/
|
|
378
|
+
export function formatDeadCodeDelta(issues, language, max = 5) {
|
|
379
|
+
if (issues.length === 0)
|
|
269
380
|
return "";
|
|
270
|
-
|
|
271
|
-
for (const
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
if (r.unusedExports.length > maxPerLang) {
|
|
281
|
-
lines.push(` … and ${r.unusedExports.length - maxPerLang} more`);
|
|
282
|
-
}
|
|
381
|
+
let report = `💀 Newly unused ${language} symbols in files you edited — check if callers need updating (dead-code):\n`;
|
|
382
|
+
for (const issue of issues.slice(0, max)) {
|
|
383
|
+
const loc = issue.file
|
|
384
|
+
? `${issue.file}${issue.line ? `:${issue.line}` : ""}`
|
|
385
|
+
: "(unknown)";
|
|
386
|
+
report += ` ${loc} — unused ${issue.kind} ${issue.name}\n`;
|
|
387
|
+
}
|
|
388
|
+
if (issues.length > max) {
|
|
389
|
+
report += ` … and ${issues.length - max} more\n`;
|
|
283
390
|
}
|
|
284
|
-
return
|
|
285
|
-
lines.join("\n "));
|
|
391
|
+
return report;
|
|
286
392
|
}
|