@herbertgao/pi-extensions 2026.8.3 → 2026.8.5
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 +45 -4
- package/examples/pi-footer.json +275 -0
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +10 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +10 -2
- 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/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 +5 -8
- 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 +8 -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 +15 -2
- 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/schedule-menu.ts +9 -8
- package/node_modules/@herbertgao/pi-subagents/src/ui/select-item.ts +48 -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 +8 -7
- package/node_modules/@narumitw/pi-btw/package.json +2 -2
- package/node_modules/@narumitw/pi-btw/src/menu.ts +1 -0
- package/node_modules/pi-footer/CHANGELOG.md +145 -0
- package/node_modules/pi-footer/LICENSE +21 -0
- package/node_modules/pi-footer/README.md +354 -0
- package/node_modules/pi-footer/package.json +68 -0
- package/node_modules/pi-footer/src/cache.ts +106 -0
- package/node_modules/pi-footer/src/colors.ts +237 -0
- package/node_modules/pi-footer/src/config.ts +202 -0
- package/node_modules/pi-footer/src/event-widgets.ts +41 -0
- package/node_modules/pi-footer/src/extension-statuses.ts +102 -0
- package/node_modules/pi-footer/src/git.ts +135 -0
- package/node_modules/pi-footer/src/index.ts +268 -0
- package/node_modules/pi-footer/src/metrics.ts +141 -0
- package/node_modules/pi-footer/src/presets.ts +383 -0
- package/node_modules/pi-footer/src/render.ts +114 -0
- package/node_modules/pi-footer/src/separators.ts +52 -0
- package/node_modules/pi-footer/src/types.ts +122 -0
- package/node_modules/pi-footer/src/ui/color-level-confirm.ts +22 -0
- package/node_modules/pi-footer/src/ui/color-options.ts +195 -0
- package/node_modules/pi-footer/src/ui/config-lifecycle.ts +61 -0
- package/node_modules/pi-footer/src/ui/edit-colors.ts +13 -0
- package/node_modules/pi-footer/src/ui/events.ts +35 -0
- package/node_modules/pi-footer/src/ui/extension-status-picker.ts +60 -0
- package/node_modules/pi-footer/src/ui/extension-statuses.ts +73 -0
- package/node_modules/pi-footer/src/ui/fields.ts +252 -0
- package/node_modules/pi-footer/src/ui/global-menu.ts +126 -0
- package/node_modules/pi-footer/src/ui/helpers.ts +44 -0
- package/node_modules/pi-footer/src/ui/layout.ts +17 -0
- package/node_modules/pi-footer/src/ui/line-list.ts +19 -0
- package/node_modules/pi-footer/src/ui/model.ts +64 -0
- package/node_modules/pi-footer/src/ui/navigation.ts +29 -0
- package/node_modules/pi-footer/src/ui/option-edit.ts +93 -0
- package/node_modules/pi-footer/src/ui/overlay-render.ts +103 -0
- package/node_modules/pi-footer/src/ui/screen-context.ts +18 -0
- package/node_modules/pi-footer/src/ui/screen-controller.ts +27 -0
- package/node_modules/pi-footer/src/ui/screen-render.ts +32 -0
- package/node_modules/pi-footer/src/ui/screen-state.ts +20 -0
- package/node_modules/pi-footer/src/ui/screen.ts +243 -0
- package/node_modules/pi-footer/src/ui/screens/add-widget.ts +92 -0
- package/node_modules/pi-footer/src/ui/screens/confirm-exit.ts +52 -0
- package/node_modules/pi-footer/src/ui/screens/controller.ts +12 -0
- package/node_modules/pi-footer/src/ui/screens/edit-colors.ts +68 -0
- package/node_modules/pi-footer/src/ui/screens/edit-widget.ts +115 -0
- package/node_modules/pi-footer/src/ui/screens/extension-status-row.ts +61 -0
- package/node_modules/pi-footer/src/ui/screens/global.ts +75 -0
- package/node_modules/pi-footer/src/ui/screens/line-list.ts +108 -0
- package/node_modules/pi-footer/src/ui/screens/main.ts +99 -0
- package/node_modules/pi-footer/src/ui/screens/terminal.ts +121 -0
- package/node_modules/pi-footer/src/ui/screens/widget-list.ts +154 -0
- package/node_modules/pi-footer/src/ui/terminal-menu.ts +32 -0
- package/node_modules/pi-footer/src/ui/theme.ts +48 -0
- package/node_modules/pi-footer/src/ui/title-bar.ts +100 -0
- package/node_modules/pi-footer/src/ui/widget-actions.ts +76 -0
- package/node_modules/pi-footer/src/ui.ts +66 -0
- package/node_modules/pi-footer/src/widgets/context.ts +28 -0
- package/node_modules/pi-footer/src/widgets/core/active-tools.ts +17 -0
- package/node_modules/pi-footer/src/widgets/core/context-window.ts +24 -0
- package/node_modules/pi-footer/src/widgets/core/cwd-basename.ts +19 -0
- package/node_modules/pi-footer/src/widgets/core/cwd.ts +108 -0
- package/node_modules/pi-footer/src/widgets/core/event.ts +34 -0
- package/node_modules/pi-footer/src/widgets/core/external-status.ts +102 -0
- package/node_modules/pi-footer/src/widgets/core/model-provider.ts +20 -0
- package/node_modules/pi-footer/src/widgets/core/model.ts +35 -0
- package/node_modules/pi-footer/src/widgets/core/provider.ts +17 -0
- package/node_modules/pi-footer/src/widgets/core/session-name.ts +17 -0
- package/node_modules/pi-footer/src/widgets/core/text-verbosity.ts +17 -0
- package/node_modules/pi-footer/src/widgets/core/thinking-level.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/ahead-behind.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/branch.ts +81 -0
- package/node_modules/pi-footer/src/widgets/git/clean.ts +19 -0
- package/node_modules/pi-footer/src/widgets/git/deletions.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/diff.ts +38 -0
- package/node_modules/pi-footer/src/widgets/git/insertions.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/remote.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/root.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/sha.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/staged.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/status.ts +19 -0
- package/node_modules/pi-footer/src/widgets/git/unstaged.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/untracked.ts +17 -0
- package/node_modules/pi-footer/src/widgets/instance.ts +118 -0
- package/node_modules/pi-footer/src/widgets/layout/custom-text.ts +18 -0
- package/node_modules/pi-footer/src/widgets/layout/flex-separator.ts +32 -0
- package/node_modules/pi-footer/src/widgets/layout/separator.ts +57 -0
- package/node_modules/pi-footer/src/widgets/layout/spacer.ts +31 -0
- package/node_modules/pi-footer/src/widgets/options.ts +122 -0
- package/node_modules/pi-footer/src/widgets/project/runtime.ts +361 -0
- package/node_modules/pi-footer/src/widgets/registry.ts +223 -0
- package/node_modules/pi-footer/src/widgets/session/assistant-messages.ts +17 -0
- package/node_modules/pi-footer/src/widgets/session/compactions.ts +17 -0
- package/node_modules/pi-footer/src/widgets/session/elapsed.ts +18 -0
- package/node_modules/pi-footer/src/widgets/session/last-activity.ts +18 -0
- package/node_modules/pi-footer/src/widgets/session/messages.ts +19 -0
- package/node_modules/pi-footer/src/widgets/session/session-id.ts +17 -0
- package/node_modules/pi-footer/src/widgets/session/session-start.ts +18 -0
- package/node_modules/pi-footer/src/widgets/session/tool-results.ts +17 -0
- package/node_modules/pi-footer/src/widgets/session/total-messages.ts +19 -0
- package/node_modules/pi-footer/src/widgets/session/total-time.ts +18 -0
- package/node_modules/pi-footer/src/widgets/session/user-messages.ts +17 -0
- package/node_modules/pi-footer/src/widgets/store.ts +26 -0
- package/node_modules/pi-footer/src/widgets/tokens/cache-hit-rate.ts +55 -0
- package/node_modules/pi-footer/src/widgets/tokens/cache-read.ts +18 -0
- package/node_modules/pi-footer/src/widgets/tokens/cache-write.ts +18 -0
- package/node_modules/pi-footer/src/widgets/tokens/context-bar.ts +108 -0
- package/node_modules/pi-footer/src/widgets/tokens/context-length.ts +26 -0
- package/node_modules/pi-footer/src/widgets/tokens/context-remaining.ts +29 -0
- package/node_modules/pi-footer/src/widgets/tokens/context.ts +29 -0
- package/node_modules/pi-footer/src/widgets/tokens/cost.ts +51 -0
- package/node_modules/pi-footer/src/widgets/tokens/input-speed.ts +25 -0
- package/node_modules/pi-footer/src/widgets/tokens/input-tokens.ts +18 -0
- package/node_modules/pi-footer/src/widgets/tokens/output-speed.ts +25 -0
- package/node_modules/pi-footer/src/widgets/tokens/output-tokens.ts +18 -0
- package/node_modules/pi-footer/src/widgets/tokens/tokens.ts +20 -0
- package/node_modules/pi-footer/src/widgets/tokens/total-speed.ts +25 -0
- package/node_modules/pi-footer/src/widgets/tokens/total-tokens.ts +18 -0
- package/node_modules/pi-footer/src/widgets/types.ts +181 -0
- package/node_modules/pi-footer/src/widgets/utils/colors.ts +11 -0
- package/node_modules/pi-footer/src/widgets/utils/context.ts +112 -0
- package/node_modules/pi-footer/src/widgets/utils/session.ts +19 -0
- package/node_modules/pi-footer/src/widgets/utils/token-format.ts +78 -0
- package/node_modules/pi-lens/CHANGELOG.md +2440 -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/agent-behavior-client.js +21 -4
- 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 +29 -25
- 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-publish.js +10 -3
- 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/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 +14 -0
- package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +2 -1
- 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 +347 -75
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +61 -11
- package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +63 -20
- 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 +1085 -681
- 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 +14 -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/lazy-installer.js +3 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +277 -26
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -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 +10 -3
- package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
- package/node_modules/pi-lens/dist/clients/extension-log.js +139 -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/format-events-publish.js +16 -3
- 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 +681 -11
- package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
- package/node_modules/pi-lens/dist/clients/go-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +8 -0
- 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 +390 -99
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +31 -54
- package/node_modules/pi-lens/dist/clients/knip-client.js +15 -36
- 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 +23 -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 +27 -4
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
- package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +30 -0
- package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/client.js +493 -48
- package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +86 -0
- package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
- package/node_modules/pi-lens/dist/clients/lsp/index.js +1807 -780
- 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 +196 -32
- 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 +123 -32
- package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
- package/node_modules/pi-lens/dist/clients/mcp/session.js +216 -8
- 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/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 +47 -13
- 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/runner-adapters/call-graph-impact.js +8 -0
- 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/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/runtime-agent-end.js +107 -73
- package/node_modules/pi-lens/dist/clients/runtime-context.js +36 -12
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +74 -6
- package/node_modules/pi-lens/dist/clients/runtime-session.js +405 -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 +218 -23
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +237 -64
- package/node_modules/pi-lens/dist/clients/rust-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
- package/node_modules/pi-lens/dist/clients/sg-runner.js +284 -174
- package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +224 -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 +1 -1
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +38 -8
- package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
- package/node_modules/pi-lens/dist/clients/tool-policy.js +388 -39
- package/node_modules/pi-lens/dist/clients/tool-render.js +155 -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 +33 -13
- package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
- package/node_modules/pi-lens/dist/clients/word-index.js +391 -64
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
- package/node_modules/pi-lens/dist/index.js +49361 -36888
- package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
- package/node_modules/pi-lens/dist/mcp/server.js +211 -44
- package/node_modules/pi-lens/dist/tools/ast-grep-search.js +93 -27
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +200 -36
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +153 -40
- 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/docs/agent-guide.md +2 -2
- 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 +5 -2
- 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 +31 -2
- package/node_modules/pi-lens/docs/globalconfig.md +45 -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 +12 -1
- package/node_modules/pi-lens/docs/tools.md +4 -1
- package/node_modules/pi-lens/docs/usage.md +18 -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-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-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-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-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 +55 -2
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +25 -0
- package/node_modules/pi-mcp-adapter/README.md +34 -3
- package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
- package/node_modules/pi-mcp-adapter/commands.ts +15 -7
- package/node_modules/pi-mcp-adapter/direct-tools.ts +54 -7
- 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/metadata-cache.ts +27 -4
- package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
- package/node_modules/pi-mcp-adapter/package.json +3 -3
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
- package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
- package/node_modules/pi-mcp-adapter/server-manager.ts +36 -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 +42 -4
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
- package/node_modules/pi-mcp-adapter/types.ts +93 -27
- package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
- package/package.json +17 -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
|
@@ -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,14 @@
|
|
|
1
|
+
import { performance } from "node:perf_hooks";
|
|
2
|
+
// Capture this before the guard's other module initialization so host boot is
|
|
3
|
+
// not charged to extension evaluation (#1374).
|
|
4
|
+
export const PI_LENS_EVAL_STARTED_MS = performance.now();
|
|
5
|
+
// #1333: install the console guard as an import side-effect so it runs before
|
|
6
|
+
// any other module's initialization code. Installing inside the extension
|
|
7
|
+
// factory is too late — by the time the factory runs, every static/transitive
|
|
8
|
+
// import has already evaluated, so a dependency writing to console during
|
|
9
|
+
// module init would still hit the TUI raw (#1338 review finding). This module
|
|
10
|
+
// MUST remain the first import of index.ts; the enforcement test pins that.
|
|
11
|
+
// installConsoleGuard() itself is idempotent and no-ops under test mode and
|
|
12
|
+
// PI_LENS_CONSOLE_GUARD=0.
|
|
13
|
+
import { installConsoleGuard } from "./extension-log.js";
|
|
14
|
+
installConsoleGuard();
|
|
@@ -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,6 +11,7 @@
|
|
|
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";
|
|
@@ -92,7 +93,7 @@ export class PythonDeadCodeClient {
|
|
|
92
93
|
log;
|
|
93
94
|
constructor(verbose = false) {
|
|
94
95
|
this.log = verbose
|
|
95
|
-
? (
|
|
96
|
+
? createSubsystemLogger("dead-code:python")
|
|
96
97
|
: () => { };
|
|
97
98
|
}
|
|
98
99
|
get minConfidence() {
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handle-origin tracer, institutionalized from the hand-rolled `async_hooks`
|
|
3
|
+
* instrumentation the #1097 investigation used to find a leaked, ref'd
|
|
4
|
+
* `setTimeout` keeping a `--print --no-session` process alive after
|
|
5
|
+
* `agent_settled` (root-caused/fixed in #1110). The #1097 reporter explicitly
|
|
6
|
+
* asked for a built-in active-resource dump (#1123 item 4) so the next
|
|
7
|
+
* "process won't exit" report is a one-line diagnostic instead of a fresh
|
|
8
|
+
* multi-hour expedition.
|
|
9
|
+
*
|
|
10
|
+
* `PI_LENS_DEBUG_HANDLES=1` (read ONCE at module load — i.e. must be set
|
|
11
|
+
* before pi-lens starts, not toggled mid-session):
|
|
12
|
+
* - OFF (default): every export below is a no-op that returns immediately
|
|
13
|
+
* on the flag check — no writer constructed, no async_hooks hook
|
|
14
|
+
* installed, zero allocations on any hot path. `dumpActiveHandles` is
|
|
15
|
+
* called from live lifecycle hooks (index.ts `agent_settled` /
|
|
16
|
+
* `session_shutdown`), so this module must cost nothing when unset.
|
|
17
|
+
* - ON: (a) `dumpActiveHandles(label)` calls `process.getActiveResourcesInfo()`,
|
|
18
|
+
* tallies counts by resource type, and writes one ndjson line to
|
|
19
|
+
* `~/.pi-lens/debug-handles.log` via the standard `createNdjsonLogger`
|
|
20
|
+
* family (size-bounded, log-cleanup registered — the same rotation
|
|
21
|
+
* bounds every other pi-lens log gets, #1101-era registry via
|
|
22
|
+
* clients/ndjson-logger.ts's self-registration + clients/log-cleanup.ts).
|
|
23
|
+
* (b) an `async_hooks` creation-site tracker, installed at module load
|
|
24
|
+
* ONLY when the flag is on, records a trimmed `Error().stack` per
|
|
25
|
+
* tracked resource id for timer/socket/handle async-hook types and
|
|
26
|
+
* removes the entry on `destroy`. This is a real per-resource-creation
|
|
27
|
+
* cost — `async_hooks` hooks run on every init/destroy in the process —
|
|
28
|
+
* so it is strictly opt-in and documented here as a debug-only expense,
|
|
29
|
+
* never installed unless the flag was on before this module first loads.
|
|
30
|
+
*
|
|
31
|
+
* Bounded tracker (the one-axis-rule, AGENTS.md "resource bounded along one
|
|
32
|
+
* axis but unbounded along another"): the creation-site map is capped at
|
|
33
|
+
* `TRACKER_MAX_ENTRIES` before adding a new entry past the cap — never
|
|
34
|
+
* unbounded growth even in a pathological timer-storm session. Eviction
|
|
35
|
+
* protects the first `TRACKER_PROTECTED_COUNT` insertion-order entries and
|
|
36
|
+
* evicts the oldest entry OUTSIDE that protected zone instead: a #1097-style
|
|
37
|
+
* leaked handle is typically among the EARLIEST created in a session (it's
|
|
38
|
+
* the one nothing ever cleaned up), so a naive drop-oldest policy would let a
|
|
39
|
+
* later burst past the cap evict exactly the evidence this tracer exists to
|
|
40
|
+
* capture, silently. `evictedCount` (a running total, always present on the
|
|
41
|
+
* dump entry once the tracker is installed) makes any eviction — protected
|
|
42
|
+
* or not — an explicit, visible fact instead of a silent gap in attribution.
|
|
43
|
+
*/
|
|
44
|
+
import * as asyncHooks from "node:async_hooks";
|
|
45
|
+
import * as path from "node:path";
|
|
46
|
+
import { getGlobalPiLensDir } from "./file-utils.js";
|
|
47
|
+
import { getMaxLogSizeMB } from "./log-cleanup.js";
|
|
48
|
+
import { createNdjsonLogger } from "./ndjson-logger.js";
|
|
49
|
+
/** Read once at module load — see module docstring. Not re-read per call. */
|
|
50
|
+
const DEBUG_HANDLES_ENABLED = process.env.PI_LENS_DEBUG_HANDLES === "1";
|
|
51
|
+
const DEBUG_HANDLES_LOG_FILE = path.join(getGlobalPiLensDir(), "debug-handles.log");
|
|
52
|
+
/** Cap on tracked in-flight creation sites (bounded past this). */
|
|
53
|
+
export const TRACKER_MAX_ENTRIES = 500;
|
|
54
|
+
/** The first N insertion-order entries are never evicted — see the module
|
|
55
|
+
* docstring's "leaked handles skew early" rationale. Must stay well below
|
|
56
|
+
* `TRACKER_MAX_ENTRIES` so there is always an evictable, unprotected tail. */
|
|
57
|
+
export const TRACKER_PROTECTED_COUNT = 100;
|
|
58
|
+
/** async_hooks `type` strings worth attributing: timers, sockets, process/
|
|
59
|
+
* signal handles — the shapes #1097's hand-rolled tracer actually needed
|
|
60
|
+
* (a `Timeout`). Excludes high-frequency non-handle async resources
|
|
61
|
+
* (`PROMISE`, `FSREQCALLBACK`, ...) that would dominate the map with no
|
|
62
|
+
* "why is the process still alive" signal. */
|
|
63
|
+
const TRACKED_TYPE_RE = /^(Timeout|Immediate|TCPWRAP|TCPSOCKETWRAP|TCPCONNECTWRAP|PIPEWRAP|PIPECONNECTWRAP|UDPWRAP|UDPSENDWRAP|TTYWRAP|ProcessWrap|Signal)/;
|
|
64
|
+
function trimStack(raw) {
|
|
65
|
+
if (!raw)
|
|
66
|
+
return "";
|
|
67
|
+
// Drop the "Error" header line and this module's own init() frame; keep a
|
|
68
|
+
// bounded number of caller frames — enough to identify the call site
|
|
69
|
+
// without unbounded per-entry memory.
|
|
70
|
+
const lines = raw.split("\n").slice(1);
|
|
71
|
+
const callerLines = lines.filter((line) => !line.includes("debug-handles.js") && !line.includes("debug-handles.ts"));
|
|
72
|
+
return callerLines.slice(0, 8).join("\n").slice(0, 2000);
|
|
73
|
+
}
|
|
74
|
+
/** Running total of tracker evictions this process (never reset) — surfaced
|
|
75
|
+
* on every dump entry once the tracker is installed, per the P2 fix: an
|
|
76
|
+
* eviction must always be a visible fact, never a silent attribution gap. */
|
|
77
|
+
let trackedEvictedCount = 0;
|
|
78
|
+
/** Test-only accessor for the running eviction total. */
|
|
79
|
+
export function _evictedCountForTest() {
|
|
80
|
+
return trackedEvictedCount;
|
|
81
|
+
}
|
|
82
|
+
/** Record one tracked resource's creation site. Once the bounded cap is
|
|
83
|
+
* reached, evicts the oldest entry OUTSIDE the first `TRACKER_PROTECTED_COUNT`
|
|
84
|
+
* insertion-order entries (Map iteration order == insertion order) — see the
|
|
85
|
+
* module docstring for why the earliest entries are protected instead of
|
|
86
|
+
* evicted. Shared by the real async_hooks `init` callback and the test-only
|
|
87
|
+
* simulator so the eviction policy has exactly one implementation. */
|
|
88
|
+
function recordTrackedInit(map, asyncId, type) {
|
|
89
|
+
if (!TRACKED_TYPE_RE.test(type))
|
|
90
|
+
return;
|
|
91
|
+
if (map.size >= TRACKER_MAX_ENTRIES) {
|
|
92
|
+
let index = 0;
|
|
93
|
+
let evictKey;
|
|
94
|
+
for (const key of map.keys()) {
|
|
95
|
+
if (index >= TRACKER_PROTECTED_COUNT) {
|
|
96
|
+
evictKey = key;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
index++;
|
|
100
|
+
}
|
|
101
|
+
if (evictKey !== undefined) {
|
|
102
|
+
map.delete(evictKey);
|
|
103
|
+
trackedEvictedCount++;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
map.set(asyncId, { type, stack: trimStack(new Error().stack) });
|
|
107
|
+
}
|
|
108
|
+
function installHandleTracker() {
|
|
109
|
+
const map = new Map();
|
|
110
|
+
const hook = asyncHooks.createHook({
|
|
111
|
+
init(asyncId, type) {
|
|
112
|
+
recordTrackedInit(map, asyncId, type);
|
|
113
|
+
},
|
|
114
|
+
destroy(asyncId) {
|
|
115
|
+
map.delete(asyncId);
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
hook.enable();
|
|
119
|
+
return map;
|
|
120
|
+
}
|
|
121
|
+
// Only ever constructed/installed when the flag is on — the entire cost of
|
|
122
|
+
// this module when unset is this one boolean check per export below.
|
|
123
|
+
const trackedResources = DEBUG_HANDLES_ENABLED ? installHandleTracker() : null;
|
|
124
|
+
const writer = DEBUG_HANDLES_ENABLED
|
|
125
|
+
? createNdjsonLogger({
|
|
126
|
+
filePath: DEBUG_HANDLES_LOG_FILE,
|
|
127
|
+
maxBytes: getMaxLogSizeMB() * 1024 * 1024,
|
|
128
|
+
})
|
|
129
|
+
: null;
|
|
130
|
+
/** True iff `PI_LENS_DEBUG_HANDLES=1` was set when this module first loaded. */
|
|
131
|
+
export function isDebugHandlesEnabled() {
|
|
132
|
+
return DEBUG_HANDLES_ENABLED;
|
|
133
|
+
}
|
|
134
|
+
export function getDebugHandlesLogPath() {
|
|
135
|
+
return DEBUG_HANDLES_LOG_FILE;
|
|
136
|
+
}
|
|
137
|
+
/** Resolve once all enqueued debug-handles writes are on disk (tests). */
|
|
138
|
+
export function flushDebugHandlesLog() {
|
|
139
|
+
return writer ? writer.flush() : Promise.resolve();
|
|
140
|
+
}
|
|
141
|
+
function safeGetActiveResourcesInfo() {
|
|
142
|
+
try {
|
|
143
|
+
const fn = process.getActiveResourcesInfo;
|
|
144
|
+
return typeof fn === "function" ? fn.call(process) : [];
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/** One representative stack per tracked TYPE (not per resource id) — keeps
|
|
151
|
+
* each dump line bounded regardless of how many live entries the capped
|
|
152
|
+
* map currently holds. */
|
|
153
|
+
function summarizeCreationSites(map) {
|
|
154
|
+
const byType = new Map();
|
|
155
|
+
for (const { type, stack } of map.values()) {
|
|
156
|
+
const existing = byType.get(type);
|
|
157
|
+
if (existing) {
|
|
158
|
+
existing.count++;
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
byType.set(type, { type, count: 1, sampleStack: stack });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return [...byType.values()];
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Dump `process.getActiveResourcesInfo()` counts-by-type (and, when the
|
|
168
|
+
* tracker was installed at startup, per-type creation-site attribution) to
|
|
169
|
+
* `debug-handles.log`. A pure no-op — no allocation past the flag check —
|
|
170
|
+
* when `PI_LENS_DEBUG_HANDLES` was unset at startup.
|
|
171
|
+
*/
|
|
172
|
+
export function dumpActiveHandles(label) {
|
|
173
|
+
if (!DEBUG_HANDLES_ENABLED || !writer)
|
|
174
|
+
return;
|
|
175
|
+
const resources = safeGetActiveResourcesInfo();
|
|
176
|
+
const counts = {};
|
|
177
|
+
for (const type of resources) {
|
|
178
|
+
counts[type] = (counts[type] ?? 0) + 1;
|
|
179
|
+
}
|
|
180
|
+
const entry = {
|
|
181
|
+
ts: new Date().toISOString(),
|
|
182
|
+
label,
|
|
183
|
+
total: resources.length,
|
|
184
|
+
counts,
|
|
185
|
+
};
|
|
186
|
+
if (trackedResources) {
|
|
187
|
+
entry.creationSites = summarizeCreationSites(trackedResources);
|
|
188
|
+
entry.evictedCount = trackedEvictedCount;
|
|
189
|
+
}
|
|
190
|
+
writer.log(entry);
|
|
191
|
+
}
|
|
192
|
+
/** Test-only view of the tracker's current size (bounded-cap assertions). */
|
|
193
|
+
export function _trackedResourceCountForTest() {
|
|
194
|
+
return trackedResources?.size ?? 0;
|
|
195
|
+
}
|
|
196
|
+
/** Test-only: is `asyncId` still tracked? (protected-vs-evicted assertions). */
|
|
197
|
+
export function _isTrackedForTest(asyncId) {
|
|
198
|
+
return trackedResources?.has(asyncId) ?? false;
|
|
199
|
+
}
|
|
200
|
+
/** Test-only: synthesize an `init` call against the installed tracker without
|
|
201
|
+
* depending on real async resource creation timing. No-op when disabled. */
|
|
202
|
+
export function _simulateTrackedInitForTest(asyncId, type) {
|
|
203
|
+
if (!trackedResources)
|
|
204
|
+
return;
|
|
205
|
+
recordTrackedInit(trackedResources, asyncId, type);
|
|
206
|
+
}
|