@herbertgao/pi-extensions 2026.8.4 → 2026.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +11 -3
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +11 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +24 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +91 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +74 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +7 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +21 -12
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +4 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +53 -14
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +22 -12
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +2 -4
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +18 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +8 -4
- package/node_modules/@herbertgao/pi-subagents/src/agent-color.ts +72 -67
- package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +255 -0
- package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +191 -3
- package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +20 -4
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +289 -113
- package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +24 -1
- package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +24 -2
- package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +15 -6
- package/node_modules/@herbertgao/pi-subagents/src/ui/schedule-menu.ts +9 -8
- package/node_modules/@herbertgao/pi-subagents/src/ui/select-item.ts +48 -0
- package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +9 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +2 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +20 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +6 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +7 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +7 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +16 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +4 -2
- package/node_modules/@narumitw/pi-btw/README.md +32 -24
- package/node_modules/@narumitw/pi-btw/package.json +6 -6
- package/node_modules/@narumitw/pi-btw/src/btw.ts +4 -2
- package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -13
- package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
- package/node_modules/pi-lens/CHANGELOG.md +2535 -954
- package/node_modules/pi-lens/README.md +13 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +73 -18
- package/node_modules/pi-lens/dist/clients/advisory-provenance.js +314 -0
- package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +21 -4
- package/node_modules/pi-lens/dist/clients/agent-nudge.js +14 -7
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +68 -16
- package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -1
- package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +37 -10
- package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +63 -0
- package/node_modules/pi-lens/dist/clients/atomic-write.js +114 -14
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +193 -23
- package/node_modules/pi-lens/dist/clients/biome-client.js +136 -24
- package/node_modules/pi-lens/dist/clients/bootstrap.js +11 -2
- package/node_modules/pi-lens/dist/clients/bounded-cache.js +34 -0
- package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +192 -0
- package/node_modules/pi-lens/dist/clients/bus-events-logger.js +62 -6
- package/node_modules/pi-lens/dist/clients/bus-publish.js +19 -4
- package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +47 -5
- package/node_modules/pi-lens/dist/clients/cache-manager.js +97 -9
- package/node_modules/pi-lens/dist/clients/cache-observability.js +301 -14
- package/node_modules/pi-lens/dist/clients/call-graph.js +449 -117
- package/node_modules/pi-lens/dist/clients/cascade-format.js +57 -2
- package/node_modules/pi-lens/dist/clients/child-unref.js +85 -0
- package/node_modules/pi-lens/dist/clients/codebase-model.js +52 -23
- package/node_modules/pi-lens/dist/clients/collateral-test-role.js +40 -0
- package/node_modules/pi-lens/dist/clients/complexity-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/console-guard-install.js +26 -0
- package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +137 -31
- package/node_modules/pi-lens/dist/clients/debug-handles.js +206 -0
- package/node_modules/pi-lens/dist/clients/debug-heap.js +167 -0
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +153 -0
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +361 -77
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +65 -13
- package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +18 -4
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +176 -29
- package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +23 -7
- package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +8 -18
- package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +8 -5
- package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +35 -0
- package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +17 -8
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +1172 -683
- package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +14 -0
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +11 -1
- package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +50 -0
- package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +135 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +23 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +5 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +154 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +5 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +47 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +9 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +4 -8
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +163 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +37 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +3 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +13 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +226 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +69 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +473 -42
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +97 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
- package/node_modules/pi-lens/dist/clients/disposition-publish.js +18 -4
- package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
- package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
- package/node_modules/pi-lens/dist/clients/extension-log.js +432 -0
- package/node_modules/pi-lens/dist/clients/extension-mode.js +73 -0
- package/node_modules/pi-lens/dist/clients/file-kinds.js +57 -0
- package/node_modules/pi-lens/dist/clients/file-role.js +18 -3
- package/node_modules/pi-lens/dist/clients/file-utils.js +56 -34
- package/node_modules/pi-lens/dist/clients/fix-worklog.js +5 -1
- package/node_modules/pi-lens/dist/clients/format-events-publish.js +51 -7
- package/node_modules/pi-lens/dist/clients/format-service.js +24 -8
- package/node_modules/pi-lens/dist/clients/formatters-lazy.js +8 -0
- package/node_modules/pi-lens/dist/clients/formatters.js +358 -61
- package/node_modules/pi-lens/dist/clients/generated-artifacts.js +148 -15
- package/node_modules/pi-lens/dist/clients/git-guard.js +678 -11
- package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
- package/node_modules/pi-lens/dist/clients/go-client.js +23 -40
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +124 -7
- package/node_modules/pi-lens/dist/clients/grammar-source.js +12 -4
- package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +21 -3
- package/node_modules/pi-lens/dist/clients/host-ports.js +31 -0
- package/node_modules/pi-lens/dist/clients/installer/index.js +395 -100
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +27 -55
- package/node_modules/pi-lens/dist/clients/knip-client.js +58 -42
- package/node_modules/pi-lens/dist/clients/language-policy.js +14 -2
- package/node_modules/pi-lens/dist/clients/language-profile.js +13 -7
- package/node_modules/pi-lens/dist/clients/latency-logger.js +72 -1
- package/node_modules/pi-lens/dist/clients/lens-config.js +12 -1
- package/node_modules/pi-lens/dist/clients/lens-engine.js +86 -17
- package/node_modules/pi-lens/dist/clients/lens-events.js +63 -9
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +16 -0
- package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +73 -0
- package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +59 -14
- package/node_modules/pi-lens/dist/clients/lsp/client.js +662 -56
- package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +115 -0
- package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
- package/node_modules/pi-lens/dist/clients/lsp/index.js +2025 -800
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +32 -26
- package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +83 -0
- package/node_modules/pi-lens/dist/clients/lsp/server.js +437 -86
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +96 -0
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +21 -5
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +18 -3
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
- package/node_modules/pi-lens/dist/clients/lsp-lazy.js +8 -0
- package/node_modules/pi-lens/dist/clients/lsp-mutation.js +248 -0
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +127 -32
- package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
- package/node_modules/pi-lens/dist/clients/mcp/session.js +235 -14
- package/node_modules/pi-lens/dist/clients/memory-sampler.js +115 -0
- package/node_modules/pi-lens/dist/clients/metrics-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/metrics-history.js +2 -1
- package/node_modules/pi-lens/dist/clients/model-provider.js +53 -0
- package/node_modules/pi-lens/dist/clients/module-report-lsp.js +5 -0
- package/node_modules/pi-lens/dist/clients/module-report.js +168 -3
- package/node_modules/pi-lens/dist/clients/ndjson-logger.js +319 -118
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +1 -1
- package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +120 -5
- package/node_modules/pi-lens/dist/clients/path-utils.js +110 -9
- package/node_modules/pi-lens/dist/clients/pipeline.js +76 -15
- package/node_modules/pi-lens/dist/clients/print-mode.js +21 -0
- package/node_modules/pi-lens/dist/clients/project-changes.js +40 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +3 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +8 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +23 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +21 -1
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +86 -9
- package/node_modules/pi-lens/dist/clients/project-report.js +27 -13
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +175 -20
- package/node_modules/pi-lens/dist/clients/project-trust.js +181 -0
- package/node_modules/pi-lens/dist/clients/quiet-window.js +18 -4
- package/node_modules/pi-lens/dist/clients/read-bridge.js +147 -0
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +159 -1
- package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +150 -29
- package/node_modules/pi-lens/dist/clients/read-guard.js +185 -4
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +70 -84
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +1122 -221
- package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +2 -1
- package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +21 -10
- package/node_modules/pi-lens/dist/clients/review-graph/query.js +24 -0
- package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +46 -0
- package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +57 -6
- package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +51 -3
- package/node_modules/pi-lens/dist/clients/review-graph-logger.js +30 -1
- package/node_modules/pi-lens/dist/clients/ruff-client.js +27 -44
- package/node_modules/pi-lens/dist/clients/run-duration.js +55 -0
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +257 -77
- package/node_modules/pi-lens/dist/clients/runtime-context.js +129 -12
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +242 -26
- package/node_modules/pi-lens/dist/clients/runtime-session.js +428 -117
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +71 -48
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +342 -33
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +619 -93
- package/node_modules/pi-lens/dist/clients/rust-client.js +23 -38
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +90 -6
- package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
- package/node_modules/pi-lens/dist/clients/sg-runner.js +424 -196
- package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +231 -0
- package/node_modules/pi-lens/dist/clients/source-filter.js +184 -20
- package/node_modules/pi-lens/dist/clients/source-walker.js +66 -10
- package/node_modules/pi-lens/dist/clients/startup-scan.js +15 -16
- package/node_modules/pi-lens/dist/clients/startup-timing.js +7 -1
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +469 -32
- package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
- package/node_modules/pi-lens/dist/clients/tool-policy.js +390 -39
- package/node_modules/pi-lens/dist/clients/tool-render.js +155 -0
- package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +6 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +419 -50
- package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +21 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +7 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +7 -2
- package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +44 -7
- package/node_modules/pi-lens/dist/clients/user-notify.js +51 -0
- package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +57 -0
- package/node_modules/pi-lens/dist/clients/warm-attach.js +50 -13
- package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
- package/node_modules/pi-lens/dist/clients/word-index.js +680 -81
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
- package/node_modules/pi-lens/dist/index.js +51785 -36118
- package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
- package/node_modules/pi-lens/dist/mcp/server.js +219 -46
- package/node_modules/pi-lens/dist/tools/activate-tools.js +17 -5
- package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +9 -4
- package/node_modules/pi-lens/dist/tools/ast-grep-search.js +93 -27
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +5 -2
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +203 -38
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +210 -42
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -3
- package/node_modules/pi-lens/dist/tools/module-report.js +9 -0
- package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -1
- package/node_modules/pi-lens/docs/agent-guide.md +40 -15
- package/node_modules/pi-lens/docs/agent-tools.md +21 -10
- package/node_modules/pi-lens/docs/analysisall.md +192 -0
- package/node_modules/pi-lens/docs/api-ports-inventory.md +165 -0
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +14 -3
- package/node_modules/pi-lens/docs/durable-store-audit-1202.md +32 -0
- package/node_modules/pi-lens/docs/environment-variables.md +44 -0
- package/node_modules/pi-lens/docs/fable.md +27 -0
- package/node_modules/pi-lens/docs/features.md +45 -3
- package/node_modules/pi-lens/docs/globalconfig.md +56 -1
- package/node_modules/pi-lens/docs/language-coverage.md +4 -3
- package/node_modules/pi-lens/docs/lsp-capability-matrix.md +2 -2
- package/node_modules/pi-lens/docs/mcp.md +37 -4
- package/node_modules/pi-lens/docs/servercapabilities.md +3 -3
- package/node_modules/pi-lens/docs/settings.md +18 -1
- package/node_modules/pi-lens/docs/tools.md +4 -1
- package/node_modules/pi-lens/docs/usage.md +41 -5
- package/node_modules/pi-lens/docs/word-index.md +35 -0
- package/node_modules/pi-lens/package.json +10 -10
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-js-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-laundering-test.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread-js.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread.yml +29 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +13 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +27 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
- package/node_modules/pi-lens/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
- package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +2 -2
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +134 -2
- package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +10 -11
- package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +22 -22
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +8 -114
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +129 -0
- package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +38 -0
- package/node_modules/pi-mcp-adapter/README.md +38 -4
- package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
- package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
- package/node_modules/pi-mcp-adapter/cli.js +25 -4
- package/node_modules/pi-mcp-adapter/commands.ts +15 -7
- package/node_modules/pi-mcp-adapter/config.ts +4 -4
- package/node_modules/pi-mcp-adapter/direct-tools.ts +58 -8
- package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -8
- package/node_modules/pi-mcp-adapter/index.ts +8 -3
- package/node_modules/pi-mcp-adapter/init.ts +29 -5
- package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -4
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
- package/node_modules/pi-mcp-adapter/mcp-auth.ts +60 -2
- package/node_modules/pi-mcp-adapter/mcp-panel.ts +45 -9
- package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +22 -20
- package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +2 -1
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +37 -4
- package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
- package/node_modules/pi-mcp-adapter/package.json +4 -3
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +336 -0
- package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
- package/node_modules/pi-mcp-adapter/server-manager.ts +41 -7
- package/node_modules/pi-mcp-adapter/session-recovery.ts +8 -2
- package/node_modules/pi-mcp-adapter/tool-approval.ts +48 -7
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +62 -4
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
- package/node_modules/pi-mcp-adapter/types.ts +176 -27
- package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
- package/node_modules/pi-web-access/CHANGELOG.md +14 -0
- package/node_modules/pi-web-access/README.md +18 -12
- package/node_modules/pi-web-access/auth-fetch.ts +148 -0
- package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
- package/node_modules/pi-web-access/curator-page.ts +5 -3
- package/node_modules/pi-web-access/curator-server.ts +2 -1
- package/node_modules/pi-web-access/extract.ts +106 -34
- package/node_modules/pi-web-access/fetch-params.ts +17 -3
- package/node_modules/pi-web-access/firecrawl.ts +172 -12
- package/node_modules/pi-web-access/gemini-search.ts +18 -4
- package/node_modules/pi-web-access/index.ts +120 -48
- package/node_modules/pi-web-access/package.json +2 -2
- package/node_modules/pi-web-access/summary-review.ts +11 -5
- package/node_modules/pi-web-access/youtube-extract.ts +2 -2
- package/package.json +12 -13
- package/node_modules/pi-lens/dist/clients/ast-grep-parser.js +0 -86
- package/node_modules/pi-lens/dist/clients/lsp/interactive-install.js +0 -367
- package/node_modules/pi-lens/dist/clients/lsp/lsp-index.js +0 -10
- package/node_modules/pi-lens/dist/clients/lsp/server-strategies.js +0 -5
- package/node_modules/pi-lens/dist/clients/project-metadata.js +0 -690
- package/node_modules/pi-lens/dist/clients/source-groups.js +0 -96
- package/node_modules/pi-lens/dist/clients/startup-marker.js +0 -6
- package/node_modules/pi-lens/dist/clients/types.js +0 -11
|
@@ -4,25 +4,15 @@
|
|
|
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
|
-
}
|
|
15
|
+
import { classifyProbeFailure, createAvailabilityLatch, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
|
|
26
16
|
/**
|
|
27
17
|
* Build the `bash -c` argv that runs `cmd` with `allArgs` as POSITIONAL
|
|
28
18
|
* parameters. The script is the constant `"$0" "$@"`, so bash re-emits the
|
|
@@ -43,6 +33,10 @@ function sgExcludeArgsForProject(rootDir) {
|
|
|
43
33
|
`!${glob}`,
|
|
44
34
|
]);
|
|
45
35
|
}
|
|
36
|
+
const DEFAULT_EXEC_TIMEOUT_MS = 30_000;
|
|
37
|
+
/** Budget for a single `--version` candidate probe, ms. */
|
|
38
|
+
const PROBE_TIMEOUT_MS = 5_000;
|
|
39
|
+
const MAX_SG_OUTPUT_BYTES = 8 * 1024 * 1024;
|
|
46
40
|
/**
|
|
47
41
|
* Format metavariable captures for display below a match line.
|
|
48
42
|
* Single captures: $VAR=x $NAME=foo
|
|
@@ -72,15 +66,65 @@ function formatMetaVarCaptures(mv) {
|
|
|
72
66
|
return undefined;
|
|
73
67
|
return ` ${parts.join(" ")}`;
|
|
74
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Parse ast-grep `--json` stdout into a match array, or return `null` when the
|
|
71
|
+
* text is not a match payload. ast-grep emits either a JSON array or (rarely) a
|
|
72
|
+
* single object; both are normalized to an array. A JSON scalar/`null` is
|
|
73
|
+
* rejected (returns `null`) so a hypothetical error-report scalar on stdout can
|
|
74
|
+
* never be misread as a phantom match.
|
|
75
|
+
*/
|
|
76
|
+
function tryParseSgMatches(stdout) {
|
|
77
|
+
try {
|
|
78
|
+
const parsed = JSON.parse(stdout);
|
|
79
|
+
if (Array.isArray(parsed))
|
|
80
|
+
return parsed;
|
|
81
|
+
if (parsed !== null && typeof parsed === "object")
|
|
82
|
+
return [parsed];
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function tryParseNonZeroSgMatches(result) {
|
|
90
|
+
if (result.outputTruncated || !result.stdout.trim())
|
|
91
|
+
return null;
|
|
92
|
+
// Trimmed on purpose: a leading BOM survives JSON.parse's whitespace
|
|
93
|
+
// tolerance but String.trim() strips it (matches the pre-refactor guard).
|
|
94
|
+
return tryParseSgMatches(result.stdout.trim());
|
|
95
|
+
}
|
|
75
96
|
export class SgRunner {
|
|
76
97
|
log;
|
|
77
98
|
sgPath = null;
|
|
78
99
|
sgArgsPrefix = [];
|
|
79
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Availability memo, backed by the shared transient-aware latch (#1476).
|
|
102
|
+
*
|
|
103
|
+
* Before this, one failed sweep over every candidate — timeouts included —
|
|
104
|
+
* latched `false` for the life of the process AND ran a full auto-install
|
|
105
|
+
* first. A host stall during warm-up therefore both disabled ast-grep until
|
|
106
|
+
* restart and paid for an install nobody needed.
|
|
107
|
+
*/
|
|
108
|
+
availabilityLatch = createAvailabilityLatch();
|
|
80
109
|
ensureInFlight = null;
|
|
110
|
+
/**
|
|
111
|
+
* Whether a DIRECT candidate — one that would have been ast-grep itself —
|
|
112
|
+
* failed for a transient reason in the current sweep. Only these block the
|
|
113
|
+
* install: a slow `npx` says nothing about whether ast-grep is on this
|
|
114
|
+
* machine, and letting it block turned "ast-grep is genuinely absent on a
|
|
115
|
+
* slow host" into "ast-grep is never installed and npx is re-spawned every
|
|
116
|
+
* sweep, forever".
|
|
117
|
+
*/
|
|
118
|
+
sweepSawTransient = false;
|
|
119
|
+
sweepTransientCause = "probe-timeout";
|
|
120
|
+
/** A transient on the npx fallback: not evidence, but not nothing either. */
|
|
121
|
+
sweepFallbackTransient = false;
|
|
122
|
+
sweepFallbackCause = "probe-timeout";
|
|
123
|
+
/** Host stall summed over every probe of the current sweep, ms. */
|
|
124
|
+
sweepHostStallMs = 0;
|
|
81
125
|
constructor(verbose = false) {
|
|
82
126
|
this.log = verbose
|
|
83
|
-
? (
|
|
127
|
+
? createSubsystemLogger("sg-runner")
|
|
84
128
|
: () => { };
|
|
85
129
|
}
|
|
86
130
|
/**
|
|
@@ -92,9 +136,11 @@ export class SgRunner {
|
|
|
92
136
|
* `KnipClient.ensureAvailable` and `DependencyChecker.ensureAvailable`.
|
|
93
137
|
*/
|
|
94
138
|
async ensureAvailable() {
|
|
95
|
-
// Fast path: already
|
|
96
|
-
|
|
97
|
-
|
|
139
|
+
// Fast path: already decided. `read()` returns null when the last verdict
|
|
140
|
+
// was transient and its cooldown expired, which re-enters the sweep.
|
|
141
|
+
const memo = this.availabilityLatch.read();
|
|
142
|
+
if (memo !== null)
|
|
143
|
+
return memo;
|
|
98
144
|
if (this.ensureInFlight)
|
|
99
145
|
return this.ensureInFlight;
|
|
100
146
|
this.ensureInFlight = this.doEnsureAvailable();
|
|
@@ -106,18 +152,26 @@ export class SgRunner {
|
|
|
106
152
|
}
|
|
107
153
|
}
|
|
108
154
|
async doEnsureAvailable() {
|
|
155
|
+
const startedAt = Date.now();
|
|
156
|
+
this.sweepSawTransient = false;
|
|
157
|
+
this.sweepTransientCause = "probe-timeout";
|
|
158
|
+
this.sweepFallbackTransient = false;
|
|
159
|
+
this.sweepFallbackCause = "probe-timeout";
|
|
160
|
+
this.sweepHostStallMs = 0;
|
|
109
161
|
// Step 1: PATH — canonical binary names + npx fallback.
|
|
110
162
|
// Prefer ast-grep over sg on Linux: /usr/bin/sg is util-linux, not ast-grep.
|
|
111
163
|
const pathCommand = await this.probeCommandCandidates([
|
|
112
164
|
{ cmd: "ast-grep", argsPrefix: [] },
|
|
113
165
|
{ cmd: "sg", argsPrefix: [] },
|
|
114
|
-
|
|
166
|
+
// `npx --no -- ast-grep` starts a Node process before it can answer, so
|
|
167
|
+
// it is the candidate most likely to blow a 5 s budget on a cold or busy
|
|
168
|
+
// box. Marked a fallback so its timeout cannot veto the install.
|
|
169
|
+
{ cmd: "npx", argsPrefix: ["--no", "--", "ast-grep"], fallback: true },
|
|
115
170
|
]);
|
|
116
171
|
if (pathCommand) {
|
|
117
172
|
this.sgPath = pathCommand.cmd;
|
|
118
173
|
this.sgArgsPrefix = pathCommand.argsPrefix;
|
|
119
|
-
this.
|
|
120
|
-
this.log(`ast-grep found on PATH: ${pathCommand.cmd}`);
|
|
174
|
+
this.noteAvailable(startedAt, `ast-grep found on PATH: ${pathCommand.cmd}`);
|
|
121
175
|
return true;
|
|
122
176
|
}
|
|
123
177
|
// Step 1b: any package manager's global bin dir (npm/pnpm/yarn/bun).
|
|
@@ -129,8 +183,7 @@ export class SgRunner {
|
|
|
129
183
|
if (globalBin && (await this.probeCommand(globalBin, []))) {
|
|
130
184
|
this.sgPath = globalBin;
|
|
131
185
|
this.sgArgsPrefix = [];
|
|
132
|
-
this.
|
|
133
|
-
this.log(`ast-grep found in global bin: ${globalBin}`);
|
|
186
|
+
this.noteAvailable(startedAt, `ast-grep found in global bin: ${globalBin}`);
|
|
134
187
|
return true;
|
|
135
188
|
}
|
|
136
189
|
}
|
|
@@ -141,8 +194,7 @@ export class SgRunner {
|
|
|
141
194
|
if (platformBinary) {
|
|
142
195
|
this.sgPath = platformBinary;
|
|
143
196
|
this.sgArgsPrefix = [];
|
|
144
|
-
this.
|
|
145
|
-
this.log(`ast-grep found via platform package: ${platformBinary}`);
|
|
197
|
+
this.noteAvailable(startedAt, `ast-grep found via platform package: ${platformBinary}`);
|
|
146
198
|
return true;
|
|
147
199
|
}
|
|
148
200
|
// Step 3: Homebrew (macOS only).
|
|
@@ -151,23 +203,80 @@ export class SgRunner {
|
|
|
151
203
|
if (brewBinary) {
|
|
152
204
|
this.sgPath = brewBinary;
|
|
153
205
|
this.sgArgsPrefix = [];
|
|
154
|
-
this.
|
|
155
|
-
this.log(`ast-grep found via Homebrew: ${brewBinary}`);
|
|
206
|
+
this.noteAvailable(startedAt, `ast-grep found via Homebrew: ${brewBinary}`);
|
|
156
207
|
return true;
|
|
157
208
|
}
|
|
158
209
|
}
|
|
159
|
-
//
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
210
|
+
// A timeout on a DIRECT candidate means the machine answered, not the tool
|
|
211
|
+
// (#1476). Installing ast-grep because the host event loop stalled is a
|
|
212
|
+
// heavyweight reaction to a hiccup, and latching the result disabled the
|
|
213
|
+
// tool until restart. Retry the sweep later instead.
|
|
214
|
+
//
|
|
215
|
+
// The npx fallback is deliberately NOT part of this test. Gating the
|
|
216
|
+
// install on it regressed the very host this change targets: a slow box
|
|
217
|
+
// with no ast-grep timed out `npx --no -- ast-grep` every sweep, so the
|
|
218
|
+
// install below was never reached and the slow npx was re-spawned on each
|
|
219
|
+
// escalating retry instead — worse than the latch it replaced.
|
|
220
|
+
if (this.sweepSawTransient) {
|
|
221
|
+
this.log("ast-grep availability probe timed out; will retry (not installing)");
|
|
222
|
+
return this.noteUnavailable(startedAt, "transient", this.sweepTransientCause);
|
|
223
|
+
}
|
|
224
|
+
// Step 4: install via the typed shared seam, then validate the returned
|
|
225
|
+
// absolute binary before publishing it.
|
|
226
|
+
const installed = await resolveManagedToolClient({
|
|
227
|
+
toolId: "ast-grep",
|
|
228
|
+
cwd: process.cwd(),
|
|
229
|
+
probe: async () => ({ outcome: "missing" }),
|
|
230
|
+
acceptInstalled: async (installedPath) => (await this.probeCommand(installedPath, [])) ? installedPath : null,
|
|
231
|
+
});
|
|
232
|
+
if (installed.outcome === "success") {
|
|
233
|
+
this.sgPath = installed.value;
|
|
165
234
|
this.sgArgsPrefix = [];
|
|
166
|
-
this.
|
|
167
|
-
this.log(`ast-grep auto-installed: ${installedPath}`);
|
|
235
|
+
this.noteAvailable(startedAt, `ast-grep auto-installed: ${installed.value}`);
|
|
168
236
|
return true;
|
|
169
237
|
}
|
|
170
|
-
|
|
238
|
+
// The install failed AND the npx fallback never got a fair hearing, so
|
|
239
|
+
// "ast-grep is not installed" is not a fact yet. Expire the verdict.
|
|
240
|
+
if (this.sweepFallbackTransient) {
|
|
241
|
+
return this.noteUnavailable(startedAt, "transient", this.sweepFallbackCause);
|
|
242
|
+
}
|
|
243
|
+
return this.noteUnavailable(startedAt, "missing", "not-found");
|
|
244
|
+
}
|
|
245
|
+
/** Record a successful sweep: available, latched, one decision record. */
|
|
246
|
+
noteAvailable(startedAt, message) {
|
|
247
|
+
this.availabilityLatch.noteAvailable();
|
|
248
|
+
this.log(message);
|
|
249
|
+
logAvailabilityDecision({
|
|
250
|
+
tool: "ast-grep",
|
|
251
|
+
verdict: "available",
|
|
252
|
+
outcome: "success",
|
|
253
|
+
cause: "ok",
|
|
254
|
+
elapsedMs: Date.now() - startedAt,
|
|
255
|
+
latched: true,
|
|
256
|
+
hostStallMs: this.sweepHostStallMs,
|
|
257
|
+
budgetMs: PROBE_TIMEOUT_MS,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Record a failed sweep. A `transient` outcome expires after a bounded
|
|
262
|
+
* cooldown; anything else is remembered for the session, as before. The
|
|
263
|
+
* elapsed/stall numbers describe the WHOLE sweep, because the verdict is
|
|
264
|
+
* about the sweep rather than any single candidate — reporting zero here
|
|
265
|
+
* would erase the evidence that cracked #1467.
|
|
266
|
+
*/
|
|
267
|
+
noteUnavailable(startedAt, outcome, cause) {
|
|
268
|
+
const retryAfterMs = this.availabilityLatch.noteUnavailable(outcome, cause);
|
|
269
|
+
logAvailabilityDecision({
|
|
270
|
+
tool: "ast-grep",
|
|
271
|
+
verdict: "unavailable",
|
|
272
|
+
outcome,
|
|
273
|
+
cause,
|
|
274
|
+
elapsedMs: Date.now() - startedAt,
|
|
275
|
+
latched: outcome !== "transient",
|
|
276
|
+
hostStallMs: this.sweepHostStallMs,
|
|
277
|
+
...(retryAfterMs > 0 && { retryAfterMs }),
|
|
278
|
+
budgetMs: PROBE_TIMEOUT_MS,
|
|
279
|
+
});
|
|
171
280
|
return false;
|
|
172
281
|
}
|
|
173
282
|
/**
|
|
@@ -206,7 +315,8 @@ export class SgRunner {
|
|
|
206
315
|
for (const root of searchRoots) {
|
|
207
316
|
const candidate = path.join(root, pkgName, exeName);
|
|
208
317
|
try {
|
|
209
|
-
if (fs.existsSync(candidate) &&
|
|
318
|
+
if (fs.existsSync(candidate) &&
|
|
319
|
+
(await this.probeCommand(candidate, []))) {
|
|
210
320
|
return candidate;
|
|
211
321
|
}
|
|
212
322
|
}
|
|
@@ -233,7 +343,8 @@ export class SgRunner {
|
|
|
233
343
|
return undefined;
|
|
234
344
|
for (const name of ["ast-grep", "sg"]) {
|
|
235
345
|
const candidate = path.join(prefix, "bin", name);
|
|
236
|
-
if (fs.existsSync(candidate) &&
|
|
346
|
+
if (fs.existsSync(candidate) &&
|
|
347
|
+
(await this.probeCommand(candidate, []))) {
|
|
237
348
|
return candidate;
|
|
238
349
|
}
|
|
239
350
|
}
|
|
@@ -246,17 +357,48 @@ export class SgRunner {
|
|
|
246
357
|
isAstGrepVersionOutput(output) {
|
|
247
358
|
return /\bast[- ]grep\b/i.test(output);
|
|
248
359
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
360
|
+
/**
|
|
361
|
+
* Probe one candidate. A failure is CLASSIFIED, not merely counted: a
|
|
362
|
+
* timeout/abort marks the whole sweep transient so the caller retries later
|
|
363
|
+
* instead of installing and latching. A command that answers but is not
|
|
364
|
+
* ast-grep (Linux `/usr/bin/sg` is util-linux) is a durable no.
|
|
365
|
+
*/
|
|
366
|
+
async probeCommand(cmd, argsPrefix, fallback = false) {
|
|
367
|
+
// Host-side budget: measure the loop stall that overlapped the window so
|
|
368
|
+
// the classifier can tell "the tool is slow" from "the host was busy".
|
|
369
|
+
const sampler = startHostStallSampler();
|
|
370
|
+
let result;
|
|
371
|
+
let hostStallMs;
|
|
372
|
+
try {
|
|
373
|
+
result = await safeSpawnAsync(cmd, [...argsPrefix, "--version"], {
|
|
374
|
+
timeout: PROBE_TIMEOUT_MS,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
finally {
|
|
378
|
+
hostStallMs = sampler.stop();
|
|
379
|
+
this.sweepHostStallMs += hostStallMs;
|
|
380
|
+
}
|
|
381
|
+
if (!result.error &&
|
|
254
382
|
result.status === 0 &&
|
|
255
|
-
this.isAstGrepVersionOutput(`${result.stdout}\n${result.stderr}`))
|
|
383
|
+
this.isAstGrepVersionOutput(`${result.stdout}\n${result.stderr}`)) {
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
const { outcome, cause } = classifyProbeFailure(result, { hostStallMs });
|
|
387
|
+
if (outcome === "transient") {
|
|
388
|
+
if (fallback) {
|
|
389
|
+
this.sweepFallbackTransient = true;
|
|
390
|
+
this.sweepFallbackCause = cause;
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
this.sweepSawTransient = true;
|
|
394
|
+
this.sweepTransientCause = cause;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return false;
|
|
256
398
|
}
|
|
257
399
|
async probeCommandCandidates(candidates) {
|
|
258
400
|
for (const candidate of candidates) {
|
|
259
|
-
if (await this.probeCommand(candidate.cmd, candidate.argsPrefix)) {
|
|
401
|
+
if (await this.probeCommand(candidate.cmd, candidate.argsPrefix, candidate.fallback ?? false)) {
|
|
260
402
|
return candidate;
|
|
261
403
|
}
|
|
262
404
|
}
|
|
@@ -271,147 +413,140 @@ export class SgRunner {
|
|
|
271
413
|
argsPrefix: this.sgArgsPrefix,
|
|
272
414
|
};
|
|
273
415
|
}
|
|
274
|
-
|
|
416
|
+
failureForSpawnResult(result) {
|
|
417
|
+
if (result.failure === "aborted")
|
|
418
|
+
return "aborted";
|
|
419
|
+
switch (result.spawnFailure?.kind) {
|
|
420
|
+
case "tool-not-found":
|
|
421
|
+
return "unavailable";
|
|
422
|
+
case "timeout":
|
|
423
|
+
return "timeout";
|
|
424
|
+
case "killed":
|
|
425
|
+
return result.failure === "aborted" ? "aborted" : "cli-failure";
|
|
426
|
+
case "cwd-unresolvable":
|
|
427
|
+
case "permission-denied":
|
|
428
|
+
case "spawn-failed":
|
|
429
|
+
case undefined:
|
|
430
|
+
return result.error ? "cli-failure" : undefined;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
formatPatternError(stderr, args) {
|
|
434
|
+
if (stderr.includes("Multiple AST nodes are detected")) {
|
|
435
|
+
return (`Invalid AST pattern: The pattern appears to contain multiple AST nodes or is malformed.\n` +
|
|
436
|
+
`Common causes:\n` +
|
|
437
|
+
` 1. Missing parentheses: use it($TEST) not it"test"\n` +
|
|
438
|
+
` 2. Raw text without structure: use console.log($MSG) not just "console.log"\n` +
|
|
439
|
+
` 3. Unclosed quotes or brackets\n\n` +
|
|
440
|
+
`Original error: ${stderr}`);
|
|
441
|
+
}
|
|
442
|
+
if (stderr.includes("Cannot parse query")) {
|
|
443
|
+
return (`Pattern syntax error: The pattern could not be parsed as valid code.\n` +
|
|
444
|
+
`Tips:\n` +
|
|
445
|
+
` - Patterns must be valid ${args.includes("--lang") ? args[args.indexOf("--lang") + 1] : "language"} syntax\n` +
|
|
446
|
+
` - Use metavariables like $NAME, $ARGS for variable parts\n` +
|
|
447
|
+
` - Example: 'function $NAME($$$PARAMS) { $$$BODY }'\n\n` +
|
|
448
|
+
`Original error: ${stderr}`);
|
|
449
|
+
}
|
|
450
|
+
return stderr;
|
|
451
|
+
}
|
|
452
|
+
async execRaw(args, timeout = DEFAULT_EXEC_TIMEOUT_MS, options = {}) {
|
|
275
453
|
const command = this.getSgCommand();
|
|
276
|
-
const result = await safeSpawnAsync(command.cmd, [...command.argsPrefix, ...args], {
|
|
454
|
+
const result = await safeSpawnAsync(command.cmd, [...command.argsPrefix, ...args], {
|
|
455
|
+
timeout,
|
|
456
|
+
deadlineAt: options.deadlineAt,
|
|
457
|
+
signal: options.signal,
|
|
458
|
+
maxOutputBytes: MAX_SG_OUTPUT_BYTES,
|
|
459
|
+
});
|
|
460
|
+
const failure = this.failureForSpawnResult(result);
|
|
277
461
|
return {
|
|
278
462
|
stdout: result.stdout,
|
|
279
463
|
stderr: result.stderr,
|
|
280
464
|
status: result.status,
|
|
281
465
|
error: result.error?.message,
|
|
466
|
+
failure,
|
|
467
|
+
...(result.outputTruncated ? { outputTruncated: true } : {}),
|
|
282
468
|
};
|
|
283
469
|
}
|
|
284
470
|
/**
|
|
285
|
-
* Run ast-grep asynchronously, return parsed matches
|
|
471
|
+
* Run ast-grep asynchronously, return parsed matches. The Windows Git
|
|
472
|
+
* Bash/MSYS path deliberately remains positional-argument based, but the
|
|
473
|
+
* child itself now goes through safeSpawnAsync so cancellation, deadlines,
|
|
474
|
+
* tree-kill, and output caps are shared with every other CLI runner.
|
|
286
475
|
*/
|
|
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({
|
|
476
|
+
async exec(args, options = {}) {
|
|
477
|
+
const command = this.getSgCommand();
|
|
478
|
+
const allArgs = [...command.argsPrefix, ...args];
|
|
479
|
+
const isWindows = process.platform === "win32";
|
|
480
|
+
const hasBash = Boolean(process.env.MSYSTEM || process.env.GIT_SHELL);
|
|
481
|
+
const useBash = isWindows && hasBash;
|
|
482
|
+
const result = await safeSpawnAsync(useBash ? "bash" : command.cmd, useBash ? buildBashRunArgs(command.cmd, allArgs) : allArgs, {
|
|
483
|
+
timeout: DEFAULT_EXEC_TIMEOUT_MS,
|
|
484
|
+
deadlineAt: options.deadlineAt,
|
|
485
|
+
signal: options.signal,
|
|
486
|
+
maxOutputBytes: MAX_SG_OUTPUT_BYTES,
|
|
487
|
+
});
|
|
488
|
+
const empty = () => ({
|
|
489
|
+
matches: [],
|
|
490
|
+
totalMatches: 0,
|
|
491
|
+
truncated: false,
|
|
492
|
+
});
|
|
493
|
+
const spawnFailure = this.failureForSpawnResult(result);
|
|
494
|
+
if (spawnFailure) {
|
|
495
|
+
return {
|
|
496
|
+
...empty(),
|
|
497
|
+
error: spawnFailure === "unavailable"
|
|
498
|
+
? "ast-grep CLI not found. Install: npm i -D @ast-grep/cli"
|
|
499
|
+
: result.error?.message || "ast-grep CLI failed to start",
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
if (result.status !== 0) {
|
|
503
|
+
const stdout = result.stdout.trim();
|
|
504
|
+
const stderr = result.stderr.trim();
|
|
505
|
+
// ast-grep's linter-style contract: a rule with `severity: error`
|
|
506
|
+
// that MATCHES exits 1 with valid JSON matches on stdout (stderr
|
|
507
|
+
// carries "Scan succeeded and found error level diagnostics"). An
|
|
508
|
+
// exit code that means "scan succeeded with findings" must never be
|
|
509
|
+
// classified as a CLI failure — parse the matches. Only fall through
|
|
510
|
+
// to failure when the JSON isn't parseable (a real diagnostic).
|
|
511
|
+
if (result.status === 1 && stdout) {
|
|
512
|
+
const matches = tryParseNonZeroSgMatches(result);
|
|
513
|
+
if (matches) {
|
|
514
|
+
return {
|
|
405
515
|
matches,
|
|
406
516
|
totalMatches: matches.length,
|
|
407
517
|
truncated: false,
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
catch {
|
|
411
|
-
resolve({ ...empty(), error: "Failed to parse output" });
|
|
518
|
+
};
|
|
412
519
|
}
|
|
413
|
-
}
|
|
414
|
-
|
|
520
|
+
}
|
|
521
|
+
// ast-grep uses status 1 with no output for a genuine no-match in
|
|
522
|
+
// some CLI versions. Preserve that historical empty-result behavior;
|
|
523
|
+
// any stderr (including an invalid kind/YAML diagnostic) is a failure.
|
|
524
|
+
if (result.status === 1 && !stdout && !stderr)
|
|
525
|
+
return empty();
|
|
526
|
+
return {
|
|
527
|
+
...empty(),
|
|
528
|
+
error: this.formatPatternError(stderr || `Command failed with exit code ${result.status}`, args),
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
if (!result.stdout.trim())
|
|
532
|
+
return empty();
|
|
533
|
+
if (result.outputTruncated) {
|
|
534
|
+
return {
|
|
535
|
+
...empty(),
|
|
536
|
+
error: "Failed to parse output: output was truncated",
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
{
|
|
540
|
+
const matches = tryParseSgMatches(result.stdout);
|
|
541
|
+
if (matches) {
|
|
542
|
+
return {
|
|
543
|
+
matches,
|
|
544
|
+
totalMatches: matches.length,
|
|
545
|
+
truncated: false,
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
return { ...empty(), error: "Failed to parse output" };
|
|
549
|
+
}
|
|
415
550
|
}
|
|
416
551
|
// --- Shared helpers for temp-dir rule scans ---
|
|
417
552
|
prepareTempScan(ruleId, ruleYaml) {
|
|
@@ -423,12 +558,6 @@ export class SgRunner {
|
|
|
423
558
|
fs.writeFileSync(path.join(rulesSubdir, `${ruleId}.yml`), ruleYaml);
|
|
424
559
|
return { sessionDir, configFile };
|
|
425
560
|
}
|
|
426
|
-
parseScanOutput(output) {
|
|
427
|
-
if (!output.trim())
|
|
428
|
-
return [];
|
|
429
|
-
const items = JSON.parse(output);
|
|
430
|
-
return Array.isArray(items) ? items : [items];
|
|
431
|
-
}
|
|
432
561
|
cleanupTempScan(sessionDir) {
|
|
433
562
|
try {
|
|
434
563
|
fs.rmSync(sessionDir, { recursive: true, force: true });
|
|
@@ -437,7 +566,67 @@ export class SgRunner {
|
|
|
437
566
|
this.log(`Cleanup failed: ${err.message}`);
|
|
438
567
|
}
|
|
439
568
|
}
|
|
440
|
-
|
|
569
|
+
interpretScanResult(result, args) {
|
|
570
|
+
const failure = this.failureForSpawnResult(result);
|
|
571
|
+
if (failure) {
|
|
572
|
+
return {
|
|
573
|
+
matches: [],
|
|
574
|
+
status: result.status,
|
|
575
|
+
error: result.error?.message || "ast-grep CLI failed to start",
|
|
576
|
+
failure,
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
if (result.status !== 0) {
|
|
580
|
+
const stdout = result.stdout.trim();
|
|
581
|
+
const stderr = result.stderr.trim();
|
|
582
|
+
// ast-grep's linter-style contract: a rule with `severity: error`
|
|
583
|
+
// that MATCHES exits 1 with valid JSON matches on stdout (stderr
|
|
584
|
+
// carries "Scan succeeded and found error level diagnostics"). An
|
|
585
|
+
// exit code that means "scan succeeded with findings" must never be
|
|
586
|
+
// classified as a CLI failure — parse the matches. Only fall through
|
|
587
|
+
// to failure when the JSON isn't parseable (a real diagnostic).
|
|
588
|
+
if (result.status === 1 && stdout) {
|
|
589
|
+
const matches = tryParseNonZeroSgMatches(result);
|
|
590
|
+
if (matches) {
|
|
591
|
+
return { matches, status: result.status };
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
// Preserve ast-grep's status-1/no-output no-match convention. A
|
|
595
|
+
// diagnostic on stderr is never treated as a no-match.
|
|
596
|
+
if (result.status === 1 && !stdout && !stderr) {
|
|
597
|
+
return { matches: [], status: result.status };
|
|
598
|
+
}
|
|
599
|
+
return {
|
|
600
|
+
matches: [],
|
|
601
|
+
status: result.status,
|
|
602
|
+
error: this.formatPatternError(stderr || `ast-grep scan failed with exit code ${result.status}`, args),
|
|
603
|
+
failure: "cli-failure",
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
if (!result.stdout.trim())
|
|
607
|
+
return { matches: [], status: result.status };
|
|
608
|
+
if (result.outputTruncated) {
|
|
609
|
+
return {
|
|
610
|
+
matches: [],
|
|
611
|
+
status: result.status,
|
|
612
|
+
error: "Failed to parse ast-grep scan output: output was truncated",
|
|
613
|
+
failure: "parse-failure",
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
{
|
|
617
|
+
const matches = tryParseSgMatches(result.stdout);
|
|
618
|
+
if (matches) {
|
|
619
|
+
return { matches, status: result.status };
|
|
620
|
+
}
|
|
621
|
+
return {
|
|
622
|
+
matches: [],
|
|
623
|
+
status: result.status,
|
|
624
|
+
error: "Failed to parse ast-grep scan output: invalid JSON",
|
|
625
|
+
failure: "parse-failure",
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
async tempScanDetailedAsync(dir, ruleId, ruleYaml, timeout = DEFAULT_EXEC_TIMEOUT_MS, options = {}) {
|
|
441
630
|
const { sessionDir, configFile } = this.prepareTempScan(ruleId, ruleYaml);
|
|
442
631
|
try {
|
|
443
632
|
const { cmd: sgCmd, args: sgPre } = getSgCommand();
|
|
@@ -449,42 +638,81 @@ export class SgRunner {
|
|
|
449
638
|
"--json",
|
|
450
639
|
...sgExcludeArgsForProject(dir),
|
|
451
640
|
dir,
|
|
452
|
-
], {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
641
|
+
], {
|
|
642
|
+
timeout,
|
|
643
|
+
deadlineAt: options.deadlineAt,
|
|
644
|
+
signal: options.signal,
|
|
645
|
+
maxOutputBytes: MAX_SG_OUTPUT_BYTES,
|
|
646
|
+
});
|
|
647
|
+
return this.interpretScanResult(result, ["scan"]);
|
|
457
648
|
}
|
|
458
649
|
finally {
|
|
459
650
|
this.cleanupTempScan(sessionDir);
|
|
460
651
|
}
|
|
461
652
|
}
|
|
653
|
+
/** Backwards-compatible match-only wrapper for existing session scanners. */
|
|
654
|
+
async tempScanAsync(dir, ruleId, ruleYaml, timeout = DEFAULT_EXEC_TIMEOUT_MS, options = {}) {
|
|
655
|
+
const result = await this.tempScanDetailedAsync(dir, ruleId, ruleYaml, timeout, options);
|
|
656
|
+
return result.matches;
|
|
657
|
+
}
|
|
462
658
|
/**
|
|
463
659
|
* Run a rule scan with optional fix application.
|
|
464
660
|
* Dry-run: --json (returns matches for preview).
|
|
465
661
|
* Apply: --update-all (writes fixes defined in the YAML `fix:` field).
|
|
466
662
|
*/
|
|
467
|
-
async tempScanWithFixAsync(dir, ruleId, ruleYaml, applyFixes, timeout =
|
|
663
|
+
async tempScanWithFixAsync(dir, ruleId, ruleYaml, applyFixes, timeout = DEFAULT_EXEC_TIMEOUT_MS, options = {}) {
|
|
468
664
|
const { sessionDir, configFile } = this.prepareTempScan(ruleId, ruleYaml);
|
|
469
665
|
try {
|
|
470
666
|
const { cmd: sgCmd, args: sgPre } = getSgCommand();
|
|
667
|
+
const scanArgs = [
|
|
668
|
+
...sgPre,
|
|
669
|
+
"scan",
|
|
670
|
+
"--config",
|
|
671
|
+
configFile,
|
|
672
|
+
"--json",
|
|
673
|
+
...sgExcludeArgsForProject(dir),
|
|
674
|
+
dir,
|
|
675
|
+
];
|
|
676
|
+
const spawnOptions = {
|
|
677
|
+
timeout,
|
|
678
|
+
deadlineAt: options.deadlineAt,
|
|
679
|
+
signal: options.signal,
|
|
680
|
+
maxOutputBytes: MAX_SG_OUTPUT_BYTES,
|
|
681
|
+
};
|
|
471
682
|
if (!applyFixes) {
|
|
472
|
-
const result = await safeSpawnAsync(sgCmd,
|
|
473
|
-
|
|
474
|
-
return
|
|
683
|
+
const result = await safeSpawnAsync(sgCmd, scanArgs, spawnOptions);
|
|
684
|
+
const scan = this.interpretScanResult(result, ["scan"]);
|
|
685
|
+
return scan.failure || scan.error
|
|
686
|
+
? { matches: [], error: scan.error }
|
|
687
|
+
: { matches: scan.matches };
|
|
475
688
|
}
|
|
476
689
|
// Apply: capture matches BEFORE writing — once --update-all applies
|
|
477
690
|
// the fix the rule no longer matches, so a post-apply json pass would
|
|
478
691
|
// report zero even on a successful apply. Count first, then write.
|
|
479
|
-
const jsonResult = await safeSpawnAsync(sgCmd,
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
692
|
+
const jsonResult = await safeSpawnAsync(sgCmd, scanArgs, spawnOptions);
|
|
693
|
+
const scan = this.interpretScanResult(jsonResult, ["scan"]);
|
|
694
|
+
if (scan.failure || scan.error) {
|
|
695
|
+
return { matches: [], error: scan.error };
|
|
696
|
+
}
|
|
697
|
+
const applyResult = await safeSpawnAsync(sgCmd, [
|
|
698
|
+
...sgPre,
|
|
699
|
+
"scan",
|
|
700
|
+
"--config",
|
|
701
|
+
configFile,
|
|
702
|
+
"--update-all",
|
|
703
|
+
...sgExcludeArgsForProject(dir),
|
|
704
|
+
dir,
|
|
705
|
+
], spawnOptions);
|
|
706
|
+
if (applyResult.error ||
|
|
707
|
+
applyResult.failure ||
|
|
708
|
+
applyResult.status !== 0) {
|
|
709
|
+
return {
|
|
710
|
+
matches: [],
|
|
711
|
+
error: applyResult.error?.message ||
|
|
712
|
+
`ast-grep apply failed with exit code ${applyResult.status}`,
|
|
713
|
+
};
|
|
486
714
|
}
|
|
487
|
-
return { matches };
|
|
715
|
+
return { matches: scan.matches };
|
|
488
716
|
}
|
|
489
717
|
catch (err) {
|
|
490
718
|
return { matches: [], error: String(err) };
|