@herbertgao/pi-extensions 2026.8.4 → 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 +4 -4
- 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-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 +11 -12
- 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
|
@@ -28,6 +28,11 @@ pi-lens gives AI coding agents fast, language-aware feedback while they write/ed
|
|
|
28
28
|
- `/lens-map` — interactive HTML dependency map of the project
|
|
29
29
|
- Read-guard and edit-autopatch support to reduce bad edits
|
|
30
30
|
- Background security/dependency scans for opted-in projects
|
|
31
|
+
- Runtime health telemetry (`/lens-health`) including a bounded degradation
|
|
32
|
+
ledger for silently-degraded behavior (LSP breakers, formatter
|
|
33
|
+
skips/failures, idle evictions, timeouts)
|
|
34
|
+
- MCP server (experimental) so Claude Code or any MCP client can drive the
|
|
35
|
+
same diagnostics/read-substitute tools pi-lens exposes to pi
|
|
31
36
|
|
|
32
37
|
## Install
|
|
33
38
|
|
|
@@ -70,6 +75,8 @@ pi install git:github.com/apmantza/pi-lens
|
|
|
70
75
|
and formatters
|
|
71
76
|
- [Dependencies](docs/dependencies.md) — auto-install policy and external tools
|
|
72
77
|
- [Custom rules](docs/custom-rules.md) — project ast-grep and tree-sitter rules
|
|
78
|
+
- [MCP server](docs/mcp.md) — experimental MCP server for Claude Code and
|
|
79
|
+
other MCP clients
|
|
73
80
|
|
|
74
81
|
## Contributing
|
|
75
82
|
|
|
@@ -206,6 +213,12 @@ Thanks goes to these wonderful people:
|
|
|
206
213
|
</tr>
|
|
207
214
|
<tr>
|
|
208
215
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pppobear"><img src="https://avatars.githubusercontent.com/u/21952175?v=4" width="100px;" alt=""/><br /><sub><b>pppobear</b></sub></a><br /><a href="#bug-pppobear" title="Bug reports">🐛</a></td>
|
|
216
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/3choBoomer"><img src="https://avatars.githubusercontent.com/u/521828?v=4" width="100px;" alt=""/><br /><sub><b>Nathan Cooke</b></sub></a><br /><a href="#code-3choBoomer" title="Code">💻</a></td>
|
|
217
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/white-hat"><img src="https://avatars.githubusercontent.com/u/922988?v=4" width="100px;" alt=""/><br /><sub><b>Eli Stark</b></sub></a><br /><a href="#code-white-hat" title="Code">💻</a></td>
|
|
218
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marvtub"><img src="https://avatars.githubusercontent.com/u/33159023?v=4" width="100px;" alt=""/><br /><sub><b>Marvin Aziz</b></sub></a><br /><a href="#code-marvtub" title="Code">💻</a></td>
|
|
219
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mjfaga"><img src="https://avatars.githubusercontent.com/u/7584015?v=4" width="100px;" alt=""/><br /><sub><b>Mark Faga</b></sub></a><br /><a href="#code-mjfaga" title="Code">💻</a></td>
|
|
220
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/aeturnal"><img src="https://avatars.githubusercontent.com/u/219271200?v=4" width="100px;" alt=""/><br /><sub><b>aeturnal</b></sub></a><br /><a href="#code-aeturnal" title="Code">💻</a></td>
|
|
221
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/floatGray"><img src="https://avatars.githubusercontent.com/u/44295302?v=4" width="100px;" alt=""/><br /><sub><b>floatGray</b></sub></a><br /><a href="#code-floatGray" title="Code">💻</a></td>
|
|
209
222
|
</tr>
|
|
210
223
|
</tbody>
|
|
211
224
|
</table>
|
|
@@ -4,9 +4,16 @@ import * as path from "node:path";
|
|
|
4
4
|
import { applyWorkspaceEdit } from "./lsp/edits.js";
|
|
5
5
|
import { getLSPService } from "./lsp/index.js";
|
|
6
6
|
import { normalizeMapKey } from "./path-utils.js";
|
|
7
|
+
import { recordLspMutationBatch, } from "./lsp-mutation.js";
|
|
7
8
|
import { toRunnerDisplayPath } from "./dispatch/runner-context.js";
|
|
8
9
|
import { logActionableWarningsEvent } from "./actionable-warnings-logger.js";
|
|
9
10
|
import { getProjectDataDir } from "./file-utils.js";
|
|
11
|
+
import { commitDurableStore } from "./durable-store.js";
|
|
12
|
+
let beforeWarningStateLockForTests = null;
|
|
13
|
+
/** Test seam for a sibling process commit immediately before lock acquisition. */
|
|
14
|
+
export function _setBeforeWarningStateLockForTests(hook) {
|
|
15
|
+
beforeWarningStateLockForTests = hook;
|
|
16
|
+
}
|
|
10
17
|
function normalizeMessage(message) {
|
|
11
18
|
return message.replace(/\s+/g, " ").trim().toLowerCase();
|
|
12
19
|
}
|
|
@@ -53,10 +60,9 @@ function serializeAction(action) {
|
|
|
53
60
|
skipReason: safety.reason,
|
|
54
61
|
};
|
|
55
62
|
}
|
|
56
|
-
function
|
|
57
|
-
const statePath = path.join(getProjectDataDir(cwd), "cache", "actionable-warning-state.json");
|
|
63
|
+
function deserializeSuppressionState(contents) {
|
|
58
64
|
try {
|
|
59
|
-
const parsed = JSON.parse(
|
|
65
|
+
const parsed = JSON.parse(contents ?? "");
|
|
60
66
|
return parsed && typeof parsed === "object"
|
|
61
67
|
? parsed
|
|
62
68
|
: {};
|
|
@@ -65,23 +71,49 @@ function readSuppressionState(cwd) {
|
|
|
65
71
|
return {};
|
|
66
72
|
}
|
|
67
73
|
}
|
|
68
|
-
function
|
|
74
|
+
function readSuppressionState(cwd) {
|
|
69
75
|
const statePath = path.join(getProjectDataDir(cwd), "cache", "actionable-warning-state.json");
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
state.warnings[warning.id] = {
|
|
76
|
-
...existing,
|
|
77
|
-
status: existing.status ?? "active",
|
|
78
|
-
firstSeenAt: existing.firstSeenAt ?? now,
|
|
79
|
-
lastSeenAt: now,
|
|
80
|
-
seenCount: (existing.seenCount ?? 0) + 1,
|
|
81
|
-
};
|
|
76
|
+
try {
|
|
77
|
+
return deserializeSuppressionState(fs.readFileSync(statePath, "utf8"));
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return {};
|
|
82
81
|
}
|
|
82
|
+
}
|
|
83
|
+
function updateWarningState(cwd, warnings) {
|
|
84
|
+
const statePath = path.join(getProjectDataDir(cwd), "cache", "actionable-warning-state.json");
|
|
83
85
|
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
|
84
|
-
|
|
86
|
+
const hook = beforeWarningStateLockForTests;
|
|
87
|
+
beforeWarningStateLockForTests = null;
|
|
88
|
+
hook?.();
|
|
89
|
+
commitDurableStore({
|
|
90
|
+
path: statePath,
|
|
91
|
+
deserialize: deserializeSuppressionState,
|
|
92
|
+
merge: (state) => {
|
|
93
|
+
const now = new Date().toISOString();
|
|
94
|
+
state.warnings ??= {};
|
|
95
|
+
for (const warning of warnings) {
|
|
96
|
+
const existing = state.warnings[warning.id] ?? {};
|
|
97
|
+
state.warnings[warning.id] = {
|
|
98
|
+
...existing,
|
|
99
|
+
status: existing.status ?? "active",
|
|
100
|
+
firstSeenAt: existing.firstSeenAt ?? now,
|
|
101
|
+
lastSeenAt: now,
|
|
102
|
+
seenCount: (existing.seenCount ?? 0) + 1,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return state;
|
|
106
|
+
},
|
|
107
|
+
serialize: (state) => JSON.stringify(state, null, 2),
|
|
108
|
+
waitMs: 2_000,
|
|
109
|
+
retryMs: 10,
|
|
110
|
+
timeoutMessage: "timed out acquiring actionable warning store lock",
|
|
111
|
+
onContention: "skip-log",
|
|
112
|
+
logContention: () => logActionableWarningsEvent({
|
|
113
|
+
event: "warning_state_write_dropped",
|
|
114
|
+
metadata: { reason: "lock_contention" },
|
|
115
|
+
}),
|
|
116
|
+
});
|
|
85
117
|
}
|
|
86
118
|
function suppressionFor(cwd, id) {
|
|
87
119
|
const entry = readSuppressionState(cwd).warnings?.[id];
|
|
@@ -452,6 +484,8 @@ export async function applyConservativeActionableWarningFixes(args) {
|
|
|
452
484
|
skipped: [],
|
|
453
485
|
};
|
|
454
486
|
const changedFiles = new Set();
|
|
487
|
+
const appliedResults = [];
|
|
488
|
+
let failedCount = 0;
|
|
455
489
|
const lspService = getLSPService();
|
|
456
490
|
const maxFixes = Math.max(0, args.maxFixes ?? 5);
|
|
457
491
|
for (const file of args.report.files) {
|
|
@@ -504,12 +538,23 @@ export async function applyConservativeActionableWarningFixes(args) {
|
|
|
504
538
|
continue;
|
|
505
539
|
}
|
|
506
540
|
const edit = selected.edit;
|
|
507
|
-
const applied = await applyWorkspaceEdit(edit, args.cwd
|
|
541
|
+
const applied = await applyWorkspaceEdit(edit, args.cwd, args.mutationContext
|
|
542
|
+
? { mutationContext: { ...args.mutationContext, emitSummary: false } }
|
|
543
|
+
: undefined);
|
|
544
|
+
appliedResults.push(applied);
|
|
508
545
|
for (const changedFile of applied.files)
|
|
509
546
|
changedFiles.add(changedFile);
|
|
510
547
|
summary.applied++;
|
|
511
548
|
}
|
|
512
549
|
catch (err) {
|
|
550
|
+
failedCount++;
|
|
551
|
+
const partial = err
|
|
552
|
+
.appliedWorkspaceEdit;
|
|
553
|
+
if (partial) {
|
|
554
|
+
appliedResults.push(partial);
|
|
555
|
+
for (const changedFile of partial.files)
|
|
556
|
+
changedFiles.add(changedFile);
|
|
557
|
+
}
|
|
513
558
|
const message = err instanceof Error ? err.message : String(err);
|
|
514
559
|
args.dbg?.(`actionable_warnings_autofix failed for ${warning.id}: ${message}`);
|
|
515
560
|
summary.skipped.push({ id: warning.id, reason: "apply_failed" });
|
|
@@ -517,6 +562,16 @@ export async function applyConservativeActionableWarningFixes(args) {
|
|
|
517
562
|
}
|
|
518
563
|
}
|
|
519
564
|
summary.changedFiles = [...changedFiles];
|
|
565
|
+
if (args.mutationContext && (summary.considered > 0 || appliedResults.length > 0)) {
|
|
566
|
+
recordLspMutationBatch(args.mutationContext, {
|
|
567
|
+
results: appliedResults,
|
|
568
|
+
considered: summary.considered,
|
|
569
|
+
completed: summary.applied,
|
|
570
|
+
failedCount,
|
|
571
|
+
status: failedCount > 0 ? "failed" : appliedResults.length > 0 ? "success" : "skipped",
|
|
572
|
+
bookkeep: false,
|
|
573
|
+
});
|
|
574
|
+
}
|
|
520
575
|
return summary;
|
|
521
576
|
}
|
|
522
577
|
export function formatActionableWarningsAdvisory(report) {
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* AgentBehaviorClient for pi-lens
|
|
3
3
|
*
|
|
4
4
|
* Tracks tool call sequences and flags anti-patterns in real-time:
|
|
5
|
-
* - Blind writes: editing or writing without reading first
|
|
5
|
+
* - Blind writes: editing or writing without reading first (a same-session
|
|
6
|
+
* write/edit of the same path counts as file knowledge)
|
|
6
7
|
* - Thrashing: repeated identical tool calls with no progress
|
|
7
8
|
*
|
|
8
9
|
* No external dependencies — purely tracks tool call history.
|
|
@@ -23,6 +24,7 @@ export class AgentBehaviorClient {
|
|
|
23
24
|
lastToolTimestamp = 0;
|
|
24
25
|
// Per-file tracking
|
|
25
26
|
fileEditCount = new Map();
|
|
27
|
+
static FILE_EDIT_IDLE_MS = 30 * 60_000;
|
|
26
28
|
/**
|
|
27
29
|
* Record a tool call and return any warnings triggered.
|
|
28
30
|
* Called from tool_result handler.
|
|
@@ -63,7 +65,14 @@ export class AgentBehaviorClient {
|
|
|
63
65
|
// Check for blind writes
|
|
64
66
|
if (WRITE_OPS.has(toolName)) {
|
|
65
67
|
const recentWindow = this.toolHistory.slice(-BLIND_WRITE_WINDOW);
|
|
66
|
-
|
|
68
|
+
// A read in the window proves file knowledge; so does a same-session
|
|
69
|
+
// write/edit of THIS path — the agent authored the content, so the
|
|
70
|
+
// write-then-edit loop on a self-authored file is not a blind write.
|
|
71
|
+
const hasRecentRead = recentWindow.some((r) => READ_OPS.has(r.toolName) ||
|
|
72
|
+
(WRITE_OPS.has(r.toolName) &&
|
|
73
|
+
r.filePath !== undefined &&
|
|
74
|
+
normalizedPath !== null &&
|
|
75
|
+
normalizeMapKey(r.filePath) === normalizedPath));
|
|
67
76
|
if (!hasRecentRead && recentWindow.length > 0) {
|
|
68
77
|
// Count how many writes in the window without reads
|
|
69
78
|
const writesWithoutRead = recentWindow.filter((r) => WRITE_OPS.has(r.toolName)).length;
|
|
@@ -82,7 +91,15 @@ export class AgentBehaviorClient {
|
|
|
82
91
|
// Track edits per file
|
|
83
92
|
if (filePath) {
|
|
84
93
|
const key = normalizeMapKey(filePath);
|
|
85
|
-
this.fileEditCount.set(key,
|
|
94
|
+
this.fileEditCount.set(key, {
|
|
95
|
+
count: (this.fileEditCount.get(key)?.count ?? 0) + 1,
|
|
96
|
+
lastUsedAt: now,
|
|
97
|
+
});
|
|
98
|
+
for (const [pathKey, entry] of this.fileEditCount) {
|
|
99
|
+
if (now - entry.lastUsedAt > AgentBehaviorClient.FILE_EDIT_IDLE_MS) {
|
|
100
|
+
this.fileEditCount.delete(pathKey);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
86
103
|
}
|
|
87
104
|
}
|
|
88
105
|
// Add to history (keep last 50 entries)
|
|
@@ -104,7 +121,7 @@ export class AgentBehaviorClient {
|
|
|
104
121
|
* Get edit count for a file in this session.
|
|
105
122
|
*/
|
|
106
123
|
getEditCount(filePath) {
|
|
107
|
-
return this.fileEditCount.get(filePath) ?? 0;
|
|
124
|
+
return this.fileEditCount.get(normalizeMapKey(filePath))?.count ?? 0;
|
|
108
125
|
}
|
|
109
126
|
/**
|
|
110
127
|
* Reset state (e.g., on session start).
|
|
@@ -7,12 +7,13 @@
|
|
|
7
7
|
* Requires: npm install -D @ast-grep/cli
|
|
8
8
|
* Rules: ./rules/ directory
|
|
9
9
|
*/
|
|
10
|
+
import { createSubsystemLogger } from "./extension-log.js";
|
|
10
11
|
import * as fs from "node:fs";
|
|
11
12
|
import * as os from "node:os";
|
|
12
13
|
import * as path from "node:path";
|
|
13
14
|
import { AstGrepRuleManager } from "./ast-grep-rule-manager.js";
|
|
14
15
|
import { resolvePackagePath } from "./package-root.js";
|
|
15
|
-
import { SgRunner } from "./sg-runner.js";
|
|
16
|
+
import { SgRunner, } from "./sg-runner.js";
|
|
16
17
|
// --- Client ---
|
|
17
18
|
function extractDebugAst(raw) {
|
|
18
19
|
const lines = raw.split(/\r?\n/);
|
|
@@ -53,7 +54,7 @@ const VALIDATION_SNIPPETS = {
|
|
|
53
54
|
html: { ext: "html", source: "<main>pi-lens</main>\n" },
|
|
54
55
|
java: { ext: "java", source: "class Main { void run() {} }\n" },
|
|
55
56
|
javascript: { ext: "js", source: "const piLensValidate = 1;\n" },
|
|
56
|
-
json: { ext: "json", source:
|
|
57
|
+
json: { ext: "json", source: '{"piLensValidate":true}\n' },
|
|
57
58
|
kotlin: { ext: "kt", source: "fun main() {}\n" },
|
|
58
59
|
lua: { ext: "lua", source: "local pi_lens_validate = 1\n" },
|
|
59
60
|
php: { ext: "php", source: "<?php $piLensValidate = 1;\n" },
|
|
@@ -66,7 +67,10 @@ const VALIDATION_SNIPPETS = {
|
|
|
66
67
|
};
|
|
67
68
|
function validationSnippetFor(language) {
|
|
68
69
|
const key = language.toLowerCase().replace(/^"|"$/g, "");
|
|
69
|
-
return VALIDATION_SNIPPETS[key] ?? {
|
|
70
|
+
return (VALIDATION_SNIPPETS[key] ?? {
|
|
71
|
+
ext: key.replace(/[^a-z0-9_-]/gi, "") || "txt",
|
|
72
|
+
source: "pi_lens_validate\n",
|
|
73
|
+
});
|
|
70
74
|
}
|
|
71
75
|
function validateInputShape(value, maxChars, label) {
|
|
72
76
|
if (value.includes("\0"))
|
|
@@ -110,7 +114,7 @@ export class AstGrepClient {
|
|
|
110
114
|
? projectRuleDir
|
|
111
115
|
: resolvePackagePath(import.meta.url, "rules"));
|
|
112
116
|
this.log = verbose
|
|
113
|
-
? (
|
|
117
|
+
? createSubsystemLogger("ast-grep")
|
|
114
118
|
: () => { };
|
|
115
119
|
this.ruleManager = new AstGrepRuleManager(this.ruleDir, this.log);
|
|
116
120
|
this.runner = new SgRunner(verbose);
|
|
@@ -129,9 +133,18 @@ export class AstGrepClient {
|
|
|
129
133
|
const allMatches = [];
|
|
130
134
|
for (const scanPath of paths) {
|
|
131
135
|
if (apply) {
|
|
132
|
-
// Stale-preview check: dry-run first
|
|
133
|
-
|
|
134
|
-
|
|
136
|
+
// Stale-preview check: dry-run first. Preserve CLI failures instead of
|
|
137
|
+
// treating an invalid rule as a stale, no-match preview.
|
|
138
|
+
const preCheck = await this.tempScanDetailed(scanPath, ruleYaml);
|
|
139
|
+
if (preCheck.failure || preCheck.error) {
|
|
140
|
+
return {
|
|
141
|
+
matches: allMatches,
|
|
142
|
+
totalMatches: allMatches.length,
|
|
143
|
+
applied: false,
|
|
144
|
+
error: preCheck.error ?? "ast-grep preview failed",
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (preCheck.matches.length === 0) {
|
|
135
148
|
return {
|
|
136
149
|
matches: [],
|
|
137
150
|
totalMatches: 0,
|
|
@@ -162,12 +175,31 @@ export class AstGrepClient {
|
|
|
162
175
|
* Routes through sg scan --config rather than sg run -p.
|
|
163
176
|
* Each path is scanned independently; results are merged.
|
|
164
177
|
*/
|
|
165
|
-
async
|
|
178
|
+
async tempScanDetailed(dir, ruleYaml, options = {}) {
|
|
179
|
+
// Keep tests and older embedders that replace the runner with the historic
|
|
180
|
+
// match-only seam working. The production SgRunner always has the detailed
|
|
181
|
+
// method, so failures cannot be mistaken for an empty result there.
|
|
182
|
+
const detailed = this.runner.tempScanDetailedAsync;
|
|
183
|
+
if (detailed) {
|
|
184
|
+
return detailed.call(this.runner, dir, "agent-rule", ruleYaml, 30_000, options);
|
|
185
|
+
}
|
|
186
|
+
const matches = await this.runner.tempScanAsync(dir, "agent-rule", ruleYaml, 30_000, options);
|
|
187
|
+
return { matches, status: 0 };
|
|
188
|
+
}
|
|
189
|
+
async searchWithRule(ruleYaml, paths, options = {}) {
|
|
166
190
|
const allMatches = [];
|
|
167
191
|
for (const scanPath of paths) {
|
|
168
192
|
try {
|
|
169
|
-
const results = await this.
|
|
170
|
-
|
|
193
|
+
const results = await this.tempScanDetailed(scanPath, ruleYaml, options);
|
|
194
|
+
if (results.failure || results.error) {
|
|
195
|
+
return {
|
|
196
|
+
matches: allMatches,
|
|
197
|
+
totalMatches: allMatches.length,
|
|
198
|
+
error: results.error ||
|
|
199
|
+
`ast-grep scan failed (${results.failure ?? "unknown failure"})`,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
allMatches.push(...results.matches);
|
|
171
203
|
}
|
|
172
204
|
catch (err) {
|
|
173
205
|
return {
|
|
@@ -227,11 +259,24 @@ export class AstGrepClient {
|
|
|
227
259
|
if (options?.strictness)
|
|
228
260
|
args.push("--strictness", options.strictness);
|
|
229
261
|
args.push(tmpFile);
|
|
230
|
-
const result = await this.runner.execRaw(args);
|
|
262
|
+
const result = await this.runner.execRaw(args, 10_000, options);
|
|
231
263
|
const stderr = result.stderr.trim();
|
|
232
264
|
const stdout = result.stdout.trim();
|
|
233
265
|
if (result.error)
|
|
234
266
|
return { valid: false, error: result.error };
|
|
267
|
+
if (result.status !== 0 && !(result.status === 1 && !stderr)) {
|
|
268
|
+
return {
|
|
269
|
+
valid: false,
|
|
270
|
+
error: stderr ||
|
|
271
|
+
`ast-grep validation failed with exit code ${result.status}`,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
if (result.outputTruncated) {
|
|
275
|
+
return {
|
|
276
|
+
valid: false,
|
|
277
|
+
error: "ast-grep validation output was truncated",
|
|
278
|
+
};
|
|
279
|
+
}
|
|
235
280
|
if (stderrHasError(stderr))
|
|
236
281
|
return { valid: false, error: stderr };
|
|
237
282
|
const warning = stderr || stdout || undefined;
|
|
@@ -249,7 +294,7 @@ export class AstGrepClient {
|
|
|
249
294
|
}
|
|
250
295
|
}
|
|
251
296
|
}
|
|
252
|
-
async validateRule(ruleYaml) {
|
|
297
|
+
async validateRule(ruleYaml, options = {}) {
|
|
253
298
|
const shapeError = validateInputShape(ruleYaml, MAX_VALIDATE_RULE_CHARS, "rule");
|
|
254
299
|
if (shapeError)
|
|
255
300
|
return { valid: false, error: shapeError };
|
|
@@ -258,7 +303,14 @@ export class AstGrepClient {
|
|
|
258
303
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-lens-sg-rule-"));
|
|
259
304
|
try {
|
|
260
305
|
fs.writeFileSync(path.join(tmpDir, `snippet.${snippet.ext}`), snippet.source, "utf-8");
|
|
261
|
-
await this.
|
|
306
|
+
const result = await this.tempScanDetailed(tmpDir, ruleYaml, options);
|
|
307
|
+
if (result.failure || result.error) {
|
|
308
|
+
return {
|
|
309
|
+
valid: false,
|
|
310
|
+
error: result.error ||
|
|
311
|
+
`ast-grep rule validation failed (${result.failure ?? "unknown failure"})`,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
262
314
|
return { valid: true };
|
|
263
315
|
}
|
|
264
316
|
catch (err) {
|
|
@@ -332,7 +384,7 @@ export class AstGrepClient {
|
|
|
332
384
|
args.push("--strictness", options.strictness);
|
|
333
385
|
}
|
|
334
386
|
args.push(...paths);
|
|
335
|
-
const result = await this.runner.exec(args);
|
|
387
|
+
const result = await this.runner.exec(args, options);
|
|
336
388
|
return {
|
|
337
389
|
matches: result.matches,
|
|
338
390
|
totalMatches: result.totalMatches,
|
|
@@ -505,8 +557,8 @@ message: found
|
|
|
505
557
|
}
|
|
506
558
|
return exports;
|
|
507
559
|
}
|
|
508
|
-
formatMatches(matches, isDryRun = false, showModeIndicator = false) {
|
|
509
|
-
return this.runner.formatMatches(matches, isDryRun,
|
|
560
|
+
formatMatches(matches, isDryRun = false, showModeIndicator = false, maxItems = 50) {
|
|
561
|
+
return this.runner.formatMatches(matches, isDryRun, maxItems, showModeIndicator);
|
|
510
562
|
}
|
|
511
563
|
/**
|
|
512
564
|
* Format diagnostics for LLM consumption
|
|
@@ -21,7 +21,12 @@ const NODE_KIND_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
|
21
21
|
* Returns true when at least one structural-intent field is present.
|
|
22
22
|
*/
|
|
23
23
|
export function hasStructuralIntent(intent) {
|
|
24
|
-
return !!(intent.
|
|
24
|
+
return !!(intent.nodeKind ||
|
|
25
|
+
intent.insideKind ||
|
|
26
|
+
intent.hasKind ||
|
|
27
|
+
intent.hasDescendantKind ||
|
|
28
|
+
intent.follows ||
|
|
29
|
+
intent.precedes);
|
|
25
30
|
}
|
|
26
31
|
/**
|
|
27
32
|
* Synthesize an ast-grep YAML rule for replace operations.
|
|
@@ -33,26 +38,48 @@ export function synthesizeReplaceRule(intent) {
|
|
|
33
38
|
return `${base}fix: ${JSON.stringify(intent.rewrite)}\n`;
|
|
34
39
|
}
|
|
35
40
|
/**
|
|
36
|
-
* Synthesize an ast-grep YAML rule from a pattern
|
|
41
|
+
* Synthesize an ast-grep YAML rule from a pattern (or node kind) and
|
|
42
|
+
* structural constraints.
|
|
37
43
|
*
|
|
38
|
-
* The generated rule uses `stopBy: end` on `inside`
|
|
39
|
-
*
|
|
44
|
+
* The generated rule uses `stopBy: end` on `inside` and
|
|
45
|
+
* `hasDescendantKind`, so those two explicit recursive forms climb all
|
|
46
|
+
* ancestors/descendants. `hasKind` intentionally retains ast-grep's default
|
|
47
|
+
* immediate-child semantics for compatibility.
|
|
40
48
|
*
|
|
41
|
-
* @throws if pattern is
|
|
49
|
+
* @throws if neither pattern nor nodeKind is supplied, or both are supplied
|
|
42
50
|
*/
|
|
43
51
|
export function synthesizeRule(intent) {
|
|
44
|
-
|
|
52
|
+
const hasPattern = !!intent.pattern?.trim();
|
|
53
|
+
const hasNodeKind = !!intent.nodeKind?.trim();
|
|
54
|
+
if (hasPattern === hasNodeKind) {
|
|
55
|
+
throw new Error("provide exactly one of pattern or nodeKind");
|
|
56
|
+
}
|
|
57
|
+
const pattern = intent.pattern;
|
|
58
|
+
if (hasPattern && pattern)
|
|
59
|
+
assertSafePattern(pattern, "pattern");
|
|
60
|
+
if (intent.hasKind?.trim() && intent.hasDescendantKind?.trim()) {
|
|
61
|
+
throw new Error("hasKind and hasDescendantKind are mutually exclusive");
|
|
62
|
+
}
|
|
45
63
|
// Canonical language name for the YAML header (ast-grep is case-sensitive here).
|
|
46
64
|
const language = canonicalLanguage(intent.lang);
|
|
47
|
-
const rule =
|
|
48
|
-
|
|
49
|
-
|
|
65
|
+
const rule = hasNodeKind
|
|
66
|
+
? { kind: assertSafeNodeKind(intent.nodeKind ?? "", "nodeKind") }
|
|
67
|
+
: { pattern };
|
|
50
68
|
if (intent.insideKind) {
|
|
51
|
-
rule.inside = {
|
|
69
|
+
rule.inside = {
|
|
70
|
+
kind: assertSafeNodeKind(intent.insideKind, "insideKind"),
|
|
71
|
+
stopBy: "end",
|
|
72
|
+
};
|
|
52
73
|
}
|
|
53
74
|
if (intent.hasKind) {
|
|
54
75
|
rule.has = { kind: assertSafeNodeKind(intent.hasKind, "hasKind") };
|
|
55
76
|
}
|
|
77
|
+
if (intent.hasDescendantKind) {
|
|
78
|
+
rule.has = {
|
|
79
|
+
kind: assertSafeNodeKind(intent.hasDescendantKind, "hasDescendantKind"),
|
|
80
|
+
stopBy: "end",
|
|
81
|
+
};
|
|
82
|
+
}
|
|
56
83
|
if (intent.follows) {
|
|
57
84
|
assertSafePattern(intent.follows, "follows");
|
|
58
85
|
rule.follows = { pattern: intent.follows };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** Owns the generic atomic-write `.tmp-<pid>-<threadId>-<seq>` namespace. */
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { threadId } from "node:worker_threads";
|
|
5
|
+
let stageSeq = 0;
|
|
6
|
+
export const STAGE_TMP_PATTERN = /\.tmp-(0|[1-9]\d*)-(0|[1-9]\d*)-(0|[1-9]\d*)$/;
|
|
7
|
+
const STAGE_TMP_PID_PATTERN = /\.tmp-(0|[1-9]\d*)-(?:0|[1-9]\d*)-(?:0|[1-9]\d*)$/;
|
|
8
|
+
export function stagePathFor(targetPath) {
|
|
9
|
+
return `${targetPath}.tmp-${process.pid}-${threadId}-${stageSeq++}`;
|
|
10
|
+
}
|
|
11
|
+
export function stageOwnerPidFromName(name) {
|
|
12
|
+
if (!STAGE_TMP_PATTERN.test(name))
|
|
13
|
+
return undefined;
|
|
14
|
+
const match = STAGE_TMP_PID_PATTERN.exec(name);
|
|
15
|
+
if (!match)
|
|
16
|
+
return undefined;
|
|
17
|
+
const pid = Number(match[1]);
|
|
18
|
+
return Number.isSafeInteger(pid) && pid > 0 ? pid : undefined;
|
|
19
|
+
}
|
|
20
|
+
export async function sweepOwnStagingFiles(directories, options) {
|
|
21
|
+
const uniqueDirectories = [...new Set(directories)].filter(Boolean);
|
|
22
|
+
const result = { directories: uniqueDirectories.length, scanned: 0, removed: 0, liveSkipped: 0, truncated: false };
|
|
23
|
+
for (const directory of uniqueDirectories) {
|
|
24
|
+
let handle;
|
|
25
|
+
try {
|
|
26
|
+
handle = await fs.promises.opendir(directory);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
let reachedEntryCap = true;
|
|
33
|
+
for (let i = 0; i < options.maxEntries; i++) {
|
|
34
|
+
const entry = await handle.read();
|
|
35
|
+
if (entry === null) {
|
|
36
|
+
reachedEntryCap = false;
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
result.scanned++;
|
|
40
|
+
if (!entry.isFile())
|
|
41
|
+
continue;
|
|
42
|
+
const pid = stageOwnerPidFromName(entry.name);
|
|
43
|
+
if (pid === undefined)
|
|
44
|
+
continue;
|
|
45
|
+
if (pid === process.pid || options.isPidAlive(pid)) {
|
|
46
|
+
result.liveSkipped++;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
await fs.promises.rm(path.join(directory, entry.name), { force: true });
|
|
51
|
+
result.removed++;
|
|
52
|
+
}
|
|
53
|
+
catch { /* leave it for the next bounded sweep */ }
|
|
54
|
+
}
|
|
55
|
+
result.truncated ||= reachedEntryCap;
|
|
56
|
+
}
|
|
57
|
+
catch { /* best-effort directory scan */ }
|
|
58
|
+
finally {
|
|
59
|
+
await handle.close().catch(() => { });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|