@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
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Refs: #130, #131, #132
|
|
14
14
|
*/
|
|
15
|
+
import { createSubsystemLogger } from "./extension-log.js";
|
|
15
16
|
import { safeSpawnAsync } from "./safe-spawn.js";
|
|
16
17
|
export class SecurityScanClient {
|
|
17
18
|
toolName;
|
|
@@ -27,7 +28,7 @@ export class SecurityScanClient {
|
|
|
27
28
|
constructor(toolName, verbose = false) {
|
|
28
29
|
this.toolName = toolName;
|
|
29
30
|
this.log = verbose
|
|
30
|
-
? (
|
|
31
|
+
? createSubsystemLogger(toolName)
|
|
31
32
|
: () => { };
|
|
32
33
|
}
|
|
33
34
|
/**
|
|
@@ -13,7 +13,7 @@ import * as path from "node:path";
|
|
|
13
13
|
import { writeFileAtomicAsync } from "./atomic-write.js";
|
|
14
14
|
import { getProjectDataDir } from "./file-utils.js";
|
|
15
15
|
import { readJsonCacheAsync } from "./json-cache-read.js";
|
|
16
|
-
const STATE_VERSION = 1;
|
|
16
|
+
export const STATE_VERSION = 1;
|
|
17
17
|
export function sessionStartMode(reason, hasPendingForkSnapshot) {
|
|
18
18
|
if (reason === "fork" && hasPendingForkSnapshot)
|
|
19
19
|
return "fork";
|
|
@@ -52,7 +52,8 @@ export async function saveSessionState(cwd, sessionId, widget, readGuard) {
|
|
|
52
52
|
// bestEffort (default): a failed write/rename just means this snapshot is
|
|
53
53
|
// lost, matching this store's documented "start clean" fallback — never
|
|
54
54
|
// throw for the caller. (Tmp naming is now the shared
|
|
55
|
-
// `${target}.tmp-${pid}` shape
|
|
55
|
+
// `${target}.tmp-${pid}-${seq}` shape (unique per call, not just per process,
|
|
56
|
+
// since #1205) rather than this site's former
|
|
56
57
|
// `${file}.${pid}.tmp` — no behavioral difference: nothing reads the
|
|
57
58
|
// intermediate tmp filename.)
|
|
58
59
|
await writeFileAtomicAsync(file, JSON.stringify(payload));
|
|
@@ -4,25 +4,14 @@
|
|
|
4
4
|
* Extracted from AstGrepClient to simplify the main client.
|
|
5
5
|
* Handles: spawn, spawnSync, temp dir management, JSON parsing.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { createSubsystemLogger } from "./extension-log.js";
|
|
8
8
|
import * as fs from "node:fs";
|
|
9
9
|
import * as os from "node:os";
|
|
10
10
|
import * as path from "node:path";
|
|
11
|
-
import { getSgCommand } from "./dispatch/runners/utils/runner-helpers.js";
|
|
11
|
+
import { getSgCommand, resolveManagedToolClient, } from "./dispatch/runners/utils/runner-helpers.js";
|
|
12
12
|
import { getProjectIgnoreGlobs } from "./file-utils.js";
|
|
13
13
|
import { findGlobalBinary } from "./package-manager.js";
|
|
14
14
|
import { safeSpawnAsync } from "./safe-spawn.js";
|
|
15
|
-
/**
|
|
16
|
-
* Escape an argument for Windows cmd.exe shell execution.
|
|
17
|
-
* Handles spaces, quotes, and special characters.
|
|
18
|
-
*/
|
|
19
|
-
function escapeWindowsArg(arg) {
|
|
20
|
-
// If no special characters, return as-is
|
|
21
|
-
if (!/[\s"]/.test(arg))
|
|
22
|
-
return arg;
|
|
23
|
-
// Escape quotes by doubling them
|
|
24
|
-
return `"${arg.replace(/"/g, '""')}"`;
|
|
25
|
-
}
|
|
26
15
|
/**
|
|
27
16
|
* Build the `bash -c` argv that runs `cmd` with `allArgs` as POSITIONAL
|
|
28
17
|
* parameters. The script is the constant `"$0" "$@"`, so bash re-emits the
|
|
@@ -43,6 +32,8 @@ function sgExcludeArgsForProject(rootDir) {
|
|
|
43
32
|
`!${glob}`,
|
|
44
33
|
]);
|
|
45
34
|
}
|
|
35
|
+
const DEFAULT_EXEC_TIMEOUT_MS = 30_000;
|
|
36
|
+
const MAX_SG_OUTPUT_BYTES = 8 * 1024 * 1024;
|
|
46
37
|
/**
|
|
47
38
|
* Format metavariable captures for display below a match line.
|
|
48
39
|
* Single captures: $VAR=x $NAME=foo
|
|
@@ -72,6 +63,33 @@ function formatMetaVarCaptures(mv) {
|
|
|
72
63
|
return undefined;
|
|
73
64
|
return ` ${parts.join(" ")}`;
|
|
74
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Parse ast-grep `--json` stdout into a match array, or return `null` when the
|
|
68
|
+
* text is not a match payload. ast-grep emits either a JSON array or (rarely) a
|
|
69
|
+
* single object; both are normalized to an array. A JSON scalar/`null` is
|
|
70
|
+
* rejected (returns `null`) so a hypothetical error-report scalar on stdout can
|
|
71
|
+
* never be misread as a phantom match.
|
|
72
|
+
*/
|
|
73
|
+
function tryParseSgMatches(stdout) {
|
|
74
|
+
try {
|
|
75
|
+
const parsed = JSON.parse(stdout);
|
|
76
|
+
if (Array.isArray(parsed))
|
|
77
|
+
return parsed;
|
|
78
|
+
if (parsed !== null && typeof parsed === "object")
|
|
79
|
+
return [parsed];
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function tryParseNonZeroSgMatches(result) {
|
|
87
|
+
if (result.outputTruncated || !result.stdout.trim())
|
|
88
|
+
return null;
|
|
89
|
+
// Trimmed on purpose: a leading BOM survives JSON.parse's whitespace
|
|
90
|
+
// tolerance but String.trim() strips it (matches the pre-refactor guard).
|
|
91
|
+
return tryParseSgMatches(result.stdout.trim());
|
|
92
|
+
}
|
|
75
93
|
export class SgRunner {
|
|
76
94
|
log;
|
|
77
95
|
sgPath = null;
|
|
@@ -80,7 +98,7 @@ export class SgRunner {
|
|
|
80
98
|
ensureInFlight = null;
|
|
81
99
|
constructor(verbose = false) {
|
|
82
100
|
this.log = verbose
|
|
83
|
-
? (
|
|
101
|
+
? createSubsystemLogger("sg-runner")
|
|
84
102
|
: () => { };
|
|
85
103
|
}
|
|
86
104
|
/**
|
|
@@ -156,15 +174,19 @@ export class SgRunner {
|
|
|
156
174
|
return true;
|
|
157
175
|
}
|
|
158
176
|
}
|
|
159
|
-
// Step 4:
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
177
|
+
// Step 4: install via the typed shared seam, then validate the returned
|
|
178
|
+
// absolute binary before publishing it.
|
|
179
|
+
const installed = await resolveManagedToolClient({
|
|
180
|
+
toolId: "ast-grep",
|
|
181
|
+
cwd: process.cwd(),
|
|
182
|
+
probe: async () => ({ outcome: "missing" }),
|
|
183
|
+
acceptInstalled: async (installedPath) => (await this.probeCommand(installedPath, [])) ? installedPath : null,
|
|
184
|
+
});
|
|
185
|
+
if (installed.outcome === "success") {
|
|
186
|
+
this.sgPath = installed.value;
|
|
165
187
|
this.sgArgsPrefix = [];
|
|
166
188
|
this.available = true;
|
|
167
|
-
this.log(`ast-grep auto-installed: ${
|
|
189
|
+
this.log(`ast-grep auto-installed: ${installed.value}`);
|
|
168
190
|
return true;
|
|
169
191
|
}
|
|
170
192
|
this.available = false;
|
|
@@ -206,7 +228,8 @@ export class SgRunner {
|
|
|
206
228
|
for (const root of searchRoots) {
|
|
207
229
|
const candidate = path.join(root, pkgName, exeName);
|
|
208
230
|
try {
|
|
209
|
-
if (fs.existsSync(candidate) &&
|
|
231
|
+
if (fs.existsSync(candidate) &&
|
|
232
|
+
(await this.probeCommand(candidate, []))) {
|
|
210
233
|
return candidate;
|
|
211
234
|
}
|
|
212
235
|
}
|
|
@@ -233,7 +256,8 @@ export class SgRunner {
|
|
|
233
256
|
return undefined;
|
|
234
257
|
for (const name of ["ast-grep", "sg"]) {
|
|
235
258
|
const candidate = path.join(prefix, "bin", name);
|
|
236
|
-
if (fs.existsSync(candidate) &&
|
|
259
|
+
if (fs.existsSync(candidate) &&
|
|
260
|
+
(await this.probeCommand(candidate, []))) {
|
|
237
261
|
return candidate;
|
|
238
262
|
}
|
|
239
263
|
}
|
|
@@ -271,147 +295,140 @@ export class SgRunner {
|
|
|
271
295
|
argsPrefix: this.sgArgsPrefix,
|
|
272
296
|
};
|
|
273
297
|
}
|
|
274
|
-
|
|
298
|
+
failureForSpawnResult(result) {
|
|
299
|
+
if (result.failure === "aborted")
|
|
300
|
+
return "aborted";
|
|
301
|
+
switch (result.spawnFailure?.kind) {
|
|
302
|
+
case "tool-not-found":
|
|
303
|
+
return "unavailable";
|
|
304
|
+
case "timeout":
|
|
305
|
+
return "timeout";
|
|
306
|
+
case "killed":
|
|
307
|
+
return result.failure === "aborted" ? "aborted" : "cli-failure";
|
|
308
|
+
case "cwd-unresolvable":
|
|
309
|
+
case "permission-denied":
|
|
310
|
+
case "spawn-failed":
|
|
311
|
+
case undefined:
|
|
312
|
+
return result.error ? "cli-failure" : undefined;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
formatPatternError(stderr, args) {
|
|
316
|
+
if (stderr.includes("Multiple AST nodes are detected")) {
|
|
317
|
+
return (`Invalid AST pattern: The pattern appears to contain multiple AST nodes or is malformed.\n` +
|
|
318
|
+
`Common causes:\n` +
|
|
319
|
+
` 1. Missing parentheses: use it($TEST) not it"test"\n` +
|
|
320
|
+
` 2. Raw text without structure: use console.log($MSG) not just "console.log"\n` +
|
|
321
|
+
` 3. Unclosed quotes or brackets\n\n` +
|
|
322
|
+
`Original error: ${stderr}`);
|
|
323
|
+
}
|
|
324
|
+
if (stderr.includes("Cannot parse query")) {
|
|
325
|
+
return (`Pattern syntax error: The pattern could not be parsed as valid code.\n` +
|
|
326
|
+
`Tips:\n` +
|
|
327
|
+
` - Patterns must be valid ${args.includes("--lang") ? args[args.indexOf("--lang") + 1] : "language"} syntax\n` +
|
|
328
|
+
` - Use metavariables like $NAME, $ARGS for variable parts\n` +
|
|
329
|
+
` - Example: 'function $NAME($$$PARAMS) { $$$BODY }'\n\n` +
|
|
330
|
+
`Original error: ${stderr}`);
|
|
331
|
+
}
|
|
332
|
+
return stderr;
|
|
333
|
+
}
|
|
334
|
+
async execRaw(args, timeout = DEFAULT_EXEC_TIMEOUT_MS, options = {}) {
|
|
275
335
|
const command = this.getSgCommand();
|
|
276
|
-
const result = await safeSpawnAsync(command.cmd, [...command.argsPrefix, ...args], {
|
|
336
|
+
const result = await safeSpawnAsync(command.cmd, [...command.argsPrefix, ...args], {
|
|
337
|
+
timeout,
|
|
338
|
+
deadlineAt: options.deadlineAt,
|
|
339
|
+
signal: options.signal,
|
|
340
|
+
maxOutputBytes: MAX_SG_OUTPUT_BYTES,
|
|
341
|
+
});
|
|
342
|
+
const failure = this.failureForSpawnResult(result);
|
|
277
343
|
return {
|
|
278
344
|
stdout: result.stdout,
|
|
279
345
|
stderr: result.stderr,
|
|
280
346
|
status: result.status,
|
|
281
347
|
error: result.error?.message,
|
|
348
|
+
failure,
|
|
349
|
+
...(result.outputTruncated ? { outputTruncated: true } : {}),
|
|
282
350
|
};
|
|
283
351
|
}
|
|
284
352
|
/**
|
|
285
|
-
* Run ast-grep asynchronously, return parsed matches
|
|
353
|
+
* Run ast-grep asynchronously, return parsed matches. The Windows Git
|
|
354
|
+
* Bash/MSYS path deliberately remains positional-argument based, but the
|
|
355
|
+
* child itself now goes through safeSpawnAsync so cancellation, deadlines,
|
|
356
|
+
* tree-kill, and output caps are shared with every other CLI runner.
|
|
286
357
|
*/
|
|
287
|
-
async exec(args) {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
// Unix: normal spawn without shell
|
|
327
|
-
proc = spawn(command.cmd, allArgs, {
|
|
328
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
catch (err) {
|
|
333
|
-
// A SYNCHRONOUS spawn throw (Windows `spawn UNKNOWN`/EINVAL — the
|
|
334
|
-
// pidusage bug class, #533) would reject this Promise; `exec` is
|
|
335
|
-
// contracted to always resolve (callers read `error`, never catch),
|
|
336
|
-
// so resolve gracefully instead of letting it escape as an
|
|
337
|
-
// unhandledRejection. Async spawn `'error'` events are already
|
|
338
|
-
// handled by the `proc.on("error", ...)` listener below.
|
|
339
|
-
resolve({
|
|
340
|
-
...empty(),
|
|
341
|
-
error: err instanceof Error ? err.message : String(err),
|
|
342
|
-
});
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
let stdout = "";
|
|
346
|
-
let stderr = "";
|
|
347
|
-
proc.stdout?.on("data", (data) => (stdout += data.toString()));
|
|
348
|
-
proc.stderr?.on("data", (data) => (stderr += data.toString()));
|
|
349
|
-
proc.on("error", (err) => {
|
|
350
|
-
if (err.message.includes("ENOENT")) {
|
|
351
|
-
resolve({
|
|
352
|
-
...empty(),
|
|
353
|
-
error: "ast-grep CLI not found. Install: npm i -D @ast-grep/cli",
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
else {
|
|
357
|
-
resolve({ ...empty(), error: err.message });
|
|
358
|
-
}
|
|
359
|
-
});
|
|
360
|
-
proc.on("close", (code) => {
|
|
361
|
-
if (code !== 0 && !stdout.trim()) {
|
|
362
|
-
const stderrMsg = stderr.trim();
|
|
363
|
-
if (code === 1 && !stderrMsg) {
|
|
364
|
-
resolve(empty());
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
|
-
if (stderrMsg.includes("Multiple AST nodes are detected")) {
|
|
368
|
-
resolve({
|
|
369
|
-
...empty(),
|
|
370
|
-
error: `Invalid AST pattern: The pattern appears to contain multiple AST nodes or is malformed.\n` +
|
|
371
|
-
`Common causes:\n` +
|
|
372
|
-
` 1. Missing parentheses: use it($TEST) not it"test"\n` +
|
|
373
|
-
` 2. Raw text without structure: use console.log($MSG) not just "console.log"\n` +
|
|
374
|
-
` 3. Unclosed quotes or brackets\n\n` +
|
|
375
|
-
`Original error: ${stderrMsg}`,
|
|
376
|
-
});
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
if (stderrMsg.includes("Cannot parse query")) {
|
|
380
|
-
resolve({
|
|
381
|
-
...empty(),
|
|
382
|
-
error: `Pattern syntax error: The pattern could not be parsed as valid code.\n` +
|
|
383
|
-
`Tips:\n` +
|
|
384
|
-
` - Patterns must be valid ${args.includes("--lang") ? args[args.indexOf("--lang") + 1] : "language"} syntax\n` +
|
|
385
|
-
` - Use metavariables like $NAME, $ARGS for variable parts\n` +
|
|
386
|
-
` - Example: 'function $NAME($$$PARAMS) { $$$BODY }'\n\n` +
|
|
387
|
-
`Original error: ${stderrMsg}`,
|
|
388
|
-
});
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
resolve({
|
|
392
|
-
...empty(),
|
|
393
|
-
error: stderrMsg || `Command failed with exit code ${code}`,
|
|
394
|
-
});
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
if (!stdout.trim()) {
|
|
398
|
-
resolve(empty());
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
401
|
-
try {
|
|
402
|
-
const parsed = JSON.parse(stdout);
|
|
403
|
-
const matches = Array.isArray(parsed) ? parsed : [parsed];
|
|
404
|
-
resolve({
|
|
358
|
+
async exec(args, options = {}) {
|
|
359
|
+
const command = this.getSgCommand();
|
|
360
|
+
const allArgs = [...command.argsPrefix, ...args];
|
|
361
|
+
const isWindows = process.platform === "win32";
|
|
362
|
+
const hasBash = Boolean(process.env.MSYSTEM || process.env.GIT_SHELL);
|
|
363
|
+
const useBash = isWindows && hasBash;
|
|
364
|
+
const result = await safeSpawnAsync(useBash ? "bash" : command.cmd, useBash ? buildBashRunArgs(command.cmd, allArgs) : allArgs, {
|
|
365
|
+
timeout: DEFAULT_EXEC_TIMEOUT_MS,
|
|
366
|
+
deadlineAt: options.deadlineAt,
|
|
367
|
+
signal: options.signal,
|
|
368
|
+
maxOutputBytes: MAX_SG_OUTPUT_BYTES,
|
|
369
|
+
});
|
|
370
|
+
const empty = () => ({
|
|
371
|
+
matches: [],
|
|
372
|
+
totalMatches: 0,
|
|
373
|
+
truncated: false,
|
|
374
|
+
});
|
|
375
|
+
const spawnFailure = this.failureForSpawnResult(result);
|
|
376
|
+
if (spawnFailure) {
|
|
377
|
+
return {
|
|
378
|
+
...empty(),
|
|
379
|
+
error: spawnFailure === "unavailable"
|
|
380
|
+
? "ast-grep CLI not found. Install: npm i -D @ast-grep/cli"
|
|
381
|
+
: result.error?.message || "ast-grep CLI failed to start",
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
if (result.status !== 0) {
|
|
385
|
+
const stdout = result.stdout.trim();
|
|
386
|
+
const stderr = result.stderr.trim();
|
|
387
|
+
// ast-grep's linter-style contract: a rule with `severity: error`
|
|
388
|
+
// that MATCHES exits 1 with valid JSON matches on stdout (stderr
|
|
389
|
+
// carries "Scan succeeded and found error level diagnostics"). An
|
|
390
|
+
// exit code that means "scan succeeded with findings" must never be
|
|
391
|
+
// classified as a CLI failure — parse the matches. Only fall through
|
|
392
|
+
// to failure when the JSON isn't parseable (a real diagnostic).
|
|
393
|
+
if (result.status === 1 && stdout) {
|
|
394
|
+
const matches = tryParseNonZeroSgMatches(result);
|
|
395
|
+
if (matches) {
|
|
396
|
+
return {
|
|
405
397
|
matches,
|
|
406
398
|
totalMatches: matches.length,
|
|
407
399
|
truncated: false,
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
catch {
|
|
411
|
-
resolve({ ...empty(), error: "Failed to parse output" });
|
|
400
|
+
};
|
|
412
401
|
}
|
|
413
|
-
}
|
|
414
|
-
|
|
402
|
+
}
|
|
403
|
+
// ast-grep uses status 1 with no output for a genuine no-match in
|
|
404
|
+
// some CLI versions. Preserve that historical empty-result behavior;
|
|
405
|
+
// any stderr (including an invalid kind/YAML diagnostic) is a failure.
|
|
406
|
+
if (result.status === 1 && !stdout && !stderr)
|
|
407
|
+
return empty();
|
|
408
|
+
return {
|
|
409
|
+
...empty(),
|
|
410
|
+
error: this.formatPatternError(stderr || `Command failed with exit code ${result.status}`, args),
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
if (!result.stdout.trim())
|
|
414
|
+
return empty();
|
|
415
|
+
if (result.outputTruncated) {
|
|
416
|
+
return {
|
|
417
|
+
...empty(),
|
|
418
|
+
error: "Failed to parse output: output was truncated",
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
{
|
|
422
|
+
const matches = tryParseSgMatches(result.stdout);
|
|
423
|
+
if (matches) {
|
|
424
|
+
return {
|
|
425
|
+
matches,
|
|
426
|
+
totalMatches: matches.length,
|
|
427
|
+
truncated: false,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
return { ...empty(), error: "Failed to parse output" };
|
|
431
|
+
}
|
|
415
432
|
}
|
|
416
433
|
// --- Shared helpers for temp-dir rule scans ---
|
|
417
434
|
prepareTempScan(ruleId, ruleYaml) {
|
|
@@ -423,12 +440,6 @@ export class SgRunner {
|
|
|
423
440
|
fs.writeFileSync(path.join(rulesSubdir, `${ruleId}.yml`), ruleYaml);
|
|
424
441
|
return { sessionDir, configFile };
|
|
425
442
|
}
|
|
426
|
-
parseScanOutput(output) {
|
|
427
|
-
if (!output.trim())
|
|
428
|
-
return [];
|
|
429
|
-
const items = JSON.parse(output);
|
|
430
|
-
return Array.isArray(items) ? items : [items];
|
|
431
|
-
}
|
|
432
443
|
cleanupTempScan(sessionDir) {
|
|
433
444
|
try {
|
|
434
445
|
fs.rmSync(sessionDir, { recursive: true, force: true });
|
|
@@ -437,7 +448,67 @@ export class SgRunner {
|
|
|
437
448
|
this.log(`Cleanup failed: ${err.message}`);
|
|
438
449
|
}
|
|
439
450
|
}
|
|
440
|
-
|
|
451
|
+
interpretScanResult(result, args) {
|
|
452
|
+
const failure = this.failureForSpawnResult(result);
|
|
453
|
+
if (failure) {
|
|
454
|
+
return {
|
|
455
|
+
matches: [],
|
|
456
|
+
status: result.status,
|
|
457
|
+
error: result.error?.message || "ast-grep CLI failed to start",
|
|
458
|
+
failure,
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
if (result.status !== 0) {
|
|
462
|
+
const stdout = result.stdout.trim();
|
|
463
|
+
const stderr = result.stderr.trim();
|
|
464
|
+
// ast-grep's linter-style contract: a rule with `severity: error`
|
|
465
|
+
// that MATCHES exits 1 with valid JSON matches on stdout (stderr
|
|
466
|
+
// carries "Scan succeeded and found error level diagnostics"). An
|
|
467
|
+
// exit code that means "scan succeeded with findings" must never be
|
|
468
|
+
// classified as a CLI failure — parse the matches. Only fall through
|
|
469
|
+
// to failure when the JSON isn't parseable (a real diagnostic).
|
|
470
|
+
if (result.status === 1 && stdout) {
|
|
471
|
+
const matches = tryParseNonZeroSgMatches(result);
|
|
472
|
+
if (matches) {
|
|
473
|
+
return { matches, status: result.status };
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
// Preserve ast-grep's status-1/no-output no-match convention. A
|
|
477
|
+
// diagnostic on stderr is never treated as a no-match.
|
|
478
|
+
if (result.status === 1 && !stdout && !stderr) {
|
|
479
|
+
return { matches: [], status: result.status };
|
|
480
|
+
}
|
|
481
|
+
return {
|
|
482
|
+
matches: [],
|
|
483
|
+
status: result.status,
|
|
484
|
+
error: this.formatPatternError(stderr || `ast-grep scan failed with exit code ${result.status}`, args),
|
|
485
|
+
failure: "cli-failure",
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
if (!result.stdout.trim())
|
|
489
|
+
return { matches: [], status: result.status };
|
|
490
|
+
if (result.outputTruncated) {
|
|
491
|
+
return {
|
|
492
|
+
matches: [],
|
|
493
|
+
status: result.status,
|
|
494
|
+
error: "Failed to parse ast-grep scan output: output was truncated",
|
|
495
|
+
failure: "parse-failure",
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
{
|
|
499
|
+
const matches = tryParseSgMatches(result.stdout);
|
|
500
|
+
if (matches) {
|
|
501
|
+
return { matches, status: result.status };
|
|
502
|
+
}
|
|
503
|
+
return {
|
|
504
|
+
matches: [],
|
|
505
|
+
status: result.status,
|
|
506
|
+
error: "Failed to parse ast-grep scan output: invalid JSON",
|
|
507
|
+
failure: "parse-failure",
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
async tempScanDetailedAsync(dir, ruleId, ruleYaml, timeout = DEFAULT_EXEC_TIMEOUT_MS, options = {}) {
|
|
441
512
|
const { sessionDir, configFile } = this.prepareTempScan(ruleId, ruleYaml);
|
|
442
513
|
try {
|
|
443
514
|
const { cmd: sgCmd, args: sgPre } = getSgCommand();
|
|
@@ -449,42 +520,81 @@ export class SgRunner {
|
|
|
449
520
|
"--json",
|
|
450
521
|
...sgExcludeArgsForProject(dir),
|
|
451
522
|
dir,
|
|
452
|
-
], {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
523
|
+
], {
|
|
524
|
+
timeout,
|
|
525
|
+
deadlineAt: options.deadlineAt,
|
|
526
|
+
signal: options.signal,
|
|
527
|
+
maxOutputBytes: MAX_SG_OUTPUT_BYTES,
|
|
528
|
+
});
|
|
529
|
+
return this.interpretScanResult(result, ["scan"]);
|
|
457
530
|
}
|
|
458
531
|
finally {
|
|
459
532
|
this.cleanupTempScan(sessionDir);
|
|
460
533
|
}
|
|
461
534
|
}
|
|
535
|
+
/** Backwards-compatible match-only wrapper for existing session scanners. */
|
|
536
|
+
async tempScanAsync(dir, ruleId, ruleYaml, timeout = DEFAULT_EXEC_TIMEOUT_MS, options = {}) {
|
|
537
|
+
const result = await this.tempScanDetailedAsync(dir, ruleId, ruleYaml, timeout, options);
|
|
538
|
+
return result.matches;
|
|
539
|
+
}
|
|
462
540
|
/**
|
|
463
541
|
* Run a rule scan with optional fix application.
|
|
464
542
|
* Dry-run: --json (returns matches for preview).
|
|
465
543
|
* Apply: --update-all (writes fixes defined in the YAML `fix:` field).
|
|
466
544
|
*/
|
|
467
|
-
async tempScanWithFixAsync(dir, ruleId, ruleYaml, applyFixes, timeout =
|
|
545
|
+
async tempScanWithFixAsync(dir, ruleId, ruleYaml, applyFixes, timeout = DEFAULT_EXEC_TIMEOUT_MS, options = {}) {
|
|
468
546
|
const { sessionDir, configFile } = this.prepareTempScan(ruleId, ruleYaml);
|
|
469
547
|
try {
|
|
470
548
|
const { cmd: sgCmd, args: sgPre } = getSgCommand();
|
|
549
|
+
const scanArgs = [
|
|
550
|
+
...sgPre,
|
|
551
|
+
"scan",
|
|
552
|
+
"--config",
|
|
553
|
+
configFile,
|
|
554
|
+
"--json",
|
|
555
|
+
...sgExcludeArgsForProject(dir),
|
|
556
|
+
dir,
|
|
557
|
+
];
|
|
558
|
+
const spawnOptions = {
|
|
559
|
+
timeout,
|
|
560
|
+
deadlineAt: options.deadlineAt,
|
|
561
|
+
signal: options.signal,
|
|
562
|
+
maxOutputBytes: MAX_SG_OUTPUT_BYTES,
|
|
563
|
+
};
|
|
471
564
|
if (!applyFixes) {
|
|
472
|
-
const result = await safeSpawnAsync(sgCmd,
|
|
473
|
-
|
|
474
|
-
return
|
|
565
|
+
const result = await safeSpawnAsync(sgCmd, scanArgs, spawnOptions);
|
|
566
|
+
const scan = this.interpretScanResult(result, ["scan"]);
|
|
567
|
+
return scan.failure || scan.error
|
|
568
|
+
? { matches: [], error: scan.error }
|
|
569
|
+
: { matches: scan.matches };
|
|
475
570
|
}
|
|
476
571
|
// Apply: capture matches BEFORE writing — once --update-all applies
|
|
477
572
|
// the fix the rule no longer matches, so a post-apply json pass would
|
|
478
573
|
// report zero even on a successful apply. Count first, then write.
|
|
479
|
-
const jsonResult = await safeSpawnAsync(sgCmd,
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
574
|
+
const jsonResult = await safeSpawnAsync(sgCmd, scanArgs, spawnOptions);
|
|
575
|
+
const scan = this.interpretScanResult(jsonResult, ["scan"]);
|
|
576
|
+
if (scan.failure || scan.error) {
|
|
577
|
+
return { matches: [], error: scan.error };
|
|
578
|
+
}
|
|
579
|
+
const applyResult = await safeSpawnAsync(sgCmd, [
|
|
580
|
+
...sgPre,
|
|
581
|
+
"scan",
|
|
582
|
+
"--config",
|
|
583
|
+
configFile,
|
|
584
|
+
"--update-all",
|
|
585
|
+
...sgExcludeArgsForProject(dir),
|
|
586
|
+
dir,
|
|
587
|
+
], spawnOptions);
|
|
588
|
+
if (applyResult.error ||
|
|
589
|
+
applyResult.failure ||
|
|
590
|
+
applyResult.status !== 0) {
|
|
591
|
+
return {
|
|
592
|
+
matches: [],
|
|
593
|
+
error: applyResult.error?.message ||
|
|
594
|
+
`ast-grep apply failed with exit code ${applyResult.status}`,
|
|
595
|
+
};
|
|
486
596
|
}
|
|
487
|
-
return { matches };
|
|
597
|
+
return { matches: scan.matches };
|
|
488
598
|
}
|
|
489
599
|
catch (err) {
|
|
490
600
|
return { matches: [], error: String(err) };
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
* values are read lazily at call time, not module load, and `Number(...)` is
|
|
28
28
|
* gated through `Number.isFinite` before use.
|
|
29
29
|
*/
|
|
30
|
+
import { BoundedLruCache } from "./bounded-cache.js";
|
|
30
31
|
import * as fs from "node:fs";
|
|
31
32
|
import * as path from "node:path";
|
|
32
33
|
import { toPositiveFinite } from "./env-utils.js";
|
|
@@ -95,7 +96,7 @@ export function probeSlowFs(rootDir) {
|
|
|
95
96
|
}
|
|
96
97
|
/** Process-lifetime memo of the slow-FS verdict, keyed by normalized cwd so
|
|
97
98
|
* `/` vs `\` inputs share one entry (see path-key invariant tests). */
|
|
98
|
-
const slowFsVerdictCache = new
|
|
99
|
+
const slowFsVerdictCache = new BoundedLruCache(32);
|
|
99
100
|
/**
|
|
100
101
|
* Resolve (and memoize) the slow-FS verdict for `cwd`. Resolution order:
|
|
101
102
|
* 1. `PI_LENS_ALLOW_SLOW_FS_SCAN=1` — kill switch, always false.
|