@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
|
@@ -9,8 +9,11 @@
|
|
|
9
9
|
import * as fs from "node:fs";
|
|
10
10
|
import * as path from "node:path";
|
|
11
11
|
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { logSessionStart } from "../../../sessionstart-logger.js";
|
|
12
13
|
import { getGlobalPiLensDir } from "../../../file-utils.js";
|
|
13
|
-
import {
|
|
14
|
+
import { PathKeyedMap } from "../../../path-keyed-map.js";
|
|
15
|
+
import { normalizeEphemeralMapKey, normalizeMapKey, } from "../../../path-utils.js";
|
|
16
|
+
import { ensureTool, isSpawnableCommand, resetPathWalkMemo, } from "../../../installer/index.js";
|
|
14
17
|
import { getServersForFileWithConfig, isServerDisabled, } from "../../../lsp/config.js";
|
|
15
18
|
import { findGlobalBinary } from "../../../package-manager.js";
|
|
16
19
|
import { safeSpawnAsync } from "../../../safe-spawn.js";
|
|
@@ -83,6 +86,102 @@ export function createVenvFinder(command, windowsExt = "", quoteWindows = false)
|
|
|
83
86
|
return command;
|
|
84
87
|
};
|
|
85
88
|
}
|
|
89
|
+
/** Typed client-facing install seam for ordered/custom candidate probes. */
|
|
90
|
+
export async function resolveManagedToolClient(options) {
|
|
91
|
+
const probed = await options.probe();
|
|
92
|
+
if (probed.outcome !== "missing")
|
|
93
|
+
return probed;
|
|
94
|
+
if (!shouldAutoInstallTool(options.toolId))
|
|
95
|
+
return probed;
|
|
96
|
+
const state = installStateFor(options.cwd, options.toolId);
|
|
97
|
+
if (state.suppressed)
|
|
98
|
+
return probed;
|
|
99
|
+
const installed = await ensureTool(options.toolId);
|
|
100
|
+
if (!installed) {
|
|
101
|
+
noteInstallFailure(options.toolId, options.cwd);
|
|
102
|
+
return probed;
|
|
103
|
+
}
|
|
104
|
+
const value = await options.acceptInstalled(installed);
|
|
105
|
+
if (value === null) {
|
|
106
|
+
noteInstallFailure(options.toolId, options.cwd);
|
|
107
|
+
return { outcome: "non-installable" };
|
|
108
|
+
}
|
|
109
|
+
noteInstallSuccess(options.toolId, options.cwd);
|
|
110
|
+
return { outcome: "success", value };
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Child environment for managed-installable tools. Keeping this beside the
|
|
114
|
+
* probe/install seam makes managed npm shims visible to every standalone
|
|
115
|
+
* client, rather than only to Knip (#1289).
|
|
116
|
+
*/
|
|
117
|
+
export async function getManagedToolEnvironment(_toolId, cwd) {
|
|
118
|
+
let env;
|
|
119
|
+
try {
|
|
120
|
+
const { getToolEnvironment } = await import("../../../installer/index.js");
|
|
121
|
+
env = await getToolEnvironment();
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// Installer-isolated unit tests historically mock only ensureTool. The
|
|
125
|
+
// ambient fallback preserves that isolation; production always exports it.
|
|
126
|
+
env = { ...process.env };
|
|
127
|
+
}
|
|
128
|
+
if (!cwd)
|
|
129
|
+
return env;
|
|
130
|
+
const separator = process.platform === "win32" ? ";" : ":";
|
|
131
|
+
const currentPath = env.PATH || env.Path || process.env.PATH || "";
|
|
132
|
+
const localBin = path.join(cwd, "node_modules", ".bin");
|
|
133
|
+
const augmentedPath = `${localBin}${separator}${currentPath}`;
|
|
134
|
+
return {
|
|
135
|
+
...env,
|
|
136
|
+
PATH: augmentedPath,
|
|
137
|
+
...(process.platform === "win32" ? { Path: augmentedPath } : {}),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/** Read-only managed/PATH discovery for spawn-time resolution memos. */
|
|
141
|
+
export async function discoverManagedTool(toolId) {
|
|
142
|
+
return (await ensureTool(toolId, { allowInstall: false })) ?? null;
|
|
143
|
+
}
|
|
144
|
+
// This is session-scoped state, not a process-global tool/path cache. The cwd
|
|
145
|
+
// key is normalized by PathKeyedMap and is cleared at session_start. A failed
|
|
146
|
+
// install must not become an install attempt on every eligible file/turn.
|
|
147
|
+
const installAttemptsByCwd = new PathKeyedMap(normalizeMapKey);
|
|
148
|
+
const resolveInstallInFlightByCwd = new PathKeyedMap(normalizeEphemeralMapKey);
|
|
149
|
+
// Checkers are created by runner modules and may also be created dynamically.
|
|
150
|
+
// Keep the session reset as a generation rather than retaining every checker
|
|
151
|
+
// reset closure forever.
|
|
152
|
+
let availabilityStateGeneration = 0;
|
|
153
|
+
function installStateFor(cwd, toolId) {
|
|
154
|
+
let states = installAttemptsByCwd.get(cwd);
|
|
155
|
+
if (!states) {
|
|
156
|
+
states = new Map();
|
|
157
|
+
installAttemptsByCwd.set(cwd, states);
|
|
158
|
+
}
|
|
159
|
+
let state = states.get(toolId);
|
|
160
|
+
if (!state) {
|
|
161
|
+
state = { attempts: 0, suppressed: false };
|
|
162
|
+
states.set(toolId, state);
|
|
163
|
+
}
|
|
164
|
+
return state;
|
|
165
|
+
}
|
|
166
|
+
function noteInstallFailure(toolId, cwd) {
|
|
167
|
+
const state = installStateFor(cwd, toolId);
|
|
168
|
+
state.attempts += 1;
|
|
169
|
+
state.suppressed = true;
|
|
170
|
+
logSessionStart(`dispatch availability ${toolId}: install attempt ${state.attempts} failed; suppressing retries until the next session or a successful install`);
|
|
171
|
+
}
|
|
172
|
+
function noteInstallSuccess(toolId, cwd) {
|
|
173
|
+
const states = installAttemptsByCwd.get(cwd);
|
|
174
|
+
states?.delete(toolId);
|
|
175
|
+
if (states?.size === 0)
|
|
176
|
+
installAttemptsByCwd.delete(cwd);
|
|
177
|
+
}
|
|
178
|
+
/** Reset availability/install suppression at the session boundary. */
|
|
179
|
+
export function resetDispatchAvailabilityState() {
|
|
180
|
+
installAttemptsByCwd.clear();
|
|
181
|
+
resolveInstallInFlightByCwd.clear();
|
|
182
|
+
resetPathWalkMemo();
|
|
183
|
+
availabilityStateGeneration += 1;
|
|
184
|
+
}
|
|
86
185
|
/**
|
|
87
186
|
* Create a cached availability checker for a command.
|
|
88
187
|
* The checker will look for the command in venv first, then global.
|
|
@@ -93,49 +192,138 @@ export function createVenvFinder(command, windowsExt = "", quoteWindows = false)
|
|
|
93
192
|
* every machine, so toolchains with a non-standard version command must override
|
|
94
193
|
* this.
|
|
95
194
|
*/
|
|
96
|
-
export function createAvailabilityChecker(command, windowsExt = "", versionArgs = ["--version"]) {
|
|
97
|
-
const cacheByCwd = new
|
|
98
|
-
const inFlightByCwd = new
|
|
195
|
+
export function createAvailabilityChecker(command, windowsExt = "", versionArgs = ["--version"], options = {}) {
|
|
196
|
+
const cacheByCwd = new PathKeyedMap(normalizeEphemeralMapKey);
|
|
197
|
+
const inFlightByCwd = new PathKeyedMap(normalizeEphemeralMapKey);
|
|
198
|
+
let checkerGeneration = availabilityStateGeneration;
|
|
99
199
|
const findCommand = createVenvFinder(command, windowsExt, true);
|
|
200
|
+
function ensureCurrentGeneration() {
|
|
201
|
+
if (checkerGeneration === availabilityStateGeneration)
|
|
202
|
+
return;
|
|
203
|
+
cacheByCwd.clear();
|
|
204
|
+
inFlightByCwd.clear();
|
|
205
|
+
checkerGeneration = availabilityStateGeneration;
|
|
206
|
+
}
|
|
207
|
+
const reset = () => {
|
|
208
|
+
cacheByCwd.clear();
|
|
209
|
+
inFlightByCwd.clear();
|
|
210
|
+
checkerGeneration = availabilityStateGeneration;
|
|
211
|
+
};
|
|
100
212
|
function getCache(cwd) {
|
|
213
|
+
ensureCurrentGeneration();
|
|
101
214
|
const key = path.resolve(cwd || process.cwd());
|
|
102
215
|
const existing = cacheByCwd.get(key);
|
|
103
216
|
if (existing)
|
|
104
217
|
return existing;
|
|
105
|
-
const created = {
|
|
218
|
+
const created = {
|
|
219
|
+
available: null,
|
|
220
|
+
command: null,
|
|
221
|
+
outcome: null,
|
|
222
|
+
};
|
|
106
223
|
cacheByCwd.set(key, created);
|
|
107
224
|
return created;
|
|
108
225
|
}
|
|
109
226
|
async function isAvailableAsync(cwd) {
|
|
227
|
+
ensureCurrentGeneration();
|
|
110
228
|
const resolvedCwd = cwd || process.cwd();
|
|
111
229
|
const cache = getCache(resolvedCwd);
|
|
112
|
-
if (cache.available
|
|
113
|
-
return
|
|
230
|
+
if (cache.available === false)
|
|
231
|
+
return false;
|
|
232
|
+
if (cache.available === true && cache.command) {
|
|
233
|
+
if (await isSpawnableCommand(cache.command))
|
|
234
|
+
return true;
|
|
235
|
+
// Cached-positive spawn feedback: a removed absolute path or vanished
|
|
236
|
+
// PATH command must fall through to a fresh probe immediately.
|
|
237
|
+
cache.available = null;
|
|
238
|
+
cache.command = null;
|
|
239
|
+
cache.outcome = null;
|
|
240
|
+
}
|
|
114
241
|
const key = path.resolve(resolvedCwd);
|
|
115
242
|
const existing = inFlightByCwd.get(key);
|
|
116
243
|
if (existing)
|
|
117
244
|
return existing;
|
|
118
|
-
const
|
|
245
|
+
const promiseGeneration = checkerGeneration;
|
|
246
|
+
let promise;
|
|
247
|
+
promise = (async () => {
|
|
248
|
+
const fastPath = options.fastPath?.();
|
|
249
|
+
if (fastPath) {
|
|
250
|
+
cache.available = true;
|
|
251
|
+
cache.outcome = "success";
|
|
252
|
+
cache.command = fastPath;
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
// A bad/removed workspace must not be mistaken for a missing tool and
|
|
256
|
+
// trigger an install. This async probe stays off the synchronous dispatch
|
|
257
|
+
// burst and makes the failure taxonomy explicit at the seam.
|
|
258
|
+
try {
|
|
259
|
+
const cwdStat = await fs.promises.stat(resolvedCwd);
|
|
260
|
+
if (!cwdStat.isDirectory()) {
|
|
261
|
+
cache.available = false;
|
|
262
|
+
cache.outcome = "non-installable";
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
cache.available = false;
|
|
268
|
+
cache.outcome = "non-installable";
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
119
271
|
const cmd = findCommand(resolvedCwd);
|
|
272
|
+
const env = await options.environment?.(resolvedCwd);
|
|
120
273
|
const result = await safeSpawnAsync(cmd, versionArgs, {
|
|
121
|
-
timeout: 5000,
|
|
274
|
+
timeout: options.probeTimeout ?? 5000,
|
|
275
|
+
cwd: resolvedCwd,
|
|
276
|
+
env,
|
|
122
277
|
});
|
|
123
|
-
|
|
124
|
-
|
|
278
|
+
if (!result.error && result.status === 0) {
|
|
279
|
+
cache.available = true;
|
|
280
|
+
cache.outcome = "success";
|
|
125
281
|
cache.command = cmd;
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
cache.available = false;
|
|
285
|
+
const errorCode = result.error?.code;
|
|
286
|
+
if (result.spawnFailure?.kind === "tool-not-found") {
|
|
287
|
+
resetPathWalkMemo();
|
|
288
|
+
cache.outcome = "missing";
|
|
289
|
+
}
|
|
290
|
+
else if (result.failure === "timeout" ||
|
|
291
|
+
result.failure === "aborted" ||
|
|
292
|
+
result.failure === "signal" ||
|
|
293
|
+
result.spawnFailure?.kind === "killed" ||
|
|
294
|
+
errorCode === "EAGAIN" ||
|
|
295
|
+
errorCode === "EBUSY") {
|
|
296
|
+
cache.outcome = "transient";
|
|
126
297
|
}
|
|
127
|
-
|
|
298
|
+
else {
|
|
299
|
+
// A present command that rejects its version probe (or an EACCES/
|
|
300
|
+
// EINVAL/UNKNOWN failure) is not repaired by reinstalling it.
|
|
301
|
+
cache.outcome =
|
|
302
|
+
options.unclassifiedFailureOutcome ?? "non-installable";
|
|
303
|
+
}
|
|
304
|
+
return false;
|
|
128
305
|
})().finally(() => {
|
|
129
|
-
|
|
306
|
+
// A session reset clears this map and a caller may immediately start a
|
|
307
|
+
// replacement probe for the same cwd. The old promise must not delete
|
|
308
|
+
// that newer-generation entry when it settles.
|
|
309
|
+
if (checkerGeneration === promiseGeneration &&
|
|
310
|
+
inFlightByCwd.get(key) === promise) {
|
|
311
|
+
inFlightByCwd.delete(key);
|
|
312
|
+
}
|
|
130
313
|
});
|
|
131
314
|
inFlightByCwd.set(key, promise);
|
|
132
315
|
return promise;
|
|
133
316
|
}
|
|
134
317
|
function getCommand(cwd) {
|
|
318
|
+
ensureCurrentGeneration();
|
|
135
319
|
const cache = getCache(cwd || process.cwd());
|
|
136
320
|
return cache.command;
|
|
137
321
|
}
|
|
138
|
-
|
|
322
|
+
function getOutcome(cwd) {
|
|
323
|
+
ensureCurrentGeneration();
|
|
324
|
+
return getCache(cwd || process.cwd()).outcome;
|
|
325
|
+
}
|
|
326
|
+
return { isAvailableAsync, getCommand, getOutcome, reset };
|
|
139
327
|
}
|
|
140
328
|
/**
|
|
141
329
|
* Per-cwd cached availability probe for spawn signatures that don't fit
|
|
@@ -150,8 +338,13 @@ export function createAvailabilityChecker(command, windowsExt = "", versionArgs
|
|
|
150
338
|
* fresh probe for retry-after-install flows.
|
|
151
339
|
*/
|
|
152
340
|
export function createCwdCachedProbe(probe) {
|
|
153
|
-
const cacheByCwd = new
|
|
341
|
+
const cacheByCwd = new PathKeyedMap(normalizeEphemeralMapKey);
|
|
342
|
+
let probeGeneration = availabilityStateGeneration;
|
|
154
343
|
return (cwd) => {
|
|
344
|
+
if (probeGeneration !== availabilityStateGeneration) {
|
|
345
|
+
cacheByCwd.clear();
|
|
346
|
+
probeGeneration = availabilityStateGeneration;
|
|
347
|
+
}
|
|
155
348
|
const key = path.resolve(cwd || process.cwd());
|
|
156
349
|
const existing = cacheByCwd.get(key);
|
|
157
350
|
if (existing)
|
|
@@ -209,7 +402,8 @@ export async function resolveToolCommandWithInstallFallback(cwd, toolId, timeout
|
|
|
209
402
|
async function verifyOrInstallCommand(command, toolId, cwd, versionArgs = ["--version"], timeout = 5000) {
|
|
210
403
|
// Skip the --version spawn when the command isn't even on disk — the ~μs
|
|
211
404
|
// stat/PATH walk beats a guaranteed-to-fail spawn round-trip.
|
|
212
|
-
|
|
405
|
+
const spawnable = await isSpawnableCommand(command);
|
|
406
|
+
if (spawnable) {
|
|
213
407
|
const versionCheck = await safeSpawnAsync(command, versionArgs, {
|
|
214
408
|
timeout,
|
|
215
409
|
cwd,
|
|
@@ -217,16 +411,22 @@ async function verifyOrInstallCommand(command, toolId, cwd, versionArgs = ["--ve
|
|
|
217
411
|
if (!versionCheck.error && versionCheck.status === 0) {
|
|
218
412
|
return command;
|
|
219
413
|
}
|
|
414
|
+
// A command that was found but rejected its probe is not fixed by a
|
|
415
|
+
// reinstall. This also covers permissions and malformed shims.
|
|
416
|
+
return null;
|
|
220
417
|
}
|
|
221
|
-
if (!shouldAutoInstallTool(toolId))
|
|
418
|
+
if (!shouldAutoInstallTool(toolId))
|
|
419
|
+
return null;
|
|
420
|
+
const state = installStateFor(cwd, toolId);
|
|
421
|
+
if (state.suppressed)
|
|
222
422
|
return null;
|
|
423
|
+
const installed = await ensureTool(toolId);
|
|
424
|
+
if (installed) {
|
|
425
|
+
noteInstallSuccess(toolId, cwd);
|
|
426
|
+
return installed;
|
|
223
427
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
// --version here booted the tool's node shim once per invocation for
|
|
227
|
-
// zero new information; a broken install now surfaces as the runner's
|
|
228
|
-
// own spawn error instead of a silent null.
|
|
229
|
-
return (await ensureTool(toolId)) ?? null;
|
|
428
|
+
noteInstallFailure(toolId, cwd);
|
|
429
|
+
return null;
|
|
230
430
|
}
|
|
231
431
|
export async function resolveCommandArgsWithInstallFallback(command, toolId, cwd, versionArgs = ["--version"], timeout = 5000) {
|
|
232
432
|
const versionCheck = await safeSpawnAsync(command.cmd, [...command.args, ...versionArgs], { timeout, cwd });
|
|
@@ -245,16 +445,55 @@ export async function resolveCommandArgsWithInstallFallback(command, toolId, cwd
|
|
|
245
445
|
export async function resolveCommandWithInstallFallback(command, toolId, cwd, versionArgs = ["--version"], timeout = 5000) {
|
|
246
446
|
return verifyOrInstallCommand(command, toolId, cwd, versionArgs, timeout);
|
|
247
447
|
}
|
|
248
|
-
|
|
448
|
+
async function resolveAvailableOrInstallUnshared(checker, toolId, cwd) {
|
|
249
449
|
const available = await checker.isAvailableAsync(cwd);
|
|
250
450
|
if (available) {
|
|
251
451
|
return checker.getCommand(cwd);
|
|
252
452
|
}
|
|
253
|
-
|
|
453
|
+
// Only a typed ENOENT/missing-command result is repairable by installing.
|
|
454
|
+
// Probe failures caused by bad cwd, permissions, rejected flags, aborts, and
|
|
455
|
+
// timeouts are unavailable/non-installable and must not enter an install loop.
|
|
456
|
+
if (checker.getOutcome?.(cwd) !== "missing")
|
|
457
|
+
return null;
|
|
458
|
+
if (!shouldAutoInstallTool(toolId))
|
|
459
|
+
return null;
|
|
460
|
+
const state = installStateFor(cwd, toolId);
|
|
461
|
+
if (state.suppressed) {
|
|
254
462
|
return null;
|
|
255
463
|
}
|
|
256
464
|
const installed = await ensureTool(toolId);
|
|
257
|
-
|
|
465
|
+
if (installed) {
|
|
466
|
+
noteInstallSuccess(toolId, cwd);
|
|
467
|
+
checker.reset?.();
|
|
468
|
+
return installed;
|
|
469
|
+
}
|
|
470
|
+
noteInstallFailure(toolId, cwd);
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
/** Share the complete probe/install transaction for each cwd/tool pair. */
|
|
474
|
+
export function resolveAvailableOrInstall(checker, toolId, cwd) {
|
|
475
|
+
const key = normalizeEphemeralMapKey(cwd);
|
|
476
|
+
let byTool = resolveInstallInFlightByCwd.get(key);
|
|
477
|
+
if (!byTool) {
|
|
478
|
+
byTool = new Map();
|
|
479
|
+
resolveInstallInFlightByCwd.set(key, byTool);
|
|
480
|
+
}
|
|
481
|
+
const existing = byTool.get(toolId);
|
|
482
|
+
if (existing)
|
|
483
|
+
return existing;
|
|
484
|
+
const generation = availabilityStateGeneration;
|
|
485
|
+
const promise = resolveAvailableOrInstallUnshared(checker, toolId, cwd).finally(() => {
|
|
486
|
+
if (generation !== availabilityStateGeneration)
|
|
487
|
+
return;
|
|
488
|
+
const current = resolveInstallInFlightByCwd.get(key);
|
|
489
|
+
if (current?.get(toolId) === promise) {
|
|
490
|
+
current.delete(toolId);
|
|
491
|
+
if (current.size === 0)
|
|
492
|
+
resolveInstallInFlightByCwd.delete(key);
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
byTool.set(toolId, promise);
|
|
496
|
+
return promise;
|
|
258
497
|
}
|
|
259
498
|
// =============================================================================
|
|
260
499
|
// SHARED AST-GREP AVAILABILITY
|
|
@@ -302,7 +541,18 @@ function buildSgLocalBins() {
|
|
|
302
541
|
return bins;
|
|
303
542
|
}
|
|
304
543
|
let sgAvailableInFlight = null;
|
|
544
|
+
let sgAvailabilityGeneration = availabilityStateGeneration;
|
|
545
|
+
function ensureCurrentSgGeneration() {
|
|
546
|
+
if (sgAvailabilityGeneration === availabilityStateGeneration)
|
|
547
|
+
return;
|
|
548
|
+
sgAvailable = null;
|
|
549
|
+
sgCmd = null;
|
|
550
|
+
sgCmdArgs = [];
|
|
551
|
+
sgAvailableInFlight = null;
|
|
552
|
+
sgAvailabilityGeneration = availabilityStateGeneration;
|
|
553
|
+
}
|
|
305
554
|
export async function isSgAvailableAsync() {
|
|
555
|
+
ensureCurrentSgGeneration();
|
|
306
556
|
if (sgAvailable !== null)
|
|
307
557
|
return sgAvailable;
|
|
308
558
|
if (sgAvailableInFlight)
|
|
@@ -352,6 +602,7 @@ export async function isSgAvailableAsync() {
|
|
|
352
602
|
return sgAvailableInFlight;
|
|
353
603
|
}
|
|
354
604
|
export function getSgCommand() {
|
|
605
|
+
ensureCurrentSgGeneration();
|
|
355
606
|
return {
|
|
356
607
|
cmd: sgCmd ?? "npx",
|
|
357
608
|
args: sgCmdArgs.length ? sgCmdArgs : ["--no", "--", "ast-grep"],
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True when a tool both failed and produced nothing to parse — it never got as
|
|
3
|
+
* far as linting the file. Callers map this to `skipped`, never `succeeded`.
|
|
4
|
+
*
|
|
5
|
+
* Guarding on `result.error` alone covers only half of it: safe-spawn sets
|
|
6
|
+
* `error` for spawn/timeout/signal failures and resolves a NORMAL exit with no
|
|
7
|
+
* `error` at any status (see `SpawnResult.failure` — "nonzero exit statuses are
|
|
8
|
+
* not spawn failures"). An unknown subcommand, a rejected flag, or a config
|
|
9
|
+
* that fails to load exits non-zero with the message on stderr and an empty
|
|
10
|
+
* stdout, so an `error`-only guard falls through, parses "" into zero
|
|
11
|
+
* diagnostics, and reports a clean file that nothing ever checked.
|
|
12
|
+
*
|
|
13
|
+
* Tools that exit non-zero *because* they found something are unaffected: their
|
|
14
|
+
* findings are on stdout, so the output half of the test is false.
|
|
15
|
+
*
|
|
16
|
+
* `output` defaults to stdout. Runners that parse stderr too pass the exact
|
|
17
|
+
* string they are about to hand the parser, so "nothing to parse" means both
|
|
18
|
+
* streams rather than just one.
|
|
19
|
+
*/
|
|
20
|
+
export function spawnFailedWithNoOutput(result, output = result.stdout) {
|
|
21
|
+
return (Boolean(result.error) || result.status !== 0) && !output?.trim();
|
|
22
|
+
}
|
|
@@ -15,6 +15,7 @@ import { createHash } from "node:crypto";
|
|
|
15
15
|
import * as fs from "node:fs";
|
|
16
16
|
import * as path from "node:path";
|
|
17
17
|
import yaml from "../../deps/js-yaml.js";
|
|
18
|
+
import { BoundedLruCache } from "../../bounded-cache.js";
|
|
18
19
|
// --- Constants ---
|
|
19
20
|
/** Overly broad patterns that match everything (cause false positive explosions) */
|
|
20
21
|
export const OVERLY_BROAD_PATTERNS = [
|
|
@@ -28,10 +29,10 @@ export const OVERLY_BROAD_PATTERNS = [
|
|
|
28
29
|
/** Maximum complexity score for rules in blockingOnly mode */
|
|
29
30
|
export const MAX_BLOCKING_RULE_COMPLEXITY = 8;
|
|
30
31
|
// --- Caches ---
|
|
31
|
-
const rulesCache = new
|
|
32
|
-
const blockingRulesCache = new
|
|
33
|
-
const contentRulesCache = new
|
|
34
|
-
const contentBlockingRulesCache = new
|
|
32
|
+
const rulesCache = new BoundedLruCache(64);
|
|
33
|
+
const blockingRulesCache = new BoundedLruCache(64);
|
|
34
|
+
const contentRulesCache = new BoundedLruCache(64);
|
|
35
|
+
const contentBlockingRulesCache = new BoundedLruCache(64);
|
|
35
36
|
// --- Public API ---
|
|
36
37
|
export function clearRulesCache() {
|
|
37
38
|
rulesCache.clear();
|
|
@@ -24,9 +24,10 @@
|
|
|
24
24
|
import { logBusEvent } from "./bus-events-logger.js";
|
|
25
25
|
import { isBusPublishEnabled } from "./bus-publish.js";
|
|
26
26
|
import { normalizeFilePath } from "./path-utils.js";
|
|
27
|
+
import { createLiveBusEmitter, recordStaleBusFailure, } from "./live-bus-emitter.js";
|
|
27
28
|
export const BUS_DISPOSITION_EVENT = "pilens:diagnostic:disposition";
|
|
28
29
|
export const BUS_DISPOSITION_VERSION = 1;
|
|
29
|
-
|
|
30
|
+
const liveEmitter = createLiveBusEmitter();
|
|
30
31
|
let hasLoggedFailure = false;
|
|
31
32
|
let hasLoggedUnwired = false;
|
|
32
33
|
let hasLoggedDisabled = false;
|
|
@@ -38,11 +39,14 @@ let hasLoggedDisabled = false;
|
|
|
38
39
|
* the MCP server path run in (no pi host, no `pi.events`).
|
|
39
40
|
*/
|
|
40
41
|
export function wireDispositionBusEmitter(emitFn) {
|
|
41
|
-
|
|
42
|
+
liveEmitter.wire(emitFn);
|
|
43
|
+
}
|
|
44
|
+
export function wireDispositionBusEmitterGetter(getter) {
|
|
45
|
+
liveEmitter.wireGetter(getter);
|
|
42
46
|
}
|
|
43
47
|
/** Test-only: reset module state between test files. */
|
|
44
48
|
export function _resetDispositionPublishForTests() {
|
|
45
|
-
|
|
49
|
+
liveEmitter.reset();
|
|
46
50
|
hasLoggedFailure = false;
|
|
47
51
|
hasLoggedUnwired = false;
|
|
48
52
|
hasLoggedDisabled = false;
|
|
@@ -63,6 +67,7 @@ export function publishDisposition(args) {
|
|
|
63
67
|
}
|
|
64
68
|
return;
|
|
65
69
|
}
|
|
70
|
+
const busEmit = liveEmitter.get();
|
|
66
71
|
if (!busEmit) {
|
|
67
72
|
if (!hasLoggedUnwired) {
|
|
68
73
|
hasLoggedUnwired = true;
|
|
@@ -88,6 +93,7 @@ export function publishDisposition(args) {
|
|
|
88
93
|
reason: args.reason,
|
|
89
94
|
};
|
|
90
95
|
busEmit(BUS_DISPOSITION_EVENT, payload);
|
|
96
|
+
hasLoggedFailure = false;
|
|
91
97
|
logBusEvent({
|
|
92
98
|
event: BUS_DISPOSITION_EVENT,
|
|
93
99
|
outcome: "emitted",
|
|
@@ -103,6 +109,7 @@ export function publishDisposition(args) {
|
|
|
103
109
|
});
|
|
104
110
|
if (!hasLoggedFailure) {
|
|
105
111
|
hasLoggedFailure = true;
|
|
112
|
+
recordStaleBusFailure(BUS_DISPOSITION_EVENT, err);
|
|
106
113
|
args.dbg?.(`disposition-publish: pilens:diagnostic:disposition emit failed (further failures suppressed): ${err}`);
|
|
107
114
|
}
|
|
108
115
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synchronous locked read-modify-write commit seam for behavior-gating JSON
|
|
3
|
+
* stores shared by multiple pi-lens processes.
|
|
4
|
+
*
|
|
5
|
+
* The authoritative read happens only after the bounded PID lock is held; the
|
|
6
|
+
* caller merges only its delta, publication is a throwing atomic replacement,
|
|
7
|
+
* and success telemetry runs only after publication succeeds.
|
|
8
|
+
*
|
|
9
|
+
* Short synchronous commits use a file lock. Awaited commits use the shared
|
|
10
|
+
* quarantine directory lock, preserving bounded contention and stale-owner
|
|
11
|
+
* recovery without allowing a late release to delete a replacement owner.
|
|
12
|
+
*/
|
|
13
|
+
import * as fs from "node:fs";
|
|
14
|
+
import fsp from "node:fs/promises";
|
|
15
|
+
import { writeFileAtomic, writeFileAtomicAsync } from "./atomic-write.js";
|
|
16
|
+
import { acquireBoundedPidFileLock, acquireQuarantinePidFileLock, } from "./bounded-pid-file-lock.js";
|
|
17
|
+
function readLocked(path) {
|
|
18
|
+
try {
|
|
19
|
+
return fs.readFileSync(path, "utf8");
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function commitDurableStore(options) {
|
|
26
|
+
const release = options.onContention === "skip-log"
|
|
27
|
+
? acquireBoundedPidFileLock(`${options.path}.lock`, {
|
|
28
|
+
waitMs: options.waitMs,
|
|
29
|
+
retryMs: options.retryMs,
|
|
30
|
+
timeoutMessage: options.timeoutMessage,
|
|
31
|
+
onContention: "skip-log",
|
|
32
|
+
logContention: options.logContention,
|
|
33
|
+
})
|
|
34
|
+
: acquireBoundedPidFileLock(`${options.path}.lock`, {
|
|
35
|
+
waitMs: options.waitMs,
|
|
36
|
+
retryMs: options.retryMs,
|
|
37
|
+
timeoutMessage: options.timeoutMessage,
|
|
38
|
+
onContention: "throw",
|
|
39
|
+
});
|
|
40
|
+
if (!release)
|
|
41
|
+
return undefined;
|
|
42
|
+
let committed;
|
|
43
|
+
try {
|
|
44
|
+
const current = options.deserialize(readLocked(options.path));
|
|
45
|
+
committed = options.merge(current);
|
|
46
|
+
writeFileAtomic(options.path, options.serialize(committed), {
|
|
47
|
+
bestEffort: false,
|
|
48
|
+
});
|
|
49
|
+
options.afterWriteLocked?.(committed);
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
release();
|
|
53
|
+
}
|
|
54
|
+
return committed;
|
|
55
|
+
}
|
|
56
|
+
async function readLockedAsync(path) {
|
|
57
|
+
try {
|
|
58
|
+
return await fsp.readFile(path, "utf8");
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
if (error.code === "ENOENT")
|
|
62
|
+
return undefined;
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export async function commitDurableStoreAsync(options) {
|
|
67
|
+
const release = options.onContention === "skip-log"
|
|
68
|
+
? await acquireQuarantinePidFileLock(`${options.path}.lock`, {
|
|
69
|
+
waitMs: options.waitMs,
|
|
70
|
+
retryMs: options.retryMs,
|
|
71
|
+
staleMs: options.staleMs,
|
|
72
|
+
timeoutMessage: options.timeoutMessage,
|
|
73
|
+
onContention: "skip-log",
|
|
74
|
+
logContention: options.logContention,
|
|
75
|
+
})
|
|
76
|
+
: await acquireQuarantinePidFileLock(`${options.path}.lock`, {
|
|
77
|
+
waitMs: options.waitMs,
|
|
78
|
+
retryMs: options.retryMs,
|
|
79
|
+
staleMs: options.staleMs,
|
|
80
|
+
timeoutMessage: options.timeoutMessage,
|
|
81
|
+
onContention: "throw",
|
|
82
|
+
});
|
|
83
|
+
if (!release)
|
|
84
|
+
return undefined;
|
|
85
|
+
try {
|
|
86
|
+
const current = options.deserialize(await readLockedAsync(options.path));
|
|
87
|
+
const committed = options.merge(current);
|
|
88
|
+
await writeFileAtomicAsync(options.path, options.serialize(committed), {
|
|
89
|
+
bestEffort: false,
|
|
90
|
+
});
|
|
91
|
+
await options.afterWriteLocked?.(committed);
|
|
92
|
+
return committed;
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
await release();
|
|
96
|
+
}
|
|
97
|
+
}
|